Estilos e modelos no WPF

O estilo e os modelos do Windows Presentation Foundation (WPF) referem-se a um conjunto de recursos que permitem que desenvolvedores e designers criem efeitos visualmente atraentes e uma aparência consistente para seus produtos. Ao personalizar a aparência de um aplicativo, você deseja um modelo de estilo e modelagem forte que permita a manutenção e o compartilhamento da aparência dentro e entre aplicativos. O WPF fornece esse modelo.

Outro recurso do modelo de estilo do WPF é a separação de apresentação e lógica. Os designers podem trabalhar na aparência de um aplicativo usando apenas XAML ao mesmo tempo em que os desenvolvedores trabalham na lógica de programação usando C# ou Visual Basic.

Esta visão geral se concentra nos aspectos de estilo e modelagem do aplicativo e não discute nenhum conceito de associação de dados. Para obter informações sobre associação de dados, consulte Visão geral de vinculação de dados.

É importante entender os recursos, que são o que permitem que estilos e modelos sejam reutilizados. Para obter mais informações sobre recursos, consulte Recursos de XAML.

Amostra

O código de exemplo fornecido nesta visão geral é baseado em um aplicativo simples de navegação de fotos mostrado na ilustração a seguir.

ListView estilizado

Essa amostra de foto simples usa estilo e modelagem para criar uma experiência de usuário visualmente atraente. O exemplo tem dois TextBlock elementos e um ListBox controle que está associado a uma lista de imagens.

Para ver a amostra completa, consulte Introdução à amostra de estilo e modelagem.

Estilos

Você pode pensar em a Style como uma maneira conveniente de aplicar um conjunto de valores de propriedade a vários elementos. Você pode usar um estilo em qualquer elemento que derive de FrameworkElement ou FrameworkContentElement como um Window ou um Button.

A maneira mais comum de declarar um estilo é como um recurso na Resources seção em um arquivo XAML. Como os estilos são recursos, eles obedecem às mesmas regras de escopo que se aplicam a todos os recursos. Simplificando, onde você declara um estilo afeta onde o estilo pode ser aplicado. Por exemplo, se você declarar o estilo no elemento raiz do arquivo XAML de definição do aplicativo, o estilo poderá ser usado em qualquer lugar do aplicativo.

Por exemplo, o código XAML a seguir declara dois estilos para um TextBlock, um aplicado automaticamente a todos os TextBlock elementos e outro que deve ser referenciado explicitamente.

<Window.Resources>
    <!-- .... other resources .... -->

    <!--A Style that affects all TextBlocks-->
    <Style TargetType="TextBlock">
        <Setter Property="HorizontalAlignment" Value="Center" />
        <Setter Property="FontFamily" Value="Comic Sans MS"/>
        <Setter Property="FontSize" Value="14"/>
    </Style>
    
    <!--A Style that extends the previous TextBlock Style with an x:Key of TitleText-->
    <Style BasedOn="{StaticResource {x:Type TextBlock}}"
           TargetType="TextBlock"
           x:Key="TitleText">
        <Setter Property="FontSize" Value="26"/>
        <Setter Property="Foreground">
            <Setter.Value>
                <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
                    <LinearGradientBrush.GradientStops>
                        <GradientStop Offset="0.0" Color="#90DDDD" />
                        <GradientStop Offset="1.0" Color="#5BFFFF" />
                    </LinearGradientBrush.GradientStops>
                </LinearGradientBrush>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

Aqui está um exemplo dos estilos declarados acima sendo usados.

<StackPanel>
    <TextBlock Style="{StaticResource TitleText}" Name="textblock1">My Pictures</TextBlock>
    <TextBlock>Check out my new pictures!</TextBlock>
</StackPanel>

Blocos de texto estilizados

ControlTemplates

No WPF, o ControlTemplate de um controle define a aparência do controle. Você pode alterar a estrutura e a aparência de um controle definindo um novo ControlTemplate e atribuindo-o a um controle. Em muitos casos, os modelos oferecem flexibilidade suficiente para que você não precise escrever seus próprios controles personalizados.

Cada controle tem um modelo padrão atribuído à propriedade Control.Template . O modelo conecta a apresentação visual do controle com os recursos do controle. Como você define um modelo em XAML, pode alterar a aparência do controle sem escrever nenhum código. Cada modelo é projetado para um controle específico, como um Button.

Normalmente, Resources você declara um modelo como um recurso na seção de um arquivo XAML. Como acontece com todos os recursos, aplicam-se as regras de escopo.

Os modelos de controle são muito mais complicados do que um estilo. Isso ocorre porque o modelo de controle reescreve a aparência visual de todo o controle, enquanto um estilo simplesmente aplica alterações de propriedade ao controle existente. No entanto, como o modelo de um controle é aplicado definindo a propriedade Control.Template , você pode usar um estilo para definir ou definir um modelo.

Os designers geralmente permitem que você crie uma cópia de um modelo existente e modifique-o. Por exemplo, no designer WPF do Visual Studio, selecione um CheckBox controle e, em seguida, clique com o botão direito do mouse e selecione Editar modelo>Criar uma cópia. Este comando gera um estilo que define um modelo.

<Style x:Key="CheckBoxStyle1" TargetType="{x:Type CheckBox}">
    <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual1}"/>
    <Setter Property="Background" Value="{StaticResource OptionMark.Static.Background1}"/>
    <Setter Property="BorderBrush" Value="{StaticResource OptionMark.Static.Border1}"/>
    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type CheckBox}">
                <Grid x:Name="templateRoot" Background="Transparent" SnapsToDevicePixels="True">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <Border x:Name="checkBoxBorder" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="1" VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
                        <Grid x:Name="markGrid">
                            <Path x:Name="optionMark" Data="F1 M 9.97498,1.22334L 4.6983,9.09834L 4.52164,9.09834L 0,5.19331L 1.27664,3.52165L 4.255,6.08833L 8.33331,1.52588e-005L 9.97498,1.22334 Z " Fill="{StaticResource OptionMark.Static.Glyph1}" Margin="1" Opacity="0" Stretch="None"/>
                            <Rectangle x:Name="indeterminateMark" Fill="{StaticResource OptionMark.Static.Glyph1}" Margin="2" Opacity="0"/>
                        </Grid>
                    </Border>
                    <ContentPresenter x:Name="contentPresenter" Grid.Column="1" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="HasContent" Value="true">
                        <Setter Property="FocusVisualStyle" Value="{StaticResource OptionMarkFocusVisual1}"/>
                        <Setter Property="Padding" Value="4,-1,0,0"/>

... content removed to save space ...

Editar uma cópia de um modelo é uma ótima maneira de aprender como os modelos funcionam. Em vez de criar um novo modelo em branco, é mais fácil editar uma cópia e alterar alguns aspectos da apresentação visual.

Para obter um exemplo, consulte Criar um modelo para um controle.

TemplateBinding

Você deve ter notado que o recurso de modelo definido na seção anterior usa a extensão de marcação TemplateBinding. A TemplateBinding é uma forma otimizada de uma associação para cenários de modelo, análoga a uma associação construída com {Binding RelativeSource={RelativeSource TemplatedParent}}. TemplateBinding é útil para associar partes do modelo às propriedades do controle. Por exemplo, cada controle tem uma BorderThickness propriedade. Use a TemplateBinding para gerenciar qual elemento no modelo é afetado por essa configuração de controle.

ContentControl e ItemsControl

Se a ContentPresenter for declarado no ControlTemplate de a ContentControl, o ContentPresenter será automaticamente vinculado às ContentTemplate propriedades e Content . Da mesma forma, um ItemsPresenter que está no ControlTemplate de um ItemsControl se ligará automaticamente às ItemTemplate propriedades e Items .

Modelos de dados

Neste aplicativo de exemplo, há um ListBox controle associado a uma lista de fotos.

<ListBox ItemsSource="{Binding Source={StaticResource MyPhotos}}"
         Background="Silver" Width="600" Margin="10" SelectedIndex="0"/>

Atualmente ListBox , isso se parece com o seguinte.

ListBox antes de aplicar o modelo

A maioria dos controles tem algum tipo de conteúdo, que, frequentemente, vem dos dados aos quais você está se associando. Neste exemplo, os dados estão na lista de fotos. No WPF, você usa a DataTemplate para definir a representação visual dos dados. Basicamente, o que você coloca em um DataTemplate determina a aparência dos dados no aplicativo renderizado.

Em nosso aplicativo de exemplo, cada objeto personalizado Photo tem uma Source propriedade do tipo string que especifica o caminho do arquivo da imagem. Atualmente, os objetos de fotos aparecem como caminhos de arquivo.

public class Photo
{
    public Photo(string path)
    {
        Source = path;
    }

    public string Source { get; }

    public override string ToString() => Source;
}
Public Class Photo
    Sub New(ByVal path As String)
        Source = path
    End Sub

    Public ReadOnly Property Source As String

    Public Overrides Function ToString() As String
        Return Source
    End Function
End Class

Para que as fotos apareçam como imagens, você cria um DataTemplate como um recurso.

<Window.Resources>
    <!-- .... other resources .... -->

    <!--DataTemplate to display Photos as images
    instead of text strings of Paths-->
    <DataTemplate DataType="{x:Type local:Photo}">
        <Border Margin="3">
            <Image Source="{Binding Source}"/>
        </Border>
    </DataTemplate>
</Window.Resources>

Observe que a DataType propriedade é semelhante à TargetType propriedade do Style. Se você DataTemplate estiver na seção resources, quando você especificar a DataType propriedade para um tipo e omitir um x:Key, o DataTemplate será aplicado sempre que esse tipo aparecer. Você sempre tem a opção de atribuir o DataTemplate com um x:Key e, em seguida, defini-lo como um StaticResource para propriedades que usam DataTemplate tipos, como a ItemTemplate propriedade ou a ContentTemplate propriedade.

Essencialmente, o DataTemplate exemplo acima define que sempre que houver um Photo objeto, ele deve aparecer como um dentro de Image um Border. Com isso DataTemplate, nosso aplicativo agora fica assim.

Imagem fotográfica

O modelo de modelagem de dados fornece outros recursos. Por exemplo, se você estiver exibindo dados de coleção que contêm outras coleções usando um HeaderedItemsControl tipo como a Menu ou a TreeView, haverá o HierarchicalDataTemplate. Outro recurso de modelagem de dados é o DataTemplateSelector, que permite escolher um DataTemplate para usar com base na lógica personalizada. Para obter mais informações, consulte Visão geral de modelagem dos dados, que oferece uma discussão mais detalhada sobre os diferentes recursos de modelagem de dados.

Gatilhos

Um gatilho define propriedades ou inicia ações, como uma animação, quando um valor da propriedade for alterado ou quando um evento for gerado. Style, ControlTemplatee DataTemplate todos têm uma Triggers propriedade que pode conter um conjunto de gatilhos. Existem vários tipos de gatilhos.

PropertyTriggers

Um Trigger que define valores de propriedade ou inicia ações com base no valor de uma propriedade é chamado de gatilho de propriedade.

Para demonstrar como usar gatilhos de propriedade, você pode tornar cada um ListBoxItem parcialmente transparente, a menos que seja selecionado. O estilo a seguir define o Opacity valor de a ListBoxItem como 0.5. Quando a IsSelected propriedade é true, no entanto, o Opacity é definido como 1.0.

<Window.Resources>
    <!-- .... other resources .... -->

    <Style TargetType="ListBoxItem">
        <Setter Property="Opacity" Value="0.5" />
        <Setter Property="MaxHeight" Value="75" />
        <Style.Triggers>
            <Trigger Property="IsSelected" Value="True">
                <Trigger.Setters>
                    <Setter Property="Opacity" Value="1.0" />
                </Trigger.Setters>
            </Trigger>
        </Style.Triggers>
    </Style>
</Window.Resources>

Este exemplo usa a Trigger para definir um valor de propriedade, mas observe que a Trigger classe também tem as EnterActions propriedades and ExitActions que permitem que um gatilho execute ações.

Observe que a MaxHeight ListBoxItem propriedade do é definida como 75. Na ilustração a seguir, o terceiro item é o item selecionado.

ListView estilizado

EventTriggers e storyboards

Outro tipo de gatilho é o EventTrigger, que inicia um conjunto de ações com base na ocorrência de um evento. Por exemplo, os objetos a seguir EventTrigger especificam que, quando o ponteiro do mouse insere o ListBoxItem, a MaxHeight propriedade é animada para um valor de mais de 90 um 0.2 segundo período. Quando o mouse se afasta do item, a propriedade retorna para o valor original durante um período de 1 segundos. Observe como não é necessário especificar um To valor para a MouseLeave animação. Isso ocorre porque a animação é capaz de controlar o valor original.

<Style.Triggers>
    <Trigger Property="IsSelected" Value="True">
        <Trigger.Setters>
            <Setter Property="Opacity" Value="1.0" />
        </Trigger.Setters>
    </Trigger>
    <EventTrigger RoutedEvent="Mouse.MouseEnter">
        <EventTrigger.Actions>
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation
                        Duration="0:0:0.2"
                        Storyboard.TargetProperty="MaxHeight"
                        To="90"  />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger.Actions>
    </EventTrigger>
    <EventTrigger RoutedEvent="Mouse.MouseLeave">
        <EventTrigger.Actions>
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation
                        Duration="0:0:1"
                        Storyboard.TargetProperty="MaxHeight"  />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger.Actions>
    </EventTrigger>
</Style.Triggers>

Para obter mais informações, consulte a visão geral dos Storyboards.

Na ilustração a seguir, o mouse está apontando para o terceiro item.

Captura de tela de exemplo de estilo

MultiTriggers, DataTriggers e MultiDataTriggers

Além de Trigger e EventTrigger, existem outros tipos de gatilhos. MultiTrigger Permite definir valores de propriedade com base em várias condições. Você usa DataTrigger e MultiDataTrigger quando a propriedade de sua condição é vinculada a dados.

Estados visuais

Os controles estão sempre em um estado específico. Por exemplo, quando o mouse se move sobre a superfície de um controle, o controle é considerado em um estado comum de MouseOver. Um controle sem um estado específico é considerado como estando no estado comum Normal . Os estados são divididos em grupos, e os estados mencionados anteriormente fazem parte do grupo CommonStatesde estados. A maioria dos controles tem dois grupos de estados: CommonStates e FocusStates. De cada grupo de estados aplicado a um controle, um controle está sempre em um estado de cada grupo, como CommonStates.MouseOver e FocusStates.Unfocused. No entanto, um controle não pode estar em dois estados diferentes dentro do mesmo grupo, como CommonStates.Normal e CommonStates.Disabled. Aqui está uma tabela de estados que a maioria dos controles reconhece e usa.

Nome do VisualState Nome do VisualStateGroup Descrição
Normal CommonStates O estado padrão.
MouseOver CommonStates O ponteiro do mouse é posicionado sobre o controle.
Pressed CommonStates O controle é pressionado.
Disabled CommonStates O controle está desabilitado.
Focused FocusStates O controle tem foco.
Unfocused FocusStates O controle não tem foco.

Ao definir um System.Windows.VisualStateManager elemento raiz de um modelo de controle, você pode disparar animações quando um controle entra em um estado específico. O VisualStateManager declara quais combinações de VisualStateGroup e VisualState assistir. Quando o controle entra em um estado assistido, a animação definida pelo VisualStateManager é iniciada.

Por exemplo, o código XAML a seguir observa o CommonStates.MouseOver estado para animar a cor de preenchimento do elemento chamado backgroundElement. Quando o controle retorna ao CommonStates.Normal estado, a cor de preenchimento do elemento nomeado backgroundElement é restaurada.

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

        ...

Para obter mais informações sobre storyboards, consulte Visão geral dos storyboards.

Recursos e temas compartilhados

Um aplicativo WPF típico pode ter vários recursos de interface do usuário que são aplicados em todo o aplicativo. Coletivamente, esse conjunto de recursos pode ser considerado o tema do aplicativo. O WPF fornece suporte para empacotar recursos de interface do usuário como um tema usando um dicionário de recursos encapsulado como a ResourceDictionary classe.

Os temas do WPF são definidos usando o mecanismo de estilo e modelagem que o WPF expõe para personalizar os visuais de qualquer elemento.

Os recursos de tema do WPF são armazenados em dicionários de recursos inseridos. Esses dicionários de recursos devem ser inseridos em um assembly assinado; podem ser inseridos no mesmo assembly, como o próprio código, ou em um assembly lado a lado. Por PresentationFramework.dll, o assembly que contém controles WPF, os recursos de tema estão em uma série de assemblies lado a lado.

O tema torna-se o último local para procurar o estilo de um elemento. Normalmente, a pesquisa começará subindo a árvore de elementos em busca de um recurso apropriado, depois examinará a coleção de recursos do aplicativo e, finalmente, consultará o sistema. Isso dá aos desenvolvedores de aplicativos a chance de redefinir o estilo de qualquer objeto no nível da árvore ou do aplicativo antes de chegar ao tema.

Você pode definir dicionários de recursos como arquivos individuais que permitem reutilizar um tema em vários aplicativos. Você também pode criar temas permutáveis definindo vários dicionários de recursos que fornecem os mesmos tipos de recursos, mas com valores diferentes. Redefinir esses estilos ou outros recursos no nível do aplicativo é a abordagem recomendada para aplicar skin em um aplicativo.

Para compartilhar um conjunto de recursos, incluindo estilos e modelos, entre aplicativos, você pode criar um arquivo XAML e definir um ResourceDictionary que inclua referência a um shared.xaml arquivo.

<ResourceDictionary.MergedDictionaries>
  <ResourceDictionary Source="Shared.xaml" />
</ResourceDictionary.MergedDictionaries>

É o compartilhamento de shared.xaml, que por si só define um ResourceDictionary que contém um conjunto de recursos de estilo e pincel, que permite que os controles em um aplicativo tenham uma aparência consistente.

Para obter mais informações, consulte Dicionários de recursos mesclados.

Se você estiver criando um tema para seu controle personalizado, consulte a seção Definindo recursos no nível do tema da Visão geral da criação de controle.

Confira também