Parte 2. Sintaxis XAML esencial

Download SampleDescargar el ejemplo

XAML está diseñado principalmente para crear instancias e inicializar objetos. Pero a menudo, las propiedades deben establecerse en objetos complejos que no se pueden representar fácilmente como cadenas XML y, a veces, las propiedades definidas por una clase deben establecerse en una clase secundaria. Estas dos necesidades requieren las características esenciales de sintaxis XAML de elementos de propiedad y propiedades adjuntas.

Elementos de propiedad

En XAML, las propiedades de las clases normalmente se establecen como atributos XML:

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

Pero hay una manera alternativa de establecer una propiedad en XAML. Para probar esta alternativa con TextColor, elimina primero la configuración de TextColor existente:

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

Abre la etiqueta Label de elemento vacío separándola en etiquetas iniciales y finales:

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

</Label>

Dentro de estas etiquetas, agrega etiquetas iniciales y finales que consten del nombre de clase y un nombre de propiedad separados por un punto:

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

    </Label.TextColor>
</Label>

Establece el valor de propiedad como contenido de estas nuevas etiquetas, de la siguiente manera:

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

Estas dos maneras de especificar la propiedad TextColor son funcionalmente equivalentes, pero no usan las dos maneras para la misma propiedad porque eso sería establecer la propiedad dos veces y podría llevar a confusión.

Con esta nueva sintaxis, se puede introducir cierta terminología práctica:

  • Label es un elemento de objeto. Es un objeto Xamarin.Forms expresado como un elemento XML.
  • Text, FontAttributes, VerticalOptions y FontSize son atributos de propiedad. Son propiedades Xamarin.Forms expresadas como atributos XML.
  • En ese fragmento final, TextColor se ha convertido en un elemento de propiedad. Es una propiedad Xamarin.Forms, pero ahora es un elemento XML.

La definición de elementos de propiedad puede parecer una infracción de la sintaxis XML al principio, pero no es así. El punto no tiene ningún significado especial en XML. Para un descodificador XML, Label.TextColor es simplemente un elemento secundario normal.

Pero en XAML esta sintaxis es muy especial. Una de las reglas para los elementos de propiedad es que no puede aparecer nada más en la etiqueta Label.TextColor. El valor de la propiedad siempre se define como contenido entre las etiquetas iniciales y finales del elemento de propiedad.

Puede usar la sintaxis del elemento de propiedad en más de una propiedad:

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

O bien puedes usar la sintaxis del elemento de propiedad para todas las propiedades:

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

En primer lugar, la sintaxis del elemento de propiedad podría parecer un reemplazo innecesario de larga duración para algo relativamente sencillo y, en estos ejemplos, esto es cierto.

Pero la sintaxis del elemento de propiedad es esencial cuando el valor de una propiedad es demasiado complejo para expresarse como una cadena simple. Dentro de las etiquetas de property-element, puedes crear una instancia de otro objeto y establecer sus propiedades. Por ejemplo, puedes establecer explícitamente una propiedad como VerticalOptions en un valor LayoutOptions con la configuración de propiedad:

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

Otro ejemplo: Grid tiene dos propiedades denominadas RowDefinitions y ColumnDefinitions. Estas dos propiedades son de tipo RowDefinitionCollection y ColumnDefinitionCollection, que son colecciones de objetos RowDefinition y ColumnDefinition. Debes usar la sintaxis del elemento de propiedad para establecer estas colecciones.

Este es el principio del archivo XAML de una clase GridDemoPage, en el que se muestran las etiquetas de elemento de propiedad para las colecciones RowDefinitions y 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>

Observa la sintaxis abreviada para definir celdas de tamaño automático, celdas de anchos y alturas de píxeles y valores de estrella.

Propiedades adjuntas

Acabas de ver que Grid requiere elementos de propiedad para que las colecciones RowDefinitions y ColumnDefinitions definan las filas y columnas. Pero también debe haber alguna manera para que el programador indique la fila y la columna donde reside cada elemento secundario de Grid.

En la etiqueta de cada elemento secundario de Grid, especifica la fila y columna de ese elemento secundario mediante los siguientes atributos:

  • Grid.Row
  • Grid.Column

Los valores predeterminados de estos atributos son 0. También puedes indicar si un elemento secundario abarca más de una fila o columna con estos atributos:

  • Grid.RowSpan
  • Grid.ColumnSpan

Estos dos atributos tienen valores predeterminados de 1.

Este es el archivo 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>

Los valores Grid.Row y Grid.Column de 0 no son obligatorios, pero generalmente se incluyen para una mayor claridad.

Este es su aspecto:

Grid Layout

A juzgar únicamente por la sintaxis, estos atributos Grid.Row, Grid.Column, Grid.RowSpan y Grid.ColumnSpan parecen ser campos o propiedades estáticos de Grid, pero curiosamente, Grid no define nada denominado Row, Column, RowSpan o ColumnSpan.

En su lugar, Grid define cuatro propiedades enlazables denominadas RowProperty, ColumnProperty, RowSpanProperty y ColumnSpanProperty. Estos son tipos especiales de propiedades enlazables conocidas como propiedades adjuntas. Las define la clase Grid, pero se establecen en los elementos secundarios de Grid.

Cuando quieras usar estas propiedades adjuntas en el código, la clase Grid proporciona métodos estáticos denominados SetRow, GetColumn, etc. Pero en XAML, estas propiedades adjuntas se establecen como atributos en los elementos secundarios de Grid mediante nombres de propiedades simples.

Las propiedades adjuntas siempre son reconocibles en archivos XAML como atributos que contienen una clase y un nombre de propiedad separados por un punto. Se denominan propiedades adjuntas porque están definidas por una clase (en este caso, Grid), pero adjuntas a otros objetos (en este caso, elementos secundarios de Grid). Durante el diseño, Grid puede consultar los valores de estas propiedades adjuntas para saber dónde colocar cada elemento secundario.

La clase AbsoluteLayout define dos propiedades adjuntas denominadas LayoutBounds y LayoutFlags. Este es un patrón de tablero de verificación realizado mediante el posicionamiento proporcional y las características de ajuste de tamaño 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>

Y aquí está:

Absolute Layout

Para algo parecido a esto, podría cuestionarse la conveniencia de usar XAML. Sin duda, la repetición y la regularidad del rectángulo LayoutBounds sugieren que podría realizarse mejor en el código.

Esto ciertamente supone una preocupación legítima y no hay ningún problema con equilibrar el uso de código y marcado al definir las interfaces de usuario. Es fácil definir algunos de los objetos visuales en XAML y, después, usar el constructor del archivo de código subyacente para agregar algunos objetos visuales más que podrían generarse mejor en bucles.

Propiedades de contenido

En los ejemplos anteriores, los objetos StackLayout, Grid y AbsoluteLayout se establecen en la propiedad Content de ContentPage y, de hecho, los elementos secundarios de estos diseños son elementos de la colección Children. Pero estas propiedades Content y Children no están en el archivo XAML.

Sin duda, puedes incluir las propiedades Content y Children como elementos de propiedad, como en el ejemplo 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>

La pregunta real es: ¿Por qué estos elementos de propiedad no son necesarios en el archivo XAML?

Los elementos definidos en Xamarin.Forms para su uso en XAML pueden tener una propiedad marcada en el atributo de la clase ContentProperty. Si buscas la clase ContentPage en la documentación de Xamarin.Forms en línea, verás este atributo:

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

Esto significa que no son necesarias las etiquetas Content de elemento de propiedad. Se supone que cualquier contenido XML que aparezca entre las etiquetas de ContentPage iniciales y finales se asigna a la propiedad Content.

StackLayout, Grid, AbsoluteLayout y RelativeLayout derivan de Layout<View> y, si buscas Layout<T> en la documentación de Xamarin.Forms, verás otro atributo ContentProperty:

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

Esto permite que el contenido del diseño se agregue automáticamente a la colección Children sin etiquetas Children explícitas de elemento de propiedad.

Otras clases también tienen definiciones del atributo ContentProperty. Por ejemplo, la propiedad de contenido de Label es Text. Consulte la documentación de la API para buscarlas.

Diferencias entre plataformas con OnPlatform

En las aplicaciones de página única, es habitual establecer la propiedad Padding en la página para evitar sobrescribir la barra de estado de iOS. En el código, puedes usar la propiedad Device.RuntimePlatform con esta finalidad:

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

También puedes hacer algo similar en XAML mediante las clases OnPlatform y On. En primer lugar, incluye elementos de propiedad para la propiedad Padding cerca de la parte superior de la 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>

En estas etiquetas, incluye una etiqueta OnPlatform. OnPlatform es una clase genérica. Debes especificar el argumento de tipo genérico, en este caso, Thickness, que es el tipo de propiedad Padding. Afortunadamente, hay un atributo XAML específicamente para definir argumentos genéricos, denominado x:TypeArguments. Esto debe coincidir con el tipo de la propiedad que estás estableciendo:

<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 tiene una propiedad denominada Platforms que es un elemento IList de objetos On. Usa etiquetas de elemento de propiedad para esa propiedad:

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

Ahora agrega elementos On. Para cada uno de ellos, establece las propiedades Platform y Value para marcar la propiedad Thickness:

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

Este marcado se puede simplificar. La propiedad de contenido de OnPlatform es Platforms, por lo que esas etiquetas de elemento de propiedad se pueden quitar:

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

La propiedad Platform de On es de tipo IList<string>, por lo que puedes incluir varias plataformas si los valores son los mismos:

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

Dado que Android y UWP están establecidos con el valor predeterminado de Padding, esa etiqueta se puede quitar:

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

Esta es la manera estándar de establecer una propiedad Padding dependiente de la plataforma en XAML. Si la configuración de Value no se puede representar mediante una sola cadena, puedes definir elementos de propiedad para este elemento:

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

Nota:

La extensión de marcado OnPlatform también se puede usar en XAML para personalizar la apariencia de la interfaz de usuario por plataforma. Ofrece la misma función que las clases OnPlatform y On, pero con una representación más concisa. Para obtener más información, consulta Extensión de marcado OnPlatform.

Resumen

Con elementos de propiedad y propiedades adjuntas, se ha establecido gran parte de la sintaxis XAML básica. Pero a veces es necesario establecer propiedades en objetos de forma indirecta, por ejemplo, desde un diccionario de recursos. Este enfoque se trata en la siguiente parte, Parte 3. Extensiones de marcado XAML.