Using C# winui 3 (.NET 6), I have written a custom control CommonButtonControl
made up of a Button
containing an Image
and a TextBlock
. The idea is that I can keep a consistent look and feel for all buttons in my app and simplify my xaml so I only need to add the CommonButtonControl
with values for ImageSource
, Text
, and Command
.
All working fine so far, but I would also like to add a Click
event which would get triggered on CommonButtonControl
when the component Button Click
event gets triggered. This would be to add consistency with the standard Button
control and allow me to catch the Click
event from CommonButtonControl
to do such things as showing a confirmation Dialog before doing anything else.
I have added a Click
event to my CommonButtonControl
class. However I have the xaml for CommonButtonControl
defined in a DataDictionary
(as per this documentation), and when I try to catch the Click
event for the component Button
, it will only call code that is in the code behind the DataDictionary
- not code that is in the CommonButtonControl
class. There does not appear to be any way to call the CommonButtonControl
object to tell it that the component Button
event has fired so that I can raise the CommonButtonControl Click
event. Any suggestions? It seems conceptually incorrect to add a RoutedEventHandler
as a DependencyProperty
.
Resource Dictionary
<ResourceDictionary
x:Class="MyApp.Views.DataTemplatesDictionary"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyApp.Views">
<Style TargetType="local:CommonButtonControl" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CommonButtonControl">
<Button
Command="{x:Bind Command, Mode=OneWay}"
Click="Button_Click">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="32"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Image
Grid.Column="0"
Width="32"
Height="32"
Source="{x:Bind ImageSource, Mode=OneWay}"
/>
<TextBlock
Margin="8,0,0,0"
Grid.Column="1"
VerticalAlignment="Center"
Text="{x:Bind Text, Mode=OneWay}"
Style="{ThemeResource BodyTextBlockStyle}"
/>
</Grid>
</Button>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Code behind resource dictionary
partial class DataTemplatesDictionary
{
public DataTemplatesDictionary()
{
InitializeComponent();
}
private void Button_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
//how to route this to the OnButtonClick method in CommonButtonControl?
}
}
CommonButtonControl class
public sealed class CommonButtonControl : Control
{
#region members
private DependencyProperty TextProperty = DependencyProperty.Register(
nameof(Text),
typeof(string),
typeof(CommonButtonControl),
new PropertyMetadata(default(string)));
private DependencyProperty ImageSourceProperty = DependencyProperty.Register(
nameof(ImageSource),
typeof(ImageSource),
typeof(CommonButtonControl),
new PropertyMetadata(default(ImageSource)));
private DependencyProperty CommandProperty = DependencyProperty.Register(
nameof(Command),
typeof(ICommand),
typeof(CommonButtonControl),
new PropertyMetadata(null));
public event RoutedEventHandler Click;
#endregion
#region properties
private void OnButtonClick(object sender, RoutedEventArgs e)
{
//how do I call this method when the component button click event is raised?
Click?.Invoke(this, new());
}
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public ImageSource ImageSource
{
get => (ImageSource)GetValue(ImageSourceProperty);
set => SetValue(ImageSourceProperty, value);
}
public ICommand Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
#endregion
public CommonButtonControl()
{
this.DefaultStyleKey = typeof(CommonButtonControl);
}
}
Usage 1 - via Command (works)
<local:CommonButtonControl
Command="{x:Bind _app.DeleteItemCommand}"
Text="Delete Item"
ImageSource="{x:Bind _app.DeleteIcon}"
/>
Usage 2 - via Click Event (how to get this to work?)
<local:CommonButtonControl
Click="CommonButtonControl_Click"
Text="Delete Item"
ImageSource="{x:Bind _app.DeleteIcon}"
/>
If you name your Button
, let's say "ButtonControl",
<Style TargetType="local:CustomButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CustomButton">
<Button x:Name="ButtonControl" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
then you can get it in your custom control class and subscribe to ButtonControl's Click
event:
CustomButton.cs
public sealed class CustomButton : Control
{
public CustomButton()
{
this.DefaultStyleKey = typeof(CustomButton);
}
public event RoutedEventHandler? Click;
private Button? ButtonControl { get; set; }
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (ButtonControl is not null)
{
ButtonControl.Click -= ButtonControl_Click;
}
if (GetTemplateChild(nameof(ButtonControl)) is Button button)
{
ButtonControl = button;
ButtonControl.Click += ButtonControl_Click;
}
}
private void ButtonControl_Click(object sender, RoutedEventArgs e)
{
Click?.Invoke(this, e);
}
}