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


Создание шаблона для элемента управления (WPF.NET)

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

В этой статье будет показано, как создать новый шаблон ControlTemplate для элемента управления Button.

Когда следует создавать ControlTemplate

Элементы управления имеют много свойств, таких, например, как Background, Foreground и FontFamily. Эти свойства управляют различными аспектами внешнего вида элемента управления, но с помощью них можно внести не так много изменений. Например, для элемента управления Foreground можно задать синий цвет фона с помощью свойства FontStyle и курсив с помощью свойства CheckBox. Если вы хотите внести такие изменения внешнего вида элемента управления, которые не предусмотрены его свойствами, можно создать шаблон ControlTemplate.

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

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

С другой стороны, если вам нужен элемент управления с новыми функциями, другими свойствами и новыми параметрами, лучше создать новый UserControl.

Необходимые компоненты

Создайте новое приложение WPF и в окне MainWindow.xaml (или в другом окне по вашему выбору) установите следующие свойства элемента <Окно>:

Свойство Значение
Title Template Intro Sample
SizeToContent WidthAndHeight
MinWidth 250

В качестве содержимого элемента <Окно> задайте следующий код XAML:

<StackPanel Margin="10">
    <Label>Unstyled Button</Label>
    <Button>Button 1</Button>
    <Label>Rounded Button</Label>
    <Button>Button 2</Button>
</StackPanel>

В итоге файл MainWindow.xaml должен выглядеть следующим образом.

<Window x:Class="IntroToStylingAndTemplating.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:IntroToStylingAndTemplating"
        mc:Ignorable="d"
        Title="Template Intro Sample" SizeToContent="WidthAndHeight" MinWidth="250">
    <StackPanel Margin="10">
        <Label>Unstyled Button</Label>
        <Button>Button 1</Button>
        <Label>Rounded Button</Label>
        <Button>Button 2</Button>
    </StackPanel>
</Window>

Если запустить приложение, оно будет выглядеть так.

Окно WPF с двумя кнопками без стиля

Создание шаблона ControlTemplate

Чаще всего ControlTemplate объявляется как ресурс в разделе Resources файла XAML. Так как шаблоны являются ресурсами, для них действуют те же правила определения области, что и для всех других ресурсов. Проще говоря, то, где вы объявляете шаблон, влияет на то, где этот шаблон может быть применен. Например, если вы объявите шаблон в корневом элементе XAML-файла определения приложения, этот шаблон можно будет использовать в любом месте вашего приложения. Если вы определяете шаблон в окне, его смогут использовать только элементы управления из этого окна.

Для начала добавьте элемент Window.Resources в свой файл MainWindow.xaml:

<Window x:Class="IntroToStylingAndTemplating.Window2"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:IntroToStylingAndTemplating"
        mc:Ignorable="d"
        Title="Template Intro Sample" SizeToContent="WidthAndHeight" MinWidth="250">
    <Window.Resources>
        
    </Window.Resources>
    <StackPanel Margin="10">
        <Label>Unstyled Button</Label>
        <Button>Button 1</Button>
        <Label>Rounded Button</Label>
        <Button>Button 2</Button>
    </StackPanel>
</Window>

Создайте новый шаблон <ControlTemplate> со следующими заданными свойствами:

Свойство Значение
x:Key roundbutton
TargetType Button

Этот шаблон элемента управления будет простым:

  • корневой элемент этого элемента управления, Grid;
  • Ellipse, чтобы кнопка отображалась круглой;
  • ContentPresenter для вывода указанного пользователем содержимого кнопки.
<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <Ellipse Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />
        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</ControlTemplate>

TemplateBinding

Создавая новый шаблон ControlTemplate, вы можете захотеть использовать общие свойства для изменения внешнего вида элемента управления. Расширение разметки TemplateBinding привязывает свойство элемента из шаблона ControlTemplate к общему свойству, которое задается элементом управления. При использовании расширения TemplateBinding свойства элемента управления могут действовать в качестве параметров шаблона. Это означает, что при задании свойства элемента управления соответствующее значение передается в элемент, который содержит TemplateBinding.

Эллипс

Обратите внимание, что свойства Fill и Stroke элемента <Эллипс> привязаны к свойствам элемента управления Foreground и свойствам Background.

ContentPresenter

Элемент <ContentPresenter> также добавляется в шаблон. Так как этот шаблон предназначен для кнопки, необходимо учитывать, что эта кнопка наследует от ContentControl. Кнопка представляет содержимое элемента. Можно задать что-либо внутри кнопки, например обычный текст или даже другой элемент управления. Оба следующих варианта — правильные кнопки:

<Button>My Text</Button>

<!-- and -->

<Button>
    <CheckBox>Checkbox in a button</CheckBox>
</Button>

В обоих приведенных выше примерах текст и флажок задаются как свойство Button.Content. Все, что задано в качестве содержимого, может быть представлено с помощью <ContentPresenter>, что и делается в шаблоне.

Если ControlTemplate применяется к типу ContentControl, такому как Button, выполняется поиск ContentPresenter в дереве элементов. Если ContentPresenter обнаруживается, шаблон автоматически привязывает свойство Content элемента управления к элементу ContentPresenter.

Использование шаблона

Найдите кнопки, которые были объявлены в начале этой статьи.

<StackPanel Margin="10">
    <Label>Unstyled Button</Label>
    <Button>Button 1</Button>
    <Label>Rounded Button</Label>
    <Button>Button 2</Button>
</StackPanel>

Задайте в свойстве Template второй кнопки ресурс roundbutton:

<StackPanel Margin="10">
    <Label>Unstyled Button</Label>
    <Button>Button 1</Button>
    <Label>Rounded Button</Label>
    <Button Template="{StaticResource roundbutton}">Button 2</Button>
</StackPanel>

Если теперь вы запустите проект и посмотрите на результат, то фон кнопки будет иметь овальную форму.

Окно WPF с одной овальной кнопкой

Вы, конечно, заметили, что кнопка не круглая, а вытянутая. Элемент <Ellipse> всегда расширяется и заполняет доступное пространство. Сделайте кнопку круглой, задав в свойствах width и height одно и то же значение:

<StackPanel Margin="10">
    <Label>Unstyled Button</Label>
    <Button>Button 1</Button>
    <Label>Rounded Button</Label>
    <Button Template="{StaticResource roundbutton}" Width="65" Height="65">Button 2</Button>
</StackPanel>

Окно WPF с одной круглой кнопкой

Добавление триггера

Хотя кнопка с примененным шаблоном выглядит иначе, она ведет себя так же, как и любая другая кнопка. При нажатии кнопки срабатывает событие Click. Однако, как вы могли заметить, при наведении указателя мыши на кнопку визуально ничего не меняется. Все визуальные взаимодействия определяются шаблоном.

В системах динамических событий и свойств, предоставляемых WPF, можно отслеживать значение конкретного свойства, а затем при необходимости изменять стиль шаблона. В данном примере вы будете отслеживать свойство IsMouseOver кнопки. Задайте новый цвет для элемента <Ellipse>, который должен отображаться, когда указатель мыши наводится над этим элементом управления. Триггер такого типа называется PropertyTrigger.

Чтобы это работало, необходимо добавить в элемент <Ellipse> имя, на которое можно ссылаться. Задайте имя backgroundElement.

<Ellipse x:Name="backgroundElement" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />

Затем добавьте новый Trigger в коллекцию ControlTemplate.Triggers. Этот триггер будет отслеживать, когда событие IsMouseOver принимает значение true.

<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <Ellipse x:Name="backgroundElement" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />
        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
    <ControlTemplate.Triggers>
        <Trigger Property="IsMouseOver" Value="true">

        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

Затем добавьте метод <Setter> в <Trigger>, который задает для свойства Fill элемента <Ellipse> новое значение цвета.

<Trigger Property="IsMouseOver" Value="true">
    <Setter Property="Fill" TargetName="backgroundElement" Value="AliceBlue"/>
</Trigger>

Запустите проект. Обратите внимание, что при наведении мыши на кнопку цвет элемента <Ellipse> меняется.

При наведении мыши на кнопку WPF цвет заливки изменяется

Использование VisualState

Визуальные состояния определяются и активируются элементом управления. Например, при наведении указателя мыши на элемент управления активируется состояние CommonStates.MouseOver. Изменения свойств можно анимировать на основе текущего состояния элемента управления. В предыдущем разделе > использовалось для изменения фона кнопки на то, когда AliceBlue свойство былоIsMouseOver.true Теперь вместо этого создайте визуальное состояние, которое анимирует изменение этого цвета, обеспечивая плавный переход. Дополнительные сведения об элементе VisualStates см. в разделе Стили и шаблоны в WPF.

Чтобы преобразовать <PropertyTrigger> в анимированное визуальное состояние, сначала нужно удалить из шаблона элемент <ControlTemplate.Triggers>.

<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <Ellipse x:Name="backgroundElement" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />
        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</ControlTemplate>

Затем в раздел <Grid> шаблона элемента управления добавьте элемент <VisualStateManager.VisualStateGroups> с элементом <VisualStateGroup> для параметров CommonStates. Определите два состояния — Normal и MouseOver.

<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup Name="CommonStates">
                <VisualState Name="Normal">
                </VisualState>
                <VisualState Name="MouseOver">
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Ellipse x:Name="backgroundElement" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />
        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</ControlTemplate>

Все анимации, определенные в <VisualState>, применяются при активации соответствующего состояния. Создайте анимации для каждого состояния. Анимации помещаются в элемент <Раскадровка>. Дополнительные сведения о раскадровках см. в статье Общие сведения о Storyboard.

  • Обычная

    Это состояние анимирует заливку эллипса, восстанавливающую цвет Background элемента управления.

    <Storyboard>
        <ColorAnimation Storyboard.TargetName="backgroundElement" 
            Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
            To="{TemplateBinding Background}"
            Duration="0:0:0.3"/>
    </Storyboard>
    
  • MouseOver

    Это состояние анимирует изменение цвета Background эллипса на новый цвет: Yellow.

    <Storyboard>
        <ColorAnimation Storyboard.TargetName="backgroundElement" 
            Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" 
            To="Yellow" 
            Duration="0:0:0.3"/>
    </Storyboard>
    

Теперь <ControlTemplate> должен выглядеть следующим образом.

<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup Name="CommonStates">
                <VisualState Name="Normal">
                    <Storyboard>
                        <ColorAnimation Storyboard.TargetName="backgroundElement" 
                            Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                            To="{TemplateBinding Background}"
                            Duration="0:0:0.3"/>
                    </Storyboard>
                </VisualState>
                <VisualState Name="MouseOver">
                    <Storyboard>
                        <ColorAnimation Storyboard.TargetName="backgroundElement" 
                            Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" 
                            To="Yellow" 
                            Duration="0:0:0.3"/>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Ellipse Name="backgroundElement" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />
        <ContentPresenter x:Name="contentPresenter" HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</ControlTemplate>

Запустите проект. Обратите внимание, что при наведении мыши на кнопку изменение цвета элемента <Ellipse> анимируется.

Мышь перемещается по кнопке WPF, чтобы изменить цвет заливки с визуальным состоянием

Следующие шаги