Часть 3. Расширения разметки XAML
Расширения разметки XAML представляют собой важную функцию в XAML, которая позволяет свойствам задавать объекты или значения, на которые ссылаются косвенно из других источников. Расширения разметки XAML особенно важны для совместного использования объектов и ссылок на константы, используемые во всем приложении, но они находят их большую служебную программу в привязках данных.
Расширения разметки XAML
Как правило, для задания свойств объекта явным значениям используется XAML, например строка, число, член перечисления или строка, преобразованная в значение за кулисами.
Однако иногда свойства должны вместо этого ссылаться на значения, определенные в другом месте или которые могут потребовать немного обработки по коду во время выполнения. В этих целях доступны расширения разметки XAML.
Эти расширения разметки XAML не являются расширениями XML. XAML является полностью юридическим XML. Они называются "расширениями", так как они поддерживаются кодом в классах, реализующих IMarkupExtension
. Вы можете написать собственные расширения разметки.
Во многих случаях расширения разметки XAML мгновенно распознаются в XAML-файлах, так как они отображаются как параметры атрибутов, разделенные фигурными скобками: { и }, но иногда расширения разметки отображаются в разметке как обычные элементы.
Общие ресурсы
Некоторые страницы XAML содержат несколько представлений со свойствами, заданными для одинаковых значений. Например, многие параметры свойств для этих Button
объектов одинаковы:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SharedResourcesPage"
Title="Shared Resources Page">
<StackLayout>
<Button Text="Do this!"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
BorderWidth="3"
Rotation="-15"
TextColor="Red"
FontSize="24" />
<Button Text="Do that!"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
BorderWidth="3"
Rotation="-15"
TextColor="Red"
FontSize="24" />
<Button Text="Do the other thing!"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
BorderWidth="3"
Rotation="-15"
TextColor="Red"
FontSize="24" />
</StackLayout>
</ContentPage>
Если одно из этих свойств необходимо изменить, вы можете сделать это изменение только один раз, а не три раза. Если бы это был код, скорее всего, вы будете использовать константы и статические объекты только для чтения, чтобы обеспечить согласованность таких значений и легко изменять.
В XAML одним из популярных решений является хранение таких значений или объектов в словаре ресурсов. Класс VisualElement
определяет свойство с именем Resources
типа ResourceDictionary
, которое является словарем с ключами типа string
и значений типа object
. Объекты можно поместить в этот словарь, а затем ссылаться на них из разметки, все в XAML.
Чтобы использовать словарь ресурсов на странице, включите пару тегов Resources
элементов свойства. Удобнее всего поместить их в верхней части страницы:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SharedResourcesPage"
Title="Shared Resources Page">
<ContentPage.Resources>
</ContentPage.Resources>
...
</ContentPage>
Кроме того, необходимо явно включить ResourceDictionary
теги:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SharedResourcesPage"
Title="Shared Resources Page">
<ContentPage.Resources>
<ResourceDictionary>
</ResourceDictionary>
</ContentPage.Resources>
...
</ContentPage>
Теперь объекты и значения различных типов можно добавить в словарь ресурсов. Эти типы должны быть экземплярами. Например, они не могут быть абстрактными классами. Эти типы также должны иметь открытый конструктор без параметров. Для каждого элемента требуется ключ словаря, указанный атрибутом x:Key
. Например:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SharedResourcesPage"
Title="Shared Resources Page">
<ContentPage.Resources>
<ResourceDictionary>
<LayoutOptions x:Key="horzOptions"
Alignment="Center" />
<LayoutOptions x:Key="vertOptions"
Alignment="Center"
Expands="True" />
</ResourceDictionary>
</ContentPage.Resources>
...
</ContentPage>
Эти два элемента являются значениями типа LayoutOptions
структуры, каждый из которых имеет уникальный ключ и один или два набора свойств. В коде и разметке гораздо чаще используются статические поля LayoutOptions
, но здесь удобнее задать свойства.
Теперь необходимо задать HorizontalOptions
и VerticalOptions
свойства этих кнопок для этих ресурсов, и это сделано с расширением StaticResource
разметки XAML:
<Button Text="Do this!"
HorizontalOptions="{StaticResource horzOptions}"
VerticalOptions="{StaticResource vertOptions}"
BorderWidth="3"
Rotation="-15"
TextColor="Red"
FontSize="24" />
StaticResource
Расширение разметки всегда разделено фигурными скобками и включает ключ словаря.
Имя StaticResource
отличает его от DynamicResource
, который Xamarin.Forms также поддерживает. DynamicResource
предназначен для ключей словаря, связанных со значениями, которые могут изменяться во время выполнения, а StaticResource
доступ к элементам из словаря осуществляется только один раз при создании элементов на странице.
BorderWidth
Для свойства необходимо сохранить двойник в словаре. XAML удобно определяет теги для распространенных типов данных, таких как x:Double
и x:Int32
:
<ContentPage.Resources>
<ResourceDictionary>
<LayoutOptions x:Key="horzOptions"
Alignment="Center" />
<LayoutOptions x:Key="vertOptions"
Alignment="Center"
Expands="True" />
<x:Double x:Key="borderWidth">
3
</x:Double>
</ResourceDictionary>
</ContentPage.Resources>
Вам не нужно ставить его на три строки. Эта запись словаря для этого угла поворота занимает только одну строку:
<ContentPage.Resources>
<ResourceDictionary>
<LayoutOptions x:Key="horzOptions"
Alignment="Center" />
<LayoutOptions x:Key="vertOptions"
Alignment="Center"
Expands="True" />
<x:Double x:Key="borderWidth">
3
</x:Double>
<x:Double x:Key="rotationAngle">-15</x:Double>
</ResourceDictionary>
</ContentPage.Resources>
Эти два ресурса можно ссылаться так же, как и LayoutOptions
на значения:
<Button Text="Do this!"
HorizontalOptions="{StaticResource horzOptions}"
VerticalOptions="{StaticResource vertOptions}"
BorderWidth="{StaticResource borderWidth}"
Rotation="{StaticResource rotationAngle}"
TextColor="Red"
FontSize="24" />
Для ресурсов типа Color
можно использовать те же строковые представления, которые используются при непосредственном назначении атрибутов этих типов. Преобразователи типов вызываются при создании ресурса. Ниже приведен ресурс типа Color
:
<Color x:Key="textColor">Red</Color>
Часто программы задают свойству FontSize
элемент перечисления NamedSize
, например Large
. Класс FontSizeConverter
работает за кулисами, чтобы преобразовать его в значение, зависящее от платформы, с помощью Device.GetNamedSized
метода. Однако при определении ресурса размера шрифта рекомендуется использовать числовое значение, показанное здесь как x:Double
тип:
<x:Double x:Key="fontSize">24</x:Double>
Теперь все свойства, кроме Text
того, определены параметрами ресурсов:
<Button Text="Do this!"
HorizontalOptions="{StaticResource horzOptions}"
VerticalOptions="{StaticResource vertOptions}"
BorderWidth="{StaticResource borderWidth}"
Rotation="{StaticResource rotationAngle}"
TextColor="{StaticResource textColor}"
FontSize="{StaticResource fontSize}" />
Кроме того, можно использовать OnPlatform
в словаре ресурсов для определения различных значений платформ. Вот как OnPlatform
объект может быть частью словаря ресурсов для различных цветов текста:
<OnPlatform x:Key="textColor"
x:TypeArguments="Color">
<On Platform="iOS" Value="Red" />
<On Platform="Android" Value="Aqua" />
<On Platform="UWP" Value="#80FF80" />
</OnPlatform>
Обратите внимание, что OnPlatform
получает как x:Key
атрибут, так как это объект в словаре и x:TypeArguments
атрибуте, так как это универсальный класс. Атрибуты iOS
и UWP
атрибуты преобразуются в Color
значения при инициализации Android
объекта.
Ниже приведен окончательный полный XAML-файл с тремя кнопками, обращаюющимися к шести общим значениям:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SharedResourcesPage"
Title="Shared Resources Page">
<ContentPage.Resources>
<ResourceDictionary>
<LayoutOptions x:Key="horzOptions"
Alignment="Center" />
<LayoutOptions x:Key="vertOptions"
Alignment="Center"
Expands="True" />
<x:Double x:Key="borderWidth">3</x:Double>
<x:Double x:Key="rotationAngle">-15</x:Double>
<OnPlatform x:Key="textColor"
x:TypeArguments="Color">
<On Platform="iOS" Value="Red" />
<On Platform="Android" Value="Aqua" />
<On Platform="UWP" Value="#80FF80" />
</OnPlatform>
<x:Double x:Key="fontSize">24</x:Double>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<Button Text="Do this!"
HorizontalOptions="{StaticResource horzOptions}"
VerticalOptions="{StaticResource vertOptions}"
BorderWidth="{StaticResource borderWidth}"
Rotation="{StaticResource rotationAngle}"
TextColor="{StaticResource textColor}"
FontSize="{StaticResource fontSize}" />
<Button Text="Do that!"
HorizontalOptions="{StaticResource horzOptions}"
VerticalOptions="{StaticResource vertOptions}"
BorderWidth="{StaticResource borderWidth}"
Rotation="{StaticResource rotationAngle}"
TextColor="{StaticResource textColor}"
FontSize="{StaticResource fontSize}" />
<Button Text="Do the other thing!"
HorizontalOptions="{StaticResource horzOptions}"
VerticalOptions="{StaticResource vertOptions}"
BorderWidth="{StaticResource borderWidth}"
Rotation="{StaticResource rotationAngle}"
TextColor="{StaticResource textColor}"
FontSize="{StaticResource fontSize}" />
</StackLayout>
</ContentPage>
Снимок экрана: согласованный стилизация и стилизация, зависящая от платформы:
Хотя в верхней части страницы чаще всего определяется Resources
коллекция, имейте в виду, что Resources
свойство определено VisualElement
и вы можете иметь Resources
коллекции на других элементах на странице. Например, попробуйте добавить его в StackLayout
этот пример:
<StackLayout>
<StackLayout.Resources>
<ResourceDictionary>
<Color x:Key="textColor">Blue</Color>
</ResourceDictionary>
</StackLayout.Resources>
...
</StackLayout>
Вы обнаружите, что цвет текста кнопок теперь синий. В основном, когда средство синтаксического анализа XAML встречает StaticResource
расширение разметки, оно выполняет поиск по дереву визуального элемента и использует первое ResourceDictionary
, что он встречает, содержащий этот ключ.
Одним из наиболее распространенных типов объектов, хранящихся в словарях ресурсов, является Xamarin.FormsStyle
коллекция параметров свойств. Стили рассматриваются в статье "Стили".
Иногда разработчики, не знакомые с XAML, могут ли они поместить визуальный элемент, например Label
или Button
в ResourceDictionary
. Хотя это, безусловно, возможно, это не имеет большого смысла. Цель состоит в том, чтобы предоставить общий ResourceDictionary
доступ к объектам. Визуальный элемент не может быть общим. Один и тот же экземпляр не может отображаться дважды на одной странице.
Расширение разметки x:Static
Несмотря на сходство их имен, x:Static
и StaticResource
очень разные. StaticResource
возвращает объект из словаря ресурсов при x:Static
доступе к одному из следующих элементов:
- общедоступное статическое поле
- общедоступное статическое свойство
- открытое поле константы
- элемент перечисления.
StaticResource
Расширение разметки поддерживается реализациями XAML, определяющими словарь ресурсов, в то время как x:Static
является встроенной частью XAML, как x
показывает префикс.
Ниже приведены несколько примеров, в которых показано, как x:Static
явно ссылаться на статические поля и элементы перечисления:
<Label Text="Hello, XAML!"
VerticalOptions="{x:Static LayoutOptions.Start}"
HorizontalTextAlignment="{x:Static TextAlignment.Center}"
TextColor="{x:Static Color.Aqua}" />
До сих пор это не очень впечатляюще. x:Static
Но расширение разметки также может ссылаться на статические поля или свойства из собственного кода. Например, вот AppConstants
класс, содержащий некоторые статические поля, которые могут потребоваться использовать на нескольких страницах в приложении:
using System;
using Xamarin.Forms;
namespace XamlSamples
{
static class AppConstants
{
public static readonly Thickness PagePadding;
public static readonly Font TitleFont;
public static readonly Color BackgroundColor = Color.Aqua;
public static readonly Color ForegroundColor = Color.Brown;
static AppConstants()
{
switch (Device.RuntimePlatform)
{
case Device.iOS:
PagePadding = new Thickness(5, 20, 5, 0);
TitleFont = Font.SystemFontOfSize(35, FontAttributes.Bold);
break;
case Device.Android:
PagePadding = new Thickness(5, 0, 5, 0);
TitleFont = Font.SystemFontOfSize(40, FontAttributes.Bold);
break;
case Device.UWP:
PagePadding = new Thickness(5, 0, 5, 0);
TitleFont = Font.SystemFontOfSize(50, FontAttributes.Bold);
break;
}
}
}
}
Чтобы ссылаться на статические поля этого класса в XAML-файле, вам потребуется какой-то способ указать в XAML-файле, где находится этот файл. Это можно сделать с объявлением пространства имен XML.
Помните, что файлы XAML, созданные в рамках стандартного Xamarin.Forms шаблона XAML, содержат два объявления пространства имен XML: один для доступа к Xamarin.Forms классам и другим для ссылки на теги и атрибуты, встроенные в XAML:
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
Для доступа к другим классам потребуется дополнительное объявление пространства имен XML. Каждое дополнительное объявление пространства имен XML определяет новый префикс. Для доступа к классам, локальным к общей библиотеке .NET Standard, например AppConstants
, программисты XAML часто используют префикс local
. Объявление пространства имен должно указывать имя пространства имен СРЕДЫ CLR (clR), также известное как имя пространства имен .NET, которое отображается в определении C# namespace
или в директиве using
:
xmlns:local="clr-namespace:XamlSamples"
Вы также можете определить объявления пространства имен XML для пространств имен .NET в любой сборке, на которую ссылается библиотека .NET Standard. Например, вот sys
префикс для стандартного пространства имен .NET System
, которое находится в сборке netstandard . Так как это другая сборка, необходимо также указать имя сборки, в данном случае netstandard:
xmlns:sys="clr-namespace:System;assembly=netstandard"
Обратите внимание, что за ключевое слово clr-namespace
следует двоеточие, а затем имя пространства имен .NET, за которым следует точка с запятой, ключевое словоassembly
, знак равенства и имя сборки.
Да, двоеточие следует, но знак равенства следует clr-namespace
assembly
. Синтаксис был определен таким образом намеренно: большинство объявлений пространства имен XML ссылались на URI, начинающий имя схемы URI, например http
, которое всегда следует двоеточию. Часть clr-namespace
этой строки предназначена для имитации этого соглашения.
Оба этих объявления пространства имен включены в пример StaticConstantsPage . Обратите внимание, что BoxView
измерения заданы Math.PI
и Math.E
масштабируются на 100:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples"
xmlns:sys="clr-namespace:System;assembly=netstandard"
x:Class="XamlSamples.StaticConstantsPage"
Title="Static Constants Page"
Padding="{x:Static local:AppConstants.PagePadding}">
<StackLayout>
<Label Text="Hello, XAML!"
TextColor="{x:Static local:AppConstants.BackgroundColor}"
BackgroundColor="{x:Static local:AppConstants.ForegroundColor}"
Font="{x:Static local:AppConstants.TitleFont}"
HorizontalOptions="Center" />
<BoxView WidthRequest="{x:Static sys:Math.PI}"
HeightRequest="{x:Static sys:Math.E}"
Color="{x:Static local:AppConstants.ForegroundColor}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Scale="100" />
</StackLayout>
</ContentPage>
Размер результирующего элемента BoxView
относительно экрана зависит от платформы:
Другие стандартные расширения разметки
Несколько расширений разметки являются встроенными в XAML и поддерживаются в Xamarin.Forms XAML-файлах. Некоторые из них не используются очень часто, но являются важными, если они нужны:
- Если свойство имеет значение, отличное
null
от значения по умолчанию, но вы хотите задать егоnull
в качестве значения, задайте его расширение разметки{x:Null}
. - Если свойство имеет тип
Type
, его можно назначитьType
объекту с помощью расширения{x:Type someClass}
разметки. - Массивы в XAML можно определить с помощью
x:Array
расширения разметки. Это расширение разметки имеет обязательный атрибут,Type
указывающий тип элементов в массиве. Binding
Расширение разметки рассматривается в части 4. Основы привязки данных.RelativeSource
Расширение разметки рассматривается в относительных привязках.
Расширение разметки ConstraintExpression
Расширения разметки могут иметь свойства, но они не задаются как атрибуты XML. В расширении разметки параметры свойств разделены запятыми, а кавычки не отображаются в фигурных скобках.
Это можно иллюстрировать с расширением Xamarin.Forms разметки с именем ConstraintExpression
, которое используется с классом RelativeLayout
. Можно указать расположение или размер дочернего представления в виде константы или относительно родительского или другого именованного представления. Синтаксис ConstraintExpression
позволяет задать позицию или размер представления с помощью Factor
свойства другого представления, а также .Constant
Что-нибудь более сложное, чем требует кода.
Ниже приведен пример.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.RelativeLayoutPage"
Title="RelativeLayout Page">
<RelativeLayout>
<!-- Upper left -->
<BoxView Color="Red"
RelativeLayout.XConstraint=
"{ConstraintExpression Type=Constant,
Constant=0}"
RelativeLayout.YConstraint=
"{ConstraintExpression Type=Constant,
Constant=0}" />
<!-- Upper right -->
<BoxView Color="Green"
RelativeLayout.XConstraint=
"{ConstraintExpression Type=RelativeToParent,
Property=Width,
Factor=1,
Constant=-40}"
RelativeLayout.YConstraint=
"{ConstraintExpression Type=Constant,
Constant=0}" />
<!-- Lower left -->
<BoxView Color="Blue"
RelativeLayout.XConstraint=
"{ConstraintExpression Type=Constant,
Constant=0}"
RelativeLayout.YConstraint=
"{ConstraintExpression Type=RelativeToParent,
Property=Height,
Factor=1,
Constant=-40}" />
<!-- Lower right -->
<BoxView Color="Yellow"
RelativeLayout.XConstraint=
"{ConstraintExpression Type=RelativeToParent,
Property=Width,
Factor=1,
Constant=-40}"
RelativeLayout.YConstraint=
"{ConstraintExpression Type=RelativeToParent,
Property=Height,
Factor=1,
Constant=-40}" />
<!-- Centered and 1/3 width and height of parent -->
<BoxView x:Name="oneThird"
Color="Red"
RelativeLayout.XConstraint=
"{ConstraintExpression Type=RelativeToParent,
Property=Width,
Factor=0.33}"
RelativeLayout.YConstraint=
"{ConstraintExpression Type=RelativeToParent,
Property=Height,
Factor=0.33}"
RelativeLayout.WidthConstraint=
"{ConstraintExpression Type=RelativeToParent,
Property=Width,
Factor=0.33}"
RelativeLayout.HeightConstraint=
"{ConstraintExpression Type=RelativeToParent,
Property=Height,
Factor=0.33}" />
<!-- 1/3 width and height of previous -->
<BoxView Color="Blue"
RelativeLayout.XConstraint=
"{ConstraintExpression Type=RelativeToView,
ElementName=oneThird,
Property=X}"
RelativeLayout.YConstraint=
"{ConstraintExpression Type=RelativeToView,
ElementName=oneThird,
Property=Y}"
RelativeLayout.WidthConstraint=
"{ConstraintExpression Type=RelativeToView,
ElementName=oneThird,
Property=Width,
Factor=0.33}"
RelativeLayout.HeightConstraint=
"{ConstraintExpression Type=RelativeToView,
ElementName=oneThird,
Property=Height,
Factor=0.33}" />
</RelativeLayout>
</ContentPage>
Возможно, наиболее важным уроком, который следует извлечь из этого примера, является синтаксис расширения разметки: кавычки не должны отображаться в фигурных скобках расширения разметки. При вводе расширения разметки в XAML-файле естественно заключить значения свойств в кавычки. Сопротивляйтесь соблазну!
Ниже приведена программа:
Итоги
Расширения разметки XAML, показанные здесь, обеспечивают важную поддержку файлов XAML. Но, возможно, наиболее ценным расширением разметки XAML является Binding
, которое рассматривается в следующей части этой серии, часть 4. Основы привязки данных.