Переходы между состояниями

37

Переход (transition) — это анимация, которая начинается с текущего состояния и заканчивается новым состоянием. Одно из преимуществ модели переходов состоит в том, что создавать раскадровку для этой анимации не придется.

Переходы применяются к группам состояний. После определения переход должен быть добавлен в коллекцию VisualStateGroup.Transitions. Переход по умолчанию удобен, но это "универсальное" решение, которое не всегда подходит. Например, может понадобиться, чтобы переходы FlipPanel происходили с разной скоростью, в зависимости от того, к какому состоянию они применяются. Чтобы настроить это, необходимо определить множество переходов и устанавливать свойство То для указания момента начала перехода.

Для еще более тонкого контроля можно создавать специальные анимации переходов, которые заменяют собой автоматически сгенерированные переходы, обычно используемые WPF. Причин создания специальных переходов несколько: управление ходом анимации; использование функций плавности анимации; последовательный запуск нескольких анимаций; воспроизведение звука одновременно с анимацией.

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

<VisualStateGroup.Transitions>
                 <VisualTransition GeneratedDuration="0:0:0.7" To="Flipped">
                             <Storyboard>
                                     <DoubleAnimation Storyboard.TargetName="FlipButtonTransform"
                                                             Storyboard.TargetProperty="Angle" 
                                                             To="90" Duration="0:0:0.2"></DoubleAnimation>
                             </Storyboard>
                </VisualTransition>
                <VisualTransition GeneratedDuration="0:0:0.7" To="Normal">
                             <Storyboard>
                                    <DoubleAnimation Storyboard.TargetName="FlipButtonTransform"
                                                             Storyboard.TargetProperty="Angle" 
                                                             To="-90" Duration="0:0:0.2"></DoubleAnimation>
                             </Storyboard>
               </VisualTransition>
    </VisualStateGroup.Transitions>

При использовании специального перехода все равно необходимо устанавливать свойство VisualTransition.GeneratedDuration для задания длительности анимации. Без этой детали VisualStateManager не сможет использовать переход и переключит элемент в новое состояние немедленно. (Действительное значение времени не оказывает влияния на специальный переход, потому что применяется только к автоматически генерированным анимациям.)

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

Связывание элементов

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

Секрет кроется в методе OnApplyTemplate(), который также использовался для установки привязок ColorPicker. Метод OnApplyTemplate() для FlipPanel извлекает кнопку ToggleButton для частей FlipButton и FlipButtonAlternate, и присоединяет обработчики событий к каждой из них, чтобы они могли реагировать на щелчки для переворачивания элемента управления. Метод OnApplyTemplate() завершается вызовом специального метода ChangeVisualState(), который обеспечивает соответствие визуального представления элемента управления его текущему состоянию:

public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            ToggleButton flipButton = base.GetTemplateChild("FlipButton") as ToggleButton;
            if (flipButton != null) flipButton.Click += flipButton_Click;

            ToggleButton flipButtonAlternate = base.GetTemplateChild("FlipButtonAlternate") as ToggleButton;
            if (flipButtonAlternate != null)
                flipButtonAlternate.Click += flipButton_Click;

            this.ChangeVisualState(false);
        }

При вызове GetTemplateChild() необходимо указать строковое имя нужного элемента. Во избежание возможных ошибок, эту строку можно объявить константной в элементе управления. Затем эту константу можно использовать в атрибуте TemplatePart и при вызове GetTemplateChild().

Ниже приведен очень простой обработчик события, который позволяет пользователю щелкать на ToggleButton и переворачивать панель:

private void flipButton_Click(object sender, RoutedEventArgs e)
{
   this.IsFlipped = !this.IsFlipped;
}

К счастью, вручную инициировать анимации состояния не понадобится. Точно также не нужно создавать или инициировать анимации переходов. Для смены одного состояния на другое вызывается статический метод VisualStateManager.GoToState().

При этом передается ссылка на объект элемента управления, состояние которого изменяется, имя нового состояния и булевское значение, определяющее, нужно ли показывать переход. Это значение должно быть true, если речь идет об инициированном пользователем изменении (например, когда пользователь щелкает на ToggleButton), и false — когда речь идет об установке свойства (например, при установке начального значения свойства IsFlipped в разметке страницы).

Поддержка различных состояний элемента управления может быть запутанной. Чтобы избежать засорения кода элемента управления множественными вызовами GoToState(), в большинстве элементов добавляется специальный метод, подобный ChangeVisualState() в FlipPanel. Этот метод отвечает за применение корректного состояния к каждой группе состояний. Код внутри него использует блок if (или оператор switch) для применения текущего состояния к каждой группе. Такой подход работает, поскольку вполне допустимо вызывать GoToState() с именем текущего состояния. В ситуации, когда текущее состояние и запрошенное состояние совпадают, ничего не происходит.

Вот как выглядит код метода ChangeVisualState() для FlipPanel:

private void ChangeVisualState(bool useTransitions)
        {
            if (!this.IsFlipped)
            {                
                VisualStateManager.GoToState(this, "Normal", useTransitions);
            }
            else
            {
                VisualStateManager.GoToState(this, "Flipped", useTransitions);                
            }

            // Disable flipped side to prevent tabbing to invisible buttons.            
            UIElement front = FrontContent as UIElement;
            if (front != null)
            {
                if (IsFlipped)
                {
                    front.Visibility = Visibility.Hidden;                    
                }
                else
                {
                    front.Visibility = Visibility.Visible;                    
                }
            }
            UIElement back = BackContent as UIElement;
            if (back != null)
            {
                if (IsFlipped)
                {
                    back.Visibility = Visibility.Visible;                    
                }
                else
                {
                    back.Visibility = Visibility.Hidden;                    
                }
            }        
        }       

Обычно метод ChangeVisualState() (или его эквивалент) вызывается в следующих местах:

Как было сказано, элемент управления FlipPanel замечательно гибок. Например, его можно использовать без кнопки ToggleButton и переключать программно (например, когда пользователь щелкает на каком-то другом элементе управления). Кроме того, можно поместить одну или две кнопки переключения в шаблон элемента и предоставить пользователю возможность управлять ими.

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