The application lifecycle diagram from MSDN:
Pretty simple, but that Resuming bit is the “gotcha”. MSDN doesn’t really explain how this lifecycle relates to code as much as I’d like, so I’ll write it in layman’s terms:
- For your Application class:
- OnLaunched will be called any time your app is not in memory or if the app is being asked open something (see the ActivationKind enum)
- The Suspending event will fire whenever your app leaves the view
- The Resuming event will fire whenever your app re-enters the view, only if OnLaunched wouldn’t be called
- For the Page class:
- OnNavigatedTo will be called whenever someone calls Frame.Navigate with your page. This could be explicitly via a code-behind call, or implicitly via a call from the SuspensionManager helper class
- OnNavigatedFrom will be called whenever your app leaves the view by the SuspensionManager (in sync with the Application’s Suspending event)
(NOTE: Visual Studio’s templates come with the NavigationHelper.cs class. You’re encouraged to ignore the OnNavigatedTo and OnNavigatedFrom overrides, instead using NavigationHelper’s LoadState and SaveState events)
You should see one basic thing missing from Page. It can’t know about the Resuming lifecycle event. This creates a bit of an awkward situation. Let’s say you’re in MainPage.xaml.cs and you need to register for some events on OnNavigatedTo with an object that you were passed as a navigation parameter. If you don’t de-register these events on OnNavigatedFrom, you’ll be sorry. It’s possible for another call to OnNavigatedTo to occur on the same instance of MainPage — you’ll register for the same event twice which is probably not what you expected.
So OK, de-register your events in OnNavigatedFrom. But wait, that doesn’t quite work either! Remember that OnNavigatedFrom is called every time your app leaves the view, but OnNavigatedTo might not be called when it returns. You’ll end up without event handlers if the user leaves the app then returns while it’s still in memory (extremely common case).
So what do you do?
Honestly, the Page class isn’t enough. We need to extend it. Try something like this:
/// <summary> /// Use in place of the Page class for better application lifecycle support and better /// code reuse. /// </summary> public class BasicPage : Page { #region Public Properties /// <summary> /// Reference to the underlying NavigationHelper, which can be accessed by the Application. /// </summary> public NavigationHelper NavigationHelper { get; private set; } /// <summary> /// Use for the DataContext. /// </summary> public ObservableDictionary DefaultViewModel { get; private set; } #endregion #region Constructors /// <summary> /// Inheriting classes should call InitializeComponent(). /// </summary> public BasicPage() { NavigationHelper = new NavigationHelper(this); NavigationHelper.LoadState += LoadState; NavigationHelper.SaveState += SaveState; NavigationHelper.ResumeState += ResumeState; DefaultViewModel = new ObservableDictionary(); } #endregion #region Protected methods /// <summary> /// Called when this page should initialize it's data for the first time. Register event handlers. /// </summary> protected virtual void LoadState(object sender, LoadStateEventArgs e) { } /// <summary> /// Called when this page should save it's data to disk in preparation for leaving memory. Deregister /// event handlers. /// </summary> protected virtual void SaveState(object sender, SaveStateEventArgs e) { } /// <summary> /// Called when this page has returned to the view. Register event handlers if necessary or update the view. /// </summary> protected virtual void ResumeState(object sender) { } protected override void OnNavigatedTo(NavigationEventArgs e) { NavigationHelper.OnNavigatedTo(e); } protected override void OnNavigatedFrom(NavigationEventArgs e) { NavigationHelper.OnNavigatedFrom(e); } #endregion }
The basic mechanics for how to hook up a BasicPage like this can be found in a similar blog post. The only real difference between the above example and the linked one is the ResumeState method. This requires a modification to NavigationHelper.cs as well. Add the following:
/// <summary> /// Register this event on the current page to react to the app coming /// back into view. This event and LoadState are mutually exclusive, /// you will never get both. /// /// You should only need to do view updates, as everything should still /// be in-memory when this event is called. /// </summary> public event ResumeStateEventHandler ResumeState; ... /// <summary> /// Invoked when the app is resuming. /// </summary> public void OnResuming() { if (this.ResumeState != null) { this.ResumeState(this); } }
The final hook is in your App.xaml.cs code-behind, you need to register for the Resuming event and pass it to the page:
this.Resuming += OnResuming; ... private void OnResuming(object sender, object e) { Frame f = Window.Current.Content as Frame; BasicPage p = f.Content as BasicPage; if (p != null) { p.NavigationHelper.OnResuming(); } else { throw new Exception("Every page must implement BasicPage!"); } }
Now you have the tools you need with minimal code reuse.