Как вы уже видели, атрибуты элемента устанавливают свойства соответствующего объекта. Например, текстовые поля конфигурируют выравнивание, поля и шрифт:
<TextBox Name="txt1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
FontFamily="Arial" FontSize="20" Foreground="Blue"></TextBox>
Чтобы это заработало, класс System.Windows.Controls.TextBox должен предоставить следующие свойства: VerticalAlignment, HorizontalAlignment, FontFamily, FontSize и Foreground.
Чтобы заставить эту систему работать, анализатор XAML должен выполнить больше работы, чем может показаться на первый взгляд. Значение в атрибуте XAML всегда представлено простой строкой. Однако свойства объекта могут быть любого типа .NET. В предыдущем примере было два свойства, использующих перечисления (VerticalAlignment и HorizontalAlignment), одна строка (FontFamily), одно целое число (FontSize) и один объект Brush (Foreground).
Чтобы преодолеть зазор между строковыми значениями и не строковыми свойствами, анализатор XAML должен выполнить преобразование. Это преобразование осуществляется конвертерами типов - базовой частью инфраструктуры .NET, которая существует еще со времен .NET 1.0.
По сути, конвертер типов играет только одну роль - он предоставляет служебные методы, которые могут преобразовывать определенный тип данных .NET в любой другой тип .NET, такой как строку в данном случае. При поиске нужного конвертера типа анализатор XAML предпринимает следующие два действия:
Проверяет объявление свойства в поисках атрибута TypeConverter. (Если атрибут TypeConverter присутствует, он указывает класс, выполняющий преобразование.) Например, когда используется такое свойство, как Foreground, .NET проверяет объявление свойства Foreground.
Если в объявлении свойства отсутствует атрибут TypeConverter, то анализатор XAML проверяет объявление класса соответствующего типа данных. Например, свойство Foreground использует объект Brush. Класс Brush (и его наследники) используют BrushConverter, потому что класс Brush оснащен объявлением атрибута TypeConverter (typeof(BrushConverter)).
Если в объявлении свойства или объявлении класса не оказывается ассоциированного конвертера типа, то анализатор XAML генерирует ошибку.
Эта система проста и гибка. Если вы устанавливаете конвертер типа на уровне класса, то этот конвертер применяется к каждому свойству, использующему этот класс. С другой стороны, если вы хотите обеспечить тонкую настройку работы конвертера типа для конкретного свойства, то вместо этого можете применять атрибут TypeConverter объявления свойства.
Формально возможно использовать конвертеры типов в коде, но синтаксис при этом несколько мудреный. Почти всегда лучше непосредственно установить свойство - это не только быстрее, но также позволяет избежать потенциальных ошибок от опечаток в строках, которые не проявляются до момента выполнения. (Эта проблема не затрагивает XAML, поскольку разметка XAML анализируется и проверяется во время компиляции.) Конечно, прежде чем можно будет устанавливать свойства в элементе WPF, необходимо узнать немного больше о базовых свойствах WPF и типах данных.
Как бы ни были удобны конвертеры типов, они подходят не для всех сценариев. Например, некоторые свойства являются полноценными объектами с собственными наборами свойств. Хотя можно создать строковое представление, которое будет использовать конвертер типа, этот синтаксис может оказаться трудным в применении, к тому же он подвержен ошибкам.
К счастью, XAML предусматривает другой выбор: синтаксис "свойство-элемент". С помощью этого синтаксиса можно добавлять дочерний элемент с именем в форме РодительскийЭлемент.ИмяСвойства. Например, у TextBox имеется свойство Background, которое позволяет указывать кисть, используемую для рисования области, находящейся под элементами управления. Чтобы применить сложную кисть - более совершенную, чем сплошное заполнение цветом, - понадобится добавить дочерний дескриптор по имени TextBox.Background.
Ключевая деталь, которая заставляет это работать - точка (.) в имени элемента. Это отличает свойства от других типов и вложенного содержимого.
Однако еще один вопрос остается: как установить сложное свойство после его идентификации? Трюк заключается в следующем. Внутрь вложенного элемента можно добавить другой дескриптор, чтобы создать экземпляр определенного класса. Например:
<TextBox Name="txt1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
FontFamily="Arial" FontSize="20" Foreground="Blue">
<TextBox.Background>
<!-- Задаем градиентную заливку -->
<LinearGradientBrush>
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0.00" Color="White" />
<GradientStop Offset="0.30" Color="Red" />
<GradientStop Offset="1.00" Color="Violet" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</TextBox.Background>
</TextBox>
LinearGradientBrush является частью набора пространств имен WPF, так что для дескрипторов можно применять пространство имен XML по умолчанию. Однако просто создать LinearGradientBrush недостаточно; нужно также указать цвета градиента. Это делается заполнением свойства LinearGradientBrush.GradientStops коллекцией объектов GradientStop.
Опять-таки, свойство GradientStops слишком сложное, чтобы его можно было установить только одним значением атрибута. Вместо этого следует положиться на синтаксис "свойство-элемент". И, наконец, можно заполнить коллекцию GradientStops серией объектов GradientStop. Каждый объект GradientStop имеет свойства Offset и Color. Указать эти два значения можно с помощью обычного синтаксиса "свойство-элемент". Результат:
Синтаксис "свойство-элемент" можно использовать для любого свойства. Однако если свойство имеет подходящий конвертер типа, обычно будет применяться более простой подход "свойство-атрибут". Это дает более компактный код.