Partilhar via


Como criar um modelo para um controle (WPF.NET)

Com o Windows Presentation Foundation (WPF), você pode personalizar a estrutura visual e o comportamento de um controle existente com seu próprio modelo reutilizável. Os modelos podem ser aplicados globalmente ao seu aplicativo, janelas e páginas ou diretamente aos controles. A maioria dos cenários que exigem que você crie um novo controle pode ser coberta criando um novo modelo para um controle existente.

Neste artigo, você explorará a criação de um novo ControlTemplate para o controle Button.

Quando criar um ControlTemplate

Os controles têm muitas propriedades, como Background, Foregrounde FontFamily. Essas propriedades controlam diferentes aspetos da aparência do controle, mas as alterações que você pode fazer definindo essas propriedades são limitadas. Por exemplo, você pode definir a propriedade Foreground como azul e FontStyle como itálico em um CheckBox. Quando você deseja personalizar a aparência do controle além do que a configuração de outras propriedades no controle pode fazer, você cria um ControlTemplate.

Na maioria das interfaces de usuário, um botão tem a mesma aparência geral: um retângulo com algum texto. Se você quisesse criar um botão arredondado, você poderia criar um novo controle que herda do botão ou recria a funcionalidade do botão. Além disso, o novo controle de usuário forneceria o visual circular.

Você pode evitar a criação de novos controles personalizando o layout visual de um controle existente. Com um botão arredondado, você cria um ControlTemplate com o layout visual desejado.

Por outro lado, se você precisar de um controle com novas funcionalidades, propriedades diferentes e novas configurações, criará um novo UserControl.

Pré-requisitos

Crie um novo aplicativo WPF e, em MainWindow.xaml (ou outra janela de sua escolha), defina as seguintes propriedades no elemento <Window>:

Propriedade Valor
Title Template Intro Sample
SizeToContent WidthAndHeight
MinWidth 250

Defina o conteúdo do elemento <Window> para o seguinte XAML:

<StackPanel Margin="10">
    <Label>Unstyled Button</Label>
    <Button>Button 1</Button>
    <Label>Rounded Button</Label>
    <Button>Button 2</Button>
</StackPanel>

No final, o arquivo de MainWindow.xaml deve ser semelhante ao seguinte:

<Window x:Class="IntroToStylingAndTemplating.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:IntroToStylingAndTemplating"
        mc:Ignorable="d"
        Title="Template Intro Sample" SizeToContent="WidthAndHeight" MinWidth="250">
    <StackPanel Margin="10">
        <Label>Unstyled Button</Label>
        <Button>Button 1</Button>
        <Label>Rounded Button</Label>
        <Button>Button 2</Button>
    </StackPanel>
</Window>

Se você executar o aplicativo, ele terá a seguinte aparência:

janela WPF com dois botões sem estilo

Criar um ControlTemplate

A maneira mais comum de declarar um ControlTemplate é como um recurso na seção Resources em um arquivo XAML. Como os modelos são recursos, eles obedecem às mesmas regras de escopo que se aplicam a todos os recursos. Simplificando, onde você declara um modelo afeta onde o modelo pode ser aplicado. Por exemplo, se você declarar o modelo no elemento raiz do arquivo XAML de definição de aplicativo, o modelo poderá ser usado em qualquer lugar do aplicativo. Se você definir o modelo em uma janela, somente os controles nessa janela poderão usá-lo.

Para começar, adicione um elemento ao seu arquivo de MainWindow.xaml :

<Window x:Class="IntroToStylingAndTemplating.Window2"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:IntroToStylingAndTemplating"
        mc:Ignorable="d"
        Title="Template Intro Sample" SizeToContent="WidthAndHeight" MinWidth="250">
    <Window.Resources>
        
    </Window.Resources>
    <StackPanel Margin="10">
        <Label>Unstyled Button</Label>
        <Button>Button 1</Button>
        <Label>Rounded Button</Label>
        <Button>Button 2</Button>
    </StackPanel>
</Window>

Crie um novo <ControlTemplate> com as seguintes propriedades definidas:

Propriedade Valor
x:Key roundbutton
TargetType Button

Este modelo de controle será simples:

  • um elemento raiz para o controle, um Grid
  • um Ellipse para desenhar a aparência arredondada do botão
  • um ContentPresenter para exibir o conteúdo do botão especificado pelo usuário
<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <Ellipse Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />
        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</ControlTemplate>

TemplateBinding

Ao criar um novo ControlTemplate, você ainda pode querer usar as propriedades públicas para alterar a aparência do controle. A extensão de marcação TemplateBinding vincula uma propriedade de um elemento que está no ControlTemplate a uma propriedade pública definida pelo controlo. Quando você usa um TemplateBinding, você habilita as propriedades no controle para agir como parâmetros para o modelo. Ou seja, quando uma propriedade em um controle é definida, esse valor é passado para o elemento que tem o TemplateBinding nele.

Elipse

Observe que as propriedades Fill e Stroke do elemento <Ellipse> estão vinculadas às propriedades Foreground e Background do controle.

ContentPresenter

É também adicionado ao modelo um elemento ContentPresenter <>. Como este modelo foi projetado para um botão, leve em consideração que o botão herda de ContentControl. O botão apresenta o conteúdo do elemento . Você pode definir qualquer coisa dentro do botão, como texto sem formatação ou até mesmo outro controle. Ambos os botões a seguir são válidos:

<Button>My Text</Button>

<!-- and -->

<Button>
    <CheckBox>Checkbox in a button</CheckBox>
</Button>

Em ambos os exemplos anteriores, o texto e a caixa de seleção são definidos como a propriedade Button.Content. O que quer que seja definido como conteúdo pode ser apresentado por meio de um <ContentPresenter>, que é exatamente o que o modelo faz.

Se o ControlTemplate for aplicado a um tipo de ContentControl, como um Button, um ContentPresenter será pesquisado na árvore de elementos. Se o ContentPresenter for encontrado, o modelo vinculará automaticamente a propriedade Content do controle ao ContentPresenter.

Use o modelo

Encontre os botões que foram declarados no início deste artigo.

<StackPanel Margin="10">
    <Label>Unstyled Button</Label>
    <Button>Button 1</Button>
    <Label>Rounded Button</Label>
    <Button>Button 2</Button>
</StackPanel>

Defina a propriedade Template do segundo botão para o recurso roundbutton:

<StackPanel Margin="10">
    <Label>Unstyled Button</Label>
    <Button>Button 1</Button>
    <Label>Rounded Button</Label>
    <Button Template="{StaticResource roundbutton}">Button 2</Button>
</StackPanel>

Se você executar o projeto e olhar para o resultado, verá que o botão tem um plano de fundo arredondado.

janela WPF com um botão oval de modelo

Você já deve ter notado que o botão não é um círculo, mas está inclinado. Devido à forma como o elemento <Ellipse> funciona, ele sempre se expande para preencher o espaço disponível. Uniformize o círculo alterando as propriedades width e height do botão para o mesmo valor:

<StackPanel Margin="10">
    <Label>Unstyled Button</Label>
    <Button>Button 1</Button>
    <Label>Rounded Button</Label>
    <Button Template="{StaticResource roundbutton}" Width="65" Height="65">Button 2</Button>
</StackPanel>

janela WPF com um botão circular modelo

Adicionar um gatilho

Mesmo que um botão com um modelo aplicado pareça diferente, ele se comporta da mesma forma que qualquer outro botão. Se você pressionar o botão, o evento Click será acionado. No entanto, você deve ter notado que, quando você move o mouse sobre o botão, os visuais do botão não mudam. Essas interações visuais são todas definidas pelo modelo.

Com os sistemas dinâmicos de eventos e propriedades que o WPF fornece, você pode observar uma propriedade específica para um valor e, em seguida, reestilizar o modelo quando apropriado. Neste exemplo, você observará a propriedade IsMouseOver do botão. Quando o rato estiver sobre o controlo, aplique um estilo à Elipse <> com uma nova cor. Esse tipo de gatilho é conhecido como PropertyTrigger.

Para que isso funcione, você precisará adicionar um nome ao <Ellipse> que você pode referenciar. Dê-lhe o nome de backgroundElement.

<Ellipse x:Name="backgroundElement" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />

Em seguida, adicione um novo à coleção ControlTemplate.Triggers . O desencadeador observará o evento IsMouseOver para o valor true.

<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <Ellipse x:Name="backgroundElement" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />
        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
    <ControlTemplate.Triggers>
        <Trigger Property="IsMouseOver" Value="true">

        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

Em seguida, adicione um Setter de ao Gatilho de que altera a propriedade Preenchimento de do de Elipse para uma nova cor.

<Trigger Property="IsMouseOver" Value="true">
    <Setter Property="Fill" TargetName="backgroundElement" Value="AliceBlue"/>
</Trigger>

Execute o projeto. Observe que quando você move o mouse sobre o botão, a cor do <Ellipse> muda.

movimento do rato sobre o botão WPF para alterar a cor de preenchimento

Usar um VisualState

Os estados visuais são definidos e acionados por um controle. Por exemplo, quando o mouse é movido sobre o controle, o estado CommonStates.MouseOver é acionado. Você pode animar alterações de propriedade com base no estado atual do controle. PropertyTrigger <> foi usado na seção anterior para alterar o plano de fundo do botão para AliceBlue quando a propriedade IsMouseOver foi true. Em vez disso, crie um estado visual que anime a mudança dessa cor, proporcionando uma transição suave. Para obter mais informações sobre VisualStates, consulte Estilos e modelos no WPF.

Para converter o PropertyTrigger em um estado visual animado, Primeiro, remova o elemento ControlTemplate.Triggers do seu modelo.

<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <Ellipse x:Name="backgroundElement" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />
        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</ControlTemplate>

Em seguida, na raiz de <Grade> do modelo de controle, adicione um <VisualStateManager.VisualStateGroups> elemento com um <VisualStateGroup> para CommonStates. Defina dois estados, Normal e MouseOver.

<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup Name="CommonStates">
                <VisualState Name="Normal">
                </VisualState>
                <VisualState Name="MouseOver">
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Ellipse x:Name="backgroundElement" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />
        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</ControlTemplate>

Todas as animações definidas num <VisualState> são aplicadas quando esse estado é acionado. Crie animações para cada estado. As animações são colocadas dentro de um elemento <> Storyboard. Para obter mais informações sobre storyboards, consulte Visão geral do Storyboards.

  • Normal

    Esse estado anima o preenchimento da elipse, restaurando-o para a cor Background do controlo.

    <Storyboard>
        <ColorAnimation Storyboard.TargetName="backgroundElement" 
            Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
            To="{TemplateBinding Background}"
            Duration="0:0:0.3"/>
    </Storyboard>
    
  • passar o cursor

    Este estado anima a elipse Background cor para uma nova cor: Yellow.

    <Storyboard>
        <ColorAnimation Storyboard.TargetName="backgroundElement" 
            Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" 
            To="Yellow" 
            Duration="0:0:0.3"/>
    </Storyboard>
    

O <ControlTemplate> agora deve ter a seguinte aparência.

<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup Name="CommonStates">
                <VisualState Name="Normal">
                    <Storyboard>
                        <ColorAnimation Storyboard.TargetName="backgroundElement" 
                            Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                            To="{TemplateBinding Background}"
                            Duration="0:0:0.3"/>
                    </Storyboard>
                </VisualState>
                <VisualState Name="MouseOver">
                    <Storyboard>
                        <ColorAnimation Storyboard.TargetName="backgroundElement" 
                            Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" 
                            To="Yellow" 
                            Duration="0:0:0.3"/>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Ellipse Name="backgroundElement" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />
        <ContentPresenter x:Name="contentPresenter" HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</ControlTemplate>

Execute o projeto. Observe que quando você move o mouse sobre o botão, a cor do <Ellipse> anima.

mouse se move sobre o botão WPF para alterar a cor de preenchimento com um estado visual

Próximos passos