WPF中的模板是非常强大的机制,可以灵活设置控件的外观和数据呈现方式,在复杂GUI场景下会发挥巨大作用。
模板种类
- 控件模板(ControlTemplate):定义控件的外观。
- 数据模板(DataTemplate):定义在内容控件或列表控件中显示数据的方式。
- 列表项面板模板(ItemsPanelTemplate):控制列表控件中各项的布局。

控件模板
控件模板也是控件的一个属性,模板绑定机制是定义控件模板最重要的概念之一,简单来说通过模板绑定可以拿到控件的属性。
其次,为一种控件定义控件模板时,需要了解该控件的可视化树,这是控件渲染的骨架。
可以通过IDE导出控件的现有模板,然后在此基础上做自定义调整。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <ControlTemplate x:Key="ButtonTemplate1" TargetType="{x:Type Button}"> <Border Name="Border" BorderThickness="2" CornerRadius="2" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"> <Grid> <Rectangle Name="FocusCue" Visibility="Hidden" Stroke="Black" StrokeThickness="2" StrokeDashArray="1 2" SnapsToDevicePixels="True"/> <ContentPresenter RecognizesAccessKey="True" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="{TemplateBinding Padding}"/> </Grid> </Border>
<ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="FocusCue" Property="Visibility" Value="Visible"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate>
<Button Content="Hello2" Template="{StaticResource ButtonTemplate1}"/>
|
如果为控件模板指定了x:Key
属性,那么就必须通过资源引用的方式使用控件模板;如果没有,就按照控件类型应用到所有控件。
数据模板
数据模板统一使用DataTemplate
定义,通常在三个位置可以使用数据模板,DataGrid
的CellTemplate
属性、列表控件的ItemTemplate
属性和内容控件的ContentTemplate
属性。
ContentTemplate
在WPF中,有很多控件继承自ContentControl
,这类控件都有Content
属性,一般为该属性指定字符串类型的值即可,例如为按钮指定显示文本。
但是,我们也可以为该属性绑定复杂类型的数据,通过ContentTemplate
指定这些数据的呈现方式。

如下所示,为Content
属性绑定一个类实例,将类实例通过模板显示到按钮上。
1 2 3 4
| [ObservableProperty] private Teacher _teacher;
Teacher = new() { Name = "陈老师", Age = 45 };
|
1 2 3 4 5 6 7 8 9
| <DataTemplate x:Key="ButtonContentTemplate1"> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Name}" FontWeight="Bold" /> <TextBlock Text="{Binding Age}" Margin="10,0,0,0" /> </StackPanel> </DataTemplate>
<Button Content="{Binding Teacher}" ContentTemplate="{StaticResource ButtonContentTemplate1}"/>
|

ItemTemplate
在WPF中,有很多控件继承自ItemsControl
,例如ComboBox
、ListBox
、TabControl
等,这里控件都有ItemsPanel
和ItemTemplate
属性,分别用来定义列表项布局模板和列表项数据模板。


项属性绑定是定义数据模板最重要的概念之一,绑定过程就是布局数据显示的过程。
如果为控件模板指定了x:Key
属性,那么就必须通过资源引用的方式使用数据模板;如果没有,就按照数据类型应用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <DataTemplate x:Key="ListBoxItemTemplate1" DataType="{x:Type s:String}"> <Border Margin="5" BorderThickness="1" CornerRadius="5" BorderBrush="SteelBlue"> <StackPanel Orientation="Vertical"> <TextBlock Text="项目:"/> <TextBlock FontWeight="Bold" Foreground="Red" Text="{Binding}"/> </StackPanel> </Border> <DataTemplate.Triggers> <DataTrigger Binding="{Binding}" Value="lihao"> <Setter Property="ListBoxItem.Foreground" Value="Black"/> </DataTrigger> </DataTemplate.Triggers> </DataTemplate>
<ItemsPanelTemplate x:Key="ListBoxItemsPanel1"> <StackPanel Orientation="Horizontal"/> </ItemsPanelTemplate>
<ListBox ItemTemplate="{DynamicResource ListBoxItemTemplate1}" ItemsSource="{Binding Names}" ItemsPanel="{DynamicResource ListBoxItemsPanel1}"> </ListBox>
|
以上示例同时展示了列表项布局模板的用法,ItemTemplate
用于设置一个项的数据显示外观;ItemsPanelTemplate
用于设置项与项之间的排列布置。

CellTemplate
通过单元格模板,可以将类的多个数据组合显示到一个单元格中,并且可以添加其它和数据无关的控件元素,例如按钮等。
1 2 3 4 5 6 7 8 9 10 11 12
| public class Teacher { public string Name { get; set; } public int Age { get; set; } }
[ObservableProperty] private List<Teacher> _teachers;
Teachers = new() { new(){Name="T1",Age=35 }, new(){Name="T2",Age=35 }, };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <DataGrid AutoGenerateColumns="False" Margin="10" CanUserAddRows="False" ItemsSource="{Binding Teachers}"> <DataGrid.Columns> <DataGridTemplateColumn Header="姓名-年龄"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Name}" FontWeight="Bold"/> <TextBlock Text="-"/> <TextBlock Text="{Binding Age}" FontWeight="SemiBold"/> </StackPanel> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn>
<DataGridTemplateColumn Header="操作"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <Button Content="修改"/> <Button Content="删除"/> </StackPanel> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> </DataGrid.Columns> </DataGrid>
|
样式
WPF中的样式用于封装对一种控件的多种属性设置,功能类似CSS。Style
有以下属性,其中BasedOn
、Setters
、TargetType
、Triggers
最为常用。

以下代码展示了前三个属性的使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <Style x:Key="BaseButton" TargetType="{x:Type Button}"> <Setter Property="BorderBrush"> <Setter.Value> <SolidColorBrush Color="OrangeRed"/> </Setter.Value> </Setter> <Setter Property="BorderThickness" Value="1"/> </Style>
<Style x:Key="ButtonStyle1" TargetType="{x:Type Button}" BasedOn="{StaticResource BaseButton}"> <Setter Property="Background"> <Setter.Value> <LinearGradientBrush> <GradientStop Color="BlueViolet" Offset="0"/> <GradientStop Color="CadetBlue" Offset="1"/> </LinearGradientBrush> </Setter.Value> </Setter> </Style>
<Style x:Key="ButtonStyle2" TargetType="{x:Type Button}" BasedOn="{StaticResource BaseButton}"> <Setter Property="Background"> <Setter.Value> <SolidColorBrush Color="GreenYellow"/> </Setter.Value> </Setter> </Style>
<Button Content="GradientColor" Margin="10" Style="{StaticResource ButtonStyle1}"/> <Button Content="SolidColor" Margin="10" Style="{StaticResource ButtonStyle2}"/>
|

触发器
样式触发器是非常实用的功能,可以监听依赖属性或被绑定数据的变化,动态调整样式中的属性。
1 2 3 4 5 6
| <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="FontStyle" Value="Italic"/> <Setter Property="FontWeight" Value="Bold"/> </Trigger> </Style.Triggers>
|
MultiTrigger
:监听多个依赖属性的变化,同时满足时才会触发。
DataTrigger
:监听被绑定数据的变化。以下实例中,绑定了一个后台数据Flag
,该触发器会监听这个后台数据的变化,从而触发样式修改。
1 2 3 4 5 6
| <Style.Triggers> <DataTrigger Binding="{Binding Flag}" Value="lihao"> <Setter Property="FontStyle" Value="Italic"/> <Setter Property="FontWeight" Value="Bold"/> </DataTrigger> </Style.Triggers>
|
MultiDataTrigger
:监听多个被绑定数据的变化,同时满足才会触发。
EventTrigger
:当事件发生时,会触发动画,用于制作炫酷的界面效果。
也可以在控件模板和数据模板上使用这些触发器,用来修改模板中某些元素的样式(可以为Setter
指定TargetName
属性,指明修改哪个控件的属性):
1 2 3 4 5 6 7 8 9 10 11 12 13
| <ControlTemplate TargetType="Button"> <Border x:Name="border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="1"> <ContentPresenter Content="{TemplateBinding Content}" /> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="border" Property="Background" Value="LightBlue"/> <Setter TargetName="border" Property="BorderBrush" Value="DarkBlue"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate>
|