Часть 3. Расширения разметки XAML

Download Sample Скачайте пример

Расширения разметки 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>

Снимок экрана: согласованный стилизация и стилизация, зависящая от платформы:

Styled Controls

Хотя в верхней части страницы чаще всего определяется 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-namespaceassembly. Синтаксис был определен таким образом намеренно: большинство объявлений пространства имен 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 относительно экрана зависит от платформы:

Controls using x:Static Markup Extension

Другие стандартные расширения разметки

Несколько расширений разметки являются встроенными в 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-файле естественно заключить значения свойств в кавычки. Сопротивляйтесь соблазну!

Ниже приведена программа:

Relative Layout using Constraints

Итоги

Расширения разметки XAML, показанные здесь, обеспечивают важную поддержку файлов XAML. Но, возможно, наиболее ценным расширением разметки XAML является Binding, которое рассматривается в следующей части этой серии, часть 4. Основы привязки данных.