Compartilhar via


Estilos e modelos no WPF

O estilo e a modelagem do WPF (Windows Presentation Foundation) referem-se a um conjunto de recursos que permitem que desenvolvedores e designers criem efeitos visualmente atraentes e uma aparência consistente para seu produto. Ao personalizar a aparência de um aplicativo, você deseja um modelo de modelagem e estilo forte que permita a manutenção e o compartilhamento de 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.

Essa visão geral se concentra nos aspectos de modelagem e estilo do aplicativo e não discute nenhum conceito de associação de dados. Para obter informações sobre a associação de dados, consulte Visão geral da associaçã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 baseia-se em um aplicativo de navegação por fotos simples mostrado na ilustração a seguir.

ListView com estilo

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

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

Estilos

Você pode pensar em uma Style como uma forma prática de aplicar um conjunto de propriedades a vários elementos. Você pode usar um estilo em qualquer elemento que deriva de FrameworkElement ou FrameworkContentElement, como um Window ou um Button.

A maneira mais comum de declarar um estilo é como um recurso na seção Resources 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 de 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 elementos TextBlock e outro que deve ser explicitamente referenciado.

<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 template foi projetado para um controle específico, como um Button.

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

Os modelos de controle são muito mais complexos 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 template de um controle é aplicado definindo a propriedade Control.Template, você pode usar um estilo para definir ou ajustar um template.

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 controle CheckBox e clique com o botão direito do mouse e selecione Editar modelo>Criar uma cópia. Esse 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. Um 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 propriedade BorderThickness. Use um TemplateBinding para gerenciar qual elemento no modelo é afetado por essa configuração de controle.

ContentControl e ItemsControl

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

DataTemplates

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

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

Este ListBox atualmente tem aparência semelhante à mostrada a seguir.

ListBox antes de aplicar o modelo

A maioria dos controles tem algum tipo de conteúdo e esse conteúdo geralmente vem de dados aos quais você está associando. Neste exemplo, os dados são a lista de fotos. No WPF, você usa um 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.

Cada objeto Photo personalizado em nosso aplicativo de exemplo tem uma propriedade Source do tipo cadeia de caracteres que indica o caminho da imagem. Atualmente, os objetos de foto 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, crie um "DataTemplate" como 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 propriedade DataType é semelhante à propriedade TargetType do Style. Se o DataTemplate estiver na seção de recursos, quando você especificar a propriedade DataType 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 x:Key ao DataTemplate e configurá-lo como um StaticResource para propriedades que aceitam tipos DataTemplate, como as propriedades ItemTemplate ou ContentTemplate.

Essencialmente, o DataTemplate no exemplo acima define que sempre que houver um objeto Photo, ele deve aparecer como um Image dentro de um Border. Com esse DataTemplate, nosso aplicativo agora tem esta aparência.

imagem foto Imagem da foto

O modelo de modelagem de dados fornece outros recursos. Por exemplo, se você estiver exibindo dados de coleta que contêm outras coleções usando um tipo de HeaderedItemsControl, como um Menu ou um TreeView, há o HierarchicalDataTemplate. Outro recurso de modelagem de dados é o DataTemplateSelector, que permite que você escolha um DataTemplate a ser usado com base na lógica personalizada. Para obter mais informações, consulte Visão geral da modelagem de dados, que fornece 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 de propriedade é alterado ou quando um evento é acionado. Style, ControlTemplatee DataTemplate têm uma propriedade Triggers que pode conter um conjunto de gatilhos. Há 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 ListBoxItem parcialmente transparente, a menos que esteja selecionado. O estilo a seguir define o valor Opacity de um ListBoxItem como 0.5. Quando a propriedade IsSelected é 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 um Trigger para definir um valor de propriedade, mas observe que a classe Trigger também tem as propriedades EnterActions e ExitActions que permitem que um gatilho execute ações.

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

ListView com estilo

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 seguintes objetos EventTrigger especificam que, quando o ponteiro do mouse entra no ListBoxItem, a propriedade MaxHeight é animada para atingir o valor de 90 durante um período de 0.2 segundos. Quando o mouse se afasta do item, a propriedade retorna ao valor original durante um período de 1 segundo. Observe como não é necessário especificar um valor To para a animação MouseLeave. 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 do de storyboards.

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

Captura de tela de amostra de estilo Captura de tela de exemplo de estilo

MultiTriggers, DataTriggers e MultiDataTriggers

Além de Trigger e EventTrigger, há 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 da sua condição está associada 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 no estado Normal comum. Os estados são divididos em grupos e os estados mencionados anteriormente fazem parte do grupo de estado CommonStates. A maioria dos controles tem dois grupos de estado: CommonStates e FocusStates. De cada grupo de estado 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 está posicionado sobre o controle.
Pressed CommonStates O botão do 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 no elemento raiz de um modelo de controle, você pode disparar animações quando um controle entra em um estado específico. O VisualStateManager especifica quais combinações de VisualStateGroup e VisualState monitorar. 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 estado CommonStates.MouseOver para animar a cor de preenchimento do elemento chamado backgroundElement. Quando o controle retorna ao estado CommonStates.Normal, a cor de preenchimento do elemento chamado 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 roteiros gráficos, consulte Visão geral de roteiros gráficos.

Recursos e temas compartilhados

Um aplicativo WPF típico pode ter vários recursos de interface do usuário 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 classe ResourceDictionary.

Os temas do WPF são definidos usando o mecanismo de modelagem e estilo 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. Para 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 se torna o último lugar para procurar o estilo de um elemento. Normalmente, a pesquisa começará percorrendo a árvore de elementos em busca de um recurso apropriado e, em seguida, examinará a coleção de recursos do aplicativo e, por fim, 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 trocá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 ao nível do aplicativo é a abordagem recomendada para personalizar 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 inclui referência a um arquivo shared.xaml.

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

O compartilhamento de shared.xaml, que define um ResourceDictionary por si só e contém um conjunto de recursos de estilo e pincel, possibilita aos controles de um aplicativo manter uma aparência consistente.

Para obter mais informações, consulte os 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 sobre a criação de controle.

Consulte também