Criação de extensões de marcação XAML

No nível programático, uma extensão de marcação XAML é uma classe que implementa a IMarkupExtension interface ou IMarkupExtension<T> . Você pode explorar o código-fonte das extensões de marcação padrão descritas abaixo no diretório MarkupExtensions do Xamarin.Forms repositório do GitHub.

Também é possível definir suas próprias extensões de marcação XAML personalizadas derivando de IMarkupExtension ou IMarkupExtension<T>. Use o formulário genérico se a extensão de marcação obtiver um valor de um tipo específico. Este é o caso de várias das Xamarin.Forms extensões de marcação:

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

As duas IMarkupExtension interfaces definem apenas um método cada, chamado ProvideValue:

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

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

Como IMarkupExtension<T> deriva de IMarkupExtension e inclui a new palavra-chave no ProvideValue, ele contém ambos os ProvideValue métodos.

Muitas vezes, as extensões de marcação XAML definem propriedades que contribuem para o valor de retorno. (A exceção óbvia é NullExtension, em que ProvideValue simplesmente retorna null.) O ProvideValue método tem um único argumento do tipo IServiceProvider que será discutido posteriormente neste artigo.

Uma extensão de marcação para especificar cor

A extensão de marcação XAML a seguir permite que você construa um Color valor usando componentes de matiz, saturação e luminosidade. Ele define quatro propriedades para os quatro componentes da cor, incluindo um componente alfa que é inicializado como 1. A classe deriva de para indicar um Color valor de IMarkupExtension<Color> retorno:

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);
    }
}

Como IMarkupExtension<T> deriva de IMarkupExtension, a classe deve conter dois ProvideValue métodos, um que retorna Color e outro que retorna object, mas o segundo método pode simplesmente chamar o primeiro método.

A página HSL Color Demo mostra uma variedade de maneiras que HslColorExtension podem aparecer em um arquivo XAML para especificar a cor de um 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, quando HslColorExtension é uma marca XML, as quatro propriedades são definidas como atributos, mas quando aparece entre chaves, as quatro propriedades são separadas por vírgulas sem aspas. Os valores padrão para H, Se L são 0 e o valor padrão de é 1, portanto, essas propriedades podem ser omitidas A se você quiser que elas sejam definidas como valores padrão. O último exemplo mostra um exemplo onde a luminosidade é 0, o que normalmente resulta em preto, mas o canal alfa é 0,5, então é meio transparente e aparece cinza contra o fundo branco da página:

Demonstração de cores HSL

Uma extensão de marcação para acessar bitmaps

O argumento para ProvideValue é um objeto que implementa a IServiceProvider interface, que é definida no namespace .NET System . Essa interface tem um membro, um método nomeado GetService com um Type argumento.

A ImageResourceExtension classe mostrada abaixo mostra um possível uso de e GetService para obter um IXmlLineInfoProvider objeto que pode fornecer informações de IServiceProvider linha e caractere indicando onde um determinado erro foi detectado. Nesse caso, uma exceção é gerada quando a Source propriedade não foi definida:

[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 é útil quando um arquivo XAML precisa acessar um arquivo de imagem armazenado como um recurso incorporado no projeto de biblioteca do .NET Standard. Ele usa a Source propriedade para chamar o método estático ImageSource.FromResource . Esse método requer um nome de recurso totalmente qualificado, que consiste no nome do assembly, o nome da pasta e o nome do arquivo separados por pontos. O segundo argumento para o ImageSource.FromResource método fornece o nome do assembly e só é necessário para compilações de versão na UWP. Independentemente disso, deve ser chamado a partir do assembly que contém o bitmap, o que significa que essa extensão de recurso XAML não pode fazer parte de uma biblioteca externa, ImageSource.FromResource a menos que as imagens também estejam nessa biblioteca. (Veja o Artigo Imagens incorporadas para obter mais informações sobre como acessar bitmaps armazenados como recursos incorporados.)

Embora ImageResourceExtension exija que a Source propriedade seja definida, a Source propriedade é indicada em um atributo como a propriedade content da classe. Isso significa que a Source= parte da expressão em chaves pode ser omitida. Na página Image Resource Demo, os Image elementos buscam duas imagens usando o nome da pasta e o nome do arquivo separados por pontos:

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

Este é o programa em execução:

Demonstração do recurso de imagem

Provedores de Serviços

Usando o IServiceProvider argumento para ProvideValue, as extensões de marcação XAML podem obter acesso a informações úteis sobre o arquivo XAML no qual estão sendo usadas. Mas para usar o IServiceProvider argumento com sucesso, você precisa saber que tipo de serviços estão disponíveis em contextos específicos. A melhor maneira de entender esse recurso é estudando o código-fonte das extensões de marcação XAML existentes na pasta MarkupExtensions no Xamarin.Forms repositório no GitHub. Esteja ciente de que alguns tipos de serviços são internos ao Xamarin.Forms.

Em algumas extensões de marcação XAML, esse serviço pode ser útil:

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

A IProvideValueTarget interface define duas propriedades TargetObject e TargetProperty. Quando essas informações são obtidas na ImageResourceExtension classe, TargetObject é o Image e TargetProperty é um BindableProperty objeto para a Source propriedade de Image. Essa é a propriedade na qual a extensão de marcação XAML foi definida.

A GetService chamada com um argumento de typeof(IProvideValueTarget) realmente retorna um objeto do tipo SimpleValueTargetProvider, que é definido no Xamarin.Forms.Xaml.Internals namespace. Se você converter o valor de retorno desse tipo para GetService esse tipo, também poderá acessar uma ParentObjects propriedade, que é uma matriz que contém o Image elemento, o Grid pai e o ImageResourceDemoPage pai do Grid.

Conclusão

As extensões de marcação XAML desempenham um papel vital no XAML, estendendo a capacidade de definir atributos de uma variedade de fontes. Além disso, se as extensões de marcação XAML existentes não fornecerem exatamente o que você precisa, você também poderá escrever suas próprias.