Showing posts with label PLM. Show all posts
Showing posts with label PLM. Show all posts

Sunday, February 24, 2013

WinRT, Caliburn.Micro and IoC - Part 3 (SuspensionManager)

In my previous posts I integrated both Ninject and Unity into Caliburn.Micro for WinRT. This fixed issues I was having with a recent 1.4.1 code change, removing implicit self bindings.

The next thing I wanted to work through was state management. The VisualStudio templates have a nice implementation of state management through SuspensionManager and LayoutAwarePage.  LayoutAwarePage isn't an ideal match as a View for Caliburn.Micro MVVM. It tries to implement it's own MVVM pattern with the DefaultViewModel property, it contains a whole nested observable class definition, LoadState/SaveState logic in the View, and just plain does too much non-view stuff. 

With that said, LayoutAwarePage has many things going for it with VisualState switching management and hooking key and mouse navigation. LayoutAwarePage is only loosely coupled to SuspensionManager, so I figured I could fix these issues while still using SuspensionManager. To start out, I copied the code from LayoutAwarePage into a new class called AppPage. Then, trimmed out the DefaultViewModel and the observable collection definition.


AppPage.cs (interesting bits)


namespace CaliburnMicro_IoC_Sample.Code
{

    public class AppPage : Page
    {

.
.
.
        #region Process lifetime management

        private String _pageKey;

        /// <summary>
        /// Invoked when this page is about to be displayed in a Frame.
        /// </summary>
        /// <param name="e">Event data that describes how this page was reached.  The Parameter
        /// property provides the group to be displayed.</param>
protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            // Returning to a cached page through navigation shouldn't trigger state loading
            if (this._pageKey != null)
                return;

            var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
            this._pageKey = "Page-" + this.Frame.BackStackDepth;

            if (e.NavigationMode == NavigationMode.New)
            {
                // Clear existing state for forward navigation when adding a new page to the
                // navigation stack
                var nextPageKey = this._pageKey;
                int nextPageIndex = this.Frame.BackStackDepth;
                while (frameState.Remove(nextPageKey))
                {
                    nextPageIndex++;
                    nextPageKey = "Page-" + nextPageIndex;
                }

                // Pass the navigation parameter to the new page
                this.LoadState(e.Parameter, null, e);
            }
            else
            {
                // Pass the navigation parameter and preserved page state to the page, using
                // the same strategy for loading suspended state and recreating pages discarded
                // from cache
                this.LoadState(e.Parameter, (Dictionary<String, Object>)frameState[this._pageKey], e);
            }
        }

.
.
.

        /// <summary>
        /// Populates the page with content passed during navigation.  Any saved state is also
        /// provided when recreating a page from a prior session.
        /// </summary>
        /// <param name="navigationParameter">The parameter value passed to
        /// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested.
        /// </param>
/// <param name="pageState">A dictionary of state preserved by this page during an earlier
        /// session.  This will be null the first time a page is visited.</param>
protected virtual void LoadState(Object navigationParameter, Dictionary<String, Object> pageState, NavigationEventArgs e)
        {
            if (DataContext == null)
            {
                var svc = ServiceLocator.Current.GetInstance<inavigationservice>() as SuspensionFrameAdapter;
                if (svc != null) svc.DoNavigated(this, e);
            }

            if (DataContext is IHaveState && pageState != null && pageState.Count() > 0)
            {
                var haveState = DataContext as IHaveState;
                haveState.LoadState(Convert.ToString(navigationParameter), pageState);
            }
        }

        /// <summary>
        /// Preserves state associated with this page in case the application is suspended or the
        /// page is discarded from the navigation cache.  Values must conform to the serialization
        /// requirements of <see cref="SuspensionManager.SessionState"/>.
        /// </summary>
        /// <param name="pageState">An empty dictionary to be populated with serializable state.</param>
protected virtual void SaveState(Dictionary<String, Object> pageState)
        {
            if (DataContext is IHaveState)
            {
                var haveState = DataContext as IHaveState;
                haveState.SaveState(pageState, SuspensionManager.KnownTypes);
            }
        }

        #endregion

    }
You may notice a strange DataContext null test in LoadState, that has to do with a problem with Frames and SuspensionManager, described below. It is not my best work, and I should probably refactor it to utilize another interface, rather than a little hidden knowledge that the INavigationService was implemented by SuspensionFrameAdapter.