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 deIMarkupExtension<Type>
.ArrayExtension
deriva deIMarkupExtension<Array>
.DynamicResourceExtension
deriva deIMarkupExtension<DynamicResource>
.BindingExtension
deriva deIMarkupExtension<BindingBase>
.ConstraintExpression
deriva deIMarkupExtension<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:
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:
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.