Деревья в WPF
Во многих технологиях элементы и компоненты организованы в форме древовидной структуры, и разработчики могут напрямую управлять узлами объекта в дереве, чтобы повлиять на визуализацию или поведение приложения. В Windows Presentation Foundation (WPF) часто используется несколько метафор древовидных структур, чтобы определить отношения между программными элементами. Для большей части WPF разработчики могут создать приложение в коде или определить части приложения в XAML и при этом концептуально думать о метафоре дерева объектов, но для этого им потребуется вызвать определенный интерфейс API или использовать конкретную разметку, а не интерфейс API управления деревом некоторых общих объектов, какой можно использовать в XML DOM. WPF предоставляет два вспомогательных класса, обеспечивающих представление метафоры дерева: LogicalTreeHelper и VisualTreeHelper. Термины "логическое дерево" и "визуальное дерево" также используются в документации WPF, поскольку эти же деревья помогают понять поведение определенных ключевых функций WPF. В этом разделе описано, что представляют визуальное дерево и логическое дерево, обсуждается, как эти деревья связаны с понятием общего дерева объектов, и вводятся классы LogicalTreeHelper VisualTreeHelper.
В этом разделе содержатся следующие подразделы.
- Деревья в WPF
- Логическое дерево
- Визуальное дерево
- Деревья, элементы содержимого и узлы содержимого
- Прохождение по дереву
- Маршруты для маршрутизируемых событий как «дерево»
- Словари и деревья ресурсов
- Связанные разделы
Деревья в WPF
Самой полной древовидной структурой в WPF является дерево объектов. При определении страницы приложения в XAML и последующей загрузке XAML древовидная структура создается на основе отношений вложенности элементов в разметке. При определении приложения или части приложения в коде древовидная структура создается в зависимости от того, как присваиваются значения свойствам, которые реализуют модель содержимого для данного объекта. В Windows Presentation Foundation (WPF) существует два способа концептуализации и передачи в открытый интерфейс API полного дерева объектов: в виде логического дерева и в виде визуального дерева. Различия между логическими деревьями и визуальными деревьями не всегда важны, но иногда они могут вызвать проблемы с некоторыми подсистемами WPF и повлиять на изменения, внесенные в разметку или код.
Несмотря на то, что управление логическим деревом или визуальным деревом не всегда происходит напрямую, понимание концепций взаимодействия деревьев позволяет понять WPF как технологию. Рассмотрение WPF как некоторой метафоры дерева также важно для понимания того, как в WPF осуществляется наследование свойств и маршрутизация событий.
Примечание |
---|
Поскольку дерево объектов — это более широкое понятие, чем фактический интерфейс API, еще одним способом представить себе концепцию является граф объекта.На практике отношения между объектами можно наблюдать во время выполнения, когда метафора дерева разделяется.Тем не менее, особенно в пользовательском интерфейсе, определенном XAML, метафора дерева достаточно релевантна, и в большей части документации WPF используется термин "дерево объекта" при ссылке на это общее понятие. |
Логическое дерево
В WPF содержимое добавляется в элементы пользовательского интерфейса путем задания свойств объектов, которые поддерживают эти элементы. Например, можно добавить элементы в элемент управления ListBox с помощью его свойства Items. При этом элементы помещаются в ItemCollection, который является значением свойства Items. Аналогично, для добавления объектов в DockPanel используется значение свойства Children. В этом случае происходит добавление объектов в UIElementCollection. Пример кода см. в разделе Практическое руководство. Динамическое добавление элемента.
В Extensible Application Markup Language (XAML) при помещении элементов списка в ListBox или элементов управления либо других элементов пользовательского интерфейса в DockPanel можно также использовать свойства Items и Children, явным или неявным образом, как показано в следующем примере.
<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>-->
<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. Затем каждый дочерний элемент объекта, существующий как содержимое для 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. Однако, как можно видеть, если фактически используется интерфейс API LogicalTreeHelper, логическое дерево иногда содержит узлы, которые не являются ни FrameworkElement, ни FrameworkContentElement. Например, логическое дерево сообщает о значении Text объекта TextBlock, которое представляет собой строку.
Переопределение логического дерева
Авторы дополнительных элементов управления могут переопределить логическое дерево, переопределив несколько APIs, которые определяют то, как основная модель объекта или содержимого добавляет или удаляет объекты логического дерева. Пример переопределения логического дерева содержится в разделе Как переопределить логическое дерево.
Наследование значения свойства
Наследование значения свойств действует через гибридное дерево. Фактические метаданные, содержащие свойствоInherits, позволяющее наследование свойств, является классом FrameworkPropertyMetadataуровня платформы WPF. Таким образом, и родительский объект, содержащий исходное значение, и дочерний объект, наследующий это значение, должны быть FrameworkElement или FrameworkContentElement, и оба должны быть частью некоторого логического дерева. Однако для существующих свойств WPF, поддерживающих наследование свойств, наследование значений свойств способно принять промежуточный объект, которого нет в логическом дереве. Как правило, это распространяется на элементы шаблона, использующие все унаследованные значения свойств, заданные как в экземпляре, который является шаблоном, так и на более высоких уровнях композиции уровня страницы и, следовательно, выше в логическом дереве. Чтобы наследование значений свойств осуществлялось согласованно в таких пределах, наследуемое свойство должно быть зарегистрировано как вложенное свойство, кроме того, необходимо следовать этому шаблону, если требуется определить пользовательское свойство зависимостей с поведением наследования свойств. Точное дерево, используемое для наследования свойств, не может быть полностью предсказано вспомогательным служебным методом класса даже во время выполнения. Дополнительные сведения см. в разделе Наследование значения свойства.
Визуальное дерево
В дополнение к концепции логического дерева в WPF также существует концепция визуального дерева. Визуальное дерево описывает структуру визуальных объектов, предоставленных базовым классом Visual. При написании шаблона для элемента управления следует определить или переопределить визуальное дерево, применяемое для данного элемента управления. Визуальное дерево также представляет интерес для разработчиков, заинтересованных в контроле рисования на нижнем уровне по соображениям производительности и оптимизации. Слабым местом визуального дерева как части программирования стандартных приложений WPF является то, что маршруты событий для перенаправленного события в большинстве случаев проходят по визуальному дереву, а не по логическому. Эта тонкость поведения маршрутизируемого события может быть неочевидна, если вы не являетесь автором элемента управления. Маршрутизация событий по визуальному дереву позволяет элементам управления, которые реализуют композицию на визуальном уровне, обрабатывать события или создавать установщики событий.
Деревья, элементы содержимого и узлы содержимого
Элементы содержимого (классы, производные от ContentElement) не являются частью визуального дерева; они не наследуют от Visual и не имеют визуального представления. Чтобы полностью отобразиться в пользовательском интерфейсе, ContentElement должен быть размещен в хранилище содержимого, которое одновременно является и Visual, и участником логического дерева. Обычно таким объектом является FrameworkElement. Можно представить сайт содержимого в качестве «обозревателя» содержимого, который выбирает способ отображения содержимого в пределах области экрана, управляемой сайтом. При размещении содержимого оно может стать участником некоторых процессов дерева, которые обычно связаны с визуальным деревом. Как правило, узловой класс FrameworkElement содержит код реализации, в котором любой размещенный ContentElement добавляется к маршруту события через подузлы логического дерева содержимого, даже если размещенное содержимое не является частью действительного визуального дерева. Это необходимо для того, чтобы ContentElement мог получить маршрутизируемое событие, которое маршрутизирует к любому элементу, кроме самого себя.
Прохождение по дереву
Класс LogicalTreeHelper предоставляет методы GetChildren, GetParent и FindLogicalNode для прохождения по логическому дереву. В большинстве случаев не следует проходить по логическому дереву существующих элементов управления, так как эти элементы управления почти всегда предоставляют свои логические дочерние элементы в качестве выделенного свойства коллекции, которое поддерживает доступ к коллекции, например Add, индексатор и т. д. Обход дерева обычно используется авторами элемента управления, которые отказались от создания элементов управления, производных от предполагаемых шаблонов элемента управления, например, ItemsControl или Panel, где свойства коллекции уже определены, и которые планируют поддержку собственных свойств коллекции.
Визуальное дерево также поддерживает вспомогательный класс для прохождения визуального дерева — VisualTreeHelper. Визуальное дерево не так удобно предоставляется из свойств определенного элемента управления. Таким образом, класс VisualTreeHelper является рекомендуемым способом прохода визуального дерева, если это необходимо для программного скрипта. Дополнительные сведения см. в разделе Общие сведения об отрисовке графики в WPF.
Примечание |
---|
Иногда необходимо проверять визуальное дерево применяемого шаблона.Необходимо соблюдать осторожность при использовании этого метода.Даже при проходе по визуальному дереву в поиске элемента управления, в котором определен шаблон, потребитель элемента управления всегда может изменить шаблон, задав свойство Template в экземплярах, и даже конечный пользователь может повлиять на применяемый шаблон, изменив тему системы. |
Маршруты для маршрутизируемых событий как «дерево»
Как уже отмечалось ранее, маршрут любого заданного перенаправленного события проходит по одному предопределенному пути дерева, представляющего собой гибрид представлений визуального и логического деревьев. Маршрут события может проходить как по восходящей, так и по нисходящий в пределах дерева в зависимости от того, имеет ли перенаправленное событие нисходящую или восходящую маршрутизацию. Концепция маршрута события не имеет непосредственного вспомогательного класса, который может быть использован для «прохода» маршрута события независимо от вызова фактически маршрутизируемого события. Класс, представляющий маршрут, существует (EventRoute), но методы этого класса, как правило, предназначены только для внутреннего использования.
Словари и деревья ресурсов
Поиск по словарю ресурсов для всех Resources, определенных на странице, обычно проходит по логическому дереву. Объекты, которые не входят в логическое дерево, могут ссылаться на ресурсы с ключом, но последовательность поиска ресурса начинается с той точки, где объект подключен к логическому дереву. В WPF только узлы логического дерева могут иметь свойство Resources, содержащее ResourceDictionary. Таким образом, это означает отсутствие преимущества при обходе визуального дерева для поиска ресурсов с ключом из ResourceDictionary.
В то же время поиск ресурсов также можно расширить за пределы логического дерева. Для разметки приложения поиск ресурсов можно затем продолжить в словарях ресурсов на уровне приложений, поддержке тем и значениях системы, на которые ссылаются как на статические свойства или ключи. Сами темы также могут ссылаться на системные значения вне логического дерева тем, если ссылки на ресурсы являются динамическими. Дополнительные сведения о словарях ресурсов и логике поиска см. в разделе Общие сведения о ресурсах.
См. также
Основные понятия
Общие сведения о входных данных
Общие сведения об отрисовке графики в WPF
Общие сведения о перенаправленных событиях