WPF 中的树Trees in WPF

在许多技术中,元素和组件都按树结构的形式组织。在这种结构中,开发人员可以直接操作树中的对象节点来影响应用程序的绘制或行为。In many technologies, elements and components are organized in a tree structure where developers directly manipulate the object nodes in the tree to affect the rendering or behavior of an application. Windows Presentation Foundation (WPF)Windows Presentation Foundation (WPF) 也使用了若干树结构形式来定义程序元素之间的关系。also uses several tree structure metaphors to define relationships between program elements. 多数情况下,在概念层面考虑对象树形式时,WPF 开发人员会用代码创建应用程序,或用 XAML 定义应用程序的组成部分,但他们会调用具体的 API 或使用特定的标记来执行此操作,而不是像在 XML DOM 中那样,使用某些常规对象树操作 API。For the most part WPF developers can create an application in code or define portions of the application in XAML while thinking conceptually about the object tree metaphor, but will be calling specific API or using specific markup to do so rather than some general object tree manipulation API such as you might use in XML DOM. WPF 公开了两个帮助器类, 它们提供树LogicalTreeHelper比喻VisualTreeHelper视图和。WPF exposes two helper classes that provide a tree metaphor view, LogicalTreeHelper and VisualTreeHelper. WPF 文档中还使用了“可视化树”和“逻辑树”两个术语,它们有助于理解某些关键 WPF 功能的行为。The terms visual tree and logical tree are also used in the WPF documentation because these same trees are useful for understanding the behavior of certain key WPF features. 本主题定义了可视化树和逻辑树表示的内容, 讨论了此类树如何与整体对象树概念相关, LogicalTreeHelperVisualTreeHelper介绍了和。This topic defines what the visual tree and logical tree represent, discusses how such trees relate to an overall object tree concept, and introduces LogicalTreeHelper and VisualTreeHelpers.

WPF 中的树Trees in WPF

WPFWPF 中,最完整的树结构是对象树。The most complete tree structure in WPFWPF is the object tree. 如果在 XAMLXAML 中定义一个应用程序页,然后加载 XAMLXAML,将根据标记中元素之间的嵌套关系来创建树结构。If you define an application page in XAMLXAML and then load the XAMLXAML, the tree structure is created based on the nesting relationships of the elements in the markup. 如果使用代码定义应用程序或应用程序的一部分,则将根据为属性(属性实现给定对象的内容模型)分配属性值的方式来创建树结构。If you define an application or a portion of the application in code, then the tree structure is created based on how you assign property values for properties that implement the content model for a given object. Windows Presentation Foundation (WPF)Windows Presentation Foundation (WPF) 中,完整的对象树可通过两种方式进行概念化并报告给其公共 API:作为逻辑树和作为可视化树。In Windows Presentation Foundation (WPF)Windows Presentation Foundation (WPF), there are two ways that the complete object tree is conceptualized and can be reported to its public API: as the logical tree and as the visual tree. 逻辑树与可视化树之间的区别不一定重要,但在某些 WPFWPF 子系统中它们偶尔可能会导致问题,并影响你对标记或代码的选择。The distinctions between logical tree and visual tree are not always necessarily important, but they can occasionally cause issues with certain WPFWPF subsystems and affect choices you make in markup or code.

尽管你并不会总是直接操作逻辑树或可视化树,但理解它们之间的关系有助于你从技术角度了解 WPF。Even though you do not always manipulate either the logical tree or the visual tree directly, understanding the concepts of how the trees interact is useful for understanding WPF as a technology. 若要理解 WPFWPF 中属性继承和事件路由的工作原理,将 WPF 视为某种树形式也相当重要。Thinking of WPF as a tree metaphor of some kind is also crucial to understanding how property inheritance and event routing work in WPFWPF.

备注

因为对象树更像是概念,而不像是实际 API,所以还可以将此概念视为对象图。Because the object tree is more of a concept than an actual API, another way to think of the concept is as an object graph. 实际上,在运行时,对象之间的某些关系不能由树形式表示。In practice, there are relationships between objects at run time where the tree metaphor will break down. 尽管如此,树形式的相关性还是很强,尤其是对于 XAML 定义的 UI。因此,大多数 WPF 文档在引用这个常见概念时,仍使用术语“对象树”。Nevertheless, particularly with XAML-defined UI, the tree metaphor is relevant enough that most WPF documentation will use the term object tree when referencing this general concept.

逻辑树The Logical Tree

WPFWPF 中,通过为支持 UI 元素的对象设置属性,可以向这些 UI 元素添加内容。In WPFWPF, you add content to UI elements by setting properties of the objects that back those elements. 例如, 通过ListBox Items操作属性来向控件添加项。For example, you add items to a ListBox control by manipulating its Items property. 通过执行此操作, 您要将项放ItemCollection入作为Items属性值的中。By doing this, you are placing items into the ItemCollection that is the Items property value. 同样, 若要将对象添加DockPanel到中, 可以Children操作其属性值。Similarly, to add objects to a DockPanel, you manipulate its Children property value. 此处, 你要将对象添加到UIElementCollection中。Here, you are adding objects to the UIElementCollection. 有关代码示例, 请参阅如何:动态添加元素。For a code example, see How to: Add an Element Dynamically.

可扩展应用程序标记语言 (XAML)Extensible Application Markup Language (XAML)中, 当你将中的列表ListBox项放入或控件或中的DockPanel其他 UI 元素时, ItemsChildren也可以显式或隐式使用和属性, 如下面的示例中所示。In 可扩展应用程序标记语言 (XAML)Extensible Application Markup Language (XAML), when you place list items in a ListBox or controls or other UI elements in a DockPanel, you also use the Items and Children properties, either explicitly or implicitly, as in the following example.

<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> 的元素以及其他隐式项。If you were to process this XAML as XML under a document object model, and if you had included the tags commented out as implicit (which would have been legal), then the resulting XML DOM tree would have included elements for <ListBox.Items> and the other implicit items. 但是,读取标记和写入对象时,XAML 不会这样处理,生成的对象图不包含 ListBox.ItemsBut XAML does not process that way when you read the markup and write to objects, the resulting object graph does not literally include ListBox.Items. ListBox但它具有一个名为Items的属性, 该ItemCollection属性ItemCollection包含, ListBox并且在处理 XAML 时已初始化但为空。It does however have a ListBox property named Items that contains a ItemCollection, and that ItemCollection is initialized but empty when the ListBox XAML is processed. 然后, 将作为的ListBox内容的每个子对象元素添加ItemCollection到中的, 由分析器调用ItemCollection.Add添加到。Then, each child object element that exists as content for the ListBox is added to the ItemCollection by parser calls to ItemCollection.Add. 此示例将 XAML 处理成对象树,目前这似乎表明所创建的对象树基本上是逻辑树。This example of processing XAML into an object tree is so far seemingly an example where the created object tree is basically the logical tree.

不过,即使不考虑 XAML 隐式语法项,该逻辑树也不是应用程序 UI 在运行时存在的整个对象图。这主要是因为视觉对象和模板。However, the logical tree is not the entire object graph that exists for your application UI at run time, even with the XAML implicit syntax items factored out. The main reason for this is visuals and templates. 例如, 请考虑ButtonFor example, consider the Button. 逻辑树报告Button对象及其字符串ContentThe logical tree reports the Button object and also its string Content. 但在运行时对象树中,此按钮还有更多内容。But there is more to this button in the run-time object tree. 特别是, 按钮只在屏幕上显示, 因为应用了特定Button的控件模板。In particular, the button only appears on screen the way it does because a specific Button control template was applied. 即使在运行时查看逻辑树 (例如, 在运行时处理输入Border事件, 也不会在逻辑树中报告来自应用的模板的视觉对象 (如视觉对象按钮的模板定义的模板)。可见 UI, 然后读取逻辑树)。The visuals that come from an applied template (such as the template-defined Border of dark gray around the visual button) are not reported in the logical tree, even if you are looking at the logical tree during run time (such as handling an input event from the visible UI and then reading the logical tree). 若要查找模板视觉对象,需要改为检查可视化树。To find the template visuals, you would instead need to examine the visual tree.

有关 XAMLXAML 语法如何映射到所创建的对象图,以及 XAML 中隐式语法的详细信息,请参阅 XAML 语法详述XAML 概述 (WPF)For more information about how XAMLXAML syntax maps to the created object graph, and implicit syntax in XAML, see XAML Syntax In Detail or XAML Overview (WPF).

逻辑树用途The Purpose of the Logical Tree

借助逻辑树,内容模型可以方便地循环访问其可能的子对象,从而实现扩展。The logical tree exists so that content models can readily iterate over their possible child objects, and so that content models can be extensible. 此外,逻辑树还为某些通知提供框架,例如在加载逻辑树中的所有对象时。Also, the logical tree provides a framework for certain notifications, such as when all objects in the logical tree are loaded. 基本上,逻辑树是框架级别的近似运行时对象图(排除了视觉对象),但其足以用于对你自己的运行时应用程序组合执行多种查询操作。Basically, the logical tree is an approximation of a run time object graph at the framework level, which excludes visuals, but is adequate for many querying operations against your own run time application's composition.

此外, 通过在初始请求对象上的Resources集合的逻辑树中向上查找, 然后继续运行逻辑树并检查每个FrameworkElement (或FrameworkContentElement)Resources 对于ResourceDictionary包含的另一个值, 可能包含该密钥。In addition, both static and dynamic resource references are resolved by looking upwards through the logical tree for Resources collections on the initial requesting object, and then continuing up the logical tree and checking each FrameworkElement (or FrameworkContentElement) for another Resources value that contains a ResourceDictionary, possibly containing that key. 当同时存在逻辑树和可视化树时,将使用逻辑树进行资源查找。The logical tree is used for resource lookup when both the logical tree and the visual tree are present. 有关资源字典和查找的详细信息,请参见 XAML 资源For more information on resource dictionaries and lookup, see XAML Resources.

逻辑树的构成Composition of the Logical Tree

逻辑树在 WPF 框架级别定义, 这意味着, 与逻辑树操作最相关的 WPF 基元素是FrameworkElement或。 FrameworkContentElementThe logical tree is defined at the WPF framework-level, which means that the WPF base element that is most relevant for logical tree operations is either FrameworkElement or FrameworkContentElement. 然而, 如您所看到的实际使用的LogicalTreeHelper是 API, 逻辑树有时会包含FrameworkElement不是或FrameworkContentElement的节点。However, as you can see if you actually use the LogicalTreeHelper API, the logical tree sometimes contains nodes that are not either FrameworkElement or FrameworkContentElement. 例如, 逻辑树报告Text的值TextBlock, 它是一个字符串。For instance, the logical tree reports the Text value of a TextBlock, which is a string.

替代逻辑树Overriding the Logical Tree

高级控件作者可以通过重写几个 Api 来重写逻辑树, 这些 Api 定义了常规对象或内容模型在逻辑树中添加或删除对象的方式。Advanced control authors can override the logical tree by overriding several APIs that define how a general object or content model adds or removes objects within the logical tree. 有关如何替代逻辑树的示例,请参阅替代逻辑树For an example of how to override the logical tree, see Override the Logical Tree.

属性值继承Property Value Inheritance

属性值继承通过混合树操作。Property value inheritance operates through a hybrid tree. 实际的元数据 (包含Inherits启用属性继承的属性) 是 WPF 框架级别FrameworkPropertyMetadata的类。The actual metadata that contains the Inherits property that enables property inheritance is the WPF framework-level FrameworkPropertyMetadata class. 因此, 持有原始值的父对象和继承该值的子对象都必须为FrameworkElementFrameworkContentElement, 并且它们必须都是某个逻辑树的一部分。Therefore, both the parent that holds the original value and the child object that inherits that value must both be FrameworkElement or FrameworkContentElement, and they must both be part of some logical tree. 但是,对于支持属性继承的现有 WPF 属性,属性值的继承可通过逻辑树中没有的中介对象永久存在。However, for existing WPF properties that support property inheritance, property value inheritance is able to perpetuate through an intervening object that is not in the logical tree. 这主要适用于以下情况:让模板元素使用在应用了模板的实例上设置的任何继承属性值,或者使用在更高级别的页级构成(因此在逻辑树中也位于更高位置)中设置的任何继承属性值。Mainly this is relevant for having template elements use any inherited property values set either on the instance that is templated, or at still higher levels of page-level composition and therefore higher in the logical tree. 为了使属性值的继承在这两种情况下保持一致,继承属性必须注册为附加属性。如果要定义具有属性继承行为的自定义依赖属性,则应采用这种模式。In order for property value inheritance to work consistently across such a boundary, the inheriting property must be registered as an attached property, and you should follow this pattern if you intend to define a custom dependency property with property inheritance behavior. 无法通过帮助器类实用工具方法完全预测属性继承确切使用的树,即使在运行时也是如此。The exact tree used for property inheritance cannot be entirely anticipated by a helper class utility method, even at run time. 有关详细信息,请参阅属性值继承For more information, see Property Value Inheritance.

可视化树The Visual Tree

WPFWPF 中除了逻辑树的概念,还存在可视化树的概念。In addition to the concept of the logical tree, there is also the concept of the visual tree in WPFWPF. 可视化树描述了可视化对象的结构, 由Visual基类表示。The visual tree describes the structure of visual objects, as represented by the Visual base class. 为控件编写模板时,将定义或重新定义适用于该控件的可视化树。When you write a template for a control, you are defining or redefining the visual tree that applies for that control. 对于出于性能和优化考虑需要对绘图进行较低级别控制的开发人员来说,他们也会对可视化树感兴趣。The visual tree is also of interest to developers who want lower-level control over drawing for performance and optimization reasons. 在传统 WPFWPF 应用程序编程中,可视化树的一个应用是:路由事件的事件路由大多遍历可视化树而非逻辑树。One exposure of the visual tree as part of conventional WPFWPF application programming is that event routes for a routed event mostly travel along the visual tree, not the logical tree. 路由事件行为的这种微妙之处可能不会很明显,除非你是控件作者。This subtlety of routed event behavior might not be immediately apparent unless you are a control author. 通过可视化树对事件进行路由可使控件在可视化级别实现组合以处理事件或创建事件资源库。Routing events through the visual tree enables controls that implement composition at the visual level to handle events or create event setters.

树、内容元素和内容宿主Trees, Content Elements, and Content Hosts

内容元素 (派生自ContentElement的类) 不是可视化树的一部分; 它们不是从Visual继承的, 也不具有可视表示形式。Content elements (classes that derive from ContentElement) are not part of the visual tree; they do not inherit from Visual and do not have a visual representation. 若要在 UI 中显示, ContentElement必须在Visual同时作为和逻辑树参与者的内容主机中承载。In order to appear in a UI at all, a ContentElement must be hosted in a content host that is both a Visual and a logical tree participant. 通常, 此类对象为FrameworkElementUsually such an object is a FrameworkElement. 从概念上讲,内容宿主有些类似于内容的“浏览器”,它选择在该宿主控制的屏幕区域中显示内容的方式。You can conceptualize that the content host is somewhat like a "browser" for the content and chooses how to display that content within the screen region that the host controls. 承载内容时,可以使内容成为通常与可视化树关联的某些树进程的参与者。When the content is hosted, the content can be made a participant in certain tree processes that are normally associated with the visual tree. 通常情况下FrameworkElement , 主机类包括实现代码, 该代码ContentElement通过 content 逻辑树的子节点将所有宿主添加到事件路由, 即使寄宿内容不是真正的可视化树的一部分。Generally, the FrameworkElement host class includes implementation code that adds any hosted ContentElement to the event route through subnodes of the content logical tree, even though the hosted content is not part of the true visual tree. 这是必需的, 以便ContentElement可以将路由的路由事件作为路由到自身之外的任何元素的源。This is necessary so that a ContentElement can source a routed event that routes to any element other than itself.

树遍历Tree Traversal

类为逻辑树GetChildren遍历GetParent提供、 FindLogicalNode和方法。 LogicalTreeHelperThe LogicalTreeHelper class provides the GetChildren, GetParent, and FindLogicalNode methods for logical tree traversal. 在大多数情况下,不需要遍历现有控件的逻辑树,因为这些控件几乎总是将其逻辑子元素公开为一个专用集合属性,这种属性支持集合访问,如 Add、索引器等等。In most cases, you should not have to traverse the logical tree of existing controls, because these controls almost always expose their logical child elements as a dedicated collection property that supports collection access such as Add, an indexer, and so on. 树遍历主要是一种方案, 控件作者可以选择不从预期的控件模式派生, 如ItemsControlPanel已定义集合属性的位置, 以及要提供自己的集合的人员属性支持。Tree traversal is mainly a scenario that is used by control authors who choose not to derive from intended control patterns such as ItemsControl or Panel where collection properties are already defined, and who intend to provide their own collection property support.

可视化树还支持 visual tree 遍历VisualTreeHelper的帮助器类。The visual tree also supports a helper class for visual tree traversal, VisualTreeHelper. 可视化树不会通过特定于控件的属性方便地公开, 因此VisualTreeHelper , 如果您的编程方案需要, 则建议使用类遍历可视化树。The visual tree is not exposed as conveniently through control-specific properties, so the VisualTreeHelper class is the recommended way to traverse the visual tree if that is necessary for your programming scenario. 有关详细信息,请参阅 WPF 图形呈现概述For more information, see WPF Graphics Rendering Overview.

备注

有时有必要检查所应用模板的可视化树。Sometimes it is necessary to examine the visual tree of an applied template. 执行此操作时应谨慎。You should be careful when using this technique. 即使您正在遍历定义模板的控件的可视化树, 控件的使用者也可以通过设置Template实例的属性来更改模板, 甚至最终用户也可以通过更改系统主题。Even if you are traversing a visual tree for a control where you define the template, consumers of your control can always change the template by setting the Template property on instances, and even the end user can influence the applied template by changing the system theme.

“树”形式路由事件的路由Routes for Routed Events as a "Tree"

如前所述,对于任何给定的路由事件,其路由都沿着一条预定的树路径进行,这棵树是可视化树和逻辑树表示形式的混合体。As mentioned before, the route of any given routed event travels along a single and predetermined path of a tree that is a hybrid of the visual and logical tree representations. 事件路由可在树中向上或向下进行,具体取决于该事件是隧道路由事件还是浮升路由事件。The event route can travel either in the up or down directions within the tree depending on whether it is a tunneling or bubbling routed event. 事件路由概念没有直接支持的帮助器类(此类可用于独立于引发实际路由的事件,遍历事件)。The event route concept does not have a directly supporting helper class that could be used to "walk" the event route independently of raising an event that actually routes. 有一个表示路由EventRoute的类, 但该类的方法通常仅供内部使用。There is a class that represents the route, EventRoute, but the methods of that class are generally for internal use only.

资源字典和树Resource Dictionaries and Trees

对页中定义的所有 Resources 进行资源字典查找时,基本上遍历逻辑树。Resource dictionary lookup for all Resources defined in a page traverses basically the logical tree. 逻辑树之外的对象可以引用键控资源,但资源查找顺序将从该对象与逻辑树的连接点开始。Objects that are not in the logical tree can reference keyed resources, but the resource lookup sequence begins at the point where that object is connected to the logical tree. 在 WPF 中, 只有逻辑树节点可以具有Resources包含的ResourceDictionary属性, 因此, 在从查找ResourceDictionary用于加密资源的可视化树方面没有任何好处。In WPF, only logical tree nodes can have a Resources property that contains a ResourceDictionary, therefore there is no benefit in traversing the visual tree looking for keyed resources from a ResourceDictionary.

但是,资源查找也可以超出直接逻辑树。However, resource lookup can also extend beyond the immediate logical tree. 对于应用程序标记,资源查找可向前继续进行到应用程序级资源字典,然后再到作为静态属性或键进行引用的主题支持和系统值。For application markup, the resource lookup can then continue onward to application-level resource dictionaries and then to theme support and system values that are referenced as static properties or keys. 如果资源引用是动态的,则主题本身也可以引用主题逻辑树之外的系统值。Themes themselves can also reference system values outside of the theme logical tree if the resource references are dynamic. 有关资源字典和查找逻辑的详细信息,请参阅 XAML 资源For more information on resource dictionaries and the lookup logic, see XAML Resources.

请参阅See also