Понимание XAML

в 11:43, , рубрики: .net, silverlight, windows phone 7, wpf, XAML, разработка под windows phone, метки: , , ,

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

Хорошо, с объектами разобрались. Но ведь есть ещё свойства. Для свойств есть два варианта синтаксиса:

  1. Атрибуты:
    <TextBox Text="Habr"/>
  2. Тэги:
    <TextBox>
        <TextBox.Text>Habr</TextBox.Text>
    </TextBox>
  3. Есть ещё вариант 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

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js