Condividi tramite


Parte 3. Estensioni di markup XAML

Le estensioni di markup XAML costituiscono una funzionalità importante in XAML che consente di impostare le proprietà su oggetti o valori a cui viene fatto riferimento indirettamente da altre origini. Le estensioni di markup XAML sono particolarmente importanti per la condivisione degli oggetti e il riferimento alle costanti usate in un'applicazione, ma trovano la loro utilità più grande nei data binding.

Estensioni di markup XAML

In generale, puoi usare XAML per impostare le proprietà di un oggetto su valori espliciti, ad esempio una stringa, un numero, un membro di enumerazione o una stringa convertita in un valore dietro le quinte.

In alcuni casi, tuttavia, le proprietà devono invece fare riferimento a valori definiti in un'altra posizione o che potrebbero richiedere una piccola elaborazione da parte del codice in fase di esecuzione. A questo scopo, sono disponibili estensioni di markup XAML.

Queste estensioni di markup XAML non sono estensioni di XML. XAML è completamente xml legale. Sono denominate "estensioni" perché sono supportate dal codice nelle classi che implementano IMarkupExtension. È possibile scrivere estensioni di markup personalizzate.

In molti casi, le estensioni di markup XAML sono immediatamente riconoscibili nei file XAML perché appaiono come impostazioni di attributo delimitate da parentesi graffe: { e }, ma talvolta le estensioni di markup vengono visualizzate nel markup come elementi convenzionali.

Risorse condivise

Alcune pagine XAML contengono diverse visualizzazioni con proprietà impostate sullo stesso valore. Ad esempio, molte delle impostazioni delle proprietà per questi Button oggetti sono le stesse:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             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="CenterAndExpand"
                BorderWidth="3"
                Rotation="-15"
                TextColor="Red"
                FontSize="24" />

        <Button Text="Do that!"
                HorizontalOptions="Center"
                VerticalOptions="CenterAndExpand"
                BorderWidth="3"
                Rotation="-15"
                TextColor="Red"
                FontSize="24" />

        <Button Text="Do the other thing!"
                HorizontalOptions="Center"
                VerticalOptions="CenterAndExpand"
                BorderWidth="3"
                Rotation="-15"
                TextColor="Red"
                FontSize="24" />

    </StackLayout>
</ContentPage>

Se una di queste proprietà deve essere modificata, è preferibile apportare la modifica una sola volta anziché tre volte. Se si trattasse di codice, è probabile che si usino costanti e oggetti statici di sola lettura per mantenere tali valori coerenti e facili da modificare.

In XAML, una soluzione comune consiste nell'archiviare tali valori o oggetti in un dizionario risorse. La VisualElement classe definisce una proprietà denominata Resources di tipo ResourceDictionary, ovvero un dizionario con chiavi di tipo e valori di tipo objectstring . Puoi inserire oggetti in questo dizionario e quindi farvi riferimento dal markup, tutto in XAML.

Per usare un dizionario risorse in una pagina, includere una coppia di tag di Resources elemento proprietà. È più comodo inserirli nella parte superiore della pagina:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.SharedResourcesPage"
             Title="Shared Resources Page">

    <ContentPage.Resources>

    </ContentPage.Resources>
    ...
</ContentPage>

È anche necessario includere ResourceDictionary in modo esplicito i tag:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.SharedResourcesPage"
             Title="Shared Resources Page">

    <ContentPage.Resources>
        <ResourceDictionary>

        </ResourceDictionary>
    </ContentPage.Resources>
    ...
</ContentPage>

È ora possibile aggiungere oggetti e valori di vari tipi al dizionario risorse. Questi tipi devono essere creabili tramite un'istanza. Non possono essere classi astratte, ad esempio. Questi tipi devono avere anche un costruttore pubblico senza parametri. Ogni elemento richiede una chiave del dizionario specificata con l'attributo x:Key . Ad esempio:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.SharedResourcesPage"
             Title="Shared Resources Page">

    <ContentPage.Resources>
        <ResourceDictionary>
            <LayoutOptions x:Key="horzOptions"
                           Alignment="Center" />

            <LayoutOptions x:Key="vertOptions"
                           Alignment="Center"
                           Expands="True" />
        </ResourceDictionary>
    </ContentPage.Resources>
    ...
</ContentPage>

Questi due elementi sono valori del tipo di LayoutOptionsstruttura e ognuno ha una chiave univoca e una o due proprietà impostate. Nel codice e nel markup, è molto più comune usare i campi statici di LayoutOptions, ma qui è più pratico impostare le proprietà.

È ora necessario impostare le HorizontalOptions proprietà e VerticalOptions di questi pulsanti su queste risorse e questa operazione viene eseguita con l'estensione StaticResource di markup XAML:

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

L'estensione StaticResource di markup è sempre delimitata da parentesi graffe e include la chiave del dizionario.

Il nome StaticResource lo distingue da DynamicResource, che Xamarin.Forms supporta anche . DynamicResource è per le chiavi del dizionario associate ai valori che possono cambiare durante il runtime, mentre StaticResource accede agli elementi dal dizionario una sola volta quando vengono costruiti gli elementi nella pagina.

Per la BorderWidth proprietà, è necessario archiviare un valore double nel dizionario. XAML definisce facilmente i tag per i tipi di dati comuni come x:Double e x:Int32:

<ContentPage.Resources>
    <ResourceDictionary>
        <LayoutOptions x:Key="horzOptions"
                       Alignment="Center" />

        <LayoutOptions x:Key="vertOptions"
                       Alignment="Center"
                       Expands="True" />

        <x:Double x:Key="borderWidth">
            3
        </x:Double>
    </ResourceDictionary>
</ContentPage.Resources>

Non è necessario metterlo su tre righe. Questa voce di dizionario per questo angolo di rotazione occupa solo una riga:

<ContentPage.Resources>
    <ResourceDictionary>
        <LayoutOptions x:Key="horzOptions"
                       Alignment="Center" />

        <LayoutOptions x:Key="vertOptions"
                       Alignment="Center"
                       Expands="True" />

         <x:Double x:Key="borderWidth">
            3
         </x:Double>

        <x:Double x:Key="rotationAngle">-15</x:Double>
    </ResourceDictionary>
</ContentPage.Resources>

È possibile fare riferimento a queste due risorse nello stesso modo dei LayoutOptions valori:

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

Per le risorse di tipo Color, è possibile usare le stesse rappresentazioni di stringa usate quando si assegnano direttamente attributi di questi tipi. I convertitori di tipi vengono richiamati quando viene creata la risorsa. Ecco una risorsa di tipo Color:

<Color x:Key="textColor">Red</Color>

Spesso, i programmi impostano una FontSize proprietà su un membro dell'enumerazione NamedSize , Largead esempio . La FontSizeConverter classe funziona in background per convertirla in un valore dipendente dalla piattaforma usando il Device.GetNamedSized metodo . Tuttavia, quando si definisce una risorsa di dimensioni del carattere, è più opportuno usare un valore numerico, illustrato di seguito come x:Double tipo:

<x:Double x:Key="fontSize">24</x:Double>

Ora tutte le proprietà tranne Text sono definite dalle impostazioni delle risorse:

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

È anche possibile usare OnPlatform all'interno del dizionario risorse per definire valori diversi per le piattaforme. Ecco come un OnPlatform oggetto può far parte del dizionario risorse per colori di testo diversi:

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

Si noti che OnPlatform ottiene entrambi un x:Key attributo perché è un oggetto nel dizionario e un x:TypeArguments attributo perché è una classe generica. Gli iOSattributi , Androide UWP vengono convertiti in Color valori quando l'oggetto viene inizializzato.

Ecco il file XAML completo finale con tre pulsanti che accedono a sei valori condivisi:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.SharedResourcesPage"
             Title="Shared Resources Page">

    <ContentPage.Resources>
        <ResourceDictionary>
            <LayoutOptions x:Key="horzOptions"
                           Alignment="Center" />

            <LayoutOptions x:Key="vertOptions"
                           Alignment="Center"
                           Expands="True" />

            <x:Double x:Key="borderWidth">3</x:Double>

            <x:Double x:Key="rotationAngle">-15</x:Double>

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

            <x:Double x:Key="fontSize">24</x:Double>
        </ResourceDictionary>
    </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>

Gli screenshot verificano lo stile coerente e lo stile dipendente dalla piattaforma:

Controlli con stile

Sebbene sia più comune definire la Resources raccolta nella parte superiore della pagina, tenere presente che la Resources proprietà è definita da VisualElemente che è possibile disporre Resources di raccolte in altri elementi nella pagina. Ad esempio, provare ad aggiungerne uno a StackLayout in questo esempio:

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

Si scoprirà che il colore del testo dei pulsanti è ora blu. Fondamentalmente, ogni volta che il parser XAML rileva un'estensione StaticResource di markup, cerca la struttura ad albero visuale e usa il primo ResourceDictionary che rileva che contiene tale chiave.

Uno dei tipi più comuni di oggetti archiviati nei dizionari risorse è Xamarin.FormsStyle, che definisce una raccolta di impostazioni delle proprietà. Gli stili sono descritti nell'articolo Stili.

A volte gli sviluppatori che non hanno esperienza con XAML si chiedono se possono inserire un elemento visivo, Label ad esempio o Button in un oggetto ResourceDictionary. Anche se è sicuramente possibile, non ha molto senso. Lo scopo di ResourceDictionary è condividere gli oggetti. Non è possibile condividere un elemento visivo. La stessa istanza non può essere visualizzata due volte in una singola pagina.

Estensione di markup x:Static

Nonostante le analogie dei loro nomi, x:Static e StaticResource sono molto diversi. StaticResource restituisce un oggetto da un dizionario risorse mentre x:Static accede a una delle opzioni seguenti:

  • un campo statico pubblico
  • una proprietà statica pubblica
  • un campo costante pubblico
  • membro di enumerazione.

L'estensione StaticResource di markup è supportata dalle implementazioni XAML che definiscono un dizionario risorse, mentre x:Static è una parte intrinseca di XAML, come rivela il x prefisso.

Ecco alcuni esempi che illustrano come x:Static fare riferimento in modo esplicito a campi statici e membri di enumerazione:

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

Finora, questo non è molto impressionante. Tuttavia, l'estensione x:Static di markup può anche fare riferimento a campi o proprietà statici dal proprio codice. Ecco ad esempio una AppConstants classe che contiene alcuni campi statici che è possibile usare in più pagine in un'applicazione:

using System;
using Xamarin.Forms;

namespace XamlSamples
{
    static class AppConstants
    {
        public static readonly Thickness PagePadding;

        public static readonly Font TitleFont;

        public static readonly Color BackgroundColor = Color.Aqua;

        public static readonly Color ForegroundColor = Color.Brown;

        static AppConstants()
        {
            switch (Device.RuntimePlatform)
            {
                case Device.iOS:
                    PagePadding = new Thickness(5, 20, 5, 0);
                    TitleFont = Font.SystemFontOfSize(35, FontAttributes.Bold);
                    break;

                case Device.Android:
                    PagePadding = new Thickness(5, 0, 5, 0);
                    TitleFont = Font.SystemFontOfSize(40, FontAttributes.Bold);
                    break;

                case Device.UWP:
                    PagePadding = new Thickness(5, 0, 5, 0);
                    TitleFont = Font.SystemFontOfSize(50, FontAttributes.Bold);
                    break;
            }
        }
    }
}

Per fare riferimento ai campi statici di questa classe nel file XAML, è necessario un modo per indicare all'interno del file XAML in cui si trova questo file. Questa operazione viene eseguita con una dichiarazione dello spazio dei nomi XML.

Tenere presente che i file XAML creati come parte del modello XAML standard Xamarin.Forms contengono due dichiarazioni dello spazio dei nomi XML: una per l'accesso alle Xamarin.Forms classi e un'altra per fare riferimento a tag e attributi intrinseci a XAML:

xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

Sono necessarie altre dichiarazioni di spazio dei nomi XML per accedere ad altre classi. Ogni dichiarazione di spazio dei nomi XML aggiuntiva definisce un nuovo prefisso. Per accedere alle classi locali alla libreria .NET Standard dell'applicazione condivisa, ad esempio AppConstants, i programmatori XAML spesso usano il prefisso local. La dichiarazione dello spazio dei nomi deve indicare il nome dello spazio dei nomi CLR (Common Language Runtime), noto anche come nome dello spazio dei nomi .NET, ovvero il nome visualizzato in una definizione C# namespace o in una using direttiva:

xmlns:local="clr-namespace:XamlSamples"

È anche possibile definire dichiarazioni di spazio dei nomi XML per gli spazi dei nomi .NET in qualsiasi assembly a cui fa riferimento la libreria .NET Standard. Ad esempio, di seguito è riportato un sys prefisso per lo spazio dei nomi .NET System standard, che si trova nell'assembly netstandard . Poiché si tratta di un altro assembly, è necessario specificare anche il nome dell'assembly, in questo caso netstandard:

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

Si noti che la parola chiave è seguita da due punti e quindi dal nome dello spazio dei nomi .NET, seguito da un punto e virgola, dalla parola clr-namespace chiave assembly, da un segno di uguale e dal nome dell'assembly.

Sì, i due punti seguono clr-namespace ma il segno di uguale segue assembly. La sintassi è stata definita in questo modo intenzionalmente: la maggior parte delle dichiarazioni dello spazio dei nomi XML fa riferimento a un URI che inizia un nome di schema URI, httpad esempio , che è sempre seguito da due punti. La clr-namespace parte di questa stringa è destinata a simulare tale convenzione.

Entrambe queste dichiarazioni dello spazio dei nomi sono incluse nell'esempio StaticConstantsPage . Si noti che le BoxView dimensioni sono impostate su e Math.E, ma ridimensionate da un fattore pari a Math.PI 100:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             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="{x:Static local:AppConstants.PagePadding}">

    <StackLayout>
       <Label Text="Hello, XAML!"
              TextColor="{x:Static local:AppConstants.BackgroundColor}"
              BackgroundColor="{x:Static local:AppConstants.ForegroundColor}"
              Font="{x:Static local:AppConstants.TitleFont}"
              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>

Le dimensioni del risultato BoxView rispetto allo schermo dipendono dalla piattaforma:

Controlli che usano x:Static Markup Extension

Altre estensioni di markup standard

Diverse estensioni di markup sono intrinseche per XAML e supportate nei Xamarin.Forms file XAML. Alcuni di questi non vengono usati molto spesso, ma sono essenziali quando sono necessari:

  • Se una proprietà ha un valore non- null per impostazione predefinita, ma si vuole impostarla su null, impostarla sull'estensione di {x:Null} markup.
  • Se una proprietà è di tipo Type, è possibile assegnarla a un Type oggetto usando l'estensione {x:Type someClass}di markup .
  • Puoi definire matrici in XAML usando l'estensione di x:Array markup. Questa estensione di markup ha un attributo obbligatorio denominato Type che indica il tipo degli elementi nella matrice.
  • L'estensione Binding di markup è descritta nella parte 4. Nozioni di base sul data binding.
  • L'estensione RelativeSource di markup è descritta in Associazioni relative.

Estensione di markup ConstraintExpression

Le estensioni di markup possono avere proprietà, ma non sono impostate come attributi XML. In un'estensione di markup le impostazioni delle proprietà sono separate da virgole e non vengono visualizzate virgolette tra parentesi graffe.

Questa operazione può essere illustrata con l'estensione Xamarin.Forms di markup denominata ConstraintExpression, che viene usata con la RelativeLayout classe . È possibile specificare la posizione o le dimensioni di una visualizzazione figlio come costante o rispetto a una vista padre o un'altra vista denominata. La sintassi di ConstraintExpression consente di impostare la posizione o le dimensioni di una visualizzazione usando una Factor proprietà di un'altra visualizzazione, oltre a .Constant Qualsiasi elemento più complesso di quello che richiede il codice.

Ecco un esempio:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.RelativeLayoutPage"
             Title="RelativeLayout Page">

    <RelativeLayout>

        <!-- Upper left -->
        <BoxView Color="Red"
                 RelativeLayout.XConstraint=
                     "{ConstraintExpression Type=Constant,
                                            Constant=0}"
                 RelativeLayout.YConstraint=
                     "{ConstraintExpression Type=Constant,
                                            Constant=0}" />
        <!-- Upper right -->
        <BoxView Color="Green"
                 RelativeLayout.XConstraint=
                     "{ConstraintExpression Type=RelativeToParent,
                                            Property=Width,
                                            Factor=1,
                                            Constant=-40}"
                 RelativeLayout.YConstraint=
                     "{ConstraintExpression Type=Constant,
                                            Constant=0}" />
        <!-- Lower left -->
        <BoxView Color="Blue"
                 RelativeLayout.XConstraint=
                     "{ConstraintExpression Type=Constant,
                                            Constant=0}"
                 RelativeLayout.YConstraint=
                     "{ConstraintExpression Type=RelativeToParent,
                                            Property=Height,
                                            Factor=1,
                                            Constant=-40}" />
        <!-- Lower right -->
        <BoxView Color="Yellow"
                 RelativeLayout.XConstraint=
                     "{ConstraintExpression Type=RelativeToParent,
                                            Property=Width,
                                            Factor=1,
                                            Constant=-40}"
                 RelativeLayout.YConstraint=
                     "{ConstraintExpression Type=RelativeToParent,
                                            Property=Height,
                                            Factor=1,
                                            Constant=-40}" />

        <!-- Centered and 1/3 width and height of parent -->
        <BoxView x:Name="oneThird"
                 Color="Red"
                 RelativeLayout.XConstraint=
                     "{ConstraintExpression Type=RelativeToParent,
                                            Property=Width,
                                            Factor=0.33}"
                 RelativeLayout.YConstraint=
                     "{ConstraintExpression Type=RelativeToParent,
                                            Property=Height,
                                            Factor=0.33}"
                 RelativeLayout.WidthConstraint=
                     "{ConstraintExpression Type=RelativeToParent,
                                            Property=Width,
                                            Factor=0.33}"
                 RelativeLayout.HeightConstraint=
                     "{ConstraintExpression Type=RelativeToParent,
                                            Property=Height,
                                            Factor=0.33}"  />

        <!-- 1/3 width and height of previous -->
        <BoxView Color="Blue"
                 RelativeLayout.XConstraint=
                     "{ConstraintExpression Type=RelativeToView,
                                            ElementName=oneThird,
                                            Property=X}"
                 RelativeLayout.YConstraint=
                     "{ConstraintExpression Type=RelativeToView,
                                            ElementName=oneThird,
                                            Property=Y}"
                 RelativeLayout.WidthConstraint=
                     "{ConstraintExpression Type=RelativeToView,
                                            ElementName=oneThird,
                                            Property=Width,
                                            Factor=0.33}"
                 RelativeLayout.HeightConstraint=
                     "{ConstraintExpression Type=RelativeToView,
                                            ElementName=oneThird,
                                            Property=Height,
                                            Factor=0.33}"  />
    </RelativeLayout>
</ContentPage>

Forse la lezione più importante da prendere da questo esempio è la sintassi dell'estensione di markup: nessuna virgoletta deve essere visualizzata tra parentesi graffe di un'estensione di markup. Quando si digita l'estensione di markup in un file XAML, è naturale racchiudere i valori delle proprietà tra virgolette. Resisti alla tentazione!

Ecco il programma in esecuzione:

Layout relativo tramite vincoli

Riepilogo

Le estensioni di markup XAML illustrate di seguito forniscono un supporto importante per i file XAML. Ma forse l'estensione di markup XAML più importante è Binding, che viene descritta nella parte successiva di questa serie, parte 4. Nozioni di base sul data binding.