衆所周知,在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的內容較多,被分紅了兩篇文章)。