Compartir a través de


Creación de extensiones de marcado XAML

En el nivel de programación, una extensión de marcado XAML es una clase que implementa la interfaz IMarkupExtension o IMarkupExtension<T>. Puede explorar el código fuente de las extensiones de marcado estándar que se describen a continuación en el directorio MarkupExtensions del repositorio de GitHub Xamarin.Forms.

También es posible definir tus propias extensiones de marcado XAML personalizadas derivadas de IMarkupExtension o IMarkupExtension<T>. Usa el formulario genérico si la extensión de marcado obtiene el valor de un tipo determinado. Este es el caso de varias de las extensiones de marcado Xamarin.Forms:

  • TypeExtension deriva de IMarkupExtension<Type>.
  • ArrayExtension deriva de IMarkupExtension<Array>.
  • DynamicResourceExtension deriva de IMarkupExtension<DynamicResource>.
  • BindingExtension deriva de IMarkupExtension<BindingBase>.
  • ConstraintExpression deriva de IMarkupExtension<Constraint>.

Las dos interfaces IMarkupExtension definen sólo un método cada una, denominado ProvideValue:

public interface IMarkupExtension
{
    object ProvideValue(IServiceProvider serviceProvider);
}

public interface IMarkupExtension<out T> : IMarkupExtension
{
    new T ProvideValue(IServiceProvider serviceProvider);
}

Dado que IMarkupExtension<T> deriva de IMarkupExtension e incluye la palabra clave new en ProvideValue, contiene ambos métodos ProvideValue.

Con mucha frecuencia, las extensiones de marcado XAML definen propiedades que contribuyen al valor devuelto. (La excepción obvia es NullExtension, en la que ProvideValue simplemente devuelve null). El método ProvideValue tiene un único argumento de tipo IServiceProvider que se analizará más adelante en este artículo.

Una extensión de marcado para especificar color

La siguiente extensión de marcado XAML permite construir un valor Color mediante componentes de tono, saturación y luminosidad. Define cuatro propiedades para los cuatro componentes del color, incluido un componente alfa que se inicializa en 1. La clase deriva de IMarkupExtension<Color> para indicar un valor devuelto Color:

public class HslColorExtension : IMarkupExtension<Color>
{
    public double H { set; get; }

    public double S { set; get; }

    public double L { set; get; }

    public double A { set; get; } = 1.0;

    public Color ProvideValue(IServiceProvider serviceProvider)
    {
        return Color.FromHsla(H, S, L, A);
    }

    object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
    {
        return (this as IMarkupExtension<Color>).ProvideValue(serviceProvider);
    }
}

Dado que IMarkupExtension<T> deriva de IMarkupExtension, la clase debe contener dos métodos ProvideValue, uno que devuelve Color y otro que devuelve object, pero el segundo método simplemente puede llamar al primer método.

La página Demostración de color de HSL muestra una variedad de formas en las que HslColorExtension puede aparecer en un archivo XAML para especificar el color de BoxView:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:MarkupExtensions"
             x:Class="MarkupExtensions.HslColorDemoPage"
             Title="HSL Color Demo">

    <ContentPage.Resources>
        <ResourceDictionary>
            <Style TargetType="BoxView">
                <Setter Property="WidthRequest" Value="80" />
                <Setter Property="HeightRequest" Value="80" />
                <Setter Property="HorizontalOptions" Value="Center" />
                <Setter Property="VerticalOptions" Value="CenterAndExpand" />
            </Style>
        </ResourceDictionary>
    </ContentPage.Resources>

    <StackLayout>
        <BoxView>
            <BoxView.Color>
                <local:HslColorExtension H="0" S="1" L="0.5" A="1" />
            </BoxView.Color>
        </BoxView>

        <BoxView>
            <BoxView.Color>
                <local:HslColor H="0.33" S="1" L="0.5" />
            </BoxView.Color>
        </BoxView>

        <BoxView Color="{local:HslColorExtension H=0.67, S=1, L=0.5}" />

        <BoxView Color="{local:HslColor H=0, S=0, L=0.5}" />

        <BoxView Color="{local:HslColor A=0.5}" />
    </StackLayout>
</ContentPage>

Observe que cuando HslColorExtension es una etiqueta XML, las cuatro propiedades se establecen como atributos, pero cuando aparece entre llaves, las cuatro propiedades se separan por comas sin comillas. Los valores predeterminados de H, S y L son 0, y el valor predeterminado de A es 1, por lo que esas propiedades pueden omitirse si deseas que tengan valores predeterminados. En el último ejemplo se muestra un ejemplo en el que la luminosidad es 0, que normalmente da como resultado negro, pero el canal alfa es 0,5, por lo que es medio transparente y aparece gris en el fondo blanco de la página:

Demostración de color de HSL

Una extensión de marcado para acceder a mapas de bits

El argumento para ProvideValue es un objeto que implementa la interfaz IServiceProvider, que se define en el espacio de nombres de .NET System. Esta interfaz tiene un miembro, un método denominado GetService con un argumento Type.

La clase ImageResourceExtension que se muestra a continuación muestra un posible uso de IServiceProvider y GetService para obtener un objeto IXmlLineInfoProvider que puede proporcionar información de líneas y caracteres que indique dónde se detectó un error determinado. En este caso, se produce una excepción cuando no se ha establecido la propiedad Source:

[ContentProperty("Source")]
class ImageResourceExtension : IMarkupExtension<ImageSource>
{
    public string Source { set; get; }

    public ImageSource ProvideValue(IServiceProvider serviceProvider)
    {
        if (String.IsNullOrEmpty(Source))
        {
            IXmlLineInfoProvider lineInfoProvider = serviceProvider.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider;
            IXmlLineInfo lineInfo = (lineInfoProvider != null) ? lineInfoProvider.XmlLineInfo : new XmlLineInfo();
            throw new XamlParseException("ImageResourceExtension requires Source property to be set", lineInfo);
        }

        string assemblyName = GetType().GetTypeInfo().Assembly.GetName().Name;
        return ImageSource.FromResource(assemblyName + "." + Source, typeof(ImageResourceExtension).GetTypeInfo().Assembly);
    }

    object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
    {
        return (this as IMarkupExtension<ImageSource>).ProvideValue(serviceProvider);
    }
}

ImageResourceExtension resulta útil cuando un archivo XAML necesita tener acceso a un archivo de imagen almacenado como un recurso incrustado en el proyecto de biblioteca de .NET Standard. Usa la propiedad Source para llamar al método estático ImageSource.FromResource. Este método requiere un nombre de recurso completo, que consta del nombre del ensamblado, el nombre de la carpeta y el nombre de archivo separados por puntos. El segundo argumento para el método ImageSource.FromResource proporciona el nombre del ensamblado y solo es necesario para las compilaciones de versión en UWP. Independientemente, se debe llamar a ImageSource.FromResource desde el ensamblado que contiene el mapa de bits, lo que significa que esta extensión de recursos XAML no puede formar parte de una biblioteca externa a menos que las imágenes también estén en esa biblioteca. (Consulte el artículo Imágenes insertadas para obtener más información sobre el acceso a mapas de bits almacenados como recursos incrustados).

Aunque ImageResourceExtension requiere que se establezca la propiedad Source, la propiedad Source se indica en un atributo como la propiedad de contenido de la clase. Esto significa que se puede omitir la parte Source= de la expresión en llaves. En la página Demostración de recursos de imagen, los elementos Image capturan dos imágenes con el nombre de carpeta y el nombre de archivo separados por puntos:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:MarkupExtensions"
             x:Class="MarkupExtensions.ImageResourceDemoPage"
             Title="Image Resource Demo">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <Image Source="{local:ImageResource Images.SeatedMonkey.jpg}"
               Grid.Row="0" />

        <Image Source="{local:ImageResource Images.FacePalm.jpg}"
               Grid.Row="1" />

    </Grid>
</ContentPage>

Esta es la ejecución del programa:

Demostración de recursos de imagen

Proveedores de servicios

Mediante el uso del argumento IServiceProvider para ProvideValue, las extensiones de marcado XAML pueden obtener acceso a información útil sobre el archivo XAML en el que se usan. Pero para usar correctamente el argumento IServiceProvider, debe saber qué tipo de servicios están disponibles en contextos concretos. La mejor manera de comprender esta característica es estudiar el código fuente de las extensiones de marcado XAML existentes en la carpeta MarkupExtensions en el repositorio de Xamarin.Forms en GitHub. Tenga en cuenta que algunos tipos de servicios son internos de Xamarin.Forms.

En algunas extensiones de marcado XAML, este servicio puede ser útil:

 IProvideValueTarget provideValueTarget = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;

La interfaz IProvideValueTarget define dos propiedades, TargetObject y TargetProperty. Cuando esta información se obtiene en la clase ImageResourceExtension, TargetObject es el Image y TargetProperty es un objeto BindableProperty para la propiedad Source de Image. Esta es la propiedad en la que se ha establecido la extensión de marcado XAML.

La llamada GetService con un argumento de typeof(IProvideValueTarget) devuelve realmente un objeto de tipo SimpleValueTargetProvider, que se define en el espacio de nombres Xamarin.Forms.Xaml.Internals. Si convierte el valor devuelto de GetService a ese tipo, también puede tener acceso a una propiedad ParentObjects, que es una matriz que contiene el elemento Image, el Grid primario y el ImageResourceDemoPage primario del Grid.

Conclusión

Las extensiones de marcado XAML desempeñan un papel fundamental en XAML al ampliar la capacidad de establecer atributos de una variedad de orígenes. Además, si las extensiones de marcado XAML existentes no proporcionan exactamente lo que necesita, también puede escribir una propia.