前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >WPF源代码分析系列一:剖析WPF模板机制的内部实现(一)

WPF源代码分析系列一:剖析WPF模板机制的内部实现(一)

作者头像
huofo
发布2022-03-17 21:45:17
1K0
发布2022-03-17 21:45:17
举报
文章被收录于专栏:huofo's bloghuofo's blog

众所周知,在WPF框架中,Visual类是可以提供渲染(render)支持的最顶层的类,所有可视化元素(包括UIElementFrameworkElmentControl等)都直接或间接继承自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()

代码语言:javascript
复制
  //****************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可视化元素实现模板应用的唯一入口,其重要性无论如何强调都不为过,以后我们还会多次提到这个方法。其代码如下:

代码语言:javascript
复制
//***************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类型,它们的定义如下:

代码语言:javascript
复制
//***************FrameworkElement********************
代码语言:javascript
复制
        // 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的内容较多,被分成了两篇文章)。

本文参与?腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-12-11 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com