一、简介

WPF包含数据模板和控件模板,其中控件模板又包括ControlTemplate和ItemsPanelTemplate,这里讨论一下ControlTemplate。
其实WPF的每一个控件都有一个默认的模板,该模板描述了控件的外观以及外观对外界刺激所做出的反应。我们可以自定义一个模板来替换掉控件的默认模板以便打造个性化的控件。
与Style不同,Style只能改变控件的已有属性值(比如颜色字体)来定制控件,但控件模板可以改变控件的内部结构(VisualTree,视觉树)来完成更为复杂的定制,比如我们可以定制这样的按钮:在它的左半部分显示一个小图标而它的右半部分显示文本。
要替换控件的模板,我们只需要声明一个ControlTemplate对象,并对该ControlTemplate对象做相应的配置,然后将该ControlTemplate对象赋值给控件的Template属性就可以了。

二、ControlTemplate包含两个重要的属性

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

三、实例

1、定制可视化树(VisualTree)

       <Button>
<Button.Template>
<ControlTemplate>
<!--定义视觉树-->
<Grid>
<Ellipse Name="faceEllipse"
Width="{TemplateBinding Button.Width}"
Height="{TemplateBinding Control.Height}"
Fill="{TemplateBinding Button.Background}"/>
<TextBlock Name="txtBlock"
Margin="{TemplateBinding Button.Padding}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Text="{TemplateBinding Button.Content}" />
</Grid>
<!--定义视觉树_end-->
</ControlTemplate>
</Button.Template>
comtroltemplate_test
</Button>

代码效果:

在上面的代码中,我们修改了Button的Template属性,我们定义了一个ControlTemplate,在<ControlTemplate> ... </ControlTemplate>之间包含的是模板的视觉树,也就是如何显示控件的外观,我们这里使用了一个Ellipse(椭圆)和一个TextBlock(文本块)来定义控件的外观。
很容易联想到一个问题:控件(Button)的一些属性,比如高度、宽度、文本等如何在新定义的外观中表现出来呢?
我们使用TemplateBinding 将控件的属性与新外观中的元素的属性关联起来Width="{TemplateBinding Button.Width}" ,这样我们就使得椭圆的宽度与按钮的宽度绑定在一起而保持一致,同理我们使用Text="{TemplateBinding Button.Content}"将TextBlock的文本与按钮的Content属性绑定在一起。

2、定制触发器Triggers

    <Button Grid.Row="1" Grid.Column="0" Margin="10">
<Button.Template>
<ControlTemplate>
<!--定义视觉树-->
<Grid>
<Ellipse Name="faceEllipse"
Width="{TemplateBinding Button.Width}"
Height="{TemplateBinding Control.Height}"
Fill="{TemplateBinding Button.Background}"/>
<TextBlock Name="txtBlock"
Margin="{TemplateBinding Button.Padding}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Text="{TemplateBinding Button.Content}" />
</Grid>
<!--定义视觉树_end-->
<!--定义触发器-->
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter Property="Button.Foreground" Value="Red" />
</Trigger>
</ControlTemplate.Triggers>
<!--定义触发器_End-->
</ControlTemplate>
</Button.Template>
comtroltemplate_test
</Button>

代码效果:

在上面的代码中注意到<ControlTemplate. Triggers> ... </ControlTemplate. Triggers> 之间的部分,我们定义了触发器 <Trigger Property="Button.IsMouseOver" Value="True">,其表示当我们Button的IsMouseIOver属性变成True时,将使用设置器<Setter Property="Button. Foreground" Value="Red" /> 来将Button的Foreground属性设置为Red。这里有一个隐含的意思是:当Button的IsMouseIOver属性变成False时,设置器中设置的属性将回复原值。

3、ControlTemplate的重用

你只需要将模板定义为资源,其实大多数情况下,我们也是这样做的。

<Window.Resources>
<ControlTemplate TargetType="Button" x:Key="ButtonTemplate">
<!--定义视觉树-->
<Grid>
<Ellipse Name="faceEllipse"
Width="{TemplateBinding Button.Width}"
Height="{TemplateBinding Control.Height}"
Fill="{TemplateBinding Button.Background}"/>
<TextBlock Name="txtBlock"
Margin="{TemplateBinding Button.Padding}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Text="{TemplateBinding Button.Content}" />
</Grid>
<!--定义视觉树_end-->
<!--定义触发器-->
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter Property="Button.Foreground" Value="Red" />
</Trigger>
</ControlTemplate.Triggers>
<!--定义触发器_End-->
</ControlTemplate>
</Window.Resources> 

上面的代码将我们原来的模板定义为窗体范围内的资源,其中TargetType="Button"指示我们的模板作用对象为Button,其中的ButtonTemplate是我们定义的模板的x:Key。
这样在整个窗体范围内的按钮都可以使用这个模板了,模板的使用方法也很简单:

<Button Grid.Column="0" Template="{StaticResource ButtonTemplate}" Content="comtroltemplate_test"/>
<Button Grid.Column="1" Template="{StaticResource ButtonTemplate}" Content="test btn2" />
<Button Grid.Column="2" Template="{StaticResource ButtonTemplate}" Content="test btn3" />

代码效果:

4、在trigger中使用storyboard,事件响应中添加的动画效果

<Window.Resources>
<ControlTemplate TargetType="Button" x:Key="ButtonTemplate">
<!--定义视觉树-->
<Grid>
<Ellipse Name="faceEllipse"
Width="{TemplateBinding Button.Width}"
Height="{TemplateBinding Control.Height}"
Fill="{TemplateBinding Button.Background}"/>
<TextBlock Name="txtBlock"
Margin="{TemplateBinding Button.Padding}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Text="{TemplateBinding Button.Content}" />
</Grid>
<!--定义视觉树_end-->
<!--定义动画资源-->
<ControlTemplate.Resources>
<Storyboard x:Key="MouseClickStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="faceEllipse" Storyboard.TargetProperty="Width" BeginTime="00:00:00">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="50"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.3" Value="100"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</ControlTemplate.Resources>
<!--定义动画资源_end-->
<!--定义触发器-->
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter Property="Button.Foreground" Value="Red" />
</Trigger>
<EventTrigger RoutedEvent="Mouse.MouseDown" SourceName="faceEllipse">
<EventTrigger.Actions>
<BeginStoryboard Storyboard="{StaticResourceMouseClickStoryboard}"/>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseDown" SourceName="txtBlock">
<EventTrigger.Actions>
<BeginStoryboard Storyboard="{StaticResource MouseClickStoryboard}"/>
</EventTrigger.Actions>
</EventTrigger>
</ControlTemplate.Triggers>
<!--定义触发器_End-->
</ControlTemplate>
</Window.Resources>

四、获取.Net中原生控件的的默认ControlTemplate

我们知道每个控件都有自己默认的模板,这是MS编写的,如果我们能够得到这些模板的XAML代码,那么它将是学习模板的最好的示例,
要想获得某个控件ctrl的默认模板,请调用以下方法:

string GetTemplateXamlCode(Control ctrl)
{
string xaml = "no template";
FrameworkTemplate template = ctrl.Template;
if (template != null)
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.IndentChars = new string(' ', );
settings.NewLineOnAttributes = true; StringBuilder strbuild = new StringBuilder();
XmlWriter xmlwrite = XmlWriter.Create(strbuild, settings); try
{
XamlWriter.Save(template, xmlwrite);
xaml = strbuild.ToString();
}
catch (Exception exc)
{
xaml = exc.Message;
}
} return xaml;
}

[No0000DA]WPF ControlTemplate简介的更多相关文章

  1. 深入详解WPF ControlTemplate

    WPF包含数据模板和控件模板,其中控件模板又包括ControlTemplate和ItemsPanelTemplate,这里讨论一下WPF ControlTemplate. 其实WPF的每一个控件都有一 ...

  2. Wpf 数据绑定简介、实例1

    简介:1.WPF绑定使用的源属性必须是依赖项属性,这是因为依赖项属性具有内置的更改通知支持,元素绑定表达式使用了Xaml扩展标记, WPF绑定一个控件是使用Binding.ElementName, 绑 ...

  3. WPF ControlTemplate

    ControlTemplate:控件模板,顾名思义也就是定制特定的控件供公共调用,有点类似WinForm中对一些通用控件进行重写使用. ControlTemplate:控件模板主要有两个重要属性:Vi ...

  4. WPF : ControlTemplate和DataTemplate的区别

    ControlTemplate用于描述控件本身. 使用TemplateBinding来绑定控件自身的属性, 比如{TemplateBinding Background}DataTemplate用于描述 ...

  5. WPF ControlTemplate 动画板 结束事件不触发

    解决此问题很简单 将Storyboard单独提取出来及可 给定Key名称,然后在触发器中的BeginStoryboard的storyboard绑定即可 <!--单独提取并设置Xkey--> ...

  6. WPF开发简介教程

    1/ VS中文件-新建-项目-WPF应用程序 2/ 左上角工具箱中有很多组件可以直接拖拽使用 3/ 双击组件,进入脚本功能编辑界面,如按钮: private void Button_Click_1(o ...

  7. WPF ControlTemplate,DataTemplate

    The Control Template defines the visual appearance of a control. All of the UI elements have some ki ...

  8. WPF DataTemplate與ControlTemplate

    一. 前言     什麼是DataTemplate? 什麼是ControlTemplate? 在stackoverflow有句簡短的解釋 "A DataTemplate, therefore ...

  9. wpf的控件style

    前段时间一直在做wpf的UI开发,每次想做些控件style定制的时候都很头疼 很多控件不知道他的controltemplate是什么样的 为了方便大家写style 特别奉上wpf的style大全 从此 ...

随机推荐

  1. [docker]macvlan实现双vlan互通

    关于vlan的冷知识 vlan范围:0~4095 0,4095 保留 仅限系统使用 用户不能查看和使用这些VLAN 1 正常 Cisco默认VLAN 用户能够使用该VLAN,但不能删除它 2-1001 ...

  2. Socket网络编程--epoll小结

    以前使用的用于I/O多路复用为了方便就使用select函数,但select这个函数是有缺陷的.因为它所支持的并发连接数是有限的(一般小于1024),因为用户处理的数组是使用硬编码的.这个最大值为FD_ ...

  3. 安装polyglot出错

    安装polyglot出错 错误 Complete output from command python setup.py egg_info: Traceback (most recent call l ...

  4. 【九天教您南方cass 9.1】 06 绘制方格网

    同学们大家好,欢迎收看由老王测量上班记出品的cass9.1视频课程 我是本节课主讲老师九天. 我们讲课的教程附件也是共享的,请注意索取测量空间中. [点击索取cass教程]5元立得 (给客服说暗号:“ ...

  5. Paxos算法1-算法形成理论[转载]

    地址 http://blog.csdn.net/chen77716/article/details/6166675 中文wiki http://zh.wikipedia.org/zh-cn/Paxos ...

  6. <王二的经济学故事>读书笔记

    不是简单地用价格把供需弄平衡就完事了,座位分配给谁同样重要 一个成功的社会必须要有成功的人,必须要给成功的人应得的回报 需求曲线向下倾斜,价格越高需求越少 先来的都是那些时间最不值钱的 无论收入分配差 ...

  7. 非root用户如何使用docker命令

    docker命令默认只能root使用的,但我们可以赋权给其他用户,使用时先照常新建用户: [root@10 ~]# useradd docker [root@10 ~]# passwd docker ...

  8. halcon区域运算

    区域运算: Ø 并:union1().union2(): Ø 交:intersection(); Ø 差:difference(); Ø 补:complement():

  9. 牛客网_Go语言相关练习_选择题(1)

    声明:题目源自牛客网. 一.单项选择题 解析:作为形参时,可以要求单向,只读或只写. 解析:Go语言的内存回收机制规定,只要有一个指针指向引用一个变量,那么这个变量就不会被释放(内存逃逸),因此在Go ...

  10. Java如何获取本地计算机的IP地址和主机名?

    在Java编程中,如何获取本地计算机的IP地址和主机名? 以下示例显示如何使用InetAddress类的getLocalAddress()方法获取系统的本地IP地址和主机名. package com. ...