Шаблоны и визуальные состояния
Поведение элемента управления заложено в его классе, а внешний вид — в шаблоне. ControlTemplate — это небольшое дерево элементов, которое строится для каждого использующего его элемента управления, поэтому кнопке можно придать любой вид, не меняя её работы. Визуальные состояния меняют свойства, когда меняется состояние элемента управления: наведение, нажатие, фокус, отметка.
Шаблон
<!-- A control template replaces how a control looks; TemplateBinding passes the control's own properties into it. -->
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<ControlTemplate x:Key="FlatButton" TargetType="Button">
<Border Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}">
<ContentPresenter Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</ControlTemplate>
<Style TargetType="Button" Template="{StaticResource FlatButton}" Background="#FF3C78D8" Padding="20,8">
<Style.StateGroups>
<VisualStateGroup Name="CommonStates">
<VisualState Name="Hovered" State="Hovered" Background="#FF5A87E6" Duration="0:0:0.15" />
<VisualState Name="Pressed" State="Pressed" Background="#FF2C5AA8" />
</VisualStateGroup>
</Style.StateGroups>
</Style>
</StackPanel.Resources>
<Button>Play</Button>
</StackPanel>
TargetTypeговорит, для какого элемента управления шаблон.{TemplateBinding Background}передаёт в шаблон собственныйBackgroundэлемента управления, поэтому стиль или локальное значение у кнопки по-прежнему доходит до рамки внутри неё.ContentPresenterсContent="{TemplateBinding Content}"показывает содержимое элемента управления (здесь — текст «Play»).
Обычно шаблон задаёт стиль (Template="{StaticResource FlatButton}"), и его получает каждая кнопка с этим стилем. См.
Ресурсы, стили и темы.
Именованные части
Некоторые элементы управления ищут в шаблоне элементы с определёнными именами и управляют ими: ползунок двигает свой
PART_Thumb, индикатор выполнения меняет размер PART_Fill. Шаблон для таких элементов управления должен содержать эти
части с нужными именами, иначе соответствующее поведение пропадёт.
| Элемент управления | Части |
|---|---|
Slider |
PART_Thumb |
ProgressBar |
PART_Fill |
SplitPane |
PART_Divider |
Expander |
PART_Header |
TreeViewItem |
PART_Indent, PART_Expander |
Dropdown |
PART_ToggleButton |
Визуальные состояния
StateGroups стиля содержат VisualStateGroup; каждое VisualState говорит,
каким состояниям элемента управления оно соответствует и какие значения свойств использовать, пока они активны:
<VisualState State="Hovered" Background="#FF5A87E6" Duration="0:0:0.15" />
State— одно или несколько значений изNormal,Hovered,Pressed,Disabled,Focused,Touching,Checked,Expanded,Selected,HighlightedиInvalidчерез запятую:State="Selected,Hovered"совпадает, только когда включены оба.- Элемент управления сам выставляет эти флаги, и часто включены сразу несколько: при нажатии мышью
Hoveredостаётся включённым, а кнопка, на которую перешли клавишей Tab, остаётсяFocused, пока на неё наведён указатель. Duration(иEasing) анимируют переход вместо мгновенного переключения.
Локальное значение сильнее визуального состояния: кнопка с заданным у неё Background="Red" не меняет цвет при
наведении.
Какое состояние активно
В каждой группе не больше одного активного состояния. Это состояние, совпадающее с наибольшим числом текущих флагов
элемента управления; если два совпадают с одинаковым числом, побеждает объявленное последним. Поэтому перечисляйте
состояния от наименее важного к самому важному: Hovered, затем Pressed, — и при нажатии виден цвет нажатия, хотя
указатель по-прежнему наведён на кнопку. Если ни одно состояние не совпадает, действуют собственные значения стиля.
Зачем несколько групп
Поскольку в группе одно активное состояние, состояния, которые должны сочетаться, помещают в разные группы. Рамка фокуса и фон при наведении независимы, поэтому тема держит их раздельно:
<!-- One active state per group: focus gets its own group, so a focused button still shows hover and press. -->
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<Style TargetType="Button" Background="#FF3C78D8" BorderBrush="#FF3C78D8" BorderThickness="2" Padding="20,8">
<Style.StateGroups>
<!-- Same name as the theme's group: replaces it, so the theme's pressed color doesn't leak in. -->
<VisualStateGroup Name="CommonStates">
<VisualState State="Hovered" Background="#FF5A87E6" />
<VisualState State="Pressed" Background="#FF2C5AA8" />
</VisualStateGroup>
<VisualStateGroup Name="FocusStates">
<VisualState State="Focused" BorderBrush="#FFFFD24A" />
</VisualStateGroup>
</Style.StateGroups>
</Style>
</StackPanel.Resources>
<Button>Continue</Button>
</StackPanel>
В одной группе наведение и фокус соперничали бы, и кнопка в фокусе теряла бы рамку, как только на неё наводят указатель. В двух группах у каждой своё активное состояние. Используйте одну группу для состояний, которые задают одно и то же свойство и сменяют друг друга (наведение, нажатие), и отдельную группу для каждой независимой стороны: фокуса, отметки, доступности.
Если активные состояния разных групп задают одно и то же свойство, побеждает группа, объявленная позже; когда её состояние заканчивается, возвращается значение более ранней группы.
Зачем нужны имена
Name группы — это то, как стиль переопределяет унаследованные состояния. Ваш стиль применяется поверх стиля темы
для того же элемента управления и поверх своего стиля BasedOn; группа с тем же именем, что у них, заменяет их
группу. В примере выше группа названа CommonStates, как в теме, поэтому цвета наведения и нажатия из темы исчезают, а
группа темы FocusStates остаётся. Группа с новым именем или без имени добавляется рядом с их группами.
Name состояния необязателен и ничего не меняет: это подпись для читателей, инструментов дизайна и диагностики.
Типичные ошибки
- Шаблон заменяет весь внешний вид. Всё, что рисовал шаблон темы (рамка, внутренние отступы, рамка фокуса), пропадает, если ваш шаблон этого не рисует.
- Забытый
ContentPresenter. Без него текст или значок кнопки не показывается. - Отсутствующие части. Шаблон ползунка без
PART_Thumbрисуется, но перетаскивать в нём нечего. - Независимые состояния в одной группе. Фокус, наведение и отметка в одной группе сменяют друг друга, а не сочетаются.
- Случайная замена группы темы. Если назвать свою группу
CommonStates, пропадут все общие состояния темы, а не только переопределённые. Повторите те, которые хотите сохранить, или дайте своей группе другое имя.
См. также
- Ресурсы, стили и темы.
- Анимации — анимация из кода.
- Шаблоны темы в
DefaultTheme.xml— готовые примеры для каждого элемента управления.