WPF、控件模板(ControlTemplate)和数据模板(DataTemplate)

embedded/2025/1/16 19:23:45/

前言

在 WPF 中,控件种类丰富且功能非常完善。一个显著的优点是 WPF 提供了强大的自定义能力和灵活的用户界面表现,能够满足各种复杂的应用需求。其中,ControlTemplate 和 DataTemplate 是两个非常重要的概念,分别用于自定义控件的外观和定义数据的展示方式。

控件模板(ControlTemplate)

ControlTemplate 主要用于自定义控件的外观,而不改变控件的行为。由于WPF默认控件的样式不是特别美观,ControlTemplate 它允许自定义控件的视觉元素,例如按钮的外观、文本框的边框等等,而不需要改变控件的内部逻辑或数据。ControlTemplate 也是一个 XAML 模板。

基本语法

<ControlTemplate x:Key="CustomerTemplate" TargetType="{x:Type 控件类型}"></ControlTemplate>

ControlTemplate有两个重要的属性,VisualTree另一个是Triggers。

  • VisualTree,定义模板的视觉树结构,其实我们就是使用这个属性来描述控件的外观的。
  • Triggers,触发器列表,里面包含一些触发器Trigger,我们可以定制这个触发器列表来使控件对外界的刺激发生反应,比如鼠标经过时文本变成粗体等。

下面我就基于比较常用的Button来演示一下,如何自定义Button样式。

<Window x:Class="WpfApp1.ControlTemplateWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:WpfApp1"mc:Ignorable="d"Title="ControlTemplateWindow" Height="450" Width="800"><Window.Resources></Window.Resources><Grid><Grid.RowDefinitions><RowDefinition Height="40"/></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition Width="100"/><ColumnDefinition Width="100"/><ColumnDefinition Width="100"/><ColumnDefinition Width="100"/><ColumnDefinition Width="100"/><ColumnDefinition Width="100"/><ColumnDefinition Width="100"/></Grid.ColumnDefinitions><Button Grid.Column="0" Grid.Row="0" Content="默认按钮" Margin="0 10 0 0"></Button><Button Grid.Column="2" Grid.Row="0" Content="自定义按钮1" Foreground="White" FontSize="12" FontWeight="Bold" Margin="0 10 0 0" Cursor="Hand"><Button.Style><Style TargetType="Button"><Setter Property="Background" Value="#409eff"/><Setter Property="BorderBrush" Value="White"/></Style></Button.Style><Button.Template><ControlTemplate TargetType="Button"><Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" CornerRadius="7"><Grid><!--Rectangle是用于绘制矩形,Fill填充颜色,RadiusX/Y 是绘制矩形的CornerRadius,Opacity透明度 --><Rectangle Name="CustomerOverlay" Fill="White" Opacity="0" RadiusX="7" RadiusY="7" /><Rectangle Name="CustomerPressed" Fill="Black" Opacity="0" RadiusX="7" RadiusY="7" /><!-- 按钮的内容 --><ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/></Grid></Border><!-- 触发器 --><ControlTemplate.Triggers><!-- 点击按钮时触发 --><Trigger Property="IsPressed" Value="True"><Setter TargetName="CustomerPressed" Property="Opacity" Value="0.3"/></Trigger><!-- 鼠标移动到按钮时触发 --><Trigger Property="Button.IsMouseOver" Value="True"><Setter TargetName="CustomerOverlay" Property="Opacity" Value="0.2"/></Trigger></ControlTemplate.Triggers></ControlTemplate></Button.Template></Button></Grid>
</Window>

代码看着有点多,其实很简单,复制到自己本地,就可以运行,大家可以试一下。

简单解释下代码:

  • ControlTemplate:我们定义了一个简单的ControlTemplate,该模板的目标类型是Button。
  • Grid:定义一个布局(类似HTML的div),该布局中就是具体的自定义样式的内容。里面我加了两个图层,一个是在鼠标悬停时展示,另一个是鼠标点击按钮时展示。我在Grid外面还包了一个Border边框。
  • ContentPresenter:这是一个占位符,用来显示按钮的内容。它确保我们可以在按钮中显示文本或其他控件。
  • TemplateBinding:用于将模板中控件的某个属性绑定到其父控件的某个属性。在此示例中,BorderBrush绑定到按钮的BorderBrush、Background属性。
  • Triggers:触发器,具体每个控件的触发器会不同,大家可以参考微软官网中WPF具体每个控件模板。

需要注意的是,ControlTemplate中的内容一般放到Window.Resources中。

具体执行效果如下所示:

当鼠标悬停在按钮上时,按钮会显示一个高亮图层;点击按钮后,会展示一个新的图层效果。这是基于element-plus按钮组件的样式用WPF实现的,下面我把element-plus上比较常用的按钮组件,用WPF实现看看效果。

<Window x:Class="WpfApp1.ControlTemplateWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:WpfApp1"mc:Ignorable="d"Title="ControlTemplateWindow" Height="450" Width="800"><Window.Resources><ControlTemplate x:Key="CustomButtonTemplate" TargetType="Button"><Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" CornerRadius="7"><Grid><!-- Rectangle用于绘制矩形,Fill填充颜色,RadiusX/Y绘制矩形的CornerRadius,Opacity透明度 --><Rectangle Name="CustomerOverlay" Fill="White" Opacity="0" RadiusX="7" RadiusY="7" /><Rectangle Name="CustomerPressed" Fill="Black" Opacity="0" RadiusX="7" RadiusY="7" /><!-- 按钮的内容 --><ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/></Grid></Border><!-- 触发器 --><ControlTemplate.Triggers><!-- 点击按钮时触发 --><Trigger Property="IsPressed" Value="True"><Setter TargetName="CustomerPressed" Property="Opacity" Value="0.3"/></Trigger><!-- 鼠标移动到按钮时触发 --><Trigger Property="Button.IsMouseOver" Value="True"><Setter TargetName="CustomerOverlay" Property="Opacity" Value="0.2"/></Trigger></ControlTemplate.Triggers></ControlTemplate><ControlTemplate  x:Key="CustomIconButtonTemplate" TargetType="Button"><Grid><Ellipse Fill="{TemplateBinding Background}" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}"/><Image Source="/images/收藏.png" Width="18" /><Ellipse Name="CustomerOverlay" Fill="White" Opacity="0" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}"/><Ellipse Name="CustomerPressed" Fill="Black" Opacity="0" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}"/></Grid><!-- 触发器 --><ControlTemplate.Triggers><!-- 点击按钮时触发 --><Trigger Property="IsPressed" Value="True"><Setter TargetName="CustomerPressed" Property="Opacity" Value="0.3"/></Trigger><!-- 鼠标移动到按钮时触发 --><Trigger Property="Button.IsMouseOver" Value="True"><Setter TargetName="CustomerOverlay" Property="Opacity" Value="0.2"/></Trigger></ControlTemplate.Triggers></ControlTemplate><ControlTemplate x:Key="CustomImageButtonTemplate" TargetType="Button"><Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" CornerRadius="7"><Grid><Rectangle Name="CustomerOverlay" Fill="White" Opacity="0" RadiusX="7" RadiusY="7" /><Rectangle Name="CustomerPressed" Fill="Black" Opacity="0" RadiusX="7" RadiusY="7" /><!-- 按钮的内容 --><WrapPanel HorizontalAlignment="Center" VerticalAlignment="Center"><ContentPresenter Content="下载" VerticalAlignment="Center" Margin="0 0 8 0"/><Image Source="/images/下载.png" Width="15" /></WrapPanel></Grid></Border><!-- 触发器 --><ControlTemplate.Triggers><!-- 点击按钮时触发 --><Trigger Property="IsPressed" Value="True"><Setter TargetName="CustomerPressed" Property="Opacity" Value="0.3"/></Trigger><!-- 鼠标移动到按钮时触发 --><Trigger Property="Button.IsMouseOver" Value="True"><Setter TargetName="CustomerOverlay" Property="Opacity" Value="0.2"/></Trigger></ControlTemplate.Triggers></ControlTemplate></Window.Resources><Grid><Grid.RowDefinitions><RowDefinition Height="40"/></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition Width="100"/><ColumnDefinition Width="100"/><ColumnDefinition Width="100"/><ColumnDefinition Width="100"/><ColumnDefinition Width="100"/><ColumnDefinition Width="100"/><ColumnDefinition Width="100"/></Grid.ColumnDefinitions><Button Grid.Column="0" Grid.Row="0" Content="默认按钮" Margin="0 10 0 0"></Button><Button Grid.Column="2" Grid.Row="0" Content="自定义按钮1" Foreground="White" FontSize="12" FontWeight="Bold" Background ="#409eff"BorderBrush="White"Margin="0 10 0 0" Cursor="Hand"Template="{StaticResource CustomButtonTemplate}"></Button><Button Grid.Column="4" Grid.Row="0" Margin="0 10 0 0" Background="#e6a23c"Width="30"Height="30"Cursor="Hand"Template="{StaticResource CustomIconButtonTemplate}"></Button><Button Grid.Column="6" Grid.Row="0" Margin="0 10 0 0" Foreground="White"Background="#409eff"BorderBrush="White"Cursor="Hand"Template="{StaticResource CustomImageButtonTemplate}"></Button></Grid>
</Window>

具体效果如下所示:

 还有更多炫酷的自定义样式,比如添加一个动画效果,但是这种样式,对于我目前刚刚学习WPF来说不太需要,等后面需要用到再了解。

数据模板(DataTemplate)

ControlTemplate 主要用于自定义控件的外观,它改变控件的整体结构和样式,通常用于修改控件的框架(例如按钮的样式)。而当控件内部需要显示与数据相关的内容时,就需要使用 DataTemplate。DataTemplate 定义了数据对象的外观,控制了数据项的可视化方式,特别是在使用像 ItemsControl、ListBox 或 DataGrid 等控件时,DataTemplate 会决定如何呈现绑定的数据项。

基本语法

<DataTemplate x:Key="CustomerDataTemplate"></DataTemplate>

DataTemplate重要属性:

  • VisualTree,定义每个数据项的样式。
  • Triggers,触发器当绑定的数据满足某个条件时,可以去设置一些控件的属性值,这个与ControlTemplate中的Triggers还不一样。

下面我将以ListBox为例,自定义ListBox的样式

前端页面代码:

<Window x:Class="WpfApp1.DataTemplateWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:WpfApp1"mc:Ignorable="d"Title="DataTemplateWindow" Height="450" Width="800"><Window.Resources></Window.Resources><Grid><Grid.RowDefinitions><RowDefinition /><RowDefinition/></Grid.RowDefinitions><ListBox Grid.Row="0" x:Name="DefaultListBox" HorizontalContentAlignment="Stretch"></ListBox><ListBox Grid.Row="1" x:Name="CustomerListBox" HorizontalContentAlignment="Stretch"><ListBox.ItemContainerStyle><Style TargetType="ListBoxItem"><Setter Property="Margin" Value="0 10 0 0"/><!-- 去掉背景 --><Setter Property="Background" Value="Transparent"/><Setter Property="BorderBrush" Value="Transparent"/><Setter Property="BorderThickness" Value="0"/></Style></ListBox.ItemContainerStyle><!-- ListBox中的内容排列方向 --><ListBox.ItemsPanel><ItemsPanelTemplate><WrapPanel Orientation="Horizontal"/></ItemsPanelTemplate></ListBox.ItemsPanel><ListBox.ItemTemplate><DataTemplate><WrapPanel Orientation="Horizontal"  ><Border  Background="{Binding TagColor}" CornerRadius="3" Width="70" Height="25" ><TextBlock Text="{Binding TagName}" Width="70" Height="25" TextAlignment="Center"Foreground="White"Padding="0 5 0 0"/></Border><Image Source="/images/删除.png" x:Name="ShowImg" Width="15" Margin="-18 0 0 0" Visibility="Hidden"  Cursor="Hand" /></WrapPanel><DataTemplate.Triggers><DataTrigger Binding="{Binding TagDelete}" Value="True"><Setter TargetName="ShowImg" Property="Visibility" Value="Visible"/></DataTrigger></DataTemplate.Triggers></DataTemplate></ListBox.ItemTemplate></ListBox></Grid>
</Window>

后台代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;namespace WpfApp1
{/// <summary>/// DataTemplateWindow.xaml 的交互逻辑/// </summary>public partial class DataTemplateWindow : Window{public DataTemplateWindow(){InitializeComponent();var tagList = new List<TagInfo>(){new TagInfo(){TagName = "Tag 1",TagColor = "#409eff"},new TagInfo(){TagName = "Tag 2",TagColor = "#67c23a",TagDelete = true},new TagInfo(){TagName = "Tag 3",TagColor = "#909399"},};DefaultListBox.ItemsSource = tagList;CustomerListBox.ItemsSource = tagList;}}public class TagInfo{public string TagName { get; set; }public string TagColor { get; set; }public bool TagDelete { get; set; } = false;public override string ToString(){return TagName;  // 使用 TagName 作为默认显示内容}}
}

执行效果:

代码也很简单,稍微解释一下:

  • ItemContainerStyle,设置每个数据项的样式。
  • ItemsPanel,设置数据集的排列方向。
  • ItemTemplate,设置每个数据项的具体自定义样式,里面就有DataTemplate。

需要注意的是,上面说的ItemContainerStyle、ItemsPanel、ItemTemplate这并不是每个数据集的控件都支持,具体还需要看官网每个控件支持的是哪些。

DataTemplate中的内容一般也是放到Window.Resources中。

<Window x:Class="WpfApp1.DataTemplateWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:WpfApp1"mc:Ignorable="d"Title="DataTemplateWindow" Height="450" Width="800"><Window.Resources><Style x:Key="CustomListBoxItemStyle" TargetType="ListBoxItem"><Setter Property="Margin" Value="0 10 0 0"/><Setter Property="Background" Value="Transparent"/><Setter Property="BorderBrush" Value="Transparent"/><Setter Property="BorderThickness" Value="0"/></Style><!-- ItemTemplate --><DataTemplate x:Key="CustomItemTemplate"><WrapPanel Orientation="Horizontal"><Border Background="{Binding TagColor}" CornerRadius="3" Width="70" Height="25"><TextBlock Text="{Binding TagName}" Width="70" Height="25" TextAlignment="Center"Foreground="White" Padding="0 5 0 0"/></Border><Image Source="/images/删除.png" x:Name="ShowImg" Width="15" Margin="-18 0 0 0"Visibility="Hidden" Cursor="Hand"/></WrapPanel><DataTemplate.Triggers><DataTrigger Binding="{Binding TagDelete}" Value="True"><Setter TargetName="ShowImg" Property="Visibility" Value="Visible"/></DataTrigger></DataTemplate.Triggers></DataTemplate></Window.Resources><Grid><Grid.RowDefinitions><RowDefinition /><RowDefinition/></Grid.RowDefinitions><ListBox Grid.Row="0" x:Name="DefaultListBox" HorizontalContentAlignment="Stretch"></ListBox><ListBox Grid.Row="1" x:Name="CustomerListBox" HorizontalContentAlignment="Stretch"ItemContainerStyle="{StaticResource CustomListBoxItemStyle}"ItemTemplate="{StaticResource CustomItemTemplate}"><ListBox.ItemsPanel><ItemsPanelTemplate><WrapPanel Orientation="Horizontal"/></ItemsPanelTemplate></ListBox.ItemsPanel></ListBox></Grid>
</Window>

总结

在 WPF 中,ControlTemplate和DataTemplate 是非常强大且重要的功能,它们分别用于自定义控件的外观和数据展示的方式。只有在用到某些控件需要自定义样式的时候,再具体了解,因为每个控件自定义模板是不一样的,本篇文章只是根据Button和ListBox简单实现这两个控件的自定义样式。如果有不同的需求,大家具体可以看下微软的模板库,里面有每个控件模板涉及需要修改的点。

后面我会用每个控件都实现自定义样,包括下拉控件、分页控件、时间控件等等,实现效果以经常使用的场景为主。

控件样式和模板 - WPF .NET Framework | Microsoft Learn


http://www.ppmy.cn/embedded/154464.html

相关文章

DFT可测性设置与Tetramax测试笔记

1 DFT 1.1 DFT类型 1、扫描链&#xff08;SCAN&#xff09;&#xff1a; 扫描路径法是一种针对时序电路芯片的DFT方案.其基本原理是时序电路可以模型化为一个组合电路网络和带触发器(Flip-Flop&#xff0c;简称FF)的时序电路网络的反馈。 Scan 包括两个步骤&#xff0c;scan…

python创建pdf水印,希望根据文本长度调整水印字体大小,避免超出页面

为了根据文本长度动态调整水印字体大小&#xff0c;可以先测量文本长度&#xff0c;然后根据页面宽度和高度动态计算合适的字体大小。以下是修改后的代码&#xff1a; from reportlab.pdfgen import canvas from reportlab.lib.pagesizes import letter from reportlab.pdfbas…

【HTML+CSS+JS+VUE】web前端教程-36-JavaScript简介

JavaScript介绍 JavaScript是一种轻量级的脚本语言&#xff0c;所谓脚本语言&#xff0c;指的是它不具备开发操作系统的能力&#xff0c;而是用来编写控制其他大型应用程序的“脚本” JavaScript是一种嵌入式语言&#xff0c;它本身提供的核心语法不算很多 为什么学习JavaScri…

智能家居企业如何通过设计师渠道打造第二曲线?

随着智能家居行业的迅速发展和消费者需求的不断升级&#xff0c;企业的营销策略也在不断变化。传统的B2C营销模式逐渐让位于更加精细化、定制化的B2B2C模式&#xff0c;其中设计师渠道的开发与合作&#xff0c;成为智能家居企业布局市场、提升品牌影响力的关键。 智能家居推广的…

HTTP 范围Range请求

引言 在现代Web应用中&#xff0c;HTTP范围请求是一种重要的技术&#xff0c;允许客户端请求资源的部分内容&#xff0c;而不是整个资源。这对于大型文件的传输尤其有用&#xff0c;如视频流、断点续传下载等。本文将深入探讨HTTP范围请求的工作原理、实现方法和应用场景。 H…

c#删除文件和目录到回收站

之前在c上遇到过这个问题&#xff0c;折腾许久才解决了&#xff0c;这次在c#上再次遇到这个问题&#xff0c;不过似乎容易了一些&#xff0c;亲测代码如下&#xff0c;两种删除方式都写在代码中了。 直接上完整代码&#xff1a; using Microsoft.VisualBasic.FileIO; using Sy…

WPS excel使用宏编辑器合并 Sheet工作表

使用excel自带的工具合并Sheet表&#xff0c;我们会发现需要开通WPS会员才能使用合并功能&#xff1b; 那么WPS excel如何使用宏编辑器进行合并 Sheet表呢&#xff1f; 1、首先我们要看excel后缀是 .xlsx 还是 .xls &#xff1b;如果是.xlsx 那么 我们需要修改为 .xls 注…

Python----Python高级(面向对象:对象,类,属性,方法)

一、面向对象简介 Python完全采用了面向对象的思想&#xff0c;是真正面向对象的编程语言&#xff0c;完全支持面向对象的基本功能&#xff0c;例如&#xff1a;继承、多态、封装等。 Python中&#xff0c;一切皆对象。python数据类型、函数等&#xff0c;都是对象。 面向对象&…