Синтаксис XAML
Приложение Windows 8 разделено на программный код и разметку, потому что у каждого аспекта имеются свои преимущества. Несмотря на ограниченность разметки XAML в области сложной логики или вычислительных задач, полезно вынести в нее как можно большую часть программы. Разметка проще редактируется и дает более четкое представление о визуальной структуре страницы. Конечно, все данные в разметках являются строковыми, поэтому представление сложных объектов в разметке иногда выглядит громоздко. Так как в разметке нет конструкции цикла, стандартной для языков программирования, она также в большей степени подвержена повторениям.
Эти проблемы были учтены в синтаксисе XAML; самые важные решения рассматриваются в этой и следующих статьях. Давайте начнем обсуждение этой важнейшей темы с вопроса, который на первый взгляд не имеет к ней никакого отношения: определения градиентной кисти.
Градиентная кисть в программном коде
Свойство Background элемента Grid и свойство Foreground элемента TextBlock относятся к типу Brush. В программах, приводившихся ранее, этим свойствам задавались экземпляры класса, производного от Brush, называемого SolidColorBrush. Как было показано в предыдущей статье, вы можете создать экземпляр SolidColorBrush в коде и задать ему значение Color; в XAML это делается за вас.
SolidColorBrush — всего лишь одна из четырех доступных разновидностей кистей, как видно из следующей иерархии классов:
Только классы SolidColorBrush, LinearGradientBrush, ImageBrush, RadialGradientBrush и WebViewBrush поддерживают создание экземпляров. Как и большинство других классов, относящихся к работе с графикой, основная часть классов кистей определяется в пространстве Windows.UI.Xaml.Media, хотя WebViewBrush определяется в Windows.UI.Xaml.Controls.
Кисть LinearGradientBrush создает градиентный переход между двумя и более цветами. Допустим, вы хотите вывести текст, который у левого края окрашен в синий цвет, постепенно переходящий в красный к правому краю. Раз уж мы этим занялись, давайте создадим аналогичный градиент для свойства Background элемента Grid, но другим способом.
В нашей тестовой программе экземпляр TextBlock создается в XAML, а элементам Grid и TextBlock назначаются имена:
Конструктор в файле отделенного кода создает два отдельных объекта LinearGradientBrush, которые задаются свойству Background элемента Grid и свойству Foreground элемента TextBlock:
Кисти создаются с разными стилями инициализации свойств, но в остальном они идентичны. Класс LinearGradientBrush определяет два свойства StartPoint и EndPoint типа Point, который представляет собой структуру со свойствами X и Y, представляющими координаты точки на плоскости. Свойства StartPoint и EndPoint задаются относительно объекта, к которому применяется кисть, в стандартной оконной системе координат: значения X возрастают слева направо, а значения Y — сверху вниз. Точка (0, 0) соответствует левому верхнему углу, а точка (1, 0) — правому верхнему углу; градиент кисти направлен по воображаемой линии, соединяющей эти две точки. По умолчанию для StartPoint и EndPoint используются значения (0, 0) и (1, 1); таким образом, градиент следует из левого верхнего в правый нижний угол целевого объекта.
LinearGradientBrush также содержит свойство с именем GradientStops, которое содержит коллекцию объектов GradientStop. Каждый объект GradientStop обозначает смещение (Offset) относительно линии градиента и цвет (Color) в этом смещении.
Обычно смещения задаются в диапазоне от 0 до 1, но в особых случаях они могут выходить из диапазона кисти. LinearGradientBrush определяет дополнительные свойства, которые определяют способ вычисления градиента и способ закраски за границей наименьшего и наибольшего смещения. Результат выглядит так:
Если теперь рассмотреть возможность определения этих же кистей в XAML, неожиданно проявляются все ограничения разметки. XAML позволяет определить SolidColorBrush простым заданием цвета, но как задать свойству Foreground или Background текстовую строку, определяющую две точки с двумя и более смещениями и цветами?
Синтаксис элементов свойств
К счастью, выход существует. Как вы уже видели, для обозначения сплошной кисти SolidColorBrush в XAML обычно просто указывается цвет кисти:
Объект SolidColorBrush будет создан автоматически. Однако существует разновидность этого синтаксиса, которая позволяет более точно описать создаваемую кисть. Удалите свойство Foreground и разделите элемент TextBlock на начальный и конечный теги. В эти теги вставьте дополнительную пару из начального и конечного тегов с именем элемента и именем свойства, разделенными точкой, а в эти теги заключите объект, который нужно задать свойству:
Теперь из разметки четко видно, что свойству Foreground задается экземпляр SolidColorBrush.
Этот синтаксис, называемый синтаксисом элементов свойств, является важной особенностью XAML. Хотя на первый взгляд вам может показаться (как показалось мне), что этот синтаксис является не то расширением, не то искажением стандартного XML, это определенно не так. Точка — абсолютно допустимый символ в имени элемента XML.
XAML устанавливает ограничение для тегов элементов свойств: начальный тег не может содержать никакой информации. Объект, задаваемый свойству, полностью задается содержимым между начальным и конечным тегами. В следующем примере для свойства Color объекта SolidColorBrush используется вторая пара тегов элемента свойства:
При желании два других свойства TextBlock можно задать аналогичным образом:
Но никакого смысла в этом нет — для таких простых свойств синтаксис атрибутов проще и компактнее. Синтаксис элементов свойств полезен при выражении более сложных объектов — таких, как LinearGradientBrush, как показано в примере ниже:
Сначала мы разместили в тегах элементов свойств элемент LinearGradientBrush, разделенный на начальный и конечный теги. Затем задали свойства StartPoint и EndPoint в начальном теге. Обратите внимание: два свойства типа Point задаются двумя числами, разделенными пробелом. При желании эти числа можно разделить запятыми.
У LinearGradientBrush есть свойство GradientStops, которое представляет собой коллекцию объектов GradientStop, это свойство относится к типу GradientStopCollection. Затем мы добавили в коллекцию два объекта GradientStop.
Мы получили то, что хотели получить: довольно сложное свойство разметки, выраженное исключительно на уровне разметки XAML.
Свойства содержимого
Только что рассмотренный нами синтаксис создания экземпляра и инициализации LinearGradientBrush в действительности получается чуть более экстравагантным, чем реально необходимо. Возможно, вы согласитесь с этим, когда примете во внимание один простой факт: во всех файлах XAML, рассматривавшихся нами до настоящего времени, отсутствовали некоторые ключевые свойства и элементы. Рассмотрим небольшой фрагмент разметки:
Из работы с классами в коде мы знаем, что элементы TextBlock добавлены в коллекцию Children элемента Grid, а сам элемент Grid задан свойству Content элемента Page. Но где же находятся свойства Children и Content в разметке?
Что ж, при желании их можно включить. Вот как выглядят элементы свойств Page.Content и Grid.Children в том виде, в котором они могли бы присутствовать в файле XAML:
В этой разметке по-прежнему отсутствует объект UIElementCollection, заданный свойству Children элемента Grid. Его невозможно включить явно, потому что в файлах XAML могут создаваться только экземпляры элементов, имеющих открытые конструкторы без параметров, а у класса UIElementCollection такого конструктора нет.
А теперь вопрос: почему элементы свойств Page.Content и Grid.Children не обязательны в файле XAML?
Все очень просто: все классы, упоминаемые в XAML, могут иметь одно (и только одно) свойство, называемое свойством содержимого. Для этого свойства (и только для него!) теги элементов свойств не обязательны.
Свойство содержимого конкретного класса задается в виде атрибута .NET. Где-то в фактическом определении класса Panel (производным от которого является Grid) находится атрибут с именем ContentProperty. Если бы эти классы определялись в C#, это выглядело бы так:
Смысл определения прост. Каждый раз, когда парсер XAML встречает разметку следующего вида:
он проверяет атрибут ContentProperty элемента Grid и обнаруживает, что элементы TextBlock должны быть добавлены в свойство Children.
Аналогичным образом в определении класса UserControl (производным от которого является Page) свойство Content определяется как свойство содержимого:
Вы можете определять атрибут ContentProperty в ваших собственных классах. Необходимый для этого класс ContentPropertyAttribute находится в пространстве имен Windows.UI.Xaml.Markup.
К сожалению, на момент написания этих строк в документации Windows Runtime сообщается лишь то, что атрибут ContentProperty установлен для класса (для примера загляните в раздел Attributes домашней страницы класса Panel), но не указано, для какого именно свойства он установлен! Возможно, в будущем документация будет доработана, но до тех пор придется изучать примеры и экспериментировать.
К счастью, многие свойства содержимого определяются как самые удобные свойства класса. Для LinearGradientBrush свойством содержимого является GradientStops. И хотя GradientStops относится к типу GradientStopCollection, XAML не требует явного включения объектов коллекций. Ни элементы свойств LinearGradientBrush.GradientStops, ни теги GradientStopCollection не являются обязательными, поэтому запись можно упростить до следующею вида:
Трудно представить себе разметку, которая была бы проще и при этом сохраняла бы корректность синтаксиса XML. Теоретически возможно переписать программу так, чтобы все делалось в разметке XAML:
Даже с синтаксисом элементов свойств разметка читается лучше, чем версия в коде. Код наиболее четко показывает, как что-то строится. Разметка показывает готовую конструкцию. Однако при этом следует обратить внимание на одно обстоятельство. Допустим, вы определили элемент свойства для Grid с несколькими потомками:
Также можно разместить элемент свойства внизу:
Однако вы не сможете разместить одну часть содержимого до элемента свойства, а другую после:
Откуда взялся такой запрет? Проблема становится очевидной при явном включении тегов элементов свойств для свойства Children:
Получается, что свойство Children определяется дважды с двумя разными коллекциями, а это запрещено.
Свойство содержимого TextBlock
Как было показано ранее, элемент TextBlock позволяет задавать текст как содержимое. Однако свойством содержимого элемента TextBlock является не свойство Text, а свойство с именем Inlines типа InlineCollection — коллекции объектов Inline, или, говоря точнее, экземпляров классов, производных от Inline. Класс Inline и его производные классы находятся в пространстве имен Windows.UI.Xaml.Documents. Иерархия выглядит так:
Эти классы позволяют задавать разные виды отформатированного текста в одном элементе TextBlock. TextElement определяет Foreground и все свойства, относящиеся к шрифтам: FontFamily, FontSize, FontStyle, FontWeight (для назначения жирного начертания), FontStretch (узкое и широкое начертание для шрифтов, поддерживающих такую возможность), CharacterSpacing — и все они наследуются производными классами.
Классы Block и Paragraph в основном используются в сочетании с расширенной версией TextBlock, называемой RichTextBlock. Элемент Run — единственный класс, определяющий свойство Text, которое также является свойством содержимого Run. Все текстовое содержимое InlineCollection преобразуется в Run, кроме случаев, когда этот текст уже является содержимым Run. Объекты Run могут использоваться для явного задания различных шрифтовых свойств текстовых строк.
Класс Span определяет свойство Inlines, как и TextBlock. Это позволяет использовать вложение Span и классов, производных от него. Три потомка Span определяются как вспомогательные классы для упрощения записи. Например, класс Bold эквивалентен классу Span, у которого атрибуту FontWeight задано значение Bold.
В качестве примера рассмотрим элемент TextBlock с небольшой коллекцией Inlines, использующей вспомогательные классы с вложением:
В процессе разбора «лишние» фрагменты текста преобразуются в объекты Run, так что коллекция Inlines элемента TextBlock содержит шесть элементов: экземпляры Run, Bold, Run, Italic, Run и Bold. Коллекция Inlines первого элемента Bold содержит один объект Run, как и коллекция Inlines первого элемента Italic. Коллекция Inlines второго элемента Bold содержит объект Italic с коллекцией Inlines, содержащей объект Run.
Использование тегов Bold и Italic в TextBlock наглядно показывает, что синтаксис XAML основан на классах и свойствах, поддерживающих элементы. Тег Italic было бы невозможно вложить в тег Bold, если бы последний не содержал коллекции Inlines.
Ниже показано более обширное определение TextBlock с применением расширенных возможностей форматирования:
TextBlock назначается ширина 440 пикселов, чтобы элемент не был слишком широким. Для форматирования фрагментов текста всегда можно использовать отдельные элементы Run, как это делается в нескольких начальных строках абзаца, но если вам потребуется вложенное форматирование (особенно в связи со вспомогательными классами), лучше переключиться на Span и его производные классы.
Как видите, элемент LineBreak способен произвольно разбивать строки. Теоретически класс InlineUIContainer позволяет внедрить в текст любой элемент UIElement (например, Image), но эта возможность работает только в RichTextBlock, а не в обычном TextBlock.
Text block
Text block is the primary control for displaying read-only text in apps. You can use it to display single-line or multi-line text, inline hyperlinks, and text with formatting like bold, italic, or underlined.
Is this the right control?
A text block is typically easier to use and provides better text rendering performance than a rich text block, so it’s preferred for most app UI text. You can easily access and use text from a text block in your app by getting the value of the Text property. It also provides many of the same formatting options for customizing how your text is rendered.
Although you can put line breaks in the text, text block is designed to display a single paragraph and doesn’t support text indentation. Use a RichTextBlock when you need support for multiple paragraphs, multi-column text or other complex text layouts, or inline UI elements like images.
For more info about choosing the right text control, see the Text controls article.
Examples
XAML Controls Gallery |
---|