Стили и шаблоны в WPF
Стилизация и шаблон Windows Presentation Foundation (WPF) относятся к набору функций, которые позволяют разработчикам и дизайнерам создавать визуальные эффекты и согласованный внешний вид для своего продукта. При настройке внешнего вида приложения требуется строгая модель стилей и шаблонов, которая обеспечивает обслуживание и совместное использование внешнего вида в приложениях и между приложениями. WPF предоставляет такую модель.
Еще одной особенностью модели стилизации WPF является разделение представления и логики. Дизайнеры могут работать над внешним видом приложения, используя только XAML, одновременно с разработчиками, которые работают над логикой программирования с помощью C# или Visual Basic.
В этом обзоре рассматриваются аспекты стилизации и шаблонов приложения и не обсуждаются понятия привязки данных. Сведения о привязке данных см. Обзор привязки данных.
Важно понимать ресурсы, которые позволяют повторно использовать стили и шаблоны. Для получения дополнительной информации о ресурсах см. Ресурсы XAML.
Образец
Образец кода, приведенный в этом обзоре, основан на простом приложении для просмотра фотографий, показанном на следующем рисунке.
В этом простом образце фотографий используется стилизация и шаблон для создания визуально привлекательного пользовательского интерфейса. В примере есть два элемента TextBlock и элемент управления ListBox, привязанный к списку изображений.
См. полный пример в разделе Введение в стили и шаблоны.
Стили
Вы можете представить Style удобным способом применения набора значений свойств к нескольким элементам. Стиль можно использовать для любого элемента, наследуемого от FrameworkElement или FrameworkContentElement, таких как Window или Button.
Наиболее распространенным способом объявления стиля является объявление его в виде ресурса в разделе Resources
в файле XAML. Поскольку стили являются ресурсами, они подчиняются одинаковым правилам области, которые применяются ко всем ресурсам. Проще говоря, когда вы объявляете стиль влияет на то, где можно применить стиль. Например, если вы объявляете стиль в корневом элементе XAML-файла определения приложения, стиль можно использовать в любом месте приложения.
Например, следующий код XAML объявляет два стиля для TextBlock
, один автоматически применяется ко всем элементам TextBlock
, а другой, который должен быть явно указан.
<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>
Ниже приведен пример стилей, объявленных выше.
<StackPanel>
<TextBlock Style="{StaticResource TitleText}" Name="textblock1">My Pictures</TextBlock>
<TextBlock>Check out my new pictures!</TextBlock>
</StackPanel>
ControlTemplates
В WPF элемент управления ControlTemplate определяет его внешний вид. Вы можете изменить структуру и внешний вид элемента управления, определив новый ControlTemplate и назначив его элементу управления. Во многих случаях шаблоны обеспечивают достаточную гибкость, чтобы не создавать собственные пользовательские элементы управления.
Каждый элемент управления имеет шаблон по умолчанию, назначенный свойству Control.Template. Шаблон подключает визуальную презентацию элемента управления с возможностями элемента управления. Так как вы определяете шаблон в XAML, вы можете изменить внешний вид элемента управления без написания кода. Каждый шаблон предназначен для определенного элемента управления, например, Button.
Обычно вы объявляете шаблон как ресурс в разделе Resources
XAML-файла. Как и для всех ресурсов, применяются правила области видимости.
Шаблоны элементов управления гораздо сложнее, чем стили. Это связано с тем, что шаблон элемента управления перезаписывает внешний вид всего элемента управления, а стиль просто применяет изменения свойств к существующему элементу управления. Однако, так как шаблон элемента управления применяется, задав свойство Control.Template, можно использовать стиль для определения или задания шаблона.
Конструкторы обычно позволяют создавать копию существующего шаблона и изменять ее. Например, в конструкторе WPF Visual Studio выберите элемент управления CheckBox
, а затем щелкните правой кнопкой мыши и выберите Изменить шаблон>Создать копию. Эта команда создает стиль , определяющий шаблон.
<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 ...
Редактирование копии шаблона — отличный способ узнать, как работают шаблоны. Вместо создания пустого шаблона проще изменить копию и изменить несколько аспектов визуальной презентации.
Пример см. в статье Создание шаблона для элемента управления.
TemplateBinding
Возможно, вы заметили, что ресурс шаблона, определенный в предыдущем разделе, использует расширение разметки TemplateBinding.
TemplateBinding
— это оптимизированная форма привязки для сценариев шаблона, аналогичная привязке, созданной с {Binding RelativeSource={RelativeSource TemplatedParent}}
.
TemplateBinding
полезно для привязки частей шаблона к свойствам элемента управления. Например, каждый элемент управления имеет свойство BorderThickness. Используйте TemplateBinding
для управления элементом в шаблоне, затронутым этим параметром элемента управления.
ContentControl и ItemsControl
Если ContentPresenter объявлен в ControlTemplateContentControl, то ContentPresenter автоматически привязывается к свойствам ContentTemplate и Content. Аналогичным образом, ItemsPresenter, который находится в ControlTemplate у ItemsControl, автоматически привязывается к свойствам ItemTemplate и Items.
Шаблоны данных
В этом примере приложения есть элемент управления ListBox, привязанный к списку фотографий.
<ListBox ItemsSource="{Binding Source={StaticResource MyPhotos}}"
Background="Silver" Width="600" Margin="10" SelectedIndex="0"/>
Этот ListBox в настоящее время выглядит следующим образом.
Большинство элементов управления имеют некоторый тип содержимого, и это содержимое часто поступает из данных, к которым вы привязываетесь. В этом примере данные — это список фотографий. В WPF вы используете DataTemplate для определения визуального представления данных. По сути, то, что вы помещаете в DataTemplate, определяет, как выглядят данные в отображаемом приложении.
В нашем примере приложения каждый пользовательский объект Photo
имеет свойство Source
типа строки, указывающее путь файла изображения. В настоящее время объекты фотографии отображаются в виде путей к файлам.
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
Чтобы фотографии отображались как изображения, вы создаете DataTemplate в качестве ресурса.
<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>
Обратите внимание, что свойство DataType аналогично свойству TargetTypeStyle. Если DataTemplate находится в разделе ресурсов, при указании свойства DataType для типа и если опустить x:Key
, DataTemplate применяется всякий раз, когда этот тип появляется. Вы всегда можете назначить DataTemplate при помощи x:Key
, а затем установить его в качестве StaticResource
для свойств, принимающих DataTemplate типы, такие как ItemTemplate или ContentTemplate.
По сути, DataTemplate в приведенном выше примере определяет, что всякий раз, когда существует объект Photo
, он должен отображаться как Image в Border. С помощью этого DataTemplateнаше приложение теперь выглядит следующим образом.
фотоизображение
Модель шаблонов данных предоставляет другие функции. Например, если вы отображаете данные сбора, содержащие другие коллекции с помощью типа HeaderedItemsControl, например Menu или TreeView, существует HierarchicalDataTemplate. Другой компонент шаблонов данных — это DataTemplateSelector, который позволяет выбрать DataTemplate для использования на основе пользовательской логики. Дополнительные сведения см. в разделе Общие сведения о шаблоне данных, что обеспечивает более подробное обсуждение различных функций шаблонов данных.
Триггеры
Триггер задает свойства или запускает действия, такие как анимация, при изменении значения свойства или при возникновении события.
Style, ControlTemplateи DataTemplate все имеют свойство Triggers
, которое может содержать набор триггеров. Существует несколько типов триггеров.
Триггеры свойств
Trigger, который задает значения свойств или запускает действия в зависимости от значения свойства, называется триггером свойства.
Чтобы продемонстрировать, как использовать триггеры свойств, можно сделать каждый ListBoxItem частично прозрачным, если он не выбран. Следующий стиль задает значение Opacity для ListBoxItem равным 0.5
. Если же свойство IsSelected имеет значение true
, то Opacity устанавливается в 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>
В этом примере используется Trigger для задания значения свойства, но обратите внимание, что класс Trigger также имеет свойства EnterActions и ExitActions, позволяющие триггеру выполнять действия.
Обратите внимание, что свойство MaxHeight у ListBoxItem установлено в 75
. На следующем рисунке третий элемент является выбранным элементом.
События и триггеры и раскадровки
Другим типом триггера является EventTrigger, который запускает набор действий в случае возникновения события. Например, следующие объекты EventTrigger указывают, что при наведении указателя мыши на ListBoxItem, свойство MaxHeight анимируется до значения 90
в течение 0.2
секунд. При переходе мыши от элемента свойство возвращается к исходному значению в течение 1
секунды. Обратите внимание, что не нужно указывать значение To для анимации MouseLeave. Это связано с тем, что анимация может отслеживать исходное значение.
<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>
Дополнительные сведения см. в разделе «Обзор раскадровок».
На следующем рисунке мышь указывает на третий элемент.
MultiTriggers, DataTriggers и MultiDataTriggers
Помимо Trigger и EventTrigger, существуют и другие типы триггеров. MultiTrigger позволяет задавать значения свойств в зависимости от нескольких условий. Вы используете DataTrigger и MultiDataTrigger, если свойство условия привязано к данным.
Визуальные состояния
Элементы управления всегда находятся в определенном состоянии . Например, когда мышь перемещается по поверхности элемента управления, элемент управления считается общим состоянием MouseOver
. Элемент управления без определенного состояния считается в общем состоянии Normal
. Государства разбиваются на группы, и ранее упомянутые государства являются частью государственной группы CommonStates
. Большинство элементов управления имеют две группы состояний: CommonStates
и FocusStates
. Из каждой группы состояний, применяемой к элементу управления, элемент управления всегда находится в одном состоянии каждой группы, например CommonStates.MouseOver
и FocusStates.Unfocused
. Однако элемент управления не может находиться в двух разных состояниях в одной группе, например CommonStates.Normal
и CommonStates.Disabled
. Ниже приведена таблица состояний, которые большинство элементов управления распознают и используют.
Имя VisualState | Имя VisualStateGroup | Описание |
---|---|---|
Normal |
CommonStates |
Состояние по умолчанию. |
MouseOver |
CommonStates |
Указатель мыши расположен над элементом управления. |
Pressed |
CommonStates |
Элемент управления нажимается. |
Disabled |
CommonStates |
Элемент управления отключен. |
Focused |
FocusStates |
Элемент управления имеет фокус. |
Unfocused |
FocusStates |
Элемент управления не имеет фокуса. |
Определив System.Windows.VisualStateManager корневого элемента шаблона элемента управления, можно активировать анимацию при вводе элемента управления в определенное состояние. В VisualStateManager
указывается, какие сочетания VisualStateGroup и VisualState отслеживать. Когда элемент управления входит в отслеживаемое состояние, запускается анимация, определяемая VisualStateManager
.
Например, следующий код XAML следит за состоянием CommonStates.MouseOver
, чтобы анимировать цвет заливки элемента с именем backgroundElement
. Когда элемент управления возвращается в состояние CommonStates.Normal
, цвет заливки элемента с именем backgroundElement
восстанавливается.
<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>
...
Дополнительные сведения о раскадровках см. в разделе Storyboards Overview.
Общие ресурсы и темы
Обычное приложение WPF может иметь несколько ресурсов пользовательского интерфейса, применяемых во всем приложении. В совокупности этот набор ресурсов можно рассматривать как тему для приложения. WPF обеспечивает поддержку упаковки ресурсов пользовательского интерфейса в качестве темы с помощью словаря ресурсов, инкапсулированного в качестве класса ResourceDictionary.
Темы WPF определяются с помощью механизма стилизации и шаблонов, который WPF предоставляет для настройки визуальных элементов любого элемента.
Ресурсы темы WPF хранятся в внедренных словарях ресурсов. Эти словари ресурсов должны быть внедрены в подписанную сборку и могут быть внедрены в ту же сборку, что и сам код или в параллельной сборке. Для сборки PresentationFramework.dll, содержащей элементы управления WPF, ресурсы темы находятся в ряде смежных сборок.
Тема становится последним местом для поиска стиля элемента. Как правило, поиск начинается с обхода дерева элементов в поисках соответствующего ресурса, затем проводится поиск в коллекции ресурсов приложения и, наконец, осуществляется запрос к системе. Это дает разработчикам приложений возможность переопределить стиль любого объекта на уровне дерева или приложения, прежде чем достичь темы.
Словари ресурсов можно определить как отдельные файлы, позволяющие повторно использовать тему в нескольких приложениях. Вы также можете создавать переключаемые темы, определяя несколько словарей ресурсов, которые содержат одинаковые типы ресурсов, но с различными значениями. Настройка этих стилей или других ресурсов на уровне приложения — это рекомендуемый подход для оформления приложения.
Чтобы предоставить общий доступ к набору ресурсов, включая стили и шаблоны, в приложениях можно создать XAML-файл и определить ResourceDictionary, включающую ссылку на файл shared.xaml
.
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Shared.xaml" />
</ResourceDictionary.MergedDictionaries>
Совместное использование shared.xaml
, которое само по себе определяет ResourceDictionary, содержащий набор ресурсов стилей и кистей, позволяет элементам управления в приложении иметь единообразный вид.
Для получения дополнительной информации см. объединенные словари ресурсов.
Если вы создаете тему для пользовательского элемента управления, ознакомьтесь с разделом Определение ресурсов на уровне темы раздела Обзор разработки элементов управления.
См. также
.NET Desktop feedback