Dynamic viewing animations using MVVM

I tried to figure out how to effectively run animations in a view when a property is in the ViewModel updates, where the animation depends on the value of the specified property.

I recreated my problem in a simple application using one View and ViewModel. The goal is to go on to change the color of the rectangle using ColorAnimation. For reference, I used uploaded here.

To summarize, I want to animate a color transition in a 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"); } } } } 

In any case, can you call ColorAnimation from the view instead of instantly switching between values? The way I am doing this now is another application that is rather messy as I set the ViewModel through the ViewModel property and then using the PropertyObserver to track the changes in the values, then create an animation and run it from codebehind:

 this.colorObserver = new PropertyObserver<Player>(value) .RegisterHandler(n => n.Color, this.CreateColorAnimation); 

In a situation where I deal with many colors and many potential animations, this becomes rather messy, and it will ruin the fact that I manually pass the ViewModel to the view, and not just bind it through the ResourceDictionary, I suppose I could do it in DataContextChanged event, but is there a better way?

+6
source share
2 answers

If only for a few animations I would recommend using Visual States. You can then use the GoToAction behavior in the view to launch various animations. If you are dealing with many similar animations, creating your own behavior would be the best solution.

Update I created a very simple behavior to give the Rectangle a bit of 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 as follows:

  <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 on the Rectangle, it should go from black to red.

+5
source

I used the code published by Xin and made some very small tweaks (code below). The only 3 significant differences:

I created a behavior to work with any UIElement, not just a rectangle

I used PropertyChangedTrigger instead of EventTrigger. This allows me to track the color property in the ViewModel instead of listening for click events.

I bound the FillColor property to the Color ViewModel property.

To use this, you need to download the Blend 4 SDK (it's free, and you only need it if you don't have Expression Blend yet) and add links to System.Windows.Interactivity and Microsoft.Expression.Interactions

Here is 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; } } }
// 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 is the XAML that anchors it 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>
<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 an Xin idea - I cleaned it a bit.

+4
source

Source: https://habr.com/ru/post/887543/


All Articles