Fire collection updated events from a special Silverlight ItemsControl

November 11, 2008

Fire collection updated events from a special Silverlight ItemsControl

For the last few months I’ve been working solely with the Model-View-ViewModel pattern developed by John Gossman of WPF fame (http://blogs.msdn.com/johngossman/archive/2005/10/08/478683.aspx). This pattern simply makes life easier :) The idea is that you create special ViewModels which map the model directly to the view… so anything the view needs to use it can get from it’s view model (the view never talks straight to the model).

The mapping is achieved using DataContext + Data Binding. You pass in the ViewModel as the DataContext and then bind to things as required. When changes are needed to the interface, you simply update the data source and binding does the rest. You can bind values from simple text and colors through to more advanced scenarios involving zoom levels and positioning.

Controls like the ItemsControl play a major role in making all this work together. ItemsControl is great because you can quickly and easily map a list of items to a template in XAML and then place them within a panel of your choice. You then bind the ItemsControl to an ObservableCollection meaning the interface will update when eve you change an item in the collection.

There is one issue with this however – what if you want to show an unload animation before the collection changes are committed. You could fire an event from the VM to the V but this requires extra work and breaks some of the M-V-VM encapsulation we worked so hard to achieve.

I wanted a simple solution where I could just update the ObservableCollection and let the system handle the rest.

So I came up with a prototype control called the EventItemsControl. Basically the EventItemsControl intercepts collection changes and allows its child controls to run animations before updating the base collection and causing a layout refresh.

Sample and demo

Live Demo

To use the demo, adjust the index to insert/remove/replace at and start pressing buttons :) Note, there is no error handling, so invalid indexes will crash the app.

The click events cause very simple changes to the underlying collection – there are no direct calls to any animation methods etc.

public void Reset()
{
    randomise();
}

public void Remove(int index)
{
    listViewModels.RemoveAt(index);
}

public void Insert(int index)
{
    SomeListItemViewModel item = getRandomItem(index);
    listViewModels.Insert(index, item);
}

public void Replace(int index)
{
    SomeListItemViewModel item = getRandomItem(index);
    listViewModels[index] = item;
}

<Sample Code>

EventItemsControl Sample Code

</Sample Code>

Create the control hierarchy

The EventItemsControl is defined just like a normal ItemsControl.

<Controls:EventItemsControl ItemsSource="{Binding ListToBindTo}" Grid.Column="0">
    <Controls:EventItemsControl.ItemsPanel>
	<ItemsPanelTemplate>
	    <StackPanel Orientation="Vertical"/>
	</ItemsPanelTemplate>
    </Controls:EventItemsControl.ItemsPanel>
    <Controls:EventItemsControl.ItemTemplate>
	<DataTemplate>
	    <View:SomeListItem/>
	</DataTemplate>
    </Controls:EventItemsControl.ItemTemplate>
</Controls:EventItemsControl>

The only difference here is that I am creating an instance of EventItemsControl.

Any control which I want to be informed when the collection is changing needs to implement the ICollectionEventControl interface.

public interface ICollectionEventControl
{
	void OnBeginUnload();
	event EventHandler ProcessingFinished;
}

When the collection is changed, the EventItemsControl scans all children for this interface and calls OnBeginUnload when found. This way I can nicely encapsulate any animations etc. in the View without needing to tie up the ViewModel or create unneeded coupling between my layers.

My example user control here (SomeListItem – shown in the EventItemsControl DataTemplate above) runs an animation on creation and one when the items is removed.

public partial class SomeListItem : UserControl, ICollectionEventControl
{
	public SomeListItem()
	{
	    InitializeComponent();

	    SBShow.Begin();

	}

	#region ICollectionEventControl Members

	public void OnBeginUnload()
	{
	    SBHide.Completed += new EventHandler(SBHide_Completed);
	    SBHide.Begin();
	}

	void SBHide_Completed(object sender, EventArgs e)
	{
	    SBHide.Completed -= new EventHandler(SBHide_Completed);
	    if (ProcessingFinished != null)
	    {
		ProcessingFinished(this, EventArgs.Empty);
	    }
	}

	public event EventHandler ProcessingFinished;

	#endregion
}

The control contains a Storyboard defined in XAML – this code merely serves to kick off the animations.

How it works

Without going in to too much detail, the EventItemsControl buffers the ItemsSource collection – then once it’s ready it changes the underlying collection. It hooks in to the INotifyCollectionChanged events on the ObservableCollection which is passed in – then figures out how to best update the underlying collection based on the change type etc. You can have a peek at the sample code yourself if you want to know more.

A quick look at the EventItemsControl

Issues

There is an issue where if you clear the collection and write new data to it the older items may still be animating out when the new items are added, meaning the on screen list will grow for a little bit… I’ll look in to fixing this *one day* :)


Silverlight Animation Builder and Chainer

November 1, 2008

For the last couple of weeks I’ve been mucking around with dynamic animations in Silverlight 2, and I found them quite tiring and verbose to write. I also found it time consuming to link them together in to a series – to run animation a, then b, then c and so on.

So I decided to do something about it, and this is the result.

High Quality (view in browser and download WMV).

I created a new set of classes collectively called the AnimationChainer. It allows you to quickly create animations using a convenient syntax. You will find creating animations with this set of classes is much nicer than the manual way. These animations can then be easily chained together to create more advanced behavior.

The basic overview of the AnimationChainer is:

  • Create groups of animations
  • Add groups to a main animation controller
  • Groups contain multiple animations
  • Animations can be delayed
  • Groups can run in serial or parallel mode (run all animations at once etc)
  • You can make the manager run all groups at once or again, serially (so run a group, which has 3 animations, then run the next group and so on)
  • You can fire callbacks on completion
  • After a manager has started animating, you can control behavior if you add more animations to it.
  • Cancel animations

Unlike some other animation systems out there already in Silverlight 2, this one uses real Timeline based animations and storyboards. A group becomes a storyboard basically, and the animations are added to it (each one can be cumulatively delayed by more and more).

<Sample Code>

Silverlight Animation Chainer Source and Sample

</Sample Code>

Important: Before this sample will work, copy the Windows Vista sample images in to AnimationHelper\Images

Live Demo

Note: Press F5 in between each demo to reset the app to the original state:)

The demo has four examples in it:

The first example demonstrates the ability to cut off an executing animation and start a new one. This happens whenever the user moves the mouse. It also shows delayed start for the groups, each letter starts slightly after the one before it.

The second example throws the Windows Vista sample images in to random positions and random rotations on the screen. It does this 10 times for each set of images. This demonstrates serial animations in groups – only one image per group comes out at a time, but parallel groups, i.e. 10 groups send out one image each at a time.

The third example is a wrap panel which uses keyframe animations to “hop” in content.

The fourth example is the same wrap panel, but this time the animations “pop” in. Animated wrap panels are cool! :)

These demonstrate only the tip of the iceburg on what is possible with this class.

I don’t get groups and stuff

So what can you do with this? You could make it so that things first zoom in to view, then they might wiggle a little, then zoom out. The Zoom in would be a ScaleY and ScaleX animation, so you would put these in the same group so they would run together. You would put the second part, the wiggle in to another group so you could make it run only after the first animation had completed.

Fluent Interface

Coding against this class couldn’t be easier.

To create the zoom in animation I spoke of you could do this:

//Set the transform group (you don't have to do this if you element comes from XAML and already has a TransformGroup)
TransformGroup tg = new TransformGroup();
element.RenderTransform = tg;

//Add a scale transform, and set it to scale X and Y to 0
ScaleTransform st = new ScaleTransform();

tg.Children.Add(st);

st.SetValue(ScaleTransform.ScaleXProperty, (double)0);
st.SetValue(ScaleTransform.ScaleYProperty, (double)0);

//Create the chainer manager to house the animation groups
AnimationChainManager cm2 = new AnimationChainManager(true); //true here means that each group will run in serial mode, one after the other.

cm2.Add() //Adds a new animation group
	.DoubleAnimation() //Adds a new DoubleAnimation to the group
	.Target(st) //Sets the animation target to the ScaleTransform
	.Property(ScaleTransform.ScaleXProperty) //Sets the Target Dependency Property
	.Duration(new TimeSpan(0, 0, 0, 0, 100)) //Sets the time span of the animation
	.From(0) //Sets the starting position of the animation
	.To(1) //Sets the ending position of the animation
	.Queue() //Adds this animation to the queue (not required, but best practice)
	.DoubleAnimation() //Creates a second animation for the other scale axis
	.Property(ScaleTransform.ScaleYProperty) //Sets the Target Dependency Property. Note we didn't have to set the Target again!
	.From(0) //Sets the starting point and so on.
	.To(1)
	.Queue();

cm2.Begin(false, false); //Start animations with parameters: Don't wait for previous anims to finish, and don't cancel any that are already running

See, easy as! Watch the video to see this in action (download and play with the sample project too).

Supported animation types

The classes support DoubleAnimation, ColorAnimation and PointAnimation. It also supports the key frame based variants of these three types.

List of available commands

  • .Serial – Run the animations within the group serially
  • .Target – The DependencyObject to animate against
  • .Property – The DependencyProperty to animation
  • .DoubleAnimation – New DoubleAnimation
  • .DoubleAnimationK – New DoubleAnimationUsingKeyFrames
  • .PointAnimation – New PointAnimation
  • .PointAnimationK – New PointAnimationUsingKeyFrames
  • .ColorAnimation – New ColorAnimation
  • .ColorAnimationK – New ColorAnimationUsingKeyFrames
  • .KeyFrame – Adds a keyframe to the KeyFrame animation. The offset is cumulative (added on to the last offset)
  • .Context – Some context to fire back with the completion event and callback
  • .From – From value for all animation types
  • .To – To value for all animation types
  • .Duration – The length of this animation
  • .Offset – The offest from the start of the group to start this animation
  • .CompleteAction – Action callback
  • .Reverse – Replay the animation backwards on completion
  • .Queue – Adds the animation to the queue

When adding multiple animations to the same group, you don’t need to keep setting properties like Target and Duration – the system remembers them and does it automatically for you.

The Manager

The manager can be configured to behave in different ways.

  • Play groups sequentially (altered in constructor)
  • Toggle cancel groups current playing and start newly added groups
  • Toggle wait for current groups to finish before playing newly added groups
  • Begin method starts playing the groups
  • GroupDelay method allows you to set a group to wait before animating.

How does it work

Basically this set of classes is just a wrapper for the animation classes. Each group is converted in to a StoryBoard when it’s told to run :) Download the code and check it out for yourself.

Another example – keyframes

This sample is from the main project available for download. It makes the elements of the wrap panel jump on to the screen.

cm2.Add()
.DoubleAnimationK() //Create the keyframe animation
.Target(tt) //Set its target to the TranslateTransform created earlier
.Property(TranslateTransform.YProperty) //Tell it to do the YProperty
.KeyFrame(-height, new TimeSpan(0, 0, 0, 0, 1)) //Add first keyframe
.KeyFrame(-300, new TimeSpan(0, 0, 0, 0, 100)) //Add the second keyframe at 101ms
.KeyFrame(0, new TimeSpan(0, 0, 0, 0, 100)) //Add third keyframe, at 201ms (1 + 100 + 100)
.Queue() //Add to queue
.DoubleAnimationK() //Repeat for other axis
.Property(TranslateTransform.XProperty)
.KeyFrame(-(width + realLeft), new TimeSpan(0, 0, 0, 0, 0))
.KeyFrame(-300,new TimeSpan(0, 0, 0, 0, 100))
.KeyFrame(0,new TimeSpan(0, 0, 0, 0, 100))
.Queue();