Creación de extensiones de marcado XAML

Ejemplo de descarga Descarga del ejemplo

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

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

  • 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 IMarkupExtension interfaces definen solo un método cada uno, denominado ProvideValue :

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

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

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

A menudo, las extensiones de marcado XAML definen propiedades que contribuyen al valor devuelto. (La excepción obvia es NullExtension , en la que simplemente devuelve ProvideValuenull ). El ProvideValue método tiene un único argumento de tipo que se IServiceProvider 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 mediante componentes de Color matiz, saturación y luminosidad. Define cuatro propiedades para los cuatro componentes del color, incluido un componente alfa que se inicializa en 1. La clase se deriva de IMarkupExtension<Color> para indicar un valor Color devuelto:

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 deriva de , la clase debe contener dos métodos, uno que devuelve y otro que devuelve , pero el segundo método simplemente puede llamar IMarkupExtension<T>IMarkupExtension al primer ProvideValueColorobject método.

La página Demostración de color de HSL muestra varias maneras que pueden 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>

Tenga en cuenta que cuando es una etiqueta XML, las cuatro propiedades se establecen como atributos, pero cuando aparece entre llaves, las cuatro propiedades se separan por comas sin HslColorExtension comillas. Los valores predeterminados de , y son 0 y el valor predeterminado de HS es L 1, por lo que esas propiedades se pueden omitir si quiere que se establezcan en A valores predeterminados. En el último ejemplo se muestra un ejemplo en el que la luminosidad es 0, lo 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:

HSL Color Demo

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

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

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

[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 es útil cuando un archivo XAML necesita tener acceso a un archivo de imagen almacenado como un recurso incrustado en el proyecto .NET Standard biblioteca. Usa la propiedad Source para llamar al método ImageSource.FromResource estático. 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 del método proporciona el nombre del ensamblado y solo es ImageSource.FromResource necesario para las compilaciones de versión en UWP. Sin embargo, se debe llamar a 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 ImageSource.FromResource esa biblioteca. (Consulte el artículo Imágenes incrustadas 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 , la propiedad se indica en un atributo como la propiedad Source content de la clase Source . Esto significa que se puede omitir la parte de la expresión entre Source= llaves. En la página Demostración de recursos de imagen, los elementos 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 demostraciónde imagen

Proveedores de servicios

Mediante el uso del argumento en , las extensiones de marcado XAML pueden obtener acceso a información útil sobre el archivo XAML en el que IServiceProviderProvideValue se usan. Pero para usar el IServiceProvider argumento correctamente, 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 Xamarin.Forms /tree/master/. Xamarin.Forms Xaml/MarkupExtensions" data-linktype="external">Xamarin.Forms en el repositorio en Xamarin.Forms 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 IProvideValueTarget interfaz define dos propiedades, y TargetObjectTargetProperty . Cuando esta información se obtiene en la ImageResourceExtension clase , es y es un objeto para la propiedad de TargetObjectImageTargetPropertyBindablePropertySourceImage . Esta es la propiedad en la que se ha establecido la extensión de marcado XAML.

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

Conclusión

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