Monday, 22 October 2012

Suspend/resume/activation in a C++/CX app

Previously I extended the sample photo viewing app so that the user can covert a colour photo to greyscale, on the PhotoView page. This used MVVM and commands to bind the Command property of a Checkbox control to an ICommand property on the PhotoViewModel. When the control’s command is invoked, the code in the view model is executed. The advantage of this approach is that it allows you to reduce (and in some cases completely eliminate) the amount of code in the code-behind file for the view, while also promoting the testability of the app.

I’ve also written about an approach to page navigation in C++/CX apps, which allows the page navigation history to be serialized during suspension and de-serialized when the app is resumed.

In this blog post I’ll further extend the app so that page state can be serialized during suspension, and de-serialized when the app reactivates. Specifically, the IsChecked property of the PhotoViewModel class will be serialized/de-serialized so that when the app reactivates following suspension, the state of the Checkbox control will be identical to when the app was suspended.

For information on the application lifecycle see Application lifecycle, Guidelines for app suspend and resume, and the Hilo project.

Implementation

Suspend

When you create a new Windows Store app in C++/CX the App class registers an event handler for the Suspending event, which is used to serialize app and user data. This event handler invokes the SaveAsync method in the SuspensionManager class, which is a helper class created by Visual Studio, to write the current session state to disk. The SaveAsync method invokes the SaveFrameNavigationState method in the SuspensionManager class, which invokes the GetNavigationState method on the Frame object, in order to serialize the Frame navigation history into a string. In turn, the GetNavigationState method invokes the OnNavigatedFrom method of the frame’s associated page object.

In the sample app, each page is an instance of the PhotoViewerPage class. The OnNavigatedFrom method of the PhotoViewerPage class invokes the OnNavigatedFrom method of the view model associated with the current page, and then invokes the OnNavigatedFrom method of its base class, LayoutAwarePage.

void LayoutAwarePage::OnNavigatedFrom(NavigationEventArgs^ e)
{
    auto frameState = SuspensionManager::SessionStateForFrame(Frame);
    auto pageState = ref new Map<String^, Object^>();
    SaveState(pageState);
    frameState->Insert(_pageKey, pageState);
}

The OnNavigatedFrom method of the LayoutAwarePage class gets the frame state from the suspension manager, and then calls the page’s SaveState method to update the frame state with data for the current page.

void PhotoViewerPage::SaveState(IMap<String^, Object^>^ pageState)
{
    auto vm = dynamic_cast<ViewModelBase^>(DataContext);
    if (vm != nullptr)
    {
        auto vmStateMap = ref new Map<String^, Object^>();
        vm->SaveState(vmStateMap);
 
        pageState->Insert(viewModelStateKey, vmStateMap);
    }
}

In the sample app, page state is saved through the associated view model, using a single key/value pair for the page’s data.

void PhotoViewModel::SaveState(IMap<String^, Object^>^ stateMap)
{
    if (m_file != nullptr)
    {
        stateMap->Insert(IsGreyscaleMapKey, m_isGreyscale);
    }
}

The SaveState method of the PhotoViewModel class saves the value of the m_isGreyscale member variable, which represents whether the photo on the PhotoView page should be displayed in greyscale or not.

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. However, other apps may need to refresh content from network connections etc. For more information see the Hilo project.

Activation

Windows may terminate an app after it has been suspended, if the system is low on resources. When an app is activated, the OnLaunched method of the App class will be invoked. The OnLaunched method will examine whether the app was terminated following suspension, and if it was, will invoke the RestoreAsync method of the SuspensionManager class in order to deserialize any frame and page state. The RestoreAsync method reads the saved state data and then calls the SetNavigationState method for each frame that was previously saved. In turn, the SetNavigationState method invokes the OnNavigatedTo method of the frame’s associated page object.

As previously mentioned, in this app each page is an instance of the PhotoViewerPage class. The OnNavigatedTo method of the PhotoViewerPage class invokes the OnNavigatedTo method of the view model associated with the current page, and then invokes the OnNavigatedTo method of its base class, LayoutAwarePage.

The OnNavigatedTo method of the PhotoViewModel class simply extracts the saved navigation data from the parameter and updates the view model.

void PhotoViewModel::OnNavigatedTo(NavigationEventArgs^ e)
{
    auto navigationData = dynamic_cast<String^>(e->Parameter);
    PhotoNavigationData photoData(navigationData);
    Initialize(photoData.GetFilePath());
}

The OnNavigatedTo method of the LayoutAwarePage gets the previously saved state for the page from the SuspensionManager class and invokes the page’s LoadState method to deserialize the page’s saved state and restore it.

void PhotoViewerPage::LoadState(Object^ navigationParameter, IMap<String^, Object^>^ pageState)
{
    auto vm = dynamic_cast<ViewModelBase^>(DataContext);
    if (vm != nullptr && pageState != nullptr)
    {
        IMap<String^, Object^>^ state = nullptr;
        state = dynamic_cast<IMap<String^, Object^>^>(pageState->Lookup(viewModelStateKey));
 
        vm->LoadState(state);
    }
}

In the sample app, page state is restored through the associated view model, using a single key/value pair for the page’s data.

void PhotoViewModel::LoadState(IMap<String^, Object^>^ stateMap)
{
    if (stateMap != nullptr)
    {
        m_isGreyscale = static_cast<bool>(stateMap->Lookup(IsGreyscaleMapKey));
    }
}

The LoadState method of the PhotoViewModel class restores the previously saved value of the m_isGreyscale member variable, which represents whether the photo on the PhotoView page should be displayed in greyscale or not. The overall effect is that if the user had the greyscale Checkbox control checked when the app suspended/terminated, the Checkbox control will be checked when the app reactivates, thus ensuring that the photo is displayed as greyscale rather than colour.

Summary

In this blog post I’ve further extend the app so that along with frame state, page state can be serialized during suspension and de-serialized when the app reactivates. Specifically, the IsChecked property of the PhotoViewModel class was serialized/de-serialized so that when the app reactivates following suspension, the state of the Checkbox control is identical to when the app was suspended.

The sample application can be downloaded here.

No comments:

Post a Comment