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.
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>
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.
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.
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.
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.
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 CommonStates
de 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
.NET Desktop feedback