WPF控件样式和模板全解析-Button控件
很早以前就想写介绍WPF控件样式的系列文章,等啊等,等到AI也出来了。
最近终于也是沉下心来写这样一系列文章了。
我们直接进入正题。
在阅读本系列文章前需要你对WPF的样式和模板功能有一定的了解,如果还不熟悉样式和模板功能,可以访问以下链接
https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/controls/styles-templates-overview
还可以参考以下示例代码
如何通过Blend获取控件的默认样式
安装Visual Studio时,只要钩选了.NET桌面开发,就会安装Blend组件。
通过Blend组件,我们可以快速获取控件的默认样式。

开始菜单搜索并启动Blend

创建一个WPF项目,并放置一个Button控件
1 <Grid> 2 <Button Content="Button" HorizontalAlignment="Left" Margin="388,172,0,0" VerticalAlignment="Top" Height="63" Width="129" Background="Pink"/> 3 </Grid>
然后在对象和时间线窗口上选择这个Button,右键选择编辑模板-编辑副本

这里可以选择新建一个资源字典 ,点击 确定

这个时候我们就可以看到Button控件的默认样式和模板
Dictionary1.xaml
1 <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 2 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> 3 4 <Style x:Key="FocusVisual"> 5 <Setter Property="Control.Template"> 6 <Setter.Value> 7 <ControlTemplate> 8 <Rectangle Margin="2" StrokeDashArray="1 2" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" SnapsToDevicePixels="true" StrokeThickness="1"/> 9 </ControlTemplate> 10 </Setter.Value> 11 </Setter> 12 </Style> 13 <SolidColorBrush x:Key="Button.Static.Background" Color="#FFDDDDDD"/> 14 <SolidColorBrush x:Key="Button.Static.Border" Color="#FF707070"/> 15 <SolidColorBrush x:Key="Button.MouseOver.Background" Color="#FFBEE6FD"/> 16 <SolidColorBrush x:Key="Button.MouseOver.Border" Color="#FF3C7FB1"/> 17 <SolidColorBrush x:Key="Button.Pressed.Background" Color="#FFC4E5F6"/> 18 <SolidColorBrush x:Key="Button.Pressed.Border" Color="#FF2C628B"/> 19 <SolidColorBrush x:Key="Button.Disabled.Background" Color="#FFF4F4F4"/> 20 <SolidColorBrush x:Key="Button.Disabled.Border" Color="#FFADB2B5"/> 21 <SolidColorBrush x:Key="Button.Disabled.Foreground" Color="#FF838383"/> 22 <Style x:Key="ButtonStyle1" TargetType="{x:Type Button}"> 23 <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/> 24 <Setter Property="Background" Value="{StaticResource Button.Static.Background}"/> 25 <Setter Property="BorderBrush" Value="{StaticResource Button.Static.Border}"/> 26 <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/> 27 <Setter Property="BorderThickness" Value="1"/> 28 <Setter Property="HorizontalContentAlignment" Value="Center"/> 29 <Setter Property="VerticalContentAlignment" Value="Center"/> 30 <Setter Property="Padding" Value="1"/> 31 <Setter Property="Template"> 32 <Setter.Value> 33 <ControlTemplate TargetType="{x:Type Button}"> 34 <Border x:Name="border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="true"> 35 <ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> 36 </Border> 37 <ControlTemplate.Triggers> 38 <Trigger Property="IsDefaulted" Value="true"> 39 <Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/> 40 </Trigger> 41 <Trigger Property="IsMouseOver" Value="true"> 42 <Setter Property="Background" TargetName="border" Value="{StaticResource Button.MouseOver.Background}"/> 43 <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.MouseOver.Border}"/> 44 </Trigger> 45 <Trigger Property="IsPressed" Value="true"> 46 <Setter Property="Background" TargetName="border" Value="{StaticResource Button.Pressed.Background}"/> 47 <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Pressed.Border}"/> 48 </Trigger> 49 <Trigger Property="IsEnabled" Value="false"> 50 <Setter Property="Background" TargetName="border" Value="{StaticResource Button.Disabled.Background}"/> 51 <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Disabled.Border}"/> 52 <Setter Property="TextElement.Foreground" TargetName="contentPresenter" Value="{StaticResource Button.Disabled.Foreground}"/> 53 </Trigger> 54 </ControlTemplate.Triggers> 55 </ControlTemplate> 56 </Setter.Value> 57 </Setter> 58 </Style> 59 </ResourceDictionary>
如何通过代码获取控件的默认模板
在前面的文章中,我介绍过这种方式
https://www.cnblogs.com/zhaotianff/p/8108151.html
使用下面的代码替换默认MainWindow.xaml.cs的代码即可
1 public partial class MainWindow : Window 2 { 3 ListBox lbox; 4 TextBox tbox; 5 Grid grid; 6 public MainWindow() 7 { 8 InitializeComponent(); 9 InitializeControl(); 10 LoadControlTemplate(); 11 12 } 13 14 /// <summary> 15 /// 创建界面 16 /// </summary> 17 private void InitializeControl() 18 { 19 grid = new Grid(); 20 ColumnDefinition col1 = new ColumnDefinition(); 21 ColumnDefinition col2 = new ColumnDefinition(); 22 col1.Width = GridLength.Auto; 23 grid.ColumnDefinitions.Add(col1); 24 grid.ColumnDefinitions.Add(col2); 25 26 //List 27 lbox = new ListBox(); 28 lbox.SelectionChanged += (a, b) => { ShowControlTemplate(); }; 29 //TextBox 30 tbox = new TextBox(); 31 tbox.VerticalScrollBarVisibility = ScrollBarVisibility.Auto; 32 tbox.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto; 33 34 grid.Children.Add(lbox); 35 grid.Children.Add(tbox); 36 37 Grid.SetColumn(lbox, 0); 38 Grid.SetColumn(tbox, 1); 39 40 this.Content = grid; 41 } 42 43 private void LoadControlTemplate() 44 { 45 Type type = typeof(System.Windows.Controls.Control); 46 List<Type> controlType = new List<Type>(); 47 System.Reflection.Assembly assembly = System.Reflection.Assembly.GetAssembly(typeof(System.Windows.Controls.Control)); 48 49 foreach (Type item in assembly.GetTypes()) 50 { 51 if (item.IsSubclassOf(type) && !item.IsAbstract && item.IsPublic) 52 { 53 controlType.Add(item); 54 } 55 } 56 lbox.ItemsSource = controlType; 57 } 58 59 private void ShowControlTemplate() 60 { 61 try 62 { 63 Type type = (Type)lbox.SelectedItem; 64 System.Reflection.ConstructorInfo info = type.GetConstructor(Type.EmptyTypes); 65 Control control = (Control)info.Invoke(null); 66 control.Visibility = Visibility.Collapsed; 67 grid.Children.Add(control); 68 ControlTemplate template = control.Template; 69 70 System.Xml.XmlWriterSettings setting = new System.Xml.XmlWriterSettings(); 71 setting.Indent = true; 72 StringBuilder sb = new StringBuilder(); 73 System.Xml.XmlWriter writer = System.Xml.XmlWriter.Create(sb, setting); 74 System.Windows.Markup.XamlWriter.Save(template, writer); 75 tbox.Text = sb.ToString(); 76 grid.Children.Remove(control); 77 78 } 79 catch (Exception ex) 80 { 81 tbox.Text = ex.Message; 82 } 83 } 84 }
拆解Button样式和模板
1、首先我们看到资源定义部分,这部分主要是定义控件样式里用到的画刷
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"/>
2、然后就是默认样式的属性设置部分,这部分也不用过多介绍。
如果对于某个属性不了解的,可以直接查看Button控件的官方文档
https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/controls/button
1 <Style x:Key="ButtonStyle1" TargetType="{x:Type Button}"> 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>
3、接下来就是Button控件的样式的核心部分,Button的控件模板
<Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Button}"> <Border x:Name="border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="true"> <ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> </Border> </ControlTemplate> </Setter.Value> </Setter>
Button控件的模板比较简单,就是在一个Border里嵌套了一个ContentPresenter元素


这里我们详细介绍一下ContentPresenter这个元素
在前面的文章中,我介绍过WPF中的内容控件
https://www.cnblogs.com/zhaotianff/p/13608111.html
像Label、Button、TabItem等都是内容控件
ContentPresenter类就是用于显示 ContentControl内容的类
严格意义上来说,ContentPresenter并不是最终的呈现类,它类似于一个外壳,真正显示内容时,它实际的逻辑如下:
1. 如果设置了ContentPresenter上的ContentTemplate属性,ContentPresenter会将该DataTemplate应用到Content属性,随后呈现生成的UIElement及其子元素(如果有)
2. 如果设置了ContentPresenter上的ContentTemplateSelector属性,ContentPresenter会选用匹配的DataTemplate应用到Content属性,随后呈现生成的UIElement及其子元素(如果有)。
关于控件模板选择器可以参考:https://www.cnblogs.com/zhaotianff/p/18380995
3. 如果存在与Content的类型关联的DataTemplate,ContentPresenter会将该DataTemplate应用到Content属性,随后呈现生成的`UIElement`及其子元素(如果有)。
4. 如果Content是UIElement对象,则直接显示该UIElement。若该UIElement已存在父元素,将抛出异常。
5. 如果存在可将Content类型转换为UIElement的TypeConverter,ContentPresenter会使用该类型转换器,并显示转换后得到的UIElement。
6. 如果存在可将Content类型转换为字符串的TypeConverter,ContentPresenter会使用该类型转换器,并创建一个TextBlock承载该字符串,显示此TextBlock。
7. 如果内容为XmlElement,会在TextBlock中显示其InnerText属性的值。
8. ContentPresenter会对Content调用ToString方法,创建TextBlock承载该方法返回的字符串,并显示该TextBlock。
4、然后就是一些设置状态的触发器
1 <ControlTemplate.Triggers> 2 <!--默认状态--> 3 <Trigger Property="IsDefaulted" Value="true"> 4 <Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/> 5 </Trigger> 6 <!--鼠标划过状态--> 7 <Trigger Property="IsMouseOver" Value="true"> 8 <Setter Property="Background" TargetName="border" Value="{StaticResource Button.MouseOver.Background}"/> 9 <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.MouseOver.Border}"/> 10 </Trigger> 11 <!--鼠标按下状态--> 12 <Trigger Property="IsPressed" Value="true"> 13 <Setter Property="Background" TargetName="border" Value="{StaticResource Button.Pressed.Background}"/> 14 <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Pressed.Border}"/> 15 </Trigger> 16 <!--启用状态--> 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 </ControlTemplate.Triggers>
一个扁平化风格的按钮样式和模板案例
源码来自:https://github.com/WPFDevelopersOrg/WPFDevelopers/blob/master/src/WPFDevelopers.Net40/Themes/Theme.xaml (欢迎大家使用WPFDevelopers控件库)
1 <SolidColorBrush x:Key="WD.PrimaryBrush" Color="#409EFF" /> 2 <SolidColorBrush x:Key="WD.PrimaryMouseOverBrush" Color="#EBF4FF" /> 3 <SolidColorBrush x:Key="WD.BackgroundBrush" Color="#FFFFFF" /> 4 <SolidColorBrush x:Key="WD.BaseBrush" Color="#DCDFE6" /> 5 <SolidColorBrush x:Key="WD.RegularTextBrush" Color="#606266" /> 6 <SolidColorBrush x:Key="WD.WindowTextBrush" Color="#FFFFFF" /> 7 8 <Style x:Key="WD.DefaultButton" TargetType="{x:Type Button}"> 9 <Setter Property="FocusVisualStyle" Value="{x:Null}" /> 10 <Setter Property="FrameworkElement.OverridesDefaultStyle" Value="True" /> 11 <Setter Property="HorizontalContentAlignment" Value="Center" /> 12 <Setter Property="VerticalContentAlignment" Value="Center" /> 13 <Setter Property="BorderThickness" Value="1" /> 14 <Setter Property="Cursor" Value="Hand" /> 15 <Setter Property="Background" Value="{DynamicResource WD.BackgroundBrush}" /> 16 <Setter Property="BorderBrush" Value="{DynamicResource WD.BaseBrush}" /> 17 <Setter Property="Foreground" Value="{DynamicResource WD.RegularTextBrush}" /> 18 <Setter Property="Template"> 19 <Setter.Value> 20 <ControlTemplate TargetType="{x:Type Button}"> 21 <Border x:Name="PART_Border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="1" SnapsToDevicePixels="True" > 22 <ContentPresenter x:Name="PART_ContentPresenter" Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True" TextElement.Foreground="{TemplateBinding Foreground}" /> 23 </Border> 24 <ControlTemplate.Triggers> 25 <Trigger Property="IsFocused" Value="True"> 26 <Setter TargetName="PART_Border" Property="UIElement.Opacity" Value=".9" /> 27 </Trigger> 28 <Trigger Property="IsPressed" Value="True"> 29 <Setter Property="BorderBrush" Value="{DynamicResource WD.PrimaryBrush}" /> 30 <Setter Property="Background" Value="{DynamicResource WD.PrimaryMouseOverBrush}" /> 31 <Setter Property="Foreground" Value="{DynamicResource WD.PrimaryBrush}" /> 32 </Trigger> 33 <Trigger Property="IsMouseOver" Value="True"> 34 <Setter Property="BorderBrush" Value="{DynamicResource WD.PrimaryBrush}" /> 35 <Setter Property="Background" Value="{DynamicResource WD.PrimaryMouseOverBrush}" /> 36 <Setter Property="Foreground" Value="{DynamicResource WD.PrimaryBrush}" /> 37 </Trigger> 38 </ControlTemplate.Triggers> 39 </ControlTemplate> 40 </Setter.Value> 41 </Setter> 42 </Style>
这个样式是在原生样式的基础上改变了一些配色,理解起来并不困难。
核心的控件模板如下:
1 <Border x:Name="PART_Border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="1" SnapsToDevicePixels="True" > 2 <ContentPresenter x:Name="PART_ContentPresenter" Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True" TextElement.Foreground="{TemplateBinding Foreground}" /> 3 </Border>
通过触发器控制各状态下的显示效果
1 <ControlTemplate.Triggers> 2 <!--焦点状态--> 3 <Trigger Property="IsFocused" Value="True"> 4 <Setter TargetName="PART_Border" Property="UIElement.Opacity" Value=".9" /> 5 </Trigger> 6 <!--鼠标按下状态--> 7 <Trigger Property="IsPressed" Value="True"> 8 <Setter Property="BorderBrush" Value="{DynamicResource WD.PrimaryBrush}" /> 9 <Setter Property="Background" Value="{DynamicResource WD.PrimaryMouseOverBrush}" /> 10 <Setter Property="Foreground" Value="{DynamicResource WD.PrimaryBrush}" /> 11 </Trigger> 12 <!--鼠标划过状态--> 13 <Trigger Property="IsMouseOver" Value="True"> 14 <Setter Property="BorderBrush" Value="{DynamicResource WD.PrimaryBrush}" /> 15 <Setter Property="Background" Value="{DynamicResource WD.PrimaryMouseOverBrush}" /> 16 <Setter Property="Foreground" Value="{DynamicResource WD.PrimaryBrush}" /> 17 </Trigger> 18 </ControlTemplate.Triggers>
运行效果如下:

使用VisualState为Button增加动画效果
在前面的文章中,我介绍过WPF里控件的VisualState功能
如果对这一块的功能不了解,可以访问以下链接:https://www.cnblogs.com/zhaotianff/p/13254430.html
Button控件提供的VisualState如下
| VisualState名称 | VisualStateGroup名称 | Description |
|---|---|---|
| Normal | CommonStates | 默认状态 |
| MouseOver | CommonStates | 鼠标划过状态 |
| Pressed | CommonStates | 鼠标按下状态 |
| Disabled | CommonStates | 禁用状态 |
| Focused | FocusStates | 焦点状态 |
| Unfocused | FocusStates | 失去焦点状态 |
| Valid | ValidationStates | 该控件使用了Validation类,且Validation.HasError附加属性为false。 |
| InvalidFocused | ValidationStates | Validation.HasError 附加属性为 true,且该控件具有焦点。 |
| InvalidUnfocused | ValidationStates | Validation.HasError 附加属性为 true,且该控件未获得焦点。 |
我们来看一下如何使用,完整示例代码如下:
1 <Style x:Key="WD.WindowButtonStyle" TargetType="{x:Type Button}"> 2 <Setter Property="Foreground" Value="#303133" /> 3 <Setter Property="Padding" Value="3" /> 4 <Setter Property="Margin" Value="0" /> 5 <Setter Property="MinWidth" Value="30" /> 6 <Setter Property="MinHeight" Value="28" /> 7 <Setter Property="BorderThickness" Value="1" /> 8 <Setter Property="Background" Value="White"></Setter> 9 <Setter Property="Template"> 10 <Setter.Value> 11 <ControlTemplate TargetType="{x:Type Button}"> 12 <Border Background="{TemplateBinding Background}" x:Name="Border"> 13 <ContentPresenter x:Name="PART_ContentPresenter" Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" Opacity="0.7" /> 14 <VisualStateManager.VisualStateGroups> 15 <VisualStateGroup x:Name="CommonStates"> 16 <!--默认状态--> 17 <VisualState x:Name="Normal"> 18 <Storyboard> 19 <ColorAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="Background.Color" To="White"/> 20 <ColorAnimation Storyboard.TargetName="PART_ContentPresenter" Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)" To="#606266"/> 21 </Storyboard> 22 </VisualState> 23 <!--鼠标划过状态--> 24 <VisualState x:Name="MouseOver"> 25 <Storyboard> 26 <ColorAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="Background.Color" To="#409EFF"/> 27 <ColorAnimation Storyboard.TargetName="PART_ContentPresenter" Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)" To="White"/> 28 </Storyboard> 29 </VisualState> 30 </VisualStateGroup> 31 </VisualStateManager.VisualStateGroups> 32 </Border> 33 </ControlTemplate> 34 </Setter.Value> 35 </Setter> 36 </Style>
这里我只增加了Normal和MouseOver两种状态(其它状态可自行添加),并使用了两个ColorAnimation对背景和文字颜色进行动画
1 <VisualStateManager.VisualStateGroups> 2 <VisualStateGroup x:Name="CommonStates"> 3 <!--默认状态--> 4 <VisualState x:Name="Normal"> 5 <Storyboard> 6 <!--背景颜色动画--> 7 <ColorAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="Background.Color" To="White"/> 8 <!--文字颜色动画--> 9 <ColorAnimation Storyboard.TargetName="PART_ContentPresenter" Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)" To="#606266"/> 10 </Storyboard> 11 </VisualState> 12 <!--鼠标划过状态--> 13 <VisualState x:Name="MouseOver"> 14 <Storyboard> 15 <!--背景颜色动画--> 16 <ColorAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="Background.Color" To="#409EFF"/> 17 <!--文字颜色动画--> 18 <ColorAnimation Storyboard.TargetName="PART_ContentPresenter" Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)" To="White"/> 19 </Storyboard> 20 </VisualState> 21 </VisualStateGroup> 22 </VisualStateManager.VisualStateGroups>
运行效果如下:

示例代码:
https://github.com/zhaotianff/cnblog-demo-code/tree/main/WPFStyleAndTemplateDemo
参考资料:
.net - What's the difference between ContentControl and ContentPresenter? - Stack Overflow

浙公网安备 33010602011771号