WPF Screen Transition

One of my favorite things about WPF is the ability to use gratuitous animations in places where it would have been difficult in previous technologies.  We threw in one of these animations on our last project.  The project was using a Surface, but since it is just WPF, the idea is the same.
Here is the high level concept.  You start out with a main window in your application.  In this main window, you want to change some content.  Instead of having a jumpy screen jerk, you get a nice animation from screen to screen.  This could be a fade in and fade out, or it could be a slide in and slide out.  It’s totally up to you as to how far you want to go.  So let’s get started.
First, we have our main window.

<Window x:Class="ScreenTransition.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="The Transition Window"
Height="300"
Width="450">
<Canvas x:Name="TransitionContainer" />
</Window>

This is going to be where all of the work is done.  This is all you will see in the Xaml view of your window.  We are going to use the Canvas later on to add and remove UIElements from.  Let’s jump to the code behind.

namespace ScreenTransition
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class MainWindow : Window
{
readonly Duration _animationDuration = new Duration(TimeSpan.FromSeconds(1.0));

DoubleAnimation CreateDoubleAnimation(double from, double to, EventHandler completedEventHandler)
{
DoubleAnimation doubleAnimation = new DoubleAnimation(from, to, _animationDuration);

if (completedEventHandler != null)
{
doubleAnimation.Completed += completedEventHandler;
}

return doubleAnimation;
}

public MainWindow()
{
InitializeComponent();
}

}
}

This is the start of our window.  First, our Duration.  Just to keep some consistency, we are going to make a single Duration.  This way, we don’t have to keep making one, and the times will be the same to give our end user a consistent experience.  The CreateDoubleAnimation method is just a helper for us.  This will create the double animation and hook up to the completed event hander that we pass to it (could be an anonymous delegate…).  Now, lets take a look at the two animations I chose to create as a starting point.

void FadeAnimation(UIElement newContent, UIElement oldContent, EventHandler completedEventHandler)
{
newContent.Opacity = 0;

TransitionContainer.Children.Add(newContent);

DoubleAnimation outAnimation = CreateDoubleAnimation(1, 0, null);
DoubleAnimation inAnimation = CreateDoubleAnimation(0, 1, completedEventHandler);

oldContent.BeginAnimation(UIElement.OpacityProperty, outAnimation);
newContent.BeginAnimation(UIElement.OpacityProperty, inAnimation);
}
void SlideAnimation(UIElement newContent, UIElement oldContent, EventHandler completedEventHandler)
{
double leftStart = Canvas.GetLeft(oldContent);
Canvas.SetLeft(newContent, leftStart - Width);

TransitionContainer.Children.Add(newContent);

if (double.IsNaN(leftStart))
{
leftStart = 0;
}

DoubleAnimation outAnimation = CreateDoubleAnimation(leftStart, leftStart + Width, null);
DoubleAnimation inAnimation = CreateDoubleAnimation(leftStart - Width, leftStart, completedEventHandler);

oldContent.BeginAnimation(Canvas.LeftProperty, outAnimation);
newContent.BeginAnimation(Canvas.LeftProperty, inAnimation);
}

Each of these methods are going to take two UIElements, the old element (current content) and the new element (content to be shown).  The methods also take the same completed event handler so we can pass it on to our helper method we just created.  The FadeAnimation is going to set the opacity of the new element to 0, add it to the TransitionContainer (our only item in our Window, remember from before?).  Then, we animation our new elements opacity from 0 to 1, and the old elements opacity from 1 to 0, creating a smooth fade in / fade out effect.  Cool huh?  Same idea for the slide animation, except we are going to animate the Canvas.Left property of each UIElement.  Only tricky part here is to make sure that the Canvas.Left property is actually set on the element, and if not, assume it is 0.  Next, the publicly exposed method that our application can call to change content.

public void ChangeContent(UIElement newContent)
{
if (TransitionContainer.Children.Count == 0)
{
TransitionContainer.Children.Add(newContent);
return;
}

if (TransitionContainer.Children.Count == 1)
{
TransitionContainer.IsHitTestVisible = false;
UIElement oldContent = TransitionContainer.Children[0];

EventHandler onAnimationCompletedHandler = delegate(object sender, EventArgs e)
{
TransitionContainer.IsHitTestVisible = true;
TransitionContainer.Children.Remove(oldContent);
if (oldContent is IDisposable)
{
(oldContent as IDisposable).Dispose();
}
oldContent = null;
};

SlideAnimation(newContent, oldContent, onAnimationCompletedHandler);
//FadeAnimation(newContent, oldContent, onAnimationCompletedHandler);
}
}

First up in this method, we check if there are any items in our Container.  If there are not, this is the first time into the screen and instead of a fade, we will just show the content right away.  If there are more than one item, we are going to do our fade.  We already went over the methods that do all of the animations, so we can ignore those calls.  First, we want to make sure that our container can’t be interacted with while the content switch is happening, so we set the IsHitTestVisible to false for now.  Next, we are going to assume that the TransitionContainer’s first element is the old content and store a reference for use as our old content.  Next, the in line delegate.  When the animation completes, we want to set IsHitTestVisible back to true.  Next, we want to remove our old content.  Finally, we want to call dispose on our object if it implements IDisposable, and the set it to null.  After that is just the calls to our two methods.  That’s it!  Here is a link to some sample code.

Share this post!

1 comments:

Anonymous said...

Hi :)
Can you resend a valid link please ? Thanks !

Post a Comment