WinUI 3中如何将自定义控件内部Button的Click事件路由到父控件
我使用C# WinUI 3(.NET 6)编写了名为CommonButtonControl的自定义控件,内部由包含Image和TextBlock的Button组成,目的是统一应用按钮外观并简化XAML编写,目前Command绑定功能正常。
我希望为该控件添加Click事件,当内部Button的Click事件触发时,CommonButtonControl也能触发对应事件,与标准Button保持一致性,方便执行显示确认对话框等操作。
我已在CommonButtonControl类中添加Click事件,但控件XAML模板定义在ResourceDictionary中,内部Button的Click事件仅能触发ResourceDictionary后台代码方法,无法调用CommonButtonControl类的OnButtonClick方法来触发自定义控件的Click事件,且我认为将RoutedEventHandler作为依赖属性在概念上不合理,请问有何解决方法?
推荐采用方法一,将控件逻辑封装在自定义控件类内部,提升复用性;若需保留现有ResourceDictionary后台代码逻辑,可使用方法二。
方法一:通过OnApplyTemplate获取内部按钮并订阅事件
这种方式将事件处理逻辑完全封装在CommonButtonControl类中,无需依赖ResourceDictionary的后台代码,是自定义控件的标准实现方式。
步骤1:修改CommonButtonControl类
重写OnApplyTemplate方法,获取模板内的内部按钮并订阅其Click事件,触发自定义控件的Click事件:
public sealed class CommonButtonControl : Control { #region 依赖属性定义 public static readonly DependencyProperty TextProperty = DependencyProperty.Register( nameof(Text), typeof(string), typeof(CommonButtonControl), new PropertyMetadata(default(string))); public static readonly DependencyProperty ImageSourceProperty = DependencyProperty.Register( nameof(ImageSource), typeof(ImageSource), typeof(CommonButtonControl), new PropertyMetadata(default(ImageSource))); public static readonly DependencyProperty CommandProperty = DependencyProperty.Register( nameof(Command), typeof(ICommand), typeof(CommonButtonControl), new PropertyMetadata(null)); #endregion #region 事件 public event RoutedEventHandler Click; #endregion #region 属性 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() { DefaultStyleKey = typeof(CommonButtonControl); } private Button _innerButton; protected override void OnApplyTemplate() { base.OnApplyTemplate(); // 移除之前的事件订阅,避免重复绑定 if (_innerButton != null) { _innerButton.Click -= InnerButton_Click; } // 获取模板内命名的内部按钮 _innerButton = GetTemplateChild("InnerButton") as Button; if (_innerButton != null) { // 订阅内部按钮的Click事件 _innerButton.Click += InnerButton_Click; } } private void InnerButton_Click(object sender, RoutedEventArgs e) { // 触发自定义控件的Click事件 Click?.Invoke(this, e); } }
步骤2:修改ResourceDictionary中的模板
给内部按钮添加x:Name="InnerButton",并移除原有的Click="Button_Click"绑定:
<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 x:Name="InnerButton" Command="{x:Bind Command, Mode=OneWay}"> <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>
步骤3:清理ResourceDictionary后台代码
删除DataTemplatesDictionary类中的Button_Click方法,因为不再需要。
方法二:在ResourceDictionary后台代码中触发自定义控件事件
若需保留现有模板的Click绑定,可在ResourceDictionary后台代码中找到当前的CommonButtonControl实例并触发其事件:
修改DataTemplatesDictionary类:
partial class DataTemplatesDictionary { public DataTemplatesDictionary() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { var innerButton = sender as Button; // 获取模板的目标控件(即CommonButtonControl) var commonButton = innerButton?.TemplatedParent as CommonButtonControl; if (commonButton != null) { // 触发自定义控件的Click事件 commonButton.Click?.Invoke(commonButton, e); } } }
修改完成后,即可正常使用Click事件绑定:
<local:CommonButtonControl Click="CommonButtonControl_Click" Text="Delete Item" ImageSource="{x:Bind _app.DeleteIcon}" />
内容的提问来源于stack exchange,提问作者Woz9

