You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 23:28:14