主題提供 Windows Presentation Foundation (WPF) 類層次結構,涵蓋了 WPF 的大部分主要子系統,並描述它們是如何交互的。node
WPF 主要編程模型是經過託管代碼公開的。在 WPF 的早期設計階段,曾有過大量關於如何界定系統的託管組件和非託管組件的爭論。CLR 提供一系列的功能,能夠令開發效率更高而且更加可靠(包括內存管理、錯誤處理和常規類型系統等),但這是須要付出代價的。算法
下圖說明了 WPF 的主要組件。關係圖的紅色部分(PresentationFramework、PresentationCore 和 milcore)是 WPF 的主要代碼部分。在這些組件中,只有一個是非託管組件 – milcore。milcore 是以非託管代碼編寫的,目的是實現與 DirectX 的緊密集成。WPF 中的全部顯示都是經過 DirectX 引擎完成的,所以硬件和軟件呈現都很高效。WPF 還要求對內存和執行進行精細控制。milcore 中的組合引擎受性能影響關係大,須要放棄 CLR 的許多優勢來提升性能。express
本主題的後面部分將討論 WPF 的託管和非託管部分之間的通訊。下面介紹託管編程模型的其他部分。編程
WPF 中的大多數對象是從 DispatcherObject 派生的,這提供了用於處理併發和線程的基本構造。WPF 基於調度程序實現的消息系統。其工做方式與常見的 Win32 消息泵很是相似;事實上,WPF 調度程序使用 User32 消息執行跨線程調用。windows
要討論 WPF 中的併發,首先必須真正理解兩個核心概念 – 調度程序和線程關聯。緩存
在 WPF 的設計階段,目標趨向於單一線程的執行,但這不是一種與線程「關聯的」模型。當一個組件使用執行線程的標識來存儲某種類型的狀態時,將發生線程關聯。最多見的形式是使用線程本地存儲 (TLS) 來存儲狀態。線程關聯要求執行的每一個邏輯線程僅由操做系統中的一個物理線程所擁有,這將佔用大量內存。最後,WPF 的線程處理模型保持與具備線程關聯的單一線程執行的現有 User32 線程處理模型同步。主要緣由是互操做性 – 相似於 OLE 2.0 的系統、剪貼板和 Internet Explorer 均須要單一線程關聯 (STA) 執行。數據結構
假設您具備帶有 STA 線程的對象,則須要一種方式來在線程之間通訊,並驗證您是否位於正確的線程上。調度程序的做用就在於此。調度程序是一個基本的消息調度系統,具備多個按優先級排列的隊列。消息的示例包括原始輸入通知(鼠標移動)、框架函數(佈局)或用戶命令(執行此方法)。經過從 DispatcherObject 派生,您能夠建立一個具備 STA 行爲的 CLR 對象,並在建立時得到一個指向調度程序的指針。併發
生成 WPF 時使用的主要體系結構原理之一是首選屬性而不是方法或事件。屬性是聲明性的,使您更方便地指定意圖而不是操做。它還支持模型驅動或數據驅動的系統,以顯示用戶界面內容。這種理念的預期效果是建立您能夠綁定到的更多屬性,從而更好地控制應用程序的行爲。app
爲了更加充分地利用由屬性驅動的系統,須要一個比 CLR 提供的內容更豐富的屬性系統。此豐富性的一個簡單示例就是更改通知。爲了實現雙向綁定,您須要綁定的雙方支持更改通知。爲了使行爲與屬性值相關聯,您須要在屬性值更改時獲得通知。Microsoft .NET Framework 具備一個 INotifyPropertyChange 接口,對象經過該接口能夠發佈更改通知,但該接口是可選的。框架
WPF 提供一個豐富的屬性系統,該屬性系統是從 DependencyObject 類型派生的。該屬性系統實際是一個「依賴」屬性系統,由於它會跟蹤屬性表達式之間的依賴關係,並在依賴關係更改時自動從新驗證屬性值。例如,若是您具備一個會繼承的屬性(如 FontSize),當繼承該值的元素的父級發生屬性更改時,會自動更新系統。
WPF 屬性系統的基礎是屬性表達式的概念。在 WPF 的初版中,屬性表達式系統是關閉的,表達式都是做爲框架的一部分提供的。表達式導致屬性系統不具備硬編碼的數據綁定、樣式調整或繼承,而是由框架內後面的層來提供這些功能。
屬性系統還提供屬性值的稀疏存儲。由於對象能夠有數十個(若是達不到上百個)屬性,而且大部分值處於其默認狀態(被繼承、由樣式設置等),因此並不是對象的每一個實例都須要具備在該對象上定義的每一個屬性的徹底權重。
屬性系統的最後一個新功能是附加屬性的概念。WPF 元素是基於組合和組件重用的原則生成的。某些包含元素(如 Grid 佈局元素)一般須要子元素上的其餘數據才能控制其行爲(如行/列信息)。任何對象均可覺得任何其餘對象提供屬性定義,而不是要將全部這些屬性與每一個元素相關聯。這與 JavaScript 中的「expando」功能類似。
定義一個系統後,下一步是將像素繪製到屏幕上。Visual 類用於生成可視化對象的樹,每一個對象能夠選擇性地包含繪製指令以及有關如何呈現這些指令(剪輯、變換等)的元數據。Visual 旨在極致輕量且靈活,所以大部分功能均未公開 API,而且很是依賴受保護的回調函數。
Visual 其實是 WPF 組合系統的入口點。Visual 是託管 API 和非託管 milcore 這兩個子系統之間的鏈接點。
WPF 經過遍歷由 milcore 管理的非託管數據結構來顯示數據。這些結構(稱爲組合節點)表明層次結構顯示樹,其中每一個節點都有呈現指令。只能經過消息傳遞協議來訪問此樹(下圖右側所示)。
當對 WPF 編程時,您將建立 Visual 元素及派生的類型,它們經過此消息傳遞協議在內部與此組合樹進行通訊。WPF 中的每一個 Visual 能夠不建立組合節點,也能夠建立一個或多個組合節點。
請注意一個很是重要的體系結構細節 – 可視對象和繪製指令的整個樹都要進行緩存。在圖形方面,WPF 使用一個保留的呈現系統。這可使系統以一個高刷新率重繪系統,而且不會發生組合系統阻止對用戶代碼的回調。這有助於防止出現應用程序無響應的狀況。
關係圖中不十分引人注意的另外一個重要細節是系統實際上如何執行組合。
在 User32 和 GDI 中,系統是在一個即時模式剪輯系統上工做。當須要呈現一個組件時,系統會創建一個剪輯邊界,不容許組件接觸該邊界以外的像素,而後會要求此組件在該框中繪製像素。此係統在內存受限的系統上工做良好,由於當某些內容更改時,只須要處理受影響的組件便可 – 不會有兩個組件對一個像素的顏色更改起做用。
WPF 使用「繪畫器的算法」繪製模型。這意味着並非剪輯每一個組件,而是要求從顯示內容的背面至正面來呈現每一個組件。這容許每一個組件在先前的組件的顯示內容上繪製。此模型的優勢是您能夠生成部分透明的複雜形狀。配合當今的新式圖形硬件,此模型相對較快(建立 User32/GDI 時則否則)。
如上面所述,WPF 的一個核心原理是轉移到一個更具聲明性且「以屬性爲中心」的編程模型。在可視化系統中,這會表現爲須要關注的兩種狀況。
首先,若是您考慮保留的模式圖形系統,則其實是從命令性 DrawLine/DrawLine 類型模型移動到面向數據的模型 new Line()/new Line()。經過這一貫數據驅動的呈現移動,能夠在使用屬性表達的繪製指令上進行復雜的操做。從 Drawing 派生的類型其實是用於呈現的對象模型。
第二,若是評估動畫系統,您將看到它幾乎是徹底聲明性的。無須要求開發人員計算下一位置或下一顏色,您能夠將動畫表示爲動畫對象上的一組屬性。因而,這些動畫能夠表示開發人員或設計人員的意圖(在 5 秒內將此按鈕從一個位置移動到另外一個位置),系統就能夠肯定完成此任務的最有效方式。
UIElement 定義核心子系統,包括 Layout、Input 和 Event。
Layout 是 WPF 中的一個核心概念。在許多系統中,可能有一組固定的佈局模型(HTML 支持三種佈局模型:流、絕對和表),也可能沒有佈局模型(User32 實際僅支持絕對定位)。WPF 先假設開發人員和設計人員但願有一個靈活的可擴展布局模型,該模型多是由屬性值而不是命令性邏輯驅動的。在 UIElement 級別,會引入佈局的基本協定 - 具備 Measure 和 Arrange 處理過程的兩階段模型。
Measure 容許組件肯定它要採用的大小。此階段獨立於 Arrange,由於在許多情形下,父元素會要求子元素測量若干次以肯定其最佳位置和大小。父元素要求子元素測量這一事實體現了 WPF 的另外一關鍵原則 – 內容大小。WPF 中的全部控件支持調整到內容原始大小的功能。這使本地化更加容易,並容許在調整大小時對元素進行動態佈局。Arrange 階段容許父元素定位並肯定每一個子元素的最終大小。
一般會花費大量的時間來討論 WPF 的輸出端(Visual 及其相關對象)。然而,在輸入端也有許多創新。WPF 輸入模型的最基本更改也許是一致模型,輸入事件經過系統藉助此模型進行路由。
輸入是做爲內核模式設備驅動程序上的信號發出的,並經過涉及 Windows 內核和 User32 的複雜進程路由到正確的進程和線程。與輸入相對應的 User32 消息路由到 WPF 後,就會轉換爲 WPF 原始輸入消息,併發送到調度程序。WPF 容許將原始輸入事件轉換爲多個實際事件,容許在保證傳遞到位的狀況下在較低的系統級別實現相似「MouseEnter」的功能。
每一個輸入事件至少會轉換爲兩個事件 – 「預覽」事件和實際事件。WPF 中的全部事件都具備經過元素樹路由的概念。若是事件從目標向上遍歷樹直到根,則被稱爲「冒泡」,若是從根開始向下遍歷到目標,它們被稱爲「隧道」。輸入預覽事件隧道,使樹中的任何元素都有機會篩選事件或對事件採起操做。而後,常規(非預覽)事件將從目標向上冒泡到根。
分割隧道和冒泡階段使快捷鍵等功能在複合世界中表現一致。在 User32 中,您能夠經過使用一個全局表來實現快捷鍵,該表中包含您但願支持的全部快捷鍵(Ctrl+N 映射爲「新建」)。在應用程序的調度程序中,您能夠調用 TranslateAccelerator,它會探查 User32 中的輸入消息,並肯定是否有任何消息與已註冊的快捷鍵匹配。在 WPF 中,上述內容不會起做用,由於系統是徹底「可組合」的 – 任何元素均可以處理和使用任何快捷鍵。將這個兩階段模型用於輸入,將容許組件實現其本身的 "TranslateAccelerator"。
爲了進一步深化此功能,UIElement 還引入了 CommandBindings 的概念。WPF 命令系統容許開發人員以命令終結點(一種用於實現 ICommand 的功能)的方式定義功能。命令綁定使元素能夠定義輸入筆勢 (Ctrl+N) 和命令(「新建」)之間的映射。輸入筆勢和命令定義都是可擴展的,而且能夠在使用時聯繫到一塊兒。這使得一些操做(例如,容許最終用戶自定義其要在應用程序內使用的鍵綁定)顯得可有可無。
至此,本主題已重點討論了 WPF 的「核心」功能 - PresentationCore 程序集中實現的功能。當生成 WPF 時,基礎部分(例如帶有 Measure 和 Arrange 的佈局的協定)和框架部分(例如 Grid 的特定佈局的實現)之間的明確劃分是但願的結果。目標就是提供在堆棧中處於較低位置的可擴展性點,這將容許外部開發人員能夠在須要時建立本身的框架。
能夠按兩種不一樣的方式來看待 FrameworkElement。它對在 WPF 的較低層中的子系統引入一組策略和自定義項。它還引入了一組新的子系統。
FrameworkElement 引入的主要策略是關於應用程序佈局。FrameworkElement 基於 UIElement 引入的基本佈局構建而成,並增長了佈局「插槽」的概念,使佈局創做者能夠更方便地擁有一組一致、屬性驅動的佈局語義。HorizontalAlignment、VerticalAlignment、MinWidth 和 Margin 等屬性使得從 FrameworkElement 派生的全部組件在佈局容器內具備一致的行爲。
利用 FrameworkElement,WPF 的核心層中具備的許多功能能夠更方便地進行 API 公開。例如,FrameworkElement 經過 BeginStoryboard 方法提供對動畫的直接訪問。Storyboard 提供一種針對一組屬性爲多個動畫編寫腳本的方式。
FrameworkElement 引入的兩個最關鍵的內容是數據綁定和樣式。
曾經使用 Windows Forms或 ASP.NET 建立應用程序user interface (UI) 的用戶應當對 WPF 中的數據綁定子系統較爲熟悉。在上述每一個系統中,可經過一種簡單的方式來表達但願將給定元素中的一個或多個屬性綁定到一個數據片斷。WPF 全面支持屬性綁定、轉換和列表綁定。
WPF 中數據綁定的最值得關注的功能之一是引入了數據模板。利用數據模板,您能夠聲明性地指定某個數據片段的可視化方式。您能夠將問題換個方向,讓數據來肯定將要建立的顯示內容,而無需建立可綁定到數據的自定義用戶界面。
樣式其實是輕量級的數據綁定。使用樣式,您能夠將共享定義的一組屬性綁定到元素的一個或多個實例。經過顯式引用(經過設置 Style 屬性)或經過將樣式與元素的 CLR 類型隱式關聯,即可以將樣式應用到元素。
控件的最重要的功能是模板化。若是將 WPF 的組合系統視爲一個保留模式呈現系統,則控件可經過模板化以一種參數化的聲明性方式描述其呈現。ControlTemplate 實際上不過是一個用於建立一組子元素的腳本,同時綁定到由控件提供的屬性。
Control 提供一組經常使用屬性,如 Foreground、Background、Padding 等,模板創做者可使用這些經常使用屬性來自定義控件的顯示。控件的實現提供了數據模型和交互模型。交互模型定義了一組命令(如窗口的「關閉」),以及到輸入筆勢的綁定(如單擊窗口上角的紅叉)。數據模型提供了一組屬性,用於自定義交互模型或自定義顯示(由模板肯定)。
數據模型(屬性)、交互模型(命令和事件)及顯示模型(模板)之間的劃分,使用戶能夠對控件的外觀和行爲進行徹底自定義。
最多見的控件數據模型是內容模型。若是查看 Button 之類的控件,您會看到它具備一個類型爲 Object 的名爲「Content」的屬性。在 Windows Forms和 ASP.NET 中,此屬性一般爲一個字符串 – 不過,這會限制您能夠在按鈕中輸入的內容類型。按鈕的內容能夠是簡單的字符串、複雜的數據對象或整個元素樹。若是是數據對象,可使用數據模板構造顯示內容。
傳統的應用程序建立一個顯示內容,而後綁定到某些數據。在 WPF 中,關於控件的全部內容、顯示內容的全部方面都是由某種類型的數據綁定生成的。經過在按鈕內部建立複合控件並將其顯示綁定到按鈕的內容屬性,就會顯示按鈕內的文本。
當開始開發基於 WPF 的應用程序時,您應對其感到很是熟悉。在其中設置屬性、使用對象和數據綁定的方式與您使用 Windows Forms或 ASP.NET 是極其類似的。若是對 WPF 體系結構有更深的瞭解,您就可以建立更豐富的應用程序,這些應用程序在根本上會將數據視爲應用程序的核心驅動力。