Estilos e modelos (WPF .NET)

O estilo e o modelo 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.

Outra característica do modelo de estilo WPF é a separação entre 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 vinculaçã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 os que permitem que estilos e modelos sejam reutilizados. Para obter mais informações sobre recursos, consulte Visão geral dos recursos XAML.

Importante

A documentação do Guia da Área de Trabalho para .NET 7 e .NET 6 está em construção.

Amostra

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

Styled ListView

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á vinculado a uma lista de imagens.

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

Estilos

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

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 , um TextBlockaplicado automaticamente a todos os TextBlock elementos 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>

Styled textblocks

Para obter mais informações, consulte Criar um estilo para um controle.

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

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

Os modelos de controle são muito mais envolvidos 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 o modifique. 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. 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. 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 vincular partes do modelo às propriedades do controle. Por exemplo, cada controle tem uma BorderThickness propriedade. Use um TemplateBinding para gerenciar qual elemento no modelo é afetado por essa configuração de controle.

ContentControl e ItemsControl

Se um for declarado no ControlTemplate de um ContentControlContentPresenter , o ContentPresenter será automaticamente vinculado às ContentTemplate propriedades e Content . Da mesma forma, um que está no de um ItemsPresenterItemsControl vai se vincular automaticamente às ItemTemplate propriedades e Items .ControlTemplate

Modelos de dados

Neste aplicativo de exemplo, há um ListBox controle que está vinculado 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 before applying template

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

Em nosso aplicativo de exemplo, cada objeto personalizado Photo tem uma Source propriedade de cadeia de caracteres de tipo 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ê estiver na seção recursos, quando você DataTemplate 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 e, em seguida, defini-lo como um x:KeyStaticResource para propriedades que usam DataTemplate tipos, como a propriedade ou a ItemTemplateContentTemplate propriedade.

Essencialmente, o DataTemplate exemplo acima define que sempre que houver um objeto, ele deve aparecer como um dentro de um ImagePhotoBorder. Com isso, nosso aplicativo agora se parece com isso DataTemplate.

Photo image

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 tipo como um ou um HeaderedItemsControlMenuTreeView, haverá o HierarchicalDataTemplate. Outro recurso de modelagem de dados é o DataTemplateSelector, que permite que você escolha 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 ListBoxItem um 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 é , no entanto, o Opacity é truedefinido 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 para definir um valor de propriedade, mas observe que a TriggerTrigger classe também tem as propriedades e ExitActions que permitem que um gatilho EnterActions execute ações.

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

Styled ListView

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 entra no 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 de storyboards.

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

Styling sample screenshot

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 no estado comum Normal . Os estados são divididos em grupos, e os estados mencionados anteriormente fazem parte do grupo CommonStatesestadual. A maioria dos controles tem dois grupos de estados: 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 é posicionado sobre o controle.
Pressionado CommonStates O controle é pressionado.
Desabilitadas CommonStates O controle está desabilitado.
Focalizado FocusStates O controle tem foco.
Sem foco FocusStates O controle não tem foco.

Ao definir um no elemento raiz de um modelo de controle, você pode disparar animações quando um controle entra em um System.Windows.VisualStateManager estado específico. O VisualStateManager declara quais combinações de VisualStateGroup e VisualState para assistir. Quando o controle entra em um estado observado, 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 de 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 da 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 incorporados. 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 torna-se o último local para procurar o estilo de um elemento. Normalmente, a pesquisa começará subindo a árvore de elementos procurando 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 atingir o 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 esfolar um aplicativo.

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

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

É o compartilhamento do shared.xaml, que define um que contém um conjunto de recursos de estilo e pincel, que permite que os controles em um ResourceDictionary 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 de criação de controle.

Confira também