WPF 中的树

更新:2007 年 11 月

在许多技术中,元素和组件按照树结构的形式组织,在这种结构中,开发人员可以直接操作树来影响应用程序的呈现。Windows Presentation Foundation (WPF) 还使用了多个树结构术语来定义程序元素之间的关系。

本主题包括下列各节。

  • WPF 中的树
  • 逻辑树
  • 可视化树
  • 树、内容元素和内容宿主
  • 树遍历
  • “树”形式路由事件的路由
  • 资源和树
  • 相关主题

WPF 中的树

WPF 中主要的树结构是元素树。如果使用 XAML 创建应用程序页,则将基于标记中元素的嵌套关系创建树结构。如果使用代码创建应用程序,则将基于为属性(实现给定元素的内容模型)指定属性值的方式创建树结构。在 Windows Presentation Foundation (WPF) 中,处理和使用概念说明元素树的方法实际上有两种:即逻辑树和可视化树。逻辑树与可视化树之间的区别并不始终很重要,但在某些 WPF 子系统中它们可能会偶尔导致问题,并影响您对标记或代码的选择。

尽管您不会始终直接操作逻辑树或可视化树,但理解树之间如何进行交互的概念也是理解 WPF 中的属性继承和事件路由如何工作的一种方法。

逻辑树

在 WPF 中,可使用属性向元素中添加内容。例如,使用 ListBox 控件的 Items 属性可向该控件中添加项。通过此方式,可将项放置到 ListBox 控件的 ItemCollection 中。若要向 DockPanel 中添加元素,可使用其 Children 属性。此时,将向 DockPanelUIElementCollection 中添加元素。有关代码示例,请参见如何:动态添加元素

在可扩展应用程序标记语言 (XAML) 中,当在 ListBox 中放置列表项或在 DockPanel 中放置控件或其他元素时,还可显式或隐式使用 ItemsChildren 属性,如以下示例所示。

<DockPanel
  Name="ParentElement"
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
  >

  <!--implicit: <DockPanel.Children>-->
  <ListBox DockPanel.Dock="Top">
    <!--implicit: <ListBox.Items>-->
    <ListItem>
      <Paragraph>Dog</Paragraph>
    </ListItem>
    <ListItem>
      <Paragraph>Cat</Paragraph>
    </ListItem>
    <ListItem>
      <Paragraph>Fish</Paragraph>
    </ListItem>
    <!--implicit: </ListBox.Items>-->
  </ListBox>
  <Button Height="20" Width="100" DockPanel.Dock="Top">Buy a Pet</Button>

  <!--implicit: </DockPanel.Children>-->
</DockPanel>

请注意,不需要显式属性元素标记,因为 XAML 读取器在创建对象(创建应用程序的可执行文件的运行时对象表示形式)时会推断属性元素。有关 XAML 语法映射到所创建逻辑树的方式以及推断标记的更多信息,请参见 XAML 语法术语XAML 概述。下图提供了运行时构造的逻辑树的概念视图(图中省略了按钮的分支)。

泛型逻辑树示意图

树示意图

逻辑树用途

逻辑树的存在用途是使内容模型可以容易地循环访问其可能包含的子元素,从而可以对内容模型进行扩展。此外,逻辑树还为某些通知提供了框架,例如当加载逻辑树中的所有元素时。

此外,在 Resources 集合的逻辑树中首先向上查找初始请求元素,然后再查找父元素,这样可以解析资源引用。当同时存在逻辑树和可视化树时,将使用逻辑树进行资源查找。有关资源的更多信息,请参见资源概述

重写逻辑树

高级控件作者可以通过重写多个 API(用于定义常规对象或内容模型如何在逻辑树中添加或移除元素)来重写逻辑树。有关如何重写逻辑树的示例,请参见如何:重写逻辑树

属性值继承

属性值继承通过混合树操作。包含用于启用属性继承的 Inherits 属性的实际元数据是 WPF 框架级别  FrameworkPropertyMetadata 类。因此,保留原始值的父元素以及继承该父元素的子元素都必须是 FrameworkElementFrameworkContentElement,并且它们都必须属于某个逻辑树的一部分。但是,允许父元素的逻辑树与子元素的逻辑树相互独立,这样可以通过一个不在逻辑树中的中介可视元素使属性值继承永续进行。若要使属性值继承在这样的界限中以一致的方式工作,必须将继承属性注册为附加属性。通过帮助器类实用工具方法无法完全预测属性继承确切使用的树,即使在运行时也一样。有关更多信息,请参见属性值继承

可视化树

WPF 中除了逻辑树的概念,还存在可视化树的概念。可视化树描述由 Visual 基类表示的可视化对象的结构。为控件编写模板时,将定义或重新定义适用于该控件的可视化树。对于出于性能和优化原因想要对绘图进行较低级别控制的开发人员来说,他们也会对可视化树感兴趣。作为常规 WPF 应用程序编程一部分的可视化树的一个公开情况是,路由事件的事件路由大多数情况下遍历可视化树,而不是逻辑树。这种微妙的路由事件行为可能不会很明显,除非您是控件作者。在可视化树中路由使得在可视化级别实现组合的控件能够处理事件或创建事件 setter。

树、内容元素和内容宿主

内容元素(从 ContentElement 派生的类)不是可视化树的一部分;内容元素不从 Visual 继承并且没有可视化表示形式。若要完全在 UI 中显示,则必须在既是 Visual,也是逻辑树元素(通常是 FrameworkElement)的内容宿主中承载 ContentElement。您可以使用概念说明,内容宿主有点类似于内容的“浏览器”,它选择要在该宿主控制的屏幕区域中显示内容的方式。承载内容时,可以使内容成为通常与可视化树关联的某些树进程的参与者。通常,FrameworkElement 宿主类包括实现代码,该代码用于通过内容逻辑树的子节点将任何已承载的 ContentElement 添加到事件路由,即使承载内容不是真实可视化树的一部分时也将如此。这样做是必要的,以便 ContentElement 可以为路由到除其自身之外的任何元素的路由事件提供来源。

树遍历

LogicalTreeHelper 类为逻辑树遍历提供 GetChildrenGetParentFindLogicalNode 方法。在大多数情况下,不需要遍历现有控件的逻辑树,因为这些控件几乎总是将其逻辑子元素公开为专用集合属性,该属性支持集合 API(如 Add、索引器等等)。对于不选择从预期控件模式(例如已定义了集合属性的 ItemsControlPanel)派生以及打算提供其自己的集合属性支持的控件作者,树遍历主要是他们使用的一种方案。

可视化树还支持用于可视化树遍历的帮助器类 VisualTreeHelper。无法通过控件特定的属性方便地公开可视化树,因此,如果您的编程方案必须遍历可视化树,建议您使用 VisualTreeHelper 类。有关更多信息,请参见 Windows Presentation Foundation 图形呈现概述

“树”形式路由事件的路由

如前所述,路由事件的路由可有效地向上遍历或向下遍历树,这要取决于该事件是隧道路由事件还是冒泡路由事件。事件路由概念没有直接支持的帮助器类,因此无法使用这样的类来独立“遍历”引发实际进行路由的事件的事件路由。存在表示路由的类 EventRoute,但该类的方法通常仅供内部使用。

资源和树

资源查找基本上遍历逻辑树。不在逻辑树中的对象可以引用资源,但查找将从该对象连接到逻辑树的位置开始。仅逻辑树节点可以有包含 ResourceDictionary 的 Resources 属性,因此这意味着,遍历可视化树来查找资源没有好处。

但是,资源查找也可以超出直接逻辑树。对于应用程序标记,资源查找可以向上继续,直到应用程序资源以及主题支持和系统值。如果资源引用是动态的,则主题本身也可以引用主题逻辑树之外的系统值。有关资源和查找逻辑的更多信息,请参见资源概述

请参见

概念

输入概述

Windows Presentation Foundation 图形呈现概述

路由事件概述

不在元素树中的对象元素的初始化

WPF 体系结构