WPF——Control与Template理解


一、前言

最近又翻看了下刘铁猛的《深入浅出WPF》,发现对模板章节中的部分内容有了更深的体会,所以写篇文扯扯。

文章标题是Control与Template,翻译成控件与模板。
将两者放一起不是无缘无故的,在WPF中,两者关系相当密切。


二、控件

如果有人问你什么是控件,你会怎么回答?
像界面上的按钮、文本框、滚动条啊,都是控件。
所以控件可以理解为界面的组成元素。

以上是我之前的回答,这样的回答没啥问题,很直观,很多人听了都能明白,虽然可能不太严谨。

不过如果继续问,
那为什么叫控件,而不是其它名称。这种界面上的可交互的图案,称作图形元素,不是更直观、更贴切?

我可能会回答,控件这个名称是根据Control这个单词翻译过来的,所以叫控件。

对面可能继续问,那为什么英文叫Control,而不是Graphic Element。
此时我陷入了沉思,确实,为什么会用Control这个单词来命名呢。

如何消除这个疑惑,最简单的就是百度,

百度词条
控件是指对数据和方法的封装
控件可以有自己的属性和方法,其中属性是控件数据的简单访问者,方法则是控件的一些简单而可见的功能、控件创建过程包含设计、开发、调试工作,然后是控件的使用。

先别管百度说的是对是错,但显然并不直观。你跟非程序员这么解释,他可能还是不懂。

那么从专业的角度来看,这段对控件的描述有没有道理呢。
学过程序的都知道,程序的本质是数据结构与算法

界面上的控件呈现了一定内容,并且提供了一些交互方式,用户通过交互可以改变程序状态。

那么,控件呈现的内容本质是什么?
是数据。
控件提供的交互方式本质/目的是什么?
我想应该是程序逻辑,比如你按下按钮,最终程序内部会执行一个方法(一段代码,即程序逻辑),程序状态可能会因此发生一些改变。

所以,控件既是数据的表现形式,以让用户可以直观地看到数据;又是算法的表现形式,以让用户方便地操作逻辑

作为“表现形式”,每个控件都是为了实现某种用户操作算法和直观显示某种数据而生的,一个控件看上去是什么样子由它的“算法内容”和“数据内容”决定,这也是哲学中常说的内容决定形式

这样看来,百度的这段话还是挺有道理的,更接近控件的本质。


三、模板

接下来回到WPF中的模板。

在以往的GUI技术中,控件内部的逻辑和数据是固定的,程序员不能改变(比如WinForms/Qt(QWidget));对于控件的外观,程序员能做的改变也非常有限,一般就是设置控件的属性,想改变控件的内部结构更是不可能。如果想扩展一个控件的功能或更改其外观让其更适合业务逻辑,哪怕只有一丁点改变,也经常要创建控件的子类或创建用户控件(UserControl)。造成这个局面的根本原因就是数据和算法的“形式”与“内容”耦合的太紧了

在WPF中,通过引入模板(Template),将数据和算法的“内容”和“形式”解耦了。

WPF中的Template分为两大类:

  • ControlTemplate,是算法内容的表现形式,一个控件怎样组织其内部结构才能让它更符合业务逻辑、让用户操作起来更舒服就是由它来控制的。它决定了控件“长成什么样子”,并让程序员有机会在控件原有的内部逻辑基础上扩展自己的逻辑。
  • DataTemplate,是数据内容的表现形式,一条数据显示成什么样子,是简单文本还是直观的图形动画就由它来决定。

一言蔽之,Template就是“外衣”——ControlTemplate是控件的外衣,DataTemplate是数据的外衣。

WPF中的控件不再具有固定的形象,仅仅是算法内容和数据内容的载体。
你可以把控件理解为一组操作逻辑穿上了一套衣服,换套衣服就能变成另外一个模样。你看到的控件默认形象实际上就是出厂时微软为它穿上的默认服装。

3.1 DataTemplate

在实际项目中,DataTemplate相较于ControlTemplate,往往是用的比较多一些。

正如其名,它是数据的模板/外观。使用时,往往会在外层绑上一个对象,然后在内部将对象的属性绑定到各种控件。

只要将绑定的控件更换,显示的外观也会发生变化。即一样的内容可以用不同形式来展现,软件设计称之为“数据-视图”(Data-View)模式

在WPF开发中,DataTemplate常用的地方有三处,分别是:

  • ContentControl的ContentTemplate属性,相当于给ContentControl的内容穿衣服。
  • ItemsControl的ItemTemplate属性,相当于给ItemsControl的数据项穿衣服。
  • GridViewColumn的CellTemplate属性,相当于给GridViewColumn单元格里的数据穿衣服。

3.2 ControlTemplate

前面说过,
你看到的控件默认形象实际上就是出厂时微软为它穿上的默认服装。
因为ControlTemplate有默认服装,且大部分时候默认服装已经够用了,所以很少有人会手动去修改它。

实际项目中,即使要给ControlTemplate做替换,也往往是用成熟的UI工具包,引入项目中,应用其样式即可。

这很合理,前面也说了,每个控件都是为了实现某种用户操作算法和直观显示某种数据而生的。而这些特定的操作算法是由控件的本质决定的,显然这部分内容较数据来讲是更为固定,交由微软或者专业的组件开发商决定没有问题。

不过,这也并不意味着普通程序员就完全不需要去了解ControlTemplate。因为某些时候,确实也会需要修改它们。

《WPF深入浅出》一书中说到,

实际项目中,ControlTemplate主要由两大用处:

  1. 通过更换ControlTemplate改变控件外观,使之具有更优的用户体验及外观。
  2. 接触ControlTemplate,程序员与设计师可以并行工作,程序员可以先用WPF标准控件进行编程,等设计师工作完成后,只需把新的ControlTemplate应用到程序中就可以了。

不过考虑到国内WPF开发的现状,应该很少有设计师和程序员工作完全分离的。所以ControlTemplate的使用上也集中在第一点了。

但第一点,听起来似乎和之前所说的冲突了,ControlTemplate不是呈现算法的吗,怎么变成改变控件外观的了。

其实这并不矛盾,因为控件的外观和控件的本质息息相关。

  • Button为什么形状都差不多,一般就是一个椭圆或矩形,可点击,点击后会凹下去。
  • TextBox为什么都是一个矩形框,里面可输入文字。
  • ScrollBar为什么都是一个长条,你可以拖动它移动。

还记得算法是什么吗?
每本编程入门书都会说,算法是解决问题的办法。

这些控件之所以有这样的外观和交互方式,并不是凭空产生的,而是人设计出来的。显然这样的设计就是为了解决一类问题,即它是一种算法。

当你对控件效果不满意时,就可能需要更改其外观,以贴合实际需求。

此时,我对ControlTemplate又有了一层理解。控件模板描述的是控件外观以及外观对外界刺激所做出的反应(比如各种事件/按钮鼠标触摸后的背景色变化等),而外观和对刺激的反应更深层的含义是该类控件要解决的问题的解决方案

下面是一个实际开发中的ControlTemplate应用场景,我需要在鼠标移动到按钮上时,按钮的背景色发生变红色(这是为了使鼠标移动到按钮的操作有反馈感):

<Style TargetType="Button">
   <Style.Triggers>
       <Trigger Property="IsMouseOver" Value="True">
           <Setter Property="Template">
               <Setter.Value>
                   <ControlTemplate TargetType="Button">
                       <Border Background="red">
                           <ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center"/>
                       </Border>
                   </ControlTemplate>
               </Setter.Value>
           </Setter>
       </Trigger>
   </Style.Triggers>
</Style>

如代码所示,用触发器检验鼠标是否在控件上,若是则修改其控件模板,主要就是将控件模板外的border的背景色变红。

3.3 ContentPresenter

实际上Button的结构就是这么简单,
在这里插入图片描述

<Border>
	<ContentPresenter/>
</Border>

一个边框加一个ContentPresenter就组成了一个按钮。
那么,ContentPresenter是什么?
我们可以通过反编译看到其中结构,下面是我整理出来的一个类关系图:

在这里插入图片描述
你会发现ContentPresenter的结构和ContentControl高度重合。
这边我也不卖关子,也不想花太多篇幅去深究它们的关系。
你可以把ContentPresenter直接理解成控件的数据内容模板DataTemplate。

有的控件继承了ContentControl,我称之为内容控件。
也有部分控件没有继承它,比如TextBox,
在这里插入图片描述

也因此,TextBox的默认控件模板中不存在ContentPresenter。这也很好理解,都没有数据内容,何来数据内容呈现?

你可能会问,那它的Text属性不是数据内容?
当然是,但从控件的角度来看,Text这样的属性已经是相当基础了,就是一段字串(有点类似于“原子数据”),而内容控件的Content可能是有分割必要的,比如Button中可能会有文字描述/图标等。因此我个人倾向于,Text本身这种简单的特性导致了它不作为内容模板出现。


四、结语

写了这么多,主要就是谈了谈对控件以及两个模板的理解。
这种理解更像是WPF开发(也不限于WPF)的内功,不对写代码直接产生效率提升,但会有长远的积极影响。

猜你喜欢

转载自blog.csdn.net/BadAyase/article/details/132807879