Присоединенные свойства
Наряду с обычными свойствами XAML также включает концепцию присоединенных свойств (attached property) — свойств, которые могут применяться к нескольким элементам управления, но определены в другом классе. В WPF присоединенные свойства часто используются для управления компоновкой.
Рассмотрим, как это работает. Каждый элемент управления обладает собственным набором внутренних свойств. (Например, текстовое поле имеет специфический шрифт, цвет текста и текстовое содержимое — все это определено свойствами FontFamily, Foreground и Text.) После помещения внутрь контейнера элемент управления получает дополнительные свойства, которые зависят от типа контейнера. (Например, если текстовое поле помещается внутрь экранной сетки, то нужно каким-то образом указать ячейку для помещения.) Эти дополнительные детали устанавливаются с использованием присоединенных свойств.
Присоединенные свойства всегда имеют имя, состоящее из двух частей, в форме ОпределяемыйТип.ИмяСвойства. Этот синтаксис позволяет анализатору XAML отличать нормальные свойства от присоединенных. Ниже показан пример использования присоединенных свойств:
<StackPanel Grid.Row="0">
...
</StackPanel>
<WrapPanel Grid.Row="1">
...
</WrapPanel>
Присоединенные свойства в действительности вообще свойствами не являются. На самом деле они транслируются в вызовы методов. Анализатор XAML вызывает статический метод, имеющий форму ОпределяемыйТип.SetИмяСвойства(). Например, в предыдущем фрагменте XAML определяемым типом является класс Grid, а свойством — Row, поэтому анализатор вызывает метод Grid.SetRow().
При вызове метода SetИмяСвойства() анализатор передает два параметра: модифицируемый объект и указанное значение свойства. Например, в случае установки свойства Grid.Row на элементе управления StackPanel анализатор XAML выполняет следующий код:
Grid.SetRow(stp1, 0);
Этот шаблон (с вызовом статического метода определенного типа) удобен тем, что скрывает то, что происходит на самом деле. На первый взгляд этот код выглядит так, будто номер строки сохраняется в объекте Grid. Однако номер строки в действительности сохраняется в объекте, которого он касается, в данном случае — StackPanel.
Присоединенные свойства — центральный ингредиент WPF. Они действуют как система расширения общего назначения. Например, определяя свойство Row как присоединенное, вы гарантируете его применимость с любым элементом управления. Другой вариант — сделать его частью базового класса, такого как FrameworkElement, однако это усложнит жизнь. Это не только засорит общедоступный интерфейс свойствами, которые понадобятся только при определенных условиях (в данном случае — когда элемент используется внутри Grid), но также сделает невозможным добавление новых типов контейнеров, которые потребуют новых свойств.
Вложенные элементы
Как уже было показано, документы XAML представляют собой дерево элементов с высокой степенью вложенности. В рассмотренном примере элемент Window содержит элемент Grid, который, в свою очередь, содержит элементы TextBox и Button.
XAML позволяет каждому элементу решать, как ему следует поступать с вложенными элементами. Это взаимодействие осуществляется при посредничестве одного из трех механизмов, запускаемых в описанном ниже порядке:
- Если родительский элемент реализует интерфейс IList, анализатор вызывает IList.Add(), передавая ему дочерний элемент.
- Если родительский элемент реализует интерфейс IDictionary, анализатор вызывает IDictionary.Add() и передает ему дочерний элемент. При использовании коллекции-словаря понадобится также устанавливать атрибут х:Кеу, чтобы назначить ключевое имя каждому элементу.
- Если родительский элемент оснащен атрибутом ContentProperty, анализатор использует дочерний элемент, чтобы установить это свойство.
Например, ранее было показано, что LinearGradientBrush может содержать коллекцию объектов GradientStop, используя синтаксис вроде следующего:
<LinearGradientBrush>
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0.00" Color="White" />
<GradientStop Offset="0.30" Color="Red" />
<GradientStop Offset="1.00" Color="Violet" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
Анализатор XAML распознает элемент LinearGradientBrush.GradientStops как сложное свойство, потому что оно включает точку. Однако ему нужно обработать внутренние дескрипторы (три элемента GradientStop) слегка по-другому. В этом случае анализатор распознает, что свойство GradientStops возвращает объект GradientStopCollection, a GradientStopCollection реализует интерфейс IList. Поэтому он предполагает (грубо), что каждый GradientStop должен быть добавлен к коллекции с помощью метода IList.Add():
GradientStop gradientStop1 = new GradientStop(); gradientStop1.Offset = 0; gradientStop1.Color = Colors.White; IList list = brush.GradientStops; list.Add(gradientStop1);
Некоторые свойства могут поддерживать более одного типа коллекций. В этом случае вы должны добавить дескриптор, указывающий класс коллекции:
<LinearGradientBrush>
<LinearGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Offset="0.00" Color="White" />
<GradientStop Offset="0.30" Color="Red" />
<GradientStop Offset="1.00" Color="Violet" />
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
Если по умолчанию коллекция установлена в null, понадобится включить дескриптор, указывающий класс коллекции, что обеспечит создание объекта коллекции. Если имеется экземпляр коллекции по умолчанию, который нужно просто заполнить, эту часть можно опустить.