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 IMarkupExtension
o . 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 LayoutOptions
de 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 iOS
atributos , Android
e 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:
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 ResourceDictionary
arquivo . 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 assembly
o 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:
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 comonull
, defina-a como a{x:Null}
extensão de marcação. - Se uma propriedade for do tipo
Type
, você poderá atribuí-la a umType
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 chamadoType
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 Constant
arquivo . 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:
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.