Farr far away....

gary farr's blog
in

Better XAML By FARR: Animations, Resources Vs. Code Behind

Animations are a feature in WPF and Silverlight that greatly enhance the user experience of the application.  Animations provide a smooth transition from one scene or view to the next and can significantly improve the overall composition and flow of that application.  Animations in WPF, currently are time based which means an animation will start and stop based on your computer system clock.  This, at times, could be an issue if your processor is experiencing excessive usage, your animation might skip frames thus resulting in choppy and less fluid animations. 

In XAML, animations are created using a storyboards which can either be defined in XAML or code behind.  Please refer to the MSDN explanation of animations for more of an introductory.  This post details when it is a good time to create a storyboard as a resource in XAML and when it is more beneficial to create a storyboard in code behind.  

XAML Storyboards

For most cases, such as dealing with opacity, brush, or simplistic transform animations, storyboards can and should be created in XAML.  Below is a simple example of a storyboard that animates the three parts described.

   1: <Storyboard x:Key="LoadAnimation">
   2:         <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="grid" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
   3:             <SplineDoubleKeyFrame KeySpline="0,0,0,1" KeyTime="00:00:00" Value="0.5"/>
   4:             <SplineDoubleKeyFrame KeySpline="0.782,0,0,1" KeyTime="00:00:00.3000000" Value="1.1"/>
   5:             <SplineDoubleKeyFrame KeySpline="0,0,0,1" KeyTime="00:00:00.6000000" Value="1"/>
   6:         </DoubleAnimationUsingKeyFrames>
   7:         <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="grid" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
   8:             <SplineDoubleKeyFrame KeySpline="0,0,0,1" KeyTime="00:00:00" Value="0.75"/>
   9:             <SplineDoubleKeyFrame KeySpline="0.782,0,0,1" KeyTime="00:00:00.3000000" Value="1.1"/>
  10:             <SplineDoubleKeyFrame KeySpline="0,0,0,1" KeyTime="00:00:00.6000000" Value="1"/>
  11:         </DoubleAnimationUsingKeyFrames>
  12:         <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="txtTitle" Storyboard.TargetProperty="(UIElement.Opacity)">
  13:             <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0" KeySpline="1,0,1,1"/>
  14:             <SplineDoubleKeyFrame KeyTime="00:00:00.6000000" Value="1" KeySpline="1,0,1,1"/>
  15:         <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="rectangle" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
  16:             <SplineColorKeyFrame KeyTime="00:00:00" Value="#CC333333"/>
  17:             <SplineColorKeyFrame KeyTime="00:00:00.4000000" Value="Black"/>
  18:         </ColorAnimationUsingKeyFrames>
  19: </Storyboard>

Now if you remember from my previous blog, Resource Dictionaries Vs. User Control Resources, this resource can either be in a resource dictionary that all XAML files can have access to or in a specific User Controls resource.  My example above is actually taken from a Resource Dictionary because I have multiple views load using this animation. 

Code-Behind Storyboards

So when is it a good time to create code behind storyboards?  Well, I have found it is easier to create storyboards in code behind when you are dealing with any sort of animation that requires run time decided parameters to be passed into it.  That is not to say this cannot be achieved in XAML as well, but a rule I follow is that any logic determinant code should be in code behind, controllers, or view models.  One thing to remember, code behind storyboards should be contained within some User control code behind itself or a base control code behind.  A lot of people are moving towards the MV-VM pattern and a rule of thumb is that anything to do with design altering should be within the View and not the View Model.  Below is an example on my application where I calculate the animation trajectory of an image. 

   1: private void BeginIndicatorAnimation()
   2:        {
   3:            Storyboard indicatorStoryboard = new Storyboard();
   4:  
   5:            _animatedImage = new Image();
   6:            _animatedImage.Source = _indicatoritems.IndicatorSmallImage;
   7:            _animatedImage.Height = 200;
   8:            _animatedImage.Width = 200;
   9:            _animatedImage.VerticalAlignment = VerticalAlignment.Top;
  10:            _animatedImage.HorizontalAlignment = HorizontalAlignment.Left;
  11:            _animatedImage.Margin = new Thickness(0, 0, 0, 0);
  12:  
  13:            Grid grid = (Grid)this.FindName("grid");
  14:            ConvertTargetToImage(grid);
  15:  
  16:            grid.Children.Add(_animatedImage);
  17:  
  18:            System.Windows.Markup.INameScope currentNameScope = NameScope.GetNameScope(this);
  19:            NameScope.SetNameScope(this, new NameScope());
  20:  
  21:            _animatedImage.Name = "adornLayer";
  22:            this.RegisterName(_animatedImage.Name, _animatedImage);
  23:  
  24:            TranslateTransform animatedTranslateTransform = new TranslateTransform();
  25:            this.RegisterName("AnimatedTranslateTransform", animatedTranslateTransform);
  26:  
  27:            ScaleTransform animatedScaleTransform = new ScaleTransform();
  28:            this.RegisterName("AnimatedScaleTransform", animatedScaleTransform);
  29:  
  30:            TransformGroup transformGroup = new TransformGroup();
  31:            transformGroup.Children.Add(animatedScaleTransform);
  32:            transformGroup.Children.Add(animatedTranslateTransform);
  33:  
  34:            _animatedImage.RenderTransformOrigin = new Point(0.5, 0.5);
  35:            _animatedImage.RenderTransform = transformGroup;
  36:  
  37:            //Create Animation Path
  38:            PathGeometry pathGeometry = new PathGeometry();
  39:            PathFigure pathFigure = new PathFigure();
  40:  
  41:            double x = (CurrentPosition.X - (_animatedImage.Width / 2));
  42:            double y = (CurrentPosition.Y - (_animatedImage.Height / 2));
  43:  
  44:            pathFigure.StartPoint = new Point(x, y);
  45:            pathFigure.Segments.Add(new LineSegment(new Point(980 - 120 * (_indicatoritems.MealIndicatorCount - ++_indicatoritems.CurrentIndex), 90), true));
  46:  
  47:            pathGeometry.Figures.Add(pathFigure);
  48:            pathGeometry.Freeze();
  49:  
  50:            //Create Animation
  51:            //Opactiy 
  52:            DoubleAnimation opacityAnimation = new DoubleAnimation();
  53:            opacityAnimation.From = 1.0;
  54:            opacityAnimation.To = 0.0;
  55:            opacityAnimation.AccelerationRatio = 1.0;
  56:            opacityAnimation.Duration = new Duration(TimeSpan.FromSeconds(1.0));
  57:            Storyboard.SetTargetName(opacityAnimation, _animatedImage.Name);
  58:            Storyboard.SetTargetProperty(opacityAnimation, new PropertyPath(Image.OpacityProperty));
  59:  
  60:            //translate X to Path
  61:            DoubleAnimationUsingPath translateXAnimation = new DoubleAnimationUsingPath();
  62:            translateXAnimation.PathGeometry = pathGeometry;
  63:            translateXAnimation.Duration = TimeSpan.FromSeconds(1.0);
  64:            translateXAnimation.Source = PathAnimationSource.X;
  65:            translateXAnimation.AccelerationRatio = 1.0;
  66:  
  67:            Storyboard.SetTargetName(translateXAnimation, "AnimatedTranslateTransform");
  68:            Storyboard.SetTargetProperty(translateXAnimation,
  69:                new PropertyPath(TranslateTransform.XProperty));
  70:  
  71:            //Translate Y to Path
  72:            DoubleAnimationUsingPath translateYAnimation = new DoubleAnimationUsingPath();
  73:            translateYAnimation.PathGeometry = pathGeometry;
  74:            translateYAnimation.Duration = TimeSpan.FromSeconds(1.0);
  75:            translateYAnimation.Source = PathAnimationSource.Y;
  76:  
  77:            Storyboard.SetTargetName(translateYAnimation, "AnimatedTranslateTransform");
  78:            Storyboard.SetTargetProperty(translateYAnimation,
  79:                new PropertyPath(TranslateTransform.YProperty));
  80:  
  81:            DoubleAnimation scaleXAnimation = new DoubleAnimation(0.4, new Duration(TimeSpan.FromSeconds(1.1)));
  82:            Storyboard.SetTargetName(scaleXAnimation, "AnimatedScaleTransform");
  83:            Storyboard.SetTargetProperty(scaleXAnimation, new PropertyPath(ScaleTransform.ScaleXProperty));
  84:  
  85:            DoubleAnimation scaleYAnimation = new DoubleAnimation(0.4, new Duration(TimeSpan.FromSeconds(1.1)));
  86:            Storyboard.SetTargetName(scaleYAnimation, "AnimatedScaleTransform");
  87:            Storyboard.SetTargetProperty(scaleYAnimation, new PropertyPath(ScaleTransform.ScaleYProperty));
  88:  
  89:            indicatorStoryboard.Children.Clear();
  90:            indicatorStoryboard.Children.Add(scaleXAnimation);
  91:            indicatorStoryboard.Children.Add(scaleYAnimation);
  92:            indicatorStoryboard.Children.Add(translateXAnimation);
  93:            indicatorStoryboard.Children.Add(translateYAnimation);
  94:            indicatorStoryboard.Children.Add(opacityAnimation);
  95:            indicatorStoryboard.FillBehavior = FillBehavior.Stop;
  96:  
  97:            TimeSpan s = indicatorStoryboard.Children.Max(z => z.Duration.TimeSpan);
  98:            EventAggregator.GetEvent<HitTestDelayEvent>().Publish(s);
  99:  
 100:            _sbPopupAnimation = indicatorStoryboard.Clone();
 101:            _sbPopupAnimation.Name = "Indicator";
 102:            _sbPopupAnimation.Completed += PopupStoryBoardEndCompleted;
 103:            _sbPopupAnimation.Begin(this);
 104:  
 105:            NameScope.SetNameScope(this, currentNameScope);
 106:        }

The code snippet above details an animation when a user clicks on a button within a screen and creates an image that is animated to a list of indicators somewhere arbitrarily on the screen.  As the image is animating, its size is being scaled down.  The pathFigure is a calculation that determines the starting point and ending point as well as the trajectory of the image during animation. 

Conclusion

As developers, we probably feel more comfortable creating animations in code behind.  I know i do.  However, I would say probably 85% of the animations I generally create are setup in XAML.  One way that makes the decision of where to put animations easier is to ask yourself, is my animation related to design or logic?  XAML - design, code behind – business logic.  If its design (UIElement updates and manipulations) then the animation should be in XAML.  If its logic (animations determined by some runtime property or calculation) then the animation should be in code behind. 

Comments

CACS Administrator at tobacco smoke said:

Pingback from  CACS Administrator at  tobacco smoke

# June 9, 2009 5:24 PM
Leave a Comment

(required) 

(required) 

(optional)

(required)