Condividi tramite


Xamarin.Forms modelli di controllo

Xamarin.FormsI modelli di controllo consentono di definire la struttura visiva dei controlli personalizzati derivati e ContentPage delle ContentView 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'applicazione. 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 Xamarin.Forms 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 si dichiara un modello di controllo nell'elemento radice del file XAML di definizione dell'applicazione, il modello può essere usato ovunque nell'applicazione. Se si definisce il modello in una pagina, solo tale pagina può usare il modello di controllo. Per altre informazioni sulle risorse, vedere Xamarin.Forms Dizionari risorse.

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

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             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}"
                 CornerRadius="5"
                 HasShadow="True"
                 Padding="8"
                 HorizontalOptions="Center"
                 VerticalOptions="Center">
              <Grid>
                  <Grid.RowDefinitions>
                      <RowDefinition Height="75" />
                      <RowDefinition Height="4" />
                      <RowDefinition Height="Auto" />
                  </Grid.RowDefinitions>
                  <Grid.ColumnDefinitions>
                      <ColumnDefinition Width="75" />
                      <ColumnDefinition Width="200" />
                  </Grid.ColumnDefinitions>
                  <Frame IsClippedToBounds="True"
                         BorderColor="{Binding BorderColor}"
                         BackgroundColor="{Binding IconBackgroundColor}"
                         CornerRadius="38"
                         HeightRequest="60"
                         WidthRequest="60"
                         HorizontalOptions="Center"
                         VerticalOptions="Center">
                      <Image Source="{Binding IconImageSource}"
                             Margin="-20"
                             WidthRequest="100"
                             HeightRequest="100"
                             Aspect="AspectFill" />
                  </Frame>
                  <Label Grid.Column="1"
                         Text="{Binding CardTitle}"
                         FontAttributes="Bold"
                         FontSize="Large"
                         VerticalTextAlignment="Center"
                         HorizontalTextAlignment="Start" />
                  <BoxView Grid.Row="1"
                           Grid.ColumnSpan="2"
                           BackgroundColor="{Binding BorderColor}"
                           HeightRequest="2"
                           HorizontalOptions="Fill" />
                  <Label Grid.Row="2"
                         Grid.ColumnSpan="2"
                         Text="{Binding CardDescription}"
                         VerticalTextAlignment="Start"
                         VerticalOptions="Fill"
                         HorizontalOptions="Fill" />
              </Grid>
          </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 usa una combinazione di oggetti Grid, Frame, Image, Label e BoxView per definire la struttura visiva di un oggetto CardView. 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 Xamarin.Forms Binding relativi.

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 il CardViewControlTemplate assegnato alla proprietà ControlTemplate di ogni oggetto CardView:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             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}" />
        <controls:CardView BorderColor="DarkGray"
                           CardTitle="Xamarin Monkey"
                           CardDescription="Aliquam sagittis, odio lacinia fermentum dictum, mi erat scelerisque erat, quis aliquet arcu."
                           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.

Gli screenshot seguenti mostrano il CardViewControlTemplate applicato ai tre oggetti CardView:

Screenshot degli oggetti CardView basati su modelli, in iOS e Android

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 Xamarin.Forms 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://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             ...>
    <ContentPage.Resources>
        <ControlTemplate x:Key="CardViewControlTemplate">
            <Frame BackgroundColor="{TemplateBinding CardColor}"
                   BorderColor="{TemplateBinding BorderColor}"
                   CornerRadius="5"
                   HasShadow="True"
                   Padding="8"
                   HorizontalOptions="Center"
                   VerticalOptions="Center">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="75" />
                        <RowDefinition Height="4" />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="75" />
                        <ColumnDefinition Width="200" />
                    </Grid.ColumnDefinitions>
                    <Frame IsClippedToBounds="True"
                           BorderColor="{TemplateBinding BorderColor}"
                           BackgroundColor="{TemplateBinding IconBackgroundColor}"
                           CornerRadius="38"
                           HeightRequest="60"
                           WidthRequest="60"
                           HorizontalOptions="Center"
                           VerticalOptions="Center">
                        <Image Source="{TemplateBinding IconImageSource}"
                               Margin="-20"
                               WidthRequest="100"
                               HeightRequest="100"
                               Aspect="AspectFill" />
                    </Frame>
                    <Label Grid.Column="1"
                           Text="{TemplateBinding CardTitle}"
                           FontAttributes="Bold"
                           FontSize="Large"
                           VerticalTextAlignment="Center"
                           HorizontalTextAlignment="Start" />
                    <BoxView Grid.Row="1"
                             Grid.ColumnSpan="2"
                             BackgroundColor="{TemplateBinding BorderColor}"
                             HeightRequest="2"
                             HorizontalOptions="Fill" />
                    <Label Grid.Row="2"
                           Grid.ColumnSpan="2"
                           Text="{TemplateBinding CardDescription}"
                           VerticalTextAlignment="Start"
                           VerticalOptions="Fill"
                           HorizontalOptions="Fill" />
                </Grid>
            </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. Gli screenshot seguenti mostrano il CardViewControlTemplate applicato ai tre oggetti CardView:

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://xamarin.com/schemas/2014/forms"
             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" />
        <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"/>
        <controls:CardView BorderColor="DarkGray"
                           CardTitle="Xamarin Monkey"
                           CardDescription="Aliquam sagittis, odio lacinia fermentum dictum, mi erat scelerisque erat, quis aliquet arcu."
                           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 Xamarin.Forms 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://xamarin.com/schemas/2014/forms"
             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}"
           CornerRadius="5"
           HasShadow="True"
           Padding="8"
           HorizontalOptions="Center"
           VerticalOptions="Center">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="75" />
                <RowDefinition Height="4" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="75" />
                <ColumnDefinition Width="200" />
            </Grid.ColumnDefinitions>
            <Frame IsClippedToBounds="True"
                   BorderColor="{Binding BorderColor, FallbackValue='Black'}"
                   BackgroundColor="{Binding IconBackgroundColor, FallbackValue='Gray'}"
                   CornerRadius="38"
                   HeightRequest="60"
                   WidthRequest="60"
                   HorizontalOptions="Center"
                   VerticalOptions="Center">
                <Image Source="{Binding IconImageSource}"
                       Margin="-20"
                       WidthRequest="100"
                       HeightRequest="100"
                       Aspect="AspectFill" />
            </Frame>
            <Label Grid.Column="1"
                   Text="{Binding CardTitle, FallbackValue='Card title'}"
                   FontAttributes="Bold"
                   FontSize="Large"
                   VerticalTextAlignment="Center"
                   HorizontalTextAlignment="Start" />
            <BoxView Grid.Row="1"
                     Grid.ColumnSpan="2"
                     BackgroundColor="{Binding BorderColor, FallbackValue='Black'}"
                     HeightRequest="2"
                     HorizontalOptions="Fill" />
            <Label Grid.Row="2"
                   Grid.ColumnSpan="2"
                   Text="{Binding CardDescription, FallbackValue='Card description'}"
                   VerticalTextAlignment="Start"
                   VerticalOptions="Fill"
                   HorizontalOptions="Fill" />
        </Grid>
    </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://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             ...>
    <ContentPage.Resources>
        <ControlTemplate x:Key="CardViewCompressed">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="100" />
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="100" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <Image Source="{TemplateBinding IconImageSource}"
                        BackgroundColor="{TemplateBinding IconBackgroundColor}"
                        WidthRequest="100"
                        HeightRequest="100"
                        Aspect="AspectFill"
                        HorizontalOptions="Center"
                        VerticalOptions="Center" />
                <StackLayout Grid.Column="1">
                    <Label Text="{TemplateBinding CardTitle}"
                           FontAttributes="Bold" />
                    <Label Text="{TemplateBinding CardDescription}" />
                </StackLayout>
            </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}" />
        <controls:CardViewUI 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 CardViewCompressed}" />
        <controls:CardViewUI BorderColor="DarkGray"
                             CardTitle="Xamarin Monkey"
                             CardDescription="Aliquam sagittis, odio lacinia fermentum dictum, mi erat scelerisque erat, quis aliquet arcu."
                             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, in iOS e Android

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>
        <Grid.RowDefinitions>
            <RowDefinition Height="0.1*" />
            <RowDefinition Height="0.8*" />
            <RowDefinition Height="0.1*" />
        </Grid.RowDefinitions>
        <BoxView Color="Teal" />
        <Label Margin="20,0,0,0"
               Text="{TemplateBinding HeaderText}"
               TextColor="White"
               FontSize="Title"
               VerticalOptions="Center" />
        <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"
               TextColor="White"
               HorizontalOptions="Start"
               VerticalOptions="Center">
            <Label.GestureRecognizers>
                <TapGestureRecognizer Tapped="OnChangeThemeLabelTapped" />
            </Label.GestureRecognizers>
        </Label>
        <controls:HyperlinkLabel Grid.Row="2"
                                 Margin="0,0,20,0"
                                 Text="Help"
                                 TextColor="White"
                                 Url="https://learn.microsoft.com/xamarin/xamarin-forms/"
                                 HorizontalOptions="End"
                                 VerticalOptions="Center" />
    </Grid>
</ControlTemplate>

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

<controls:HeaderFooterPage xmlns="http://xamarin.com/schemas/2014/forms"
                           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 basata su modelli, in iOS e Android

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"
               Grid.Row="2"
               Margin="20,0,0,0"
               Text="Change Theme"
               TextColor="White"
               HorizontalOptions="Start"
               VerticalOptions="Center">
            <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. Gli screenshot seguenti mostrano che il testo visualizzato da Label è stato modificato:

Screenshot dell'oggetto pagina basato su modelli

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://xamarin.com/schemas/2014/forms"
             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}"
           CornerRadius="5"
           HasShadow="True"
           Padding="8"
           HorizontalOptions="Center"
           VerticalOptions="Center">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="75" />
                <RowDefinition Height="4" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Label Text="{Binding CardTitle}"
                   FontAttributes="Bold"
                   FontSize="Large"
                   VerticalTextAlignment="Center"
                   HorizontalTextAlignment="Start" />
            <BoxView Grid.Row="1"
                     BackgroundColor="{Binding BorderColor}"
                     HeightRequest="2"
                     HorizontalOptions="Fill" />
            <Label Grid.Row="2"
                   Text="{Binding CardDescription}"
                   VerticalTextAlignment="Start"
                   VerticalOptions="Fill"
                   HorizontalOptions="Fill" />
            <Button Text="Delete"
                    Command="{Binding Source={RelativeSource AncestorType={x:Type local:PeopleViewModel}}, Path=DeletePersonCommand}"
                    CommandParameter="{Binding CardTitle}"
                    HorizontalOptions="End" />
        </Grid>
    </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. Gli screenshot seguenti mostrano la pagina che visualizza la raccolta People, costituita da tre elementi:

Screenshot di tre oggetti CardView basati su modelli

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 CardView specificato dal layout associabile:

Screenshot di due oggetti CardView basati su modelli

Per altre informazioni sulle associazioni relative, vedere Xamarin.Forms Associazioni relative.