Animating ViewModel Properties instead of View Bound Properties

January 24, 2011

no comments

Trying to animate a View property which is bound two-way to a View Model property, yields working animation but also unchanged View Model.

It turns out that animating a two-way data-bound property breaks the data-binding!

For example, having a line bounds to its view-model, X1, Y1, X2, Y2 properties, if you’ll try to animate the Line, the line will be animated but at the same time its X1, Y1, X2, Y2 properties will left unbound.

So how to fix that?

Instead of animating the view, animate the view-model. Anyhow, it’s much easier to reflect view-model changes on the view.

Now the problem is: Animation in WPF only works on dependency properties.

Here you’ve got at least two options:

1. Make your view-model a DependencyObject and expose dependency properties.

2. Expose CLR properties of type DependencyObject which have dependency properties.

The first approach is really simple but less common, so I’ll demonstrate the second approach.

 

Let’s say that you have a view/view-model that reflect a line which has two points: A and B, and you wish to animate this line by changing the two points.

To handle that, create a LineGeometry in your view-model, and expose it as a "Line" CLR property. Then create an animation in the view which animates the Line.StartPoint and Line.EndPoint dependency properties.

Here is how:

public class LineViewModel : INotifyPropertyChanged
{
   
private LineGeometry
_line;

    public LineViewModel()
    {
        ResetLine();
    }

    private void line_Changed(object sender, EventArgs e)
    {
       
// Line is changing on animation…
    }

    public LineGeometry Line
    {
       
get { return
_line; }
       
set
        {
           
if (_line != value
)
            {
               
if (_line != null
)
                {
                    _line.Changed -= line_Changed;
                }

                _line = value;
                _line.Changed += line_Changed;
                    
                PropertyChanged(
this, new PropertyChangedEventArgs("Line"
));
            }
        }
    }

    public ICommand ResetLineCommand
    {
       
get
        {
           
return new RelayCommand
(unused => ResetLine());
        }
    }        

    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    private void ResetLine()
    {
        Line =
new LineGeometry(new Point(50, 50), new Point
(150, 150));
    }
}

<UserControl x:Class="AnimatingView.LineView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 
   
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 
   
xmlns:l="clr-namespace:AnimatingView"
    mc:Ignorable="d"
 
   
d:DesignHeight="341" d:DesignWidth="382">

    <UserControl.DataContext>
        <l:LineViewModel />
    </UserControl.DataContext>

    <UserControl.Resources>
       
       
<Storyboard x:Key="LineAnimation"
                   AutoReverse="True"
                   RepeatBehavior="Forever">
            <PointAnimationUsingKeyFrames
                    Storyboard.TargetProperty="DataContext.Line.StartPoint">
                <EasingPointKeyFrame KeyTime="0:0:2" Value="200,150" />
            </PointAnimationUsingKeyFrames>
            <PointAnimationUsingKeyFrames
                   
                  
 Storyboard.TargetProperty="DataContext.Line.EndPoint">
                <EasingPointKeyFrame KeyTime="0:0:1" Value="50,150" />
            </PointAnimationUsingKeyFrames>
        </Storyboard>
    </UserControl.Resources>

    <UserControl.Triggers>
        <EventTrigger RoutedEvent="ButtonBase.Click"
                     SourceName="buttonAnimateLines">
            <BeginStoryboard Storyboard="{StaticResource LineAnimation}" />
        </EventTrigger>
    </UserControl.Triggers>

    <Grid>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="0.5*" />
            <ColumnDefinition Width="0.5*" />
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="55" />
        </Grid.RowDefinitions>

        <Path Data="{Binding Line, Mode=OneWay}"
             Stroke="Red"
             StrokeThickness="4"
             
            
StrokeStartLineCap="Round"
             StrokeEndLineCap="Round"
             Grid.Column="0" />

        <Path Data="{Binding Line, Mode=OneWay}"
             Stroke="Green"
             StrokeThickness="4"
             
            
StrokeStartLineCap="Round"
             StrokeEndLineCap="Round"
             Grid.Column="1" />

        <Button x:Name="buttonAnimateLines"
               Content="Animate Lines"
               Height="32" Width="112"
               Grid.Row="1" />

        <Button x:Name="buttonResetLines"
               Content="Reset Lines"
               Height="32" Width="112"
               Command="{Binding ResetLineCommand}"
               Grid.Row="1" Grid.Column="2" />
    </Grid>
   
</UserControl>

As you can see, I set the animation target property to be the DataContext.Line.XxxPoint, where DataContext is of course the view-model.

Using the same approach, you can benefit from having the WPF sophisticated animation mechanism to animate your view-model properties.

You can download the code from here.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*