Modelos de controle

Procurar amostra. Procurar no exemplo

Os modelos de controle da interface do usuário de aplicativo multiplataforma do .NET (.NET MAUI) permitem definir a estrutura visual de controles personalizados derivados deContentView e páginas derivadas de ContentPage. Os modelos de controle separam a IU (interface do usuário) para uma página ou controle personalizado da lógica que implementa o controle ou a página. Também é possível inserir conteúdo adicional no controle modelo personalizado ou na página modelo, em um local predefinido.

Por exemplo, é possível criar um modelo de controle que redefine a interface do usuário fornecida por um controle personalizado. O modelo de controle pode então ser consumido pela instância de controle personalizado necessária. Como alternativa, um modelo de controle pode ser criado que define qualquer interface do usuário comum que será usada por várias páginas em um aplicativo. O modelo de controle pode, então, ser consumido por várias páginas, com cada página ainda exibindo o conteúdo exclusivo.

Criar um ControlTemplate

O exemplo a seguir mostra o código de um controle personalizado de 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);
    }
    ...
}

A classe CardView, que deriva da classe ContentView, representa um controle personalizado que exibe dados em um layout de tipo de cartão. A classe contém propriedades, que são apoiadas por propriedades associáveis, para os dados que ela exibe. No entanto, a classe CardView não define nenhuma interface do usuário. Em vez disso, a interface do usuário será definida com um modelo de controle. Para obter mais informações sobre como criar controles personalizados derivados de ContentView, consulte ContentView.

Um modelo de controle é criado com o tipo ControlTemplate. Ao criar um ControlTemplate, você combina objetos View para criar a interface do usuário para uma página ou controle personalizado. Um ControlTemplate deve ter apenas um View como seu elemento raiz. No entanto, o elemento raiz geralmente contém outros objetos View. A combinação de objetos compõe a estrutura visual do controle.

Embora um ControlTemplate possa ser definido em linha, a abordagem típica para declarar um ControlTemplate é como um recurso em um dicionário de recursos. Como os modelos de controle são recursos, eles obedecem às mesmas regras de escopo que se aplicam a todos os recursos. Por exemplo, se você declarar um modelo de controle no dicionário de recursos no nível do aplicativo, o modelo poderá ser usado em qualquer lugar do aplicativo. Se você definir o modelo em uma página, somente essa página poderá usar o modelo de controle. Para obter mais informações sobre recursos, consulte Dicionários de recursos.

O exemplo de XAML a seguir mostra um ControlTemplate para objetos 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 um ControlTemplate é declarado como um recurso, ele precisa ter uma chave especificada com o atributo x:Key para que ele possa ser identificado no dicionário de recursos. Neste exemplo, o elemento raiz do CardViewControlTemplate é um objeto Frame. O objeto Frame usa a extensão de marcação RelativeSource para definir o próprio BindingContext para a instância de objeto de runtime à qual o modelo será aplicado, que é conhecida como o pai modelo. O objeto Frame usa uma combinação de controles para definir a estrutura visual de um objeto CardView. As expressões de associação desses objetos são resolvidas em relação às propriedades CardView, devido à herança do BindingContext do elemento raiz Frame. Para obter mais informações sobre a extensão de marcação RelativeSource, consulte Associações relativas.

Consumir um ControlTemplate

Um ControlTemplate pode ser aplicado a um controle personalizado derivado de ContentView definindo a propriedade ControlTemplate dele para o objeto de modelo de controle. Semelhantemente, um ControlTemplate pode ser aplicado a uma página derivada de ContentPage definindo a propriedade ControlTemplate dele para o objeto de modelo de controle. No runtime, quando um ControlTemplate é aplicado, todos os controles definidos no ControlTemplate são adicionados à árvore visual do controle modelo personalizado ou da página modelo.

O exemplo a seguir mostra o CardViewControlTemplate atribuído à propriedade ControlTemplate de dois objetos CardView:

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

Neste exemplo, os controles no CardViewControlTemplate tornam-se parte da árvore visual para cada objeto CardView. Já que o objeto Frame raiz para o modelo de controle define o respectivo BindingContext para o pai modelo, o Frame e os filhos dele resolvem as respectivas expressões de associação em relação às propriedades de cada objeto CardView.

A captura de tela a seguir mostra a CardViewControlTemplate aplicada aos objetos CardView:

Captura de tela de dois objetos CardView com modelo.

Importante

O ponto no tempo em que um ControlTemplate é aplicado a uma instância de controle pode ser detectado pela substituição do método OnApplyTemplate no controle modelo personalizado ou na página modelada. Para obter mais informações, veja Obter um elemento nomeado de um modelo.

Passar parâmetros com TemplateBinding

A extensão de marcação TemplateBinding associa uma propriedade de um elemento que está no ControlTemplate a uma propriedade pública que é definida pelo controle modelo personalizado ou pela página modelo. Quando você usa um TemplateBinding, você habilita propriedades do controle para atuar como parâmetros para o modelo. Portanto, quando uma propriedade é definida em um controle modelo personalizado ou em uma página modelo, esse valor é passado para o elemento que contém o TemplateBinding.

Importante

A expressão de marcação TemplateBinding permite que a associação RelativeSource do modelo de controle anterior seja removida e substitui as expressões Binding.

A extensão de marcação TemplateBinding define as seguintes propriedades:

  • Path, do tipo string, o caminho para a propriedade.
  • Mode, do tipo BindingMode, a direção em que as alterações se propagam entre a origem e o destino.
  • Converter, do tipo IValueConverter, o conversor de valor de associação.
  • ConverterParameter, do tipo object, o parâmetro do conversor de valor de associação.
  • StringFormat, do tipo string, o formato da cadeia de caracteres para a associação.

O ContentProperty para a extensão de marcação TemplateBinding é Path. Portanto, a parte "Path=" da extensão de marcação poderá ser omitida se o caminho for o primeiro item na expressão TemplateBinding. Para obter mais informações sobre como usar essas propriedades em uma expressão de associação, consulte Vinculação de dados.

Aviso

A extensão de marcação TemplateBinding só deve ser usada dentro de um ControlTemplate. No entanto, tentar usar uma expressão TemplateBinding fora de um ControlTemplate não resultará em um erro de build nem na geração de uma exceção.

O XAML de exemplo a seguir mostra um ControlTemplate para objetos CardView, que usa a extensão de marcação 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>

Neste exemplo, a extensão de marcação TemplateBinding resolve expressões de associação em relação às propriedades de cada objeto CardView. A captura de tela a seguir mostra a CardViewControlTemplate aplicada aos objetos CardView:

Captura de tela de objetos CardView com modelo.

Importante

O uso da extensão de marcação TemplateBinding é equivalente a definir o BindingContext do elemento raiz no modelo para o respectivo pai modelo com a extensão de marcação RelativeSource e, em seguida, resolver associações de objetos filho com a extensão de marcação Binding. Na verdade, a extensão de marcação TemplateBinding cria um Binding cujo Source é RelativeBindingSource.TemplatedParent.

Aplicar um ControlTemplate com um estilo

Os modelos de controle também podem ser aplicados com estilos. Isso é alcançado pela criação de um estilo implícito ou explícito que consome o ControlTemplate.

O seguinte exemplo de XAML mostra um estilo implícito que consome o 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>

Neste exemplo, o Style implícito é aplicado automaticamente a cada objeto CardView e define a propriedade ControlTemplate de cada CardView para CardViewControlTemplate.

Para mais informações sobre estilos, confira Estilos.

Redefinir a interface do usuário de um controle

Quando um ControlTemplate é instanciado e atribuído à propriedade ControlTemplate de um controle personalizado derivado de ContentView ou uma página derivada de ContentPage, a estrutura visual definida para a página ou controle personalizado é substituída pela estrutura visual definida no ControlTemplate.

Por exemplo, o controle personalizado CardViewUI define a própria interface do usuário usando o seguinte XAML:

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

No entanto, os controles que compõem essa interface do usuário podem ser substituídos definindo uma nova estrutura visual em um ControlTemplate e atribuindo-o à propriedade ControlTemplate de um objeto 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>

Neste exemplo, a estrutura visual do objeto CardViewUI é redefinida em um ControlTemplate que fornece uma estrutura visual mais compacta, que é adequada para uma lista condensada:

Captura de tela de objetos CardViewUI com modelo.

Substituir o conteúdo em um ContentPresenter

Um ContentPresenter pode ser colocado em um modelo de controle para marcar o local em que o conteúdo a ser exibido pela página modelo ou controle modelo personalizado será exibido. A página ou controle personalizado ou a página que consome o modelo de controle definirá o conteúdo a ser exibido pelo ContentPresenter. O diagrama a seguir ilustra um ControlTemplate para uma página que contém um número de controles, incluindo um ContentPresenter marcado por um retângulo azul:

Modelo de controle para um ContentPage.

O XAML a seguir mostra um modelo de controle chamado TealTemplate que contém um ContentPresenter na própria estrutura visual:

<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://video2.skills-academy.com/dotnet/maui/"
                                 ... />
    </Grid>
</ControlTemplate>

O exemplo a seguir mostra o TealTemplate atribuído à propriedade ControlTemplate de uma página derivada de 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>

No runtime, quando TealTemplate é aplicado à página, o conteúdo da página é substituído no ContentPresenter definido no modelo de controle:

Captura de tela de objeto de página com modelo.

obter um elemento nomeado de um modelo

Elementos nomeados dentro de um modelo de controle podem ser recuperados do controle modelo personalizado ou da página modelo. Isso pode ser obtido com o método GetTemplateChild, que retorna o elemento nomeado na árvore visual ControlTemplate instanciada, se encontrado. Caso contrário, ele retornará null.

Depois que um modelo de controle foi instanciado, o método OnApplyTemplate do modelo é chamado. O método GetTemplateChild, portanto, deve ser chamado de uma substituição de OnApplyTemplate no controle modelo ou na página modelo.

Importante

O método GetTemplateChild só deve ser chamado após o método OnApplyTemplate ser chamado.

O XAML a seguir mostra um modelo de controle chamado TealTemplate que pode ser aplicado a páginas derivadas de ContentPage:

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

Neste exemplo, o elemento Label é nomeado e pode ser recuperado no código para a página modelo. Isso é feito chamando o método GetTemplateChild da substituição OnApplyTemplate para a página modelo:

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";
    }
}

Neste exemplo, o objeto Label chamado changeThemeLabel será recuperado depois que o ControlTemplate tiver sido instanciado. Então changeThemeLabel pode ser acessado e manipulado pela classe AccessTemplateElementPage. A captura de tela a seguir mostra que o texto exibido pelo Label foi alterado:

Captura de tela de objeto de página com modelo que foi alterada.

Associar a um ViewModel

Um ControlTemplate pode associar dados a um ViewModel, mesmo quando o ControlTemplate é associado ao pai modelo (a instância do objeto de runtime à qual o modelo é aplicado).

O seguinte exemplo de XAML mostra uma página que consome um ViewModel chamado 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>

Neste exemplo, o BindingContext da página é definido para uma instância de PeopleViewModel. Esse ViewModel expõe uma coleção de People e um ICommand chamado DeletePersonCommand. O StackLayout na página usa um layout associável para associar dados à coleção de People e o ItemTemplate do layout associável é definido como o recurso PersonTemplate. Este DataTemplate especifica que cada item na coleção People será exibido usando um objeto CardView. A estrutura visual do objeto CardView é definida usando um ControlTemplate chamado 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>

Neste exemplo, o elemento raiz do ControlTemplate é um objeto Frame. O objeto Frame usa a extensão de marcação RelativeSource para definir o próprio BindingContext como o pai modelo. As expressões de associação do objeto Frame e os respectivos filhos são resolvidas em relação às propriedades CardView, devido à herança do BindingContext do elemento raiz Frame. A captura de tela a seguir mostra a página exibindo a coleção People:

Captura de tela de três objetos CardView com modelo que se associam a um viewmodel.

Embora os objetos no ControlTemplate se associem a propriedades no respectivo pai modelo, o Button dentro do modelo de controle se associa ao seu pai modelo e ao DeletePersonCommand no ViewModel. Isso ocorre porque a propriedade Button.Command redefine a própria origem de associação como o contexto de associação do ancestral cujo tipo de contexto de associação é PeopleViewModel, que é o StackLayout. A parte Path das expressões de associação pode então resolver a propriedade DeletePersonCommand. No entanto, a propriedade Button.CommandParameter não altera a própria fonte de associação. Em vez disso, ela herda do próprio pai no ControlTemplate. Portanto, a propriedade CommandParameter é associada à propriedade CardTitle do CardView.

O efeito geral das associações Button é que, quando o Button é tocado, o DeletePersonCommand na classe PeopleViewModel é executado e o valor da propriedade CardName é passado para o DeletePersonCommand. Isso faz com que o CardView especificado seja removido do layout associável.

Para obter mais informações sobre associações relativas, consulte Associações relativas.