WPF控件样式和模板全解析-ToggleButton控件
ToggleButton的默认样式和模板
依旧是使用Blend来查看ToggleButton的默认样式和模板
1 <Style x:Key="FocusVisual"> 2 <Setter Property="Control.Template"> 3 <Setter.Value> 4 <ControlTemplate> 5 <Rectangle Margin="2" StrokeDashArray="1 2" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" SnapsToDevicePixels="true" StrokeThickness="1"/> 6 </ControlTemplate> 7 </Setter.Value> 8 </Setter> 9 </Style> 10 <SolidColorBrush x:Key="Button.Static.Background" Color="#FFDDDDDD"/> 11 <SolidColorBrush x:Key="Button.Static.Border" Color="#FF707070"/> 12 <SolidColorBrush x:Key="Button.MouseOver.Background" Color="#FFBEE6FD"/> 13 <SolidColorBrush x:Key="Button.MouseOver.Border" Color="#FF3C7FB1"/> 14 <SolidColorBrush x:Key="Button.Pressed.Background" Color="#FFC4E5F6"/> 15 <SolidColorBrush x:Key="Button.Pressed.Border" Color="#FF2C628B"/> 16 <SolidColorBrush x:Key="Button.Disabled.Background" Color="#FFF4F4F4"/> 17 <SolidColorBrush x:Key="Button.Disabled.Border" Color="#FFADB2B5"/> 18 <SolidColorBrush x:Key="Button.Disabled.Foreground" Color="#FF838383"/> 19 <Style x:Key="ToggleButtonStyle1" TargetType="{x:Type ToggleButton}"> 20 <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/> 21 <Setter Property="Background" Value="{StaticResource Button.Static.Background}"/> 22 <Setter Property="BorderBrush" Value="{StaticResource Button.Static.Border}"/> 23 <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/> 24 <Setter Property="BorderThickness" Value="1"/> 25 <Setter Property="HorizontalContentAlignment" Value="Center"/> 26 <Setter Property="VerticalContentAlignment" Value="Center"/> 27 <Setter Property="Padding" Value="1"/> 28 <Setter Property="Template"> 29 <Setter.Value> 30 <ControlTemplate TargetType="{x:Type ToggleButton}"> 31 <Border x:Name="border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="true"> 32 <ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> 33 </Border> 34 <ControlTemplate.Triggers> 35 <Trigger Property="Button.IsDefaulted" Value="true"> 36 <Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/> 37 </Trigger> 38 <Trigger Property="IsMouseOver" Value="true"> 39 <Setter Property="Background" TargetName="border" Value="{StaticResource Button.MouseOver.Background}"/> 40 <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.MouseOver.Border}"/> 41 </Trigger> 42 <Trigger Property="IsPressed" Value="true"> 43 <Setter Property="Background" TargetName="border" Value="{StaticResource Button.Pressed.Background}"/> 44 <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Pressed.Border}"/> 45 </Trigger> 46 <Trigger Property="IsEnabled" Value="false"> 47 <Setter Property="Background" TargetName="border" Value="{StaticResource Button.Disabled.Background}"/> 48 <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Disabled.Border}"/> 49 <Setter Property="TextElement.Foreground" TargetName="contentPresenter" Value="{StaticResource Button.Disabled.Foreground}"/> 50 </Trigger> 51 <Trigger Property="IsChecked" Value="True"> 52 <Setter Property="Background" TargetName="border" Value="{StaticResource Button.Pressed.Background}"/> 53 <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Pressed.Border}"/> 54 </Trigger> 55 </ControlTemplate.Triggers> 56 </ControlTemplate> 57 </Setter.Value> 58 </Setter> 59 </Style>
拆解ToggleButton的样式和模板
1、首先是Tab 键切换控件焦点时看到的虚线焦点框样式,如下所示
1 <Style x:Key="FocusVisual"> 2 <Setter Property="Control.Template"> 3 <Setter.Value> 4 <ControlTemplate> 5 <Rectangle Margin="2" StrokeDashArray="1 2" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" SnapsToDevicePixels="true" StrokeThickness="1"/> 6 </ControlTemplate> 7 </Setter.Value> 8 </Setter> 9 </Style>
2、再就是一些资源画刷的定义
1 <SolidColorBrush x:Key="Button.Static.Background" Color="#FFDDDDDD"/> 2 <SolidColorBrush x:Key="Button.Static.Border" Color="#FF707070"/> 3 <SolidColorBrush x:Key="Button.MouseOver.Background" Color="#FFBEE6FD"/> 4 <SolidColorBrush x:Key="Button.MouseOver.Border" Color="#FF3C7FB1"/> 5 <SolidColorBrush x:Key="Button.Pressed.Background" Color="#FFC4E5F6"/> 6 <SolidColorBrush x:Key="Button.Pressed.Border" Color="#FF2C628B"/> 7 <SolidColorBrush x:Key="Button.Disabled.Background" Color="#FFF4F4F4"/> 8 <SolidColorBrush x:Key="Button.Disabled.Border" Color="#FFADB2B5"/> 9 <SolidColorBrush x:Key="Button.Disabled.Foreground" Color="#FF838383"/>
3、样式属性设置
ToggleButton样式属性这一块也不做详细介绍,可以参考以下链接学习ToggleButton的属性
https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/controls/togglebutton
https://learn.microsoft.com/zh-cn/dotnet/api/system.windows.controls.primitives.togglebutton
这里设置的基本都是一些比较常见的属性
1 <Style x:Key="ToggleButtonStyle1" TargetType="{x:Type ToggleButton}"> 2 <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/> 3 <Setter Property="Background" Value="{StaticResource Button.Static.Background}"/> 4 <Setter Property="BorderBrush" Value="{StaticResource Button.Static.Border}"/> 5 <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/> 6 <Setter Property="BorderThickness" Value="1"/> 7 <Setter Property="HorizontalContentAlignment" Value="Center"/> 8 <Setter Property="VerticalContentAlignment" Value="Center"/> 9 <Setter Property="Padding" Value="1"/> 10 </Style>
4、然后就是我们的重点,ToggleButton的模板部分
1 <ControlTemplate TargetType="{x:Type ToggleButton}"> 2 <Border x:Name="border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="true"> 3 <ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> 4 </Border> 5 <ControlTemplate.Triggers> 6 <Trigger Property="Button.IsDefaulted" Value="true"> 7 <Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/> 8 </Trigger> 9 <Trigger Property="IsMouseOver" Value="true"> 10 <Setter Property="Background" TargetName="border" Value="{StaticResource Button.MouseOver.Background}"/> 11 <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.MouseOver.Border}"/> 12 </Trigger> 13 <Trigger Property="IsPressed" Value="true"> 14 <Setter Property="Background" TargetName="border" Value="{StaticResource Button.Pressed.Background}"/> 15 <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Pressed.Border}"/> 16 </Trigger> 17 <Trigger Property="IsEnabled" Value="false"> 18 <Setter Property="Background" TargetName="border" Value="{StaticResource Button.Disabled.Background}"/> 19 <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Disabled.Border}"/> 20 <Setter Property="TextElement.Foreground" TargetName="contentPresenter" Value="{StaticResource Button.Disabled.Foreground}"/> 21 </Trigger> 22 <Trigger Property="IsChecked" Value="True"> 23 <Setter Property="Background" TargetName="border" Value="{StaticResource Button.Pressed.Background}"/> 24 <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Pressed.Border}"/> 25 </Trigger> 26 </ControlTemplate.Triggers> 27 </ControlTemplate>
可以看到ToggleButton的模板跟Button是一模一样的,就是Border里面套一个ContentPresenter,常规内容控件也都是这种层级结构。
层级结构如下:

但是ToggleButton和Button不一样就是它增加了复选功能。
这里我们可以把它理解为CheckBox的Button版本。
因为它也具备选中/未选中/不确定三种状态。
下面是选中/未选中状态的效果演示

因为在官方的样式里,没有“不确定”状态的相关逻辑,所以即使设置了IsThreeState="true",这个状态暂时也无法在UI上显示出来。
VisualState
| VisualState Name | VisualStateGroup Name | Description |
|---|---|---|
| Normal | CommonStates | 控件处于正常状态。 |
| MouseOver | CommonStates | 鼠标在控件上 |
| Pressed | CommonStates | 鼠标按下 |
| Disabled | CommonStates | 禁用 |
| Focused | FocusStates | 控件具有键盘焦点 |
| Unfocused | FocusStates | 控件没有键盘焦点 |
| Checked | CheckStates | 选中控件 |
| Unchecked | CheckStates | 未选中控件 |
| Indeterminate | CheckStates | 第三状态(不确定状态) |
| Valid | ValidationStates | 控件有效且没有验证错误 |
| InvalidFocused | ValidationStates | 控件存在验证错误,并且具有键盘焦点。 |
| InvalidUnfocused | ValidationStates | 控件存在验证错误,但没有键盘焦点。 |
使用VisualState的样式示例如下
1 <Style x:Key="ToggleButtonStyle2" TargetType="{x:Type ToggleButton}"> 2 <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/> 3 <Setter Property="Background" Value="{StaticResource Button.Static.Background}"/> 4 <Setter Property="BorderBrush" Value="{StaticResource Button.Static.Border}"/> 5 <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/> 6 <Setter Property="BorderThickness" Value="1"/> 7 <Setter Property="HorizontalContentAlignment" Value="Center"/> 8 <Setter Property="VerticalContentAlignment" Value="Center"/> 9 <Setter Property="Padding" Value="1"/> 10 <Setter Property="Template"> 11 <Setter.Value> 12 <ControlTemplate TargetType="{x:Type ToggleButton}"> 13 <!--使用默认模板结构--> 14 <Border x:Name="Border" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="true" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"> 15 <ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> 16 <VisualStateManager.VisualStateGroups> 17 <VisualStateGroup x:Name="CommonStates"> 18 <VisualState x:Name="Normal" /> 19 <!--鼠标划过--> 20 <VisualState x:Name="MouseOver"> 21 <Storyboard> 22 <!--设置背景颜色--> 23 <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" 24 Storyboard.TargetName="Border"> 25 <EasingColorKeyFrame KeyTime="0" 26 Value="{StaticResource ControlMouseOverColor}" /> 27 </ColorAnimationUsingKeyFrames> 28 <!--设置文字颜色--> 29 <ColorAnimation Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)" 30 Storyboard.TargetName="contentPresenter" 31 To="{StaticResource ControlLightColor}" Duration="0:0:0.2"></ColorAnimation> 32 </Storyboard> 33 </VisualState> 34 <VisualState x:Name="Pressed" /> 35 <!--禁用状态--> 36 <VisualState x:Name="Disabled"> 37 <Storyboard> 38 <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" 39 Storyboard.TargetName="Border"> 40 <EasingColorKeyFrame KeyTime="0" 41 Value="{StaticResource DisabledControlDarkColor}" /> 42 </ColorAnimationUsingKeyFrames> 43 <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" 44 Storyboard.TargetName="Border"> 45 <EasingColorKeyFrame KeyTime="0" 46 Value="{StaticResource DisabledBorderDarkColor}" /> 47 </ColorAnimationUsingKeyFrames> 48 </Storyboard> 49 </VisualState> 50 </VisualStateGroup> 51 <VisualStateGroup x:Name="CheckStates"> 52 <!--选中状态--> 53 <VisualState x:Name="Checked"> 54 <Storyboard> 55 <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" 56 Storyboard.TargetName="Border"> 57 <EasingColorKeyFrame KeyTime="0" 58 Value="{StaticResource ControlPressedColor}" /> 59 </ColorAnimationUsingKeyFrames> 60 <ColorAnimation Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)" 61 Storyboard.TargetName="contentPresenter" 62 To="{StaticResource ControlLightColor}" Duration="0:0:0.2"></ColorAnimation> 63 </Storyboard> 64 </VisualState> 65 <!--未选中状态--> 66 <VisualState x:Name="Unchecked" /> 67 <!--不确定状态--> 68 <VisualState x:Name="Indeterminate" /> 69 </VisualStateGroup> 70 </VisualStateManager.VisualStateGroups> 71 </Border> 72 </ControlTemplate> 73 </Setter.Value> 74 </Setter> 75 </Style>
运行效果

ToggleButton样式示例
在日常使用中,我们一般会改变一下ToggleButton的形态,使之变成一个Switch开关。
1 <Style x:Key="WD.DefaultToggleButton" TargetType="{x:Type ToggleButton}"> 2 <Setter Property="SnapsToDevicePixels" Value="True" /> 3 <Setter Property="UseLayoutRounding" Value="True" /> 4 <Setter Property="FrameworkElement.OverridesDefaultStyle" Value="True" /> 5 <Setter Property="Focusable" Value="False" /> 6 <Setter Property="Padding" Value="4,0" /> 7 <Setter Property="Background" Value="{DynamicResource WD.BaseBrush}" /> 8 <Setter Property="Template"> 9 <Setter.Value> 10 <ControlTemplate TargetType="{x:Type ToggleButton}"> 11 <Grid Background="Transparent"> 12 <Grid.ColumnDefinitions> 13 <ColumnDefinition Width="auto"/> 14 <ColumnDefinition/> 15 </Grid.ColumnDefinitions> 16 17 <!--外部边框--> 18 <Border x:Name="PART_Border" Width="40" Height="20" Background="{TemplateBinding Background}" BorderBrush="Transparent" BorderThickness="1" CornerRadius="10"> 19 <!--滚动的椭圆--> 20 <Ellipse x:Name="PART_Ellipse" Width="16" Height="16" Margin="2,0" HorizontalAlignment="Left" VerticalAlignment="Center" Fill="{DynamicResource WD.WindowTextBrush}"> 21 <Ellipse.RenderTransform> 22 <TranslateTransform /> 23 </Ellipse.RenderTransform> 24 </Ellipse> 25 </Border> 26 <!--内容显示--> 27 <ContentPresenter Grid.Column="1" Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" ContentTemplate="{TemplateBinding ContentTemplate}" Focusable="False" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" /> 28 29 <VisualStateManager.VisualStateGroups> 30 <VisualStateGroup x:Name="CommonStates"> 31 <!--正常状态--> 32 <VisualState x:Name="Normal"> 33 <Storyboard> 34 <!--设置边框颜色--> 35 <ColorAnimationUsingKeyFrames 36 Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" 37 Storyboard.TargetName="PART_Border"> 38 <EasingColorKeyFrame KeyTime="0" Value="Transparent" /> 39 </ColorAnimationUsingKeyFrames> 40 </Storyboard> 41 </VisualState> 42 <!--鼠标划过状态--> 43 <VisualState x:Name="MouseOver"> 44 <Storyboard> 45 <!--设置边框颜色--> 46 <ColorAnimationUsingKeyFrames 47 Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" 48 Storyboard.TargetName="PART_Border"> 49 <EasingColorKeyFrame KeyTime="0" Value="{StaticResource BorderLightColor}" /> 50 </ColorAnimationUsingKeyFrames> 51 </Storyboard> 52 </VisualState> 53 </VisualStateGroup> 54 <VisualStateGroup x:Name="CheckStates"> 55 <!--选中状态--> 56 <VisualState x:Name="Checked"> 57 <Storyboard> 58 <!--通过RenderTransform.TranslateTransform平移X坐标来实现选中时的动画效果--> 59 <!--对Ellipse进行动画--> 60 <DoubleAnimation Storyboard.TargetName="PART_Ellipse" Storyboard.TargetProperty="(Ellipse.RenderTransform).(TranslateTransform.X)" To="20" Duration="00:00:.3"> 61 <DoubleAnimation.EasingFunction> 62 <CubicEase EasingMode="EaseInOut"/> 63 </DoubleAnimation.EasingFunction> 64 </DoubleAnimation> 65 </Storyboard> 66 </VisualState> 67 <!--未选中状态--> 68 <VisualState x:Name="Unchecked"> 69 <Storyboard> 70 <DoubleAnimation Storyboard.TargetName="PART_Ellipse" Storyboard.TargetProperty="(Ellipse.RenderTransform).(TranslateTransform.X)" To="0" Duration="00:00:.3"> 71 <DoubleAnimation.EasingFunction> 72 <CubicEase EasingMode="EaseInOut"/> 73 </DoubleAnimation.EasingFunction> 74 </DoubleAnimation> 75 </Storyboard> 76 </VisualState> 77 <VisualState x:Name="Indeterminate" /> 78 </VisualStateGroup> 79 </VisualStateManager.VisualStateGroups> 80 </Grid> 81 <ControlTemplate.Triggers> 82 <!--选中状态--> 83 <!--防止动画导致显示效果不好,使用触发器来实现--> 84 <Trigger Property="IsChecked" Value="True"> 85 <Setter Property="Background" Value="{DynamicResource WD.PrimaryBrush}" /> 86 </Trigger> 87 </ControlTemplate.Triggers> 88 </ControlTemplate> 89 </Setter.Value> 90 </Setter> 91 </Style>
显示效果如下:

说明:“不确定”状态在日常使用中并不常见,所以这里就没有为它设置样式效果了,有需要的小伙伴可以自行补全<VisualState x:Name="Indeterminate" />部分的代码。
示例代码
https://github.com/zhaotianff/cnblog-demo-code/tree/main/WPFStyleAndTemplateDemo

浙公网安备 33010602011771号