Creare un'interfaccia utente coerente con gli stili

Completato

In .NET Multi-platform App UI (MAUI), le risorse sono perfette per evitare valori hardcoded duplicati nel markup Extensible Application Markup Language (XAML), ma può essere noioso applicarle. Poiché si assegna ogni valore di proprietà singolarmente, il codice XAML può risultare troppo dettagliato e pieno di informazioni. In questa unità viene illustrato come raggruppare più impostazioni in uno stile, in modo da rendere il codice più ordinato e gestibile.

In che modo le risorse possono rendere poco leggibile il codice XAML

Una risorsa fornisce un valore per una singola proprietà. Tuttavia, l'uso di un'elevata quantità di risorse può produrre codice XAML eccessivamente dettagliato. Si supponga che di voler personalizzare l'aspetto dei pulsanti. Prima di tutto si creano le risorse per i valori necessari, quindi si applica ogni risorsa a tutti i pulsanti. Il codice seguente mostra come potrebbe essere il markup XAML per due pulsanti.

<Button
    Text = "OK"
    BackgroundColor = "{StaticResource highlightColor}"
    BorderColor = "{StaticResource borderColor}"
    BorderWidth = "{StaticResource borderWidth}"
    TextColor = "{StaticResource textColor}" />

<Button
    Text = "Cancel"
    BackgroundColor = "{StaticResource highlightColor}"
    BorderColor = "{StaticResource borderColor}"
    BorderWidth = "{StaticResource borderWidth}"
    TextColor = "{StaticResource textColor}" />

Si noti che le stesse cinque proprietà sono impostate per ogni pulsante. L'uso delle risorse elimina la necessità di usare valori hardcoded ripetuti in quattro di essi. ma questo tipo di markup XAML diventa rapidamente difficile da leggere. Inoltre, se si imposta un numero elevato di proprietà per ogni controllo, capita facilmente di omettere accidentalmente una di esse, causando incoerenze nell'aspetto dei controlli. Per risolvere il problema è possibile creare uno stile che assegni tutte e quattro le proprietà in una sola volta.

Che cos'è un setter?

I setter sono i componenti principali usati per creare stili.

Un setter è un contenitore di una coppia proprietà-valore. Un setter rappresenta sostanzialmente un'istruzione di assegnazione. Si specificano la proprietà da assegnare e il valore da applicare. In genere gli oggetti Setter vengono creati nel markup XAML. L'esempio seguente crea un oggetto Setter per la proprietà TextColor.

<Setter Property="TextColor" Value="White" />

È possibile usare una risorsa per il valore in un setter, come illustrato nel codice seguente. Questa tecnica è ideale quando si vuole usare lo stesso valore in più setter.

<Setter Property="TextColor" Value="{StaticResource textColor}" />

Nota

Il valore della proprietà specificato in un setter deve essere implementato come proprietà associabile. Tutte le proprietà dei controlli di .NET MAUI che terminano con il suffisso Property sono proprietà associabili. Se si intende usare una proprietà come TextColor in un setter, verificare che esista una proprietà associabile corrispondente denominata TextColorProperty per il controllo. In pratica, quasi tutte le proprietà usate nei setter vengono implementate in questo modo.

Che cos'è uno stile?

Uno stile è una raccolta di setter destinati a un tipo di controllo specifico. .NET MAUI richiede un tipo di destinazione per poter verificare che nei setter esistano le proprietà per tale tipo.

Il codice seguente illustra uno stile che combina i quattro valori dell'esempio precedente. Si noti che TargetType è impostato su Button e tutte le proprietà nei setter sono membri della classe Button. Non è possibile usare questo stile per un'etichetta, in quanto la classe Label non include la proprietà BorderColor o BorderWidth.

<Style TargetType="Button">
    <Setter Property="BackgroundColor" Value="#2A84D3" />
    <Setter Property="BorderColor" Value="#1C5F9B" />
    <Setter Property="BorderWidth" Value="3" />
    <Setter Property="TextColor" Value="White" />
</Style>

Definire uno stile

In genere gli stili vengono definiti come risorse all'interno di un oggetto ResourceDictionary. Un dizionario risorse consente di usare facilmente lo stile in più controlli della stessa pagina o persino dell'intera applicazione. Il codice seguente illustra come definire uno stile come risorsa all'interno di un dizionario. Si noti che allo stile viene assegnato un nome usando la proprietà x:Key. La denominazione di uno stile consente di farvi riferimento dall'interno delle pagine XAML.

<ContentPage.Resources>
    <Style x:Key="MyButtonStyle" TargetType="Button">
        ...
    </Style>
</ContentPage.Resources>

Applicare uno stile

È possibile associare uno stile a un controllo assegnando il nome dello stile alla proprietà Style. In seguito all'assegnazione, ogni oggetto Setter nello stile viene applicato al controllo di destinazione. Il codice seguente mostra come applicare uno stile a due pulsanti.

<Button Text="OK" Style="{StaticResource MyButtonStyle}" />
<Button Text="Cancel" Style="{StaticResource MyButtonStyle}" />

Nell'esempio precedente è stata usata l'estensione di markup StaticResource per associare lo stile ai controlli. Questa è un'ottima tecnica quando non è necessario modificare lo stile in fase di esecuzione, ma cosa accade se si vuole implementare ad esempio un tema dinamico, dove l'interfaccia utente deve cambiare? In questo caso si può usare l'estensione di markup DynamicResource per caricare lo stile.

<Button Text="Cancel" Style="{DynamicResource MyButtonStyle}" />

DynamicResource attende la sostituzione della proprietà x:Key nel dizionario risorse. Se si scrive codice che carica un nuovo stile in ResourceDictionary con lo stesso valore di x:Key, il nuovo stile viene applicato automaticamente all'interfaccia utente.

Usare uno stile implicito per più controlli

Si supponga che l'interfaccia utente abbia 50 pulsanti e che si voglia applicare lo stesso stile a tutti. Basandosi su quanto visto finora, sarebbe necessario eseguire manualmente l'assegnazione alla proprietà Style per ogni pulsante. Non è un'operazione difficile, ma è comunque noiosa.

Uno stile implicito è uno stile aggiunto a un dizionario risorse senza assegnargli un identificatore x:Key. Gli stili impliciti vengono applicati automaticamente a tutti i controlli dell'oggetto TargetType specificato.

Il codice seguente mostra l'esempio precedente dichiarato come stile implicito. Questo stile viene applicato a ogni pulsante della pagina.

<ContentPage.Resources>
    <Style TargetType="Button">
        <Setter Property="BackgroundColor" Value="Blue" />
        <Setter Property="BorderColor" Value="Navy" />
        ...
    </Style>
</ContentPage.Resources>

Importante

La corrispondenza degli stili impliciti con i controlli richiede una corrispondenza esatta con il TargetType specificato. I controlli che ereditano dal tipo di destinazione non riceveranno gli stili. Per l'applicazione ai controlli ereditati, è possibile impostare l'attributo Style.ApplyToDerivedTypes su True al momento della definizione dello stile. Ad esempio, per applicare uno stile al tipo Button e fare in modo che venga applicato anche ai pulsanti che ereditano da Button (ad esempio imageButton, RadioButton o un tipo personalizzato creato dall'utente), è possibile usare uno stile come questo.

<ContentPage.Resources>
    <Style TargetType="Button"
           ApplyToDerivedTypes="True">
        <Setter Property="BackgroundColor" Value="Black" />
    </Style>
</ContentPage.Resources>

Eseguire l'override di uno stile

Si può pensare a uno stile come a qualcosa che fornisce un set di valori predefiniti per i controlli. Uno stile esistente potrebbe avvicinarsi ai tuoi requisiti, ma contenere uno o due setter non desiderati. In tal caso, è possibile applicare lo stile ed eseguire l'override del valore impostando direttamente le proprietà. L'impostazione esplicita viene applicata dopo lo stile e pertanto eseguirà l'override del valore.

Si supponga di voler usare lo stile seguente per diversi pulsanti della pagina.

<Style x:Key="MyButtonStyle" TargetType="Button">
    <Setter Property="BackgroundColor" Value="Blue" />
    <Setter Property="BorderRadius" Value="10" />
    <Setter Property="BorderWidth" Value="3" />
</Style>

Questo stile è applicabile alla maggior parte dei pulsanti, ma non al pulsante Cancel, che richiede uno sfondo rosso. È possibile usare lo stesso stile per il pulsante Cancel purché si imposti direttamente anche la proprietà BackgroundColor. Il codice seguente mostra come eseguire l'override dell'impostazione del colore.

<Button
    Text="Cancel"
    Style="{StaticResource MyButtonStyle}"
    BackgroundColor="Red"
    ... />

Specificare come destinazione un tipo di predecessore

Si supponga di volere un colore di sfondo personalizzato per i pulsanti e le etichette. È possibile creare stili distinti per ogni tipo oppure crearne uno con TargetType impostato su VisualElement. Questa tecnica funziona perché VisualElement è una classe di base sia per Button che per Label.

Il codice seguente illustra uno stile che ha come destinazione una classe di base e viene applicato a due diversi tipi derivati.

<Style x:Key="MyVisualElementStyle" TargetType="VisualElement">
    <Setter Property="BackgroundColor" Value="#2A84D3" />
</Style>
...
<Button Style="{StaticResource MyVisualElementStyle}" ... />
<Label Style="{StaticResource MyVisualElementStyle}" ... />

Questo esempio identifica lo stile usando x:Key e i controlli lo applicano in modo esplicito. In questo caso uno stile implicito non sarebbe valido perché, per uno stile di questo tipo, TargetType deve corrispondere esattamente al tipo di controllo.

Usare BasedOn per ereditare da uno stile

Si supponga di voler creare un aspetto coerente per l'interfaccia utente. Si decide che tutti i controlli devono usare un colore di sfondo coerente. L'impostazione del colore di sfondo è probabilmente presente in più stili. Il codice seguente illustra due stili con un setter ripetuto.

<Style x:Key="MyButtonStyle" TargetType="Button">
    <Setter Property="BackgroundColor" Value="Blue" />
    <Setter Property="BorderColor" Value="Navy" />
    <Setter Property="BorderWidth" Value="5" />
</Style>

<Style x:Key="MyEntryStyle" TargetType="Entry">
    <Setter Property="BackgroundColor" Value="Blue" />
    <Setter Property="TextColor" Value="White" />
</Style>

È possibile usare l'ereditarietà dello stile per trasformare il setter duplicato in uno stile di base. Per creare uno stile derivato, impostare la relativa proprietà BasedOn in modo che faccia riferimento allo stile di base. Il nuovo stile eredita tutti i setter da quello di base. Lo stile derivato può anche aggiungere nuovi setter o sostituire un setter ereditato con uno che contiene un valore diverso.

Il codice seguente mostra gli stili dell'esempio precedente sottoposti a refactoring in una gerarchia. Il setter comune è presente solo nello stile di base invece di essere ripetuto. Si noti che per cercare lo stile di base si usa l'estensione di markup StaticResource. Non è possibile usare DynamicResource in questa situazione.

<Style x:Key="MyVisualElementStyle" TargetType="VisualElement">
    <Setter Property="BackgroundColor" Value="Blue" />
</Style>

<Style x:Key="MyButtonStyle" TargetType="Button" BasedOn="{StaticResource MyVisualElementStyle}">
    <Setter Property="BorderColor" Value="Navy" />
    <Setter Property="BorderWidth" Value="5" />
</Style>

<Style x:Key="MyEntryStyle" TargetType="Entry" BasedOn="{StaticResource MyVisualElementStyle}">
    <Setter Property="TextColor" Value="White" />
</Style>

Il valore TargetType dello stile di base e quello degli stili derivati devono essere compatibili. Per essere compatibili, gli stili devono avere la stessa proprietà TargetType oppure la proprietà TargetType dello stile derivato deve essere un discendente del TargetType dello stile di base.

Verifica delle conoscenze

1.

Perché definire uno stile all'interno di un ResourceDictionary?