Data binding con tipi non corrispondenti
In alcuni casi i dati in uso non corrispondono al tipo di dati della proprietà del controllo che visualizza i dati. Ad esempio, potrebbe essere presente un valore monetario archiviato in un tipo decimal
che si vuole visualizzare in un controllo Label
, formattato come valuta. Un esempio più complesso è rappresentato dall'app meteo presentata nel modulo. Un'immagine dovrebbe essere visualizzata in base al valore di enumerazione Sunny
o Cloudy
delle previsioni meteo, ma non è possibile associare il valore di enumerazione dell'origine a una proprietà di immagine di destinazione. Questa unità esamina i modi in cui è possibile convertire i dati.
Formattazione di stringhe
Un caso comune di mancata corrispondenza dei tipi riguarda un tipo intrinseco da visualizzare come stringa formattata. Analogamente a quando si vogliono visualizzare parti di un valore DateTime
o si vuole formattare un tipo decimal
come valuta.
Si supponga di voler visualizzare l'importo dovuto in una fattura e di avere questa proprietà nell'oggetto dati:
public decimal BillAmount { get; set; }
L'importo dovuto è 22,0304. È possibile usare due controlli etichetta per visualizzare testo e l'importo in dollari, come illustrato nel frammento di codice seguente:
<HorizontalStackLayout>
<Label Text="You owe" Margin="0,0,5,0" />
<Label Text="{Binding BillAmount}" />
<Label Text="to the bank" Margin="5,0,0,0" />
</HorizontalStackLayout>
In questo modo viene restituita all'interfaccia utente una stringa simile a You owe 22.0304 to the bank
, ma manca il simbolo di valuta e le cifre decimali potrebbero essere troppe o insufficienti in base alla valuta locale. In genere si elabora il valore come stringa con l'identificatore di formato "C" (o valuta) nel codice, come indicato di seguito:
string formattedBillAmount = string.Format("{0:C}", BillAmount);
Per usare la formattazione nel data binding, è tuttavia necessario che l'oggetto dati fornisca tale stringa formattata come proprietà diversa oppure occorre intercettarla in qualche modo e formattarla manualmente. Fortunatamente, i binding di .NET MAUI consentono di formattare le stringhe con la proprietà di binding StringFormat
. La stringa del formato segue le stesse regole del metodo String.Format
. Racchiudere tale stringa tra virgolette singole per evitare che il parser XAML si confonda a causa delle parentesi graffe. Il parametro 0
del formato stringa è il valore della proprietà elaborato dal binding.
<Label Text="{Binding BillAmount, StringFormat='You owe {0:C} to the bank'}" />
Si consideri il codice XAML seguente, che illustra la visualizzazione di BillAmount
usando entrambe le modalità:
<VerticalStackLayout Padding="10">
<HorizontalStackLayout>
<Label Text="You owe" Margin="0,0,5,0" />
<Label Text="{Binding BillAmount}" />
<Label Text="to the bank" Margin="5,0,0,0" />
</HorizontalStackLayout>
<HorizontalStackLayout>
<Label Text="{Binding BillAmount, StringFormat='You owe {0:C} to the bank'}" />
</HorizontalStackLayout>
</VerticalStackLayout>
L'immagine seguente illustra quello che viene prodotto dall'output XAML sullo schermo:
Il codice XAML che usa la proprietà di binding StringFormat
è più semplice rispetto all'altro codice XAML ed è possibile accedere al sistema di formattazione avanzato delle stringhe di NET.
Conversione dei tipi personalizzata
La proprietà di binding StringFormat
è utile quando si visualizza un valore come stringa, ma non quando si vuole convertire qualcosa come Color
o Image
da un altro tipo. In questi casi è necessario scrivere codice di conversione personalizzato.
Si supponga di richiedere all'utente di scegliere una password e di voler usare un colore nell'interfaccia utente per indicare la complessità della password. Esistono tre livelli di complessità: vulnerabile, buona, complessa. Il livello di complessità si basa sul numero di caratteri presenti nella password. Per fornire un feedback immediato all'utente sulla complessità della password, si vuole che lo sfondo del controllo Entry
contenente la password cambi in base alla complessità. L'immagine seguente illustra questi tre livelli di complessità: vulnerabile, buona, complessa.
Dei tre controlli immissione nello screenshot, il primo ha quattro caratteri immessi e presenta uno sfondo rosso. Il secondo ha nove caratteri immessi e presenta uno sfondo giallo. L'ultimo controllo immissione ha 15 caratteri e presenta uno sfondo blu.
Questi livelli vengono assegnati all'enumerazione Strength
:
private enum Strength
{
Weak,
Good,
Strong
}
Un oggetto dati viene assegnato come BindingContext
della pagina, che contiene il livello di complessità della password con la proprietà PasswordStrength
. Quando l'utente digita una password, la proprietà PasswordStrength
viene modificata in base alla lunghezza della password. Poiché l'oggetto dati contiene la proprietà PasswordStrength
, la proprietà viene associata a BackgroundColor
del controllo Entry
:
<Entry BackgroundColor="{Binding PasswordStrength} ... />
C'è però un problema. PasswordStrength
è di tipo Strength
mentre BackgroundColor
è Color
. Questi tipi non sono compatibili tra loro. .NET MAUI consente di risolvere questi problemi di mancata corrispondenza del tipo, ovvero la proprietà Converter
del binding.
Come indicato dal nome, un convertitore di binding esegue la conversione tra l'origine e la destinazione del binding. I convertitori vengono implementati tramite l'interfaccia IValueConverter
.
Implementare IValueConverter
Si crea la logica di conversione in una classe che implementa l'interfaccia IValueConverter
. I nomi di queste classi terminano in genere con il nome Converter
per renderne chiaro lo scopo.
L'interfaccia IValueConverter
definisce due metodi:
Convert
: esegue la conversione dalla proprietà dell'origine di associazione alla proprietà dell'interfaccia utente della destinazione del binding.ConvertBack
: esegue la conversione dalla proprietà della destinazione del binding alla proprietà dell'interfaccia utente dell'origine di associazione.Questo metodo viene usato raramente e non viene usato in questo scenario. La maggior parte dei convertitori genera una
NotSupportedException
per indicare che questa conversione non è supportata.
Di seguito è riportato il contratto dell'interfaccia:
public interface IValueConverter
{
object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture);
object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture);
}
Tenere presente che lo scenario in uso sta associando la proprietà Entry.BackgroundColor
alla proprietà PasswordStrength
dell'oggetto dati, ovvero un'enumerazione Strength
.
<Entry BackgroundColor="{Binding PasswordStrength} ... />
L'enumerazione Strength
deve essere convertita in Color
. Il convertitore deve quindi valutare quale valore Strength
viene fornito e restituire un valore Color
diverso. Il codice seguente illustra questa conversione:
namespace MyProject.Converters;
class StrengthToColorConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return (Strength)value! switch
{
Strength.Weak => Colors.OrangeRed,
Strength.Good => Colors.Yellow,
Strength.Strong => Colors.LightBlue,
_ => Colors.LightBlue
};
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) =>
throw new NotImplementedException();
}
È possibile esaminare in dettaglio questo codice:
- Il metodo
Convert
ha quattro parametri. In genere è possibile rimuovere gli ultimi due parametri, a meno che non si abbia un motivo specifico per usarli. - Il parametro
value
contiene il valore in ingresso. In questo esempio si tratta di un valore di enumerazioneStrength
. - Il parametro
targetType
viene ignorato. È tuttavia possibile usare questo parametro per verificare che il tipo di proprietà usato dal convertitore siaColor
. Questo viene omesso nell'esempio per semplicità. - Un'espressione switch viene usata per restituire un colore diverso in base al valore
Strength
.
Usare il convertitore in XAML
Dopo aver creato la classe del convertitore, è necessario crearne un'istanza e farvi riferimento nel binding. Il modo standard per creare un'istanza del convertitore è nel dizionario risorse dell'elemento radice.
Prima di tutto, eseguire il mapping di uno spazio dei nomi XML allo spazio dei nomi .NET che contiene il convertitore. Nell'esempio di codice seguente lo spazio dei nomi XML cvt
esegue è mappato allo spazio dei nomi MyProject.Converters
.NET:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:cvt="clr-namespace:MyProject.Converters"
...
Creare quindi un'istanza in ContentPage.Resources
:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:cvt="clr-namespace:MyProject.Converters"
x:Class="MyProject.PasswordExample">
<ContentPage.Resources>
<cvt:StrengthToColorConverter x:Key="StrengthToColorConverter" />
</ContentPage.Resources>
Ora un'istanza del convertitore si trova nel dizionario risorse con la chiave StrengthToColorConverter
, che ha lo stesso nome del tipo. Si tratta di un modo tipico di denominare i convertitori, in quanto si ha in genere un solo convertitore che viene riutilizzato in XAML. Se, per qualche motivo, sono necessarie più istanze del convertitore, le chiavi devono essere diverse tra di loro.
Fare infine riferimento al convertitore sul binding. Poiché il convertitore si trova in un dizionario risorse, usare l'estensione di markup {StaticResource}
per farvi riferimento. Il convertitore viene assegnato alla proprietà di binding Converter
:
<Entry BackgroundColor="{Binding PasswordStrength, Converter={StaticResource StrengthToColorConverter}}" ... />