A Better Multi-Page Solution

Switch I’ve posted on multi-page Silverlight applications, and in fact have two videos (here and here) that show an approach that works quite well and that I was happy to steal at the time.

I recently received email from Lucas Stark (Senior Web Developer at Delta College) who suggested (quite correctly) that the pages should not have to find the PageSwitcher each time they want to navigate to another page, but rather they ought to be able to call a static method. 

I’ve tinkered with the code he provided, factoring out a few things and simplifying, and I now have a version that works as follows:

  • Any page wishing to participate in the switching may do so
  • If the page wishes to send data to the page it is calling, it must implement ISwitchable (defined below)

To make this work, I added ISwitchable.cs that defines the interface:

public interface ISwitchable
{
   void UtilizeState( object state );
}

I also added a new class, Switcher, in the file Switcher.cs

using System;
using System.Windows.Controls;

public static class Switcher
{
  public static PageSwitcher pageSwitcher;

  public static void Switch( UserControl newPage )
  {
    pageSwitcher.Navigate( newPage );
  }

  public static void Switch( UserControl newPage, 
                               object state )
  {
    pageSwitcher.Navigate( newPage, state );
  }

}

This static class' PageSwitcher instance is set in the startup code in App.xaml.cs (where the root.visual is set as well)

 private void Application_Startup( object sender, StartupEventArgs e )
 {
    PageSwitcher pageSwitcher = new PageSwitcher();
    this.RootVisual = pageSwitcher;
    Switcher.pageSwitcher = pageSwitcher;
    Switcher.Switch( new Page() );
 }

Let's pause here and consider… when the application starts up, a new PageSwitcher (not seen yet) is created. This class, you may remember from previous coverage, is responsible for acting as the shell that holds the current page.  This instance of PageSwitcher is assigned

We'll look at it in just a moment. It is then assigned as the RootVisual (this value can only be set here and not changed while the program is running).  In addition, that same value is assigned to the static property pageSwitcher in the Switcher class.

This happens before anything else. Thus, upon start up that static class has a PageSwitcher instance available to it.

Finally, in the startup code, the static method Switch is called passing in a new instance of the Page user control.   We see above, that Switch is overloaded. The overload that takes a single argument takes a user control and calls Navigate on the newly instantiated PageSwitcher, passing along the user control. (There is a test to make sure that the PageSwitcher isn't null, but it really can't be, and so I've left that out here).

Let's look at PageSwitcher. The xaml has just an empty user control (as in previous versions), here is the .cs file:

public partial class PageSwitcher : UserControl
{
  public PageSwitcher()
  {
    InitializeComponent();
  }

  public void Navigate( UserControl nextPage )
  {
    this.Content = nextPage;
  }

  public void Navigate( UserControl nextPage, object state )
  {
    this.Content = nextPage;
    ISwitchable s = nextPage as ISwitchable;

     // test that s is not null or throw exception
     s.UtilizeState( state );
  }
}

 

When we call Navigate we pass in a new instance of Page so the first overload is called, and the content of the PageSwitcher class is filled with that user control, and the user sees the page as intended:

Page1
[this image has been cropped to save room]

When you click on switch, the event handler for that button is called and this is where the "great improvement" comes. In the previous version, each page had to look for the PageSwitcher (which was its parent), cast that value and then directly call the navigation. Now, all the page has to do is call a static method on the Switcher, passing in either a new page, or a new page and a value for the new page.

void SwitchToPage2_Click( object sender, RoutedEventArgs e )
{
  Switcher.Switch( new Page2(), YourName.Text );
}

As you can see, in this case, we call Page2 and pass in the text taken from the textBox.  Let's look at the code in Page2.xaml.cs,

using System.Windows;
using System.Windows.Controls;

namespace SimplePageSwitcherForBlog
{
  public partial class Page2 : UserControl, ISwitchable
  {
    public Page2()
    {
      InitializeComponent();
      Loaded += new RoutedEventHandler( Page2_Loaded );
    }

    void Page2_Loaded( object sender, RoutedEventArgs e )
    {
      SwitchToPage2.Click += new RoutedEventHandler( SwitchToPage2_Click );
    }

    public void UtilizeState( object state )
    {
      Message.Text = state as string;
    }

    void SwitchToPage2_Click( object sender, RoutedEventArgs e )
    {
      Switcher.Switch( new Page(), Age.Text );
    }
  }
}

The first thing to notice is that Page2 implements ISwitchable. And sure enough, we see that it does, by implementing the method UtilizeState. The implementation of that is to take the value we passed in, cast it to the string that it is, and assign that string to the Text property of the TextBlock named message in the first row (in red).

I've included the entire file because it is critical to note that this method is never called in this class, yet if you run the program you'll find that in fact the value is displayed! 

This "magic" is accomplished by delegating the responsibility to calling UtilizeState to the second overload of Navigate in PageSwitcher,

 public void Navigate( UserControl nextPage, object state )
 {
   this.Content = nextPage;
   ISwitchable s = nextPage as ISwitchable;
   if ( s != null )
   {
     s.UtilizeState( state );
   }
   else
   {
     throw new ArgumentException( "nextPage is not ISwitchable! "
       + nextPage.Name.ToString() );
   }
 }

When this method is called with two arguments, we know that the second argument is an object to be used in UtilizeState, so we call UtilizeState. To be careful, however, we first make sure that the userControl we've been given does in fact implement ISwitchable (as is required if you're going to use this method); if not we throw an exception.

This is very clean and works extremely well. The UserControl (page) only needs to know about calling the static Switch methods on a static class, the internals are entirely encapsulated and hidden from the consuming user controls, and on the flip side, the internal mechanism (the SearchPage and the Switcher, never need to know any of the semantics of any of the pages.

All of this is covered in detail, and placed in the context of a much more realistic scenario in my forthcoming tutorial (should be published in less than 2 weeks in both C# and VB). For now, the code used in this blog entry is available here.

Share

About Jesse Liberty

Jesse Liberty is an independent consultant and programmer with three decades of experience writing and delivering software projects. He is the author of 2 dozen books and multiple Pluralsight courses, and has been a Senior Technical Evangelist for Microsoft, a Distinguished Software Engineer for AT&T, a VP for Information Services for Citibank and a Software Architect for PBS. He is a Xamarin Certified Mobile Developer and a Xamarin MVP, Microsoft MVP and Telerik MVP.
This entry was posted in z Silverlight Archives. Bookmark the permalink.

5 Responses to A Better Multi-Page Solution

  1. Okay I’m convinced. Let’s put it to action.

  2. Thanks for spending time on the computer (writing) so others don’t have to.

  3. Keischa says:

    Thanky Thanky for all this good inrafmotion!

  4. Jesse, this is a useful article.

    Do you have any thoughts on passing user state when using the Navigation Framework (SL3/SL4) ?

    Bernard

Leave a Reply

Your email address will not be published.