Custom Journal in WPF

On Web  when you go to Page1 to Page2, you go back to Page1. This has become now universally accepted behavior. of course, it means, you are actually doing a server side trip to get a new page from the server.  With new AJAX based applications though since client side code now modifies the DOM on the fly, this helps with server side round trip part because actual communication with server now can happen asynchronously. But this means some times even though user thinks that they have gone from one state to another, there is no real Navigation. Since there is no real navigation happening, browser back/forward functionally seems broken. Windows Presentation Foundation has a concept of navigation which is pretty close to what you see on web. E.g. you can connect two pages using hyperlink and when in a host that support navigation, you click on a link you go from one page to another. This also gives you html+browser like journal where you can navigate back/forward using browser like buttons. When you run a Xbap (Xaml Browser Application) in Internet Explorer 7, you also get integration with browser back/forward navigation. With WPF also there are scenarios where user does not actually want to make real navigation but they want to change the state of the page but still have Back/forward paradigm work.

Let’s take a simple scenario where application is trying to show how a car would like in Red/Green/Blue color. When user changes the color, they can either hit back to go back to original color.

This is where concept of custom journal helps. Essentially, developer can not only make changes to rendered UI on the fly like all other platform, developer also has ability to store/replay the custom state in the journal so that it seems like navigation and works well with browser back/forward functionality.

There are three concepts that you need to understand here…

  1. NavigationService.AddBackEntry is the method that allows you to add a journal entry.
  2. CustomContentState: This is the base class that you will use to store the custom state information. It has a replay method when WPF calls when to restore the journal entry. You will override this method to restore the state.
  3. IProvideCustomContentState: This is the interface that you will need to implement on the page that is trying to store the custom state. This interface gets called using GetContentState when navigation using journal happens (using back/forward button).

So here is an simple example  that we talked about…

Scene1.xaml

I have a scene1.xaml that is the page which shows the car to user. Due to my great skills in drawing objects on the screen, car is represented here by a dockpanel that I will set color on. This page is set is that startup page for my application.

<Page
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:mc=”http://schemas.openxmlformats.org/markup-compatibility/2006″
xmlns:d=”http://schemas.microsoft.com/expression/interactivedesigner/2006″
Background=”#FFFFFFFF”
x:Name=”DocumentRoot”
x:Class=”ColorMyCar.Scene1″
Width=”590″ Height=”225″>
      <StackPanel>
          <DockPanel Height=”150″ Width=”550″ Background=”Red” Name=”myCar”/>
          <StackPanel Orientation=”Horizontal” HorizontalAlignment=”Center”>
                     <Button Height=”50″ Width=”100″ Content=”Red” Name=”btnRed” Click=”OnRedClick”/>
                     <Button Height=”50″ Width=”100″ Content=”Blue” Name=”btnBlue” Click=”OnBlueClick”/>
                     <Button Height=”50″ Width=”100″ Content=”Green” Name=”btnGreen” Click=”OnGreenClick”/>
          </StackPanel>
     </StackPanel>
</Page>

Scene1.xaml.cs

This has all the event handlers for each of the buttons that basically can change the background color for the dockpanel. It also implements the IProvideCustomContentState interface where you can return another journal entry with the current state of the page so that appropriate forward/backward journal entries can be created.

namespace ColorMyCar
{
          public partial class Scene1 : IProvideCustomContentState
         {
                  public Scene1()
                  {
                         this.InitializeComponent();
                    }

                  private void AddBackEntry(Brush oldbrush)
                 {
                         this.NavigationService.AddBackEntry(new CarColorJournalEntry(myCar.Background));
                   }

                public void OnRedClick(object sender, RoutedEventArgs e)
                 {
                        if (myCar.Background != Brushes.Red)
                        {
                                AddBackEntry(myCar.Background);
                                myCar.Background = Brushes.Red;
                        }
                 }

              public void OnBlueClick(object sender, RoutedEventArgs e)
              {
                       if (myCar.Background != Brushes.Blue)
                      {
                               AddBackEntry(myCar.Background);
                               myCar.Background = Brushes.Blue;
                       }
           }

           public void OnGreenClick(object sender, RoutedEventArgs e)
           {
                      if (myCar.Background != Brushes.Green)
                      {
                               AddBackEntry(myCar.Background);
                               myCar.Background = Brushes.Green;
                       }
           }

           CustomContentState IProvideCustomContentState.GetContentState()
           {
                      return new CarColorJournalEntry(myCar.Background);
           }
     }
}

First part of code that is marked as Red actually creates a custom journal entry. So everytime user clicks a button, code creates a CustomContentStateobject that can stores the current color of the car before it changes the color.

Second part of the that is marked gets called when users does journal navigation using back/forward button. It also returns CustomContentState object that stores the current color of the car before the other journal entry is restored.

CarColorJournalEntry

This is the code that actually implements a class that inherits from CustomContentState object and stores the color change data.This object needs to be serializable and hence the attribute Serializable. That is also the reason why actual color state is stored as string instead of brush that we get from the page. This string value of brush is restored using brushconverter class.

namespace ColorMyCar
{
        //A class that derives from CustomContentState must be serializable, 
       //which means it must either be augmented with SerializableAttribute 
       //or implement ISerializable
        [Serializable]
       class CarColorJournalEntry: CustomContentState
        {
                    private string _myCarColor;

                    public CarColorJournalEntry(Brush MyCar)
                    {
                            _myCarColor = MyCar.ToString();
                     }

         //The name that appears in the back and forward drop down navigation buttons displayed from NavigationWindow, Frame, or Microsoft Internet Explorer 7 Beta 3 navigation chrome is automatically generated, unless you return your own by overriding JournalEntryName property.
                  public override string JournalEntryName
                {
                         get {return “Journal: ” + _myCarColor.ToString();}
                 }

           //When a user navigates back through navigation history to a piece of content that has an associated CustomContentState object, Replay is called; you override Replay to provide an implementation that reapplies state to the current content.
                public override void Replay(NavigationService navigationService, NavigationMode mode)
              {
                        BrushConverter convertobrush = new BrushConverter();
                       (navigationService.Content as Scene1).myCar.Background = convertobrush.ConvertFromString(_myCarColor) as Brush;
                }

         }
}

In the overridden replay method, we use the brushconverterr class to convert the string back into  brush and then apply that brush to the dockpanel. To get to dockpanel, Content property on NavigationService  can be used.

When you compile the application you can get the journal behavior described in the above scenario.

Follow

Get every new post delivered to your Inbox.