Наряду с обычными свойствами 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, понадобится включить дескриптор, указывающий класс коллекции, что обеспечит создание объекта коллекции. Если имеется экземпляр коллекции по умолчанию, который нужно просто заполнить, эту часть можно опустить.