Animated Navigation Page In WPF Application

Introduction

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

Requirements

Many applications are made up of several screens. Here when I used 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 Implementation

By looking at the previous diagram, we can imagine the 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 will use a grid with a single cell to overlay the views. Please also note that in this 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…)

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

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 used a ListBox to display available views through a binding to XML data (in order to improve flexibility).

The XML data:

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

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

The ListBox:

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

Here, ListBox is used to display available views in the application. IItemsSource property is used above in order to bind it to the XML data. I also specified an ItemsPanel in order to change the default vertical StackPanel to horizontal one.

The ItemsControl:

  1. <ItemsControl x:Name="viewer" DataContext="{Binding Path=SelectedItem, ElementName=viewList}" ItemsSource="{Binding XPath=Page}">  
  2.     <ItemsControl.ItemTemplate>  
  3.         <DataTemplate>  
  4.             <Frame x:Name="frame" Source="{Binding XPath=@Source}"/>  
  5.         </DataTemplate>  
  6.     </ItemsControl.ItemTemplate>  
  7.     <ItemsControl.RenderTransform>  
  8.         <TranslateTransform/>  
  9.     </ItemsControl.RenderTransform>  
  10. </ItemsControl>  
In order to create this ImageBrush, I will use a function that converts any FrameworkElement into a bitmap:
  1. public RenderTargetBitmap RenderBitmap(FrameworkElement element)  
  2. {  
  3.     double topLeft = 0;  
  4.     double topRight = 0;  
  5.     int width = (int)element.ActualWidth;  
  6.     int height = (int)element.ActualHeight;  
  7.     double dpiX = 96// this is the magic number  
  8.     double dpiY = 96// this is the magic number  
  9.    
  10.     PixelFormat pixelFormat = PixelFormats.Default;  
  11.     VisualBrush elementBrush = new VisualBrush(element);  
  12.     DrawingVisual visual = new DrawingVisual();  
  13.     DrawingContext dc = visual.RenderOpen();  
  14.    
  15.     dc.DrawRectangle(elementBrush, nullnew Rect(topLeft, topRight, width, height));  
  16.     dc.Close();  
  17.    
  18.     RenderTargetBitmap bitmap = new RenderTargetBitmap(width, height, dpiX, dpiY, pixelFormat);  
  19.    
  20.     bitmap.Render(visual);  
  21.     return bitmap;  
  22. }