WP7 Part 3: Navigation

Posted on May 16, 2010 by

This entry is part 3 of 8 in the series Windows Phone 7

NOTE: Since the first article was released, the WP7 tools have been updated with an April Refresh. The original post has been updated to reflect the proper download to get the April CTP-friendly code and we will use that as a starting point for this post.

OK – I have a confession to make. Actually, it’s more of something I have to admit – I made a mistake. In picking NerdDinner as the foundation for this blog series, I figured that having all kinds of screwball data in it would be perfectly OK since it wasn’t a “real” application, and I didn’t want to worry about working with actual live sites. That turned out to be a mistake. Although the NerdDinner site is a great idea, and a great learning tool for ASP.NET MVC developers, the data is *really* bad, and not something I want to continue working with for this series. SO… I’ve re-written the application from NerdDinner to use Community Megaphone, an event management and listing site maintained by my friend and collegue G. Andrew Duthie (a.k.a. DevHammer). I’ve also simplified things a bit and removed the repository pattern (at least for now) in favor of a more traditional approach, that we can look later at refactoring when we introduce an IoC container and begin to do DI, etc. So, for now, let’s just pretend that we’ve been working with CM all along, shall we?

Out of the box, the Windows Phone 7 tools give you a pretty good starting place for your application. The List template is based on the MVVM pattern, includes some sample code to help you better visualize your solution, and two pages to get you started: main page (list of items) and details page (details for an item). In this post we�ll explore the page navigation options available to the WP7 developer, starting with the OOB experience.

The sample application uses an, um� interesting approach to navigating from one page to another. The idea is simple: choose an item from the main list, show a cool page transition animation, then navigate to the new page with the details showing up. The way they accomplish this is to trap the left mouse button up event, store off the currently selected item, and start a page transition (i.e. storyboard). When the animation is complete, the main page uses the built-in Navigation framework to navigate the application to the new page. For that details page to get its data context, the main page (remember that he�s still in control until the current method exits) grabs hold of the root visual in the details page and sets that�s page�s DataContext attribute to the saved off currently selected item in the list.

hmmm.

I don�t know about you, but this somehow just plain bothers me. I realize that there are always several ways to skin a cat, but this one seems particularly icky to me. I don�t think it�s the job of the main page to force a data context on a details page. The details page should be told which object to use as its reference, or a key to that object perhaps, and then allowed to render itself.

To accomplish this in WP7 there are a couple of options:

  1. Continue to attach to the details page after navigating to it and send it the data on which it should operate � just do it in a slightly less icky way
  2. Send a parameter to the details page so that when it opens, it knows which object to get from the database, then it makes that stuff happen.
  3. Store the selected item into Isolated Storage (or some other caching mechanism), navigate to the new page, and then retrieve it again.

Navigation in WP7 is based on a similar framework to what you�re used to with ASP.NET. It uses a simple URI-based scheme for indicating which page in your application to load and navigate to.

Uri theUri = new Uri(�/DetailsPage.xaml�, UriKind.Relative);
NavigationContext.Navigate(theUri);

In this example, the NavigationContext loads the DetailsPage.xaml and navigates the application there using an unadorned, relative URI. Note: absolute URIs are not supported – you can’t use this mechanism to cause the built-in web browser to open a web page. There is a different technique for that which we’ll look at later. One thing you get for free with the Navigation framework is the ability to respond smartly to the Back button on the phone. After navigating to a new page, if the user presses the Back button on the phone, the application will go back to the last item in the Navigation stack. Pretty smart, eh?

OK – enough bakground, let’s go build something. In the MVVM world, the ViewModel takes the responsibility for loading itself on behalf of it’s View. In our case, we�re going to need a ViewModel that will do this for a given Event based on it’s associated Event ID. We�ll start by changing the call to the Details page to accept an Event ID as a parameter in the Navigation Context

private void PageTransitionList_Completed(object sender, EventArgs e)
{
    // Set datacontext of details page to selected listbox item
    //NavigationService.Navigate(new Uri("/DetailsPage.xaml", UriKind.Relative));
    //FrameworkElement root = Application.Current.RootVisual as FrameworkElement;
    //root.DataContext = _selectedItem;

    Uri theUri = new Uri("/DetailsPage.xaml?eventId=" + _selectedItem.id, UriKind.Relative);
    NavigationService.Navigate(theUri);

}

The QueryString, you say? Yes, the QueryString. Once inside the Details page, we can then use the NavigationContext again to retrieve the associated eventId and load the ViewModel with the right data:

void DetailsPage_Loaded(object sender, RoutedEventArgs e)
{
    int eventId = 0;

    //TODO: put some better error handling in here
    Int32.TryParse(NavigationContext.QueryString["eventId"], out eventId);

    // Set the data context of the listbox control to the sample data
    vm.View = this;
    DataContext = vm;
    vm.LoadFromId(eventId);
}

The view model can then use our same tricks from before to load the specific Event:

public void LoadFromId(int eventId)
{
    var catalog = CMEventsEntities.Instance;

    //var query = from e in catalog.ApprovedEvents
    //            where e.id == eventId
    //            select e;
    //DataServiceQuery dsq = query as DataServiceQuery;

    DataServiceQuery dsq = catalog.ApprovedEvents
        .AddQueryOption("$filter", "id eq " + eventId);

    dsq.BeginExecute(new AsyncCallback(a =>
    {
        var result = dsq.EndExecute(a).FirstOrDefault();

        View.Dispatcher.BeginInvoke(() =>
        {
            Item = result;
            NotifyPropertyChanged("Item");
            NotifyPropertyChanged("PageTitle");
        });

    }), null);
}

Note that this time, I was unable to get the typical LINQ query to retrieve the data I wanted, so I reverted to the AddQueryOption method of the DataServiceQuery to get what I needed from the OData service. I think this might be due to a bug in the pre-release version of the OData provider I’m using, becuase I can’t see any reason why the straight-up LINQ syntax shouldn’t work. Anyway, the pattern is the same as before: create a DataServiceQuery based on the data you want to retrieve, call BeginExecute, when the results come back they get processed on the UI thread in order to update the UI (note to self: I should probably create a code snippet for this so I don’t have to keep typing it all the time…) I’m also directly raising the NotifyPropertyChanged event for both the Item (our target Event) and the PageTitle property (which is based on the selected Item). That way, our UI will stay in sync with the data we’re maintaining in the ViewModel.

After this, and a little bit of Data Binding magic, our UI is populated and we have our details page!

One thing left to do related to Navigation – that pesky URL. As I mentioned before, you can’t use the built-in Navigation Framework to open a Web Browser to a given URL. You can try it if you want to – I’ll wait here… Didn’t work did it? Here’s what you can do:

  • Web Browser Control: Create a separate page that hosts a web browser control, and navigate there using the target URL as a QueryString property. When the web browser page loads, you can navigate the web browser control bring up the page you want. This is an OK option, but depends on what you’re really trying to accomplish/
  • WebBrowserTask: Using the WP7 Task infrastructure (I know, we haven’t covered that yet), you can launch the phone’s default web browser on a given URL. This is the method I’ll choose for this application, and I’ll show the code that makes it work.

We’ll be covering the Tasks infrastructure in more detail as part of a later post, but for now, just go with me…

There are many Task objects in the Windows.Phone.Tasks namespace – they are all designed to do a specific type of thing: launch some process to either display information, or capture information. The one we care about here, is the WebBrowserTask object. It’s whole purpose in life is to launch the phone’s built-in web browser to a specific URL. That being said, it couldn’t be easier to use. For our application, we’re displaying the event URL in a HyperlinkButton and capturing the Click event:


Once this event fires, we create an instance of the WebBrowserTask object, set it’s URL and call the Show() method to get the job done.

private void HyperlinkButton_Click(object sender, RoutedEventArgs e)
{
    WebBrowserTask task = new WebBrowserTask();
    task.URL = vm.Item.eventUrl;
    task.Show();
}

Clicking on the link launches the web browser and we can see the event in all of its default glory. Pressing the Back button takes us back to the details page, and back again will take us back to the home page.

That’s it for this post. In future posts, I’m planning to add an application bar that will allow us to modify our query parameters controlling what events we show in the main list, and hopefully integrate the Bing Map engine to show more information about the selected event.

Series NavigationWP7 Part 2 – Working with DataWP7 Part 4: Morphing and Mapping

Comments (3)

 

  1. Anonymous says:

    If your pages are located in a reference assembly then you will need to use the full Pack URI format to access the page. See this article about Silverlight 3 Navigation: Navigating to Pages in referenced assemblies for more information:

    http://www.davidpoll.com/2009/07/12/silverlight-3-navigation-navigating-to-pages-in-referenced-assemblies/

  2. [...] the typical web-based approach. They add query string parameters to the URL. Chris Koenig gives an excellent example of parameter-passing. The Silverlight NavigationContext object exposes these parameters via the QueryString property, [...]

  3. Great article! We will be linking to this great post on
    our site. Keep up the good writing.