Присоединенные свойства и вложенные элементы XAML

79

Присоединенные свойства

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

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

Пройди тесты
Лучший чат для C# программистов