WPF源代码分析系列一:剖析WPF模板机制的内部实现(一)
众所周知,在WPF框架中,Visual类是可以提供渲染(render)支持的最顶层的类,所有可视化元素(包括UIElement、FrameworkElment、Control等)都直接或间接继承自Visual类。一个WPF应用的用户界面上的所有可视化元素一起组成了一个可视化树(visual tree),任何一个显示在用户界面上的元素都在且必须在这个树中。通常一个可视化元素都是由众多可视化元素组合而成,一个控件的所有可视化元素一起又组成了一个局部的visual tree,当然这个局部的visual tree也是整体visual tree的一部分。一个可视化元素可能是由应用直接创建(要么通过Xaml,要么通过背后的代码),也可能是从模板间接生成。前者比较容易理解,这里我们主要讨论后者,即WPF的模板机制,方法是通过简单分析WPF的源代码。由于内容较多,为了便于阅读,将分成一系列共5篇文章来叙述。本文是这一系列的第一篇,主要讨论FrameworkTemplate类和FrameworkElement的模板应用框架。
一、从FrameworkTemplate到visual tree
我们知道尽管WPF中模板众多,但是它们的类型无外乎四个,这四个类的继承关系如下图所示:

可见开发中常用的三个模板类都以FrameworkTemplate为基类。问题是,除了继承关系,这些模板类的子类与基类还有什么关系?三个子类之间有什么关系?这些模板类在WPF模板机制中的各自角色是什么?WPF究竟是如何从模板生成visual tree的?
要回答这些问题,最佳途径是从分析模板基类FrameworkTemplate着手。
FrameworkTemplate是抽象类,其定义代码比较多,为了简明,这里就不贴完整代码了,我们只看比较关键的地方。首先,注意到这个类的注释只有一句话:A generic class that allow instantiation of a tree of Framework[Content]Elements,意思是这个类是允许实例化一个Framework元素树(也即visual tree)的基类,其重要性不言而喻。浏览其代码会发现一个引人注意的方法ApplyTemplateContent():
//****************FrameworkTemplate******************
//
// This method
// Creates the VisualTree
//
internal bool ApplyTemplateContent(
UncommonField<HybridDictionary[]> templateDataField,
FrameworkElement container)
{
ValidateTemplatedParent(container); bool visualsCreated = StyleHelper.ApplyTemplateContent(templateDataField, container,
_templateRoot, _lastChildIndex,
ChildIndexFromChildName, this); return visualsCreated;
}
这是删除了打印调试信息后的代码,虽然简单到只有三个语句,但是这个方法的注释提示我们这里是从FrameworkTemplate生成VisualTree的总入口。
其中最重要的是第二句,它把具体应用模板内容的工作交给了辅助类StyleHelper.ApplyTemplateContent()方法。由于这个方法的代码较多,这里为了简洁就不贴了。简而言之,这个方法的流程有三个分支:1)如果一个FrameworkTemplate的_templateRoot字段(FrameworkElementFactory类型)不为空,则调用其_templateRoot.InstantiateTree()方法来生成visual tree;2)否则,如果这个FrameworkTemplate的HasXamlNodeContent属性为真,则调用其LoadContent()方法生成visual tree;3)如果二者均不满足,则最终调用其BuildVisualTree()来生成visual tree。这些方法都比较复杂,它们的主要工作是实例化给定模板以生成visual tree。因为我们只关心模板框架和模板应用的流程,所以不妨忽略这些细节。
由于FrameworkTemplate.ApplyTemplateContent()不是虚方面,因此其子类无法覆写。用代码工具我们可以看到,这个方法只在FrameworkElement.ApplyTemplate()里被调用了一次,这意味着这个方法是WPF可视化元素实现模板应用的唯一入口,其重要性无论如何强调都不为过,以后我们还会多次提到这个方法。其代码如下:
//***************FrameworkElement********************
/// <summary>
/// ApplyTemplate is called on every Measure
/// </summary>
/// <remarks>
/// Used by subclassers as a notification to delay fault-in their Visuals
/// Used by application authors ensure an Elements Visual tree is completely built
/// </remarks>
/// <returns>Whether Visuals were added to the tree</returns>
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<HybridDictionary[]> dataField = StyleHelper.TemplateDataField;
FrameworkTemplate template = TemplateInternal; // The Template may change in OnApplyTemplate so we'll retry in this case.
// We dont want to get stuck in a loop doing this, so limit the number of
// template changes before we bail out.
int retryCount = 2;
for (int i = 0; template != null && i < retryCount; i++)
{
// VisualTree application never clears existing trees. Trees
// will be conditionally cleared on Template invalidation
if (!HasTemplateGeneratedSubTree)
{ // Create a VisualTree using the given template
visualsCreated = template.ApplyTemplateContent(dataField, this);
if (visualsCreated)
{
// This VisualTree was created via a Template
HasTemplateGeneratedSubTree = true; // We may have had trigger actions that had to wait until the
// template subtree has been created. Invoke them now.
StyleHelper.InvokeDeferredActions(this, template); // Notify sub-classes when the template tree has been created
OnApplyTemplate();
} if (template != TemplateInternal)
{
template = TemplateInternal;
continue;
}
} break;
} OnPostApplyTemplate(); return visualsCreated;
}
方法的注释表明FrameworkElement和其子类在每次measure时都会调用这个方法,而我们知道measure和arrange是UIElement进行布局的两个重要步骤。这个方法的代码并不复杂,它先是调用虚方法OnPreApplyTemplate();然后如果TemplateInternal非空,则调用其ApplyTemplateContent()方法生成相应的visual tree,并调用虚方法OnApplyTemplate()(这个虚方法在开发自定义控件时经常需要重写,此时visual tree已经生成并可以访问了);最后调用虚方法OnPostApplyTemplate()。
注意上面代码有一个语句:
FrameworkTemplate template = TemplateInternal;
这说明FrameworkElement实际是根据其属性TemplateInternal的值来生成visual tree的。那么这个TemplateInternal又是从哪里来的呢?事实上,这个属性与另一个属性TemplateCache是有密切关系的,二者都是FrameworkTemplate类型,它们的定义如下:
//***************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的子类可以通过覆写它们来实现多态性,提供自定义的模板。另外,利用工具我们可以看到只有4个子类重写了TemplateInternal属性:Control、ContentPresenter、ItemsPresenter、Page,这意味着只有这4个类及其子类调用ApplyTemplate()才有意义。
现在问题是:FrameworkElement的子类具体是如何通过覆写虚属性TemplateInternal来自定义模板的?FrameworkTemplate的三个子类的变量有哪些?它们在这个过程中的角色又有何不同?
为了便于理解,下面我们将按照三个模板子类,分成四篇文章来讨论(由于DataTemplate的内容较多,被分成了两篇文章)。
(原创文章,欢迎批评指正,转载请注明出处,谢谢!)
WPF源代码分析系列一:剖析WPF模板机制的内部实现(一)的更多相关文章
- MyBatis架构设计及源代码分析系列(一):MyBatis架构
		如果不太熟悉MyBatis使用的请先参见MyBatis官方文档,这对理解其架构设计和源码分析有很大好处. 一.概述 MyBatis并不是一个完整的ORM框架,其官方首页是这么介绍自己 The MyBa ... 
- WPF入门教程系列十五——WPF中的数据绑定(一)
		使用Windows Presentation Foundation (WPF) 可以很方便的设计出强大的用户界面,同时 WPF提供了数据绑定功能.WPF的数据绑定跟Winform与ASP.NET中的数 ... 
- Spark SQL 源代码分析系列
		从决定写Spark SQL文章的源代码分析,到现在一个月的时间,一个又一个几乎相同的结束很快,在这里也做了一个综合指数,方便阅读,下面是读取顺序 :) 第一章 Spark SQL源代码分析之核心流程 ... 
- MyCat源码分析系列之——BufferPool与缓存机制
		更多MyCat源码分析,请戳MyCat源码分析系列 BufferPool MyCat的缓冲区采用的是java.nio.ByteBuffer,由BufferPool类统一管理,相关的设置在SystemC ... 
- spring源码分析系列 (8) FactoryBean工厂类机制
		更多文章点击--spring源码分析系列 1.FactoryBean设计目的以及使用 2.FactoryBean工厂类机制运行机制分析 1.FactoryBean设计目的以及使用 FactoryBea ... 
- Cordova Android源代码分析系列一(项目总览和CordovaActivity分析)
		版权声明:本文为博主offbye西涛原创文章.未经博主同意不得转载. https://blog.csdn.net/offbye/article/details/31776833 PhoneGap/Co ... 
- WPF入门教程系列十八——WPF中的数据绑定(四)
		六.排序 如果想以特定的方式对数据进行排序,可以绑定到 CollectionViewSource,而不是直接绑定到 ObjectDataProvider.CollectionViewSource 则会 ... 
- WPF入门教程系列十六——WPF中的数据绑定(二)
		三.绑定模式 通过上一文章中的示例,学习了简单的绑定方式.在这里的示例,要学习一下绑定的模式,和模式的使用效果. 首先,我们来做一个简单示例,这个示例是根据ListBox中的选中项,去改变TextBl ... 
- lightning mdb 源代码分析系列(3)
		本系列前两章已经描述了系统架构以及系统构建的基础内存映射,本章将详细描述lmdb的核心,外存B+Tree的操作.本文将从基本原理.内存操作方式.外存操作方式以及LMDB中的相关函数等几方面描述LMDB ... 
随机推荐
- 预训练模型——开创NLP新纪元
			预训练模型--开创NLP新纪元 论文地址 BERT相关论文列表 清华整理-预训练语言模型 awesome-bert-nlp BERT Lang Street huggingface models 论文 ... 
- 重要 | Spark分区并行度决定机制
			最近经常有小伙伴在本公众号留言,核心问题都比较类似,就是虽然接触Spark有一段时间了,但是搞不明白一个问题,为什么我从HDFS上加载不同的文件时,打印的分区数不一样,并且好像spark.defaul ... 
- 查询osd上的pg数
			本文中的命令的第一版来源于国外的一个博客,后面的版本为我自己修改的版本 查询的命令如下: ceph pg dump | awk ' /^pg_stat/ { col=1; while($col!=&q ... 
- Go语言配置管理神器——Viper中文教程
			Viper是适用于Go应用程序的完整配置解决方案.它被设计用于在应用程序中工作,并且可以处理所有类型的配置需求和格式. Viper Viper是适用于Go应用程序的完整配置解决方案.它被设计用于在应用 ... 
- Java项目读取resources资源文件路径那点事
			今天在Java程序中读取resources资源下的文件,由于对Java结构了解不透彻,遇到很多坑.正常在Java工程中读取某路径下的文件时,可以采用绝对路径和相对路径,绝对路径没什么好说的,相对路径, ... 
- css3系列之box-sizing
			box-sizing box-sizing: 俗称ie6 的混杂模式的盒子模型. 首先来了解一下 ie6 的混杂模式,和我们常用的 盒子模型有什么不一样 正常模式下: 我们设置的 width 和 ... 
- 统一软件开发过程(RUP)的概念和方法
			统一软件开发过程(Rational Unified Process,RUP)是一种面向对象且基于网络的程序开发方法论. 根据Rational(Rational Rose和统一建模语言的开发者)的说法, ... 
- 蓝桥杯——压缩变换(2016JavaB组第9题)
			压缩变换(16JavaB9) 小明最近在研究压缩算法. 他知道,压缩的时候如果能够使得数值很小,就能通过熵编码得到较高的压缩比. 然而,要使数值很小是一个挑战. 最近,小明需要压缩一些正整数的序列,这 ... 
- 蓝桥杯——测试次数·摔手机(2018JavaB组第4题,17分)
			x星球的居民脾气不太好,但好在他们生气的时候唯一的异常举动是:摔手机. 各大厂商也就纷纷推出各种耐摔型手机.x星球的质监局规定了手机必须经过耐摔测试,并且评定出一个耐摔指数来,之后才允许上市流通. x ... 
- 【数据结构】关于前缀树(单词查找树,Trie)
			前缀树的说明和用途 前缀树又叫单词查找树,Trie,是一类常用的数据结构,其特点是以空间换时间,在查找字符串时有极大的时间优势,其查找的时间复杂度与键的数量无关,在能找到时,最大的时间复杂度也仅为键的 ... 
