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.