+ FrameworkTemplate // 基类
+ ControlTemplate // 控件模板, 会完全替代原来的模板
+ DataTemplate // 数据模板
+ HierarchicalDataTemplate // 树形数据模板
+ ItemsPanelTemplate // ItemsControl 的 Panel 模板
控件的 Template 属性 (ControlTemplate 类) 定义控件事个的外观
- ContentPresenter (在 ContentControl 的模板中表示内容)
- ItemsPresenter (在 ItemsControl 的模板中表示内容)
- ScrollContentPresenter (继承自 ContentPresenter, 在 ScrollViewer 的模板中表示内容)
控件的 ContentTemplate 属性 (DataTemplate 类)定义数据的外观
创建模板的指导原则
- 将颜色等一些共性的元素资源单独定义在 xaml 中
- 定义模板,但是将样式等抽出,只定义相应的外观与动作, 定义结构
- 定义样式,应用颜色等资源,并指定对应的模板及与动作, 定义样式
- 如果要换肤的话,只要覆盖对应的颜色资源,或是模板,或是样式
# 按约定创建 Themes/generic.xaml, 引用资源文件
# 创建资源文件, 创建样式 `<Style TargetType="{x:Type local:ColorPicker}"/>
# 静态构造函数中调用,指求使用 typeof(ColorPicker) 为 key 找默认资源,即上面的 {x:Type ColorPicker}
DefaultStyleKeyProperty.OverrideMetadata(typeof(ColorPicker), new
FrameworkPropertyMetadata(typeof(ColorPicker)));
# 重载 OnApplyTemplate(在 ControlTemplate 被应用时)进行一些初始化动作
ControlTemplate 找子控件
// 如果是 Template , 从控件的 Template.FindName 查找子控件
if (this.TemplateControl.Template.FindName("ListItemId", this.TemplateControl) is TextBox textBox)
{
textBox.Text = "test";
}
DataTemplate 找子控件
// 这里搭建的环境是 ListView CellTemplate 定制的 DataTemplate 中的一个子控件,引发一个事件,进行相应元素的查找
// 使用 listView.ItemContainerGenerator.ContainerFromItem(item) 查找绑定对象的列表项
// 作为测试,发送的源节点是 DataTemplate 中的一个 CheckBox 元素,
if (e.OriginalSource is not CheckBox checkBox)
{
return;
}
// 找到 DataTemplate 子元素的 TemplatedParent 就是那个 ContentPresenter
if (checkBox.TemplatedParent is not ContentPresenter contentPresenter)
{
return;
}
// 根据子元素,使用 VisualTreeHelper.GetParent 获取到当前选择的 ListViewItem
var curretnListViewItem = GetToAncestralElement<ListViewItem>(contentPresenter);
// 获取当前子元素的 DataContext, 因为我用 listView 举例子,所以这里是 ListView.ItemSources 中的对象, 也即当 listViewItem 前绑定的对象
if (contentPresenter.DataContext is not ListDisplayItem item)
{
return;
}
// 这里找到顶层的 ListView
var listView = GetToAncestralElement<ListView>(contentPresenter);
if (listView == null)
{
throw new InvalidOperationException("我的测试必须为父节点");
}
// 使用 ListView 辅助函数,找到绑定对象对应的 ListViewItem
var listViewItem = listView.ItemContainerGenerator.ContainerFromItem(item);
// 通过 VisualTree 获得的 ListViewItem 与使用绑定对象得到的 ListViewItem 是同一个对象
Debug.Assert(curretnListViewItem == listViewItem);
XMAL 例子
<ResourceDictionary>
<!-- 定义背景资源 -->
<SolidColorBrush x:Key="DefaultBackground" Color="White" />
<RadialGradientBrush x:Key="HighlightBackground" GradientOrigin="0.5, 0.3" RadiusX="1" RadiusY="5">
<GradientStop Offset="0" Color="White" />
<GradientStop Offset=".4" Color="Blue" />
</RadialGradientBrush>
<RadialGradientBrush x:Key="PressedBackground" GradientOrigin="0.5, 0.3" RadiusX="1" RadiusY="5">
<GradientStop Offset="0" Color="White" />
<GradientStop Offset="1" Color="Blue" />
</RadialGradientBrush>
<!-- 定义结构 -->
<ControlTemplate x:Key="ButtonTemplate" TargetType="Button">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="5">
<Grid>
<!-- 焦点 -->
<Rectangle
x:Name="FocusCue" Margin="3"
SnapsToDevicePixels="True" Stroke="Gray"
StrokeDashArray="1 2" StrokeThickness="1"
Visibility="Hidden" />
<!-- 内容 -->
<ContentPresenter Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" />
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="FocusCue" Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<!-- 定义样式 -->
<Style
x:Key="ButtonStyle"
BasedOn="{StaticResource {x:Type Button}}"
TargetType="Button">
<Setter Property="Template" Value="{StaticResource ButtonTemplate}" />
<!--<Setter Property="Background" Value="{DynamicResource DefaultBackground}" />-->
<Setter Property="Background" Value="{DynamicResource DefaultBackground1}" />
<Setter Property="BorderBrush" Value="Brown" />
<Setter Property="BorderThickness" Value="1" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{StaticResource HighlightBackground}" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="{StaticResource PressedBackground}" />
</Trigger>
</Style.Triggers>
</Style>
<!-- 定义为默认样式 -->
<Style BasedOn="{StaticResource ButtonStyle}" TargetType="Button" />
<ResourceDictionary.MergedDictionaries>
<!-- 引入更新样式,可以覆盖原样式, 注意要放在更接近于使用该样式的控件 -->
<ResourceDictionary Source="ResDemoDictionary.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>