Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF - sequential animation simple example

I'm learning about WPF animation, and am confused about how to apply animations sequentially. As a simple example, I've got four rectangles in a uniform grid, and would like to change the color of each one sequentially. Here's what I have so far:

public partial class Window1 : Window
{
    Rectangle blueRect;
    Rectangle redRect;
    Rectangle greenRect;
    Rectangle yellowRect;

    public Window1()
    {
        InitializeComponent();
        blueRect = new Rectangle() { Fill = System.Windows.Media.Brushes.Blue, Name="Blue"};
        redRect = new Rectangle() { Fill = System.Windows.Media.Brushes.Red, Name="Yellow"};
        greenRect = new Rectangle() { Fill = System.Windows.Media.Brushes.Green, Name="Green" };
        yellowRect = new Rectangle() { Fill = System.Windows.Media.Brushes.Yellow, Name="Yellow" };

        UniformGrid1.Children.Add(blueRect);
        UniformGrid1.Children.Add(redRect);
        UniformGrid1.Children.Add(greenRect);
        UniformGrid1.Children.Add(yellowRect);

    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        animateCell(blueRect, Colors.Blue);
        animateCell(redRect, Colors.Red);
    }

    private void animateCell(Rectangle rectangle, Color fromColor)
    {
        Color toColor = Colors.White;
        ColorAnimation ani = new ColorAnimation(toColor, new Duration(TimeSpan.FromMilliseconds(300)));
        ani.AutoReverse = true;

        SolidColorBrush newBrush = new SolidColorBrush(fromColor);
        ani.BeginTime = TimeSpan.FromSeconds(2);
        rectangle.Fill = newBrush;
        newBrush.BeginAnimation(SolidColorBrush.ColorProperty, ani);
        //NameScope.GetNameScope(this).RegisterName(rectangle.Name, rectangle);
        //Storyboard board = new Storyboard();
        //board.Children.Add(ani);
        //Storyboard.SetTargetName(rectangle, rectangle.Name);
        //Storyboard.SetTargetProperty(ani, new PropertyPath(SolidColorBrush.ColorProperty));
        //board.Begin();

    }

What's the easiest way of accomplishing this? The code in the comments is my first guess, but it's not working correctly.

like image 735
David Hodgson Avatar asked Oct 02 '09 21:10

David Hodgson


1 Answers

There should be an event ani.Completed - handle that event and start the next phase of the animation, then start the first one running and each phase will trigger the next.

ColorAnimation ani = // whatever...

ani.Completed += (s, e) => 
   {
       ColorAnimation ani2 = // another one...

       // and so on
   };

newBrush.BeginAnimation(SolidColorBrush.ColorProperty, ani);

UPDATE:

public partial class Window1 : Window
{
    Rectangle blueRect;
    Rectangle redRect;
    Rectangle greenRect;
    Rectangle yellowRect;

    public Window1()
    {
        InitializeComponent();
        blueRect = new Rectangle() { Fill = System.Windows.Media.Brushes.Blue, Name = "Blue" };
        redRect = new Rectangle() { Fill = System.Windows.Media.Brushes.Red, Name = "Yellow" };
        greenRect = new Rectangle() { Fill = System.Windows.Media.Brushes.Green, Name = "Green" };
        yellowRect = new Rectangle() { Fill = System.Windows.Media.Brushes.Yellow, Name = "Yellow" };

        UniformGrid1.Children.Add(blueRect);
        UniformGrid1.Children.Add(redRect);
        UniformGrid1.Children.Add(greenRect);
        UniformGrid1.Children.Add(yellowRect);
    }

    IEnumerable<Action<Action>> AnimationSequence()
    {
        for (; ; )
        {
            yield return AnimateCell(blueRect, Colors.Blue);
            yield return AnimateCell(redRect, Colors.Red);
            yield return AnimateCell(greenRect, Colors.Green);
            yield return AnimateCell(yellowRect, Colors.Yellow);
        }
    }

    private IEnumerator<Action<Action>> _actions;

    private void RunNextAction()
    {
        if (_actions.MoveNext())
            _actions.Current(RunNextAction);
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        _actions = AnimationSequence().GetEnumerator();
        RunNextAction();
    }

    private Action<Action> AnimateCell(Rectangle rectangle, Color fromColor)
    {
        return completed =>
        {
            Color toColor = Colors.White;
            ColorAnimation ani = new ColorAnimation(toColor, 
                                    new Duration(TimeSpan.FromMilliseconds(300)));
            ani.AutoReverse = true;
            ani.Completed += (s, e) => completed();

            SolidColorBrush newBrush = new SolidColorBrush(fromColor);
            ani.BeginTime = TimeSpan.FromSeconds(2);
            rectangle.Fill = newBrush;
            newBrush.BeginAnimation(SolidColorBrush.ColorProperty, ani);
        };
    }
}

Try pasting the above into your program. It does what you need, but in a way that may be useful to you in other contexts. It's still event driven, but it uses an "iterator method" (with yield return) to create the impression that it is sequential coding that blocks while the animation is going on.

The nice thing about this is that you can play around with the AnimationSequence method in a very intuitive way - you could write out the timeline of the animation in a series of statements, or use loops, or whatever you want.

like image 84
Daniel Earwicker Avatar answered Nov 15 '22 07:11

Daniel Earwicker