Управление воспроизведением

69

До сих пор в триггерах событии применялось одно действие — BeginStoryboard, которое запускает анимацию. Однако есть ряд других действий, позволяющих управлять созданной однажды раскадровкой. Эти действия, унаследованные от класса ControllableStoryboardAction, перечислены ниже:

PauseStoryboard

Приостанавливает воспроизведение анимации и сохраняет ее в текущей позиции

Resumestoryboard

Возобновляет воспроизведение приостановленной анимации

StopStoryboard

Останавливает воспроизведение анимации и сбрасывает ее часы в начало

SeekStoryboard

Перепрыгивает в определенную точку временной шкалы анимации. Если анимация в данный момент воспроизводится, то воспроизведение продолжается с новой позиции. Если же анимация приостановлена, она остается приостановленной

SetStoryboardSpeedRatio

Изменяет SpeedRatio всей раскадровки (а не только одной анимации внутри нее)

SkipStoryboardToFill

Перемещает раскадровку в конец ее временной шкалы. Этот период известен как область заполнения (fill region). Для стандартной анимации, у которой свойство FillBehavior установлено в HoldEnd, анимация продолжается для удержания финального значения

Removestoryboard

Удаляет раскадровку, прерывая все текущие выполняющиеся анимации и возвращая свойства в исходные, установленные последний раз значения. Это дает тот же эффект, что и вызов BeginAnimation() для соответствующего элемента с null-объектом анимации

Остановка анимации не эквивалентна ее завершению (если только свойство FillBehavior не установлено в Stop). Это объясняется тем, что даже когда анимация достигает конца своей временной шкалы, она продолжает удерживать финальное значение свойства. Аналогично, когда анимация приостановлена, она продолжает удерживать текущее промежуточное значение свойства. Однако когда анимация остановлена окончательно, она более не применяет никакого значения, и свойство возвращается к своему исходному значению.

Однако с использованием этих действий связан один недокументированный камень преткновения. Чтобы они успешно работали, все триггеры должны быть определены в одной коллекции Triggers. Если поместить действие BeginStoryboard в другую коллекцию триггеров, отличную от PauseStoryboard, то действие PauseStoryboard работать не будет. Для демонстрации проектного решения, которое понадобится применить, рассмотрим пример.

Возьмем окно, которое накладывает два элемента Image точно в одной позиции, используя при этом сетку. Изначально видимым является только одно изображение — дневной снимок знаменитой достопримечательности Торонто.

После запуска анимации прозрачность этого изображения снижается от 1 до 0, постепенно проявляя ночной снимок с того же места. Эффект похож на последовательное отображение ряда фотографий, сделанных через равные промежутки времени.

Чтобы сделать этот пример еще более интересным, в него включено несколько кнопок в нижней части окна, которые позволяют управлять воспроизведением анимации. С помощью этих кнопок можно предпринимать обычные действия по воспроизведению медиафайлов — паузу, продолжение и останов. (Допускается добавить и другие кнопки, например, изменяющие скорость или указывающие определенную точку во времени.)

Ниже показана необходимая разметка:

<Grid>
                <Grid.RowDefinitions>
                    <RowDefinition></RowDefinition>
                    <RowDefinition Height="auto"></RowDefinition>
                </Grid.RowDefinitions>
                <Image Source="night.jpg"></Image>
                <Image Source="day.jpg" Name="imgDay"></Image>
                <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center"
                            Margin="5">
                    <StackPanel.Resources>
                        <Style TargetType="Button">
                            <Setter Property="Padding" Value="5"></Setter>
                            <Setter Property="Margin" Value="0,0,3,0"></Setter>
                        </Style>
                    </StackPanel.Resources>
                    <Button Name="cmd_Start">Старт</Button>
                    <Button Name="cmd_Pause">Пауза</Button>
                    <Button Name="cmd_Resume">Продолжить</Button>
                    <Button Name="cmd_Stop">Стоп</Button>
                    <Button Name="cmd_Middle">В середину</Button>
                </StackPanel>
</Grid>
Управление анимацией

Обычно триггер события помещается в коллекцию Triggers каждой индивидуальной кнопки. Однако, как упоминалось ранее, это не работает с анимациями. Простейшее решение — определить все триггеры событий в одном месте, таком как коллекция Triggers содержащего кнопки элемента, и привязать их с помощью свойства EventTrigger.SourceName. Когда SourceName соответствует свойству Name кнопки, триггер применяется к этой кнопке.

В рассматриваемом примере можно использовать коллекцию Triggers объекта StackPanel, содержащего кнопки. Однако часто проще иметь дело с коллекцией Triggers элемента верхнего уровня, в данном случае — окна. Это позволит перемещать кнопки в разные места пользовательского интерфейса, не теряя их функциональности:

<Window.Triggers>
        <EventTrigger SourceName="cmd_Start" RoutedEvent="Button.Click">
            <BeginStoryboard Name="fadeStoryBoardBegin">
                <Storyboard  Name="MyStoryboard">
                    <DoubleAnimation Storyboard.TargetProperty="Opacity" From="1"
                                     To="0" Storyboard.TargetName="imgDay" Duration="0:0:3"></DoubleAnimation>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
        <EventTrigger SourceName="cmd_Pause" RoutedEvent="Button.Click">
            <PauseStoryboard BeginStoryboardName="fadeStoryBoardBegin"></PauseStoryboard>
        </EventTrigger>
        <EventTrigger SourceName="cmd_Resume" RoutedEvent="Button.Click">
            <ResumeStoryboard BeginStoryboardName="fadeStoryBoardBegin"></ResumeStoryboard>
        </EventTrigger>
        <EventTrigger SourceName="cmd_Stop" RoutedEvent="Button.Click">
            <StopStoryboard BeginStoryboardName="fadeStoryBoardBegin"></StopStoryboard>
        </EventTrigger>
        <EventTrigger SourceName="cmd_Middle" RoutedEvent="Button.Click">
            <SeekStoryboard BeginStoryboardName="fadeStoryBoardBegin"
                            Offset="0:0:1.5"></SeekStoryboard>
        </EventTrigger>
</Window.Triggers>

Обратите внимание, что действию BeginStoryboard должно быть назначено имя (в примере — это fadeStoryboardBegin). Другие триггеры указывают это имя в свойстве BeginStoryboardName, чтобы привязаться к одной и той же раскадровке.

При использовании действий раскадровки вы столкнетесь с одним ограничением. Свойства, которые они предоставляют (такие как SeekStoryboard.Offset и SetStoryboardSpeedRatio.SpeedRatio), не являются свойствами зависимости. Это ограничивает возможности по применению выражений привязки данных. Например, не получится автоматически прочитать свойство Slider.Value и применить его к действию SetStoryboardSpeedRatio.SpeedRatio, т.к. свойство SpeedRatio не принимает выражения привязки данных.

Может показаться, что данное ограничение обходится за счет написания некоторого кода, использующего свойство SpeedRatio объекта Storyboard, но это не сработает. Когда анимация стартует, значение SpeedRatio читается и используется для создания таймера анимации. Если изменить его после этого момента, анимация будет продолжаться в нормальном темпе.

Если требуется динамически изменять скорость и позицию воспроизведения, то единственным решением будет делать это в коде. Класс Storyboard предоставляет методы, обеспечивающие ту же функциональность, что и триггеры, описанные выше, в том числе Begin(), Pause(), Seek(), Stop(), SkipToFill(), SetSpeedRatio() и Remove().

Чтобы получить доступ к объекту Storyboard, необходимо удостовериться, что в коде разметки установлено свойство Name.

He путайте имя объекта Storyboard (которое необходимо для использования раскадровки в коде) с именем действия BeginStoryboard (которое требуется для привязки других действий триггера, манипулирующих раскадровкой). Для предотвращения конфиликтов можно принять соглашение по именованию, например, добавлять слово Begin в конец имени BeginStoryboard.

Теперь необходимо просто написать соответствующий обработчик событий и воспользоваться методами объекта Storyboard. (Вспомните, что простое изменение свойств раскадровки, подобных SpeedRatio, не даст никакого эффекта. Они просто конфигурируют настройки, которые применяются на момент запуска анимации.)

Ниже приведен обработчик событий, реагирующий на перетаскивание ползунка Slider. Он получает значение ползунка (находящееся в пределах от 0 до 3) и использует его для применения нового масштаба скорости:

private void sld_ValueChanged(object sender, RoutedPropertyChangedEventArgs e)
{
    MyStoryboard.SetSpeedRatio(this, sld.Value);
}

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

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