Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a typesafe way of navigating between screens in Windows Phone?

I'm looking for a way to navigate between screens in my app. Basically what I've seen so far consists of passing a string URI to the NavigationService, complete with query string parameters., e.g.

NavigationService.Navigate(new Uri("/MainPage.xaml?selectedItem=" +bookName.Id, UriKind.Relative));

I'm not really keen on this though ultimately because it requires magic strings, and they can lead to problems down the road.

Ideally I'd just create an instance of the class I want to navigate to, passing the parameters as arguments to the constructor. Is this possible? If so, how?

like image 472
DaveDev Avatar asked Nov 15 '13 15:11

DaveDev


4 Answers

While the actual navigation will have to use strings eventually, you can create or use a wrapper that is type safe.

I would suggest looking at Caliburn Micro even if you only used it for the type safe navigation. Here is a snippet from a tutorial on using it in WP8:

The NavigationService that comes with the toolkit supports a view model first approach: instead of declaring which is the URL of the page where we want to take the user (that is the standard approach), we declare which is the ViewModel we want to display. The service will take care of creating the correct URL and display the view that is associated with the view model.

Alternatively you could look at Windows Phone MVC which also has some type safe navigation. You might even just be able to pull the navigation code out to use on your own since it's licensed under MS-PL.

like image 193
Austin Thompson Avatar answered Nov 17 '22 20:11

Austin Thompson


Basically, no, not that's built-in. Complex parameters like IRepository instances are, unfortunately, beyond the ability of the navigation facilities in Silverlight; I usually use some form of IoC container to handle those. Simpler POCO parameters are easily serialized to a string, but that still requires magic strings and manual query-string parsing.

You can, however, easily build something typesafe yourself. For example, here's my approach.

For parameter data, I have a class that I call 'Extras', which wraps a Dictionary<string, object> with methods like GetBool(string), GetInt32(string), etc., and has a static factory method CreateFromUri(Uri); this is good enough for my purposes.

I use this in conjunction with type-safe navigation. I really like the MVVM pattern, and each of my pages has a ViewModel encapsulating nearly all logic. The one-to-one relationship of page to ViewModel makes the latter an ideal navigation key. That, combined with attributes and reflection, gives us a simple solution:

public class NavigationTargetAttribute : Attribute
{
    private readonly Type target;

    public ViewModelBase Target
    {
        get { return target; }
    }

    public NavigationTargetAttribute(Type target)
    {
        this.target = target;
    }
}

Put one of these on each of your pages, with the proper ViewModel type.

[NavigationTarget(typeof(LoginViewModel))]
public class LoginPage : PhoneApplicationPage
{ ... }

Then, in a singleton NavigationManager-esque class, you can do:

GetType().Assembly
    .GetTypes()
    .Select(t => new { Type = t, Attr = t.GetCustomAttributes(false).FirstOrDefault(attr => attr is NavigationTargetAttribute) })
    .Where(t => t.Attr != null);

And just like that, you have a collection of every navigable type in your app. From there, it's not much more work to put them in a dictionary, for example. If you follow a convention for where you put your pages, you can (for example) translate between type and Uri quite easily... for example, new Uri("/Pages/" + myPageType.Name + ".xaml", UriKind.Relative). It's not much more to add support for query parameters. Finally, you'll end up with a method, like so:

public void Navigate(Type target, Extras extras)
{
    Type pageType;
    if (navigationTargets.TryGetValue(target, out pageType))
    {
        var uri = CreateUri(pageType, extras);
        navigationService.NavigateTo(uri);
    }

    // error handling here
}

Finally, in the page's OnNavigatedTo method, I do something like:

var extras = Extras.CreateFromUri(e.Uri);
((ViewModelBase) DataContext).OnNavigatedTo(extras);

This, finally, gives a semblance of strongly-typed navigation. This is a bare-bones approach; off the top of my head, this could be improved by adding required parameters in the navigation attribute and validating them at navigation-time. It also doesn't support more complex types of navigation, where the value of nav arguments would determine the ultimate destination. Nevertheless, this suits my 90% use case - maybe it will work for you, too.

There are definitely some details omitted here, like how exactly to get an instance of NavigationService - I can work up a more complete sample later tonight, but this should be enough to get started.

like image 23
Ben Avatar answered Nov 17 '22 21:11

Ben


You can use PhoneApplicationService.State

It is a Dictionary<String,Object>

PhoneApplicationService.State is commonly used in tombstoning to store the current state of the application. However, it can be used to conveniently pass data between pages.

MSDN documentation

Windows Phone applications are deactivated when the user navigates to another application. When the user returns to the application, by using the Back button or by completing a Launcher or Chooser task, the application is reactivated. An application can store transient application state in the State dictionary in the handler for the Deactivated event. In the Activated event handler, an application can use the values stored in the State dictionary to transient application state.

Basically what you would do is

PhoneApplicationService.State.add(selectedName,yourobjectInstance);
NavigationService.Navigate((new Uri("/MainPage.xaml?selectedItem="+selectedName,UriKind.Relative));

Then in your navigated too method you can retrieve it

YourObject yourObjectInstance;
var yourObj = PhoneApplicationService.State["yourObjectName"];
yourObjectInstance = yourObj is YourObject ? (yourObj as YourObject) : null;

Here is a more indepth look into how to use this feature

like image 26
Anthony Russell Avatar answered Nov 17 '22 19:11

Anthony Russell


WPF supports navigating to an already created object, but WP8 lacks that Navigate overload.

If you don't want to hard-code XAML page URIs, you can you can use the following (a bit dirty) helper function to get the .xaml resource URI of some class.

static Uri GetComponentUri<T>() where T : DependencyObject, new() {
    return BaseUriHelper.GetBaseUri(new T());
}

Then you can modify that URL and navigate to it:

var baseUri = GetComponentUri<SomePage>(); //Uri="pack://application:,,,/MyProject;component/MainWindow.xaml"
var pageUri = new UriBuilder(baseUri) { Query = "selectedItem=" + bookName.Id };
NavigationService.Navigate(pageUri);
like image 1
Ark-kun Avatar answered Nov 17 '22 21:11

Ark-kun