Previously I’ve written about how Prism for the Windows Runtime can be used to save and restore view model state.To have data survive the suspend/terminate cycle in a Prism app all you have to do is mark any view model properties whose data you want to survive termination with the custom [RestorableState] attribute. When the app is reactivated, any properties that are marked with this attribute will have their values restored. All of this work is performed by the Prism.StoreApps library. For details of how Prism undertakes this operation see Handling suspend, resume, and activation in AdventureWorks Shopper.
Less well known is that Prism provides support for saving and restoring view state. Therefore, in this blog post I’ll extend the sample photo viewing app so that view state can be serialized during suspension, and de-serialized when the app reactivates. Specifically, the position of the ScrollViewer thumb in the GridView on the MainPage will be serialized/de-serialized so that when the app reactivates following termination, the scroll position of the MainPage will be identical to when the app was suspended. Furthermore, the code will restore the correct scroll position even if the view state of the app changes in-between termination and reactivation.
An obvious question is why would you want to save view state? Surely any controls in the view will just bind to properties in the view model? While that’s often the case, many controls contain other controls buried deeply within their control template. An example of this is the GridView control, which contains a ScrollViewer control for scrolling purposes. Prism supports saving view state in order to easily support scenarios where you want to save the view state of a control that’s referenced through a control template.
Implementation
Suspend
The SaveAsync method of the SessionStateService class is responsible for writing the current session state to disk, and is called by the OnSuspending event handler in the MvvmAppBase class.
The SaveAsync method calls the GetNavigationState method of each registered Frame object in order to persist the serialized navigation history (the frame stack). In a Prism app there’s only one registered frame, and it corresponds to the rootFrame in the InitializeFrameAsync method in the MvvmAppBase class.
When the SaveAsync method calls the GetNavigationState method, it in turn invokes the OnNavigatedFrom method of each of the frame’s associated page objects. The OnNavigatedFrom method in the VisualStateAwarePage class then invokes the SaveState method of any page that derives from it, allowing pages to save view state such as the current scroll position of a control.
protected override void SaveState(Dictionary<string, object> pageState)
{
if (pageState == null) return;
base.SaveState(pageState);
pageState["ScrollViewerOffsetProportion"] = ScrollViewerUtilities.GetScrollViewerOffsetProportion(_photosGridViewScrollViewer);
}
The SaveState method preserves the state associated with the MainPage, in this case being a value that reflects the proportion of scrolling that has occurred either horizontally or vertically, depending on view state, within the ScrollViewer of the GridView. This value is retrieved by the GetScrollViewerOffsetProportion method of the ScrollViewerUtilities class, and can be restored when reactivation occurs.
Resume
When an app resumes from suspension it continues from where it was when it was suspended, as the app is still stored in memory. Therefore, the sample app has no special behaviour associated with resuming from suspension.
Activation
Windows may terminate an app after it has been suspended, if the system is low on resources. When a Prism app is reactivated the OnLaunched method in the MvvmAppBase class calls the InitializeFrameAsync method, which in turn calls the RestoreFrameState method in the SessionStateService class.
The RestoreFrameState method calls the SetNavigationState method of each registered Frame object in order to restore the serialized navigation history (the frame stack). In the sample app there is only one registered frame, and it corresponds to the rootFrame in the InitializeFrameAsync method in the MvvmAppBase class.
When the RestoreFrameState method calls the SetNavigationState method, it in turn invokes the OnNavigatedTo method of each of the frame’s associated page objects. The OnNavigatedTo method in the VisualStateAwarePage class then invokes the LoadState method of any page that derives from it, allowing pages to restore view state such as the current scroll position of a control.
protected override void LoadState(object navigationParameter, Dictionary<string, object> pageState)
{
if (pageState == null) return;
base.LoadState(navigationParameter, pageState);
if (pageState.ContainsKey("ScrollViewerOffsetProportion"))
{
_scrollViewerOffsetProportion = double.Parse(pageState["ScrollViewerOffsetProportion"].ToString(), CultureInfo.InvariantCulture.NumberFormat);
}
}
The LoadState method restores state associated with the MainPage, in this case a value that reflects the proportion of horizontal or vertical scrolling that had occurred, depending on view state, within the ScrollViewer of the GridView. The ScrollViewer is set with the restored value by the ScrollToProportion method in the ScrollViewerUtilities class once the windows has rendered or changed its rendering size. Therefore, the user will see the page content scrolled to the exact location it was at prior to termination or navigation, regardless of whether the page orientation has changed in between termination and reactivation.
Summary
In this blog post I’ve further extended the sample photo viewer app so that along with view model state, view state can be serialized during suspension and de-serialized when the app reactivates following termination. This is achieved by overriding the SaveState and LoadState methods in a page that derives from the VisualStateAwarePage class, and then saving the desired state. In the sample app the user will see the page content scrolled to the exact location it was at prior to termination or navigation, regardless of whether the page orientation has changed in between termination and reactivation.
The sample app can be downloaded here.
Do I understand correctly that I shoud write these 2 methods to the View's code-behind? If yes, I don't understand this solution because, as far as I know, MVVM implies not to use code-behind.
ReplyDeleteYes. It all comes down to how purist your view of MVVM is. We took the view that UI related code should go in the UI (the view) and business logic should go in the view model. As this is UI related code, it's in the view.
ReplyDeleteFurthermore, while in this example you could use Blend behaviours to handle the events (that capture scroll position) in the view model, you've then introduced a view dependency into your view model, which is worse than having the code in the view. This can then make unit testing very difficult.
So the overall approach is if you want to save view state, you should do it from your view. If you want to save view model state, you should do it from your view model.
Its nice blog with lot of information thanks for sharing keep doing it
ReplyDeletedot net training in chennai
Thanks for share the innovative message its very useful for us
ReplyDeletesalesforce training in chennai
Well said its very useful for us thank you
ReplyDeletecloud computing training in chennai