I've been trying to figure out how to effectively trigger animations in a View when a property in the ViewModel updates, where the animation depends on the value of said property.
I've recreated my problem in a simple application with a single View and ViewModel. The goal here is to transition the color change of a rectangle by using a ColorAnimation. For reference, I've been using the MVVM Foundation package by Josh Smith.
The example project can be downloaded here.
To summarize, I want to animate the color transition in the View whenever the Color property changes.
MainWindow.xaml
<Window x:Class="MVVM.ColorAnimation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ColorAnimation="clr-namespace:MVVM.ColorAnimation" Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<ColorAnimation:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<Rectangle Margin="10">
<Rectangle.Fill>
<SolidColorBrush Color="{Binding Color}"/>
</Rectangle.Fill>
</Rectangle>
<StackPanel Orientation="Horizontal" Grid.Row="1">
<Button Command="{Binding BlueCommand}" Width="100">Blue</Button>
<Button Command="{Binding GreenCommand}" Width="100">Green</Button>
</StackPanel>
</Grid>
</Window>
MainWindowViewModel.cs
namespace MVVM.ColorAnimation
{
using System.Windows.Input;
using System.Windows.Media;
using MVVM;
public class MainWindowViewModel : ObservableObject
{
private ICommand blueCommand;
private ICommand greenCommand;
public ICommand BlueCommand
{
get
{
return this.blueCommand ?? (this.blueCommand = new RelayCommand(this.TurnBlue));
}
}
private void TurnBlue()
{
this.Color = Colors.Blue;
}
public ICommand GreenCommand
{
get
{
return this.greenCommand ?? (this.greenCommand = new RelayCommand(this.TurnGreen));
}
}
private void TurnGreen()
{
this.Color = Colors.Green;
}
private Color color = Colors.Red;
public Color Color
{
get
{
return this.color;
}
set
{
this.color = value;
RaisePropertyChanged("Color");
}
}
}
}
Is there anyway from the View to trigger a ColorAnimation instead of an instant transition between the values? The way I'm currently doing this is another application is quite messy, in that I set the ViewModel through a ViewModel
property, and then using a PropertyObserver to monitor value changes, then create the Animation and trigger it from the codebehind:
this.colorObserver = new PropertyObserver<Player>(value)
.RegisterHandler(n => n.Color, this.CreateColorAnimation);
In a situation where I'm dealing with many colors and many potential animations, this becomes quite a mess, and messes up the fact that I'm manually passing in the ViewModel to the View than simply binding the two through a ResourceDictionary. I suppose I could do this in the DataContextChanged
event as well, but is there a better way?
If just for a few animations I would recommend using Visual States. Then you can use GoToAction behavior on the view to trigger different animations. If you are dealing with a lot of similar animations, creating your own behavior would be a better solution.
Update I have created a very simple behaivor to give a Rectangle a little color animation. Here is the code.
public class ColorAnimationBehavior : TriggerAction<FrameworkElement>
{
#region Fill color
[Description("The background color of the rectangle")]
public Color FillColor
{
get { return (Color)GetValue(FillColorProperty); }
set { SetValue(FillColorProperty, value); }
}
public static readonly DependencyProperty FillColorProperty =
DependencyProperty.Register("FillColor", typeof(Color), typeof(ColorAnimationBehavior), null);
#endregion
protected override void Invoke(object parameter)
{
var rect = (Rectangle)AssociatedObject;
var sb = new Storyboard();
sb.Children.Add(CreateVisibilityAnimation(rect, new Duration(new TimeSpan(0, 0, 1)), FillColor));
sb.Begin();
}
private static ColorAnimationUsingKeyFrames CreateVisibilityAnimation(DependencyObject element, Duration duration, Color color)
{
var animation = new ColorAnimationUsingKeyFrames();
animation.KeyFrames.Add(new SplineColorKeyFrame { KeyTime = new TimeSpan(duration.TimeSpan.Ticks), Value = color });
Storyboard.SetTargetProperty(animation, new PropertyPath("(Shape.Fill).(SolidColorBrush.Color)"));
Storyboard.SetTarget(animation, element);
return animation;
}
}
In xaml, you simply attach this behavior like this,
<Rectangle x:Name="rectangle" Fill="Black" Margin="203,103,217,227" Stroke="Black">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<local:ColorAnimationBehavior FillColor="Red"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Rectangle>
When you click the Rectangle, it should go from Black color to Red.
I used the code that Xin posted, and made a few very minor tweeks (code is below). The only 3 material differences:
I created the behavior to work on any UIElement, not just a rectangle
I used a PropertyChangedTrigger instead of an EventTrigger. That let's me Monitor the color property on the ViewModel instead of listening for click events.
I bound the FillColor to the Color property of the ViewModel.
To use this, you will need to download the Blend 4 SDK (it's free, and you only need it if you don't already have Expression Blend), and add references to System.Windows.Interactivity, and Microsoft.Expression.Interactions
Here's the code for the behavior class:
// complete code for the animation behavior
using System.Windows;
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace ColorAnimationBehavior
{
public class ColorAnimationBehavior: TriggerAction<UIElement>
{
public Color FillColor
{
get { return (Color)GetValue(FillColorProperty); }
set { SetValue(FillColorProperty, value); }
}
public static readonly DependencyProperty FillColorProperty =
DependencyProperty.Register("FillColor", typeof(Color), typeof(ColorAnimationBehavior), null);
public Duration Duration
{
get { return (Duration)GetValue(DurationProperty); }
set { SetValue(DurationProperty, value); }
}
// Using a DependencyProperty as the backing store for Duration. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DurationProperty =
DependencyProperty.Register("Duration", typeof(Duration), typeof(ColorAnimationBehavior), null);
protected override void Invoke(object parameter)
{
var storyboard = new Storyboard();
storyboard.Children.Add(CreateColorAnimation(this.AssociatedObject, this.Duration, this.FillColor));
storyboard.Begin();
}
private static ColorAnimationUsingKeyFrames CreateColorAnimation(UIElement element, Duration duration, Color color)
{
var animation = new ColorAnimationUsingKeyFrames();
animation.KeyFrames.Add(new SplineColorKeyFrame() { KeyTime = duration.TimeSpan, Value = color });
Storyboard.SetTargetProperty(animation, new PropertyPath("(Shape.Fill).(SolidColorBrush.Color)"));
Storyboard.SetTarget(animation, element);
return animation;
}
}
}
Now here's the XAML that hooks it up to your rectangle:
<UserControl x:Class="MVVM.ColorAnimation.Silverlight.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ColorAnimation="clr-namespace:MVVM.ColorAnimation"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:ca="clr-namespace:ColorAnimationBehavior;assembly=ColorAnimationBehavior"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.DataContext>
<ColorAnimation:MainWindowViewModel />
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<Rectangle x:Name="rectangle" Margin="10" Stroke="Black" Fill="Red">
<i:Interaction.Triggers>
<ei:PropertyChangedTrigger Binding="{Binding Color}">
<ca:ColorAnimationBehavior FillColor="{Binding Color}" Duration="0:0:0.5" />
</ei:PropertyChangedTrigger>
</i:Interaction.Triggers>
</Rectangle>
<StackPanel Orientation="Horizontal" Grid.Row="1">
<Button Command="{Binding BlueCommand}" Width="100" Content="Blue"/>
<Button Command="{Binding GreenCommand}" Width="100" Content="Green"/>
</StackPanel>
</Grid>
</UserControl>
It was really Xin's idea -- I just cleaned it up a bit.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With