在前面一篇我们粗略说了Style和Behaviors,如果要自定义一个个性十足的控件,仅仅用Style和Behaviors是不行的,Style和Behaviors只能通过控件的既有属性来简单改变外观,还需要有ControlTemplate来彻底定制,这是改变Control的呈现,也可以通过DataTemplate来改变Data的呈现,对于ItemsControl,还可以通过ItemsPanelTemplate来改变Items容器的呈现。

1.模板

WPF模板有三种:ControlTemplate、DataTemplate和ItemsPanelTemplate,它们都继承自FrameworkTemplate抽象类。在这个抽象类中有一个FrameworkElementFactory类型的VisualTree变量,通过该变量可以设置或者获取模板的根节点,包含了你想要的外观元素树。

先来看下ControlTemplate的例子:

  1. <Window x:Class="TemplateDemo.MainWindow"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. Title="MainWindow" Height="350" Width="525">
  5. <Window.Resources>
  6. <ControlTemplate x:Key="buttonTemplate" TargetType="{x:Type Button}">
  7. <Grid>
  8. <Ellipse Width="100" Height="100">
  9. <Ellipse.Fill>
  10. <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
  11. <GradientStop Offset="0" Color="Cyan" />
  12. <GradientStop Offset="1" Color="LightCyan" />
  13. </LinearGradientBrush>
  14. </Ellipse.Fill>
  15. </Ellipse>
  16. <Ellipse Width="80" Height="80">
  17. <Ellipse.Fill>
  18. <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
  19. <GradientStop Offset="0" Color="Yellow" />
  20. <GradientStop Offset="1" Color="Transparent" />
  21. </LinearGradientBrush>
  22. </Ellipse.Fill>
  23. </Ellipse>
  24. <ContentPresenter Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
  25. </Grid>
  26. </ControlTemplate>
  27. </Window.Resources>
  28. <Grid>
  29. <StackPanel>
  30. <Button Content="Hi,WPF" Template="{StaticResource buttonTemplate}" Click="Button_Click"/>
  31. </StackPanel>
  32. </Grid>
  33. </Window>

这里是将ControlTemplate作为资源的方式共享的,当然也可以通过Style的Setter来设置Button的Template属性来做。

效果如下:

在该ControlTemplate的VisualTree中,Button是被作为TemplatedParent的,这个属性定义在FrameworkElement和FrameworkContentElement中。关于TemplatedParent的介绍,这里可以看Mgen这篇文章。

这里完成了一个自定义风格的Button,然后在很多时候,我们只是想稍微修改下Button的外观,仍然像保留其阴影特性等功能,这时候我们就要"解剖"Button来了解其内部结构,VS2012自带的Expression Blend 5就具有这样的解剖功能。

生成了这样的代码:

  1. <Style x:Key="FocusVisual">
  2. <Setter Property="Control.Template">
  3. <Setter.Value>
  4. <ControlTemplate>
  5. <Rectangle Margin="2" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
  6. </ControlTemplate>
  7. </Setter.Value>
  8. </Setter>
  9. </Style>
  10. <SolidColorBrush x:Key="Button.Static.Background" Color="#FFDDDDDD"/>
  11. <SolidColorBrush x:Key="Button.Static.Border" Color="#FF707070"/>
  12. <SolidColorBrush x:Key="Button.MouseOver.Background" Color="#FFBEE6FD"/>
  13. <SolidColorBrush x:Key="Button.MouseOver.Border" Color="#FF3C7FB1"/>
  14. <SolidColorBrush x:Key="Button.Pressed.Background" Color="#FFC4E5F6"/>
  15. <SolidColorBrush x:Key="Button.Pressed.Border" Color="#FF2C628B"/>
  16. <SolidColorBrush x:Key="Button.Disabled.Background" Color="#FFF4F4F4"/>
  17. <SolidColorBrush x:Key="Button.Disabled.Border" Color="#FFADB2B5"/>
  18. <SolidColorBrush x:Key="Button.Disabled.Foreground" Color="#FF838383"/>
  19. <Style x:Key="ButtonStyle1" TargetType="{x:Type Button}">
  20. <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/>
  21. <Setter Property="Background" Value="{StaticResource Button.Static.Background}"/>
  22. <Setter Property="BorderBrush" Value="{StaticResource Button.Static.Border}"/>
  23. <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
  24. <Setter Property="BorderThickness" Value="1"/>
  25. <Setter Property="HorizontalContentAlignment" Value="Center"/>
  26. <Setter Property="VerticalContentAlignment" Value="Center"/>
  27. <Setter Property="Padding" Value="1"/>
  28. <Setter Property="Template">
  29. <Setter.Value>
  30. <ControlTemplate TargetType="{x:Type Button}">
  31. <Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
  32. <ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
  33. </Border>
  34. <ControlTemplate.Triggers>
  35. <Trigger Property="IsDefaulted" Value="true">
  36. <Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
  37. </Trigger>
  38. <Trigger Property="IsMouseOver" Value="true">
  39. <Setter Property="Background" TargetName="border" Value="{StaticResource Button.MouseOver.Background}"/>
  40. <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.MouseOver.Border}"/>
  41. </Trigger>
  42. <Trigger Property="IsPressed" Value="true">
  43. <Setter Property="Background" TargetName="border" Value="{StaticResource Button.Pressed.Background}"/>
  44. <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Pressed.Border}"/>
  45. </Trigger>
  46. <Trigger Property="IsEnabled" Value="false">
  47. <Setter Property="Background" TargetName="border" Value="{StaticResource Button.Disabled.Background}"/>
  48. <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Disabled.Border}"/>
  49. <Setter Property="TextElement.Foreground" TargetName="contentPresenter" Value="{StaticResource Button.Disabled.Foreground}"/>
  50. </Trigger>
  51. </ControlTemplate.Triggers>
  52. </ControlTemplate>
  53. </Setter.Value>
  54. </Setter>
  55. </Style>

看的出来,是由一个Border里面放了一个ContentPresenter构成的,然后是触发器定义的默认行为,相对比较简单,像ScrollBar等控件内部是很复杂的。关于ContentPresent,我们将在第二小节详细描述。

接下来,我们以Selector中的ListBox为例,来说明DataTemplate和ItemsPanelTemplate。

  1. <!--ItemsPanelTemplate-->
  2. <ItemsPanelTemplate x:Key="itemspanel">
  3. <StackPanel Orientation="Vertical" />
  4. </ItemsPanelTemplate>
  5. <!--DataTemplate-->
  6. <DataTemplate x:Key="datatemplate">
  7. <StackPanel Orientation="Horizontal">
  8. <TextBlock Text="{Binding ID}" Width="30"/>
  9. <TextBlock Text="{Binding Name}" Width="60"/>
  10. <Image Source="{Binding imgPath}" Width="30"/>
  11. </StackPanel>
  12. </DataTemplate>

cs代码:

  1. List<Student> studentList = new List<Student>()
  2. {
  3. new Student(){ID=,Name="Rethinker",imgPath="/TemplateDemo;component/Images/1.png"},
  4. new Student(){ID=,Name="Jello",imgPath="/TemplateDemo;component/Images/2.png"},
  5. new Student(){ID=,Name="Taffy",imgPath="/TemplateDemo;component/Images/3.png"}
  6. };
  7. this.lbStudentList.ItemsSource = studentList;

注意:这里Image的Source采用的是Pack Uri,详细内容请查看WPF中的Pack Uri

效果如下:

2.ContentPresenter

在第一节,我们发现在解剖的Button内部有个叫ContentPresenter的东东,根据名字也许你已经猜到它是干嘛的了,它就是呈现ContentControl的内容的。这里,当我们将ContentPresenter换成TextBlock好像效果也没变化。

  1. <TextBlock Text="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center"/>

我们知道TextBlock的Text属性是String类型,这就限制了其显示的丰富性。有人会说,既然这样那换成ContentControl算了,它可以显示更丰富的东西,类似这样:

  1. <ContentControl Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center"/>

看起来也没什么问题,我们知道Button本身就是一个ContentControl,它是一个很重量级的Control,它的Content其实也是通过ContentPresenter来表现的,看起来ContentPresenter是一个更轻量级的Control。另外,ContentPresenter还有一个比较特别的地方,当你未指定它的Content时,它会默认去取该模板的使用者的Content。

在继承自ItemsControl的控件内部也有个类似的ItemsPresenter,它是来负责Item的展示。

在ItemsPresenter内部会以ItemsPanelTemplate中的容器作为自己的容器,会以ItemTemplate中的布局作为ListBoxItem的布局。当然,在具体显示内容的地方,还是要用到ContentTemplate的。归根结底,我们可以将Presenter看作是一个占位符,设置了Button的Content,它就获取,否则默认。

3.TemplatePart机制

TemplatePart机制是某些WPF控件,如ProgressBar等,通过内建的逻辑来控制控件的可是行为的方式。我们先来解剖下ProgressBar一探究竟。

我们发现里面有几个很特别的东西,一个名为PART_Track的Rectangle,一个名为PART_Indicator的Grid。这并不是偶然,实际上在ComboBox和TextBox中也有这样类似PART_×××这样命名的元素。在这些类的定义中,我们也会发现一些端倪,例如ProgressBar类的定义:

  1. [TemplatePart(Name = "PART_GlowRect", Type = typeof(FrameworkElement))]
  2. [TemplatePart(Name = "PART_Indicator", Type = typeof(FrameworkElement))]
  3. [TemplatePart(Name = "PART_Track", Type = typeof(FrameworkElement))]
  4. public class ProgressBar : RangeBase
  5. {
  6.  
  7. }

ProgressBar用了TemplatePart这个Attribute,那它到底如何有何作用呢?实际上,如果在ControlTemplate中找到了这样的元素,就会应用一些附加的行为。

例如,在ComboBox的空间模板中有个名为PART_Popup的Popup,当它关闭时,ComboBox的DropDownClosed事件会自动触发,如果ComboBox控件模板中有名为PART_EditableTextBox的TextBox,它就会将用户的选项作为它的显示项。在后面的自定义控件这一篇中,我们也将使用它。

4.如何找Template中的控件

在Template的基类FrameworkTemplate中有FindName方法,通过它我们可以找到模板中的控件。这个方法对于ControlTemplate和ItemsPanelTemplate很直接有效,但是,在ItemsControl的DataTemplate中,因为展示的数据是集合,所以相对复杂些。在前面我们已经剖析了ListBox内部,这对于找控件是最本质的。我们将前面的DataTemplate稍微修改下,如下:

  1. <!--DataTemplate-->
  2. <DataTemplate x:Key="datatemplate">
  3. <StackPanel x:Name="sp" Orientation="Horizontal">
  4. <TextBlock x:Name="tbID" Text="{Binding ID}" Width="30"/>
  5. <TextBlock x:Name="tbName" Text="{Binding Name}" Width="60"/>
  6. <Image x:Name="tbImgPath" Source="{Binding imgPath}" Width="30"/>
  7. <TextBlock x:Name="tbNameLen" Text="{Binding Path=Name.Length}" />
  8. </StackPanel>
  9. </DataTemplate>

要查找由某个ListBoxItem的DataTemplate生成的TextBlock元素,需要获得ListBoxItem,在该ListBoxItem内查找ContentPresenter,然后对在该 ContentPresenter 上设置的 DataTemplate 调用 FindName,在由ListBoxItem查找ContentPresenter时,需要遍历VisualTree,这里给出遍历方法:

  1. class CommonHelper
  2. {
  3. public static T ChildOfType<T>(DependencyObject Parent) where T : DependencyObject
  4. {
  5. for (int i = ; i < VisualTreeHelper.GetChildrenCount(Parent); i++)
  6. {
  7. DependencyObject obj = VisualTreeHelper.GetChild(Parent, i);
  8. if (obj != null && obj is T)
  9. {
  10. return (T)obj;
  11. }
  12. else
  13. {
  14. T child = ChildOfType<T>(obj);
  15. if (child != null)
  16. return child;
  17. }
  18. }
  19. return default(T);
  20. }
  21. }

在这里通过监听ListBox的SelectionChanged事件来展示效果,cs代码:

  1. private void lbStudentList_SelectionChanged(object sender, SelectionChangedEventArgs e)
  2. {
  3. //第一步:找到ListBoxItem
  4. ListBoxItem item = this.lbStudentList.ItemContainerGenerator.ContainerFromIndex(this.lbStudentList.SelectedIndex) as ListBoxItem;
  5. if (item == null) return;
  6. //第二步:遍历找到ContentPresenter,这里需要写个辅助方法
  7. ContentPresenter cp = CommonHelper.ChildOfType<ContentPresenter>(item);
  8. if (cp == null) return;
  9. //第三步:找到DataTemplate
  10. DataTemplate dt = cp.ContentTemplate;
  11. //第四步:通过DataTemplate的FindName方法
  12. TextBlock tb = dt.FindName("tbName", cp) as TextBlock;
  13. if (tb != null)
  14. MessageBox.Show(tb.Text);
  15. }

效果如下:

最后,推荐几篇比较好的文章:

1)Creating WPF Data Templates in Code: The Right Way

2)Customizing WPF Expander with ControlTemplate

WPF学习(10)模板的更多相关文章

  1. WPF学习10:基于MVVM Light 制作图形编辑工具(1)

    图形编辑器的功能如下图所示: 除了MVVM Light 框架是一个新东西之外,本文所涉及内容之前的WPF学习0-9基本都有相关介绍. 本节中,将搭建编辑器的界面,搭建MVVM Light 框架的使用环 ...

  2. nodeJs学习-10 模板引擎 ejs语法案例

    ejs语法案例 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <t ...

  3. WPF学习11:基于MVVM Light 制作图形编辑工具(2)

    本文是WPF学习10:基于MVVM Light 制作图形编辑工具(1)的后续 这一次的目标是完成 两个任务. 画布 效果: 画布上,选择的方案是:直接以Image作为画布,使用RenderTarget ...

  4. WPF学习之深入浅出话模板

    图形用户界面应用程序较之控制台界面应用程序最大的好处就是界面友好.数据显示直观.CUI程序中数据只能以文本的形式线性显示,GUI程序则允许数据以文本.列表.图形等多种形式立体显示. 用户体验在GUI程 ...

  5. WPF学习12:基于MVVM Light 制作图形编辑工具(3)

    本文是WPF学习11:基于MVVM Light 制作图形编辑工具(2)的后续 这一次的目标是完成 两个任务. 本节完成后的效果: 本文分为三个部分: 1.对之前代码不合理的地方重新设计. 2.图形可选 ...

  6. 【WPF学习】第五十章 故事板

    正如上一章介绍,WPF动画通过一组动画类(Animation类)表示.使用少数几个熟悉设置相关信息,如开始值.结束值以及持续时间.这显然使得它们非常适合于XAML.不是很清晰的时:如何为特定的事件和属 ...

  7. WPF学习概述

    引言 在桌面开发领域,虽然在某些领域,基于electron的跨平台方案能够为我们带来某些便利,但是由于WPF技术能够更好的运用Direct3D带来的性能提升.以及海量Windows操作系统和硬件资源的 ...

  8. WPF学习开发客户端软件-任务助手(下 2015年2月4日代码更新)

    时光如梭,距离第一次写的 WPF学习开发客户端软件-任务助手(已上传源码)  已有三个多月,期间我断断续续地对该项目做了优化.完善等等工作,现在重新向大家介绍一下,希望各位可以使用,本软件以实用性为主 ...

  9. WPF学习之路初识

    WPF学习之路初识   WPF 介绍 .NET Framework 4 .NET Framework 3.5 .NET Framework 3.0 Windows Presentation Found ...

  10. ThinkPhp学习10

    原文:ThinkPhp学习10 查询操作 Action模块 User下的search public function search(){ //判断username是否已经传入,且不为空 if(isse ...

随机推荐

  1. CSharp Oracle 登陆

    =======后台Oracle存储过程================ 1.创建表 --判读表存在先删除begin    EXECUTE IMMEDIATE 'DROP TABLE student'; ...

  2. html 跳转页面,同时跳转到一个指定的位置

    比如我现在 a.html 的时候,我想跳转到 b.html ,并且是 b.html 的某一个位置,用 <a href=>, a.html里: <a href="b.html ...

  3. php 禁止 URL 直接访问 php文件

    通过判断访问来源来实现. $fromurl="http://www.111.com/index.php"; //只能从这个地址访问 if( $_SERVER['HTTP_REFER ...

  4. JSP简单介绍

    前言 知识点 1.JSP是什么   java  server   page,javaserver端页面技术.其主要作用在server端动态生成页面, 其组成java代码和html, 2.JSP的组成 ...

  5. 工程PMO工作

     算起来,这是第一次以项目PMO人员的身份參与项目.尽管非常可惜没有从头參与,也没有參与到项目结束.仅仅有短短的两个月,但对项目PMO也可略窥一斑.如今就当个流水账写一写吧. 进项目组的时候,是中 ...

  6. Java EE (3) -- Java EE 6 Web Services Developer Certified Expert(1z0-897)

    Create an SOAP web service in a servlet container Create a RESTful web service in a servlet containe ...

  7. 配置Tomcat的日志系统

    成功配置tomcat的log4j日志系统,格式:HTML+每天以yyyy-mm-dd.log命名的日志文件 一.引言: 实习单位让用log4j配置webapp的日志系统,要求产生的日志文件是html格 ...

  8. jspsmart(支持中文下载)

    将excel文件从jsp页面导入到数据库,先将文件上传到server,然后读取,最后删除掉上传//要加encType="multipart/form-data"<form a ...

  9. Word文件交换的电脑打开字体、排版变化的原因和解决方法!

    方案: 有时候.我们好不easy用Word写好文档,做好排版发给别人,别人会告诉你格式怎么是乱的啊,标题.正文.页眉页脚什么的格式都变了. 想尽各种办法都没能得到解决,那么出现这样的情况的原因究竟是什 ...

  10. 解决Centos 7 dhcp服务器-no subnet declaration for start (no IPV4 addresses.)

    上面的配置是hyper-v 安装的 centos 7.0 安装dhcp 服务器的方法是 yum install dhcpd 在安装和配置好后,运行的时候出现错误 错误提示如下: no subnet d ...