{"id":2180,"date":"2022-01-20T09:06:48","date_gmt":"2022-01-20T14:06:48","guid":{"rendered":"https:\/\/badecho.com\/?p=2180"},"modified":"2022-11-15T20:18:25","modified_gmt":"2022-11-16T01:18:25","slug":"canceling-animations","status":"publish","type":"post","link":"https:\/\/badecho.com\/index.php\/2022\/01\/20\/canceling-animations\/","title":{"rendered":"Canceling WPF Animations Made Simple"},"content":{"rendered":"\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large is-resized\"><img loading=\"lazy\" src=\"https:\/\/badecho.com\/wp-content\/uploads\/2022\/01\/CancelAnimations.png\" alt=\"Canceling WPF Animations Made Simple\" class=\"wp-image-2183\" width=\"857\" height=\"191\" srcset=\"https:\/\/badecho.com\/wp-content\/uploads\/2022\/01\/CancelAnimations.png 857w, https:\/\/badecho.com\/wp-content\/uploads\/2022\/01\/CancelAnimations-300x67.png 300w, https:\/\/badecho.com\/wp-content\/uploads\/2022\/01\/CancelAnimations-768x171.png 768w, https:\/\/badecho.com\/wp-content\/uploads\/2022\/01\/CancelAnimations-480x107.png 480w\" sizes=\"(max-width: 857px) 100vw, 857px\" \/><\/figure><\/div>\n\n\n\n<p>Not long ago, I had the opportunity to debut the new <a href=\"https:\/\/badecho.com\/index.php\/2020\/10\/19\/apocalypse-system\/\" target=\"_blank\" rel=\"noreferrer noopener\">Apocalypse<\/a> module for my <a href=\"https:\/\/badecho.com\/index.php\/what-is-omnified\/\" target=\"_blank\" rel=\"noreferrer noopener\">Omnified<\/a> <em>Vision<\/em> overlay application, live on <a href=\"https:\/\/twitch.tv\/omni\" target=\"_blank\" rel=\"noreferrer noopener\">stream<\/a>. It was something I&#8217;d been working on for a little while, its importance amplified by that fact that it was to be the final piece of the Omnified experience (for now).<\/p>\n\n\n\n<p>Naturally, given how I pine for (and even sometimes get!) flawless performances, I subjected the Apocalypse <em>Vision <\/em>module to a little bit of playtesting prior to the stream. This meant running around in game, getting smacked by enemies, dying lots, and marveling at the new visualization magic that is <em>Vision<\/em>.<\/p>\n\n\n\n<p>Many issues were corrected and fixed, however there was one <em>glaring<\/em> issue: whenever there were many, many new Apocalypse events within a few seconds, the screen would be absolutely cluttered with a whole batch of new items, all running their (eye-grabbing) &#8220;reveal&#8221; notifications.<\/p>\n\n\n\n<p>So, in order to alleviate this, the idea was simple: upon a new event being added to the collection of Apocalypse events, any preexisting and in progress animations would have their playback canceled. Annoyingly, doing this wasn&#8217;t as easy as I thought it&#8217;d be. In this article, we&#8217;ll go over just how I ended up doing it.<\/p>\n\n\n\n<h2>Visualizing the Problem<\/h2>\n\n\n\n<p>The Apocalypse <em>Vision<\/em> module&#8217;s root view is the <code>ApocalypseView<\/code> type, and it occupies a particular &#8220;anchor point&#8221; on <em>Vision<\/em>&#8216;s overlay. Typically, this will be the top-center or bottom-center of the screen.<\/p>\n\n\n\n<p>The <code>ApocalypseView<\/code> is a collection-based view that is composed of an <code>ItemsControl<\/code> bound to a collection of <code>ApocalypseEventViewModel<\/code> children. Each child&#8217;s data context is then resolved to a particular view dependent on the nature of the Apocalypse event itself.<\/p>\n\n\n\n<p>Each child view is decorated by the <code>ApocalypseView<\/code> in a <code>ContentControl<\/code> that is essentially composed of two <code>Border<\/code> controls surrounding the actual child view content; it is these two <code>Border<\/code> controls where the animations we desire to cancel are being ran.<\/p>\n\n\n\n<p>There are two animation storyboards at play: one for the outer border, and one for the inner border. The first storyboard, which we can refer to as the <code>RevealBounce<\/code>, causes the event to &#8220;bounce&#8221; into view, and also causes the otherwise transparent background of the event to be filled with color temporarily.<\/p>\n\n\n\n<p>The second storyboard, which we can refer to as the <code>RevealShimmer<\/code>, begins playing a few seconds after <code>RevealBounce<\/code>, and causes the background of the event to &#8220;shimmer&#8221; slightly, further drawing attention to this newly arriving event.<\/p>\n\n\n\n<p>While these animations are very neat on their own, they end up blocking too much of the game&#8217;s screen when there are many of them appearing at once, as can be seen below:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large is-resized\"><img loading=\"lazy\" src=\"https:\/\/badecho.com\/wp-content\/uploads\/2022\/01\/AnimationsToCancel.gif\" alt=\"Shows a growing collection of child controls whose animations are starting to clutter up the screen. We must cancel them!\" class=\"wp-image-2185\" width=\"873\" height=\"190\"\/><figcaption>The &#8220;new item&#8221; reveal animation becomes a bit too much (in terms of screen real estate) when multiple events are happening per second.<\/figcaption><\/figure><\/div>\n\n\n\n<p>As <em>Vision<\/em> is meant to be used as an overlay on a game, all the controls used in its various modules are mostly transparent, with the text designed to be readable on top of a variety of backgrounds. This is so we can still see everything we need to in the game we&#8217;re playing, and having too many of these just-described animations running goes against this goal.<\/p>\n\n\n\n<h2>How To Cancel an Animation?<\/h2>\n\n\n\n<p>There are a number of ways we can cancel an in progress <code>Storyboard<\/code>, most of which are fairly inconvenient when trying to rock a purely MVVM approach, where there&#8217;s almost no logic in the code-behind of our XAML views.<\/p>\n\n\n\n<p>Let&#8217;s go over these different ways.<\/p>\n\n\n\n<h3>Trigger Actions<\/h3>\n\n\n\n<p>We typically begin playback of an animation using the <code><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.windows.media.animation.beginstoryboard?view=windowsdesktop-6.0\" target=\"_blank\" rel=\"noreferrer noopener\">BeginStoryboard<\/a><\/code> action, triggered by either an event or a change of a bound property. <\/p>\n\n\n\n<p>Likewise, we can also abort the playback of an animation using another trigger action, such as the <code><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.windows.media.animation.stopstoryboard?view=windowsdesktop-6.0\" target=\"_blank\" rel=\"noreferrer noopener\">StopStoryboard<\/a><\/code> and <code><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.windows.media.animation.skipstoryboardtofill?view=windowsdesktop-6.0\" target=\"_blank\" rel=\"noreferrer noopener\">SkipStoryboardToFill<\/a><\/code> actions, which we can use like so:<\/p>\n\n\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\n&lt;StopStoryboard BeginStoryboardName=&quot;RevealBounceAction&quot;\/&gt;\n<\/pre>\n\n\n<p>This approach would be purely XAML-based, so it is rather palatable to my tastes at least; however, it does require a proper source of triggering, such as a <code>RoutedEvent<\/code>. In this particular case, this requirement essentially precludes this approach from being a valid one to take, as the communication regarding a new child item must occur from a parent to its children, a direction of communication that routed events simply do not support.<\/p>\n\n\n\n<h3>Individual Animation Methods<\/h3>\n\n\n\n<p>The next way of canceling the playback of an animation involves invoking methods on the individual animations found in a storyboard themselves. Doing it this way would obviously require us to use C# instead of XAML.<\/p>\n\n\n\n<p>Animations, such as the <code><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.windows.media.animation.doubleanimation?view=windowsdesktop-6.0\" target=\"_blank\" rel=\"noreferrer noopener\">DoubleAnimation<\/a><\/code> type, have a <code>BeginAnimation<\/code> method which accepts two parameters: the <code>DependencyProperty<\/code> being animated, and the <code>Storyboard<\/code> to use as the animation timeline.<\/p>\n\n\n\n<p>Calling this method while providing the <code>DependencyProperty<\/code> and <code>null<\/code> for the timeline will essentially cancel the animation.<\/p>\n\n\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\ndoubleAnimation.BeginAnimation(UIElement.OpacityProperty, null);\n<\/pre>\n\n\n<p>This particular way of canceling an animation is painful for a number of reasons. First of all, we&#8217;d need to do it for every single animation that might exist in a particular storyboard, each of which is probably going to be problematic to retrieve in code-behind using a generalized method.<\/p>\n\n\n\n<p>Secondly, we would need to provide the actual <code>DependencyProperty<\/code> that&#8217;s being animated. That&#8217;s another requirement that would end up being painful to do, especially if we mean to do so using a generalized solution.<\/p>\n\n\n\n<h3>Storyboard Methods<\/h3>\n\n\n\n<p>A final way of canceling animations is to invoke the same methods on a <code>Storyboard<\/code> instance that the trigger actions, described in the first approach above, invoke.<\/p>\n\n\n\n<p>Using a <code>Storyboard<\/code> instance, we can call something like <code>SkipToFill<\/code>, which only would require a target <code>DependencyObject<\/code> to be passed as a parameter.<\/p>\n\n\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nstoryboard.SkipToFill(targetObject);\n<\/pre>\n\n\n<p>Using this approach will probably be the easiest if we wish to concoct a generalized solution for canceling in progress animations. All it requires are instances of the <code>Storyboard<\/code> and target <code>DependencyObject<\/code> in question.<\/p>\n\n\n\n<p>And, because it looks like we&#8217;re going to be using an approach requiring C# code, the best way to implement it, while maintaining our pure use of XAML, is with a custom attached behavior.<\/p>\n\n\n\n<h2>A Behavior to Cancel Animations<\/h2>\n\n\n\n<p>Attached behaviors are simply attached properties that influence the way the target <code>DependencyObject<\/code> instances they are attached to act. While you don&#8217;t always need a set of foundational objects to create your own behaviors, I will be using the <a href=\"https:\/\/github.com\/BadEcho\/core\/tree\/master\/src\/Presentation\" target=\"_blank\" rel=\"noreferrer noopener\">Bad Echo Presentation framework<\/a>, part of the <a href=\"https:\/\/github.com\/BadEcho\/core\" target=\"_blank\" rel=\"noreferrer noopener\">Bad Echo technologies<\/a> that I&#8217;ve been publishing.<\/p>\n\n\n\n<p>This behavior will be attached to the <code>DependencyObject<\/code> being animated in XAML. It then requires two more things for its successful operation: 1) the storyboard resource to exert control over, and 2) a means of receiving a notification to go ahead and cancel the animation.<\/p>\n\n\n\n<p>Having to provide more than one piece of data to an attached behavior or property is an innately difficult thing to do in XAML. This is due to how these behaviors are typically applied to an element, which is by including them using attribute syntax, setting the attached property exposed by the behavior to a single binding or value.<\/p>\n\n\n\n<p>For example, we could attach a <code>CancelableAnimationBehavior<\/code> to an object while providing the <code>Storyboard<\/code> to control like so:<\/p>\n\n\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\n&lt;Border badEcho:CancelableAnimationBehavior.Storyboard=&quot;{StaticResource NameOfStoryboard}&quot;\/&gt;\n<\/pre>\n\n\n<p>But how do we also provide the notification object? You simply cannot using attribute syntax; rather, such things become much easier to do if you use property element syntax. This requires, however, a &#8220;properties&#8221; type that can hold the required pieces of data.<\/p>\n\n\n\n<p>This properties type cannot just be a simple class, however, as it most likely will require the ability to support bindings and therefore an inheritance context. Additionally, the behavior class has to also be crafted in a special way that can support an attachable child element.<\/p>\n\n\n\n<p>Luckily for us, or for me rather, the Bad Echo Presentation framework provides a number of powerful foundational behavior-related objects we can use to craft the types that we need.<\/p>\n\n\n\n<h3>CancelableAnimationState<\/h3>\n\n\n\n<p>Let&#8217;s start off with the &#8220;properties&#8221; type that will hold the data the behavior will require in order to do its work. <\/p>\n\n\n\n<p>We&#8217;ll name this as the <code>CancelableAnimationState<\/code>, and it will be an <code>AttachableComponent&lt;DependencyObject&gt;<\/code>. This is a type exposed by the Bad Echo Presentation framework, and one that essentially makes it a <code><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/desktop\/wpf\/advanced\/freezable-objects-overview?view=netframeworkdesktop-4.8\" target=\"_blank\" rel=\"noreferrer noopener\">Freezable<\/a><\/code> with support for having an inheritance context.<\/p>\n\n\n\n<p>This type will expose two attached properties: 1) one for receiving a <code>Storyboard<\/code> instance, and 2) one for receiving the notification object, which will be an instance of the <code>Mediator<\/code> type, an object exposed by the Bad Echo Presentation framework that allows for simple message passing between disparate UI elements in an WPF application.<\/p>\n\n\n\n<p>Upon receiving a request to cancel its animations via the <code>Mediator<\/code>, the <code>CancelableAnimationState<\/code> will invoke an event that the behavior is listening to, in order for that behavior to go ahead and do its work.<\/p>\n\n\n\n<p>Here is the code for the <code>CancelableAnimationState<\/code>:<\/p>\n\n\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\/\/\/ &lt;summary&gt;\n\/\/\/ Provides attachable animation state data to a &lt;see cref=&quot;CancelableAnimationBehavior&quot;\/&gt;\n\/\/\/ instance controlling it.\n\/\/\/ &lt;\/summary&gt;\n\/\/\/ &lt;remarks&gt;\n\/\/\/ This is meant to be provided to a &lt;see cref=&quot;CancelableAnimationBehavior&quot;\/&gt; instance using\n\/\/\/ property element syntax in order to provide both messaging support as well as a means to\n\/\/\/ specify which storyboard we aim to make cancelable.\n\/\/\/ &lt;\/remarks&gt;\npublic sealed class CancelableAnimationState : AttachableComponent&lt;DependencyObject&gt;\n{\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Identifies the &lt;see cref=&quot;Mediator&quot;\/&gt; dependency property.\n    \/\/\/ &lt;\/summary&gt;\n    public static readonly DependencyProperty MediatorProperty =\n        DependencyProperty.Register(nameof(Mediator),\n                                    typeof(Mediator),\n                                    typeof(CancelableAnimationState),\n                                    new PropertyMetadata(OnMediatorChanged));\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Identifies the &lt;see cref=&quot;Storyboard&quot;\/&gt; dependency property.\n    \/\/\/ &lt;\/summary&gt;\n    public static readonly DependencyProperty StoryboardProperty =\n        DependencyProperty.Register(nameof(Storyboard),\n                                    typeof(Storyboard),\n                                    typeof(CancelableAnimationState));\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Occurs when a request to cancel the playback of &lt;see cref=&quot;Storyboard&quot;\/&gt; has been made.\n    \/\/\/ &lt;\/summary&gt;\n    public event EventHandler? AnimationCanceling;\n\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Gets or sets the storyboard whose playback on the dependency object this component is\n    \/\/\/ attached to will be made cancelable.\n    \/\/\/ &lt;\/summary&gt;\n    public Storyboard? Storyboard\n    {\n        get =&gt; (Storyboard) GetValue(StoryboardProperty); \n        set =&gt; SetValue(StoryboardProperty, value);\n    }\n\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Gets or sets the mediator used to receive animation cancellation requests.\n    \/\/\/ &lt;\/summary&gt;\n    public Mediator? Mediator\n    {\n        get =&gt; (Mediator) GetValue(MediatorProperty);\n        set =&gt; SetValue(MediatorProperty, value);\n    }\n    \n    \/\/\/ &lt;inheritdoc\/&gt;\n    protected override Freezable CreateInstanceCore()\n        =&gt; new CancelableAnimationState();\n\n    private static void OnMediatorChanged(\n        DependencyObject sender, DependencyPropertyChangedEventArgs e)\n    {\n        var animationState = (CancelableAnimationState) sender;\n\n        if (e.OldValue is Mediator oldMediator) \n            animationState.UnregisterMediator(oldMediator);\n\n        if (e.NewValue is Mediator newMediator)\n            animationState.RegisterMediator(newMediator);\n    }\n    \n    private void RegisterMediator(Mediator mediator)\n        =&gt; mediator.Register(SystemMessages.CancelAnimationsRequested, \n                             MediateCancelAnimationsRequest);\n\n    private void UnregisterMediator(Mediator mediator)\n        =&gt; mediator.Unregister(SystemMessages.CancelAnimationsRequested, \n                               MediateCancelAnimationsRequest);\n\n    private void MediateCancelAnimationsRequest()\n    { \n        AnimationCanceling?.Invoke(TargetObject, EventArgs.Empty);\n    }\n}\n<\/pre>\n\n\n<h3>CancelableAnimationBehavior<\/h3>\n\n\n\n<p>Let&#8217;s next go over the actual attached behavior that will do the animation canceling.<\/p>\n\n\n\n<p>This behavior, which we&#8217;ll name as the <code>CancelableAnimationBehavior<\/code> will derive from <code>CompoundBehavior&lt;FrameworkElement, CancelableAnimationState&gt;<\/code>, which is another type made available by the Bad Echo Presentation framework. It allows for us to easily create a behavior that requires an <code>AttachableComponent&lt;T&gt;<\/code> to be able to be assigned to it using property element syntax.<\/p>\n\n\n\n<p>Once this behavior is told to cancel animations via the <code>AnimationCanceling<\/code> event, it will do so by calling <code>SkipToFill<\/code> on the attached data&#8217;s <code>Storyboard<\/code> property. This method essentially fast forwards the animation to its end, and will leave us with a UI element at the end of its animation timeline (which, in this case, will appear as if it was never animated at all, as the animated properties return to their original values at the end).<\/p>\n\n\n\n<p>Here is the code for <code>CancelableAnimationBehavior<\/code>:<\/p>\n\n\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\/\/\/ &lt;summary&gt;\n\/\/\/ Provides a behavior that, when attached to a target dependency object, allows for the\n\/\/\/ immediate cancellation of an animation running on it. \n\/\/\/ &lt;\/summary&gt;\npublic sealed class CancelableAnimationBehavior \n    : CompoundBehavior&lt;FrameworkElement, CancelableAnimationState&gt;\n{\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Identifies the attached property that gets or sets this behavior's\n    \/\/\/ &lt;see cref=&quot;CancelableAnimationState&quot;\/&gt; data, which ultimately specifies the storyboard\n    \/\/\/ this behavior will be canceling if requested to do so.\n    \/\/\/ &lt;\/summary&gt;\n    public static readonly DependencyProperty StateProperty \n        = RegisterAttachment();\n\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Gets the value of the &lt;see cref=&quot;StateProperty&quot;\/&gt; attached property for a given\n    \/\/\/ &lt;see cref=&quot;DependencyObject&quot;\/&gt;.\n    \/\/\/ &lt;\/summary&gt;\n    \/\/\/ &lt;param name=&quot;source&quot;&gt;The dependency object from which the property value is read.&lt;\/param&gt;\n    \/\/\/ &lt;returns&gt;\n    \/\/\/ The &lt;see cref=&quot;CancelableAnimationState&quot;\/&gt; associated with &lt;c&gt;source&lt;\/c&gt;.\n    \/\/\/ &lt;\/returns&gt;\n    public static CancelableAnimationState GetState(DependencyObject source)\n    {\n        Require.NotNull(source, nameof(source));\n\n        return GetAttachment(source, StateProperty);\n    }\n\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Sets the value of the &lt;see cref=&quot;StateProperty&quot;\/&gt; attached property on a given\n    \/\/\/ &lt;see cref=&quot;DependencyObject&quot;\/&gt;.\n    \/\/\/ &lt;\/summary&gt;\n    \/\/\/ &lt;param name=&quot;source&quot;&gt;The dependency object to which the property is written.&lt;\/param&gt;\n    \/\/\/ &lt;param name=&quot;value&quot;&gt;The &lt;see cref=&quot;CancelableAnimationState&quot;\/&gt; to set.&lt;\/param&gt;\n    public static void SetState(DependencyObject source, CancelableAnimationState value)\n    {\n        Require.NotNull(source, nameof(source));\n\n        source.SetValue(StateProperty, value);\n    }\n\n    \/\/\/ &lt;inheritdoc\/&gt;\n    protected override void OnValueAssociated(\n        FrameworkElement targetObject, CancelableAnimationState newValue)\n    {\n        base.OnValueAssociated(targetObject, newValue);\n\n        newValue.AnimationCanceling += HandleAnimationCanceling;\n    }\n\n    \/\/\/ &lt;inheritdoc\/&gt;\n    protected override void OnValueDisassociated(\n        FrameworkElement targetObject, CancelableAnimationState oldValue)\n    {\n        base.OnValueDisassociated(targetObject, oldValue);\n\n        oldValue.AnimationCanceling -= HandleAnimationCanceling;\n    }\n\n    \/\/\/ &lt;inheritdoc\/&gt;\n    protected override Freezable CreateInstanceCore()\n        =&gt; new CancelableAnimationBehavior();\n\n    private static DependencyProperty RegisterAttachment()\n    {\n        var behavior = new CancelableAnimationBehavior();\n\n        return DependencyProperty.RegisterAttached(\n            NameOf.ReadAccessorEnabledDependencyPropertyName(() =&gt; StateProperty),\n            typeof(CancelableAnimationState),\n            typeof(CancelableAnimationBehavior),\n            behavior.DefaultMetadata);\n    }\n\n    private static void HandleAnimationCanceling(object? sender, EventArgs e)\n    {\n        if (sender == null)\n            return;\n\n        var targetObject = (FrameworkElement)sender;\n\n        CancelableAnimationState state = GetAttachment(targetObject, StateProperty);\n        \n        state.Storyboard?.SkipToFill(targetObject);\n    }\n}\n<\/pre>\n\n\n<p>The <code>State<\/code> attached property is <a href=\"https:\/\/badecho.com\/index.php\/2021\/12\/20\/wpf-shadow-accessors\/\" target=\"_blank\" rel=\"noreferrer noopener\">shadowed<\/a>, something required in order for the attached data to function correctly.<\/p>\n\n\n\n<h3>The Cancel Animations Request and Behavior Implementation<\/h3>\n\n\n\n<p>The message to cancel animations needs to be sent whenever a new item is being added to the collection of view models. The perfect place for this, in our case, is the view model that acts as the data context for the collection-based view: the <code>ApocalypseViewModel<\/code>.<\/p>\n\n\n\n<p>The <code>Mediator<\/code> used for communication will be exposed by this view model, and its use will occur within the <code>OnChildrenChanged<\/code> override when a new item is being detected:<\/p>\n\n\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\/\/\/ &lt;summary&gt;\n\/\/\/ Gets the mediator for messages to be sent or received through.\n\/\/\/ &lt;\/summary&gt;\npublic Mediator Mediator \n{ get; } = new();\n.\n.\n.\n\/\/\/ &lt;inheritdoc\/&gt;\nprotected override void OnChildrenChanged(CollectionPropertyChangedEventArgs e)\n{\n    base.OnChildrenChanged(e);\n\n    if (e.Action is CollectionPropertyChangedAction.Add)\n        Mediator.Broadcast(SystemMessages.CancelAnimationsRequested);\n}\n<\/pre>\n\n\n<p>We can then make use of our new behavior by applying it directly on the <code>Border<\/code> controls whose animations need to be canceled. Here is the entire code for the <code>ContentControl<\/code> style&#8217;s template used to decorate the child views &#8212; you will be able observe our new behavior being applied using property element syntax:<\/p>\n\n\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\n&lt;ControlTemplate TargetType=&quot;{x:Type ContentControl}&quot;&gt;\n  &lt;Border Margin=&quot;0,5,0,5&quot;&gt;\n      &lt;Border.Resources&gt;\n          &lt;badEcho:ArithmeticConverter x:Key=&quot;DirectionalConverter&quot;\n                                       Operand=&quot;35&quot;\n                                       Operation=&quot;Multiplication&quot;\n                                       \/&gt;\n          &lt;Storyboard x:Key=&quot;RevealBounce&quot;&gt;\n              &lt;DoubleAnimation Storyboard.TargetProperty=&quot;(Border.Background).(GradientBrush.Opacity)&quot; \n                               BeginTime=&quot;00:00:05&quot;\n                               From=&quot;1&quot;\n                               To=&quot;0&quot;\n                               \/&gt;\n              &lt;DoubleAnimation Storyboard.TargetProperty=&quot;(UIElement.RenderTransform).(TranslateTransform.Y)&quot;\n                               Duration=&quot;00:00:01&quot;\n                               From=&quot;{Binding DataContext.DirectionalCoefficient, \n                                              Converter={StaticResource DirectionalConverter},\n                                              RelativeSource={RelativeSource AncestorType={x:Type badEcho:View}}}&quot;\n                               To=&quot;0&quot;&gt;\n                  &lt;DoubleAnimation.EasingFunction&gt;\n                      &lt;BounceEase Bounces=&quot;3&quot;\n                                  EasingMode=&quot;EaseOut&quot;\n                                  Bounciness=&quot;2&quot;\n                                  \/&gt;\n                  &lt;\/DoubleAnimation.EasingFunction&gt;\n              &lt;\/DoubleAnimation&gt;\n          &lt;\/Storyboard&gt;\n      &lt;\/Border.Resources&gt;\n      &lt;Border.RenderTransform&gt;\n          &lt;TranslateTransform\/&gt;\n      &lt;\/Border.RenderTransform&gt;\n      &lt;Border.Background&gt;\n          &lt;LinearGradientBrush StartPoint=&quot;0,0.5&quot; EndPoint=&quot;1,0.5&quot; &gt;\n              &lt;GradientStop Color=&quot;#FFA4A2A3&quot;\/&gt;\n              &lt;GradientStop Color=&quot;#FFD2D2D2&quot; Offset=&quot;1&quot;\/&gt;\n          &lt;\/LinearGradientBrush&gt;\n      &lt;\/Border.Background&gt;\n      &lt;Border.Triggers&gt;\n          &lt;EventTrigger RoutedEvent=&quot;Loaded&quot;&gt;\n              &lt;!--The BeginStoryboard action must have a name, as giving it name will result in the runtime making the\n                  resulting storyboard controllable, a capability this view requires.--&gt;\n              &lt;BeginStoryboard x:Name=&quot;RevealBounceAction&quot; Storyboard=&quot;{StaticResource RevealBounce}&quot;\/&gt;\n          &lt;\/EventTrigger&gt;\n      &lt;\/Border.Triggers&gt;\n      &lt;badEcho:CancelableAnimationBehavior.State&gt;\n          &lt;badEcho:CancelableAnimationState Storyboard=&quot;{StaticResource RevealBounce}&quot;\n                                            Mediator=&quot;{Binding   DataContext.Mediator, \n                                                                 RelativeSource={RelativeSource AncestorType={x:Type badEcho:View}}}&quot;\n                                            \/&gt;\n      &lt;\/badEcho:CancelableAnimationBehavior.State&gt;\n      &lt;Border&gt;\n          &lt;Border.Resources&gt;\n              &lt;Storyboard x:Key=&quot;RevealShimmer&quot;&gt;\n                  &lt;DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;(Border.Background).(GradientBrush.GradientStops)[0].(GradientStop.Offset)&quot;\n                                                 BeginTime=&quot;00:00:02&quot;&gt;\n                      &lt;EasingDoubleKeyFrame KeyTime=&quot;00:00:03&quot; \n                                            Value=&quot;0.8&quot;\n                                            \/&gt;\n                  &lt;\/DoubleAnimationUsingKeyFrames&gt;\n                  &lt;DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;(Border.Background).(GradientBrush.GradientStops)[1].(GradientStop.Offset)&quot;\n                                                 BeginTime=&quot;00:00:02&quot;&gt;\n                      &lt;EasingDoubleKeyFrame KeyTime=&quot;00:00:03&quot; Value=&quot;0.9&quot;\/&gt;\n                  &lt;\/DoubleAnimationUsingKeyFrames&gt;\n                  &lt;DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;(Border.Background).(GradientBrush.GradientStops)[2].(GradientStop.Offset)&quot;\n                                                 BeginTime=&quot;00:00:02&quot;&gt;\n                      &lt;EasingDoubleKeyFrame KeyTime=&quot;00:00:03&quot; Value=&quot;1&quot;\/&gt;\n                  &lt;\/DoubleAnimationUsingKeyFrames&gt;\n                  &lt;DoubleAnimation Storyboard.TargetProperty=&quot;(Border.Background).(GradientBrush.Opacity)&quot;\n                                   BeginTime=&quot;00:00:05&quot;\n                                   From=&quot;1&quot;\n                                   To=&quot;0&quot;\n                                   \/&gt;\n              &lt;\/Storyboard&gt;\n          &lt;\/Border.Resources&gt;\n          &lt;Border.Background&gt;\n              &lt;LinearGradientBrush EndPoint=&quot;1.7,1&quot; StartPoint=&quot;-0.5,0&quot;&gt;\n                  &lt;GradientStop Color=&quot;#00FFFFFF&quot;\/&gt;\n                  &lt;GradientStop Color=&quot;#FFFFFFFF&quot; Offset=&quot;0.1&quot;\/&gt;\n                  &lt;GradientStop Color=&quot;#00FFFFFF&quot; Offset=&quot;0.2&quot;\/&gt;\n              &lt;\/LinearGradientBrush&gt;\n          &lt;\/Border.Background&gt;\n          &lt;Border.Triggers&gt;\n              &lt;EventTrigger RoutedEvent=&quot;Loaded&quot;&gt;\n                  &lt;!--The BeginStoryboard action must have a name, as giving it name will result in the runtime making the\n                      resulting storyboard controllable, a capability this view requires.--&gt;\n                  &lt;BeginStoryboard x:Name=&quot;RevealShimmerAction&quot; Storyboard=&quot;{StaticResource RevealShimmer}&quot;\/&gt;\n              &lt;\/EventTrigger&gt;\n          &lt;\/Border.Triggers&gt;\n          &lt;badEcho:CancelableAnimationBehavior.State&gt;\n              &lt;badEcho:CancelableAnimationState Storyboard=&quot;{StaticResource RevealShimmer}&quot; \n                                                Mediator=&quot;{Binding   DataContext.Mediator, \n                                                                     RelativeSource={RelativeSource AncestorType={x:Type badEcho:View}}}&quot;\n                                                \/&gt;\n          &lt;\/badEcho:CancelableAnimationBehavior.State&gt;\n          &lt;ContentPresenter\/&gt;\n      &lt;\/Border&gt;\n  &lt;\/Border&gt;\n&lt;\/ControlTemplate&gt;\n<\/pre>\n\n\n<p>The <a href=\"https:\/\/github.com\/BadEcho\/vision\/blob\/master\/src\/Vision.Apocalypse\/Views\/ApocalypseView.xaml\" target=\"_blank\" rel=\"noreferrer noopener\">full code can be found here<\/a> if you wish to see it.<\/p>\n\n\n\n<h2>The Results<\/h2>\n\n\n\n<p>The Apocalypse module for <em>Vision<\/em> now successfully cancels all extraneous animation playback upon a new item being added:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><img loading=\"lazy\" width=\"875\" height=\"190\" src=\"https:\/\/badecho.com\/wp-content\/uploads\/2022\/01\/CanceledAnimations.gif\" alt=\"Shows existing animations being canceled when new events get added.\" class=\"wp-image-2186\"\/><figcaption>Running animations now get canceled whenever a new item is added. Beautiful.<\/figcaption><\/figure><\/div>\n\n\n\n<p>A wonderful result, implemented in an elegant fashion!<\/p>\n\n\n\n<p>I hope you found this article illuminating. Until next time!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Not long ago, I had the opportunity to debut the new Apocalypse module for my Omnified Vision overlay application, live [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[10],"tags":[42,64,70],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v14.9 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\r\n<title>Canceling WPF Animations Made Simple - omni&#039;s hackpad<\/title>\r\n<meta name=\"description\" content=\"While working on Vision, I found canceling in progress WPF animations to be harder than expected. Let&#039;s go over the way to do it, elegantly.\" \/>\r\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\r\n<link rel=\"canonical\" href=\"https:\/\/badecho.com\/index.php\/2022\/01\/20\/canceling-animations\/\" \/>\r\n<meta property=\"og:locale\" content=\"en_US\" \/>\r\n<meta property=\"og:type\" content=\"article\" \/>\r\n<meta property=\"og:title\" content=\"Canceling WPF Animations Made Simple - omni&#039;s hackpad\" \/>\r\n<meta property=\"og:description\" content=\"While working on Vision, I found canceling in progress WPF animations to be harder than expected. Let&#039;s go over the way to do it, elegantly.\" \/>\r\n<meta property=\"og:url\" content=\"https:\/\/badecho.com\/index.php\/2022\/01\/20\/canceling-animations\/\" \/>\r\n<meta property=\"og:site_name\" content=\"omni&#039;s hackpad\" \/>\r\n<meta property=\"article:published_time\" content=\"2022-01-20T14:06:48+00:00\" \/>\r\n<meta property=\"article:modified_time\" content=\"2022-11-16T01:18:25+00:00\" \/>\r\n<meta property=\"og:image\" content=\"https:\/\/badecho.com\/wp-content\/uploads\/2022\/01\/CancelAnimations.png\" \/>\r\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\r\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebSite\",\"@id\":\"https:\/\/badecho.com\/#website\",\"url\":\"https:\/\/badecho.com\/\",\"name\":\"omni&#039;s hackpad\",\"description\":\"Game Code Disassembly. Omnified Modification. Madness.\",\"publisher\":{\"@id\":\"https:\/\/badecho.com\/#\/schema\/person\/3de67496328be7ae6e1f52faf582e9d2\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":\"https:\/\/badecho.com\/?s={search_term_string}\",\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/badecho.com\/index.php\/2022\/01\/20\/canceling-animations\/#primaryimage\",\"inLanguage\":\"en-US\",\"url\":\"https:\/\/badecho.com\/wp-content\/uploads\/2022\/01\/CancelAnimations.png\",\"width\":857,\"height\":191,\"caption\":\"Canceling WPF Animations Made Simple\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/badecho.com\/index.php\/2022\/01\/20\/canceling-animations\/#webpage\",\"url\":\"https:\/\/badecho.com\/index.php\/2022\/01\/20\/canceling-animations\/\",\"name\":\"Canceling WPF Animations Made Simple - omni&#039;s hackpad\",\"isPartOf\":{\"@id\":\"https:\/\/badecho.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/badecho.com\/index.php\/2022\/01\/20\/canceling-animations\/#primaryimage\"},\"datePublished\":\"2022-01-20T14:06:48+00:00\",\"dateModified\":\"2022-11-16T01:18:25+00:00\",\"description\":\"While working on Vision, I found canceling in progress WPF animations to be harder than expected. Let's go over the way to do it, elegantly.\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/badecho.com\/index.php\/2022\/01\/20\/canceling-animations\/\"]}]},{\"@type\":\"Article\",\"@id\":\"https:\/\/badecho.com\/index.php\/2022\/01\/20\/canceling-animations\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/badecho.com\/index.php\/2022\/01\/20\/canceling-animations\/#webpage\"},\"author\":{\"@id\":\"https:\/\/badecho.com\/#\/schema\/person\/3de67496328be7ae6e1f52faf582e9d2\"},\"headline\":\"Canceling WPF Animations Made Simple\",\"datePublished\":\"2022-01-20T14:06:48+00:00\",\"dateModified\":\"2022-11-16T01:18:25+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/badecho.com\/index.php\/2022\/01\/20\/canceling-animations\/#webpage\"},\"publisher\":{\"@id\":\"https:\/\/badecho.com\/#\/schema\/person\/3de67496328be7ae6e1f52faf582e9d2\"},\"image\":{\"@id\":\"https:\/\/badecho.com\/index.php\/2022\/01\/20\/canceling-animations\/#primaryimage\"},\"keywords\":\"C#,WPF,XAML\",\"articleSection\":\"General Dev\",\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\/\/badecho.com\/#\/schema\/person\/3de67496328be7ae6e1f52faf582e9d2\",\"name\":\"Matt Weber\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/badecho.com\/#personlogo\",\"inLanguage\":\"en-US\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/7e345ac2708b3a41c7bd70a4a0440d41?s=96&d=mm&r=g\",\"caption\":\"Matt Weber\"},\"logo\":{\"@id\":\"https:\/\/badecho.com\/#personlogo\"}}]}<\/script>\r\n<!-- \/ Yoast SEO plugin. -->","_links":{"self":[{"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/posts\/2180"}],"collection":[{"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/comments?post=2180"}],"version-history":[{"count":14,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/posts\/2180\/revisions"}],"predecessor-version":[{"id":2529,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/posts\/2180\/revisions\/2529"}],"wp:attachment":[{"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/media?parent=2180"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/categories?post=2180"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/tags?post=2180"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}