Extensiones de marcado XAML

Browse sample.Examina la muestra

Las extensiones de marcado XAML de la interfaz de usuario de aplicaciones multiplataforma de .NET (.NET MAUI) permiten establecer propiedades en objetos o valores a los que se hace referencia indirectamente desde otros orígenes. Las extensiones de marcado XAML son especialmente importantes para compartir objetos y hacer referencia a constantes usadas en toda una aplicación, pero encuentran su mayor utilidad en los enlaces de datos.

Normalmente, XAML sirve para establecer propiedades de un objeto en valores explícitos, como una cadena, un número, un miembro de enumeración o una cadena que se convierte en un valor en segundo plano. A veces, sin embargo, las propiedades deben hacer referencia a valores definidos en otro lugar o que podrían requerir un poco de procesamiento por parte del código en tiempo de ejecución. Para estos fines, existen extensiones de marcado XAML.

Las extensiones de marcado XAML se denominan así porque están basadas en código en clases que implementan IMarkupExtension. También es posible escribir tus propias extensiones de marcado personalizadas.

En muchos casos, las extensiones de marcado XAML se reconocen instantáneamente en los archivos XAML porque aparecen como valores de atributo delimitados por llaves, { y }, pero a veces las extensiones de marcado también aparecen en el marcado como elementos convencionales.

Importante

Las extensiones de marcado pueden tener propiedades, pero no se establecen como atributos XML. En una extensión de marcado, los valores de propiedad están separados por comas y no aparecen comillas dentro de las llaves.

Recursos compartidos

Algunas páginas XAML contienen varias vistas con propiedades establecidas en los mismos valores. Por ejemplo, gran parte de la configuración de las propiedades de estos objetos Button es la misma:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             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="Center"
                BorderWidth="3"
                Rotation="-15"
                TextColor="Red"
                FontSize="24" />
        <Button Text="Do that!"
                HorizontalOptions="Center"
                VerticalOptions="Center"
                BorderWidth="3"
                Rotation="-15"
                TextColor="Red"
                FontSize="24" />
        <Button Text="Do the other thing!"
                HorizontalOptions="Center"
                VerticalOptions="Center"
                BorderWidth="3"
                Rotation="-15"
                TextColor="Red"
                FontSize="24" />
    </StackLayout>
</ContentPage>

Si es necesario cambiar una de estas propiedades, puede que prefieras hacer el cambio una sola vez en lugar de tres veces. Si fuera código, es probable que estuvieras usando constantes y objetos estáticos de solo lectura para ayudar a mantener estos valores coherentes y fáciles de modificar.

En XAML, una solución popular es almacenar estos valores u objetos en un diccionario de recursos. La clase VisualElement define una propiedad llamada Resources de tipo ResourceDictionary, que es un diccionario con claves de tipo string y valores de tipo object. Puedes colocar objetos en este diccionario y, después, hacer referencia a ellos desde el marcado, todo en XAML.

Para usar un diccionario de recursos en una página, incluye un par de etiquetas del elemento de propiedad Resources en la parte superior de la página y agrega recursos dentro de estas etiquetas. Los objetos y valores de varios tipos se pueden agregar al diccionario de recursos. Estos tipos deben poder instanciarse. No pueden ser clases abstractas, por ejemplo. Estos tipos también deben tener un constructor sin parámetros público. Cada elemento requiere una clave de diccionario especificada con el atributo x:Key:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.SharedResourcesPage"
             Title="Shared Resources Page">
    <ContentPage.Resources>
        <LayoutOptions x:Key="horzOptions"
                       Alignment="Center" />
        <LayoutOptions x:Key="vertOptions"
                       Alignment="Center" />
    </ContentPage.Resources>
    ...
</ContentPage>

En este ejemplo, los dos recursos son valores del tipo de estructura LayoutOptions, y cada uno tiene una clave única y una o dos propiedades establecidas. En el código y el marcado, es mucho más común usar los campos estáticos de LayoutOptions, pero aquí es más cómodo establecer las propiedades.

Nota:

Las etiquetas opcionales ResourceDictionary se pueden incluir como elemento secundario de las etiquetas Resources.

Los objetos Button pueden entonces consumir los recursos con la extensión de marcado XML StaticResource para establecer sus propiedades HorizontalOptions y VerticalOptions:

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

La extensión de marcado StaticResource siempre está delimitada con llaves e incluye la clave de diccionario. El nombre StaticResource lo distingue de DynamicResource, que .NET MAUI también admite. DynamicResource es para las claves de diccionario asociadas a valores que pueden cambiar en tiempo de ejecución, mientras que StaticResource accede a los elementos del diccionario solo una vez cuando se construyen los elementos de la página. Cada vez que el analizador XAML encuentra una extensión de marcado StaticResource, busca en el árbol visual y usa el primer objeto ResourceDictionary que encuentra que contenga esa clave.

Es necesario almacenar dobles en el diccionario para las propiedades BorderWidth, Rotation y FontSize. XAML define cómodamente etiquetas para tipos de datos comunes como x:Double y x:Int32:

<ContentPage.Resources>
        <LayoutOptions x:Key="horzOptions"
                       Alignment="Center" />
        <LayoutOptions x:Key="vertOptions"
                       Alignment="Center" />
        <x:Double x:Key="borderWidth">3</x:Double>
        <x:Double x:Key="rotationAngle">-15</x:Double>
        <x:Double x:Key="fontSize">24</x:Double>        
</ContentPage.Resources>

Puedes hacer referencia a estos tres recursos adicionales de la misma manera que los valores LayoutOptions:

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

En el caso de los recursos de tipo Color, puedes usar las mismas representaciones de cadena que se usan al asignar directamente atributos de estos tipos. Los convertidores de tipos incluidos en .NET MAUI se invocan cuando se crea el recurso. También es posible usar la clase OnPlatform dentro del diccionario de recursos para definir valores diferentes para las plataformas. En el ejemplo siguiente se usa esta clase para establecer diferentes colores de texto:

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

El recurso OnPlatform obtiene un atributo x:Key porque es un objeto del diccionario, y un atributo x:TypeArguments porque es una clase genérica. Los atributos iOS y Android se convierten en valores Color cuando se inicializa el objeto.

En el ejemplo siguiente se muestran los tres botones que acceden a seis valores compartidos:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.SharedResourcesPage"
             Title="Shared Resources Page">
    <ContentPage.Resources>
        <LayoutOptions x:Key="horzOptions"
                       Alignment="Center" />
        <LayoutOptions x:Key="vertOptions"
                       Alignment="Center" />
        <x:Double x:Key="borderWidth">3</x:Double>
        <x:Double x:Key="rotationAngle">-15</x:Double>
        <x:Double x:Key="fontSize">24</x:Double>    
        <OnPlatform x:Key="textColor"
                    x:TypeArguments="Color">
            <On Platform="iOS" Value="Red" />
            <On Platform="Android" Value="Aqua" />
            <On Platform="WinUI" Value="#80FF80" />
        </OnPlatform>
    </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>

En la captura de pantalla siguiente se comprueba el estilo coherente:

Screenshot of styled controls.

Aunque es habitual definir la colección Resources en la parte superior de la página, puedes tener colecciones Resources en otros elementos de la página. Por ejemplo, en el ejemplo siguiente se muestran recursos agregados a un StackLayout:

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

Uno de los tipos más comunes de objetos almacenados en diccionarios de recursos es Style de .NET MAUI, que define una colección de configuraciones de propiedades. Para obtener más información sobre los estilos, consulta Aplicaciones de estilo con XAML.

Nota:

El propósito de un diccionario de recursos es compartir objetos. Por lo tanto, no tiene sentido poner controles como Label o Button en un diccionario de recursos. Los elementos visuales no se pueden compartir porque la misma instancia no puede aparecer dos veces en una página.

Extensiones de marcado x:Static

Además de la extensión de marcado StaticResource, también existe la extensión de marcado x:Static. Sin embargo, mientras que StaticResource devuelve un objeto de un diccionario de recursos, x:Static accede a un campo estático público, a una propiedad estática pública, a un campo constante público o a un miembro de una enumeración.

Nota:

La extensión de marcado StaticResource es compatible con las implementaciones de XAML que definen un diccionario de recursos, mientras que x:Static es una parte intrínseca de XAML, como muestra el prefijo x.

El siguiente ejemplo demuestra cómo x:Static puede hacer referencia explícita a campos estáticos y miembros de enumeración:

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

El principal uso de la extensión de marcado x:Static es para hacer referencia a campos estáticos o propiedades desde tu propio código. Por ejemplo, aquí tienes una clase AppConstants que contiene algunos campos estáticos que podrías usar en varias páginas de una aplicación:

namespace XamlSamples
{
    static class AppConstants
    {
        public static readonly Color BackgroundColor = Colors.Aqua;
        public static readonly Color ForegroundColor = Colors.Brown;
    }
}

Para hacer referencia a los campos estáticos de esta clase en un archivo XAML, debes usar una declaración de espacio de nombres XML para indicar dónde se encuentra este archivo. Cada declaración de espacio de nombres XML adicional define un nuevo prefijo. Para acceder a las clases locales al espacio de nombres de la aplicación raíz, como AppConstants, puedes usar el prefijo local. La declaración de espacio de nombres debe indicar el nombre del espacio de nombres CLR (Common Language Runtime), también conocido como nombre de espacio de nombres .NET, que es el nombre que aparece en una definición de C# namespace o en una directiva using:

xmlns:local="clr-namespace:XamlSamples"

También puedes definir declaraciones de espacio de nombres XML para espacios de nombres .NET. Por ejemplo, aquí tienes un prefijo sys para el espacio de nombres estándar System de .NET, que está en el ensamblado netstandard. Dado que se trata de otro ensamblado, también debes especificar el nombre del ensamblado, en este caso netstandard:

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

Nota:

La palabra clave clr-namespace va seguida de dos puntos y después el nombre del espacio de nombres de .NET, seguido de un punto y coma, la palabra clave assembly, un signo igual y el nombre del ensamblado.

Los campos estáticos se pueden consumir después de declarar el espacio de nombres XML:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             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="5,25,5,0">
    <StackLayout>
       <Label Text="Hello, XAML!"
              TextColor="{x:Static local:AppConstants.BackgroundColor}"
              BackgroundColor="{x:Static local:AppConstants.ForegroundColor}"
              FontAttributes="Bold"
              FontSize="30"
              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>

En este ejemplo, las dimensiones BoxView se establecen en Math.PI y Math.E, pero escaladas por un factor de 100:

Screenshot of controls using the x:Static markup extension.

Otras extensiones de marcado

Varias extensiones de marcado son intrínsecas de XAML y se admiten en XAML de .NET MAUI. Algunas no se usan con mucha frecuencia, pero son esenciales cuando las necesitas:

  • Si una propiedad tiene un valor no null de forma predeterminada, pero quieres establecerlo en null, establécelo en la extensión de marcado {x:Null}.
  • Si una propiedad es de tipo Type, puedes asignarla a un objeto Type con la extensión de marcado {x:Type someClass}.
  • Puede definir matrices en XAML usando la extensión de marcado x:Array. Esta extensión de marcado tiene un atributo obligatorio denominado Type que indica el tipo de los elementos de la matriz.

Para obtener más información sobre las extensiones de marcado XAML, consulta Consumir extensiones de marcado XAML.

Pasos siguientes

Los enlaces de datos de .NET MAUI permiten enlazar propiedades de dos objetos para que un cambio en uno provoque un cambio en el otro.