Parte 2. Sintaxe essencial de XAML

O XAML foi projetado principalmente para instanciar e inicializar objetos. Mas, muitas vezes, as propriedades devem ser definidas como objetos complexos que não podem ser facilmente representados como cadeias de caracteres XML e, às vezes, as propriedades definidas por uma classe devem ser definidas em uma classe filha. Essas duas necessidades exigem os recursos essenciais de sintaxe XAML de elementos de propriedade e propriedades anexadas.

Elementos da propriedade

Em XAML, as propriedades das classes são normalmente definidas como atributos XML:

<Label Text="Hello, XAML!"
       VerticalOptions="Center"
       FontAttributes="Bold"
       FontSize="Large"
       TextColor="Aqua" />

No entanto, há uma maneira alternativa de definir uma propriedade em XAML. Para tentar essa alternativa com TextColoro , primeiro exclua a configuração existente TextColor :

<Label Text="Hello, XAML!"
       VerticalOptions="Center"
       FontAttributes="Bold"
       FontSize="Large" />

Abra a tag empty-element Label separando-a em tags start e end:

<Label Text="Hello, XAML!"
       VerticalOptions="Center"
       FontAttributes="Bold"
       FontSize="Large">

</Label>

Dentro dessas tags, adicione marcas de início e fim que consistem no nome da classe e um nome de propriedade separados por um ponto:

<Label Text="Hello, XAML!"
       VerticalOptions="Center"
       FontAttributes="Bold"
       FontSize="Large">
    <Label.TextColor>

    </Label.TextColor>
</Label>

Defina o valor da propriedade como conteúdo dessas novas tags, da seguinte forma:

<Label Text="Hello, XAML!"
       VerticalOptions="Center"
       FontAttributes="Bold"
       FontSize="Large">
    <Label.TextColor>
        Aqua
    </Label.TextColor>
</Label>

Essas duas maneiras de especificar a TextColor propriedade são funcionalmente equivalentes, mas não use as duas maneiras para a mesma propriedade porque isso seria efetivamente definir a propriedade duas vezes e pode ser ambíguo.

Com esta nova sintaxe, algumas terminologias úteis podem ser introduzidas:

  • Labelé um elemento de objeto. É um Xamarin.Forms objeto expresso como um elemento XML.
  • TextFontAttributes, VerticalOptionse FontSize são atributos de propriedade. Xamarin.Forms São propriedades expressas como atributos XML.
  • Nesse trecho final, TextColor tornou-se um elemento de propriedade. É uma Xamarin.Forms propriedade, mas agora é um elemento XML.

A definição de elementos de propriedade pode, a princípio, parecer uma violação da sintaxe XML, mas não é. O período não tem nenhum significado especial em XML. Para um decodificador XML, Label.TextColor é simplesmente um elemento filho normal.

Em XAML, no entanto, essa sintaxe é muito especial. Uma das regras para elementos de propriedade é que nada mais pode aparecer na Label.TextColor marca. O valor da propriedade é sempre definido como conteúdo entre as marcas de início e fim do elemento de propriedade.

Você pode usar a sintaxe de elemento de propriedade em mais de uma propriedade:

<Label Text="Hello, XAML!"
       VerticalOptions="Center">
    <Label.FontAttributes>
        Bold
    </Label.FontAttributes>
    <Label.FontSize>
        Large
    </Label.FontSize>
    <Label.TextColor>
        Aqua
    </Label.TextColor>
</Label>

Ou você pode usar a sintaxe do elemento de propriedade para todas as propriedades:

<Label>
    <Label.Text>
        Hello, XAML!
    </Label.Text>
    <Label.FontAttributes>
        Bold
    </Label.FontAttributes>
    <Label.FontSize>
        Large
    </Label.FontSize>
    <Label.TextColor>
        Aqua
    </Label.TextColor>
    <Label.VerticalOptions>
        Center
    </Label.VerticalOptions>
</Label>

A princípio, a sintaxe do elemento de propriedade pode parecer uma substituição desnecessária para algo comparativamente bastante simples, e nesses exemplos esse é certamente o caso.

No entanto, a sintaxe do elemento de propriedade torna-se essencial quando o valor de uma propriedade é muito complexo para ser expresso como uma cadeia de caracteres simples. Dentro das tags property-element, você pode instanciar outro objeto e definir suas propriedades. Por exemplo, você pode definir explicitamente uma propriedade, como VerticalOptions um valor com configurações de LayoutOptions propriedade:

<Label>
    ...
    <Label.VerticalOptions>
        <LayoutOptions Alignment="Center" />
    </Label.VerticalOptions>
</Label>

Outro exemplo: O Grid tem duas propriedades nomeadas RowDefinitions e ColumnDefinitions. Essas duas propriedades são do tipo RowDefinitionCollection e ColumnDefinitionCollection, que são coleções de RowDefinition e ColumnDefinition objetos. Você precisa usar a sintaxe do elemento de propriedade para definir essas coleções.

Aqui está o início do arquivo XAML de uma GridDemoPage classe, mostrando as marcas de elemento de propriedade para as RowDefinitions coleções e ColumnDefinitions :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.GridDemoPage"
             Title="Grid Demo Page">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="100" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="100" />
        </Grid.ColumnDefinitions>
        ...
    </Grid>
</ContentPage>

Observe a sintaxe abreviada para definir células de tamanho automático, células de larguras e alturas de pixels e configurações de estrelas.

Propriedades Anexadas

Você acabou de ver que o Grid requer elementos de propriedade para as RowDefinitions coleções e ColumnDefinitions para definir as linhas e colunas. No entanto, também deve haver alguma maneira de o programador indicar a linha e a coluna onde cada filho do Grid reside reside.

Dentro da marca para cada filho do Grid você especifica a linha e a coluna desse filho usando os seguintes atributos:

  • Grid.Row
  • Grid.Column

Os valores padrão desses atributos são 0. Você também pode indicar se um filho abrange mais de uma linha ou coluna com estes atributos:

  • Grid.RowSpan
  • Grid.ColumnSpan

Esses dois atributos têm valores padrão de 1.

Aqui está o arquivo GridDemoPage.xaml completo:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.GridDemoPage"
             Title="Grid Demo Page">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="100" />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="100" />
        </Grid.ColumnDefinitions>

        <Label Text="Autosized cell"
               Grid.Row="0" Grid.Column="0"
               TextColor="White"
               BackgroundColor="Blue" />

        <BoxView Color="Silver"
                 HeightRequest="0"
                 Grid.Row="0" Grid.Column="1" />

        <BoxView Color="Teal"
                 Grid.Row="1" Grid.Column="0" />

        <Label Text="Leftover space"
               Grid.Row="1" Grid.Column="1"
               TextColor="Purple"
               BackgroundColor="Aqua"
               HorizontalTextAlignment="Center"
               VerticalTextAlignment="Center" />

        <Label Text="Span two rows (or more if you want)"
               Grid.Row="0" Grid.Column="2" Grid.RowSpan="2"
               TextColor="Yellow"
               BackgroundColor="Blue"
               HorizontalTextAlignment="Center"
               VerticalTextAlignment="Center" />

        <Label Text="Span two columns"
               Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
               TextColor="Blue"
               BackgroundColor="Yellow"
               HorizontalTextAlignment="Center"
               VerticalTextAlignment="Center" />

        <Label Text="Fixed 100x100"
               Grid.Row="2" Grid.Column="2"
               TextColor="Aqua"
               BackgroundColor="Red"
               HorizontalTextAlignment="Center"
               VerticalTextAlignment="Center" />

    </Grid>
</ContentPage>

As Grid.Row configurações e Grid.Column de 0 não são necessárias, mas geralmente são incluídas para fins de clareza.

Veja como fica:

Layout de grade

A julgar apenas pela sintaxe, esses Grid.Rowatributos , Grid.Column, Grid.RowSpan, e Grid.ColumnSpan parecem ser campos estáticos ou propriedades de Grid, mas, curiosamente, Grid não define nada chamado Row, Column, RowSpan, ou ColumnSpan.

Em vez disso, Grid define quatro propriedades vinculáveis chamadas RowProperty, ColumnProperty, RowSpanPropertye ColumnSpanProperty. Esses são tipos especiais de propriedades vinculáveis conhecidas como propriedades anexadas. Eles são definidos pela classe, Grid mas definidos em crianças do Grid.

Quando você deseja usar essas propriedades anexadas no código, a Grid classe fornece métodos estáticos chamados SetRow, GetColumne assim por diante. Mas em XAML, essas propriedades anexadas são definidas como atributos nos filhos dos nomes de Grid propriedades simples.

As propriedades anexadas são sempre reconhecíveis em arquivos XAML como atributos que contêm uma classe e um nome de propriedade separados por um ponto. Eles são chamados de propriedades anexadas porque são definidas por uma classe (neste caso, Grid) mas anexadas a outros objetos (neste caso, filhos do Grid). Durante o layout, o Grid pode interrogar os valores dessas propriedades anexadas para saber onde colocar cada filho.

A AbsoluteLayout classe define duas propriedades anexadas chamadas LayoutBounds e LayoutFlags. Aqui está um padrão quadriculado realizado usando os recursos de posicionamento e dimensionamento proporcionais de AbsoluteLayout:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.AbsoluteDemoPage"
             Title="Absolute Demo Page">

    <AbsoluteLayout BackgroundColor="#FF8080">
        <BoxView Color="#8080FF"
                 AbsoluteLayout.LayoutBounds="0.33, 0, 0.25, 0.25"
                 AbsoluteLayout.LayoutFlags="All" />

        <BoxView Color="#8080FF"
                 AbsoluteLayout.LayoutBounds="1, 0, 0.25, 0.25"
                 AbsoluteLayout.LayoutFlags="All" />

        <BoxView Color="#8080FF"
                 AbsoluteLayout.LayoutBounds="0, 0.33, 0.25, 0.25"
                 AbsoluteLayout.LayoutFlags="All" />

        <BoxView Color="#8080FF"
                 AbsoluteLayout.LayoutBounds="0.67, 0.33, 0.25, 0.25"
                 AbsoluteLayout.LayoutFlags="All" />

        <BoxView Color="#8080FF"
                 AbsoluteLayout.LayoutBounds="0.33, 0.67, 0.25, 0.25"
                 AbsoluteLayout.LayoutFlags="All" />

        <BoxView Color="#8080FF"
                 AbsoluteLayout.LayoutBounds="1, 0.67, 0.25, 0.25"
                 AbsoluteLayout.LayoutFlags="All" />

        <BoxView Color="#8080FF"
                 AbsoluteLayout.LayoutBounds="0, 1, 0.25, 0.25"
                 AbsoluteLayout.LayoutFlags="All" />

        <BoxView Color="#8080FF"
                 AbsoluteLayout.LayoutBounds="0.67, 1, 0.25, 0.25"
                 AbsoluteLayout.LayoutFlags="All" />

  </AbsoluteLayout>
</ContentPage>

Aqui está:

Layout Absoluto

Para algo assim, você pode questionar a sabedoria de usar XAML. Certamente, a repetição e regularidade LayoutBounds do retângulo sugere que ele pode ser melhor realizado em código.

Essa é certamente uma preocupação legítima, e não há nenhum problema em equilibrar o uso de código e marcação ao definir suas interfaces de usuário. É fácil definir alguns dos elementos visuais em XAML e, em seguida, usar o construtor do arquivo code-behind para adicionar mais alguns elementos visuais que podem ser melhor gerados em loops.

Propriedades de Conteúdo

Nos exemplos anteriores, os objetos , e são StackLayoutdefinidos como a Content propriedade do ContentPage, e os filhos desses layouts são, na verdade, itens na Children coleçãoAbsoluteLayout. Grid No entanto, essas Content e Children propriedades não estão em nenhum lugar no arquivo XAML.

Você certamente pode incluir as Content propriedades e Children como elementos de propriedade, como no exemplo XamlPlusCode :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.XamlPlusCodePage"
             Title="XAML + Code Page">
    <ContentPage.Content>
        <StackLayout>
            <StackLayout.Children>
                <Slider VerticalOptions="CenterAndExpand"
                        ValueChanged="OnSliderValueChanged" />

                <Label x:Name="valueLabel"
                       Text="A simple Label"
                       FontSize="Large"
                       HorizontalOptions="Center"
                       VerticalOptions="CenterAndExpand" />

                <Button Text="Click Me!"
                      HorizontalOptions="Center"
                      VerticalOptions="CenterAndExpand"
                      Clicked="OnButtonClicked" />
            </StackLayout.Children>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

A verdadeira questão é: por que esses elementos de propriedade não são necessários no arquivo XAML?

Os elementos definidos para Xamarin.Forms uso em XAML têm permissão para ter uma propriedade sinalizada ContentProperty no atributo na classe. Se você procurar a ContentPage classe na documentação on-line Xamarin.Forms , verá este atributo:

[Xamarin.Forms.ContentProperty("Content")]
public class ContentPage : TemplatedPage

Isso significa que as Content tags property-element não são necessárias. Presume-se que qualquer conteúdo XML que apareça entre as marcas de início e fim ContentPage seja atribuído à Content propriedade.

StackLayout, Grid, AbsoluteLayoute RelativeLayout todos derivam de Layout<View>, e se você procurar Layout<T> na Xamarin.Forms documentação, verá outro ContentProperty atributo:

[Xamarin.Forms.ContentProperty("Children")]
public abstract class Layout<T> : Layout ...

Isso permite que o conteúdo do layout seja adicionado automaticamente à Children coleção sem marcas explícitas Children de elemento de propriedade.

Outras classes também têm ContentProperty definições de atributo. Por exemplo, a propriedade content de Label é Text. Verifique a documentação da API para outras pessoas.

Diferenças de plataforma com o OnPlatform

Em aplicativos de página única, é comum definir a Padding propriedade na página para evitar substituir a barra de status do iOS. No código, você pode usar a Device.RuntimePlatform propriedade para esta finalidade:

if (Device.RuntimePlatform == Device.iOS)
{
    Padding = new Thickness(0, 20, 0, 0);
}

Você também pode fazer algo semelhante em XAML usando as OnPlatform classes e On . Primeiro, inclua elementos de propriedade para a Padding propriedade próxima à parte superior da página:

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

    <ContentPage.Padding>

    </ContentPage.Padding>
    ...
</ContentPage>

Dentro dessas tags, inclua uma OnPlatform tag . OnPlatform é uma classe genérica. Você precisa especificar o argumento de tipo genérico, neste caso, Thickness, que é o tipo de Padding propriedade. Felizmente, há um atributo XAML especificamente para definir argumentos genéricos chamado x:TypeArguments. Isso deve corresponder ao tipo de propriedade que você está definindo:

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

    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness">

        </OnPlatform>
    </ContentPage.Padding>
  ...
</ContentPage>

OnPlatform tem uma propriedade chamada Platforms que é um IList de On objetos. Use marcas de elemento de propriedade para essa propriedade:

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

    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness">
            <OnPlatform.Platforms>

            </OnPlatform.Platforms>
        </OnPlatform>
    </ContentPage.Padding>
  ...
</ContentPage>

Agora adicione On elementos. Para cada um defina a Platform propriedade e a Value propriedade como marcação para a Thickness propriedade:

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

    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness">
            <OnPlatform.Platforms>
                <On Platform="iOS" Value="0, 20, 0, 0" />
                <On Platform="Android" Value="0, 0, 0, 0" />
                <On Platform="UWP" Value="0, 0, 0, 0" />
            </OnPlatform.Platforms>
        </OnPlatform>
    </ContentPage.Padding>
  ...
</ContentPage>

Essa marcação pode ser simplificada. A propriedade content de é Platforms, portanto, essas tags de OnPlatform elemento de propriedade podem ser removidas:

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

    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness">
            <On Platform="iOS" Value="0, 20, 0, 0" />
            <On Platform="Android" Value="0, 0, 0, 0" />
            <On Platform="UWP" Value="0, 0, 0, 0" />
        </OnPlatform>
    </ContentPage.Padding>
  ...
</ContentPage>

A Platform propriedade de On é do tipo IList<string>, portanto, você pode incluir várias plataformas se os valores forem os mesmos:

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

    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness">
            <On Platform="iOS" Value="0, 20, 0, 0" />
            <On Platform="Android, UWP" Value="0, 0, 0, 0" />
        </OnPlatform>
    </ContentPage.Padding>
  ...
</ContentPage>

Como o Android e a UWP estão definidos com o valor padrão do Padding, essa marca pode ser removida:

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

    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness">
            <On Platform="iOS" Value="0, 20, 0, 0" />
        </OnPlatform>
    </ContentPage.Padding>
  ...
</ContentPage>

Essa é a maneira padrão de definir uma propriedade dependente Padding da plataforma em XAML. Se a Value configuração não puder ser representada por uma única cadeia de caracteres, você poderá definir elementos de propriedade para ela:

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

    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness">
            <On Platform="iOS">
                <On.Value>
                    0, 20, 0, 0
                </On.Value>
            </On>
        </OnPlatform>
    </ContentPage.Padding>
  ...
</ContentPage>

Observação

A OnPlatform extensão de marcação também pode ser usada em XAML para personalizar a aparência da interface do usuário por plataforma. Ele fornece a mesma funcionalidade que as OnPlatform classes e On , mas com uma representação mais concisa. Para obter mais informações, consulte Extensão de marcação OnPlatform.

Resumo

Com elementos de propriedade e propriedades anexadas, grande parte da sintaxe XAML básica foi estabelecida. No entanto, às vezes você precisa definir propriedades para objetos de maneira indireta, por exemplo, a partir de um dicionário de recursos. Essa abordagem é abordada na próxima parte, Parte 3. Extensões de marcação XAML.