WPF 中的樹狀結構

在許多技術中,元素和元件都會組織成樹狀結構,開發人員可直接管理樹狀結構中的物件節點來影響應用程式的轉譯或行為。 Windows Presentation Foundation (WPF) 也會使用數個樹狀結構隱喻來定義程式專案之間的關聯性。 在大部分情形下,WPF 開發人員在概念上思考物件樹狀結構比喻時,可以使用程式碼建立應用程式或使用 XAML 定義應用程式的部分,但將會呼叫特定的 API 或使用特定標記來進行這些作業,而不是使用某些一般的物件樹狀結構管理 API,就像您可能在 XML DOM 中使用的方式。 WPF 會公開兩個提供樹狀隱喻檢視 LogicalTreeHelperVisualTreeHelper 的協助程式類別。 WPF 文件中也會使用視覺化樹狀結構和邏輯樹狀結構等詞彙,因為這些相同的樹狀結構在了解某些主要的 WPF 功能時非常好用。 本主題定義視覺化樹狀結構和邏輯樹狀結構所代表的內容、討論這類樹狀結構與整體物件樹狀結構概念的關聯性,並介紹 LogicalTreeHelperVisualTreeHelper

WPF 中的樹狀結構

WPF 中最完整的樹狀結構是物件樹狀結構。 如果您在 XAML 中定義應用程式頁面,然後載入 XAML,則會根據標記中元素的巢狀關聯性來建立樹狀結構。 如果您利用程式碼定義應用程式或應用程式的一部分,則會依據下列方式來建立樹狀結構:您如何針對用於實作指定物件之內容模型的屬性指派屬性值。 在 WPF 中,有兩種方式可將完整的物件樹狀結構概念化,並可以回報給其公用 API:做為邏輯樹狀結構,以及做為視覺化樹狀結構。 邏輯樹狀結構與視覺化樹狀結構之間的差異不一定很重要,但它們偶爾可能會導致某些 WPF 子系統的問題,並影響您在標記或程式碼中所做的選擇。

即使您不一定會直接管理邏輯樹狀結構或視覺化樹狀結構,但了解這些樹狀結構的互動方式,將有助於對 WPF 這種技術的認識。 將 WPF 視為某種樹狀結構的隱喻,對於瞭解屬性繼承和事件路由在 WPF 中的運作方式也至關重要。

注意

因為物件樹狀結構比較接近是個概念而非實際的 API,所以另一種想像這個概念的方式是當成物件圖形。 實際上,物件之間的關聯性在執行階段,可能會讓樹狀結構比喻失效。 不過,特別是對以 XAML 定義的 UI 而言,樹狀結構比喻仍算是恰當,因此大部分的 WPF 文件在提及這個一般概念時,將會使用物件樹狀結構一詞。

邏輯樹狀結構

在 WPF 中,您可以藉由設定這些元素之物件的屬性,將內容新增至 UI 元素。 例如,您可以藉由操作其 Items 屬性,將專案新增至 ListBox 控制項。 如此一來,您會將專案 ItemCollection 放入 屬性值的 Items 中。 同樣地,若要將 物件加入 至 DockPanel ,您可以操作其 Children 屬性值。 在這裡,您要將 物件新增至 UIElementCollection 。 如需程式碼範例,請參閱 如何:動態 新增專案。

在 Extensible Application Markup Language (XAML)中,當您將清單專案放在 或 控制項或其他 UI 元素中 ListBoxDockPanel 時,您也可以明確地或隱含地使用 ItemsChildren 屬性,如下列範例所示。

<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 初始化,但為空白。 然後,剖析器會呼叫 ItemCollection.Add ,將存在於 做為 內容 ListBox 的每個子物件專案加入至 ItemCollection 。 目前為止,這個將 XAML 處理到物件樹狀結構中的範例,看起來很像所建立物件樹狀結構基本上是邏輯樹狀結構的範例。

不過,邏輯樹狀結構並不是在執行時間針對應用程式 UI 存在的整個物件圖形,即使 XAML 隱含語法專案也納入考慮。主要原因是視覺效果和範本。 例如,請考慮 Button 。 邏輯樹狀結構會 Button 報告 物件及其字串 Content 。 但在執行階段的物件樹狀結構中,還有更多關於此按鈕的資訊。 特別是,按鈕只會以套用特定 Button 控制項範本的方式出現在畫面上。 來自已套用範本的視覺效果(例如視覺效果按鈕周圍深灰色的範本定義 Border )不會在邏輯樹狀結構中報告,即使您在執行時間查看邏輯樹狀結構(例如從可見 UI 處理輸入事件,然後讀取邏輯樹狀結構)。 若要尋找範本視覺效果,您需要改為檢查視覺化樹狀結構。

如需 XAML 語法如何對應至所建立物件圖形以及 XAML 中的隱含語法的詳細資訊,請參閱 WPF 中的 XAML 語法詳細 資料或 XAML。

邏輯樹狀結構的用途

邏輯樹狀結構的存在,是要讓內容模型可立即逐一查看其可能的子物件,並使內容模型得以延伸。 此外,邏輯樹狀結構也可提供某些通知適用的架構,例如,載入邏輯樹狀結構中的所有物件時。 基本上,邏輯樹狀結構近似於架構層級的執行階段物件圖形,其會排除視覺效果,但足以對您自己的執行階段應用程式組合進行許多查詢作業。

此外,靜態和動態資源參考都會透過邏輯樹狀結構向上查看初始要求物件上的集合,然後繼續邏輯樹 Resources 狀結構,並檢查每個 FrameworkElement 包含 另一個 ResourcesFrameworkContentElementResourceDictionary ,其中可能包含該索引鍵。 當邏輯樹狀結構和視覺化樹狀結構同時存在時,資源查閱會使用邏輯樹狀結構。 如需資源字典和查閱的詳細資訊,請參閱 XAML 資源

邏輯樹狀結構的組合

邏輯樹狀結構是在 WPF 架構層級定義,這表示與邏輯樹狀結構作業最相關的 WPF 基底專案為 FrameworkElementFrameworkContentElement 。 不過,如您實際使用 LogicalTreeHelper API 一樣,邏輯樹狀結構有時會包含不是 FrameworkElementFrameworkContentElement 的節點。 例如,邏輯樹狀結構會報告 TextTextBlock 的值,這是字串。

覆寫邏輯樹狀結構

進階控制項作者可以覆寫數個 API 來覆寫邏輯樹狀結構,以定義一般物件或 con帳篷模式l 如何在邏輯樹狀結構中新增或移除物件。 如需如何覆寫邏輯樹狀結構的範例,請參閱覆寫邏輯樹狀結構

屬性值繼承

屬性值繼承會透過混合式樹狀結構來進行。 包含 Inherits 屬性繼承之屬性的實際中繼資料是 WPF 架構層級 FrameworkPropertyMetadata 類別。 因此,同時保留原始值的父代和繼承該值的子物件必須是 FrameworkElementFrameworkContentElement ,而且兩者都必須是某些邏輯樹狀結構的一部分。 不過,對於支援屬性繼承的現有 WPF 屬性而言,透過不在邏輯樹狀結構中的中間物件,屬性值繼承便能永遠存在。 主要是因為這與讓範本元素使用任何繼承屬性值有關,這些值是設定於套用範本的執行個體上,或設定於比頁面層級組合還要更高的層級中,因而在邏輯樹狀結構中會比較高。 為了使屬性值繼承能夠跨這類界限一致地運作,必須將繼承屬性註冊為附加屬性,而如果您想要利用屬性繼承行為來定義自訂的相依性屬性,則必須遵循這個模式。 Helper 類別公用程式方法完全無法預期屬性繼承所使用的實際樹狀結構,即使在執行階段也一樣。 如需詳細資訊,請參閱屬性值繼承

視覺化樹狀結構

除了邏輯樹狀結構的概念之外,WPF 中也有視覺化樹狀結構的概念。 視覺化樹狀結構描述以基類表示 Visual 的視覺物件結構。 當您撰寫控制項的範本時,就是在定義或重新定義適用於該控制項的視覺化樹狀結構。 視覺化樹狀結構也可引起開發人員的關注,讓想要對繪製作業採取低階控制的開發人員,能夠改善效能並進行最佳化。 作為傳統 WPF 應用程式程式設計一部分的視覺化樹狀結構暴露,就是路由事件的事件路由大多沿著視覺化樹狀結構移動,而不是邏輯樹狀結構。 除非您是控制項作者,否則不容易立即察覺到此路由事件行為的細微差異。 透過視覺化樹狀結構路由傳送事件,就能讓在視覺化層級中實作組合的控制項處理事件或建立事件 setter。

樹狀結構、內容項目元素及內容主機

內容元素(衍生自 ContentElement 的類別)不是視覺化樹狀結構的一部分;它們不會繼承自 Visual ,而且沒有視覺標記法。 若要完全出現在 UI 中, ContentElement 必須將 裝載在同時 Visual 為 和邏輯樹狀結構參與者的內容主機中。 通常這類物件是 FrameworkElement 。 您可以將內容主機想像成某些像是內容「瀏覽器」的項目,並選擇如何在主機控制的螢幕區域內顯示該內容。 裝載內容之後,該內容就可成為某些樹狀結構處理序 (通常會與視覺化樹狀結構相關聯) 中的參與者。 一般而言,主機類別包含實作程式碼, FrameworkElement 這些實作程式碼會透過內容邏輯樹狀結構的子節點新增任何裝載 ContentElement 至事件路由,即使裝載的內容不是真實視覺化樹狀結構的一部分也一樣。 這是必要的,讓 ContentElement 可以產生路由事件,以路由傳送至本身以外的任何專案。

樹狀周遊

類別 LogicalTreeHelper 提供邏輯樹狀結構周遊的 GetChildrenGetParentFindLogicalNode 方法。 在大部分情況下,您應該不需要周遊現有控制項的邏輯樹狀結構,因為這些控制項幾乎都會將其邏輯子元素公開為專用的集合屬性,以支援集合存取,例如 Add、索引子等等。 樹狀結構周遊主要是由選擇不衍生自預定控制項模式的控制項作者使用的案例,例如 ItemsControlPanel 已定義集合屬性的位置,以及想要提供自己的集合屬性支援的人員。

視覺化樹狀結構也支援視覺化樹狀結構周遊的協助程式類別。 VisualTreeHelper 視覺化樹狀結構不會透過控制項特定屬性方便公開,因此 VisualTreeHelper ,如果您的程式設計案例需要,類別是周遊視覺化樹狀結構的建議方式。 如需詳細資訊,請參閱 WPF 圖形轉譯概觀

注意

有時必須檢查所套用範本的視覺化樹狀結構。 您應該謹慎使用此技術。 即使您在定義範本的控制項周遊視覺化樹狀結構,控制項的取用者仍可藉由在 實例上設定 Template 屬性來變更範本,甚至使用者也可以藉由變更系統主題來影響套用的範本。

路由事件的樹狀結構路由

如前所述,任何指定路由事件的路由是在樹狀結構中沿著預先決定的單一路徑來周遊,該樹狀結構是視覺化樹狀結構和邏輯樹狀結構表示法的混合。 依據事件路由為通道或事件反昇的路由事件而定,其會在樹狀結構內向上或向下進行周遊。 事件路由概念沒有直接支援的 Helper 類別可用來在事件路由上「前進」,而不管是否會引發實際路由傳送的事件。 有一個類別代表路由, EventRoute 但該類別的方法通常僅供內部使用。

資源字典和樹狀目錄

對於頁面中定義的所有 Resources 的資源字典查閱,基本上會周遊邏輯樹狀結構。 不在邏輯樹狀結構中的物件可以參考具有索引鍵的資源,但資源查閱序列是從物件連接到邏輯樹狀結構的點開始。 在 WPF 中,只有邏輯樹狀節點可以有 Resources 包含 ResourceDictionary 的屬性,因此在周遊視覺化樹狀結構時沒有好處,尋找來自 ResourceDictionary 的索引鍵資源。

不過,資源查閱也可以延伸至目前邏輯樹狀結構以外的地方。 對於應用程式標記,資源查閱可接著繼續前進到應用程式層級的資源字典,然後到做為靜態屬性或索引鍵加以參考的佈景主題支援和系統值。 如果資源參考是動態的,則佈景主題本身也可以參考佈景主題邏輯樹狀結構以外的系統值。 如需資源字典和查閱邏輯的詳細資訊,請參閱 XAML 資源

另請參閱