Xamarin.Forms-Steuerelementvorlagen

Download Sample Das Beispiel herunterladen

Mit Xamarin.Forms-Steuerelementvorlagen können Sie die visuelle Struktur der von ContentView abgeleiteten benutzerdefinierten Steuerelemente und der von ContentPage abgeleiteten Seiten definieren. Steuerelementvorlagen trennen die Benutzeroberfläche (UI) für ein benutzerdefiniertes Steuerelement oder eine Seite von der Logik, die das Steuerelement oder die Seite implementiert. Zusätzliche Inhalte können auch an einer vordefinierten Stelle in benutzerdefinierten Steuerelementen mit Vorlagen oder Seiten mit Vorlagen eingefügt werden.

Beispielsweise können Sie eine Steuerelementvorlage erstellen, die die Benutzeroberfläche eines benutzerdefinierten Steuerelements neu definiert. Die Steuerelementvorlage kann anschließend von der erforderlichen Instanz des benutzerdefinierten Steuerelements genutzt werden. Alternativ können Sie eine benutzerdefinierte Steuerelementvorlage erstellen, die eine allgemeine Benutzeroberfläche definiert, die für mehrere Seiten einer Anwendung verwendet wird. Die Steuerelementvorlage kann anschließend von mehreren Seiten genutzt werden, wobei jede Seite ihren eindeutigen Inhalt anzeigt.

Erstellen einer ControlTemplate

Im folgenden Beispiel wird der Code für ein benutzerdefiniertes CardView-Steuerelement veranschaulicht:

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);
    }
    // ...
}

Die CardView-Klasse, die von der ContentView-Klasse abgeleitet wird, stellt ein benutzerdefiniertes Steuerelement dar, das Daten in einem Karten ähnlichen Layout anzeigt. Die Klasse enthält Eigenschaften, die von bindbaren Eigenschaften unterstützt werden, für die angezeigten Daten. Die CardView-Klasse definiert jedoch keine Benutzeroberfläche. Stattdessen wird die Benutzeroberfläche mithilfe einer Steuerelementvorlage definiert. Weitere Informationen zum Erstellen der von ContentView abgeleiteten benutzerdefinierten Steuerelemente finden Sie unter Xamarin.Forms-ContentView.

Eine Steuerelementvorlage wird mit dem Typ ControlTemplate erstellt. Wenn Sie eine ControlTemplate-Klasse erstellen, kombinieren Sie View-Objekte, um die Benutzeroberfläche für ein benutzerdefiniertes Steuerelement oder eine Seite zu erstellen. Eine ControlTemplate-Klasse darf nur ein View als Stammelement aufweisen. In der Regel enthält das Stammelement jedoch andere View-Objekte. Die visuelle Struktur wird durch die Kombination dieser Objekte bestimmt.

Zwar kann eine ControlTemplate-Klasse inline definiert werden, jedoch besteht der übliche Ansatz zum Deklarieren einer ControlTemplate-Klasse darin, sie als Ressource in einem Ressourcenverzeichnis zu deklarieren. Da es sich bei Steuerelementvorlagen um Ressourcen handelt, unterliegen sie denselben Bereichsregeln, die für alle Ressourcen gelten. Wenn Sie beispielsweise eine Steuerelementvorlage im Stammelement der XAML-Anwendungsdefinitionsdatei deklarieren, kann die Vorlage überall in Ihrer Anwendung verwendet werden. Wenn Sie die Vorlage in einer Seite definieren, kann die Steuerelementvorlage nur auf dieser Seite verwendet werden. Weitere Informationen zu Ressourcen finden Sie unter Ressourcenverzeichnisse in Xamarin.Forms.

Im folgenden XAML-Beispiel eine ControlTemplate-Klasse für CardView-Objekte veranschaulicht:

<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>

Wenn eine ControlTemplate-Klasse als Ressource deklariert wird, muss sie einen mit dem x:Key-Attribut festgelegten Schlüssel enthalten, damit sie im Ressourcenverzeichnis identifiziert werden kann. In diesem Beispiel handelt es sich beim Stammelement von CardViewControlTemplate um ein Frame-Objekt. Das Frame-Objekt nutzt die Markuperweiterung RelativeSource, um BindingContext auf die Instanz des Laufzeitobjekts festzulegen, auf die die Vorlage angewendet wird. Diese wird auch als übergeordnetes Element mit Vorlagen bezeichnet. Das Frame-Objekt enthält eine Kombination aus Grid-, Frame-, Image-, Label- und BoxView-Objekten, die die visuelle Struktur eines CardView-Objekts definieren. Die Bindungsausdrücke dieser Objekte werden mit CardView-Eigenschaften aufgelöst, weil sie BindingContext vom Frame-Stammelement erben. Weitere Informationen zur Markuperweiterung RelativeSource finden Sie unter Relative Bindungen in Xamarin.Forms.

Verwenden von ControlTemplate

Ein ControlTemplate-Objekt kann auf ein von ContentViewabgeleitetes benutzerdefiniertes Steuerelement angewendet werden, indem das Steuerelementvorlagenobjekt als ControlTemplate-Eigenschaft festgelegt wird. Entsprechend kann ein ControlTemplate-Objekt auf eine von ContentPage abgeleitete Seite angewendet werden, indem das Steuerelementvorlagenobjekt als ControlTemplate-Eigenschaft festgelegt wird. Wenn zur Laufzeit ein ControlTemplate-Objekt angewendet wird, werden alle in ControlTemplate definierten Steuerelemente zur visuellen Struktur des benutzerdefinierten Steuerelements mit Vorlagen oder der Seite mit Vorlagen hinzugefügt.

Im folgenden Beispiel wird veranschaulicht, wie CardViewControlTemplate zur ControlTemplate-Eigenschaft aller CardView-Objekte zugewiesen wird:

<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 diesem Beispiel werden die Steuerelemente in CardViewControlTemplate Teil der visuellen Struktur aller CardView-Objekte. Da das Stammobjekt Frame für die Steuerelementvorlage das übergeordnete Element mit Vorlagen als BindingContext festlegt, werden die Bindungsausdrücke von Frame und dessen untergeordnete Elemente anhand der Eigenschaften der einzelnen CardView-Objekte aufgelöst.

In den folgenden Screenshots werden die CardViewControlTemplate-Elemente veranschaulicht, die auf die drei CardView-Objekte angewendet wurden:

Screenshots of templated CardView objects, on iOS and Android

Wichtig

Der Zeitpunkt, zu dem ein ControlTemplate auf eine Steuerelementinstanz angewendet wird, kann ermittelt werden, indem die OnApplyTemplate-Methode im benutzerdefinierten Steuerelement mit Vorlagen oder in der Seite mit Vorlagen überschrieben wird. Weitere Informationen finden Sie unter Abrufen eines benannten Elements aus einer Vorlage.

Übergeben von Parametern mit TemplateBinding

Die Markuperweiterung TemplateBinding bindet eine Eigenschaft eines in einem ControlTemplate-Objekt enthaltenen Elements an eine vom benutzerdefinierten Steuerelement mit Vorlagen oder der Seite mit Vorlagen definierte öffentliche Eigenschaft. Wenn Sie die Markuperweiterung TemplateBinding verwenden, können Eigenschaften des Steuerelements als Parameter der Vorlage fungieren. Wenn eine Eigenschaft für ein benutzerdefiniertes Steuerelement mit Vorlagen oder eine Seite mit Vorlagen festgelegt wird, wird daher dieser Wert an das Element übergeben, das TemplateBinding enthält.

Wichtig

Die Markuperweiterung TemplateBinding ermöglicht, dass die RelativeSource-Bindung aus der vorherigen Steuerungsvorlage entfernt werden kann, und ersetzt die Binding-Ausdrücke.

Die Markuperweiterung TemplateBinding definiert die folgenden Eigenschaften:

  • Die Eigenschaft Path vom Typ string, die den Pfad zur Eigenschaft definiert.
  • Die Eigenschaft Mode vom Typ BindingMode, die die Richtung für die Weitergabe von Änderungen zwischen der Quelle und dem Ziel definiert.
  • Die Eigenschaft Converter vom Typ IValueConverter, die den Bindungswertkonverter definiert.
  • Die Eigenschaft ConverterParameter vom Typ object, die den Parameter für den Bindungswertkonverter definiert.
  • Die Eigenschaft StringFormat vom Typ string, die das Zeichenfolgenformat für die Bindung definiert.

Die ContentProperty-Eigenschaft für die Markuperweiterung TemplateBinding ist Path. Daher kann der Abschnitt „Path=“ der Markuperweiterung ausgelassen werden, wenn der Pfad das erste Element im Ausdruck TemplateBinding ist. Weitere Informationen zur Verwendung dieser Eigenschaften in einem Bindungsausdruck finden Sie unter Xamarin.Forms-Datenbindung.

Warnung

Die Markuperweiterung TemplateBinding sollte nur in ControlTemplate verwendet werden. Wenn Sie jedoch versuchen, einen TemplateBinding-Ausdruck außerhalb von ControlTemplate zu verwenden, führt dies weder zu einem Buildfehler noch zu einer Ausnahme.

Im folgenden XAML-Beispiel wird ein ControlTemplate für CardView-Objekte veranschaulicht, das die Markuperweiterung TemplateBinding verwendet:

<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 diesem Beispiel löst die Markuperweiterung TemplateBinding Bindungsausdrücke für die Eigenschaften der CardView-Objekte auf. In den folgenden Screenshots werden die CardViewControlTemplate-Elemente veranschaulicht, die auf die drei CardView-Objekte angewendet wurden:

Screenshots of templated CardView objects

Wichtig

Die Verwendung der Markuperweiterung TemplateBinding gleicht dem Festlegen des übergeordneten Elements mit Vorlagen für die BindingContext-Eigenschaft des Stammelements in der Vorlage mithilfe der Markuperweiterung RelativeSource und dem anschließenden Auflösen von Bindungen von untergeordneten Objekten mit der Markuperweiterung Binding. Durch die Markuperweiterung TemplateBinding wird ein Binding erstellt, dessen SourceRelativeBindingSource.TemplatedParent ist.

Anwenden von ControlTemplate mit einer Formatvorlage

Steuerelementvorlagen können auch mit Formatvorlagen angewendet werden. Hierzu wird eine implizite oder explizite Formatvorlage erstellt, die das ControlTemplate-Objekt verwendet.

Im folgenden XAML-Beispiel wird eine implizite Formatvorlage veranschaulicht, die CardViewControlTemplate verwendet:

<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 diesem Beispiel wird implizitStyle automatisch auf jedes CardView-Objekt angewandt und setzt die ControlTemplate-Eigenschaft jedes CardView auf CardViewControlTemplate.

Weitere Informationen zu Formatvorlagen finden Sie unter Xamarin.Forms-Formatvorlagen.

Neudefinieren der Benutzeroberfläche eines Steuerelements

Wenn ein ControlTemplate-Objekt instanziiert und der ControlTemplate-Eigenschaft eines von ContentView abgeleiteten benutzerdefinierten Steuerelements oder einer von ContentPage abgeleiteten Seite zugewiesen wird, wird die vom benutzerdefinierten Steuerelement oder der Seite definierte visuelle Struktur durch die in ControlTemplate definierte visuelle Struktur ersetzt.

Das benutzerdefinierte Steuerelement CardViewUI definiert die Benutzeroberfläche beispielsweise mithilfe des folgenden XAML-Codes:

<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>

Die Steuerelemente, aus der sich diese Benutzeroberfläche zusammensetzt, können jedoch ersetzt werden, indem eine neue visuelle Struktur in einer ControlTemplate-Klasse definiert wird, die dann der ControlTemplate-Eigenschaft eines CardViewUI-Objekts zugewiesen wird:

<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 diesem Beispiel wird die visuelle Struktur des CardViewUI-Objekts in einer ControlTemplate-Klasse neu definiert, die eine kompaktere visuelle Struktur bereitstellt, die sich für eine komprimierte Liste eignet:

Screenshots of templated CardViewUI objects, on iOS and Android

Ersetzen von Inhalten in ContentPresenter

Eine ContentPresenter-Klasse kann in einer Steuerelementvorlage platziert werden, um zu markieren, wo Inhalt vom benutzerdefinierten Steuerelement mit Vorlagen oder der Seite mit Vorlagen angezeigt werden soll. Das benutzerdefinierte Steuerelement oder die Seite, die die Steuerelementvorlage nutzen, definieren dann den Inhalt, der von ContentPresenter angezeigt werden soll. Das folgende Diagramm veranschaulicht eine ControlTemplate-Klasse für eine Seite mit einer Reihe von Steuerelementen. Zusätzlich abgebildet ist eine ContentPresenter-Klasse, hier dargestellt durch ein blaues Rechteck:

Control Template for a ContentPage

Im folgenden XAML-Code wird eine Steuerelementvorlage namens TealTemplate veranschaulicht, die eine ContentPresenter-Klasse in der visuellen Struktur enthält:

<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>

Im folgenden Beispiel wird veranschaulicht, wie TealTemplate zur ControlTemplate-Eigenschaft einer von ContentPage abgeleiteten Seite zugewiesen wird:

<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>

Wenn TealTemplate zur Laufzeit auf die Seite angewendet wird, wird der Seiteninhalt in das ContentPresenter-Element eingefügt, das in der Steuerelementvorlage definiert wird:

Screenshots of templated page object, on iOS and Android

Abrufen eines benannten Elements aus einer Vorlage

Benannte Elemente innerhalb einer Steuerelementvorlage können aus dem benutzerdefinierten Steuerelement mit Vorlagen oder der Seite mit Vorlagen abgerufen werden. Hierzu dient die GetTemplateChild-Methode, die das benannte Element in der instanziierten visuellen ControlTemplate-Struktur zurückgibt, wenn es gefunden wird. Andernfalls wird nullzurückgegeben.

Nachdem eine Steuerelementvorlage instanziiert wurde, wird die OnApplyTemplate-Methode der Vorlage aufgerufen. Die GetTemplateChild-Methode sollte daher über eine OnApplyTemplate-Außerkraftsetzung im Steuerelement mit Vorlagen oder der Seite mit Vorlagen aufgerufen werden.

Wichtig

Die GetTemplateChild-Methode sollte nur nach dem Aufruf der OnApplyTemplate-Methode aufgerufen werden.

Im folgenden XAML-Code wird eine Steuerelementvorlage namens TealTemplate veranschaulicht, die auf von ContentPage abgeleitete Seiten angewendet werden kann:

<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>

Im folgenden Beispiel wird das Label-Element benannt, das im Code für die Seite mit Vorlagen abgerufen werden kann. Hierzu wird die GetTemplateChild-Methode der OnApplyTemplate-Außerkraftsetzung für die Seite mit Vorlagen aufgerufen:

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 diesem Beispiel wurde das Label-Objekt namens changeThemeLabel abgerufen, sobald ControlTemplate instanziiert wurde. changeThemeLabel kann anschließend von der AccessTemplateElementPage-Klasse aufgerufen und bearbeitet werden. In den folgenden Screenshots wird gezeigt, dass der von Label angezeigte Text geändert wurde:

Screenshots of templated page object

Einbindung in ViewModel

Eine ControlTemplate-Klasse kann eine Datenbindung mit einem ViewModel-Element auch dann herstellen, wenn die ControlTemplate-Klasse in das übergeordnete Element mit Vorlagen eingebunden ist (die Laufzeitobjektinstanz, auf die die Vorlage angewendet wird).

Im folgenden XAML-Beispiel wird eine Seite veranschaulicht, die ein ViewModel-Element namens PeopleViewModel verwendet:

<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 diesem Beispiel wird eine PeopleViewModel-Instanz für die BindingContext-Eigenschaft der Seite festgelegt. Dieses ViewModel-Element stellt eine People-Sammlung und eine ICommand-Schnittstelle namens DeletePersonCommand zur Verfügung. Die StackLayout-Klasse der Seite verwendet ein bindbares Layouts, um eine Datenbindung mit der People-Sammlung herzustellen. Die ItemTemplate-Eigenschaft des bindbaren Layouts wird auf die Ressource PersonTemplate festgelegt. Diese DataTemplate-Klasse legt fest, dass alle Elemente in der People-Sammlung mithilfe eines CardView-Objekts angezeigt werden. Die visuelle Struktur des CardView-Objekts wird mithilfe einer ControlTemplate-Klasse namens CardViewControlTemplate definiert:

<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 diesem Beispiel handelt es sich beim Stammelement von ControlTemplate um ein Frame-Objekt. Das Objekt Frame verwendet die Markuperweiterung RelativeSource, um das übergeordnete Element mit Vorlagen für BindingContext festzulegen. Die Bindungsausdrücke des Frame-Objekts und dessen untergeordneten Elemente werden mit CardView-Eigenschaften aufgelöst, weil sie BindingContext vom Frame-Stammelement erben. In den folgenden Screenshots wird die Seite veranschaulicht, die die People-Sammlung anzeigt, die aus drei Elementen besteht:

Screenshots of three templated CardView objects

Während die Objekte in ControlTemplate Eigenschaften nur an das übergeordnete Element mit Vorlagen binden, wird die Button-Klasse in der Steuerelementvorlage sowohl an das übergeordnete Element mit Vorlagen als auch an die DeletePersonCommand-Eigenschaft im ViewModel-Element gebunden. Dies liegt daran, dass Eigenschaft Button.Command ihre Bindungsquelle als Bindungskontext des Vorgängers neu definiert, dessen Bindungskontexttyp PeopleViewModel ist. Dabei handelt es sich um die StackLayout-Klasse. Der Abschnitt Path der Bindungsausdrücke kann dann die DeletePersonCommand-Eigenschaft auflösen. Die Button.CommandParameter-Eigenschaft ändert jedoch nicht ihre Bindungsquelle, stattdessen erbt sie sie vom übergeordneten Element in der ControlTemplate-Klasse. Daher wird die CommandParameter-Eigenschaft an die CardTitle-Eigenschaft von CardView gebunden.

Der Gesamteffekt der Button-Bindungen besteht darin, dass DeletePersonCommand in der PeopleViewModel-Klasse ausgeführt wird, wenn auf das Button-Element getippt wird, wodurch der Wert der CardName-Eigenschaft an DeletePersonCommand übergeben wird. Dies führt wiederum dazu, dass das angegebene CardView-Element aus dem bindbaren Layout entfernt wird:

Screenshots of two templated CardView objects

Weitere Informationen zu relativen Bindungen finden Sie unter Relative Bindungen in Xamarin.Forms.