wpf 自定义模板
+ 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>
上一篇
下一篇