衆所周知,在WPF框架中,Visual類是能夠提供渲染(render)支持的最頂層的類,全部可視化元素(包括UIElement、FrameworkElment、Control等)都直接或間接繼承自Visual類。一個WPF應用的用戶界面上的全部可視化元素一塊兒組成了一個可視化樹(visual tree),任何一個顯示在用戶界面上的元素都在且必須在這個樹中。一般一個可視化元素都是由衆多可視化元素組合而成,一個控件的全部可視化元素一塊兒又組成了一個局部的visual tree,固然這個局部的visual tree也是總體visual tree的一部分。一個可視化元素多是由應用直接建立(要麼經過Xaml,要麼經過背後的代碼),也多是從模板間接生成。前者比較容易理解,這裏咱們主要討論後者,即WPF的模板機制,方法是經過簡單分析WPF的源代碼。因爲內容較多,爲了便於閱讀,將分紅一系列共5篇文章來敘述。本文是這一系列的第一篇,主要討論FrameworkTemplate類和FrameworkElement的模板應用框架。html
1、從FrameworkTemplate到visual treeapp
咱們知道盡管WPF中模板衆多,可是它們的類型無外乎四個,這四個類的繼承關係以下圖所示:框架
可見開發中經常使用的三個模板類都以FrameworkTemplate爲基類。問題是,除了繼承關係,這些模板類的子類與基類還有什麼關係?三個子類之間有什麼關係?這些模板類在WPF模板機制中的各自角色是什麼?WPF到底是如何從模板生成visual tree的?工具
要回答這些問題,最佳途徑是從分析模板基類FrameworkTemplate着手。oop
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的總入口。ui
其中最重要的是第二句,它把具體應用模板內容的工做交給了輔助類StyleHelper.ApplyTemplateContent()方法。因爲這個方法的代碼較多,這裏爲了簡潔就不貼了。簡而言之,這個方法的流程有三個分支:1)若是一個FrameworkTemplate的_templateRoot字段(FrameworkElementFactory類型)不爲空,則調用其_templateRoot.InstantiateTree()方法來生成visual tree;2)不然,若是這個FrameworkTemplate的HasXamlNodeContent屬性爲真,則調用其LoadContent()方法生成visual tree;3)若是兩者均不知足,則最終調用其BuildVisualTree()來生成visual tree。這些方法都比較複雜,它們的主要工做是實例化給定模板以生成visual tree。由於咱們只關心模板框架和模板應用的流程,因此不妨忽略這些細節。this
因爲FrameworkTemplate.ApplyTemplateContent()不是虛方面,所以其子類沒法覆寫。用代碼工具咱們能夠看到,這個方法只在FrameworkElement.ApplyTemplate()裏被調用了一次,這意味着這個方法是WPF可視化元素實現模板應用的惟一入口,其重要性不管如何強調都不爲過,之後咱們還會屢次提到這個方法。其代碼以下:spa
//***************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模板機制的內部實現》的第一篇,查看下一篇點這裏)
(原創文章,歡迎批評指正,轉載請註明出處,謝謝!)