WPF:如何为继承外观的自定义按钮添加触发器并保留默认样式
问题解答
问题概述
基于Button封装了CustomButton,自带圆角边框等自定义ControlTemplate和依赖属性。现在想给它添加DataTrigger做视图验证,但直接在<controls:CustomButton.Triggers>中添加无效,必须重新定义Style——但新定义的<Style TargetType="{x:Type controls:CustomButton}">会覆盖CustomButton的默认样式。不想用BasedOn继承样式,也不想将控件套入容器(效率低下),询问是否能保留CustomButton默认样式的同时添加触发器。
相关代码
使用CustomButton的XAML
<controls:CustomButton Command="{Binding ValidateCommand}" Content="Validate" Color="{StaticResource PrimarySolidColorBrush}"> <controls:CustomButton.Style> <!-- 此处会覆盖CustomButton的默认样式 --> <Style TargetType="{x:Type controls:CustomButton}"> <Setter Property="IsEnabled" Value="True" /> <Style.Triggers> <!-- 后续将添加更多触发器 --> <DataTrigger Binding="{Binding ElementName=Amount, Path=(Validation.HasError)}" Value="True"> <Setter Property="IsEnabled" Value="False" /> </DataTrigger> </Style.Triggers> </Style> </controls:CustomButton.Style> </controls:CustomButton>
CustomButton的XAML定义
<Button x:Class="Software.Desktop.Controls.CustomButton" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Software.Desktop.Controls" Padding="4" Cursor="Hand" FontSize="12" FontWeight="bold" Foreground="White"> <Button.Resources> <!-- 样式 --> <Style BasedOn="{StaticResource TooltipStyle}" TargetType="{x:Type ToolTip}"> <Setter Property="Foreground" Value="{Binding Color, RelativeSource={RelativeSource AncestorType={x:Type local:CustomButton}}}" /> </Style> <!-- 控件模板 --> <ControlTemplate x:Key="ButtonControlTemplate" TargetType="{x:Type Button}"> <Border x:Name="Bd" Background="{Binding Color, RelativeSource={RelativeSource AncestorType={x:Type local:CustomButton}}}" BorderBrush="{Binding Color, RelativeSource={RelativeSource AncestorType={x:Type local:CustomButton}}}" BorderThickness="0" CornerRadius="8"> <ContentPresenter Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" /> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="Bd" Property="Background" Value="White" /> <Setter TargetName="Bd" Property="BorderBrush" Value="{Binding Color, RelativeSource={RelativeSource AncestorType={x:Type local:CustomButton}}}" /> <Setter TargetName="Bd" Property="BorderThickness" Value="4" /> <Setter TargetName="Bd" Property="TextElement.Foreground" Value="{Binding Color, RelativeSource={RelativeSource AncestorType={x:Type local:CustomButton}}}" /> </Trigger> <Trigger Property="IsEnabled" Value="False"> <Setter TargetName="Bd" Property="Background" Value="LightGray" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Button.Resources> <Button.Style> <Style TargetType="{x:Type Button}"> <Setter Property="Template" Value="{StaticResource ButtonControlTemplate}" /> </Style> </Button.Style> </Button>
CustomButton的C#代码
namespace Software.Desktop.Controls { /// <summary></summary> public partial class CustomButton : Button { #region 私有静态只读成员 public static readonly DependencyProperty ColorProperty = DependencyProperty.Register( nameof(Color), typeof(Brush), typeof(CustomButton), new FrameworkPropertyMetadata(Application.Current.Resources["PrimarySolidColorBrush"])); #endregion #region 公共属性 public Brush Color { get => GetValue(ColorProperty) as Brush; set => SetValue(ColorProperty, value); } #endregion #region 公共事件 public event EventHandler Clicked; #endregion #region 公共构造函数 /// <summary>默认构造函数</summary> public CustomButton() { Width = 64; Height = 32; InitializeComponent(); Click += (sender, e) => { Clicked?.Invoke(this, EventArgs.Empty); }; } #endregion } }
解决方案
最优方案:使用Interaction.Triggers添加触发器
无需重新定义Style,完全保留CustomButton默认样式,直接通过System.Windows.Interactivity库的扩展触发器实现逻辑:
<controls:CustomButton Command="{Binding ValidateCommand}" Content="Validate" Color="{StaticResource PrimarySolidColorBrush}"> <i:Interaction.Triggers> <i:DataTrigger Binding="{Binding ElementName=Amount, Path=(Validation.HasError)}" Value="True"> <ei:ChangePropertyAction PropertyName="IsEnabled" Value="False" /> </i:DataTrigger> </i:Interaction.Triggers> </controls:CustomButton>
需要在XAML头部添加对应命名空间:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
备选方案:修改CustomButton内部样式结构
将CustomButton内部的样式目标类型改为自身,并设置为隐式样式(移除x:Key),这样外部可以通过BasedOn继承默认样式:
<Button.Resources> <!-- 保留原有Tooltip样式和ControlTemplate --> <Style TargetType="{x:Type local:CustomButton}"> <Setter Property="Template" Value="{StaticResource ButtonControlTemplate}" /> <Setter Property="Padding" Value="4"/> <Setter Property="Cursor" Value="Hand"/> <Setter Property="FontSize" Value="12"/> <Setter Property="FontWeight" Value="Bold"/> <Setter Property="Foreground" Value="White"/> </Style> </Button.Resources>
外部使用时,通过BasedOn继承默认样式并添加触发器:
<controls:CustomButton.Style> <Style TargetType="{x:Type controls:CustomButton}" BasedOn="{StaticResource {x:Type controls:CustomButton}}"> <Setter Property="IsEnabled" Value="True" /> <Style.Triggers> <DataTrigger Binding="{Binding ElementName=Amount, Path=(Validation.HasError)}" Value="True"> <Setter Property="IsEnabled" Value="False" /> </DataTrigger> </Style.Triggers> </Style> </controls:CustomButton.Style>
总结
如果完全不想使用BasedOn,优先选择Interaction.Triggers方案,无需修改控件内部结构,直接扩展触发器逻辑且保留默认样式。
内容的提问来源于stack exchange,提问作者Ludovic Wagner
相关产品推荐
相关产品推荐

