viewmodelbackstack for when you navigate to the same page

When working on my comic application for Windows Phone I encountered a problem that I’ve had in the past and have heard others ran into as well. When on the detailspage of a comic character I have a list of enemies of that character. Those enemies are clickable to load their details. Nothing hard there, but both the first character and its enemies are of the same type and they use the same view and viewmodel to show their data. This isn’t hard to do, the difficult part is using the phone’s back button. After navigating to the same CharacterDetailPage 4 times I would expect the back button to take me back through all the characters I’ve viewed.

I was trying to use OnNavigatedTo and OnNavigatedFrom events but apparently those do not fire when navigating to the same page. After a bit of tinkering I came up with a ViewModelBackstack class that does the trick.

I’ve uploaded the class and a sample project to GitHub and I’ve created a NuGet package for everyone to use (my very first public package, hooray!). In this post I’ll walk through the sample project to explain how it works.

First of all, if you want to add the package to your solution, search for ViewModelBackstack on NuGet or use the command line

Install-Package ViewModelBackstack

Once that’s setup start building your application, MVVM style (the sample application uses MVVM Light, but ViewModelBackstack should work with all the other ones out there).

The sample application

The sample application is a basic one, it has two pages, a MainPage and a GuidPage. The MainPage only contains some text and a button to navigate to the second page. The GuidPage contains a textblock that is bound to a property on the viewmodel, and a button that simulates navigating to the same page again but loading in different data.

The scenario is this:

MainPage > GuidPage > GuidPage > GuidPage > …

MainVM > GuidVM > GuidVM > GuidVM > …

Follow it the other way around to know how the back button will respond.

The ViewModelBackstack class

ViewModelBackstack is a static class, and not a very big one.

Code Snippet
  1. public static class ViewModelBackStack
  2. {
  3.     private static Dictionary<string, string> _viewModelStack;
  4.  
  5.     public static void Add(string key, object value)
  6.     {
  7.         if (_viewModelStack == null)
  8.             _viewModelStack = new Dictionary<string, string>();
  9.  
  10.         _viewModelStack.Add(key, JsonConvert.SerializeObject(value));
  11.     }
  12.  
  13.     public static object Take<T>(string key)
  14.     {
  15.         string toReturn = _viewModelStack[key];
  16.         Delete(key);
  17.  
  18.         return JsonConvert.DeserializeObject<T>(toReturn);
  19.     }
  20.  
  21.     public static bool TryTake<T>(string key, out T value)
  22.         where T : class
  23.     {
  24.         try
  25.         {
  26.             value = JsonConvert.DeserializeObject<T>(_viewModelStack[key]);
  27.             Delete(key);
  28.  
  29.             return true;
  30.         }
  31.         catch (Exception)
  32.         {
  33.             value = null;
  34.             return false;
  35.         }
  36.     }
  37.  
  38.     public static bool ContainsKey(string key)
  39.     {
  40.         if (_viewModelStack == null)
  41.             return false;
  42.  
  43.         return _viewModelStack.ContainsKey(key);
  44.     }
  45.  
  46.     public static void Delete(string key)
  47.     {
  48.         _viewModelStack.Remove(key);
  49.     }
  50.  
  51.     public static void Replace(string key, object newValue)
  52.     {
  53.         _viewModelStack[key] = JsonConvert.SerializeObject(newValue);
  54.     }
  55.  
  56.     public static bool CanGoBack()
  57.     {
  58.         if (_viewModelStack == null)
  59.             return false;
  60.  
  61.         return _viewModelStack.Count > 0;
  62.     }
  63.  
  64.     public static T GoBack<T>()
  65.     {
  66.         var toReturn = _viewModelStack.Last();
  67.         _viewModelStack.Remove(toReturn.Key);
  68.  
  69.         return JsonConvert.DeserializeObject<T>(toReturn.Value);
  70.     }
  71. }

It contains a Dictionary<string, string> that will hold the instances of the viewmodels. The instances are serialized into JSON strings with Json.net. This to save memory and avoid reference issues.

There are some methods in there to manually take out a specific instance or to delete one. But more importantly are the CanGoBack() and GoBack() methods. Let’s have a look at how to use this.

Usage

In the GuidViewModel’s constructor we start listening for a message, when that message arrives we load in new data (in this case, generate a new GUID) GuidString is a normal property that calls RaisePropertyChanged from the setter.

Code Snippet
  1. public GuidViewModel()
  2. {
  3.     Messenger.Default.Register<GenerateNewGuidMessage>(this, msg => GenerateGuid());
  4. }
  5.  
  6. private void GenerateGuid()
  7. {
  8.     GuidString = Guid.NewGuid().ToString();
  9. }

Next is the command that is bound to the button on the page, this is a RelayCommand that will call the LoadNewData method

Code Snippet
  1. private void LoadNewData()
  2. {
  3.     if (ViewModelBackStack.ContainsKey(GuidString))
  4.         ViewModelBackStack.Replace(GuidString, this);
  5.     else
  6.         ViewModelBackStack.Add(GuidString, this);
  7.  
  8.     Messenger.Default.Send(new GenerateNewGuidMessage());
  9. }

The LoadNewData method will check if the ViewModelBackStack already contains the key we use (each instance needs a unique key, we’re using the GUID in this case). If it’s already there, replace it, if not add it to the backstack. After that, send the message to generate new data.

Note that we’re not actually navigating away from the page, since the NavigationService doesn’t actually navigate when you try going to the same page there’s really no use in trying.

The final step is intercepting the back button press and using it load in a previous instance of the GuidViewModel. We need to do this in the code-behind of the page, since we need to cancel the navigation there (by default, when pressing the back button here it would just take us back to MainPage, so navigation needs to be cancelled).

Code Snippet
  1. protected override void OnBackKeyPress(CancelEventArgs e)
  2. {
  3.     if (ViewModelBackStack.CanGoBack())
  4.     {
  5.         DataContext = ViewModelBackStack.GoBack<GuidViewModel>();
  6.         e.Cancel = true;
  7.         return;
  8.     }
  9.  
  10.     base.OnBackKeyPress(e);
  11. }

OnBackKeyPress can be overriden from PhoneApplicationPage base class. If the ViewModelBackStack can go back we take out the most recent record in the dictionary, deserialize it to T, set that result as datacontext and we’re done. We can cancel the navigation by setting e.Cancel to true. Once the ViewModelBackStack is empty the app will return to MainPage.

Wrap up

So that’s about it. I’m currently building a Windows Phone app that uses the ViewModelBackStack, so there could be some changes coming in the next few weeks, or it might just prove to work perfectly as-is.

Feel free to fork the repo on GitHub and send pull requests if you can enhance / improve upon the project, props will be given Glimlach.

This is an imported post. It was imported from my old blog using an automated tool and may contain formatting errors and/or broken images.

Leave a Comment