WPF 中的樹狀結構
在許多技術中,元素和元件都會組織成樹狀結構,開發人員可直接管理樹狀結構中的物件節點來影響應用程式的轉譯或行為。 Windows Presentation Foundation (WPF) 也會使用數個樹狀結構比喻來定義程式元素之間的關聯性。 在大部分情形下,WPF 開發人員在概念上思考物件樹狀結構比喻時,可以使用程式碼建立應用程式或使用 XAML 定義應用程式的部分,但將會呼叫特定的 API 或使用特定標記來進行這些作業,而不是使用某些一般的物件樹狀結構管理 API,就像您可能在 XML DOM 中使用的方式。 WPF 會公開兩個協助程式類別,以提供樹狀結構隱喻檢視,LogicalTreeHelper 和 VisualTreeHelper。 WPF 文件中也會使用視覺化樹狀結構和邏輯樹狀結構等詞彙,因為這些相同的樹狀結構在了解某些主要的 WPF 功能時非常好用。 本主題將定義視覺化樹狀結構和邏輯樹狀結構所代表的意義、討論這類樹狀結構與整體物件樹狀結構概念的關聯性,以及介紹 LogicalTreeHelper 和 VisualTreeHelper。
WPF 中的樹狀結構
WPF 中最完整的樹狀結構就是物件樹狀結構。 如果您以 XAML 定義應用程式頁面,然後載入 XAML,則會依據標記中元素的巢狀關聯性來建立樹狀結構。 如果您利用程式碼定義應用程式或應用程式的一部分,則會依據下列方式來建立樹狀結構:您如何針對用於實作指定物件之內容模型的屬性指派屬性值。 在 WPF 中,將完整的物件樹狀結構概念化並可回報給其公用 API 的方式有兩種:當做邏輯樹狀結構,以及當做視覺化樹狀結構。 邏輯樹狀結構與視覺化樹狀結構之間的差異不一定很重要,但有時可能會導致某些 WPF 子系統發生問題,並影響您對於使用標記或程式碼的選擇。
即使您不一定會直接管理邏輯樹狀結構或視覺化樹狀結構,但了解這些樹狀結構的互動方式,將有助於對 WPF 這種技術的認識。 將 WPF 比喻為某種樹狀結構,對於了解 WPF 中屬性繼承和事件路由的運作方式也很重要。
注意
因為物件樹狀結構比較接近是個概念而非實際的 API,所以另一種想像這個概念的方式是當成物件圖形。 實際上,物件之間的關聯性在執行階段,可能會讓樹狀結構比喻失效。 不過,特別是對以 XAML 定義的 UI 而言,樹狀結構比喻仍算是恰當,因此大部分的 WPF 文件在提及這個一般概念時,將會使用物件樹狀結構一詞。
邏輯樹狀結構
在 WPF 中,您要將內容加入至 UI 元素,方法則是為支援這些元素的物件設定屬性。 例如,您可以藉由操作 ListBox 屬性,將項目新增至 Items 控制項。 如此一來,您會將項目放入 ItemCollection 屬性值的 Items 中。 同樣地,若要將物件新增至 DockPanel,您可以操作其 Children 屬性值。 在這裡,您要將物件新增至 UIElementCollection。 如需程式碼範例,請參閱操作說明:以動態方式加入元素。
在 Extensible Application Markup Language (XAML) 中,當您將清單項目放置在 ListBox 中或將控制項或其他 UI 元素放置在 DockPanel 時,您也可以明確或隱含地使用 Items 和 Children 屬性,如下列範例所示。
<DockPanel
Name="ParentElement"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<!--implicit: <DockPanel.Children>-->
<ListBox DockPanel.Dock="Top">
<!--implicit: <ListBox.Items>-->
<ListBoxItem>
<TextBlock>Dog</TextBlock>
</ListBoxItem>
<ListBoxItem>
<TextBlock>Cat</TextBlock>
</ListBoxItem>
<ListBoxItem>
<TextBlock>Fish</TextBlock>
</ListBoxItem>
<!--implicit: </ListBox.Items>-->
</ListBox>
<Button Height="20" Width="100" DockPanel.Dock="Top">Buy a Pet</Button>
<!--implicit: </DockPanel.Children>-->
</DockPanel>
如果您原本要處理這個 XAML 做為文件物件模型下的 XML,而且已包含註解化為隱含的標記 (這應該已合法),則產生的 XML DOM 樹狀結構應該已包含 <ListBox.Items>
的元素及其他隱含項目。 但是當您讀取標記並寫入至物件時,XAML 不會以該方式處理,因此,產生的物件圖形實際上不會包含 ListBox.Items
。 然而,它確實有一個名為 ListBox 的 Items
屬性,其中包含 ItemCollection,並且在處理 ItemCollection XAML 時,ListBox 已初始化但為空白。 然後,作為 ListBox 內容存在的每個子物件元素都會透過剖析器呼叫 ItemCollection 新增至 ItemCollection.Add
。 目前為止,這個將 XAML 處理到物件樹狀結構中的範例,看起來很像所建立物件樹狀結構基本上是邏輯樹狀結構的範例。
不過,即使去除 XAML 隱含語法項目的因素,邏輯樹狀結構並不是會在執行階段針對應用程式 UI 存在的完整物件圖形。針對這點的主要原因是視覺效果和範本。 例如,請考量 Button。 邏輯樹狀結構會回報 Button 物件及其字串 Content
。 但在執行階段的物件樹狀結構中,還有更多關於此按鈕的資訊。 特別是,按鈕只會以其特定方式出現在畫面上,這是因為已套用特定的 Button 控制項範本。 即使您正在執行階段查看邏輯樹狀結構 (例如,處理視覺化 UI 的輸入事件,然後讀取邏輯樹狀結構),邏輯樹狀結構中還是不會報告來自已套用範本的視覺效果 (例如,位於視覺化按鈕周圍深灰色框線之範本定義的 Border)。 若要尋找範本視覺效果,您需要改為檢查視覺化樹狀結構。
如需 XAML 語法與所建立物件圖形的對應方式以及 XAML 中隱含語法的詳細資訊,請參閱 XAML 語法詳細資料或 WPF 中的 XAML。
邏輯樹狀結構的用途
邏輯樹狀結構的存在,是要讓內容模型可立即逐一查看其可能的子物件,並使內容模型得以延伸。 此外,邏輯樹狀結構也可提供某些通知適用的架構,例如,載入邏輯樹狀結構中的所有物件時。 基本上,邏輯樹狀結構近似於架構層級的執行階段物件圖形,其會排除視覺效果,但足以對您自己的執行階段應用程式組合進行許多查詢作業。
此外,靜態和動態資源參考都會透過邏輯樹狀結構向上查看初始要求物件上的 Resources 集合,然後繼續向上查看邏輯樹狀結構並檢查每個 FrameworkElement (或 FrameworkContentElement) 中包含Resources
的另一個 ResourceDictionary 值來解析,其可能包含該索引鍵。 當邏輯樹狀結構和視覺化樹狀結構同時存在時,資源查閱會使用邏輯樹狀結構。 如需資源字典和查閱的詳細資訊,請參閱 XAML 資源。
邏輯樹狀結構的組合
邏輯樹狀結構定義於 WPF 架構層級,這表示與邏輯樹狀結構作業最相關的 WPF 基底元素是 FrameworkElement 或 FrameworkContentElement。 不過,如您所看到的,如果您實際使用 LogicalTreeHelper API,邏輯樹狀結構有時會包含不是 FrameworkElement 或 FrameworkContentElement 的節點。 例如,邏輯樹狀結構會回報 Text 的 TextBlock 值,也就是字串。
覆寫邏輯樹狀結構
資深的控制項作者可藉由覆寫數個用來定義一般物件或內容模型如何在邏輯樹狀結構內加入或移除物件的 API,來覆寫邏輯樹狀結構。 如需如何覆寫邏輯樹狀結構的範例,請參閱覆寫邏輯樹狀結構。
屬性值繼承
屬性值繼承會透過混合式樹狀結構來進行。 包含可啟用屬性繼承之 Inherits 屬性的實際中繼資料是 WPF 架構層級 FrameworkPropertyMetadata 類別。 因此,保存原始值的父物件和繼承該值的子物件必須都是 FrameworkElement 或 FrameworkContentElement,而且必須都屬於某個邏輯樹狀結構。 不過,對於支援屬性繼承的現有 WPF 屬性而言,透過不在邏輯樹狀結構中的中間物件,屬性值繼承便能永遠存在。 主要是因為這與讓範本元素使用任何繼承屬性值有關,這些值是設定於套用範本的執行個體上,或設定於比頁面層級組合還要更高的層級中,因而在邏輯樹狀結構中會比較高。 為了使屬性值繼承能夠跨這類界限一致地運作,必須將繼承屬性註冊為附加屬性,而如果您想要利用屬性繼承行為來定義自訂的相依性屬性,則必須遵循這個模式。 Helper 類別公用程式方法完全無法預期屬性繼承所使用的實際樹狀結構,即使在執行階段也一樣。 如需詳細資訊,請參閱屬性值繼承。
視覺化樹狀結構
除了邏輯樹狀結構的概念,WPF 中還有視覺化樹狀結構的概念。 視覺化樹狀結構會描述視覺化物件的結構,就如同 Visual 基底類別所表示。 當您撰寫控制項的範本時,就是在定義或重新定義適用於該控制項的視覺化樹狀結構。 視覺化樹狀結構也可引起開發人員的關注,讓想要對繪製作業採取低階控制的開發人員,能夠改善效能並進行最佳化。 通常在進行 WPF 應用程式程式設計時,公開視覺化樹狀結構的方式之一,就是路由事件的事件在路由傳送時大多會沿著視覺化樹狀結構進行周遊,而非邏輯樹狀結構。 除非您是控制項作者,否則不容易立即察覺到此路由事件行為的細微差異。 透過視覺化樹狀結構路由傳送事件,就能讓在視覺化層級中實作組合的控制項處理事件或建立事件 setter。
樹狀結構、內容項目元素及內容主機
內容項目元素 (衍生自ContentElement 的類別) 不屬於視覺化樹狀結構,它們不是繼承自 Visual,也沒有視覺化表示法。 為了在 UI 中顯示,ContentElement 必須裝載在既是 Visual 又是邏輯樹狀結構參與者的內容主機中。 通常這類物件是 FrameworkElement。 您可以將內容主機想像成某些像是內容「瀏覽器」的項目,並選擇如何在主機控制的螢幕區域內顯示該內容。 裝載內容之後,該內容就可成為某些樹狀結構處理序 (通常會與視覺化樹狀結構相關聯) 中的參與者。 一般而言,FrameworkElement 裝載類別包含實作程式碼,此實作程式碼會透過內容邏輯樹狀結構的子節點,將任何裝載的 ContentElement 加入到事件路由,即使裝載的內容不屬於實際的視覺化樹狀結構也一樣。 這是必要的,如此一來,ContentElement 就能獲得路由傳送至本身以外任何元素的路由事件。
樹狀周遊
LogicalTreeHelper 類別提供邏輯樹狀結構周遊的 GetChildren、GetParent 和 FindLogicalNode 方法。 在大部分情況下,您應該不需要周遊現有控制項的邏輯樹狀結構,因為這些控制項幾乎都會將其邏輯子元素公開為專用的集合屬性,以支援集合存取,例如 Add
、索引子等等。 樹狀周遊主要是由下列控制項作者所使用的案例:選擇不從適用的控制項模式 (例如 ItemsControl 或 Panel,其中已經定義集合屬性) 衍生的控制項作者,以及想要提供自己之集合屬性支援的控制項作者。
視覺化樹狀結構也支援可進行視覺化樹狀結構周遊的 Helper 類別 VisualTreeHelper。 由於視覺化樹狀結構無法方便地透過控制項特定屬性來公開,因此,如果您的程式設計案例有該需求時,建議使用 VisualTreeHelper 類別來周遊視覺化樹狀結構。 如需詳細資訊,請參閱 WPF 圖形轉譯概觀。
注意
有時必須檢查所套用範本的視覺化樹狀結構。 您應該謹慎使用此技術。 即使您正在定義範本之控制項的視覺化樹狀結構中周遊,控制項的取用者總是能夠藉由在執行個體上設定 Template 屬性來變更範本,甚至使用者也可藉由變更系統佈景主題來影響已套用的範本。
路由事件的樹狀結構路由
如前所述,任何指定路由事件的路由是在樹狀結構中沿著預先決定的單一路徑來周遊,該樹狀結構是視覺化樹狀結構和邏輯樹狀結構表示法的混合。 依據事件路由為通道或事件反昇的路由事件而定,其會在樹狀結構內向上或向下進行周遊。 事件路由概念沒有直接支援的 Helper 類別可用來在事件路由上「前進」,而不管是否會引發實際路由傳送的事件。 有一個代表路由的類別 EventRoute,但此類別的方法一般僅供內部使用。
資源字典和樹狀目錄
對於頁面中定義的所有 Resources
的資源字典查閱,基本上會周遊邏輯樹狀結構。 不在邏輯樹狀結構中的物件可以參考具有索引鍵的資源,但資源查閱序列是從物件連接到邏輯樹狀結構的點開始。 在 WPF 中,只有邏輯樹狀結構節點能有包含 Resources
的 ResourceDictionary 屬性,因此,周遊視覺化樹狀結構以尋找來自 ResourceDictionary 且具索引鍵的資源並無任何好處。
不過,資源查閱也可以延伸至目前邏輯樹狀結構以外的地方。 對於應用程式標記,資源查閱可接著繼續前進到應用程式層級的資源字典,然後到做為靜態屬性或索引鍵加以參考的佈景主題支援和系統值。 如果資源參考是動態的,則佈景主題本身也可以參考佈景主題邏輯樹狀結構以外的系統值。 如需資源字典和查閱邏輯的詳細資訊,請參閱 XAML 資源。