WPF 架構

本主題提供 Windows Presentation Foundation (WPF) 類別階層的引導式導覽。 它涵蓋 WPF 的大部分主要子系統,並描述其互動方式。 它也會詳細說明 WPF 架構設計人員所做的一些選擇。

System.Object

主要 WPF 程式設計模型是透過 Managed 程式碼公開。 在 WPF 的設計階段早期,有一些關於系統管理元件與 Unmanaged 元件之間應該繪製線條的爭論。 CLR 提供一些功能,可讓開發更具生產力和健全性(包括記憶體管理、錯誤處理、一般型別系統等),但會付出代價。

下圖說明 WPF 的主要元件。 圖表的紅色區段(PresentationFramework、PresentationCore 和 milcore)是 WPF 的主要程式碼部分。 其中只有一種是 Unmanaged 元件:milcore。 Milcore 是以 Unmanaged 程式碼撰寫,以便與 DirectX 緊密整合。 WPF 中的所有顯示都是透過 DirectX 引擎完成,允許有效率的硬體和軟體轉譯。 WPF 也需要對記憶體和執行進行精細的控制。 milcore 中的組合引擎非常敏感,而且需要放棄 CLR 的許多優點以提升效能。

The position of WPF within the .NET Framework.

本主題稍後會討論 WPF 的 Managed 與 Unmanaged 部分之間的通訊。 Managed 程式設計模型的其餘部分說明如下。

System.Threading.DispatcherObject

WPF 中的大部分物件都是衍生自 DispatcherObject ,其提供處理並行和執行緒的基本建構。 WPF 是以發送器所實作的傳訊系統為基礎。 這很像熟悉的 Win32 訊息幫浦;事實上,WPF 發送器會使用 User32 訊息來執行跨執行緒呼叫。

在 WPF 中討論並行性時,確實有兩個核心概念可瞭解 – 發送器和執行緒親和性。

在 WPF 的設計階段,目標是移至單一執行執行緒,但非執行緒「親和化」模型。 元件使用執行中執行緒的身分識別來儲存某些類型的狀態時,會發生執行緒親和性。 這最常見的形式是使用執行緒本機存放區 (TLS) 來儲存狀態。 執行緒親和性需要作業系統中只有一個實體執行緒擁有每個邏輯執行的執行緒,而這樣可能會耗用大量記憶體。 最後,透過執行緒親和性,WPF 的執行緒模型會與單一執行緒執行作業的現有 User32 執行緒模型同步。 這是互通性的主要原因– OLE 2.0、剪貼簿和 Internet Explorer 等系統都需要單一執行緒親和性 (STA) 執行。

假設您的物件具有 STA 執行緒,則您需要執行緒之間的通訊方法,並驗證您位於正確的執行緒。 其中具有發送器的角色。 發送器是基本訊息發送系統,具有多個已設定優先權的佇列。 訊息範例包括未經處理的輸入通知 (滑鼠移動)、架構功能 (版面配置) 或使用者命令 (執行此方法)。 藉由衍生自 DispatcherObject ,您可以建立具有 STA 行為的 CLR 物件,並在建立時提供發送器的指標。

System.Windows.DependencyObject

在建置 WPF 時所使用的其中一個主要架構哲學,是針對方法或事件的屬性喜好設定。 屬性是宣告式,可讓您更輕鬆地指定意圖,而不是動作。 這也支援用於顯示使用者介面內容的模型驅動或資料驅動系統。 此原理的預期效果是建立多個可繫結的屬性,以進一步控制應用程式的行為。

為了擁有更多由屬性驅動的系統,比 CLR 所提供的更豐富屬性系統。 這項豐富性的簡單範例是變更通知。 若要啟用雙向繫結,您需要繫結兩端都支援變更通知。 若要讓行為繫結至屬性值,您需要在屬性值變更時收到通知。 Microsoft .NET Framework 有一個介面 INotifyPropertyChange ,可讓物件發佈變更通知,但這是選擇性的。

WPF 提供更豐富的屬性系統,衍生自 DependencyObject 類型。 屬性系統實際上是「相依性」屬性系統,因此它會追蹤屬性運算式之間的相依性,並在相依性變更時自動重新驗證屬性值。 例如,如果您有繼承的屬性(例如 FontSize ),如果屬性在繼承值的元素父代上變更,系統就會自動更新。

WPF 屬性系統的基礎是屬性運算式的概念。 在這個 WPF 的第一個版本中,屬性運算式系統會關閉,而且這些運算式全都提供為架構的一部分。 運算式是屬性系統沒有資料繫結、樣式或繼承硬式編碼,而是由架構內的稍後層級所提供的原因。

屬性系統也會提供屬性值的疏鬆儲存體。 因為物件可以有數十個 (甚至數百個) 屬性,所以大部分的值都處於其預設狀態 (繼承、透過樣式設定等等),而且並非物件的每個執行個體都需要有其上定義之每個屬性的完整加權。

屬性系統的最終新功能是附加屬性概念。 WPF 元素是以組合和元件重複使用的原則為基礎所建置。 通常,有些包含元素(例如 Grid layout 元素)需要子項目的其他資料來控制其行為(例如 Row/Column 資訊)。 允許任何物件提供所有其他物件的屬性定義,而不是建立所有這些屬性與每個項目的關聯。 這與 JavaScript 的 "expando" 功能類似。

System.Windows.Media.Visual

定義系統之後,下一個步驟是將像素繪製到螢幕。 類別 Visual 提供建置視覺化物件的樹狀結構,每個物件都會選擇性地包含如何轉譯這些指令的繪圖指示和中繼資料(裁剪、轉換等)。 Visual 設計為極輕量且具有彈性,因此大部分功能都沒有任何公用 API 公開,而且高度依賴受保護的回呼函式。

Visual 實際上是 WPF 組合系統的進入點。 Visual 是這兩個子系統、受控 API 和 Unmanaged milcore 之間的連線點。

WPF 會透過周遊 milcore 所管理的 Unmanaged 資料結構來顯示資料。 這些結構稱為組合節點,代表具有每個節點之轉譯指示的階層式顯示樹狀結構。 下圖右側所說明的這個樹狀結構只能透過訊息通訊協定進行存取。

在進行 WPF 程式設計時,您會建立 Visual 元素和衍生類型,其會在內部透過這個傳訊通訊協定與組合樹狀結構通訊。 VisualWPF 中的每個專案可以建立一個、無或數個組合節點。

The Windows Presentation Foundation Visual Tree.

這裡需要注意一個極為重要的架構詳細資料:會快取視覺效果的整個樹狀結構以及繪圖指示。 在圖形詞彙中,WPF 會使用保留的轉譯系統。 這可讓系統以高重新整理速率重繪,而組合系統不會封鎖使用者程式碼的回呼。 這有助於防止出現沒有回應的應用程式。

圖表中實際上不大明顯的另一個重要詳細資料是系統如何實際執行組合。

在 User32 和 GDI 中,系統會在立即模式裁剪系統上運作。 需要轉譯元件時,系統會建立不允許元件接觸像素的外部裁剪界限,接著要求元件在該方塊中繪製像素。 此系統非常適合在記憶體受限系統中運作,因為內容變更時,您只需要接觸受影響元件;沒有兩個元件會形成單一像素的色彩。

WPF 使用「畫家演算法」繪製模型。 這表示,會要求每個元件從後到前轉譯顯示,而不是裁剪每個元件。 這可在前一個元件的顯示上方繪製每個元件。 此模型的優點在於您可以有複雜且部分透明的圖形。 使用現今的現代化圖形硬體,此模型相對較快(這不是建立 User32/ GDI 的情況)。

如先前所述,WPF 的核心哲學是移至程式設計更宣告的「以屬性為中心的」模型。 在視覺物件系統中,這出現在幾個有趣的地方。

首先,如果您考慮使用保留的模式圖形系統,則這真地會從命令式 DrawLine/DrawLine 類型模型移至資料導向模型:新的 Line()/新的 Line()。 這項移至資料驅動轉譯作業允許使用屬性來表示繪圖指示上的複雜作業。 衍生自 Drawing 的類型實際上是用於轉譯的物件模型。

其次,如果您評估動畫系統,則會看到它幾乎完全是宣告式。 您可以將動畫表示為動畫物件的一組屬性,而不需要開發人員計算下一個位置或下一個色彩。 這些動畫接著可以表示開發人員或設計人員的意圖 (在 5 秒內,將此按鈕從這裡移至目的地),而且系統可以判斷完成該作業的最有效方式。

System.Windows.UIElement

UIElement 定義核心子系統,包括版面配置、輸入和事件。

版面配置是 WPF 的核心概念。 在許多系統中,會有固定一組版面配置模型 (HTML 支援三種版面配置模型:非固定、絕對和資料表) 或沒有版面配置模型 (User32 實際上只支援絕對定位)。 WPF 一開始假設開發人員和設計工具想要彈性且可延伸的版面配置模型,而這種模型可由屬性值驅動,而不是命令式邏輯。 UIElement在層級上,會引進配置的基本合約 – 具有 MeasureArrange 傳遞的兩個階段模型。

Measure 可讓元件判斷其需要多少大小。 這是與 不同的階段 Arrange ,因為有許多情況下,父元素會要求子系測量數次,以判斷其最佳位置和大小。 父元素要求子項目測量的事實會示範 WPF 的另一個重要哲學 – 大小到內容。 WPF 中的所有控制項都支援調整其內容自然大小的能力。 這會讓當地語系化更為簡單,並在調整事物時允許動態配置項目。 階段 Arrange 可讓父代定位並判斷每個子系的最終大小。

經常花很多時間談論 WPF Visual 的輸出端和相關的物件。 不過,輸入端上也會進行極大的創新。 WPF 輸入模型中最根本的變更可能是 consis帳篷模式l,輸入事件會透過系統路由傳送。

輸入來源是核心模式裝置驅動程式上的訊號,並且透過包含 Windows 核心和 User32 的複雜處理序路由傳送至正確的處理序和執行緒。 一旦對應至輸入的 User32 訊息路由傳送至 WPF,就會轉換成 WPF 原始輸入訊息,並傳送至發送器。 WPF 允許原始輸入事件轉換成多個實際事件,讓 「MouseEnter」 等功能能夠在保證傳遞的低層級系統實作。

每個輸入事件都會轉換成至少兩個事件:「預覽」事件和實際事件。 WPF 中的所有事件都有透過專案樹狀結構路由的概念。 如果事件從樹狀結構上的目標周遊到根目錄,就會「泡泡」事件,如果它們從根目錄開始,並向下周遊到目標,就會被說為「通道」。 輸入預覽事件通道,讓樹狀結構中的任何項目有機會進行篩選或對事件採取動作。 一般 (非預覽) 事件接著會從目標往上反昇到根。

通道與事件反昇階段之間的這項分割可讓鍵盤快速鍵這類功能的實作在複合情況下以一致的方式運作。 在 User32 中,您將實作鍵盤快速鍵,方法是具有包含所有您要支援之快速鍵的單一全域資料表 (Ctrl+N 對應至 "New")。 在應用程式的發送器中,您會呼叫可發覺 User32 中輸入訊息的 TranslateAccelerator,並判斷是否有任何快速鍵符合已註冊的快速鍵。 在 WPF 中,這無法運作,因為系統完全「可組合」–任何元素都可以處理並使用任何鍵盤快速鍵。 具有這個兩階段輸入模型可讓元件實作其專屬 "TranslateAccelerator"。

若要進一步採取這一步, UIElement 也引進 CommandBindings 的概念。 WPF 命令系統可讓開發人員在命令結束點定義功能, 這是實作 的 ICommand 。 命令繫結可讓項目定義輸入手勢 (Ctrl+N) 與命令 (New) 之間的對應。 輸入手勢和命令定義都是可以擴充的,而且可以在使用階段連接在一起。 例如,這會讓允許使用者自訂其想要在應用程式內使用的重要繫結變得簡單。

在本主題中,WPF 的「核心」功能 – 在 PresentationCore 元件中實作的功能一直是焦點。 建置 WPF 時,基底片段(例如使用 Measure Arrange 的版面配置合約)和架構片段(例如實作之類的 Grid 特定版面配置)之間的乾淨分隔是所需的結果。 目標是提供堆疊低層的擴充點,以讓外部開發人員視需要建立其專屬架構。

System.Windows.FrameworkElement

FrameworkElement 可以透過兩種不同的方式來查看。 它會在較低層 WPF 中引進的子系統上引進一組原則和自訂專案。 它同時引進一組新的子系統。

引進 FrameworkElement 的主要原則是圍繞應用程式佈建。 FrameworkElement 以 所 UIElement 引進的基本版面配置合約為基礎,並新增版面配置「位置」的概念,讓版面配置作者更容易擁有一組一致的屬性驅動版面配置語意。 、、 和 MarginHorizontalAlignment 屬性(以幾個名稱命名)提供所有衍生自 FrameworkElement 版面配置容器內一 MinWidth 致行為的元件。 VerticalAlignment

FrameworkElement 也可讓您更輕鬆地接觸 WPF 核心層中找到的許多功能。 例如, FrameworkElement 提供透過 BeginStoryboard 方法直接存取動畫。 Storyboard提供一種方式,針對一組屬性編寫多個動畫的腳本。

引進的兩項最重要事項 FrameworkElement 是資料系結和樣式。

WPF 中的資料系結子系統應該對已使用 Windows Forms 或 ASP.NET 建立應用程式使用者介面 (UI) 的任何人而言相當熟悉。 在所有這些系統中,有一種簡單的方式可表示您想要一或多個屬性從指定的項目繫結至資料的某個部分。 WPF 完全支援屬性系結、轉換和清單系結。

WPF 中資料系結最有趣的功能之一是引進資料範本。 資料範本可讓您以宣告方式指定應該如何視覺化資料。 您可以解決問題,並讓資料判斷將建立的顯示,而不是建立可繫結至資料的自訂使用者介面。

樣式實際上是簡單形式的資料繫結。 使用樣式,您可以將共用定義中的一組屬性繫結至項目的一或多個執行個體。 樣式會透過明確參考來套用至專案(藉由設定 Style 屬性),或隱含地將樣式與元素的 CLR 類型產生關聯。

System.Windows.Controls.Control

控制項的最重要功能是設定範本。 如果您將 WPF 的組合系統視為保留模式轉譯系統,則設定範本可讓控制項透過參數化宣告方式描述其轉譯。 ControlTemplate實際上只不過是用來建立一組子項目的腳本,而 控制項所提供屬性的系結。

Control 提供一組股票屬性 、 ForegroundBackgroundPadding 來命名幾個屬性,然後範本作者可以使用這些屬性來自訂控制項的顯示。 控制項的實作會提供資料模型和互動模型。 互動模型定義一組命令 (例如視窗的 Close) 以及輸入手勢的繫結 (例如按一下視窗右上角的紅色 X)。 資料模型提供一組屬性來自訂互動模型或是自訂顯示 (透過範本決定)。

資料模型 (屬性)、互動模型 (命令和事件) 與顯示模型 (範本) 之間的這項分割可啟用控制項外觀和行為的完整自訂。

控制項資料模型的一般層面是內容模型。 如果您查看類似 Button 的控制項,您會看到其具有類型 Object 為 「Content」 的屬性。 在 Windows Forms 和 ASP.NET 中,此屬性通常是一個字串,但會限制您可以放入按鈕的內容類型。 按鈕的內容可以是簡單字串、複雜資料物件或整個項目樹狀結構。 如果是資料物件,則使用資料範本來建構顯示。

摘要

WPF 的設計目的是讓您建立動態的資料驅動簡報系統。 系統的每個組件都是設計成透過可驅動行為的屬性集來建立物件。 資料繫結是系統的基礎部分,並且在各層級進行整合。

傳統應用程式會建立顯示,然後繫結至一些資料。 在 WPF 中,控制項的所有專案,顯示的每個層面,都是由某種類型的資料系結所產生。 在按鈕內建立組合的控制項,並將其顯示繫結至按鈕的內容屬性,即會顯示在按鈕內找到的文字。

當您開始開發 WPF 型應用程式時,應該會感到非常熟悉。 您可以使用與使用 Windows Forms 或 ASP.NET 相同的方式來設定屬性、使用物件和資料系結。 對 WPF 架構進行更深入的調查後,您會發現建立更豐富的應用程式的可能性,基本上會將資料視為應用程式的核心驅動程式。

另請參閱