打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
custom animation class

Introduction

When we (Christian and Nish) started our WPF series last July, we didn‘texactly plan to have a 9 month gap between parts 1 and 2. We were both busywith various projects and we kept procrastinating writing the next part. Well,now we are ready with the 2nd part of our WPF series and as they say - betterlate than never. In this article, we will talk about how animations can beapplied on properties that do not have an associated animation class, and wewill specifically focus on animating the GridLength propertywith respect to a Grid control‘s columns and rows.

Trouble with animating a Grid‘s columns and rows

Before we get into animating the GridLength, we‘ll lookat why it‘s different from animating a property such as Widthor Opacity. And while we expect you to have a basicunderstanding of how animations work in WPF, let‘s briefly look at how regularanimations work, where by regular we mean animating properties that haveassociated animation classes.

How do regular animations work?

Typical usages of animations involve changing a property (usually a dependencyproperty) over a specific duration via linear interpolation. As an example, thefollowing Xaml shows how a button‘s opacity can be animated from fully opaque tofully transparent.

<Button Name="button1">
One
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="button2"
Storyboard.TargetProperty="Opacity"
From="1" To="0" Duration="0:0:2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>

We could specify double values 1 and 0 as start and stop values for the Opacity property. The DoubleAnimation classthat we used is specialized to work with properties of type double(and Opacity, Width, Lengthetc. are all of type double). If you look at the System.Windows.Media.Animation namespace, you‘ll see that there areother specialized animation classes for handling values of other common typessuch as Boolean, Char, Byte, Color, Point etc.Next lets look at why the Grid‘s column and rowdimensions cannot be animated this way.

Why can‘t regular animations work with a Grid?

The ColumnDefinition and RowDefinitionclasses have Width and Height properties(respectively) of type GridLength. The GridLengthis a struct whose purpose is to support Star-basedunits in addition to pixel-based units. Star-unitsspecify a dimension as a weighted proportion of the total available space. Soif you have two columns, where the first has a width {*}and the second has a width of {3*}, the first columnwill take up 25% of the total width of the containing panel, while the secondcolumn will take up the remaining 75% of space. This offers us a lot offlexibility when using grids and we don‘t need to specify hard coded values -which has the added advantage that it‘s easier to add rows and columns infuture.

The side-effect of the fact that the Grid uses GridLength for dimensions is that we cannot use any of the library‘sbuilt-in animation classes with it since none of them were intended to support GridLength. But that does not mean there‘s nothing we can do aboutit. We can (and will) write an animation class specifically for handling unitsof type GridLength.

Writing a custom animation class for GridLength

Our aim is write a GridLengthAnimation class that willsupport animations based on the GridLength property. Tokeep the example simple and to the point, we will only support the Fromand To properties, and will not support properties suchas By, which are supported by other classes such as DoubleAnimation. It would be trivial to add a Byproperty and this is left as an exercise for the reader (shouldn‘t take youmore than a few minutes).

We derive a class from AnimationTimeline whichrepresents a time line over which values are produced (in our case we‘llproduce GridLength values between the Fromand To range).

namespace GridAnimationDemo
{
internal class GridLengthAnimation : AnimationTimeline
{

We have to override the TargetPropertyType propertywhich is abstract in AnimationTimeline.This is a get-only property that returns the type of theproperty that will be animated across a range of supported values. Ourimplementation is simple and returns the type of the GridLengthobject.

public override Type TargetPropertyType
{
get
{
return typeof(GridLength);
}
}

AnimationTimeLine has a protectedconstructor, and thus any animation object that derives from it has to becreated indirectly. Animation classes indirectly derive from Freezable, which defines objects that have two states - mutable(unfrozen) andimmutable(frozen). Such classes need to implement (override) a CreateInstanceCoremethod which will be used to construct the animation (freezable) object. CreateInstanceCore will be called by GetCurrentValueAsFrozenCoreto return a freezable clone of the current object (which may or may not be in afrozen state at that moment). Again our implementation is very simple.

protected override System.Windows.Freezable CreateInstanceCore()
{
return new GridLengthAnimation();
}

Next, we‘ll add two dependency properties to handle Fromand To properties. For an excellent write-up ondependency properties outside of MSDN, read WPF guru and MVP Josh Smith‘s blogentry on this topic : Dependency Properties by Josh Smith. The implementation isstraightforward and there‘s nothing special to be done here.

Collapse
static GridLengthAnimation()
{
FromProperty = DependencyProperty.Register("From", typeof(GridLength),
typeof(GridLengthAnimation));

ToProperty = DependencyProperty.Register("To", typeof(GridLength),
typeof(GridLengthAnimation));
}
public static readonly DependencyProperty FromProperty;
public GridLength From
{
get
{
return (GridLength)GetValue(GridLengthAnimation.FromProperty);
}
set
{
SetValue(GridLengthAnimation.FromProperty, value);
}
}
public static readonly DependencyProperty ToProperty;
public GridLength To
{
get
{
return (GridLength)GetValue(GridLengthAnimation.ToProperty);
}
set
{
SetValue(GridLengthAnimation.ToProperty, value);
}
}

Now all that‘s left is to override GetCurrentValue andreturn the current animated value of the property that‘s being animated.

public override object GetCurrentValue(object defaultOriginValue, 
object defaultDestinationValue, AnimationClock animationClock)
{
double fromVal = ((GridLength)GetValue(GridLengthAnimation.FromProperty)).Value;
double toVal = ((GridLength)GetValue(GridLengthAnimation.ToProperty)).Value;

if (fromVal > toVal)
{
return new GridLength((1 - animationClock.CurrentProgress.Value) *
(fromVal - toVal) + toVal, GridUnitType.Star);
}
else
{
return new GridLength(animationClock.CurrentProgress.Value *
(toVal - fromVal) + fromVal, GridUnitType.Star);
}
}

What we do is calculate and return a gradated value based on the current valueof the AnimationClock object - which will be between 0and 1. We create a GridLength object by using theconstructor that accepts a GridUnitType as the secondargument for which we specify Star. That‘s it - our GridLengthAnimation class is ready, and we‘ll now see how it can beput to use.

Class usage

Let‘s look at how the class can be used from both procedural code and fromXaml.

The sample app

The sample project has a grid with three rows and two columns and each cell hasan image. Note that all six photos shown in the screen shot and available in theproject zip were taken by Nish, and those images are royalty free and may bereused in whatever legitimate way the reader needs to. You can click onany of the six images and that cell will animate to fill the window, while theother cells will diminish in size till they vanish. And if you click on themaximized image, the reverse animation occurs - where the current image sizesback to its original dimensions, and the other cells will obviously increase atthe same time, until they return to their starting positions. The GridLengthAnimationclass is used from procedural code in the demo project as shown below.

Collapse
void image_MouseDown(object sender, MouseButtonEventArgs e)
{
Image image = sender as Image;
if (image != null)
{
int col = Grid.GetColumn(image);
int row = Grid.GetRow(image);

for (int indexRow = 0; indexRow < mainGrid.RowDefinitions.Count;
indexRow++)
{
if (indexRow != row)
{
GridLengthAnimation gla = new GridLengthAnimation();
gla.From = new GridLength(bSingleImageMode
0 : 1, GridUnitType.Star);
gla.To = new GridLength(bSingleImageMode
1 : 0, GridUnitType.Star); ;
gla.Duration = new TimeSpan(0, 0, 2);
mainGrid.RowDefinitions[indexRow].BeginAnimation(
RowDefinition.HeightProperty, gla);
}

}

for (int indexCol = 0;
indexCol < mainGrid.ColumnDefinitions.Count; indexCol++)
{
if (indexCol != col)
{
GridLengthAnimation gla = new GridLengthAnimation();
gla.From = new GridLength(bSingleImageMode
0 : 1, GridUnitType.Star);
gla.To = new GridLength(bSingleImageMode
1 : 0, GridUnitType.Star);
gla.Duration = new TimeSpan(0, 0, 2);
mainGrid.ColumnDefinitions[indexCol].BeginAnimation(
ColumnDefinition.WidthProperty, gla);
}
}
}
bSingleImageMode = !bSingleImageMode;
}

Note that while the demo uses procedural code (since it needs to dynamicallyapply the animation to the clicked on image cell), you can use it from Xaml toojust as you would use any other animation class. Also note how in the samplecode, we‘ve iterated through the rows and columns and run animations one afterthe other. For a more complicated scenario, you would want to create a StoryBoard and have all the animations run in parallel, instead ofone after the other as we‘ve done above.

Using from Xaml

Here‘s some sample Xaml that shows how the GridLengthAnimation class can be used from Xaml to animate a grid‘s column width.

<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Name="Col0" Width="*"/>
<ColumnDefinition Name="Col1" Width="*"/>
</Grid.ColumnDefinitions>

<Button Name="button1">
One
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<proj:GridLengthAnimation
Storyboard.TargetName="Col1"
Storyboard.TargetProperty="Width"
From="*" To="2*" Duration="0:0:2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>

<Button Name="button2" Grid.Column="1">Two</Button>
</Grid>
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
WPF 事件触发器
动态定义Grid行列
WPF简单动画
利用C#动态生成Xaml
C# WPF从后台代码生成行列可变的表格
animate.css在vue项目中的使用教程
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服