Триггеры, изменяющие свойства
39WPF --- Шаблоны и пользовательские элементы управления WPF --- Триггеры, изменяющие свойства
Если вы опробуете кнопку, которая была создана в предыдущем разделе, то обнаружите, что в результате получилось одно сплошное разочарование. По сути все, что вышло — не более чем прямоугольник со скругленными углами; при наведении на него курсора мыши и щелчка никакого визуального отклика не происходит. Кнопка совершенно инертна.
Эту проблему легко решить, добавив к шаблону элемента управления триггеры. Как известно, триггеры можно использовать для изменения одного или более свойств в ответ на изменения другого свойства. Минимальный набор свойств, на которые понадобится отреагировать в кнопке — это IsMouseOver и IsPressed. Ниже приведена модифицированная версия шаблона элемента управления, который изменяет цвет в ответ на изменение этих свойств:
<ControlTemplate x:Key="ButtonTemplate" TargetType="Button">
<Border BorderBrush="Orange" BorderThickness="2" CornerRadius="2" Name="border"
TextBlock.Foreground="White">
<Border.Background>
<LinearGradientBrush>
<GradientStopCollection>
<GradientStop Offset="0" Color="LimeGreen"></GradientStop>
<GradientStop Offset="1" Color="LightBlue"></GradientStop>
</GradientStopCollection>
</LinearGradientBrush>
</Border.Background>
<ContentPresenter RecognizesAccessKey="True" Margin="{TemplateBinding Padding}"></ContentPresenter>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="Background" Value="Red"></Setter>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="border" Property="BorderBrush" Value="Yellow"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Есть другое изменение, которое заставляет этот шаблон работать. Элементу Border назначено имя, и это имя используется для установки свойства TargetName каждого объекта Setter. Таким образом, Setter может обновить свойства Background и BorderBrush элемента Border, указанного в шаблоне. Использование имен — простейший способ гарантировать, что определенная часть шаблона будет обновлена. Можно было бы создать типизированное в отношении элементов правило, которое касалось бы всех элементов Border (поскольку известно, что у кнопки есть только одна граница), но данный подход и более ясен, и более гибок, если позже понадобится изменить шаблон.
Каждой кнопке (и большинству других элементов управления) необходим еще один элемент — индикатор фокуса. Изменить существующую границу, добавив к ней эффект фокуса, не удастся, но можно легко добавить другой элемент, который воспроизводит его, и просто отображать или скрывать этот элемент с помощью триггера в зависимости от свойства Button.IsKeyboardFocused.
Хотя эффект фокуса можно реализовать множеством разных способов, в показанном ниже примере просто добавляется прозрачный элемент Rectangle с прерывистым контуром. Rectangle не обладает способностью удерживать в себе дочерний элемент, так что нужно гарантировать перекрытие Rectangle другого содержимого. Простейший способ сделать это — поместить Rectangle и ContentPresenter в Grid с одной ячейкой.
Вот как выглядит усовершенствованный шаблон с поддержкой фокуса:
<Grid>
<Rectangle Name="FocusCue" Visibility="Hidden" Stroke="Black"
StrokeThickness="1.5" StrokeDashArray="1 2" SnapsToDevicePixels="True"></Rectangle>
<ContentPresenter RecognizesAccessKey="True" Margin="{TemplateBinding Padding}"></ContentPresenter>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="Background" Value="Red"></Setter>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="border" Property="BorderBrush" Value="Yellow"></Setter>
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter TargetName="FocusCue" Property="Visibility" Value="Visible"></Setter>
</Trigger>
Объект Setter находит элемент, который необходимо изменить, с использованием свойства TargetName (которое в данном примере указывает на прямоугольник FocusCue).
Такой прием сокрытия и отображения элементов в ответ на триггер является удобным строительным блоком для многих шаблонов. Его можно использовать для замены визуального представления элемента управления чем-то совершенно иным, когда изменяется его состояние. (Например, кнопка после щелчка может изменять свою форму с прямоугольной на эллиптическую, скрывая прямоугольник и показывая эллипс.)
На рисунке показаны три кнопки, в которых применяется усовершенствованный шаблон. Вторая кнопка находится в фокусе (на что указывает пунктирный прямоугольник), а над третьей кнопкой в данный момент расположен курсор мыши:
Должно быть, вы заметили сходство между шаблонами и стилями. Оба позволяют изменить внешний вид элемента, обычно во всем приложении. Однако контекст применения стилей намного более ограниченный. Они могут изменять свойства элемента управления, но не заменяют полностью визуального дерева, составляющего различные элементы.
Простая кнопка, которая была показана ранее, включает средства, которые невозможно воспроизвести одними только стилями. Хотя стили можно было использовать для установки фона кнопки, больше хлопот будет при изменении цвета фона, когда кнопка нажата, потому что встроенный шаблон для кнопки уже содержит триггер для этой цели. Также не получилось бы просто добавить прямоугольник фокуса.
Шаблоны элементов управления — это открытая дверь для многих экзотических типов кнопок, которые немыслимо реализовать стилями. Например, вместо применения прямоугольного контура можно создать кнопку в виде эллипса либо воспользоваться Path для рисования более сложной фигуры. Все, что для этого понадобится — это классы рисования. Остаток разметки — даже триггеры, которые переключают цвет фона из одного состояния в другое — потребуют относительно небольших изменений.