Skip Navigation LinksHome > View Post

Animating when Data Changes - Part II

In Animating when data changes I talked about some of the difficulties of using animation with the MVVM pattern and how to overcome them.

Having discussed this post with some people they felt I targetted a fairly edge scenario, that is - content changing. What if a control is bound to a property of the view model and I want the control to animate when the value changes?

Fair enough. The important note is that the concept is the same. In my opinion, this animation is not a concern of the ViewModel and so we need to extract it from that space. As with most problems in software development (save for code simplicity and performance) we just need another layer of indirection. In this case we'll use a UserControl.

Imagine we have a Value property on a ViewModel and it is two-way bound to a Slider and also bound to a ProgressBar. However, we want the ProgressBar to animate smoothly when the value is changed.

Our ViewModel might look something like this:

public class SimpleViewModel : INotifyPropertyChanged
{
    private double _value;

    public double Value
    {
        get { return _value; }
        set
        {
            _value = value;
            OnPropertyChanged("Value");
        }
    }

    // snipped INPC implementation for brevity
}

Easy. Our xaml might look something like this:

<StackPanel>
    <Slider Value="{Binding Value, Mode=TwoWay}" Maximum="10"/>
    <ProgressBar Value="{Binding Value}" Height="20" Maximum="10"/>
</StackPanel>

The two controls are now bound together via our ViewModel. Let's imagine that there are multiple ways of updating the Value property (not just via the slider) and we want the ProgressBar to react in a nice animated smooth and slooooow way.

This is a pattern I use a lot in both WPF and Silverlight to implement animation. In this case the example is Silverlight but the code for WPF is almost identical.

First, I create a new UserControl to host the ProgressBar and add a Value Dependency Property.

public partial class AnimatedProgressBar : UserControl
{
    public AnimatedProgressBar()
    {
        InitializeComponent();
    }

    public double Value
    {
        get { return (double)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }

    public static readonly DependencyProperty ValueProperty =
    DependencyProperty.Register("Value", typeof(double), typeof(AnimatedProgressBar), new PropertyMetadata(0d, ValueChanged));
}

Notice that I registered a DependencyPropertyChangedEventHandler called ValueChanged - more on him later.

And the Xaml for my user control looks like this:

<UserControl x:Class="AnimatingOnDataChangePartII.AnimatedProgressBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ProgressBar x:Name="progress" Maximum="10" />
</UserControl>

Now, that ValueChanged handler:

public static void ValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
    // Get out of a static and into the instance ASAP.
    AnimatedProgressBar control = (AnimatedProgressBar)sender;
    control.ValueChanged();
}

private void ValueChanged()
{
    DoubleAnimation animation = new DoubleAnimation();;
    animation.To = this.Value;
    animation.Duration = new Duration(TimeSpan.FromSeconds(2));
    Storyboard.SetTarget(animation, progress);
    Storyboard.SetTargetProperty(animation, new PropertyPath(ProgressBar.ValueProperty));
    Storyboard sb = new Storyboard();
    sb.Children.Add(animation);
    sb.Begin();
}

So, we needed a little code behind but at least it's in the right place. The animation is part of the visual implementation and nothing to do with our 'canonical ViewModel'. Even better, our root Xaml still makes sense from a binding/ViewModel perspective - thanks to our use of a Dependency Property:

<StackPanel>
    <Slider Value="{Binding Value, Mode=TwoWay}" Maximum="10"/>
    <local:AnimatedProgressBar Value="{Binding Value}" Height="20" />
</StackPanel>

Here's the sample for you to play with:

And here's the source: Download Source (24KB) - usual demoware disclaimers apply.

Update - WinRT code

Thanks to Jerry Nixon for providing WinRT/Windows 8 code:


<UserControl.Resources>
    <Storyboard x:Name="AnimateStoryboard">
        <DoubleAnimation x:Name="AnimateTimeline"
            Duration="0:0:0.5"
            EnableDependentAnimation="True"
            Storyboard.TargetName="progress"
            Storyboard.TargetProperty="Value" />
        </Storyboard>
</UserControl.Resources>
<!-- adjust maximum to your needs -->
<ProgressBar x:Name="progress" Maximum="100" />


public partial class AnimatedProgressBar : UserControl
{
    public AnimatedProgressBar() { InitializeComponent(); }

    public double Value
    {
        get { return (double)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }

    public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(AnimatedProgressBar), new PropertyMetadata(0d, (s, e) => (s as AnimatedProgressBar).AnimateChange((double)e.NewValue)));

    private void AnimateChange(double value)
    {
        AnimateTimeline.To = this.Value;
        AnimateStoryboard.Begin();
    }
}

 
Josh Post By Josh Twist
2:14 AM
13 Jul 2009

» Next Post: Creating a Range Slider in WPF (and other cool tips and tricks for UserControls)
« Previous Post: Scripting Wake On Lan

Comments are closed for this post.

Posted by Ido Ran @ 13 Jul 2009 11:35 PM
Hello,
I've read your post and I think it a start of something bigger.
What I mean by that is that you again implement a specific thing, progress bar for that matter, but you lost all the other abilities of the progress bar.
What if you want to put your progress bar into Indeterminate state? You'll have to recreate all the progress bar properties all over - that's not reuse.

We need to think how to create a layer of indirection which will not conflict with the current layers already in place.

Thank you for the post,
Ido.

Posted by josh @ 14 Jul 2009 12:38 AM
Thanks Ido,

This is a great point. It rarely causes me much pain as it's easy to add a new dependency property and bind this within the usercontrol - however, it would be useful to have a mechanism that could do this more automatically. I have to admit, I have no idea what this would look like and think that significant investment on this particular approach (by MS or otherwise) might be in the wrong place - providing tweening support for properties on existing controls (similar to how VSM works) would be my preferred option.

This would include tweens for everything including changes in ListBoxes when items move, are deleted and added etc.

Josh

© 2005 - 2014 Josh Twist - All Rights Reserved.