
- •Особенности xaml
- •Графический интерфейс пользователя до wpf
- •Определение MainWindow в xaml
- •Пространства имен xaml
- •Дополнительные дескрипторы xaml
- •Классы отделенного кода xaml
- •Метод InitializeComponent()
- •Именование элементов
- •Управление объявлениями классов и переменных-членов
- •Сложные свойства
- •Расширения разметки xaml
- •Вложенные элементы
- •События xaml
- •Использование типов из других пространств имен
- •Код и скомпилированный xaml
- •Только xaml
- •Автоматическая привязка событий
- •Встроенные типы
- •Расширенное создание объектов
Сложные свойства
Как бы ни были удобны конвертеры типов, они подходят не для всех сценариев. Например, некоторые свойства являются полноценными объектами с собственными наборами свойств. Хотя можно создать строковое представление, которое будет использовать конвертер типа, этот синтаксис может оказаться трудным в применении, к тому же он подвержен ошибкам.
К счастью, XAML предусматривает другой выбор: синтаксис "свойство-элемент". С помощью этого синтаксиса можно добавлять дочерний элемент с именем в форме РодительскийЭлемент.ИмяСвойства. Например, у Grid имеется свойство Background, которое позволяет указывать кисть, используемую для рисования области, находящейся под элементами управления. Чтобы применить сложную кисть — более совершенную, чем сплошное заполнение цветом, — понадобится добавить дочерний дескриптор по имени Grid.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. Указать эти два значения можно с помощью обычного синтаксиса "свойство-элемент". Результат:
Синтаксис "свойство-элемент" можно использовать для любого свойства. Однако если свойство имеет подходящий конвертер типа, обычно будет применяться более простой подход "свойство-атрибут". Это дает более компактный код.
Расширения разметки xaml
Для большинства свойств синтаксис свойств XAML работает исключительно хорошо. Но в некоторых случаях просто невозможно жестко закодировать значение свойства. Например, значение свойства должно быть установлено в уже существующий объект. Или может понадобиться установить значение свойства динамически, привязывая его к свойству в другом элементе управления. В обоих таких случаях необходимо использовать расширение разметки (markup extension) — специализированный синтаксис, устанавливающий свойство нестандартным образом.
Расширения разметки позволяют анализатору XAML получать значение свойства из выделенного внешнего класса. Это может быть очень выгодно, учитывая, что некоторые значения свойств требуют выполнения множества операторов кода для поиска значения.
Расширения разметки предлагают способ ясного расширения грамматики XAML новой функциональностью. Расширение разметки внутренне представлено как класс, унаследованный от MarkupExtension. Следует подчеркнуть: шансы, что когда-либо придется строить специальное расширение разметки, невелики. Тем не менее, подмножество ключевых слов XAML (таких как х:Array, x:Null, x:Static, х:Туре) — это именно расширения разметки.
Расширение разметки заключается в фигурные скобки, как показано ниже:
<Элемент УстанавливаемоеСвойство = "{РасширениеРазметки}"/>
Расширения разметки используют синтаксис {КлассРасширенияРазметки Аргумент}. В этом случае расширением разметки служит класс StaticExtension. (По соглашению при ссылке на класс расширения последнее слово Extension можете опустить.)
Префикс х указывает на то, что StaticExtension находится в одном из пространств имен XAML. Также вы встретите расширения разметки, являющиеся частью пространств имен WPF, но не имеющие префикса х.
Все расширения разметки реализованы классами, производными от System.Windows.Markup.MarkupExtension. Базовый класс MarkupExtension чрезвычайно прост — он включает единственный метод ProvideValue(), получающий необходимое значение. Другими словами, когда анализатор XAML встречает предыдущий оператор, он создает экземпляр класса StaticExtension, а затем вызывает ProvideValue(), чтобы получить объект, возвращенный статическим свойством. Ниже представлен пример использования расширений разметки:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CorLib="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525">
<Grid x:Name="MyGrid">
<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>
<StackPanel>
<!-- Используем расширение разметки для получения значения статического члена класса -->
<Label Content="{x:Static CorLib:Environment.OSVersion}"></Label>
<Label Content="{x:Static CorLib:Environment.ProcessorCount}"></Label>
<!-- Оператор typeof на XAML -->
<Label Content="{x:Type BulletDecorator}"></Label>
<Label Content="{x:Type CorLib:Int32}"></Label>
<!-- Наполнение элемента TextBox массивом строк -->
<ListBox Width="200" Height="50">
<ListBox.ItemsSource>
<x:Array Type="CorLib:String">
<CorLib:String>Элемент 1</CorLib:String>
<CorLib:String>Элемент 2</CorLib:String>
<CorLib:String>Элемент 3</CorLib:String>
</x:Array>
</ListBox.ItemsSource>
</ListBox>
</StackPanel>
</Grid>
</Window>
Прежде всего, обратите внимание, что определение <Window> имеет новое объявление пространства имен XML, что позволяет получать доступ к пространству имен System сборки mscorlib.dll. Имея это пространство имен, сначала с помощью расширения разметки x:Static извлекаются значения OSVersion и ProcessorCount класса System.Environment.
Расширение разметки х:Туре позволяет получить доступ к описанию метаданных указанного элемента. Здесь просто назначаются полностью квалифицированные имена типов WPF BulletDecorator и System.Int32.
Наиболее интересная часть показанной выше разметки связана с элементом ListBox. Его свойство ItemSource устанавливается в массив строк, полностью объявленный в разметке. Обратите внимание, что расширение разметки х:Array позволяет указывать набор подэлементов внутри своего контекста.
Присоединенные свойства и вложенные элементы XAML
Присоединенные свойства
Наряду с обычными свойствами 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), но также сделает невозможным добавление новых типов контейнеров, которые потребуют новых свойств.