一文读懂WPF系列之控件模版数据模板

WPF控件模版数据模板

之前的篇章虽然有提到过 控件模版和数据模板,但是这里还是要重点在讲解一下。

控件模板(ControlTemplate)

  1. 定义与用途

    作用:控件模板用于定义控件的可视化结构和行为,允许完全自定义控件的外观,而不影响其功能。例如,将默认的矩形按钮改为圆形,并自定义其悬停、点击效果。

  2. 应用场景:需要修改控件的视觉布局(如替换按钮的默认外观),或调整控件的组成部分(如添加动画、调整状态变化逻辑)。

  3. 关键特性
    TargetType:指定模板适用的控件类型(如Button、ListBox)。

    视觉状态(Visual States):通过VisualStateManager定义控件在不同状态(如MouseOver、Pressed)下的外观变化。

    必需元素:需保留控件的功能逻辑。例如,按钮的ContentPresenter用于显示内容,若省略则内容无法显示

定义方式

内联定义(直接写在ListBox中)

若将ControlTemplate直接内联到ListBox的Template属性中(如示例代码),则该模板​​仅作用于当前ListBox实例​​。这种写法不会影响其他ListBox控件的外观,属于局部应用

javascript 复制代码
<ListBox>
    <ListBox.Template>
        <ControlTemplate TargetType="ListBox">
            <!-- 自定义模板内容 -->
        </ControlTemplate>
    </ListBox.Template>
</ListBox>

特点​​:

无x:Key标识,直接绑定到当前控件的Template属性。

修改仅限当前控件,不影响其他ListBox实例

资源字典中定义

若将ControlTemplate定义在资源字典(如Window.Resources或全局资源文件)中,其作用范围由以下两种方式决定

  1. 显式指定x:Key
    通过x:Key标识的模板需手动引用,适用于需要复用的场景
    特点:
    必须通过StaticResource或DynamicResource显式引用。
    仅作用于手动引用该模板的ListBox。
javascript 复制代码
<Window.Resources>
    <ControlTemplate x:Key="CustomListBoxTemplate" TargetType="ListBox">
        <!-- 模板内容 -->
    </ControlTemplate>
</Window.Resources>

<ListBox Template="{StaticResource CustomListBoxTemplate}"/>
  1. 隐式样式(无x:Key)
    若定义模板时省略x:Key并指定TargetType="ListBox",该模板会隐式作用于所有同类型控件(即所有未显式指定模板的ListBox)
    特点:
    覆盖所有ListBox的默认样式。
    需通过TargetType匹配控件类型,适用于统一全局风格
javascript 复制代码
<Window.Resources>
    <Style TargetType="ListBox">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ListBox">
                    <!-- 全局模板内容 -->
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

定义方式区别

TemplateBinding 用法

TemplateBinding 是一个将模板内部元素的属性与目标控件属性动态关联的关键机制。

TemplateBinding Background​​ 用最通俗的话来说,它相当于在模板中对目标控件说:"你的Background属性值是多少,我就用多少,按下面图片列子就是 Ellipse的填充颜色根据模板button的背景来,设置红色那填充就是红色

javascript 复制代码
<Button Content="Click Me">
    <Button.Template>
        <ControlTemplate TargetType="Button">
            <Grid>
                 <!-- 椭圆背景绑定到按钮的Background -->
                <Ellipse Fill="{TemplateBinding Background}" Stroke="Black"/>
                <!-- 内容区域绑定到按钮的Content -->
                <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
            </Grid>
        </ControlTemplate>
    </Button.Template>
</Button>

对比普通Binding的区别

其实用普通的binding 也能实现 比如下面这俩个写法 效果一样的

javascript 复制代码
<Border Background="{TemplateBinding Background}"/>
<Border Background="{Binding Background, RelativeSource={RelativeSource TemplatedParent}}"/>

而TemplateBinding实际上是这种写法的简化版,​​只适用于控件模板内部​​,且语法更简洁

常见误区

  1. 误以为自动继承属性
    有人认为模板内部元素会自动继承宿主控件的属性,但实际必须显式使用TemplateBinding建立关联。
  2. 忽略目标类型限制
    如果宿主控件(如Button)没有某个属性(例如Fill),则模板内部无法通过TemplateBinding绑定该属性。
  3. 忘记TargetType
    在定义ControlTemplate时,必须指定TargetType(如TargetType="Button"),否则TemplateBinding可能无法正确解析属性

何时使用

  1. 需要动态同步属性:如按钮的Background、BorderThickness等需要根据宿主控件状态变化;
  2. 简化模板代码:避免冗长的RelativeSource绑定;
  3. 性能敏感场景:在需要高频更新属性的动画或复杂控件中优先使用

通过这种机制,WPF实现了控件逻辑与视觉表现的解耦------开发者只需修改宿主控件的属性,模板外观即可自动响应

数据模板(DataTemplate)

定义与用途

  1. 作用:数据模板定义数据对象的呈现方式,将数据属性映射到UI元素。例如,将Customer对象的姓名和地址以特定布局显示。
  2. 应用场景:需自定义数据在控件中的显示形式(如在ListBox中显示复杂数据项)。
  3. 关键特性
    DataType:指定模板适用的数据类型,支持隐式应用(无x:Key时自动匹配类型)。
  4. 绑定与布局:通过数据绑定({Binding})连接数据属性与UI元素,并定义布局结构(如StackPanel、Grid)

主要应用控件

ItemsControl及其派生控件

ListBox

为列表中的每个数据项定义布局,例如显示学生列表时自定义包含头像、姓名和分数的卡片布局

javascript 复制代码
<ListBox ItemsSource="{Binding Students}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <Image Source="{Binding Avatar}"/>
                <TextBlock Text="{Binding Name}"/>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>
​​ListView​​

类似ListBox,但支持更复杂的列定义。常用于数据表格展示,结合GridView定义列模板

javascript 复制代码
<ListView>
    <ListView.View>
        <GridView>
            <GridViewColumn Header="姓名" DisplayMemberBinding="{Binding Name}"/>
            <GridViewColumn Header="年龄" CellTemplate="{StaticResource AgeTemplate}"/>
        </GridView>
    </ListView.View>
</ListView>
ComboBox​​

自定义下拉项的外观,例如在选项中加入图标和详细描述

javascript 复制代码
<ComboBox ItemTemplate="{StaticResource PersonTemplate}"/>

ContentControl及其派生控件

Button、Label、Window​​

当控件内容为复杂对象时,用DataTemplate替代默认的ToString()显示。例如按钮内容显示为带图标的用户信息

javascript 复制代码
<Button Content="{Binding SelectedUser}">
    <Button.ContentTemplate>
        <DataTemplate>
            <StackPanel>
                <Image Source="{Binding Icon}"/>
                <TextBlock Text="{Binding UserName}"/>
            </StackPanel>
        </DataTemplate>
    </Button.ContentTemplate>
</Button>
TabItem​​

自定义标签页头的显示内容,如显示带状态指示器的标题

javascript 复制代码
<TabControl>
    <TabItem Header="{Binding TabHeader}">
        <TabItem.HeaderTemplate>
            <DataTemplate>
                <StackPanel>
                    <Ellipse Fill="{Binding StatusColor}" Width="10"/>
                    <TextBlock Text="{Binding Title}"/>
                </StackPanel>
            </DataTemplate>
        </TabItem.HeaderTemplate>
    </TabItem>
</TabControl>

其他特殊场景控件

TreeView​​

通过HierarchicalDataTemplate定义层级数据的显示,例如文件树结构

javascript 复制代码
<TreeView ItemsSource="{Binding Folders}">
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding SubItems}">
            <TextBlock Text="{Binding Name}"/>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>
​DataGrid(需手动配置)​​

虽然DataGrid默认使用列绑定,但可通过CellTemplate自定义单元格内容。例如在单元格内显示评分星级

javascript 复制代码
<DataGrid>
    <DataGrid.Columns>
        <DataGridTemplateColumn Header="评分">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <RatingControl Value="{Binding Score}"/>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

动态模板选择

核心方法​​

必须继承 DataTemplateSelector 类并重写 SelectTemplate 方法。该方法接收数据项和容器参数,返回匹配的 DataTemplate

  1. 设计多个 DataTemplate
    在 XAML 资源中为不同数据类型定义模板。例如,文本项用 TextBlock,颜色项用 Border 背景
javascript 复制代码
<Window.Resources>
    <DataTemplate x:Key="TextTemplate">
        <TextBlock Text="{Binding Ob.Name}" />
    </DataTemplate>
    <DataTemplate x:Key="ColorTemplate">
        <Border Background="{Binding Ob.Color}" Width="90" Height="30" />
    </DataTemplate>
</Window.Resources>
  1. 实现选择器类
    自定义选择器将数据属性与模板关联。例如,根据 TypeName 属性选择模板
javascript 复制代码
public class ListBoxTemplateSelector : DataTemplateSelector
{
    public DataTemplate TextTemplate { get; set; }
    public DataTemplate ColorTemplate { get; set; }
    
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        var data = item as DataModel;
        return data?.TypeName switch
        {
            "text" => TextTemplate,
            "color" => ColorTemplate,
            _ => base.SelectTemplate(item, container)
        };
    }
}
  1. 绑定到控件
    将选择器实例赋给控件的 ItemTemplateSelector 属性
javascript 复制代码
<ListBox ItemsSource="{Binding Items}">
    <ListBox.ItemTemplateSelector>
        <local:ListBoxTemplateSelector 
            TextTemplate="{StaticResource TextTemplate}"
            ColorTemplate="{StaticResource ColorTemplate}" />
    </ListBox.ItemTemplateSelector>
</ListBox>

典型应用场景

相关推荐
Marzlam15 小时前
一文读懂WPF布局
wpf
WineMonk19 小时前
.NET WPF 控件类分层结构
.net·wpf
界面开发小八哥3 天前
界面控件DevExpress WPF v25.1新功能预览 - 数据网格、报表性能增强
wpf·界面控件·devexpress·ui开发·.net 9
WineMonk3 天前
.NET WPF 可视化树(Visual Tree)
.net·wpf
ALex_zry4 天前
构建高可靠C++服务框架:从日志系统到任务调度器的完整实现
开发语言·c++·wpf
Bruce_Cheung5 天前
WPF旋转板栈设计一例
wpf·rack·tube·料盒·料管
leslie_xin5 天前
(原创)[开源][.Net Framework 4.5] SimpleMVVM(极简MVVM框架)更新 v1.1,增加NuGet包
c#·wpf
不知名君6 天前
WPF轮播图动画交互 动画缩放展示图片
wpf
Sitarrrr7 天前
【WPF】IOC控制反转的应用:弹窗但不互相调用ViewModel
设计模式·c#·wpf