Telerik blogs

We all know that animations are making our applications look more slick. As Silverlight and WPF are platforms that have great support for animations, we want our Silverlight and WPF applications to be more animated and look better.

 

 

Today I will show you how to add animations support to the Telerik RadWindow control using one of the most powerful approaches in WPF and Silverlight for extending the functionality of existing components – the attached behavior. We will create two attached properties that take care to run the animations when needed. The good thing is that you can use this approach for almost every piece of functionality that you need to add to any component.

First of all we need to create a class that will host our attached properties. Let’s call it WindowAnimation and add the Attached properties that will handle the animations. Here is the code:

 

public static class WindowAnimation
{
 public static Storyboard GetOpenAnimation(DependencyObject obj)
    {
 return (Storyboard)obj.GetValue(OpenAnimationProperty);
    }
 
 public static void SetOpenAnimation(DependencyObject obj, Storyboard value)
    {
        obj.SetValue(OpenAnimationProperty, value);
    }
 
 public static readonly DependencyProperty OpenAnimationProperty =
        DependencyProperty.RegisterAttached("OpenAnimation", typeof(Storyboard), typeof(WindowAnimation), new PropertyMetadata(OnOpenAnimationChanged));
 
 private static void OnOpenAnimationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
    }
 
 public static Storyboard GetCloseAnimation(DependencyObject obj)
    {
 return (Storyboard)obj.GetValue(CloseAnimationProperty);
    }
 
 public static void SetCloseAnimation(DependencyObject obj, Storyboard value)
    {
        obj.SetValue(CloseAnimationProperty, value);
    }
 
 public static readonly DependencyProperty CloseAnimationProperty =
        DependencyProperty.RegisterAttached("CloseAnimation", typeof(Storyboard), typeof(WindowAnimation), new PropertyMetadata(OnCloseAnimationChanged));
 
 private static void OnCloseAnimationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
    }
}
 

 

As you can see the Attached properties are of type Storyboard – the animation that will be triggered when the window is opened or closed. Now we need to attach the events when a Storyboard is set or detach them when the value is Null. As the Open animation is the more simple case, we will implement it first.

 

 

We have two cases that we need to take care of – the old value is null and the new one is not null and the old value is not null and the new value is null.

What happens in the first case is that we get a OpenAnimation for the first time and we need to hook up to the events of the window (and in particular to the Opened event).

The second case is opposite to the first one – the OpenAnimation is removed from the window and now we don’t need to listen to the Window Opened event any more. In this case we need to remove the hooked up events.

Here is the code that accomplishes this task:

 

private static void OnOpenAnimationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var window = d as RadWindow;
    var newValue = e.NewValue as Storyboard;
    var oldValue = e.OldValue as Storyboard;
 
 if (window != null)
    {
 if (oldValue == null)
        {
            window.Opened += OnWindowOpened;
        }
 else if (newValue == null)
        {
            window.Opened -= OnWindowOpened;
        }
    }
}
 
private static void OnWindowOpened(object sender, RoutedEventArgs e)
{
}

 

 

Now we need to figure out what we should do when the RadWindow control is opened. We need to hook up to the Completed event of the animation and just to run it. When the animation is completed we need to unhook the Completed event. Here is the code:

 

private static void OnWindowOpened(object sender, RoutedEventArgs e)
{
    var window = sender as RadWindow;
 if (window != null)
    {
        var openAnimation = GetOpenAnimation(window);
 if (openAnimation != null)
        {
            openAnimation.Completed += OnOpenAnimationCompleted;
            openAnimation.Begin();
        }
    }
}
 
private static void OnOpenAnimationCompleted(object sender, EventArgs e)
{
    var openAnimation = sender as Storyboard;
 if (openAnimation != null)
    {
        openAnimation.Completed -= OnOpenAnimationCompleted;
    }
}

 

 

With this we are ready to try the OpenAnimation attached property out. Here is simple example how to use this property:

 

<telerikNavigation:RadWindow x:Name="Window" Header="Window"
 RenderTransformOrigin="0.5, 0.5">
 <local:WindowAnimation.OpenAnimation>
 <Storyboard Storyboard.TargetName="scaleTransform">
 <DoubleAnimation Duration="0:0:0.150" Storyboard.TargetProperty="ScaleX"
 From="0.5" To="1.0" />
 <DoubleAnimation Duration="0:0:0.150" Storyboard.TargetProperty="ScaleY"
 From="0.5" To="1.0" />
 </Storyboard>
 </local:WindowAnimation.OpenAnimation>
 <telerikNavigation:RadWindow.RenderTransform>
 <ScaleTransform x:Name="scaleTransform" />
 </telerikNavigation:RadWindow.RenderTransform>
 <Button Content="Sample content" />
</telerikNavigation:RadWindow>

 

 

The idea of the close animation is similar, but we need to take care of some problems. The main one is that when the window fires its Closed event it is completely closed. If we look at the PreviewClosed event we will notice that the RadWindow control is closed almost immediately after its PreviewClosed event is called (if it haven’t been canceled). What we could do is to handle the PreviewClosed event and to cancel it. After that we can start our animation and after the animation is completed we can close the window. It is easy to notice that the PreviewClosed event will be called again and we need some kind of flag to know who tries to close the window and do we need to show animation. Something more – we need to associate the window to an animation (the animation is already associated to a window by the attached property we already added). There are many ways you can achieve this, but I’ll choose the following approach – I’ll create a class that will keep both the flag and the reference to the RadWindow control that owns it. Here is the code for the close animation:

 

public static Storyboard GetCloseAnimation(DependencyObject obj)
{
 return (Storyboard)obj.GetValue(CloseAnimationProperty);
}
 
public static void SetCloseAnimation(DependencyObject obj, Storyboard value)
{
    obj.SetValue(CloseAnimationProperty, value);
}
 
public static readonly DependencyProperty CloseAnimationProperty =
    DependencyProperty.RegisterAttached("CloseAnimation", typeof(Storyboard), typeof(WindowAnimation), new PropertyMetadata(OnCloseAnimationChanged));
 
private static void OnCloseAnimationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var window = d as RadWindow;
    var newValue = e.NewValue as Storyboard;
    var oldValue = e.OldValue as Storyboard;
 
 if (window != null)
    {
 if (oldValue == null)
        {
            window.SetValue(EventWrapperProperty, new CloseEventsWrapper(window));
        }
 else if (newValue == null)
        {
            var wrapper = window.GetValue(EventWrapperProperty) as CloseEventsWrapper;
            wrapper.DetachCloseEvents();
            window.ClearValue(EventWrapperProperty);
        }
    }
}
  
private static readonly DependencyProperty EventWrapperProperty =
    DependencyProperty.RegisterAttached("EventWrapper", typeof(CloseEventsWrapper), typeof(WindowAnimation), null);
 
private class CloseEventsWrapper
{
 private RadWindow window;
 
 private bool closing;
 
 public CloseEventsWrapper(RadWindow window)
    {
 this.window = window;
 this.window.PreviewClosed += this.OnWindowPreviewClosed;
    }
 
 
 public void DetachCloseEvents()
    {
 this.window.PreviewClosed -= this.OnWindowPreviewClosed;
 this.window = null;
    }
 
 private void OnWindowPreviewClosed(object sender, WindowPreviewClosedEventArgs e)
    {
 if (closing)
        {
            closing = false;
        }
 else
        {
            var closeAnimation = GetCloseAnimation(this.window);
 if (closeAnimation != null)
            {
                e.Cancel = true;
                closing = true;
 
                closeAnimation.Completed += this.OnCloseAnimationCompleted;
                closeAnimation.Begin();
            }
        }
    }
 
 private void OnCloseAnimationCompleted(object sender, EventArgs e)
    {
        var openAnimation = sender as Storyboard;
 if (openAnimation != null)
        {
            openAnimation.Completed -= OnOpenAnimationCompleted;
 this.window.Close();
        }
    }
}

 

 

The CloseEventWrapper is the class I was talking about before. It has a reference to the RadWindow control and a flag that is true when the windows is currently closing and false when not. We need to asociate the CloseEventWrapper instance to the window, because we will need to detach it in some moment. We could use a Dictionary for this purpose, but as the RadWindow is a DependencyObject I choose to use an attached property to hold this object.

 

 

Here is an example of how to use both of these properties. I changed the show animation to look more slick:

 

<telerikNavigation:RadWindow x:Name="Window" Header="Window"
 RenderTransformOrigin="0.5, 0.5">
 <local:WindowAnimation.OpenAnimation>
 <Storyboard Storyboard.TargetName="scaleTransform">
 <DoubleAnimationUsingKeyFrames Duration="0:0:0.150"
 Storyboard.TargetProperty="ScaleX">
 <SplineDoubleKeyFrame KeyTime="0:0:0.100" Value="1.5" />
 <SplineDoubleKeyFrame KeyTime="0:0:0.150" Value="1" />
 </DoubleAnimationUsingKeyFrames>
 <DoubleAnimationUsingKeyFrames Duration="0:0:0.150"
 Storyboard.TargetProperty="ScaleY">
 <SplineDoubleKeyFrame KeyTime="0:0:0.100" Value="1.5" />
 <SplineDoubleKeyFrame KeyTime="0:0:0.150" Value="1" />
 </DoubleAnimationUsingKeyFrames>
 </Storyboard>
 </local:WindowAnimation.OpenAnimation>
 <local:WindowAnimation.CloseAnimation>
 <Storyboard Storyboard.TargetName="scaleTransform">
 <DoubleAnimation Duration="0:0:0.150" Storyboard.TargetProperty="ScaleX"
 From="1.0" To="0.5" />
 <DoubleAnimation Duration="0:0:0.150" Storyboard.TargetProperty="ScaleY"
 From="1.0" To="0.5" />
 </Storyboard>
 </local:WindowAnimation.CloseAnimation>
 
 <telerikNavigation:RadWindow.RenderTransform>
 <ScaleTransform x:Name="scaleTransform" />
 </telerikNavigation:RadWindow.RenderTransform>
 <Button Content="Sample content" />
</telerikNavigation:RadWindow>

 

 

You can download the full source code from here.


About the Author

Miroslav Nedyalkov

is a XAML enthusiast. Speaker at various local user groups and events. Miroslav is passionate about cutting edge technologies and likes to share his knowlage with others. You can follow him on Twitter at @miro_nedyalkov.

Comments

Comments are disabled in preview mode.