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 object
string
. 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 LayoutOptions
struttura 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
, Large
ad 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 iOS
attributi , Android
e 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:
Sebbene sia più comune definire la Resources
raccolta nella parte superiore della pagina, tenere presente che la Resources
proprietà è definita da VisualElement
e 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, http
ad 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:
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 sunull
, impostarla sull'estensione di{x:Null}
markup. - Se una proprietà è di tipo
Type
, è possibile assegnarla a unType
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 denominatoType
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:
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.