Для кого эта статья: для людей, которые только начинают своё знакомство с технологиями использующими XAML. Чтобы не усложнять статью, я не касаюсь многих деталей вроде Markup Extensions, управления ресурсами и т.п. Прочитав данную статью, я надеюсь, вы сможете понять что происходит под капотом XAML парсера и более чётко представлять как из вашего текстового документа получается граф объектов в памяти, с различными свойствами.
XAML — это язык разметки, который появился вместе с первой версией WPF от Microsoft. Сейчас он также используется в Silverlight и Windows Phone 7 (сути тот же Silverlight). Таким образом, сейчас довольно много людей активно используют XAML. Однако для эффективной работы полезно будет понять концепции, которые стоят за я языком, чтобы отдельные конструкции не казались странными.
В первую очередь стоит провести маленькое лирическое отступление. Существует два основных вида языков программирования: императивные и декларативные.
Императивные языки — это всем известные языки программирования, вроде C, C++, C#, Pascal, Basic и множество других. Основная идея в том, что в императивном языке мы говорим, что нужно сделать. Но не говорим, что должно получиться (обычно это мы должны описать и проверить в unit-тестах).
Декларативные языки, в обратную сторону, позволяют нам описать состояние, которого мы хотим добиться, но не требуют (и обычно не дают) описать как прийти в это состояние. Примеры таких языков: XAML (да и вообще все основанные на иерархической разметке XML, HTML и т.п.).
Итак в чём разница?
Допустим я хочу создать TextBox и задать ему в текст «Habr», на C# это будет выглядеть так:
var tb = new TextBox();
tb.Text = "Habr";
На XAML это будет выглядеть так:
<TextBox Text="Habr"/>
Разница очевидна. В первом случае, я сказал:
1. Создать экземпляр класса TextBox и присвоить его переменной tb.
2. Присвоить свойству переменной tb.Text значение «Habr».
Во втором, я сказал, что хочу получить в итоге TextBox со значением «Habr» в тексте. А уже как это будет сделано меня не волнует, этим занимается XAML парсер. Такое длинное отступление важно, чтобы понять как работает парсер.
Итак, теперь более подробный пример, с объяснением работы парсера:
<UserControl x:Class="WpfApplication1.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextBox Text="Habr" Foreground="Yellow">
<TextBox.Background>
<SolidColorBrush Color="Red"/>
</TextBox.Background>
</TextBox>
</UserControl>
Что же тут просиходит?
Начнём с простого: все тэги, в которых нет точки "." в XAML заставляют парсер создать экземпляр класса. Как парсер знает какой класс создать? Это делается за счёт установки соответствия между пространствами имён XML указанными в начале XAML и пространствами имён .Net.
Записи вида xmlns=«schemas.microsoft.com/winfx/2006/xaml/presentation» указывают парсеру какие будут использоваться пространства имён. Причём некоторые пространства связаны с .Net пространствами по умолчанию (то есть не требуют указания пространства из .Net), как в данном примере. «schemas.microsoft.com/winfx/2006/xaml/presentation» связано с System.Windows.Controls из PresentationFramework.dll. Для других нужно явно указывать сооветствие: xmlns:CustomPrefix=«clr-namespace:WpfApplication1»
В этом примере
- CustomPrefix — любой идентификатор легальный в XML, который вы будете использовать для обращения к своим объектам.
- clr-namespace: — специальный префикс, который обозначает, что дальше пойдёт пространство имён .Net
- WpfApplication1 — собственно ваше пространство имён.
После того как вы объявите ваше собственное пространство имён, вы можете создавать элементы из него:
<CustomPrefix:CustomObject/>
Итак наш XAML заставляет парсер создать экземпляр класса WpfApplication1.UserControl1, потом парсер видит, что мы хотим, чтобы в свойстве Content нашего контрола находился TextBox, парсер и это сделает и так далее.
Хорошо, с объектами разобрались. Но ведь есть ещё свойства. Для свойств есть два варианта синтаксиса:
- Атрибуты:
<TextBox Text="Habr"/>
- Тэги:
<TextBox> <TextBox.Text>Habr</TextBox.Text> </TextBox>
- Есть ещё вариант 2.1. Когда для наиболее часто используемого свойства можно задать содержимое просто указав его внутри объекта:
<TextBox>Habr</TextBox>
Эта запись эквивалента пункту 2, потому что объект TextBox отмечен атрибутом
[ContentProperty("Text")]
Теперь подробнее о двух вариантах:
1. В XML атрибутах, вполне очевидно, можно хранить только строки. Поэтому какой бы ни был тип свойства, которое вы хотите установить, на самом деле вы задаёте строковое значение. А уже во время создания объекта парсер конвертирует это значение из строки к тому типу, который требуется.
Для примитивных типов всё легко, я не знаю на 100%, но вполне вероятно, что парсер просто вызывает методы класса Convert или интерфейса IConvertible, если он реализован целевым типом и поддерживает конвертацию из строки. Но как же быть со сложными объектами?
Простой пример:
<TextBox Background="Red"/>
Свойство TextBox.Background имеет тип Brush — это абстрактный класс, у которого есть несколько конкретных реализаций, например SolidColorBrush, LinerGradientBrush и другие. Так как же наша строка «Red» превращается в подкласс Brush? За это отвечает конвертер. WPF имеет набор встроенных стандартных конвертеров, которые не нужно указывать явно. Эти конвертеры поддерживают довольно много типов, в том числе как в нашем примере, есть конвертер из строки в Brush, который создаёт экземляр SolidColorBrush и задаёт ему цвет указанный в строке.
Что делать, если вы хотите установить значение свойству не поддерживаемому стандартным конвертером или просто провести какую-то операцию над значением перед установкой? — Использовать собственный конвертер. Для этого достаточно реализовать интерфейс IValueConverter, в нём записать все необходимые манипуляции, а потом использовать конвертер в XAML следующим образом:
<TextBox Background="{Binding Source='Red-Green', Converter={StaticResource GradientColorConverter}}">
Конечно данный пример выглядит несколько странно, но чаще всего данные берутся из объектов бизнес-логики, тогда всё встанет на свои места.
И конечно, чтобы пример сработал, перед использованием конвертер нужно добавить в ресурсы, например так:
<TextBox Background="{Binding Source='Red-Green', Converter={StaticResource GradientColorConverter}}">
<TextBox.Resources>
<WpfApplication1:GradientColorConverter x:Key="GradientColorConverter"/>
</TextBox.Resources>
</TextBox>
Чтобы не захламлять статью, я не буду тут подробнее рассказывать о ресурсах, лучше почитать другие статьи или примеры.
2. Второй вариант как можно задать свойству значение в виде сложного объекта: использовать объектный синтаксис:
<TextBox Text="Habr">
<TextBox.Background>
<SolidColorBrush Color="Blue"/>
</TextBox.Background>
</TextBox>
Тут всё уже должно быть ясно. Создаём отдельный тэг для свойства объекта TextBox, а в нём создаём экземпляр SolidColorBrush или любого другого подтипа Brush с нужными нам параметрами.
На этом введение в концепцию XAML стоит закончить, надеюсь после прочтение этой статьи, некоторые конструкции языка станут понятнее, а главное будет легче создавать собственную разметку.
Автор: maxbl4