Windows Phone 7 Tombstoning with MVVM and Sterling

來源:互聯網
上載者:User

Sterling makes tombstoning very easy because it handles serialization of just about any type of object. To show an example, we'll start with the concept of a view model that holds a set of categories (that a pivot is bound to) and a set of items that are filtered by category. When tombstoned, the application must remember the category as well as any item that is selected.

Build the Tombstone

First thing to do is create the generic tombstone model to hold values. This is the model: 

public class TombstoneModel{    public TombstoneModel()    {        State = new Dictionary<string, object>();    }    public Dictionary<string, object> State { get; set; }    public T TryGet<T>(string key, T defaultValue)    {        if (State.ContainsKey(key))        {            return (T)State[key];        }        return defaultValue;    }}

But wait ... what's the point? Isn't that just like the application settings object? Sure ... sort of. There are a few problems with application settings. First, they don't handle complex object graphs and the objects must be serializable. They don't recognize things like "foreign keys" or sub lists. Second, they load into memory as opposed to isolated storage files which can be loaded on demand.

Sterling, on the other hand, can handle almost any type of object (and for the ones it can't handle, you can write acustom serializer). 

Define the Tombstone Table

A full lesson on Sterling is outside the scope of this post. To learn about Sterling, read the User's Guide. To define a table in Sterling, you pass the type of the table and a key. Because there will only ever be one instance of the tombstone model, you cheat the key and make it always have a "true" value like this:

CreateTableDefinition<TombstoneModel,bool>(c=>true)

Finally, because the tombstone model is only to hold values that must be saved during a tombstone event, the model should be cleared when the application truly closes (as opposed to being deactivated due to a tombstone event). Therefore, in the Application_Closing method you can delete the tombstone model, if it exists:

private void Application_Closing(object sender, ClosingEventArgs e){    Database.Delete(typeof (TombstoneModel), true);}

Now everything is set to handle the tombstone properties.

Making your View Model a Caretaker

What other way is there to describe a view model that is "tombstone friendly?" To make a view model friendly, decorate it with the ITombstoneFriendly interface that looks like this:

public interface ITombstoneFriendly{    void Deactivate();    void Activate();}

These will be implemented in a moment.

Hooking into the Page

The best way to manage tombstone events is to hook into the OnNavigatedTo and OnNavigatedFrom overrides in the page code-behind. In fact, because forward navigation always generates a new instance of the view, this is the perfect way to maintain state of the view during navigation even when tomstoning isn't taking place.

Like before, we'll take advantage of some extension methods to make this easy. Here's a little caveat: you may be familiar with some "gotchas" with tombstoning if you've read Jeff Prosise's Real World Tombstoning series (here'spart 2, part 3, and Part 4). Oh, and did you know that Sterling automatically serializes WriteableBitmaps using the "fast" technique Jeff describes in those posts? You can easily add one to the tombstone collection and it will serialize for you! 

The issue with the pivot index happens due to the order of loading. If you restore values on the view model before the view is loaded, the view itself will reset any two-way bindings and you'll find a pivot simply snap to the first item. This is no good! Therefore, the extension method for the activate waits until the page is loaded, then calls the activate method on the view model.

Here are the extension methods: 

public static void DeactivatePage(this PhoneApplicationPage phonePage, IViewModel viewModel){    if (viewModel is ITombstoneFriendly)    {        ((ITombstoneFriendly)viewModel).Deactivate();    }}public static void ActivatePage(this PhoneApplicationPage phonePage, IViewModel viewModel){    RoutedEventHandler loaded = null;    loaded = (o, e) =>                    {                                                     ((PhoneApplicationPage) o).Loaded -= loaded;                        if (viewModel is ITombstoneFriendly)                        {                            ((ITombstoneFriendly) viewModel).Activate();                        }                    };    phonePage.Loaded += loaded;}

The loaded event doesn't take any conditional arguments, so it would be impossible to pass the view model to that event. Therefore, an anonymous method is used instead. By using a local variable, it can be unhooked inside the event call so it is not fired again. Once the page is loaded, the activate method is called on the view model. Because the page has been loaded, binding a pivot control item will work perfectly if the pivot SelectedItem is synchronized with a value on the view model.

Here's what the calls look like from the code-behind for the MainPage.xaml, hooking into the navigation events and passing the correct view model:

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e){    this.ActivatePage(GlobalManager.GetViewModel<IMainViewModel>());    base.OnNavigatedTo(e);}        protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e){    this.DeactivatePage(GlobalManager.GetViewModel<IMainViewModel>());    base.OnNavigatedFrom(e);}

Raising the Dead

The only thing left is to perform the actual deed. When the view model is flagged for death, it will hydrate out key values. In this case, it is the current category and current item: 

public void Deactivate(){    var tombstone = new TombstoneModel();    if (CurrentCategory != null)    {        tombstone.State.Add(ExtractPropertyName(() => CurrentCategory), CurrentCategory.Id);    }    if (CurrentItem != null)    {        tombstone.State.Add(ExtractPropertyName(()=>CurrentItem), CurrentItem.Id);    }    App.Database.Save(tombstone);}

A new model is created because you never care about the old model - this is saving state for the currenttombstone event, and the state from any other don't matter (they will be wiped when the application closes anyway). The dictionary is loaded with the key values for the current catalog and the current item. 

Now when the application is revived:

public void Activate(){    var saved = App.Database.Load<TombstoneModel>(true);    if (saved == null) return;    var categoryId = saved.TryGet(ExtractPropertyName(() => CurrentCategory), 0);    if (categoryId > 0)    {        CurrentCategory = (from c in Categories where c.Id == categoryId select c).FirstOrDefault();    }    var currentItemId = saved.TryGet(ExtractPropertyName(() => CurrentItem), 0);                if (currentItemId > 0)    {        CurrentItem = (from i in Items where i.Id == currentItemId select i).FirstOrDefault();    }}

In this case, the category list is always loaded in the constructor of the view model. That doesn't change regardless of tombstoning or not. The items is a filter by category, so when the category is updated, the items filter is also updated. When the old state is loaded, first the category is checked and if it exists, the current category is set to the corresponding category item. The same happens with the item. 

That's it. When testing the application, going into a pivot page always returns to the correct pivot column, and the selection state of the list is always maintained. If the application is exited by backing out, the close event deletes the tombstone key and upon re-entering, the pivot and list start on the first page with no selection as expected.

Tombstoning with MVVM can be dead simple with a few helper methods and Sterling to serialize the data.

by@Jeremy Likness

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.