Using animated navigation pages in a WPF application

Article updated ! You can now download the example as a Visual Studio 2008 solution.
Article updated ! For a more complete solution, please take a look at this post

Introduction

Before going into fun topics, let me give some information about myself. I started to play with WPF about 1 year ago. Since the beginning, I fell in love with this new technology and the more I work with it, the more I like it. Today, I consider I’m still a newbie in the WPF world and this is my first article, so please forgive me if I do some mistakes. Please also forgive my English as this is not my natural language (I’m French).

In this article, I describe a technique that I’ve been using in order to use navigation pages in a WPF application. In the next section, I’m going to give more details about what I mean by “navigation pages”.

Requirements

Many applications are made up several screens. Here when I use the word “screen” I mean a view in the application. For example, you might have a view as a welcome screen and several other views.

In this case, you would like the current screen to display the current view, but you also need a technique to switch from one view to another. If you also want to improve the user experience, you might want to have some kind of animation when the user switches from one view to another.

A possible animation is to use a “slide effect”:

Using animation when changing current views

I’m going to describe 2 techniques that could be used to achieve this behaviour. I will also show you why the last one is better than the first one.

A possible implementation

By looking at the previous diagram, we can imagine a first implementation. Why not playing with the Margin property to slide out the view that we want to hide? Let’s try to implement this first solution…

I use a grid with a single cell to overlay the views. Please also note that in this very simple example, I used a TextBlock inside each view. We can of course imagine that we would put a custom UserControl containing the real view (for example the first one with a welcome image, the next one a basic form…).

Here is the main window:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<Window x:Class="FirstTry.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" SizeToContent="WidthAndHeight" ResizeMode="NoResize">
 
    <Grid>
        <StackPanel>
            <StackPanel Orientation="Horizontal">
                <Button x:Name="previousButton" Click="previousButton_Click">Previous</Button>
                <Button x:Name="nextButton" Click="nextButton_Click">Next</Button>
            </StackPanel>
            <Grid Width="300" Height="300">
                <Border x:Name="view3" Width="300" Background="LightBlue">
                    <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">
                        View 3
                    </TextBlock>
                </Border>
                <Border x:Name="view2" Width="300" Background="AntiqueWhite">
                    <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">
                        View 2
                    </TextBlock>
                </Border>
                <Border x:Name="view1" Width="300" Background="AliceBlue">
                    <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">
                        View 1
                    </TextBlock>
                </Border>
            </Grid>
        </StackPanel>       
    </Grid>
</Window>

In the code behind, I update the Margin property of the Border elements regarding the one I’ve to display in the UI.

Code behind:

?View Code CSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
using System.Windows;
using System.Windows.Controls;
 
namespace FirstTry
{
    /// 
    /// Interaction logic for Window1.xaml
    /// 
    public partial class Window1 : Window
    {
        private int currentView = 1;
 
        public Window1()
        {
            InitializeComponent();
        }
 
        private void slide(Border oldVisual, Border newVisual)
        {
            oldVisual.Margin = new Thickness(300, 0, 0, 0);
            newVisual.Margin = new Thickness(0);
        }
 
        private void previousButton_Click(object sender, RoutedEventArgs e)
        {
            switch (currentView)
            {
                case 2:
                    slide(view2, view1);
                    currentView--;
                    break;
                case 3:
                    slide(view3, view2);
                    currentView--;
                    break;
            }
        }
 
        private void nextButton_Click(object sender, RoutedEventArgs e)
        {
            switch (currentView)
            {
                case 1:
                    slide(view1, view2);
                    currentView++;
                    break;
                case 2:
                    slide(view2, view3);
                    currentView++;
                    break;
            }
        }
    }
}

In my opinion, the problems with this first implementation are the following:

  • no flexibility at all
  • memory consumption : all pages are loaded into memory even those which are not visible

I didn’t add animation to this first example but I let you imagine that we could achieve the “slide effect” by animating the margin property (or using a TranslateTransform).

Using an ItemsControl and an ImageBrush

A problem with the previous implementation is that all views are loaded into memory, even those that never got displayed.

How could we improve that? The solution I present here uses an ItemsControl as view’s host control. I also use a ListBox to display available views through a binding to XML data (in order to improve flexibility).

The XML data:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<XmlDataProvider x:Key="views">
    <x:XData>
        <Views xmlns="">
            <View Title="View1">
                <Page Source="view1.xaml"/> 
            </View>
            <View Title="View2">
                <Page Source="view2.xaml"/>
            </View>
            <View Title="View3">
                <Page Source="view3.xaml"/>
            </View>
        </Views>
    </x:XData>
</XmlDataProvider>

Here, I basically describe the available views in my application. Using this XML formatting, it becomes very straightforward to add or remove a view. I’m going to use this XMLDataProvider as a source data in order to bind my other controls to it.

The ListBox:

1
2
3
4
5
6
7
8
9
10
<ListBox x:Name="viewList" Height="20" Width="300" SelectedIndex="0"
    ItemsSource="{Binding Source={StaticResource views}, XPath=Views/View}"
    DisplayMemberPath="@Title"                    
    SelectionChanged="viewList_SelectionChanged">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal"/>
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
</ListBox>

Here the ListBox is used to display the available views of in the application. It just setup is ItemsSource property in order to bind it to the XML data. I also specify an ItemsPanel in order to change the default vertical StackPanel to a horizontal one.

The ItemsControl:

1
2
3
4
5
6
7
8
9
10
<ItemsControl x:Name="viewer" DataContext="{Binding Path=SelectedItem, ElementName=viewList}" ItemsSource="{Binding XPath=Page}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Frame x:Name="frame" Source="{Binding XPath=@Source}"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.RenderTransform>
        <TranslateTransform/>
    </ItemsControl.RenderTransform>
</ItemsControl>

The ItemsControl is used to contain the current view. I setup an ItemTemplate to specify that each item in the control should be rendered using a frame. This frame has its Source property bound to the XML source data. I also add a TranslateTransform in the RenderTransform collection in order to be able to animate this ItemsControl.

The problem with this news solution is that we have only one item (the ItemsControl), that seems to be hard to build a transition only with that… The solution I found uses an ImageBrush.

An ImageBrush allows painting a control with an image. By using an ImageBrush we can paint a Rectangle with the content of the current view. Then, when we change the current view, we can animate both Rectangle and the new current view:

Using animation when changing current views

In order to create this ImageBrush, I use a function that converts any FrameworkElement into a bitmap:

?View Code CSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public RenderTargetBitmap RenderBitmap(FrameworkElement element)
{
    double topLeft = 0;
    double topRight = 0;
    int width = (int)element.ActualWidth;
    int height = (int)element.ActualHeight;
    double dpiX = 96; // this is the magic number
    double dpiY = 96; // this is the magic number
 
    PixelFormat pixelFormat = PixelFormats.Default;
    VisualBrush elementBrush = new VisualBrush(element);
    DrawingVisual visual = new DrawingVisual();
    DrawingContext dc = visual.RenderOpen();
 
    dc.DrawRectangle(elementBrush, null, new Rect(topLeft, topRight, width, height));
    dc.Close();
 
    RenderTargetBitmap bitmap = new RenderTargetBitmap(width, height, dpiX, dpiY, pixelFormat);
 
    bitmap.Render(visual);
    return bitmap;
}

By using this procedure, it becomes possible to paint the content of a rectangle with a ‘snapshot’ of the current view (just before it changes). I do this in the SelectionChanged event handler:

?View Code CSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void viewList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    XmlElement root = (XmlElement)viewer.DataContext;
    XmlNodeList xnl = root.SelectNodes("Page");
 
    if (viewer.ActualHeight &gt; 0 &amp;&amp; viewer.ActualWidth &gt; 0)
    {
        RenderTargetBitmap rtb = RenderBitmap(viewer);
        rectanglevisual.Fill = new ImageBrush(BitmapFrame.Create(rtb));
    }
 
    viewer.ItemsSource = xnl;
 
    if (oldSelectedIndex &lt; viewList.SelectedIndex)
    {
   viewer.BeginStoryboard((Storyboard)this.Resources["slideLeftToRight"]);
    }
    else
    {
   viewer.BeginStoryboard((Storyboard)this.Resources["slideRightToLeft"]);
    }
 
    oldSelectedIndex = viewList.SelectedIndex;
}

I also start the animations in this EventHandler. One animation is responsible for sliding out the bordervisual and sliding in the new viewer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<Storyboard x:Key="slideLeftToRight"  
            TargetProperty="RenderTransform.(TranslateTransform.X)"
            AccelerationRatio=".4"
            DecelerationRatio=".4">
    <DoubleAnimation Storyboard.TargetName="viewer" Duration="0:0:0.6" From="300" To="0"/>
    <DoubleAnimation Storyboard.TargetName="bordervisual" Duration="0:0:0.6" From="0" To="-300"/>
</Storyboard>
 
<Storyboard x:Key="slideRightToLeft" 
            TargetProperty="RenderTransform.(TranslateTransform.X)"
            AccelerationRatio=".4"
            DecelerationRatio=".4">
    <DoubleAnimation Storyboard.TargetName="viewer" Duration="0:0:0.6" From="-300" To="0"/>
    <DoubleAnimation Storyboard.TargetName="bordervisual" Duration="0:0:0.6" From="0" To="300"/>
</Storyboard>

In order to make the animation works properly, I need to have both items (the current view and the snapshot of the previous view) in the same control, that’s why I’m using a Grid:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<Grid Width="300" Height="300">
        <Border x:Name="bordervisual" Width="300">
            <Rectangle x:Name="rectanglevisual"/>
            <Border.RenderTransform>
                <TranslateTransform/>
            </Border.RenderTransform>
        </Border>
 
        <ItemsControl x:Name="viewer" DataContext="{Binding Path=SelectedItem, ElementName=viewList}"
            ItemsSource="{Binding XPath=Page}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Frame x:Name="frame" Source="{Binding XPath=@Source}"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
            <ItemsControl.RenderTransform>
                <TranslateTransform/>
            </ItemsControl.RenderTransform>
        </ItemsControl>
</Grid>

Conclusion

In this article, I showed a technique that can be used to build navigation based application with transition effect. Of course there are a lot of things that could be improved, I didn’t want to write something complete but I wanted to demonstrate the general idea.

This is my very first article about WPF, so please don’t be too rude with me! I hope you like it. Please don’t hesitate to give your feedback to improve this article.

23 thoughts on “Using animated navigation pages in a WPF application

  1. Jeremy,
    This look great, but could you post the entire code as block or put a link on a downloadable file. As I am new to WPF I am not too sure yet where to paste all the pieces to make a finish compilable exe.
    Thanks

  2. Dan,

    I updated the article with a link to the Visual Studio 2008 solution. Sorry for the long delay… I’ll try to post before the end of the week another article on this “slide animation” in order to use it as a custom control. Stay tuned !

  3. Jeremy,

    You have started with a really important and significant topic in WPF. WPF does not have any MDI-like support, unlike Winforms. Managing “views” can become very challenging, especially if you have several “modeless pages” open within a parent window. Your solutions are very interesting, especially the second one as you point.

    On a different note, have you checked out the Dot Net Rocks video where Billy Hollis demonstrates an application he and others wrote in WPF? It talks about several “views” open at the same time, and, the video does show these views sliding in from the left or right, something you have discussed. If you are interested, watch the first 25 minutes of the video – http://www.dnrtv.com/default.aspx?showNum=115 [Billy Hollis on Getting Smart with WPF].

    Thanks for sharing your expertise with us! You are on my blog roll. Looking forward to some more goodies :)

    Cheers,
    indyfromoz.

  4. A great article, thanks for sharing your efforts. I’ve got the framework to handle browsing of an image library and these ideas have been great in pointing me in the right direction for the visual part. Thanks.

  5. Thank you Matt.

    I’m preparing a new article about using transitions using MVVM methodology. It should be ready soon, stay tuned :)

    In the meantime you can also have a look at my other article about transitions (Introducing ContentSlider control) that uses the concepts described here to build a transition control.

  6. Jeremy Hi,
    This is exactly what I was looking for, could you post the entire code as block or put a link on a downloadable file. Like Dan, I’m new to WPF I am not too sure yet where to paste all the pieces to make a finish compilable exe.
    Thanks

  7. Hi,
    Thanks for the article, I finally have time to check out WPF.
    A strange thing happens when you remove the background color property form the views.
    It is like the image being rendered for the transition is kind of re-scaled. put the BG color property back and all works good.

    Any explanation? or workaround? thanks

  8. Hi Jeremy,

    Thanks for the awesome article, this really helped me understand the basics of slide transitions. I am however very new to programming and WPF and would really appreciate some help. I’m looking to use the base of this article to slid user controls inside a wizard that I’m creating. In your code you bind a control list to the selecteditem of the list view. How would I go about in achieving the same result but with a “Next” and “Back” button instead?

    Sorry if this question seems silly, but as I’ve mentioned I’m fairly new to this and am a bit stumped.

  9. Francois,

    I think you could use the ICollectionView interface. You can get it using:

    var view = CollectionViewSource.GetDefaultView(yourCollection);

    Then you can use the MoveCurrentToNext/MoveCurrentToPrevious method to change the currently selected item.

    Does it makes sense ?

  10. Hi Jeremy,

    Your blog is impressive! I really like very much. But I have one question:
    I am trying to replace the View Title with an image icon, how should I it? Sorry i am new in WPF

    Thanks in advance!

  11. Jeffrey,

    Glad you like the blog :-)

    About your question, I think you should tweak the DataTemplate of the ItemsControl. In the example, it is composed of only a frame. You could try replace that with a Grid containing 2 rows: one for your image, one for the frame.

    Good luck with WPF :-)

  12. Hi Jeremy,
    Actually this is what i mean:
    I have 3 views:

    Then i was trying to put in the image:

    and I tried this too:

    I do this to replace the Title by using an image, but the image still failed to load, am I doing anything wrong?

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>