Деревья в WPF

Во многих технологиях элементы и компоненты организованы в форме древовидной структуры, и разработчики могут напрямую управлять узлами объекта в дереве, чтобы повлиять на визуализацию или поведение приложения. В Windows Presentation Foundation (WPF) часто используется несколько метафор древовидных структур, чтобы определить отношения между программными элементами. Для большей части WPF разработчики могут создать приложение в коде или определить части приложения в XAML и при этом концептуально думать о метафоре дерева объектов, но для этого им потребуется вызвать определенный интерфейс API или использовать конкретную разметку, а не интерфейс API управления деревом некоторых общих объектов, какой можно использовать в XML DOM. WPF предоставляет два вспомогательных класса, которые предоставляют представление метафор дерева, LogicalTreeHelper и VisualTreeHelper . Термины "логическое дерево" и "визуальное дерево" также используются в документации WPF, поскольку эти же деревья помогают понять поведение определенных ключевых функций WPF. В этом разделе определяются представления визуального дерева и логического дерева, обсуждаются связи этих деревьев с общей концепцией дерева объектов, а также представлены LogicalTreeHelper и VisualTreeHelper s.

Деревья в WPF

Самой полной древовидной структурой в WPF является дерево объектов. При определении страницы приложения в XAML и последующей загрузке XAML древовидная структура создается на основе отношений вложенности элементов в разметке. При определении приложения или части приложения в коде древовидная структура создается в зависимости от того, как присваиваются значения свойствам, которые реализуют модель содержимого для данного объекта. В Windows Presentation Foundation (WPF) существует два способа концептуализации и передачи в открытый интерфейс API полного дерева объектов: в виде логического дерева и в виде визуального дерева. Различия между логическими деревьями и визуальными деревьями не всегда важны, но иногда они могут вызвать проблемы с некоторыми подсистемами WPF и повлиять на изменения, внесенные в разметку или код.

Несмотря на то, что управление логическим деревом или визуальным деревом не всегда происходит напрямую, понимание концепций взаимодействия деревьев позволяет понять WPF как технологию. Рассмотрение WPF как некоторой метафоры дерева также важно для понимания того, как в WPF осуществляется наследование свойств и маршрутизация событий.

Примечание

Поскольку дерево объектов — это более широкое понятие, чем фактический интерфейс API, еще одним способом представить себе концепцию является граф объекта. На практике отношения между объектами можно наблюдать во время выполнения, когда метафора дерева разделяется. Тем не менее, особенно в пользовательском интерфейсе, определенном XAML, метафора дерева достаточно релевантна, и в большей части документации WPF используется термин "дерево объекта" при ссылке на это общее понятие.

Логическое дерево

В WPF содержимое добавляется в элементы пользовательского интерфейса путем задания свойств объектов, которые поддерживают эти элементы. Например, вы добавляете элементы в ListBox элемент управления, управляя его Items свойством. Таким образом, вы помещаете в объект элементы ItemCollection , которые являются Items значением свойства. Аналогичным образом, чтобы добавить объекты в DockPanel , вы управляете его Children значением свойства. Здесь вы добавляете объекты в UIElementCollection . Пример кода см. в разделе как добавить элемент динамически.

В Язык XAML при помещении элементов списка в ListBox элементы управления или или другие элементы пользовательского интерфейса в 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 инициализируется, но является пустым при ListBox обработке XAML. Затем каждый дочерний элемент объекта, существующий как содержимое для, ListBox добавляется в средство ItemCollection синтаксического анализа для вызовов ItemCollection.Add . Этот пример обработки XAML в дереве объектов на первый взгляд похож на пример, в котором созданное дерево объектов, по сути, является логическим деревом.

Однако логическое дерево не является всей диаграммой объектов, которая существует для пользовательского интерфейса приложения во время выполнения, даже при использовании неявных элементов синтаксиса XAML. Основная причина этого — визуальные элементы и шаблоны. Например, рассмотрим Button . Логическое дерево сообщает об Button объекте, а также его строке Content . Но в дереве объектов времени выполнения имеется больше сведений об этой кнопке. В частности, кнопка отображается на экране только в том виде, в котором Button был применен определенный шаблон элемента управления. Визуальные элементы, полученные из примененного шаблона (например, определенного шаблоном Border темно-серого вокруг визуальной кнопки), не передаются в логическое дерево, даже если вы просматриваете логическое дерево во время выполнения (например, обрабатываете событие ввода из видимого пользовательского интерфейса и затем считываете логическое дерево). Чтобы найти визуальные элементы шаблона, необходимо проверить визуальное дерево.

Дополнительные сведения о XAML сопоставлении синтаксиса с созданным графом объектов и неявном синтаксисе в XAML см. в разделе синтаксис XAML подробно или XAML в WPF.

Назначение логического дерева

Логическое дерево существует для того, чтобы модели содержимого имели возможность пройти по своим доступным дочерним объектам, а также для их расширяемости. Кроме того, логическое дерево предоставляет оболочку для некоторых уведомлений, например при загрузке всех объектов логического дерева. По существу, логическое дерево является подобием графа объекта времени выполнения на уровне оболочки, исключающего визуальные элементы, но подходит для большинства операций запросов для собственной композиции приложения времени выполнения.

Кроме того, как статические, так и динамические ссылки на ресурсы разрешаются путем просмотра логического дерева для Resources коллекций исходного запрашивающего объекта, а затем продолжается логическое дерево и выполняется проверка каждого FrameworkElement (или FrameworkContentElement ) другого Resources значения, содержащего ResourceDictionary , возможно, содержащего этот ключ. Логическое дерево используется для просмотра ресурсов при наличии логического дерева и визуального дерева. Дополнительные сведения о словарях ресурсов и поиске см. в разделе Общие сведения о ресурсах.

Композиция логического дерева

Логическое дерево определяется на уровне платформы WPF. Это означает, что базовый элемент WPF, наиболее подходящий для операций логического дерева, — это либо FrameworkElement FrameworkContentElement . Однако, как можно увидеть, если вы фактически используете LogicalTreeHelper API, логическое дерево иногда содержит узлы, которые не являются либо FrameworkElement FrameworkContentElement . Например, логическое дерево сообщает Text значение TextBlock , которое является строкой.

Переопределение логического дерева

Авторы расширенного элемента управления могут переопределить логическое дерево путем переопределения нескольких интерфейсов API, определяющих, как общий объект или модель содержимого добавляют или удаляют объекты в логическом дереве. Пример переопределения логического дерева содержится в разделе Переопределение логического дерева.

Наследование значения свойства

Наследование значения свойств действует через гибридное дерево. Фактические метаданные, содержащие Inherits свойство, которое позволяет наследование свойств, — это класс уровня среды WPF FrameworkPropertyMetadata . Таким образом, как родитель, содержащий исходное значение, так и дочерний объект, который наследует это значение, должны быть FrameworkElement или FrameworkContentElement , и они должны быть частью некоторого логического дерева. Однако для существующих свойств WPF, поддерживающих наследование свойств, наследование значений свойств способно принять промежуточный объект, которого нет в логическом дереве. Как правило, это распространяется на элементы шаблона, использующие все унаследованные значения свойств, заданные как в экземпляре, который является шаблоном, так и на более высоких уровнях композиции уровня страницы и, следовательно, выше в логическом дереве. Чтобы наследование значений свойств осуществлялось согласованно в таких пределах, наследуемое свойство должно быть зарегистрировано как вложенное свойство, кроме того, необходимо следовать этому шаблону, если требуется определить пользовательское свойство зависимостей с поведением наследования свойств. Точное дерево, используемое для наследования свойств, не может быть полностью предсказано вспомогательным служебным методом класса даже во время выполнения. Дополнительные сведения см. в разделе Наследование значения свойства.

Визуальное дерево

В дополнение к концепции логического дерева в WPF также существует концепция визуального дерева. Визуальное дерево описывает структуру визуальных объектов, представленных Visual базовым классом. При написании шаблона для элемента управления следует определить или переопределить визуальное дерево, применяемое для данного элемента управления. Визуальное дерево также представляет интерес для разработчиков, заинтересованных в контроле рисования на нижнем уровне по соображениям производительности и оптимизации. Слабым местом визуального дерева как части программирования стандартных приложений WPF является то, что маршруты событий для перенаправленного события в большинстве случаев проходят по визуальному дереву, а не по логическому. Эта тонкость поведения перенаправленного события может быть не очевидна, если вы не являетесь автором элемента управления. Маршрутизация событий по визуальному дереву позволяет элементам управления, которые реализуют композицию на визуальном уровне, обрабатывать события или создавать установщики событий.

Деревья, элементы содержимого и узлы содержимого

Элементы содержимого (классы, производные от ContentElement ) не являются частью визуального дерева; они не наследуются Visual и не имеют визуального представления. Для отображения в пользовательском интерфейсе элемент ContentElement должен размещаться на узле содержимого, который является Visual и, и участником логического дерева. Обычно такой объект является FrameworkElement . Можно представить сайт содержимого в качестве "обозревателя" содержимого, который выбирает способ отображения содержимого в пределах области экрана, управляемой сайтом. При размещении содержимого оно может стать участником некоторых процессов дерева, которые обычно связаны с визуальным деревом. Как правило, FrameworkElement класс Host включает код реализации, который добавляет все, размещенные ContentElement в маршруте события, через подузлы логического дерева содержимого, даже если размещенное содержимое не является частью истинного визуального дерева. Это необходимо для того, чтобы в качестве ContentElement источника можно было направить перенаправленное событие, которое будет маршрутизироваться к любому элементу, отличному от самого себя.

Прохождение по дереву

LogicalTreeHelperКласс предоставляет GetChildren GetParent методы, и FindLogicalNode для обхода логического дерева. В большинстве случаев не следует проходить по логическому дереву существующих элементов управления, так как эти элементы управления почти всегда предоставляют свои логические дочерние элементы в качестве выделенного свойства коллекции, которое поддерживает доступ к коллекции, например Add, индексатор и т. д. Обход дерева — главным образом сценарий, который используется авторами элементов управления, которые не являются производными от предполагаемых шаблонов элементов управления, таких как ItemsControl или Panel где уже определены свойства коллекции, и кто планирует предоставлять поддержку собственных свойств коллекции.

Визуальное дерево также поддерживает вспомогательный класс для обхода визуального дерева, VisualTreeHelper . Визуальное дерево недоступно для свойств конкретного элемента управления, поэтому VisualTreeHelper класс является рекомендуемым способом прохода по визуальному дереву, если это необходимо для сценария программирования. Дополнительные сведения см. в разделе Общие сведения об отрисовке графики в WPF.

Примечание

Иногда необходимо проверять визуальное дерево применяемого шаблона. Необходимо соблюдать осторожность при использовании этого метода. Даже если вы просматриваете визуальное дерево для элемента управления, в котором вы определили шаблон, потребители элемента управления всегда могут изменить шаблон, задав Template свойство для экземпляров, и даже конечный пользователь может повлиять на примененный шаблон, изменив тему системы.

Маршруты для маршрутизируемых событий как "дерево"

Как уже отмечалось ранее, маршрут любого заданного перенаправленного события проходит по одному предопределенному пути дерева, представляющего собой гибрид представлений визуального и логического деревьев. Маршрут события может проходить как по восходящей, так и по нисходящий в пределах дерева в зависимости от того, имеет ли перенаправленное событие нисходящую или восходящую маршрутизацию. Концепция маршрута события не имеет непосредственного вспомогательного класса, который может быть использован для "прохода" маршрута события независимо от вызова фактически перенаправленного события. Существует класс, представляющий маршрут, EventRoute но методы этого класса обычно предназначены только для внутреннего использования.

Словари и деревья ресурсов

Поиск по словарю ресурсов для всех Resources, определенных на странице, обычно проходит по логическому дереву. Объекты, которые не входят в логическое дерево, могут ссылаться на ресурсы с ключом, но последовательность поиска ресурса начинается с той точки, где объект подключен к логическому дереву. В WPF только логические узлы дерева могут иметь Resources свойство, которое содержит ResourceDictionary , поэтому нет смысла просматривать визуальное дерево для поиска ресурсов с ключом из ResourceDictionary .

В то же время поиск ресурсов также можно расширить за пределы логического дерева. Для разметки приложения поиск ресурсов можно затем продолжить в словарях ресурсов на уровне приложений, поддержке тем и значениях системы, на которые ссылаются как на статические свойства или ключи. Сами темы также могут ссылаться на системные значения вне логического дерева тем, если ссылки на ресурсы являются динамическими. Дополнительные сведения о словарях ресурсов и логике поиска см. в разделе Ресурсы XAML.

См. также раздел