Поделиться через


Что такое стили и шаблоны?

Стилизация и шаблон Windows Presentation Foundation (WPF) относятся к набору функций, которые вы используете для создания визуальных эффектов и согласованного внешнего вида для вашего продукта. При настройке внешнего вида приложения требуется строгая модель стилизации и шаблонов, которая позволяет поддерживать внешний вид и совместно использовать внешний вид в приложениях и между приложениями. WPF предоставляет такую модель.

Еще одной особенностью модели стилизации WPF является разделение представления и логики. Дизайнеры могут работать над внешним видом приложения, используя только XAML, одновременно с разработчиками, которые работают над логикой программирования с помощью C# или Visual Basic.

В этом обзоре рассматриваются аспекты стилизации и шаблонов приложения и не обсуждаются понятия привязки данных. Сведения о привязке данных см. Обзор привязки данных.

Важно понимать ресурсы, которые позволяют повторно использовать стили и шаблоны. Дополнительные сведения о ресурсах см. в разделе Общие сведения о ресурсах XAML.

Образец

Образец кода, приведенный в этом обзоре, основан на простом приложении для просмотра фотографий, показанном на следующем рисунке.

Стилизированный ListView

В этом простом образце фотографий используется стилизация и шаблон для создания визуально привлекательного пользовательского интерфейса. В примере есть два элемента 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>

Стилизованные текстовые блоки

Дополнительные сведения см. в статье Создание стиля элемента управления в WPF.

ControlTemplates

В WPF элемент управления ControlTemplate определяет его внешний вид. Вы можете изменить структуру и внешний вид элемента управления, определив новый ControlTemplate и назначив его элементу управления. Во многих случаях шаблоны обеспечивают достаточную гибкость, чтобы не создавать собственные пользовательские элементы управления.

Каждый элемент управления имеет шаблон по умолчанию, назначенный свойству Control.Template. Шаблон подключает визуальную презентацию элемента управления с возможностями элемента управления. Так как вы определяете шаблон в XAML, вы можете изменить внешний вид элемента управления без написания кода. Каждый шаблон предназначен для определенного элемента управления, например, Button.

Это важно

При изменении визуального дерева элемента управления необходимо заменить все ControlTemplate. Невозможно заменить только часть визуального дерева элемента управления. Чтобы изменить визуальное дерево элемента управления, необходимо задать свойство Template этого элемента управления новым и завершенным ControlTemplate.

Обычно вы объявляете шаблон как ресурс в 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 — это оптимизированная форма привязки для сценариев шаблона, аналогичная привязке, созданной с {Binding RelativeSource={RelativeSource TemplatedParent}}. TemplateBinding полезно для привязки частей шаблона к свойствам элемента управления. Например, каждый элемент управления имеет свойство BorderThickness. Используйте TemplateBinding для управления элементом в шаблоне, затронутым этим параметром элемента управления.

ContentControl и ItemsControl

Если объект ContentPresenter объявлен в ControlTemplate объекта ContentControl, ContentPresenter автоматически привязывается к свойствам ContentTemplate и Content. Аналогичным образом, объект, ItemsPresenter, находящийся в ControlTemplate, автоматически привязывается к свойствам ItemTemplate и Items.

Шаблоны данных

В этом примере приложения ListBox элемент управления привязывается к списку фотографий.

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

В настоящее время это ListBox выглядит так, как показано на следующем рисунке.

ListBox до применения шаблона

Большинство элементов управления имеют некоторый тип содержимого, и это содержимое часто поступает из данных, к которым вы привязываетесь. В этом примере данные — это список фотографий. В WPF вы используете DataTemplate для определения визуального представления данных. По сути, то, что вы помещаете в DataTemplate, определяет, как выглядят данные в отображаемом приложении.

В этом примере приложения каждый пользовательский Photo объект имеет Source свойство типа string, указывающее путь к файлу изображения. В настоящее время объекты фотографии отображаются в виде путей к файлам.

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.

По сути, в предыдущем примере определяется, что всякий раз, когда существует объект Photo, его следует отображать как Image внутри Border. С помощью этого DataTemplate приложение теперь выглядит, как на этом изображении.

Фото

Модель шаблонов данных предоставляет другие функции. Например, если вы отображаете данные сбора, содержащие другие коллекции, с помощью HeaderedItemsControl типа, например a Menu или aTreeView, используйте .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. На следующем рисунке третий элемент является выбранным элементом.

Стилизированный ListView

Триггеры событий и раскадровки

Другим типом триггера является EventTrigger, который запускает набор действий в случае возникновения события. Например, следующие объекты EventTrigger указывают, что при наведении указателя мыши на ListBoxItem, свойство MaxHeight анимируется до значения 90 в течение 0.2 секунд. При переходе мыши от элемента свойство возвращается к исходному значению в течение 1 секунды. Обратите внимание, что для анимации MouseLeave не нужно указывать значение To. Это происходит, так как анимация отслеживает исходное значение.

<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 с набором стилей и ресурсов кисти, вы способствуете тому, чтобы элементы управления в приложении имели согласованный вид.

Для получения дополнительной информации см. объединенные словари ресурсов.

Чтобы создать тему для пользовательского элемента управления, ознакомьтесь с разделом "Определение ресурсов" на уровне темы в обзоре разработки элемента управления.

Использование встроенных тем

WPF включает несколько встроенных тем, которые можно явно применить к приложению. Эти темы внедрены в сборки PresentationFramework, и вы можете ссылаться на них с помощью URI пакетов в словарях ресурсов приложения.

Чтобы применить встроенную тему, объедините словарь ресурсов темы в ресурсы приложения:App.xaml

<Application x:Class="MyApp.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/PresentationFramework.Aero2;component/themes/Aero2.NormalColor.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

Доступные встроенные темы

В следующей таблице перечислены встроенные темы, доступные с помощью URI пакетов:

Тема URI пакета Описание
Аэро2 /PresentationFramework.Aero2;component/themes/Aero2.NormalColor.xaml Тема по умолчанию для приложений Windows 8 и более поздних версий.
AeroLite /PresentationFramework.AeroLite;component/themes/AeroLite.NormalColor.xaml Упрощенная версия Aero2 с уменьшенными визуальными эффектами.
Aero /PresentationFramework.Aero;component/themes/Aero.NormalColor.xaml Тема периода Windows 7 с эффектами стекла и градиентами.
Fluent /PresentationFramework.Fluent;component/Themes/Fluent.xaml Тема в стиле Windows 11 с поддержкой светло-темного режима (.NET 9+).
Луна (синий) /PresentationFramework.Luna;component/themes/Luna.NormalColor.xaml Цветовая схема Windows XP по умолчанию.
Луна (Silver) /PresentationFramework.Luna;component/themes/Luna.Metallic.xaml Металлическая цветовая схема Windows XP с серебристым цветом.
Луна (Оливка) /PresentationFramework.Luna;component/themes/Luna.Homestead.xaml Оливковая зеленая цветовая схема Windows XP.
Royale /PresentationFramework.Royale;component/themes/Royale.NormalColor.xaml Тема Windows XP Media Center Edition.
Classic /PresentationFramework.Classic;component/themes/Classic.xaml Традиционный внешний вид Windows 95/98/2000.

Замечание

Для приложений .NET Framework в проекте может потребоваться добавить ссылки на сборки темы (например, PresentationFramework.Aero2). Для приложений .NET сборки тем обычно включаются автоматически.

Подсказка

Для справки разработчика Visual Studio содержит XAML-файлы для этих тем в каталоге установки, обычно расположенные в папке C:\Program Files\Microsoft Visual Studio\2022\<edition>\DesignTools\SystemThemes\wpf. Эти файлы полезны для понимания структуры темы, но не используются во время выполнения.

Использование современной темы Fluent (.NET 9+)

Для .NET 9 и более поздних версий можно использовать новую тему Fluent, которая поддерживает светлые и темные режимы:

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="pack://application:,,,/PresentationFramework.Fluent;component/Themes/Fluent.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

Кроме того, можно использовать ThemeMode свойство для еще более простого приложения темы:

<Application x:Class="MyApp.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml"
             ThemeMode="System">
</Application>

См. также