Деревья в WPF
Во многих технологиях элементы и компоненты организованы в форме древовидной структуры, и разработчики могут напрямую управлять узлами объекта в дереве, чтобы повлиять на визуализацию или поведение приложения. Windows Presentation Foundation (WPF) также использует несколько метафор древовидных структур, чтобы определить отношения между программными элементами. Для большей части WPF разработчики могут создать приложение в коде или определить части приложения в XAML и при этом концептуально думать о метафоре дерева объектов, но для этого им потребуется вызвать определенный интерфейс API или использовать конкретную разметку, а не интерфейс API управления деревом некоторых общих объектов, какой можно использовать в XML DOM. WPF предоставляет два вспомогательных класса, которые обеспечивают представление метафоры дерева LogicalTreeHelper и VisualTreeHelper. Термины "логическое дерево" и "визуальное дерево" также используются в документации WPF, поскольку эти же деревья помогают понять поведение определенных ключевых функций WPF. В этом разделе определены понятия визуального дерева и логического дерева, обсуждается, как эти деревья связаны с общим понятием дерева объектов, и представлены классы LogicalTreeHelper и VisualTreeHelpers.
Деревья в WPF
Самой полной древовидной структурой в WPF является дерево объектов. При определении страницы приложения в XAML и последующей загрузке XAML древовидная структура создается на основе отношений вложенности элементов в разметке. При определении приложения или части приложения в коде древовидная структура создается в зависимости от того, как присваиваются значения свойствам, которые реализуют модель содержимого для данного объекта. В Windows Presentation Foundation (WPF) существует два способа концептуализации и передачи в открытый интерфейс API полного дерева объектов: в виде логического дерева и в виде визуального дерева. Различия между логическими деревьями и визуальными деревьями не всегда важны, но иногда они могут вызвать проблемы с некоторыми подсистемами WPF и повлиять на изменения, внесенные в разметку или код.
Несмотря на то, что управление логическим деревом или визуальным деревом не всегда происходит напрямую, понимание концепций взаимодействия деревьев позволяет понять WPF как технологию. Рассмотрение WPF как некоторой метафоры дерева также важно для понимания того, как в WPF осуществляется наследование свойств и маршрутизация событий.
Note
Поскольку дерево объектов — это более широкое понятие, чем фактический интерфейс 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 класс узла содержит код реализации, который добавляет все размещенные ContentElement к маршруту события через подузлы логического дерева содержимого, даже если размещенное содержимое не является частью действительного визуального дерева. Это необходимо, чтобы ContentElement можно источника перенаправленное событие, которое маршрутизирует к любому элементу, кроме самого себя.
Прохождение по дереву
LogicalTreeHelper Класс предоставляет GetChildren, GetParent, и FindLogicalNode методы для логическому дереву. В большинстве случаев не следует проходить по логическому дереву существующих элементов управления, так как эти элементы управления почти всегда предоставляют свои логические дочерние элементы в качестве выделенного свойства коллекции, которое поддерживает доступ к коллекции, например Add
, индексатор и т. д. Обход дерева является главным образом сценарий, который используется авторами элемента управления, которые решили не наследовать от предполагаемых шаблонов элемента управления, например ItemsControl или Panel где свойства коллекции уже определены, и которые планируют предоставляют свои собственные коллекции Поддержка свойств.
Визуальное дерево также поддерживает вспомогательный класс для прохождения визуального дерева — VisualTreeHelper. Визуальное дерево не так удобно предоставляется через свойства элемента управления, поэтому VisualTreeHelper класс является рекомендуемым способом прохода визуального дерева, если это необходимо для программного скрипта. Дополнительные сведения см. в разделе Общие сведения об отрисовке графики в WPF.
Note
Иногда необходимо проверять визуальное дерево применяемого шаблона. Необходимо соблюдать осторожность при использовании этого метода. Даже при проходе по визуальному дереву для элемента управления, в котором необходимо задать шаблон, потребитель элемента управления всегда можно изменить шаблон, задав Template свойства в экземплярах и даже конечный пользователь может повлиять на применяемый шаблон путем изменения системной темы.
Маршруты для маршрутизируемых событий как "дерево"
Как уже отмечалось ранее, маршрут любого заданного перенаправленного события проходит по одному предопределенному пути дерева, представляющего собой гибрид представлений визуального и логического деревьев. Маршрут события может проходить как по восходящей, так и по нисходящий в пределах дерева в зависимости от того, имеет ли перенаправленное событие нисходящую или восходящую маршрутизацию. Концепция маршрута события не имеет непосредственного вспомогательного класса, который может быть использован для "прохода" маршрута события независимо от вызова фактически перенаправленного события. Класс, представляющий маршрут, EventRoute, но методы этого класса обычно являются только для внутреннего использования.
Словари и деревья ресурсов
Поиск по словарю ресурсов для всех Resources
, определенных на странице, обычно проходит по логическому дереву. Объекты, которые не входят в логическое дерево, могут ссылаться на ресурсы с ключом, но последовательность поиска ресурса начинается с той точки, где объект подключен к логическому дереву. В WPF только узлы логического дерева могут иметь Resources
свойство, содержащее ResourceDictionary, поэтому не имеет смысла при обходе визуального дерева, который ищет ресурсы с ключом из ResourceDictionary.
В то же время поиск ресурсов также можно расширить за пределы логического дерева. Для разметки приложения поиск ресурсов можно затем продолжить в словарях ресурсов на уровне приложений, поддержке тем и значениях системы, на которые ссылаются как на статические свойства или ключи. Сами темы также могут ссылаться на системные значения вне логического дерева тем, если ссылки на ресурсы являются динамическими. Дополнительные сведения о словарях ресурсов и логике поиска см. в разделе Ресурсы XAML.