Parte 3. Extensões de Marcação XAML

As extensões de marcação XAML constituem um recurso importante em XAML que permite que as propriedades sejam definidas como objetos ou valores que são referenciados indiretamente de outras fontes. As extensões de marcação XAML são particularmente importantes para compartilhar objetos e fazer referência a constantes usadas em um aplicativo, mas encontram sua maior utilidade em associações de dados.

Extensões de Marcação XAML

Em geral, você usa XAML para definir propriedades de um objeto para valores explícitos, como uma cadeia de caracteres, um número, um membro de enumeração ou uma cadeia de caracteres que é convertida em um valor nos bastidores.

Às vezes, no entanto, as propriedades devem, em vez disso, fazer referência a valores definidos em outro lugar, ou que podem exigir um pouco de processamento por código em tempo de execução. Para esses fins, as extensões de marcação XAML estão disponíveis.

Essas extensões de marcação XAML não são extensões de XML. XAML é XML totalmente legal. Eles são chamados de "extensões" porque são apoiados por código em classes que implementam IMarkupExtensiono . Você pode escrever suas próprias extensões de marcação personalizadas.

Em muitos casos, as extensões de marcação XAML são instantaneamente reconhecíveis em arquivos XAML porque aparecem como configurações de atributo delimitadas por chaves: { e }, mas às vezes as extensões de marcação aparecem na marcação como elementos convencionais.

Recursos compartilhados

Algumas páginas XAML contêm vários modos de exibição com propriedades definidas com os mesmos valores. Por exemplo, muitas das configurações de propriedade para esses Button objetos são as mesmas:

<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>

Se uma dessas propriedades precisar ser alterada, talvez você prefira fazer a alteração apenas uma vez em vez de três vezes. Se esse fosse o código, você provavelmente estaria usando constantes e objetos estáticos somente leitura para ajudar a manter esses valores consistentes e fáceis de modificar.

Em XAML, uma solução popular é armazenar esses valores ou objetos em um dicionário de recursos. A VisualElement classe define uma propriedade chamada Resources de tipo ResourceDictionary, que é um dicionário com chaves de tipo string e valores de tipo object. Você pode colocar objetos neste dicionário e, em seguida, fazer referência a eles a partir de marcação, tudo em XAML.

Para usar um dicionário de recursos em uma página, inclua um par de marcas de elemento de Resources propriedade. É mais conveniente colocá-los no topo da página:

<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>

Também é necessário incluir ResourceDictionary explicitamente tags:

<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>

Agora, objetos e valores de vários tipos podem ser adicionados ao dicionário de recursos. Esses tipos devem ser instanciáveis. Não podem ser aulas abstratas, por exemplo. Esses tipos também devem ter um construtor público sem parâmetros. Cada item requer uma chave de dicionário especificada com o x:Key atributo. Por exemplo:

<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>

Esses dois itens são valores do tipo LayoutOptionsde estrutura e cada um tem uma chave exclusiva e um ou dois conjuntos de propriedades. Em código e marcação, é muito mais comum usar os campos estáticos do LayoutOptions, mas aqui é mais conveniente definir as propriedades.

Agora é necessário definir as HorizontalOptions propriedades e VerticalOptions desses botões para esses recursos, e isso é feito com a StaticResource extensão de marcação XAML:

<Button Text="Do this!"
        HorizontalOptions="{StaticResource horzOptions}"
        VerticalOptions="{StaticResource vertOptions}"
        BorderWidth="3"
        Rotation="-15"
        TextColor="Red"
        FontSize="24" />

A StaticResource extensão de marcação é sempre delimitada com chaves e inclui a chave do dicionário.

O nome StaticResource o distingue de DynamicResource, que Xamarin.Forms também apoia. DynamicResource é para chaves de dicionário associadas a valores que podem mudar durante o tempo de execução, enquanto StaticResource acessa elementos do dicionário apenas uma vez quando os elementos na página são construídos.

Para o BorderWidth imóvel, é necessário guardar uma dupla no dicionário. O XAML define convenientemente marcas para tipos de dados comuns, como x:Double e 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>

Você não precisa colocá-lo em três linhas. Esta entrada de dicionário para este ângulo de rotação ocupa apenas uma linha:

<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>

Esses dois recursos podem ser referenciados da mesma forma que os LayoutOptions valores:

<Button Text="Do this!"
        HorizontalOptions="{StaticResource horzOptions}"
        VerticalOptions="{StaticResource vertOptions}"
        BorderWidth="{StaticResource borderWidth}"
        Rotation="{StaticResource rotationAngle}"
        TextColor="Red"
        FontSize="24" />

Para recursos do tipo Color, você pode usar as mesmas representações de cadeia de caracteres que você usa ao atribuir diretamente atributos desses tipos. Os conversores de tipo são chamados quando o recurso é criado. Aqui está um recurso do tipo Color:

<Color x:Key="textColor">Red</Color>

Muitas vezes, os NamedSize programas definem uma FontSize propriedade para um membro da enumeração, como Large. A FontSizeConverter classe trabalha nos bastidores para convertê-la em um valor dependente da plataforma usando o Device.GetNamedSized método. No entanto, ao definir um recurso de tamanho de fonte, faz mais sentido usar um valor numérico, mostrado aqui como um x:Double tipo:

<x:Double x:Key="fontSize">24</x:Double>

Agora, todas as propriedades, exceto Text são definidas pelas configurações do recurso:

<Button Text="Do this!"
        HorizontalOptions="{StaticResource horzOptions}"
        VerticalOptions="{StaticResource vertOptions}"
        BorderWidth="{StaticResource borderWidth}"
        Rotation="{StaticResource rotationAngle}"
        TextColor="{StaticResource textColor}"
        FontSize="{StaticResource fontSize}" />

Também é possível usar OnPlatform dentro do dicionário de recursos para definir valores diferentes para as plataformas. Veja como um OnPlatform objeto pode fazer parte do dicionário de recursos para diferentes cores de texto:

<OnPlatform x:Key="textColor"
            x:TypeArguments="Color">
    <On Platform="iOS" Value="Red" />
    <On Platform="Android" Value="Aqua" />
    <On Platform="UWP" Value="#80FF80" />
</OnPlatform>

Observe que OnPlatform obtém um x:Key atributo porque é um objeto no dicionário e um x:TypeArguments atributo porque é uma classe genérica. Os iOSatributos , Androide UWP são convertidos em Color valores quando o objeto é inicializado.

Aqui está o arquivo XAML completo final com três botões acessando seis valores compartilhados:

<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>

As capturas de tela verificam o estilo consistente e o estilo dependente da plataforma:

Controles com estilo

Embora seja mais comum definir a Resources coleção na parte superior da página, lembre-se de que a Resources propriedade é definida por VisualElement, e você pode ter Resources coleções em outros elementos na página. Por exemplo, tente adicionar um ao StackLayout neste exemplo:

<StackLayout>
    <StackLayout.Resources>
        <ResourceDictionary>
            <Color x:Key="textColor">Blue</Color>
        </ResourceDictionary>
    </StackLayout.Resources>
    ...
</StackLayout>

Você descobrirá que a cor do texto dos botões agora é azul. Basicamente, sempre que o analisador XAML encontra uma StaticResource extensão de marcação, ele pesquisa a árvore visual e usa os primeiros ResourceDictionary encontros que contém essa chave.

Um dos tipos mais comuns de objetos armazenados em dicionários de recursos é o Xamarin.FormsStyle, que define uma coleção de configurações de propriedade. Os estilos são discutidos no artigo Estilos.

Às vezes, os desenvolvedores novos em XAML se perguntam se podem colocar um elemento visual, como Label ou Button em um ResourceDictionaryarquivo . Embora seja certamente possível, não faz muito sentido. O objetivo do ResourceDictionary é compartilhar objetos. Um elemento visual não pode ser compartilhado. A mesma instância não pode aparecer duas vezes em uma única página.

A extensão de marcação x:Static

Apesar das semelhanças de seus nomes, x:Static e StaticResource são muito diferentes. StaticResource Retorna um objeto de um dicionário de recursos enquanto x:Static acessa um dos seguintes:

  • um campo estático público
  • uma propriedade estática pública
  • um campo público constante
  • um membro de enumeração.

A StaticResource extensão de marcação é suportada por implementações XAML que definem um dicionário de recursos, enquanto x:Static é uma parte intrínseca de XAML, como o prefixo x revela.

Aqui estão alguns exemplos que demonstram como x:Static podem fazer referência explícita a campos estáticos e membros de enumeração:

<Label Text="Hello, XAML!"
       VerticalOptions="{x:Static LayoutOptions.Start}"
       HorizontalTextAlignment="{x:Static TextAlignment.Center}"
       TextColor="{x:Static Color.Aqua}" />

Até agora, isso não é muito impressionante. Mas a x:Static extensão de marcação também pode fazer referência a campos estáticos ou propriedades de seu próprio código. Por exemplo, aqui está uma AppConstants classe que contém alguns campos estáticos que você pode querer usar em várias páginas em um aplicativo:

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;
            }
        }
    }
}

Para fazer referência aos campos estáticos dessa classe no arquivo XAML, você precisará de alguma maneira de indicar dentro do arquivo XAML onde esse arquivo está localizado. Você faz isso com uma declaração de namespace XML.

Lembre-se de que os arquivos XAML criados como parte do modelo XAML padrão Xamarin.Forms contêm duas declarações de namespace XML: uma para acessar Xamarin.Forms classes e outra para fazer referência a marcas e atributos intrínsecos ao XAML:

xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

Você precisará de declarações de namespace XML adicionais para acessar outras classes. Cada declaração de namespace XML adicional define um novo prefixo. Para acessar classes locais para a biblioteca do .NET Standard do aplicativo compartilhado, como AppConstants, os programadores XAML geralmente usam o prefixo local. A declaração de namespace deve indicar o nome do namespace CLR (Common Language Runtime), também conhecido como nome do namespace .NET, que é o nome que aparece em uma definição C# namespace ou em uma using diretiva:

xmlns:local="clr-namespace:XamlSamples"

Você também pode definir declarações de namespace XML para namespaces .NET em qualquer assembly que a biblioteca do .NET Standard faça referência. Por exemplo, aqui está um sys prefixo para o namespace .NET System padrão, que está no assembly netstandard. Como este é outro assembly, você também deve especificar o nome do assembly, neste caso netstandard:

xmlns:sys="clr-namespace:System;assembly=netstandard"

Observe que a palavra-chave clr-namespace é seguida por dois pontos e, em seguida, o nome do namespace .NET, seguido por um ponto-e-vírgula, a palavra-chave assembly, um sinal de igual e o nome do assembly.

Sim, segue-se clr-namespace dois pontos, mas segue-se assemblyo sinal de igual. A sintaxe foi definida desta forma deliberadamente: A maioria das declarações de namespace XML faz referência a um URI que inicia um nome de esquema de URI, como http, que é sempre seguido por dois pontos. A clr-namespace parte dessa cadeia de caracteres destina-se a imitar essa convenção.

Essas duas declarações de namespace estão incluídas no exemplo StaticConstantsPage . Observe que as BoxView dimensões são definidas como Math.PI e Math.E, mas dimensionadas por um fator de 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>

O tamanho do resultante BoxView em relação à tela depende da plataforma:

Controles usando x:Static Markup Extension

Outras extensões de marcação padrão

Várias extensões de marcação são intrínsecas ao XAML e têm suporte em Xamarin.Forms arquivos XAML. Alguns deles não são usados com muita frequência, mas são essenciais quando você precisa deles:

  • Se uma propriedade tiver um valor diferente null por padrão, mas você quiser defini-la como null, defina-a como a {x:Null} extensão de marcação.
  • Se uma propriedade for do tipo Type, você poderá atribuí-la a um Type objeto usando a extensão {x:Type someClass}de marcação .
  • Você pode definir matrizes em XAML usando a x:Array extensão de marcação. Essa extensão de marcação tem um atributo obrigatório chamado Type que indica o tipo dos elementos na matriz.
  • A Binding extensão de marcação é discutida na Parte 4. Noções básicas de vinculação de dados.
  • A RelativeSource extensão de marcação é discutida em Ligações relativas.

A extensão de marcação ConstraintExpression

As extensões de marcação podem ter propriedades, mas não são definidas como atributos XML. Em uma extensão de marcação, as configurações de propriedade são separadas por vírgulas e nenhuma aspa aparece nas chaves.

Isso pode ser ilustrado com a extensão de Xamarin.Forms marcação chamada ConstraintExpression, que é usada com a RelativeLayout classe. Você pode especificar o local ou o tamanho de um modo de exibição filho como uma constante ou em relação a um modo de exibição pai ou outro modo de exibição nomeado. A sintaxe do ConstraintExpression permite definir a posição ou o tamanho de um modo de exibição usando uma Factor vez uma propriedade de outro modo de exibição, mais um Constantarquivo . Qualquer coisa mais complexa do que isso requer código.

Aqui está um exemplo:

<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>

Talvez a lição mais importante que você deve tirar deste exemplo é a sintaxe da extensão de marcação: Nenhuma aspa deve aparecer dentro das chaves de uma extensão de marcação. Ao digitar a extensão de marcação em um arquivo XAML, é natural querer colocar os valores das propriedades entre aspas. Resista à tentação!

Aqui está o programa em execução:

Layout relativo usando restrições

Resumo

As extensões de marcação XAML mostradas aqui fornecem suporte importante para arquivos XAML. Mas talvez a extensão de marcação XAML mais valiosa seja Binding, que é abordada na próxima parte desta série, Parte 4. Noções básicas de vinculação de dados.