眾所周知,在WPF框架中,Visual類是可以提供渲染(render)支持的最頂層的類,所有可視化元素(包括UIElement、FrameworkElment、Control等)都直接或間接繼承自Visual類,一個WPF應用的用戶界面上的所有可視化元素一起組成了一個可視化樹(visual tree),任何一個顯示在用戶界面上的元素都在且必須在這個樹中,通常一個可視化元素都是由眾多可視化元素組合而成,一個控制元件的所有可視化元素一起又組成了一個區域的visual tree,當然這個區域的visual tree也是整體visual tree的一部分,一個可視化元素可能是由應用直接創建(要么通過Xaml,要么通過背后的代碼),也可能是從模板間接生成,前者比較容易理解,這里我們主要討論后者,即WPF的模板機制,方法是通過簡單分析WPF的源代碼,由于內容較多,為了便于閱讀,將分成一系列共5篇文章來敘述,本文是這一系列的第一篇,重點討論FrameworkTemplate類和FrameworkElement模板應用機制,這也是WPF模板機制的框架,
一、從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)的基類(generic class),其重要性不言而喻,瀏覽其代碼會發現一個值得注意的方法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()方法,這個方法的注釋是: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的唯一入口,
由于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()里被呼叫了一次,這意味著FrameworkElement的這個方法是FrameworkElement及其子類實作模板應用的唯一入口,這個方法的重要性無論如何強調都不為過,以后我們還會多次提到這個方法,因此有必要貼一下其代碼:
//***************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進行布局的兩個主要步驟,如果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的內容較多,被分成了兩篇文章),
(本文是系列文章《剖析WPF模板機制的內部實作》的第一篇,查看下一篇點這里)
(原創文章,歡迎批評指正,轉載請注明出處,謝謝!)
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/233328.html
標籤:WPF
