Понимание XAML

Для кого эта статья: для людей, которые только начинают своё знакомство с технологиями использующими XAML. Чтобы не усложнять статью, я не касаюсь многих деталей вроде Markup Extensions, управления ресурсами и т.п. Прочитав данную статью, я надеюсь, вы сможете понять что происходит под капотом XAML парсера и более чётко представлять как из вашего текстового документа получается граф объектов в памяти, с различными свойствами.

XAML — это язык разметки, который появился вместе с первой версией WPF от Microsoft. Сейчас он также используется в Silverlight и Windows Phone 7 (сути тот же Silverlight). Таким образом, сейчас довольно много людей активно используют XAML. Однако для эффективной работы полезно будет понять концепции, которые стоят за я языком, чтобы отдельные конструкции не казались странными.

В первую очередь стоит провести маленькое лирическое отступление. Существует два основных вида языков программирования: императивные и декларативные.

Императивные языки — это всем известные языки программирования, вроде C, C++, C#, Pascal, Basic и множество других. Основная идея в том, что в императивном языке мы говорим, что нужно сделать. Но не говорим, что должно получиться (обычно это мы должны описать и проверить в unit-тестах).

Декларативные языки, в обратную сторону, позволяют нам описать состояние, которого мы хотим добиться, но не требуют (и обычно не дают) описать как прийти в это состояние. Примеры таких языков: XAML (да и вообще все основанные на иерархической разметке XML, HTML и т.п.), также SQL.

Итак в чём разница?

Допустим я хочу создать 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, парсер и это сделает и так далее.

Хорошо, с объектами разобрались. Но ведь есть ещё свойства. Для свойств есть два варианта синтаксиса:
  1. Атрибуты:
    <TextBox Text="Habr"/>
  2. Тэги:
    
    <TextBox>
        <TextBox.Text>Habr</TextBox.Text>
    </TextBox>
  3. Есть ещё вариант 2.1. Когда для наиболее часто используемого свойства можно задать содержимое просто указав его внутри объекта:
    
    <TextBox>Habr</TextBox>
    Эта запись эквивалента пункту 2, потому что объект TextBox отмечен атрибутом
    [ContentProperty("Text")]

Теперь подробнее о двух вариантах:
1. В XML атрибутах, вполне очевидно, можно хранить только строки. Поэтому какой бы ни был тип свойства, которое вы хотите установить, на самом деле вы задаёте строковое значение. А уже во время создания объекта парсер конвертирует это значение из строки к тому типу, который требуется.
Примитивные типы, вроде int, DateTime, парсер умеет конвертировать сам, по сути он просто вызывает метод Parse соответствующего типа. Но как же быть со сложными объектами?
Простой пример:
<TextBox Background="Red"/>

Свойство TextBox.Background имеет тип Brush — это абстрактный класс, у которого есть несколько конкретных реализаций, например SolidColorBrush, LinerGradientBrush и другие. Так как же наша строка «Red» превращается в подкласс Brush? За это отвечает конвертер. Чтобы указать какой конвертер применить для определённого типа на тип устанавливается TypeConverterAttribute. Для многих встроенных типов уже есть конвертеры, в том числе как в нашем примере, есть конвертер из строки в 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 стоит закончить, надеюсь после прочтение этой статьи, некоторые конструкции языка станут понятнее, а главное будет легче создавать собственную разметку.

UPD Обновил часть про конвертеры благодаря комментариям afsherman.
Поделиться публикацией

Комментарии 14

  • НЛО прилетело и опубликовало эту надпись здесь
    • НЛО прилетело и опубликовало эту надпись здесь
        0
        Насчёт «стандартных» конвертеров. Я не спорю, что внутри это может быть реализовано не совсем так как я описал. Главное, что внешне это выглядит так. Вы ведь можете использовать многие типы свойств без указания конвертера?
        А для других нужно или явно его указать в ресурсах и биндинге, или как вы показываете: добавить атрибут на класс. Только ваш вариант сработает исключительно для собственных классов, которые можно редактировать. Так что вариант ограниченный.
        • НЛО прилетело и опубликовало эту надпись здесь
            0
            Ну да, тут наверно стоит перефразировать. Я подумаю как улучшить.
              0
              Поискал информацию по конвертерам. Вот интересная статья:
              msdn.microsoft.com/en-us/library/ee126043.aspx на английском.
              В целом я почти верно описал: для примитивов парсер сам делает конверсию.
              А вы правы, что для более сложных типов можно указать TypeConverterAttribute.
          0
          Если расписывать подробно, то нужна целая книга, а такие книги уже есть. Я считаю, что важно понимать самые основы, остальное потом постепенно приходит.
          +1
          Какое отношение статья имеет к разработке именно под WP7? Ничего специфичного я не увидел. И на мой взгляд такой материал утратил ценность уже в 2008 году.
            0
            Ну почему утратил? Если вы уже хорошо знаете как происходит разбор XAML, это не значит, что все это знают. Я просто вспомнил, как мне самому вначале было сложно разобраться что и куда идёт. Сейчас как раз новая волна популярности XAML за счёт людей, которые хотят писать софт под WP7.
              0
              Не думаю, что прям так и нахлынула «волна»… звучит будто цунами. Сильного проседания популярности я не заметил. Как раз наоборот с того времени и до сих пор идет плотный поток постов в блогах разного уровня популярности. И материала, как в данном посте, на мой взгляд пипл нахавался ПРЕОГРОНМНОЕ количество.
              Я думаю лозунг «Хабр за мысли из своей головы» не повод флудить размышлениями на заезженную тему. Да и к тому же, судя по посту, основания для изложенных мыслей неособо-то и твердые.
            0
            xaml достаточно непрост, чтобы объяснить его новичку вот такой небольшой статьей.
              +1
              К декларативным языкам можно ещё добавить SQL
                0
                А разве он декларативный? Я бы ещё с натяжкой назвал декларативной часть по созданию таблиц. А всё остальное явные команды вида «что сделать»
                  0
                  Вы же сами пишете «Декларативные языки, в обратную сторону, позволяют нам описать состояние, которого мы хотим добиться, но не требуют (и обычно не дают) описать как прийти в это состояние»
                  И в википедии то же самое.

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.