locked
How to Navigate to next page from ViewModel of MVVM design in Metro Application RRS feed

  • Question

  • Hi

    I'm using MVVM in metro application where I would like to know how to navigate to next page.

    Main page displays GridView with bunch of images, after user selecting images , user will click a button, now app is navigate to next page.

    Because of MVVM, button is bind to a command in the ViewModel class. It is pretty straight forward to do it from View, but I am doing it from ViewModel, i'm not sure whether that is a recommended way also I'm not using MVVM light toolkit or anything. How to navigate to next page from ViewModel where it has no knowledge of View(or Frame)?

    Any help is greatly appreciated.


    CAT

    Saturday, March 17, 2012 1:36 AM

Answers

  • I usually create an INavigationService interface and inject the implementation at runtime. You could have the main page implement the interface using an implementation which uses the RootFrame to actually carry out the navigation.

    See http://geekswithblogs.net/lbugnion/archive/2011/01/06/navigation-in-a-wp7-application-with-mvvm-light.aspx for a good overview (albeit for windows phone).


    http://babaandthepigman.spaces.live.com/blog/

    Saturday, March 17, 2012 7:31 PM
  • The easy solution is to expose the Frame used in the application, eg. like this:

    public static Frame RootFrame;
    
    /// <summary>
    /// Invoked when the application is launched normally by the end user.  Other entry points
    /// will be used when the application is launched to open a specific file, to display
    /// search results, and so forth.
    /// </summary>
    /// <param name="args">Details about the launch request and process.</param>
    protected override void OnLaunched(LaunchActivatedEventArgs args)
    {
        if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
        {
            //TODO: Load state from previously suspended application
        }
    
        // Create a Frame to act navigation context and navigate to the first page
        RootFrame = new Frame();
        RootFrame.Navigate(typeof(BlankPage));
    
        // Place the frame in the current Window and ensure that it is active
        Window.Current.Content = RootFrame;
        Window.Current.Activate();
    }
    

    Then your viewmodel can navigate to the new page like this:

    App.RootFrame.Navigate(typeof(BlankPage));
    Of course this breaks some mvvm rules - your view model becomes aware of a view (BlankPage) and your view model can't be in a separate, decoupled library.

    Personally - I don't worry much about such things yet since I think the Frame/Page implementation seems a bit unfinished and there are other areas that need more work, but if you are working on a bigger project and really want to get your design right early on - you would need some sort of mapping of views (pages) to view models - either explicit or convention based (eg. using reflection and mapping based on view model name prefix - FooPageViewModel to FooPage) and listen to events from your view models that request navigation to a specific view model and respond on the view side by checking the mapping, navigating to the corresponding page and setting its DataContext to the requested view model.

    I have also seen issues when navigating to multiple page instances of the same type - seems like the back stack can only remember one instance of the page type, so you can only navigate forward to a new instance if you leave NavigationCacheMode on your page as Disabled and won't be able to return to the previous instance. To fix that you would probably have to wait for a newer build of the framework or implement your own Frame control (which should be pretty straightforward - just derive from a ContentControl and implement Navigate/GoBack methods and the backstack logic as you see fit.


    Filip Skakun

    Saturday, March 17, 2012 7:27 AM

All replies

  • I am writing a metro style gallery app using MVVM where View (MainPage) displays collection of pictures in GridView with grouped collection.

    View is bind to ViewModel which has ObservableCollection of Folder (Model), each folder returns GetVirtualizedFilesVector.

    After selecting few pictures, user will click, <next> button,  since view model command is hooked to the button, eventually ViewModel command (GoToNextPageCommand) will be executed.

    class CurrentPageViewMode {

    public void GotoNextPageCommand()

    {

    // Navigate to next page with passing selected items from gridView.

    }

    GotoNextPageCommand should navigate to next page with selected items from gridView displayed in the current page, how to do that?

    Any help would be greatly appreciated.


    CAT

    Saturday, March 17, 2012 5:32 AM
  • The easy solution is to expose the Frame used in the application, eg. like this:

    public static Frame RootFrame;
    
    /// <summary>
    /// Invoked when the application is launched normally by the end user.  Other entry points
    /// will be used when the application is launched to open a specific file, to display
    /// search results, and so forth.
    /// </summary>
    /// <param name="args">Details about the launch request and process.</param>
    protected override void OnLaunched(LaunchActivatedEventArgs args)
    {
        if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
        {
            //TODO: Load state from previously suspended application
        }
    
        // Create a Frame to act navigation context and navigate to the first page
        RootFrame = new Frame();
        RootFrame.Navigate(typeof(BlankPage));
    
        // Place the frame in the current Window and ensure that it is active
        Window.Current.Content = RootFrame;
        Window.Current.Activate();
    }
    

    Then your viewmodel can navigate to the new page like this:

    App.RootFrame.Navigate(typeof(BlankPage));
    Of course this breaks some mvvm rules - your view model becomes aware of a view (BlankPage) and your view model can't be in a separate, decoupled library.

    Personally - I don't worry much about such things yet since I think the Frame/Page implementation seems a bit unfinished and there are other areas that need more work, but if you are working on a bigger project and really want to get your design right early on - you would need some sort of mapping of views (pages) to view models - either explicit or convention based (eg. using reflection and mapping based on view model name prefix - FooPageViewModel to FooPage) and listen to events from your view models that request navigation to a specific view model and respond on the view side by checking the mapping, navigating to the corresponding page and setting its DataContext to the requested view model.

    I have also seen issues when navigating to multiple page instances of the same type - seems like the back stack can only remember one instance of the page type, so you can only navigate forward to a new instance if you leave NavigationCacheMode on your page as Disabled and won't be able to return to the previous instance. To fix that you would probably have to wait for a newer build of the framework or implement your own Frame control (which should be pretty straightforward - just derive from a ContentControl and implement Navigate/GoBack methods and the backstack logic as you see fit.


    Filip Skakun

    Saturday, March 17, 2012 7:27 AM
  • I usually create an INavigationService interface and inject the implementation at runtime. You could have the main page implement the interface using an implementation which uses the RootFrame to actually carry out the navigation.

    See http://geekswithblogs.net/lbugnion/archive/2011/01/06/navigation-in-a-wp7-application-with-mvvm-light.aspx for a good overview (albeit for windows phone).


    http://babaandthepigman.spaces.live.com/blog/

    Saturday, March 17, 2012 7:31 PM
  • That's what I do on Windows Phone too, but PhoneApplicationFrame supports Uri-based navigation that WinRT's Frame does not - at least not yet.

    Filip Skakun

    Saturday, March 17, 2012 7:52 PM
  • Hi Filip/BabaAndThePigman,

    Thanks for your response. As long as basic goal of the pattern is met, breaking few rules is ok. I also believe that most of the metro style apps are trying to solve single problem, simple approach should be ok.

    My second part of the question is that :

    page1  ==> bind to Page1ViewModel==> Model (Data comes from OS's FileSystem FolderInformation, FileInformation))

    First page displays GridView with the file system folder and file view, SelectedItems of the GridView is owned by View now, it is not bind to ViewModel.

    When ViewModel uses navigation service to navigate to Page2, it will create a Page2ViewModel, but data is in the Page1 (View) as GridView.SelectedItems, What is the correct approach that GridView.SelectedItems of Page1 (View) is passed to Page2ViewModel and bind to Page2(View)? 

    One approach would be :

    1. Create copy of the selected items from Page1(view) and set to Page2ViewModel, set this to DataContext of Page2(View)

    (or) 2. Bind Page1.itemGridView.SelectedItems ==> to Page1ViewModel.Member==> which is passed to Page2ViewModel, is it possible to bind GridView.SelectedItems to data in viewmodel?

    Thanks


    CAT

    Saturday, March 17, 2012 8:20 PM
  • Yep - and, as you say, you can keep the mapping yourself if the separation is important enough to your app.

    http://babaandthepigman.spaces.live.com/blog/

    Saturday, March 17, 2012 8:51 PM
  • I think you would need to implement a behavior/attached property to support binding SelectedItems - maybe this would be a good sample:

    http://blog.functionalfun.net/2009/02/how-to-databind-to-selecteditems.html


    Filip Skakun

    Monday, March 19, 2012 9:11 PM
  • Thanks.

    I believe that this would also handle based on the visual state, specific control will have SelectedItems which all points to data in ViewModel, but I don't like each control binding copy of the ViewModel's SelectedItems.


    CAT

    Tuesday, March 20, 2012 10:32 PM
  • Not sure what you mean by "each control binding copy of the ViewModel's SelectedItems". You already have a copy of all items bound using ItemsSource, right?


    Filip Skakun

    Tuesday, March 20, 2012 10:43 PM
  • I was thinking little different, I thought two list view controls (GridView, ListView (SnappedView)) keep copy of the two collections, but now I assume that since it is data binding to viewmodel, all points to same collection, I hope internally no copy was made inside the list view controls.

    FullView -- GridView :ItemSource -->Points to VM's Collection

    SnappedView -- ListView:ItemSource--> points to VM's Collection

    When user changes the view, selected items of the specific view( GridView or ListView) should update the selected items from the previous view (or) if SelectedItems of both control bind to VM's SelectedItem collection, it will be automatically reflected. That's where data bind to selected items comes into very handy.

     


    CAT

    Thursday, March 22, 2012 9:52 PM
  • Right.

    Filip Skakun

    Thursday, March 22, 2012 9:55 PM
  • If anyone is interested, I implemented my own Frame control as Filip suggested:

    http://blogs.microsoft.co.il/blogs/eshaham/archive/2012/04/30/fixing-frame-navigation-in-metro-style-apps.aspx

    Tuesday, May 1, 2012 4:22 AM
  • For anyone following this thread they might be interested in the open-Source Okra App Framework (disclaimer: I am the lead developer on this project). One of the key features is to provide a navigation service for Metro style applications and even supports persistence of the navigation stack and view model state upon app suspension. By default it uses MEF for composition and you simply add attributes to your page and view model classes, then can navigate to them via an INavigationManager.NavigateTo("My Page", arguments) method.
    Friday, August 17, 2012 11:44 AM
  • Hi,

    i have tried a few times and the simplest method by which one can achieve navigation in MVVM model is using these two lines in your ViewModel

          var rootFrame = Window.Current.Content as Frame;
          rootFrame.Navigate(typeof(NameOfSecondPage));

    though it navigates correctly but it not pure MVVM as name of the second page has to be given. Another issue that i have encountered is that I am not able to send data directly to the Second Page viewModel. 

    Thursday, September 26, 2013 5:02 AM
  • Hi,

    i have tried a few times and the simplest method by which one can achieve navigation in MVVM model is using these two lines in your ViewModel

          var rootFrame = Window.Current.Content as Frame;
          rootFrame.Navigate(typeof(NameOfSecondPage));

    though it navigates correctly but it not pure MVVM as name of the second page has to be given. Another issue that i have encountered is that I am not able to send data directly to the Second Page viewModel. 

    Bad way.

    Best way, as said above - create NavigationService, which will contains frame, dictionary of all page types and methods for navigation.

    Thursday, September 26, 2013 7:03 AM