Condividi tramite


Modelli di controllo

Sfogliare l'esempio. Esplorare l'esempio

I modelli di controllo dell'interfaccia utente dell'app multipiattaforma .NET (.NET MAUI) consentono di definire la struttura visiva dei ContentView controlli personalizzati derivati e ContentPage delle pagine derivate. I modelli di controllo separano l'interfaccia utente (UI) per un controllo personalizzato o una pagina dalla logica che implementa il controllo o la pagina. Il contenuto aggiuntivo può anche essere inserito nel controllo personalizzato basato su modelli, o nella pagina basata su modelli, in una posizione predefinita.

È ad esempio possibile creare un modello di controllo per ridefinire l'interfaccia utente fornita da un controllo personalizzato. Il modello di controllo può quindi essere utilizzato dall'istanza del controllo personalizzato richiesta. In alternativa, è possibile creare un modello di controllo che definisce qualsiasi interfaccia utente comune che verrà usata da più pagine in un'app. Il modello di controllo può quindi essere utilizzato da più pagine, anche se ogni pagina visualizza comunque contenuto univoco.

Creare un ControlTemplate

L'esempio seguente mostra il codice per un controllo personalizzato CardView:

public class CardView : ContentView
{
    public static readonly BindableProperty CardTitleProperty =
        BindableProperty.Create(nameof(CardTitle), typeof(string), typeof(CardView), string.Empty);
    public static readonly BindableProperty CardDescriptionProperty =
        BindableProperty.Create(nameof(CardDescription), typeof(string), typeof(CardView), string.Empty);

    public string CardTitle
    {
        get => (string)GetValue(CardTitleProperty);
        set => SetValue(CardTitleProperty, value);
    }

    public string CardDescription
    {
        get => (string)GetValue(CardDescriptionProperty);
        set => SetValue(CardDescriptionProperty, value);
    }
    ...
}

La classe CardView, che deriva dalla classe ContentView, rappresenta un controllo personalizzato che visualizza i dati in un layout simile a una scheda. La classe contiene proprietà, supportate da proprietà associabili, per i dati visualizzati. Tuttavia, la classe CardView non definisce alcuna interfaccia utente. L'interfaccia utente verrà invece definita con un modello di controllo. Per altre informazioni sulla creazione ContentView di controlli personalizzati derivati, vedere ContentView.

Viene creato un modello di controllo con il tipo ControlTemplate. Quando si crea un ControlTemplate, si combinano oggetti View per creare l'interfaccia utente per un controllo personalizzato o una pagina. Un ControlTemplate deve avere un solo oggetto View come elemento radice. Tuttavia, l'elemento radice contiene in genere altri oggetti View. La combinazione degli oggetti costituisce la struttura visiva del controllo.

Mentre un ControlTemplate può essere definito inline, l'approccio tipico prevede la dichiarazione di ControlTemplate come risorsa in un dizionario risorse. Poiché i modelli di controllo sono risorse, rispettano le stesse regole di ambito che si applicano a tutte le risorse. Ad esempio, se dichiari un modello di controllo nel dizionario risorse a livello di app, il modello può essere usato ovunque nella tua app. Se si definisce il modello in una pagina, solo tale pagina può usare il modello di controllo. Per altre informazioni sulle risorse, vedere Dizionari risorse.

Nell'esempio di codice XAML seguente viene illustrato un ControlTemplate per oggetti CardView:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             ...>
    <ContentPage.Resources>
      <ControlTemplate x:Key="CardViewControlTemplate">
          <Frame BindingContext="{Binding Source={RelativeSource TemplatedParent}}"
                 BackgroundColor="{Binding CardColor}"
                 BorderColor="{Binding BorderColor}"
                 ...>
              <!-- UI objects that define the CardView visual structure -->
          </Frame>
      </ControlTemplate>
    </ContentPage.Resources>
    ...
</ContentPage>

Quando un ControlTemplate viene dichiarato come risorsa, è necessario specificare una chiave con l'attributo x:Key in modo che possa essere identificata nel dizionario risorse. In questo esempio, l'elemento radice del CardViewControlTemplate è un oggetto Frame. L'oggetto Frame usa l'estensione di markup RelativeSource per impostare il relativo BindingContext sull'istanza dell'oggetto di runtime a cui verrà applicato il modello, noto come oggetto padre basato su modelli. L'oggetto Frame utilizza una combinazione di controlli per definire la struttura visiva di un CardView oggetto . Le espressioni di associazione di questi oggetti vengono risolte in base alle proprietà di CardView, dato che BindingContext viene ereditato dall'elemento Frame radice. Per altre informazioni sull'estensione RelativeSource di markup, vedere Associazioni relative.

Utilizzare un ControlTemplate

Un ControlTemplate può essere applicato a un controllo personalizzato derivato da ContentView impostando la relativa proprietà ControlTemplate sull'oggetto modello di controllo. Analogamente, è possibile applicare un ControlTemplate a una pagina derivata da ContentPage impostando la relativa proprietà ControlTemplate sull'oggetto modello di controllo. In fase di esecuzione, quando viene applicato un ControlTemplate, tutti i controlli definiti nel ControlTemplate vengono aggiunti alla struttura ad albero visuale del controllo personalizzato basato su modelli o della pagina basata su modelli.

Nell'esempio seguente viene illustrato l'oggetto CardViewControlTemplate assegnato alla ControlTemplate proprietà di due CardView oggetti :

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:controls="clr-namespace:ControlTemplateDemos.Controls"
             ...>
    <StackLayout Margin="30">
        <controls:CardView BorderColor="DarkGray"
                           CardTitle="John Doe"
                           CardDescription="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla elit dolor, convallis non interdum."
                           IconBackgroundColor="SlateGray"
                           IconImageSource="user.png"
                           ControlTemplate="{StaticResource CardViewControlTemplate}" />
        <controls:CardView BorderColor="DarkGray"
                           CardTitle="Jane Doe"
                           CardDescription="Phasellus eu convallis mi. In tempus augue eu dignissim fermentum. Morbi ut lacus vitae eros lacinia."
                           IconBackgroundColor="SlateGray"
                           IconImageSource="user.png"
                           ControlTemplate="{StaticResource CardViewControlTemplate}" />
    </StackLayout>
</ContentPage>

In questo esempio, i controlli nel CardViewControlTemplate diventano parte della struttura ad albero visuale per ogni oggetto CardView. Poiché l'oggetto Frame radice per il modello di controllo imposta il relativo BindingContext sull'elemento padre basato su modelli, l'elemento Frame e i relativi elementi figlio risolvono le espressioni di associazione in base alle proprietà di ogni oggetto CardView.

Lo screenshot seguente mostra l'oggetto CardViewControlTemplate applicato agli CardView oggetti :

Screenshot di due oggetti CardView basati su modelli.

Importante

Il momento in cui un ControlTemplate viene applicato a un'istanza del controllo può essere rilevato eseguendo l'override del metodo OnApplyTemplate nel controllo personalizzato basato su modelli o nella pagina basata su modelli. Per altre informazioni, vedere Ottenere un elemento denominato da un modello.

Passare parametri con TemplateBinding

L'estensione di markup TemplateBinding associa una proprietà di un elemento che si trova in un ControlTemplate a una proprietà pubblica definita dal controllo personalizzato basato su modelli o dalla pagina basata su modelli. Quando si usa TemplateBinding, si consente alle proprietà del controllo di fungere da parametri per il modello. Pertanto, quando viene impostata una proprietà per un controllo personalizzato basato su modelli o una pagina basata su modelli, tale valore viene passato all'elemento per cui è stato definito il TemplateBinding.

Importante

L'espressione TemplateBinding di markup consente di rimuovere l'associazione RelativeSource dal modello di controllo precedente e di sostituire le Binding espressioni.

L'estensione di markup TemplateBinding definisce le proprietà seguenti:

  • Path, di tipo string, il percorso della proprietà.
  • Mode, di tipo BindingMode, la direzione in cui le modifiche vengono propagate tra l'origine e la destinazione.
  • Converter, di tipo IValueConverter, il convertitore di valori di associazione.
  • ConverterParameter, di tipo object, il parametro per il convertitore di valori di associazione.
  • StringFormat, di tipo string, il formato di stringa per l'associazione.

ContentProperty per l'estensione di markup TemplateBinding è Path. È pertanto possibile omettere la parte "Path=" dell'estensione di markup se il percorso è il primo elemento nell'espressione TemplateBinding. Per altre informazioni sull'uso di queste proprietà in un'espressione di associazione, vedere Data binding.

Avviso

L'estensione di markup TemplateBinding deve essere usata solo in un ControlTemplate. Tuttavia, se si tenta di usare un'espressione TemplateBinding al di fuori di un ControlTemplate non verrà generato un errore di compilazione o un'eccezione.

Nell'esempio di codice XAML seguente viene illustrato un ControlTemplate per gli oggetti CardView, che usa l'estensione di markup TemplateBinding:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             ...>
    <ContentPage.Resources>
        <ControlTemplate x:Key="CardViewControlTemplate">
            <Frame BackgroundColor="{TemplateBinding CardColor}"
                   BorderColor="{TemplateBinding BorderColor}"
                   ...>
                <!-- UI objects that define the CardView visual structure -->                   
            </Frame>
        </ControlTemplate>
    </ContentPage.Resources>
    ...
</ContentPage>

In questo esempio, l'estensione di markup TemplateBinding risolve le espressioni di associazione in base alle proprietà di ogni oggetto CardView. Lo screenshot seguente mostra l'oggetto CardViewControlTemplate applicato agli CardView oggetti :

Screenshot degli oggetti CardView basati su modelli.

Importante

L'uso dell'estensione di markup TemplateBinding equivale a impostare il BindingContext dell'elemento radice nel modello sul relativo elemento padre basato su modelli con l'estensione di markup RelativeSource e quindi a risolvere le associazioni degli oggetti figlio con l'estensione di markup Binding. L'estensione di markup TemplateBinding crea infatti un Binding con RelativeBindingSource.TemplatedParent come Source.

Applicare un ControlTemplate con uno stile

I modelli di controllo possono anche essere applicati con gli stili. A tale scopo, è possibile creare uno stile implicito o esplicito che utilizza il ControlTemplate.

Nell'esempio di codice XAML seguente viene illustrato uno stile implicito che utilizza il CardViewControlTemplate:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:controls="clr-namespace:ControlTemplateDemos.Controls"
             ...>
    <ContentPage.Resources>
        <ControlTemplate x:Key="CardViewControlTemplate">
            ...
        </ControlTemplate>

        <Style TargetType="controls:CardView">
            <Setter Property="ControlTemplate"
                    Value="{StaticResource CardViewControlTemplate}" />
        </Style>
    </ContentPage.Resources>
    <StackLayout Margin="30">
        <controls:CardView BorderColor="DarkGray"
                           CardTitle="John Doe"
                           CardDescription="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla elit dolor, convallis non interdum."
                           IconBackgroundColor="SlateGray"
                           IconImageSource="user.png" />
        ...
    </StackLayout>
</ContentPage>

In questo esempio, il Style implicito viene applicato automaticamente a ogni oggetto CardView e imposta la proprietà ControlTemplate di ogni CardView su CardViewControlTemplate.

Per altre informazioni sugli stili, vedere Stili.

Ridefinire l'interfaccia utente di un controllo

Quando viene creata un'istanza di un ControlTemplate e tale istanza viene assegnata alla proprietà ControlTemplate di un controllo personalizzato derivato da ContentView oppure di una pagina derivata da ContentPage, la struttura visiva definita per il controllo personalizzato o la pagina viene sostituita con la struttura visiva definita nel ControlTemplate.

Ad esempio, il controllo personalizzato CardViewUI definisce la relativa interfaccia utente usando il codice XAML seguente:

<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ControlTemplateDemos.Controls.CardViewUI"
             x:Name="this">
    <Frame BindingContext="{x:Reference this}"
           BackgroundColor="{Binding CardColor}"
           BorderColor="{Binding BorderColor}"
           ...>
        <!-- UI objects that define the CardView visual structure -->           
    </Frame>
</ContentView>

Tuttavia, i controlli che comprendono questa interfaccia utente possono essere sostituiti definendo una nuova struttura visiva in un ControlTemplate e assegnandolo alla proprietà ControlTemplate di un oggetto CardViewUI:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             ...>
    <ContentPage.Resources>
        <ControlTemplate x:Key="CardViewCompressed">
            <Grid RowDefinitions="100"
                  ColumnDefinitions="100, *">
                <Image Source="{TemplateBinding IconImageSource}"
                       BackgroundColor="{TemplateBinding IconBackgroundColor}"
                       ...>
                <!-- Other UI objects that define the CardView visual structure -->
            </Grid>
        </ControlTemplate>
    </ContentPage.Resources>
    <StackLayout Margin="30">
        <controls:CardViewUI BorderColor="DarkGray"
                             CardTitle="John Doe"
                             CardDescription="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla elit dolor, convallis non interdum."
                             IconBackgroundColor="SlateGray"
                             IconImageSource="user.png"
                             ControlTemplate="{StaticResource CardViewCompressed}" />
        ...
    </StackLayout>
</ContentPage>

In questo esempio, la struttura visiva dell'oggetto CardViewUI viene ridefinita in un ControlTemplate che fornisce una struttura visiva più compatta adatta a un elenco ridotto:

Screenshot degli oggetti CardViewUI basati su modelli.

Sostituire il contenuto in un ContentPresenter

Un ContentPresenter può essere inserito in un modello di controllo per contrassegnare il punto in cui dovrà comparire il contenuto visualizzato dal controllo personalizzato basato su modelli o dalla pagina basata su modelli. Il controllo personalizzato o la pagina che utilizza il modello di controllo definirà quindi il contenuto che verrà visualizzato dal ContentPresenter. Il diagramma seguente illustra un ControlTemplate per una pagina che contiene alcuni controlli, tra cui un ContentPresenter contrassegnato da un rettangolo blu:

Modello di controllo per contentPage.

Il codice XAML seguente mostra un modello di controllo denominato TealTemplate che contiene un ContentPresenter nella struttura visiva:

<ControlTemplate x:Key="TealTemplate">
    <Grid RowDefinitions="0.1*, 0.8*, 0.1*">
        <BoxView Color="Teal" />
        <Label Margin="20,0,0,0"
               Text="{TemplateBinding HeaderText}"
               ... />
        <ContentPresenter Grid.Row="1" />
        <BoxView Grid.Row="2"
                 Color="Teal" />
        <Label x:Name="changeThemeLabel"
               Grid.Row="2"
               Margin="20,0,0,0"
               Text="Change Theme"
               ...>
            <Label.GestureRecognizers>
                <TapGestureRecognizer Tapped="OnChangeThemeLabelTapped" />
            </Label.GestureRecognizers>
        </Label>
        <controls:HyperlinkLabel Grid.Row="2"
                                 Margin="0,0,20,0"
                                 Text="Help"
                                 Url="https://learn.microsoft.com/dotnet/maui/"
                                 ... />
    </Grid>
</ControlTemplate>

Nell'esempio seguente viene illustrato TealTemplate assegnato alla proprietà ControlTemplate di una pagina derivata da ContentPage:

<controls:HeaderFooterPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                           xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                           xmlns:controls="clr-namespace:ControlTemplateDemos.Controls"                           
                           ControlTemplate="{StaticResource TealTemplate}"
                           HeaderText="MyApp"
                           ...>
    <StackLayout Margin="10">
        <Entry Placeholder="Enter username" />
        <Entry Placeholder="Enter password"
               IsPassword="True" />
        <Button Text="Login" />
    </StackLayout>
</controls:HeaderFooterPage>

In fase di esecuzione, quando TealTemplate viene applicato alla pagina, il contenuto della pagina viene sostituito nel ContentPresenter definito nel modello di controllo:

Screenshot dell'oggetto pagina basato su modelli.

Ottenere un elemento denominato da un modello

Gli elementi denominati in un modello di controllo possono essere recuperati dal controllo personalizzato basato su modelli o dalla pagina basata su modelli. È possibile ottenere questo risultato con il metodo GetTemplateChild, che restituisce l'elemento denominato nella struttura ad albero visuale di ControlTemplate di cui viene creata un'istanza, se trovato. In caso contrario, viene restituito null.

Dopo la creazione di un'istanza di un modello di controllo, viene chiamato il metodo OnApplyTemplate del modello. Il metodo GetTemplateChild deve pertanto essere chiamato da un override di OnApplyTemplate nel controllo basato su modelli o nella pagina basata su modelli.

Importante

Il metodo GetTemplateChild deve essere chiamato solo dopo la chiamata del metodo OnApplyTemplate.

Il codice XAML seguente mostra un modello di controllo denominato TealTemplate che può essere applicato alle pagine derivate da ContentPage:

<ControlTemplate x:Key="TealTemplate">
    <Grid>
        ...
        <Label x:Name="changeThemeLabel"
               Text="Change Theme"
               ...>
            <Label.GestureRecognizers>
                <TapGestureRecognizer Tapped="OnChangeThemeLabelTapped" />
            </Label.GestureRecognizers>
        </Label>
        ...
    </Grid>
</ControlTemplate>

In questo esempio, l'elemento Label è denominato e può essere recuperato nel codice per la pagina basata su modelli. Si ottiene questo risultato chiamando il metodo GetTemplateChild dall'override di OnApplyTemplate per la pagina basata su modelli:

public partial class AccessTemplateElementPage : HeaderFooterPage
{
    Label themeLabel;

    public AccessTemplateElementPage()
    {
        InitializeComponent();
    }

    protected override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        themeLabel = (Label)GetTemplateChild("changeThemeLabel");
        themeLabel.Text = OriginalTemplate ? "Aqua Theme" : "Teal Theme";
    }
}

In questo esempio, l'oggetto Label denominato changeThemeLabel viene recuperato dopo la creazione dell'istanza del ControlTemplate. La classe AccessTemplateElementPage può quindi accedere e modificare changeThemeLabel. Lo screenshot seguente mostra che il testo visualizzato da Label è stato modificato:

Screenshot dell'oggetto pagina basato su modelli modificato.

Associare a un ViewModel

Un ControlTemplate può eseguire l'associazione dati a un ViewModel, anche quando il ControlTemplate viene associato all'elemento padre basato su modelli (l'istanza dell'oggetto di runtime a cui è applicato il modello).

Nell'esempio di codice XAML riportato di seguito viene illustrata una pagina che utilizza un elemento ViewModel denominato PeopleViewModel:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:ControlTemplateDemos"
             xmlns:controls="clr-namespace:ControlTemplateDemos.Controls"
             ...>
    <ContentPage.BindingContext>
        <local:PeopleViewModel />
    </ContentPage.BindingContext>

    <ContentPage.Resources>
        <DataTemplate x:Key="PersonTemplate">
            <controls:CardView BorderColor="DarkGray"
                               CardTitle="{Binding Name}"
                               CardDescription="{Binding Description}"
                               ControlTemplate="{StaticResource CardViewControlTemplate}" />
        </DataTemplate>
    </ContentPage.Resources>

    <StackLayout Margin="10"
                 BindableLayout.ItemsSource="{Binding People}"
                 BindableLayout.ItemTemplate="{StaticResource PersonTemplate}" />
</ContentPage>

In questo esempio, il BindingContext della pagina è impostato su un'istanza di PeopleViewModel. Questo ViewModel espone una raccolta di People e un ICommand denominato DeletePersonCommand. Il StackLayout nella pagina usa un layout associabile per l'associazione ai dati alla raccolta People e l'ItemTemplate del layout associabile viene impostato sulla risorsa PersonTemplate. Questo DataTemplate specifica che ogni elemento nella raccolta People verrà visualizzato usando un oggetto CardView. La struttura visiva dell'oggetto CardView viene definita usando un ControlTemplate denominato CardViewControlTemplate:

<ControlTemplate x:Key="CardViewControlTemplate">
    <Frame BindingContext="{Binding Source={RelativeSource TemplatedParent}}"
           BackgroundColor="{Binding CardColor}"
           BorderColor="{Binding BorderColor}"
           ...>
        <!-- UI objects that define the CardView visual structure -->           
    </Frame>
</ControlTemplate>

In questo esempio, l'elemento radice del ControlTemplate è un oggetto Frame. L'oggetto Frame usa l'estensione di markup RelativeSource per impostare il relativo BindingContext sull'elemento padre basato su modelli. Le espressioni di associazione dell'oggetto Frame e dei relativi figli vengono risolte in base alle proprietà di CardView, dato che BindingContext viene ereditato dall'elemento Frame radice. Lo screenshot seguente mostra la pagina che mostra la People raccolta:

Screenshot di tre oggetti CardView basati su modelli associati a un modello di visualizzazione.

Mentre gli oggetti nl ControlTemplate sono associati alle proprietà nel relativo elemento padre basato su modelli, il Button all'interno del modello di controllo è associato sia al relativo elemento padre basato su modelli sia al DeletePersonCommand nell'elemento ViewModel. Ciò è dovuto al fatto che la proprietà Button.Command ridefinisce l'origine di associazione come contesto di associazione del predecessore il cui tipo di contesto di associazione è PeopleViewModel, ovvero StackLayout. La parte Path delle espressioni di associazione può quindi risolvere la proprietà DeletePersonCommand. Tuttavia, la proprietà Button.CommandParameter non modifica l'origine di associazione, ma eredita dal relativo elemento padre nel ControlTemplate. Pertanto, la proprietà CommandParameter viene associata alla proprietà CardTitle di CardView.

L'effetto complessivo delle associazioni di Button è che, quando viene toccato il controllo Button, viene eseguito il DeletePersonCommand nella classe PeopleViewModel e il valore della proprietà CardName viene passato a DeletePersonCommand. Ciò comporta la rimozione dell'oggetto specificato CardView dal layout associabile.

Per altre informazioni sulle associazioni relative, vedere Associazioni relative.