Wednesday, 10 October 2012

Navigating between pages in a C++/CX app

Previously I’ve developed a small photo viewing sample app to show how to implement a Windows Store app that supports design-time data, and uses the MVVM and Repository patterns to separate concerns. The app also includes code to navigate between pages, allowing navigation to be requested from both views and view models.

For example, when a photo thumbnail is clicked on the first page of the app, the second page of the app is navigated to and the clicked upon photo is shown. This functionality is invoked by the event handler shown below.

void MainView::OnPhotoClick(Object^ sender, ItemClickEventArgs^ e)
{
    auto photo = dynamic_cast<Windows::Storage::BulkAccess::FileInformation^>(e->ClickedItem);
    if (photo != nullptr)
    {
        PhotoViewerPage::NavigateToPage(PageType::Photo, photo);
    }
}

The event handler invokes the NavigateToPage method of the PhotoViewerPage class, passing the page to be navigated to and a parameter to pass to the page, in this case a FileInformation object representing the photo to be loaded on the Photo page. While this gives the desired result it creates a problem if the app is suspended after navigation has occurred.

When you create a new Windows Store app project in C++/CX the App class registers an event handler for the Suspending event. This event handler invokes the SaveAsync method in the SuspensionManager class, which is a helper class created by Visual Studio. In turn, 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. However, the GetNavigationState method doesn’t support serialization of any complex object parameter types which were passed to Frame->Navigate (which is invoked by PhotoViewerPage::NavigateToPage). Instead, it only supports serialization of primitive types. Therefore, in order to to allow the Frame navigation history to be serialized when the app is suspending, the parameter passed during page navigation must be a primitive type, rather than a FileInformation object.


Implementation


In order to to allow the Frame navigation history to be serialized when the app is suspending, the parameter passed during page navigation must be a primitive type, rather than a FileInformation object. This is the role of the PhotoNavigationData class. This class is a pure C++ class, rather than a ref class. This is because ref classes should only be used for interop with the Windows Runtime. For more information see the Hilo project.

PhotoNavigationData::PhotoNavigationData(FileInformation^ file)
{
    m_filePath = file->Path;
}
 
PhotoNavigationData::PhotoNavigationData(String^ serializedData)
{
    wstring data(serializedData->Data());
    m_filePath = ref new String(data.c_str());
}
 
String^ PhotoNavigationData::GetFilePath() const
{
    return m_filePath;
}
 
String^ PhotoNavigationData::SerializeToString()
{
    wstringstream stringStream;
    stringStream << m_filePath->Data();
    return ref new String(stringStream.str().c_str());
}

This class has two constructors. The first constructor takes a FileInformation object and stores the Path property of the object in a member variable. The second constructor takes a serialized String and stores it in a member variable. The GetFilePath method simply returns the member variable, and the SerializeToString method takes the member variable and turns it into a serialized String.

Note: in this example the PhotoNavigationData class can be thought of as overkill, as it ultimately takes a piece of data of type String (the Path property of the FileInformation object), and returns the data as a new String. However, this class has been developed for extensibility, for scenarios where you may need to pass a piece of data that is not a String, or where you may need to pass more than one piece of data, such as a String and a Date. This would be useful, for example, for pages that execute a query against the file system based upon a date range.

Then on the first page of the application when the user clicks on a thumbnail, in order to invoke page navigation, the event handler code becomes as shown below.

void MainView::OnPhotoClick(Object^ sender, ItemClickEventArgs^ e)
{
    auto photo = dynamic_cast<Windows::Storage::BulkAccess::FileInformation^>(e->ClickedItem);
    if (photo != nullptr)
    {
        PhotoNavigationData photoData(photo);
        PhotoViewerPage::NavigateToPage(PageType::Photo, photoData.SerializeToString());
    }
}

Here, once the FileInformation object for the clicked upon thumbnail is retrieved, a new PhotoNavigationData object is created using stack semantics. When PhotoViewerPage::NavigateToPage is invoked, the first parameter remains the page to be navigated to, but the second parameter becomes a call to SerializeToString on the instance of the PhotoNavigationData object.

When the Photo page is loaded, the OnNavigatedTo event handler in the PhotoViewModel class is executed. This deserializes the String passed to the page, and invokes the GetFilePath method on the PhotoNavigationData instance in order to pass the path of the FileInformation object to the Initialize method, where it’s stored as a member variable.

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

The benefit of this approach is that it enables the suspend/resume process for this sample app, as the Frame navigation history, which is serialized to disk when the app is suspended/terminated and deserialized when the app re-activates, contains the path of the FileInformation object to load on the Photo page. This means that if the app is suspended/terminated when the Photo page is active, upon re-activation the photo the user had selected before suspension will be re-loaded.

As I’ve previously stated, this may be seen as overkill for this simple example app, but it shows an approach that can be used when the parameter to be passed is not a String, or when more than one parameter needs to be passed to a page.


Summary


When an app is suspended, the Frame navigation history is serialized into a string by the GetNavigationState method. However, the GetNavigationState method doesn’t support serialization of any complex object parameter types which were passed to Frame->Navigate. Instead, it only supports serialization of primitive types. The PhotoNavigationData class shows an approach that enables data to be passed to a page as a primitive type, thus allowing the Frame navigation history to be serialized during suspension.

The sample application can be downloaded here.

No comments:

Post a Comment