Логические и визуальные деревья
При вводе XAML-разметки в Visual Studio 2010, Expression Blend либо таком инструменте, как Kaxaml, разметка становится логическим представлением документа XAML. Точно также, при написании кода C#, добавляющего новые элементы к элементу управления StackPanel, новые элементы вставляются в логическое дерево. По сути, логическое представление показывает, как содержимое будет позиционировать внутри различных диспетчеров компоновки для главного окна (или другого корневого элемента, такого как Page или NavigationWindow).
Тем не менее, за каждым логическим деревом стоит более сложное представление, которое называется и внутренне использует WPF для корректной визуализации элементов на экране. Внутри любого визуального дерева находятся все детали шаблонов и стилей, используемых для визуализации каждого объекта, включая все необходимые рисунки, фигуры, визуальные элементы и анимации.
Полезно знать разницу между логическим и визуальным деревьями, потому что когда строится специальный шаблон элемента управления, то, по сути, заменяется все или часть визуального дерева элемента управления и вставляете свое. Поэтому, если необходимо визуализировать элемент управления Button в виде звезды, можно определить новый звездообразный шаблон и включить его в визуальное дерево Button.
Логически Button остается типом Button, и поддерживает все свойства, методы и события, как и ожидалось. Но визуально элемент получает совершенно новый внешний вид. Один этот факт делает WPF исключительно удобным API-интерфейсом, учитывая, что другие инструментарии потребовали бы в такой ситуации построения совершенно нового класса, представляющего звездообразную кнопку. В WPF достаточно определить новую разметку.
Рассмотрим исключительно простое окно с двумя кнопками. Чтобы создать это окно, элемент управления StackPanel вкладывается внутрь Window. В StackPanel помещаются два элемента управления Button, а внутрь каждого Button можно добавить некоторое содержимое по своему выбору (в данном случае — две строки). Вот необходимая для этого разметка:
Множество добавленных элементов называется логическим деревом, и оно показано на рисунке. Как программист WPF, вы будете тратить большую часть времени на построение логического дерева с последующей поддержкой его в коде обработчиков событий. Фактически, все средства, рассмотренные до сих пор (вроде наследования значений свойств, маршрутизации событий и стилизации), работают через логическое дерево.
Однако в настройке элементов логическое дерево мало помогает. Очевидно, можно было бы заменить целый элемент другим элементом (например, подставить специальный класс FancyButton вместо текущего Button), но это потребовало бы больше работы и могло разрушить интерфейс приложения или его код. По этой причине в WPF предлагается визуальное дерево.
Визуальное дерево — это расширенная версия логического дерева. Оно разбивает элементы на более мелкие части. Другими словами, вместо тщательно инкапсулированного черного ящика, такого как элемент управления Button, вы видите визуальные компоненты этой кнопки — рамку, которая обеспечивает кнопкам узнаваемый текстурированный фон (представленный классом ButtonChrome), контейнер внутри (ContentPresenter) и блок, хранящий текст кнопки (представленный знакомым классом TextBlock). На рисунке показана схема визуального дерева для окна из примера:
Все эти детали сами по себе являются элементами. Другими словами, каждая индивидуальная деталь такого элемента управления, как Button, представлена классом, унаследованным FrameworkElement.
Важно понимать, что существует более одного возможного расширения логического дерева в визуальное дерево. Такие детали, как используемые стили, установленные свойства, операционная система (Windows ХР или Windows 7/Vista), а также текущая тема Windows, могут влиять на способ отображения визуального дерева. Например, в предыдущем примере кнопка включает текстовое содержимое, в результате чего автоматически создает вложенный элемент TextBlock. Но, как известно, элемент управления Button — это элемент с содержимым, и потому может иметь внутри себя любой другой элемент, который вы пожелаете в него вставить.
Пока все должно быть ясно. Было показано, что элементы WPF можно разбирать на меньшие части. Но какое преимущество это дает разработчику WPF? Визуальное дерево позволяет делать две полезные вещи.
- Один из элементов в визуальном дереве может быть изменен с помощью стилей. Для выбора конкретного элемента с целью модификации служит свойство Style.TargetType. Можно даже использовать триггеры для автоматического внесения изменений, когда изменяется свойство элемента управления. Тем не менее, определенные детали модифицировать трудно или невозможно.
- Для элемента управления можно создать новый шаблон. Шаблон элемента управления будет использоваться для построения визуального дерева именно так, как было запланировано.
Источник
Программный просмотр логического и визуального дерева
Хотя анализ логического дерева окна во время выполнения — не слишком часто применяемое программное действие в WPF, стоит упомянуть, что в пространстве имен System.Windows определен класс по имени LogicalTreeHelper, который позволяет просматривать структуру логического дерева во время выполнения. Для иллюстрации связи между логическими деревьями, визуальными деревьями и шаблонами элементов управления создайте новое приложение WPF.
Модифицируйте разметку окна, чтобы она содержала два элемента управления Button и большой доступный только для чтения элемент TextBox с включенными линейками прокрутки. Воспользуйтесь IDE-средой для обработки события Click каждой кнопки. Ниже показана необходимая XAML-разметка:
Показать в TreeView Показать в TextBox
using System.Windows.Markup; //. public partial class MainWindow : Window < public MainWindow() < InitializeComponent(); tw.Name = "treeView"; tw.BorderBrush = new SolidColorBrush(Colors.Blue); tw.BorderThickness = new Thickness(2.0); tw.Margin = new Thickness(5.0); >public void ShowLogicalTree(DependencyObject element) < // Очистить дерево tw.Items.Clear(); // Начать обработку элементов ProcessElement(element, null); >private TreeView tw = new TreeView(); // Новое диалоговое окно с элементом TreeView public void MyShowDialog() < Window win = new Window(); win.Title = "LogicTreeDisplay"; win.Height = 400; win.Width = 250; Grid grid = new Grid(); IAddChild container = grid; container.AddChild(tw); container = win; container.AddChild(grid); ShowLogicalTree(this); win.ShowDialog(); >public void ProcessElement(DependencyObject element, TreeViewItem previousItem) < TreeViewItem item = new TreeViewItem(); item.Header = element.GetType().Name; item.IsExpanded = true; if (previousItem == null) < tw.Items.Add(item); >else < previousItem.Items.Add(item); >for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++) < ProcessElement(VisualTreeHelper.GetChild(element, i), item); >> private void Btn_visualTreeView_Click(object sender, RoutedEventArgs e) < if ((bool)InTreeView.IsChecked) < MyShowDialog(); >> >
Класс LogicalTreeHelper предлагает относительно небольшой набор методов, перечисленных ниже. Хотя эти методы могут иногда пригодиться, в большинстве случаев вместо них используются методы специфического FrameworkElement.
FindLogicalNode()
Ищет определенный элемент по имени, начиная с указанного, вниз по логическому дереву
BringIntoView()
Прокручивает элемент в область видимости (если он находится в прокручиваемом контейнере и в данный момент не виден). Метод FrameworkElement.BringIntoView() выполняет тот же трюк
Получает родительский элемент определенного элемента
GetChildren()
Получает дочерний элемент определенного элемента. Как известно, разные элементы поддерживают разные модели содержимого. Например, панели поддерживают множество дочерних элементов, а элементы управления содержимым — только один дочерний элемент. Однако метод GetChildren() абстрагирован от этого различия и работает с обоими типами элементов
Класс VisualTreeHelper предлагает несколько похожих методов — GetChildrenCount(), GetChild() и GetParent() — наряду с небольшим набором методов, предназначенных для выполнения низкоуровневого рисования. (Например, здесь есть методы для проверки попадания и проверки границ)
Класс VisualTreeHelper также предоставляет интересный способ исследования визуального дерева внутри приложения. С использованием метода GetChild() можно углубиться в визуальное дерево любого окна и отобразить его для наглядности. Это замечательное учебное пособие, не требующее ничего кроме порции рекурсивного кода.
Исследовать визуальное дерево другого приложения можно с помощью замечательной утилиты Snoop. Она позволяет просматривать визуальное дерево любого функционирующего приложения WPF. Можно также просмотреть детали любого элемента, отследить любые маршрутизируемые события, просмотреть и даже модифицировать свойства элементов.
Источник