Silverlight 2 Navigating Between Xaml Pages

Update - 9/6/2008 : Updated for Silverlight 2 Beta 2!
 

One of the problems I've run across while playing around with the Beta is the lack of support for any easy way to navigate around you application by moving between Xaml pages. The problem really stems from this little piece of generated code in your App.xaml.cs file.

private void Application_Startup(object sender, StartupEventArgs e)

{

    // Load the main control

    this.RootVisual = new Page();

}

Looking at the docs for the RootVisual property of Application class, we're told that RootVisual can only be set once. My limited testing shows that this isn't actually true, and that it's possible to set LayoutRoot as many times as you'd like but only in the Application_Startup handler, which is obviously of very little use!

My solution is actually really very simple and can be summed up with one short snippet of code...

private void Application_Startup(object sender, StartupEventArgs e)

{

    // Load the main control

    Grid root = new Grid();

    root.Children.Add(new Page());

    this.RootVisual = root;

}

As you can see, here I am setting the RootVisual of the application as a new Grid and adding my Page usercontrol as a child of the grid. All we need to do now when we want to switch out Xaml pages is to manipulate the Children collection of our Grid! Here, check out my demo to see how it looks in action, this is a little two page application which allows you to switch back and forth between the pages in a number of different ways.

Woah! Hang on, where did all those cool transition effects come from? It just so happens that I've knocked together a few classes (I'm not going to use the word 'framework'!) that make these navigation issues and transition effects, even making your own transitions, really really easy. So... On to the code.

First up we'll take a look at the transitions. Every transition is implemented as a class deriving from the abstract TransitionBase, it's a really tiny little class which provides the implementation for a completed event, for when the transition has finished, and the contract for a single method which must be implemented by derived transitions.

public abstract class TransitionBase

{

    public event EventHandler<TransitionCompletedEventArgs> TransitionCompleted;

    protected void OnTransitionCompleted(UserControl newPage, UserControl oldPage)

    {

        if (TransitionCompleted != null)

            TransitionCompleted(this, new TransitionCompletedEventArgs() { NewPage = newPage, OldPage = oldPage });

    }

 

    public abstract void PerformTranstition(UserControl newPage, UserControl oldPage);

}

We have a PerformTransition method here which takes a reference to the two pages, the current and the one we'd like to change it with. This method is obviously implemented by our derived transitions to manipulate and animate the pages in some pleasing way, one thing to note here is that when the transition has completed its shuffling of the pages around, it must fire the completed event as we will see shortly.

Next up lets look at NavigationHelper, this is a static class which contains methods which can be called to switch pages.

public static class NavigationHelper

{

    private static Grid root;

 

    static NavigationHelper()

    {

        root = Application.Current.RootVisual as Grid;

    }

 

    public static void Navigate(TransitionBase transition, UserControl newPage)

    {

        UserControl oldPage = root.Children[0] as UserControl;

        root.Children.Insert(0, newPage);

 

        transition.TransitionCompleted += transition_TransitionCompleted;

        transition.PerformTranstition(newPage, oldPage);

    }

 

    public static void Navigate(UserControl newPage)

    {

        UserControl oldPage = root.Children[0] as UserControl;

        root.Children.Add(newPage);

        root.Children.Remove(oldPage);

    }

 

    private static void transition_TransitionCompleted(object sender, TransitionCompletedEventArgs e)

    {

        root.Children.Remove(e.OldPage);

    }

}

Ok, so here in our static constrcutor we grab a reference to our root visual from the application object. This is cast to a Grid so we can manipulate the Children collection. Now this is not really 100% safe because our static constructor will run the first time and member of our NavigationHelper class is accessed and this could be before the application's root visual has been set as a Grid. In practice though this is very unlikely to ever happen and it would make no sense at all for someone to call our navigation methods before Application_Startup so I'm going to let it go...

We also have two methods here, one which take a TransitionBase and a UserControl and one which just takes a UserControl. The method taking just a UserControl simply switches out the Xaml pages with no other action. The method first gets a reference to the current page which should be at the top of the children collection, then it inserts our new page at the top, this will cause it to be visible and hide the previous page as we have dropped our new one on top, the final step is to pull out the old page from underneath. This is how the "Switch" button in the demo above is working...

The other Navigate overload is more interesting, first we get a reference to the old page but this time we insert the new page behind it so it isn't immediately visible. Next we subscribe to the completed event for the passed in transition object and fire it off to let it do it's thing. We can see that when the transition completes and fires it's event, we simply remove the old page.

Looks great! Now we'll check out how a transition is implemented. There are a few transitions included with the code, but it's really easy for you to write your own and pass them to the NavigationHelper.Navigate method, just by inheriting from TransitionBase. Here is my FadeTransition.

public class FadeTransition : TransitionBase

{

    private UserControl newPage;

    private UserControl oldPage;

    private TimeSpan time;

    public FadeTransition(TimeSpan duration)

    {

        time = duration;

    }

    public FadeTransition() : this(TimeSpan.FromSeconds(2))

    { }

 

    public override void PerformTranstition(UserControl newPage, UserControl oldPage)

    {

        this.newPage = newPage;

        this.oldPage = oldPage;

 

        Duration duration = new Duration(time);

 

        DoubleAnimation animation = new DoubleAnimation();

        animation.Duration = duration;

        animation.To = 0;

 

        Storyboard sb = new Storyboard();

        sb.Duration = duration;

        sb.Children.Add(animation);

        sb.Completed += sb_Completed;

 

        Storyboard.SetTarget(animation, oldPage);

        Storyboard.SetTargetProperty(animation, new PropertyPath("Opacity"));

 

        sb.Begin();

    }

 

    void sb_Completed(object sender, EventArgs e)

    {

        OnTransitionCompleted(newPage, oldPage);

    }

}

This first thing to notice is that we overload the constructor so that we can pass in timespan, this lets us lengthen or shorten the transition as we see fit. Next is the PerformTransition implementation, this method just steps through and constructs a Storyboard object in code then sets it to run. When the Storyboard has finished its work we fire the TransitionCompleted event which lets NavigationHelper clear up out old page. I won't step through how the Storyboard is created as it's well documented on MSDN and I pretty much just copied if from there anyway, so if you want to know more, go check it out.

There is one other transition I'd like to show you, which is called CompositeTransition. This is how I achieve multiple effects at once, for example fading and sliding. I create a fade transition and a wipe transition, wrap them in a CompositeTransition and pass that to the NavigationHelper.

public class CompositeTransition : TransitionBase

{

    private TransitionBase transitionOne;

    private TransitionBase[] transitions;

    private Int32 count;

 

    private UserControl newPage;

    private UserControl oldPage;

 

    public CompositeTransition(TransitionBase transitionOne, params TransitionBase[] transitions)

    {

        this.transitionOne = transitionOne;

        this.transitions = transitions;

    }

 

    public override void PerformTranstition(UserControl newPage, UserControl oldPage)

    {

        this.newPage = newPage;

        this.oldPage = oldPage;

        transitionOne.TransitionCompleted += transition_TransitionCompleted;

        transitionOne.PerformTranstition(newPage, oldPage);

        foreach (TransitionBase transition in transitions)

        {

            transition.TransitionCompleted += transition_TransitionCompleted;

            transition.PerformTranstition(newPage, oldPage);

        }

    }

 

    private void transition_TransitionCompleted(object sender, TransitionCompletedEventArgs e)

    {

        count++;

        if (count == transitions.Length + 1)

            OnTransitionCompleted(newPage, oldPage);

    }

}

There is nothing particularly clever going on here, we have a constructor which take a variable number of TransitionBase instances. When PerformTransition is called we fire off all the transitions at once. When each one of them has completed we fire the TransitionCompleted event. Nice, now we can create loads of cool stuff! One thing worth noting though when stringing transition together like this, in my implementation there is a limitation in that my transtions set the UserControl's RenderTransform property and animate on this. If you chain multiple transitions which work like this they will over write the UserControls RenderTransform and only the last transition will do anything. The fix for this I guess would be to pass a TransformGroup into each transition so they're all animating against the same instance, but I'll leave that bit of fun and games as an excercise for the reader!

Just quickly, here is a snipet showing what a call to NavigationHelper.Navigate looks like, and then I'll leave you with the code download.

TransitionBase transition = new CompositeTransition(

    new FadeTransition(TimeSpan.FromSeconds(1)),

    new WipeTransition(WipeTransition.WipeDirection.LeftToRight));

 

NavigationHelper.Navigate(transition, new AnotherPage());

Enjoy!

FlawlessCode.Navigation.zip (144.27 kb)

kick it on DotNetKicks.com  

Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList
March 21, 2008 09:23 by Sean
E-mail | Permalink | Comments (10) | Comment RSSRSS comment feed

Related posts

Comments

March 21. 2008 12:48

Pingback from alvinashcraft.com

Dew Drop - March 21, 2008 | Alvin Ashcraft's Morning Dew

alvinashcraft.com

March 26. 2008 15:55

Got this error:

http://www.ryancdavidson.com/_images/sl2error.jpg

I was just clicking around your app.

Ryan

March 27. 2008 09:18

Hi Ryan,

Thanks for the heads-up.. Is this reproducible and does it happen predictably?

I'd probably guess that it's some Beta/OSX type bug in Silverlight as I can't get it to happen on Windows XP..

Cheers

Sean

March 27. 2008 10:59

Hi Sean,

Awesome! I really needed a solution like this – works great.

Thank you for sharing all of this,
David Roh

David Roh

March 27. 2008 16:27

Sean,

No I was not able to reproduce, but I didn't do an exhaustive test by any means. I just know how frustrating it can be to dev something and then have someone say I get an error message and leave it at that. From a fellow dev I figured I'd show you the error message.

On Refresh of the page, I didn't receive the error again.

When I got it initially, I was rapidly clicking on the different buttons. The error appeared when I clicked on the Rotate button after clicking on the Composite(Fade&Wipe) button.

Ryan

March 27. 2008 16:30

Interesting, once I gave the SL plugin focus, I could not return to the TextField to continue typing my response.
WinXP SP2, Firefox 2.

Anyway, I was also going to say that click on different buttons before the transition is finished will break all the animations except for the Switch. Obviously just a little logic needed to get rid of that bug.

Thanks for the great sample!

Ryan

April 4. 2008 16:48

Hi Sean,

thank's a lot for your very helpful postings.
Your design of the blog is excellent!

Thank's Hans

Hans

April 17. 2008 07:54

hi ,

thanks a lot, for such a wonderful article Smile

lakshmi

May 10. 2008 05:56

Pingback from uttech.net

Interesting technique for navigating between XAML files

uttech.net

June 6. 2008 12:29

Pingback from dotnetforum.dk

Moving between XAML pages in Silverlight - tomledk

dotnetforum.dk

Comments are closed