众所周知,在WPF框架中,Visual类是能够提供渲染(render)支持的最顶层的类,全部可视化元素(包括UIElement、FrameworkElment、Control等)都直接或间接继承自Visual类。一个WPF应用的用户界面上的全部可视化元素一块儿组成了一个可视化树(visual tree),任何一个显示在用户界面上的元素都在且必须在这个树中。一般一个可视化元素都是由众多可视化元素组合而成,一个控件的全部可视化元素一块儿又组成了一个局部的visual tree,固然这个局部的visual tree也是总体visual tree的一部分。一个可视化元素多是由应用直接建立(要么经过Xaml,要么经过背后的代码),也多是从模板间接生成。前者比较容易理解,这里咱们主要讨论后者,即WPF的模板机制,方法是经过简单分析WPF的源代码。因为内容较多,为了便于阅读,将分红一系列共5篇文章来叙述。本文是这一系列的第一篇,重点讨论FrameworkTemplate类和FrameworkElement模板应用机制,这也是WPF模板机制的框架。app
1、从FrameworkTemplate到visual tree框架
咱们知道尽管WPF中模板众多,可是它们的类型无外乎四个,这四个类的继承关系以下图所示:ide
可见开发中经常使用的三个模板类都以FrameworkTemplate为基类。问题是,除了继承关系,这些模板类的子类与基类还有什么关系?三个子类之间有什么关系?这些模板类在WPF模板机制中的各自角色是什么?WPF到底是如何从模板生成visual tree的?工具
要回答这些问题,最佳途径是从分析模板基类FrameworkTemplate着手。布局
FrameworkTemplate是抽象类,其定义代码比较多,为了简明,这里就不贴完整代码了,咱们只看比较关键的地方。首先,注意到这个类的注释只有一句话:A generic class that allow instantiation of a tree of Framework[Content]Elements,意思是这个类是容许实例化一个Framework元素树(也即visual tree)的基类(generic class),其重要性不言而喻。浏览其代码会发现一个值得注意的方法ApplyTemplateContent():ui
****************FrameworkTemplate****************** // // This method // Creates the VisualTree // internal bool ApplyTemplateContent( UncommonField
这是删除了打印调试信息后的代码,简单到只有三个语句。注释代表FrameworkTemplate生成VisualTree用的就是这个方法。其中最重要的是第二句,它把具体应用模板内容的工做交给了辅助类StyleHelper.ApplyTemplateContent()方法。这个方法的注释是:Instantiate the content of the template (either from FEFs or from Baml).This is done for every element to which this template is attached。其意思是说,每个带有模板的元素要实例化模板的内容(不管是来自FEF仍是来自Baml),都必须调用这个方法。而查看对StyleHelper.ApplyTemplateContent()方法的引用,会发现它只被引用了一次。而这惟一一次引用就是在FrameworkTemplate.ApplyTemplateContent()方法里。这也代表这个方法是FrameworkTemplate生成visual tree的惟一入口。this
因为StyleHelper.ApplyTemplateContent()方法的代码较多,这里为了简洁就不贴了。简而言之,这个方法会视具体状况选择合适的方法来实例化一个FrameworkTemplate,用其生成一个visual tree。生成的visual tree最终都会被传递到FrameworkElement.TemplateChild属性上,而这个属性的setter又会调用Visaul.AddVisualChild()方法。后者的主要目的创建两个visual之间的父子关系(parent-child relationship),以方便之后进行布局(layout)。至此,一切准备就绪,生成的visual tree已经可视化了。调试
*****************FrameworkElement******************* /// /// Gets or sets the template child of the FrameworkElement. /// virtual internal UIElement TemplateChild { get { return _templateChild; } set { if (value != _templateChild) { RemoveVisualChild(_templateChild); _templateChild = value; AddVisualChild(value); } } }
因为FrameworkTemplate.ApplyTemplateContent()不是虚方面,所以其子类没法覆写。查看这个方法的引用咱们能够看到,这个方法只在FrameworkElement.ApplyTemplate()里被调用了一次,这意味着FrameworkElement的这个方法是FrameworkElement及其子类实现模板应用的惟一入口。这个方法的重要性不管如何强调都不为过,之后咱们还会屡次提到这个方法。所以有必要贴一下其代码:blog
//***************FrameworkElement******************** /// /// ApplyTemplate is called on every Measure /// /// /// Used by subclassers as a notification to delay fault-in their Visuals /// Used by application authors ensure an Elements Visual tree is completely built /// /// Whether Visuals were added to the tree public bool ApplyTemplate() { // Notify the ContentPresenter/ItemsPresenter that we are about to generate the // template tree and allow them to choose the right template to be applied. OnPreApplyTemplate(); bool visualsCreated = false; UncommonField
方法的注释代表FrameworkElement在每次measure时都会调用这个方法,而咱们知道measure和arrange是UIElement进行布局的两个主要步骤。若是FrameworkElement元素在布局其HasTemplateGeneratedSubTree属性为false,那么就将调用FrameworkTemplate.ApplyTemplateContent()从新应用模板,生成visual tree。继承
这个方法的代码并不复杂,它先是调用虚方法OnPreApplyTemplate();而后若是HasTemplateGeneratedSubTree为false且TemplateInternal非空,则调用TemplateInternal的ApplyTemplateContent()方法生成相应的visual tree,并调用虚方法OnApplyTemplate()(这个虚方法在开发自定义控件时常常须要重写,此时visual tree已经生成并能够访问了);最后调用虚方法OnPostApplyTemplate()完成收尾工做。
从上面的分析能够看到,FrameworkElement能生成什么样的visual tree,或者说生成的visual tree的结构,彻底取决于其TemplateInternal。给这个属性一个什么样的模板,就会生成一个什么样的visual tree。换句话说,FrameworkElement的visual tree的模板彻底是由TemplateInternal惟一提供的。那么这个神奇的TemplateInternal属性又是怎如何定义的呢?事实上,除了这个属性FrameworkElement还定义了一个FrameworkTemplate类型的属性TemplateCache。这两个属性的定义都很简单,代码以下:
//***************FrameworkElement********************
// Internal helper so the FrameworkElement could see the // ControlTemplate/DataTemplate set on the // Control/Page/PageFunction/ContentPresenter internal virtual FrameworkTemplate TemplateInternal { get { return null; } } // Internal helper so the FrameworkElement could see the // ControlTemplate/DataTemplate set on the // Control/Page/PageFunction/ContentPresenter internal virtual FrameworkTemplate TemplateCache { get { return null; } set {} }
能够看到两者的注释几乎都彻底相同,也都是虚属性,FrameworkElement的子类能够经过覆写它们来实现多态性,提供自定义的模板。它们的自定义模板彻底决定了它们的visual tree。事实上,利用工具咱们能够看到只有4个FrameworkElement子类重写了TemplateInternal属性:Control、ContentPresenter、ItemsPresenter、Page,这意味着只有这4个类及其子类调用ApplyTemplate()才有意义。
如今问题是:FrameworkElement的子类具体是如何经过覆写虚属性TemplateInternal来自定义模板的呢?FrameworkTemplate的三个子类的变量有哪些?它们在这个过程当中的角色又有何不一样?
为了便于理解,下面咱们将按照三个模板子类,分红四篇文章来讨论(因为DataTemplate的内容较多,被分红了两篇文章)。