Шаблон элемента управления по умолчанию

56

Теперь можно поместить все части примера из предыдущей статьи в шаблон элемента управления по умолчанию. Корневой элемент — это Grid с двумя строками, удерживающий область содержимого (в верхней строке) и кнопку для переворачивания (в нижней строке). Область содержимого заполняется двумя перекрывающимися элементами Border, которые представляют переднюю и заднюю стороны содержимого, но только один из них видим в каждый конкретный момент времени.

Чтобы заполнить переднюю и заднюю области содержимого, FlipPanel использует ContentPresenter. Это почти такой же прием, который применялся в примере с пользовательской кнопкой, за исключением того, что здесь нужны два элемента ContentPresenter, по одному для каждой стороны FlipPanel. Элемент FlipPanel также включает отдельный элемент Border, в который помещен каждый ContentPresenter.

Это позволяет потребителю элемента управления определить переворачиваемую область содержимого, устанавливая несколько простых свойств в FlipPanel (BorderBrush, BorderThickness, Background и CornerRadius), вместо того, чтобы добавлять Border вручную.

Ниже показана базовая структура шаблона элемента управления по умолчанию:

<Style TargetType="{x:Type local:FlipPanel}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:FlipPanel}">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="auto"></RowDefinition>
                            <RowDefinition Height="auto"></RowDefinition>
                        </Grid.RowDefinitions>
                        <!-- Передняя область -->
                        <Border Background="{TemplateBinding Background}" x:Name="FrontContent"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            CornerRadius="{TemplateBinding CornerRadius}">
                            <ContentPresenter Content="{TemplateBinding FrontContent}"></ContentPresenter>
                        </Border>
                        <!-- Задняя область -->
                        <Border Background="{TemplateBinding Background}" x:Name="BackContent"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            CornerRadius="{TemplateBinding CornerRadius}">
                            <ContentPresenter Content="{TemplateBinding BackContent}"></ContentPresenter>
                        </Border>
                        <!-- Кнопка -->
                        <ToggleButton Grid.Row="1" x:Name="FlipButton" Margin="0,10,0,0" Width="19"
                                      Height="19">
                        </ToggleButton>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

При создании шаблона элемента управления по умолчанию лучше избегать жесткого кодирования деталей, которые потребитель этого элемента может захотеть настроить по-своему. Вместо этого необходимо применять выражения привязки шаблонов. В рассматриваемом примере устанавливаются несколько свойств с использованием выражений привязки шаблона: BorderBrush, BorderThickness, CornerRadius, Background, FrontContent и BackContent. Чтобы установить значения по умолчанию для этих свойств, понадобится добавить дополнительные средства установки к стилю по умолчанию элемента управления.

Кнопка переворачивания

Показанный в предыдущем примере шаблон элемента управления включает ToggleButton. Однако в результате он использует внешний вид ToggleButton, т.е. кнопка выглядит как совершенно обычная, с традиционным текстурированным фоном. Для FlipPanel это не подходит.

Хотя можно поместить внутри ToggleButton любое содержимое, FlipPanel требует немного большего. Вместо стандартного фона необходимо менять внешность элементов внутри в зависимости от состояния ToggleButton. Как было показано на рисунке, кнопка ToggleButton определяет способ "переворачивания" содержимого (первоначально стрелка вправо, когда отображается лицевая сторона, и стрелка влево, когда отображается изнанка). Это помогает яснее понять назначение кнопки.

Чтобы создать этот эффект, нужно спроектировать специальный шаблон элемента управления для ToggleButton. Этот шаблон элемента управления может включать элементы-фигуры, которые рисуют нужные стрелки. В данном примере ToggleButton рисуется с помощью элемента Ellipse (отображает кружок) и элемента Path (для стрелки), и оба они помещаются в Grid из одной ячейки. Для ToggleButton требуется еще одна деталь — трансформация RotateTransform, которая поворачивает стрелку с одной стороны в другую. Эта RotateTransform будет использоваться при создании анимаций состояния:

<ToggleButton Grid.Row="1" x:Name="FlipButton" Margin="0,10,0,0" Width="19"
                                      Height="19" RenderTransformOrigin="0.5,0.5">
      <ToggleButton.Template>
            <ControlTemplate>
                   <Grid>
                        <Ellipse Stroke="#FFA9A9A9" Fill="AliceBlue"></Ellipse>
                        <Path Data="M1,1.5L4.5,5 8,1.5" Stroke="#FF666666" StrokeThickness="2"
                                HorizontalAlignment="Center" VerticalAlignment="Center"></Path>
                  </Grid>
            </ControlTemplate>
      </ToggleButton.Template>
      <ToggleButton.RenderTransform>
              <RotateTransform x:Name="FlipButtonTransform" Angle="-90"></RotateTransform>
      </ToggleButton.RenderTransform>
</ToggleButton>

Определение анимаций состояния

Анимации состояния — наиболее интересная часть шаблона элемента управления. Они являются ингредиентами, которые обеспечивают поведение "переворачивания". Кроме того, это детали, которые наиболее вероятно будут изменяться, если разработчик создаст специальный шаблон для FlipPanel.

Для определения групп состояний понадобится добавить элемент VisualStateManager.VisualStateGroups в корневой узел шаблона элемента управления.

Чтобы к шаблону можно было добавить элемент VisualStateManager, шаблон должен использовать панель компоновки. Эта панель компоновки содержит оба объекта Visual для элемента управления и невидимый VisualStateManager. Диспетчер VisualStateManager определяет раскадровки с анимациями, которые может применять элемент управления, чтобы в надлежащий момент изменять свой внешний вид.

Внутри элемента VisualStateManager можно создавать группы состояний, используя соответствующим образом именованные элементы VisualStateGroup. В каждую группу VisualStateGroup добавляется элемент VisualState для каждого визуального состояния. В случае FlipPanel имеется одна группа, которая содержит два визуальных состояния.

Каждое состояние соответствует раскадровке с одной или более анимациями. Если эти раскадровки существуют, они инициируются в соответствующие моменты времени. (Если нет, то элемент управления не должен генерировать ошибку.)

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

<VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="ViewStates">
                                <VisualState x:Name="Normal">
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetName="BackContent"
                                                         Storyboard.TargetProperty="Opacity"
                                                         To="0" Duration="0"></DoubleAnimation>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Flipped">
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetName="FlipButtonTransform"
                                                         Storyboard.TargetProperty="Angle"
                                                         To="90" Duration="0"></DoubleAnimation>
                                        <DoubleAnimation Storyboard.TargetName="FrontContent"
                                                         Storyboard.TargetProperty="Opacity"
                                                         To="0" Duration="0"></DoubleAnimation>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>

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

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

Процесс переворачивания — это переход, который происходит непосредственно перед тем, как FlipPanel входит в перевернутое состояние, и он не является частью самого состояния. (Это различие между состояниями и переходами важно, потому что некоторые элементы управления имеют анимации, которые выполняются во время нахождения в каком-то состоянии.)

Пройди тесты
Лучший чат для C# программистов