Introduction
Windows Presentation Foundation (WPF) supports browser-style navigation that can
be used in two types of applications: standalone applications and XAML browser
applications (XBAPs). To package content for navigation, WPF provides the Page
class [MSDN]. The WPF support of page navigation is very similar to the page
navigation in the web. Each page has its own content and each page can contain
hyperlinks to point to other pages similar to the web. But unlike the web, WPF
has great build in support for animations. It would be a good idea if the WPF
pages could be animated; similar to the slides of the MS PowerPoint. In the MS
PowerPoint, user just selects an animation for the slide entrance and an animation
for the slide movement. Although, one can easily define Storyboards and Transforms
and then assign them to the pages, but it makes the XAML code too crowded. The
problem is the fact that the stupid Storyboard and its target have to be in the
same NameScope. In practical, many developers prefer to don’t have such fancy graphic feature in
case it makes their codes complicated. The ideal is a framework that can be easily
deployed without any risk and whenever the developer doesn’t like it, he/she
could remove it without any cost; here is the main requirement we wanted from
the framework.
1- A plug and play deployment that can be used in both WPF and Silverlight.
2- The ability to animate page entering and page moving.
3- Containing many animations.
4- The ability of extension. It should not be difficult for graphic designers to extend
the codes.
In this paper, I develop such framework. Using the framework is simple and needs
adding one attached property to the navigation frame.
<Frame local:VisualNavigation.HavingAnimatedNavigation="True" ></Frame>
And in the pages, developer just needs to add two attached attributes about the type
of animation.
<Page x:Class="ICP.NavigationFramework.Demo.Page2"
local:VisualNavigation.PageEntrance="FlyingEnter"
local:VisualNavigation.PageExit="FlyingExit"
>
In the above code, the PageEntrance animation has been set to FlyingEnter. It means
that when user wants to see the page, it flies to the screen. The framework contains
dozen of build in animations and the developer can easily assign them to the
pages, or add his own page animations.
WPF Version
The following class diagram represents the main classes of the framework in the WPF.

PageAnimationFramework
PageAnimationFramework is the main class of the framework. It has attached properties
that can be used to enable/disable the framework, and also specifying the type
of animations per page. Here is the list of its attached properties.
HavingAnimatedNavigation: This attached property used by the Frame and NavigationWindows to enable/ disable the framework. The framework can gain access to the NavigationService using this attached property. NavigationService encapsulates the ability to download
content within the context of a browser-style navigation [ MSDN]. Both the Frame
and NavigationWindow have instances of it.
It has methods like Navigate, GoForward and GoBack that can be used to do navigation.
Its Source property points to the current loaded page and it also has the Navigating
and Navigated events. The Navigating even occurs when a new navigation is requested.
Its handler is the place that the “Page Exit” animation code should be put. The
Navigated event occurs when the content that is being navigated to has been found,
and is available from the Content property, although it may not have completed loading [MSDN]. Its handler is the
place that the “Page Enter” animation code should be put.
The framework hooks its event handlers of the navigating and navigated events in
the onHavingAnimatedNavigationChanged method.
PageEntrance attached property: This attached property specifies the page entering animation.
PageExit attached property: This attached property specifies the page exit animation.
Here is the code of the class:
public class PageAnimationFramework : DependencyObject
{
#region Private members
private static Uri uri;
private static bool animated = false;
private static NavigationService navigation;
private static ResourceDictionary pageNavigationDictionary;
#endregion
#region Frame & Navigation Events
private static void onHavingAnimatedNavigationChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
bool newValue = (bool)e.NewValue;
if (pageNavigationDictionary == null)
{
Assembly assembly = Assembly.GetExecutingAssembly();
Stream resourceStream = assembly.GetManifestResourceStream("ICP.NavigationFramework.PageNavigationDictionary.xaml");
pageNavigationDictionary = (ResourceDictionary)XamlReader.Load(resourceStream);
Application.Current.Resources.MergedDictionaries.Add(pageNavigationDictionary);
}
if (newValue)
{
if (obj is Frame)
{
Frame frame = obj as Frame;
frame.Navigating += new NavigatingCancelEventHandler(frame_Navigating);
frame.Navigated += new NavigatedEventHandler(frame_Navigated);
navigation = frame.NavigationService;
}
else if (obj is NavigationWindow)
{
NavigationWindow navigationWindow = obj as NavigationWindow;
navigationWindow.Navigated += new NavigatedEventHandler(navigationWindow_Navigated);
navigationWindow.Navigating += new NavigatingCancelEventHandler(navigationWindow_Navigating);
navigation = navigationWindow.NavigationService;
}
}
else
{
if (obj is Frame)
{
Frame frame = obj as Frame;
frame.Navigating -= new NavigatingCancelEventHandler(frame_Navigating);
frame.Navigated -= new NavigatedEventHandler(frame_Navigated);
navigation = frame.NavigationService;
}
else if (obj is NavigationWindow)
{
NavigationWindow navigationWindow = obj as NavigationWindow;
navigationWindow.Navigated -= new NavigatedEventHandler(navigationWindow_Navigated);
navigationWindow.Navigating -= new NavigatingCancelEventHandler(navigationWindow_Navigating);
navigation = navigationWindow.NavigationService;
}
}
}
private static void navigationWindow_Navigating(object sender, NavigatingCancelEventArgs e)
{
NavigationWindow navigationWindow = sender as NavigationWindow;
Page page = navigationWindow.Content as Page;
Context.CurrentContext.FrameSize = new Rect(0, 0, navigationWindow.ActualWidth, navigationWindow.ActualHeight);
Context.CurrentContext.CurrentPage = page;
showExitAnimation(page, e);
}
private static void showExitAnimation(Page page, NavigatingCancelEventArgs e)
{
if (page != null && !animated)
{
string PageExit = PageAnimationFramework.GetPageExit(page);
if (PageExit.Length > 0)
{
animated = true;
PageAnimation p = ((PageAnimation)Application.Current.FindResource(PageExit)).Copy();
page.RenderTransform = p.MainTransform as Transform;
p.MainStoryboard.Completed += new EventHandler(animation_Completed);
p.MainStoryboard.RepeatBehavior = new RepeatBehavior(1);
p.MainStoryboard.Begin(p);
uri = e.Uri;
e.Cancel = true;
}
}
else
{
animated = false;
}
}
private static void navigationWindow_Navigated(object sender, NavigationEventArgs e)
{
NavigationWindow navigationWindow = sender as NavigationWindow;
Page page = navigationWindow.Content as Page;
Context.CurrentContext.FrameSize = new Rect(0, 0, navigationWindow.ActualWidth, navigationWindow.ActualHeight);
Context.CurrentContext.CurrentPage = page;
showEnterAnimation(page, e);
}
private static void showEnterAnimation(Page page, NavigationEventArgs e)
{
if (page != null)
{
// means the main window does not laoded
if (Application.Current.MainWindow.ActualWidth == 0)
{
return;
}
string pageEntranceAnimation = PageAnimationFramework.GetPageEntrance(page);
if (pageEntranceAnimation.Length > 0)
{
PageAnimation p = ((PageAnimation)Application.Current.FindResource(pageEntranceAnimation)).Copy();
page.RenderTransform = p.MainTransform as Transform;
p.MainStoryboard.RepeatBehavior = new RepeatBehavior(1);
p.MainStoryboard.Begin(p);
}
}
}
private static void frame_Navigated(object sender, NavigationEventArgs e)
{
Frame frame = sender as Frame;
Page page = frame.Content as Page;
Context.CurrentContext.FrameSize = new Rect(0, 0, frame.ActualWidth, frame.ActualHeight);
Context.CurrentContext.CurrentPage = page;
showEnterAnimation(page, e);
}
private static void animation_Completed(object sender, EventArgs e)
{
navigation.Navigate(uri);
}
private static void frame_Navigating(object sender, NavigatingCancelEventArgs e)
{
Frame frame = sender as Frame;
Page page = frame.Content as Page;
Context.CurrentContext.FrameSize = new Rect(0, 0, frame.ActualWidth, frame.ActualHeight);
Context.CurrentContext.CurrentPage = page;
showExitAnimation(page, e);
}
private static void onPageExit(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
}
private static void onPageEntranceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
}
#endregion
#region Attached Properties
public static string GetPageEntrance(DependencyObject obj)
{
return (string)obj.GetValue(PageEntranceProperty);
}
public static void SetPageEntrance(DependencyObject obj, string value)
{
obj.SetValue(PageEntranceProperty, value);
}
// Using a DependencyProperty as the backing store for PageEntrance. This enables
animation, styling, binding, etc...
public static readonly DependencyProperty PageEntranceProperty =
DependencyProperty.RegisterAttached("PageEntrance", typeof(string), typeof(PageAnimationFramework), new UIPropertyMetadata("", onPageEntranceChanged));
public static string GetPageExit(DependencyObject obj)
{
return (string)obj.GetValue(PageExitProperty);
}
public static void SetPageExit(DependencyObject obj, string value)
{
obj.SetValue(PageExitProperty, value);
}
// Using a DependencyProperty as the backing store for PageExit. This enables animation,
styling, binding, etc...
public static readonly DependencyProperty PageExitProperty =
DependencyProperty.RegisterAttached("PageExit", typeof(string), typeof(PageAnimationFramework), new UIPropertyMetadata("", onPageExit));
public static string GetName(DependencyObject obj)
{
return (string)obj.GetValue(NameProperty);
}
public static void SetName(DependencyObject obj, string value)
{
obj.SetValue(NameProperty, value);
}
// Using a DependencyProperty as the backing store for Name. This enables animation,
styling, binding, etc...
public static readonly DependencyProperty NameProperty =
DependencyProperty.RegisterAttached("Name", typeof(string), typeof(PageAnimationFramework), new UIPropertyMetadata(""));
public static bool GetHavingAnimatedNavigation(DependencyObject obj)
{
return (bool)obj.GetValue(HavingAnimatedNavigationProperty);
}
public static void SetHavingAnimatedNavigation(DependencyObject obj, bool value)
{
obj.SetValue(HavingAnimatedNavigationProperty, value);
}
public static readonly DependencyProperty HavingAnimatedNavigationProperty =
DependencyProperty.RegisterAttached("HavingAnimatedNavigation", typeof(bool), typeof(PageAnimationFramework), new UIPropertyMetadata(false, onHavingAnimatedNavigationChanged));
#endregion
}
The following flowchart illustrates the main process when a page has been loaded.

The following flowchart illustrates the main process when a page has been unloaded

PageAnimation
This class is used to define the custom animations. It has two main properties: MainStoryboard
and MainTransform. The MainStoryBoard contains custom animations that target
the transformations of the MainTransform. The MainTransform is a transformation
that is used to change the appearance of the Page. In the Navigating and Navigated
events, the MainTransform of the loaded PageAnimation has been assigned to the
Page and then the Storyborad of the PageAnimation has been started. The main
question here is: “Why we need such class? We can easily define Storyboards and
transformations in the ResourceDictionaries.” The philosophy of existence the
class relies on the accessing of the animation targets. Animations inside a Stoaryboard
have to specify the target of animation using an attached property of the Storyboard
called TargetName. Unfortunately, one cannot set the target of animation directly
using the Target property. A Storyboard can be used to animate dependency properties of animatable classes (for
more information about what makes a class animatable, see the Animation Overview). However, because storyboarding is a framework-level feature, the object must belong
to the NameScope of a FrameworkElement or a FrameworkContentElement.
Like it or not, a Storyboard should be inside the NameScope of its target. A NameScope
is a class that implements the INameScope interface. WPF uses the NameScopes to enforce the uniqueness of names. All of the
controls inside a NameScope must have unique names. The following classes in
the WPF have implemented the INameScope: Window, Page, UserControl, Style, Template, NameScope and ResourceDictionary. Although
ResourceDictionary implements the INameScope, but it does not allow controls
to have x:Name attribute. NameScope is a non UI implementation of the INameScope.
According to the above notations, the Storyboards cannot be located in the ResourceDictionaries
due to the problem of NameScopes. I struggled with the problem a couple of hours
and finally find two solutions.
1- Do not define the Transformation and Storyboards in the XAML files and instead create
them from the scratch in the code of navigated and navigating events.
2- Define a class that implements the INameScope that has two main properties: A StoryBoard and a Transformation. Each instance of
the class represents a custom page navigation animation. Since the class implements
the INameScope, so there is no problem in assigning the TargetName properties.
(This class is PageAnimation).
Among the above solution, number 2 is clearly superior. Its main advantage is the
fact that it doesn’t hardcode the animations which is very useful for the ones
that want to add their own custom animations. Here is the code of PageAnimation.
public class PageAnimation : FrameworkElement, INameScope
{
#region NameScope
private NameScope nameScope = new NameScope();
public object FindName(string name)
{
return nameScope.FindName(name);
}
public void RegisterName(string name, object scopedElement)
{
nameScope.RegisterName(name, scopedElement);
}
public void UnregisterName(string name)
{
nameScope.UnregisterName(name);
}
#endregion
#region Properties
public Storyboard MainStoryboard
{
get
{
return mainStrotyborard;
}
set
{
mainStrotyborard = value;
}
}
private Storyboard mainStrotyborard;
public Transform MainTransform
{
get
{
return mainTransform;
}
set
{
mainTransform = value;
string name = PageAnimationFramework.GetName(mainTransform);
if (this.FindName(name) == null && name.Length > 0)
{
this.RegisterName(name, mainTransform);
}
if (mainTransform is TransformGroup)
{
TransformGroup transformGroup = mainTransform as TransformGroup;
foreach (Transform transform in transformGroup.Children)
{
name = PageAnimationFramework.GetName(transform);
if (this.FindName(name) == null && name.Length > 0)
{
this.RegisterName(name, transform);
}
}
}
}
}
private Transform mainTransform;
#endregion
#region Methods
public PageAnimation Copy()
{
PageAnimation value = (PageAnimation)this.MemberwiseClone();
value.MainStoryboard = this.MainStoryboard.Clone();
return value;
}
#endregion
}
Here is a custom implementation of the PageAnimation in a ResourceDictionary.
<local:PageAnimation x:Key="SkewEnter">
<local:PageAnimation.MainStoryboard>
<Storyboard x:Name="storyBoard">
<DoubleAnimation Duration="0:0:1" From="90" To="0" Storyboard.TargetName="skew" Storyboard.TargetProperty="AngleX"></DoubleAnimation>
</Storyboard>
</local:PageAnimation.MainStoryboard>
<local:PageAnimation.MainTransform>
<TransformGroup>
<TransformGroup.Children>
<SkewTransform local:PageAnimationFramework.Name="skew"
CenterX="{Binding Source={x:Static local:Context.CurrentContext}, Path=FrameSize.Width, Mode=TwoWay}"
CenterY="{Binding Source={x:Static local:Context.CurrentContext}, Converter={StaticResource divideConverter}, ConverterParameter=2, Path=FrameSize.Height, Mode=TwoWay}"
></SkewTransform>
</TransformGroup.Children>
</TransformGroup>
</local:PageAnimation.MainTransform>
</local:PageAnimation>
Context Class
Using the PageAnimation class, one can define the Storyboards and Transformations
in a systematic way. But the story is not over. An animation inside a Stoaryboard
or a Transformation definitely needs to have access the Page and binds its values
to the Page value. Basically they need the boundaries of the page. The Context
class is used to hold the current page temporary. Here is its code.
public class Context : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void notifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
#endregion
public Rect FrameSize
{
get
{
return frameSize;
}
set
{
frameSize = value;
notifyPropertyChanged("FrameSize");
}
}
private static Rect frameSize;
public Page CurrentPage
{
get
{
return currentPage;
}
set
{
currentPage = value;
notifyPropertyChanged("CurrentPage");
}
}
private static Page currentPage;
public static Context CurrentContext = new Context();
}
The static instance of the class insures that the PageAnimation can access it. Here
is an example of how the PageAnimations bind their values to the Context class.
<SkewTransform local:PageAnimationFramework.Name="skew"
CenterX="{Binding Source={x:Static local:Context.CurrentContext}, Path=FrameSize.Width, Mode=TwoWay}"
CenterY="{Binding Source={x:Static local:Context.CurrentContext}, Converter={StaticResource divideConverter}, ConverterParameter=2, Path=FrameSize.Height, Mode=TwoWay}"
></SkewTransform>
Using the Code
As I mentioned in the Introduction section, using the framework is simple and just
needs two lines of code. In the Navigation Frame, the Framework should be enabled
using one line of code.
<Frame local:VisualNavigation.HavingAnimatedNavigation="True" ></Frame>
And in the Pages, the PageEntance and PageExit animation should be specified.
<Page x:Class="ICP.NavigationFramework.Demo.Page2"
local:VisualNavigation.PageEntrance="FlyingEnter"
local:VisualNavigation.PageExit="FlyingExit"
>
The attached code contains two demo projects: One is a standalone navigation application
and one is a Navigation project hosted in the IE. The code of WPF version can
be downloaded here.
Silverlight Version
Unfortunately, Silverlight applications cannot access NameScopes and on the other
hand, Storyboards and Transformations on the silverlight do not support binding.
It means that Storyboards and Transformation should be static and being on the
same NameScope of their targets. But fortunately, Storyboards in the Silverlight
can use the SetTarget property which is a great achievement towards WPF. Using
the SetTarget property, we could easily create Storyboards in the code behind.
The Silverlight version of the framework is simpler than its WPF version. There
are no PageAnimation classes and the Storyboards & Transformation have been
created in the code behind. Here are the Navigating and Navigated event handlers
of the PageAnimationFramework class.
private static void showExitAnimation(Page page, NavigatingCancelEventArgs e)
{
if (page != null && !animated)
{
string PageExit = PageAnimationFramework.GetPageExit(page);
if (PageExit.Length > 0)
{
animated = true;
Storyboard storyBoard = null;
Transform transform = null;
if (PageExit == "FlyingExit")
{
storyBoard = PageAnimations.FlyingExit(currentFrame, out transform);
}
else if (PageExit == "SkewExit")
{
storyBoard = PageAnimations.SkewExit(currentFrame, out transform);
}
else
{
storyBoard = PageAnimations.FlyingExit(currentFrame, out transform);
}
page.RenderTransform = transform;
storyBoard.Completed += new EventHandler(animation_Completed);
storyBoard.Begin();
uri = e.Uri;
e.Cancel = true;
}
}
else
{
animated = false;
}
}
private static void showEnterAnimation(Page page, NavigationEventArgs e)
{
if (page != null)
{
// means the main window does not laoded
if (firstTime)
{
firstTime = false;
return;
}
string pageEntranceAnimation = PageAnimationFramework.GetPageEntrance(page);
if (pageEntranceAnimation.Length > 0)
{
Storyboard storyBoard = null;
Transform transform = null;
if (pageEntranceAnimation == "FlyingEnter")
{
storyBoard = PageAnimations.FlyEnter(currentFrame, out transform);
}
else if (pageEntranceAnimation == "SkewEnter")
{
storyBoard = PageAnimations.SkewEnter(currentFrame, out transform);
}
else
{
storyBoard = PageAnimations.FlyEnter(currentFrame, out transform);
}
page.RenderTransform = transform;
storyBoard.Begin();
}
}
}
And here is an instance of how creating Storyboards in code behind.
public static Storyboard SkewExit(Frame currentFrame, out Transform transform)
{
Storyboard storyBoard = new Storyboard();
TransformGroup mainTransform = new TransformGroup();
SkewTransform skewTransform = new SkewTransform();
skewTransform.CenterX = currentFrame.ActualWidth / 2;
skewTransform.CenterY = currentFrame.ActualHeight / 2;
mainTransform.Children.Add(skewTransform);
DoubleAnimation skewTransformAnimation = new DoubleAnimation();
skewTransformAnimation.Duration = new Duration(new TimeSpan(0, 0, 1));
skewTransformAnimation.From = 0;
skewTransformAnimation.To = 90;
Storyboard.SetTarget(skewTransformAnimation, skewTransform);
Storyboard.SetTargetProperty(skewTransformAnimation, new PropertyPath("SkewTransform.AngleX"));
storyBoard.Children.Add(skewTransformAnimation);
transform = mainTransform;
return storyBoard;
}
The code of Silverlight version can be downloaded from
here.