第十八章:MVVM(十)

wangccsy發表於2018-10-17

ViewModels和應用程式生命週期

在移動裝置上的實際計算器程式中,一個重要特性涉及在程式終止時儲存計算器的整個狀態,並在程式再次啟動時恢復它。
再次,ViewModel的概念似乎崩潰了。
當然,可以編寫一些應用程式程式碼來訪問ViewModel的公共屬性並儲存它們,但計算器的狀態也取決於私有欄位。 AdderViewModel的isSumDisplayed和accumulationSum欄位對於恢復計算器的狀態至關重要。
顯而易見,AdderViewModel外部的程式碼無法儲存和恢復AdderViewModel狀態,而ViewModel不會暴露更多公共屬性。只有一個類知道表示ViewModel的整個內部狀態的必要條件,那就是ViewModel本身。
解決方案是讓ViewModel定義儲存和恢復其內部狀態的公共方法。但是因為ViewModel應該努力與平臺無關,所以這些方法不應該使用特定於特定平臺的任何東西。例如,他們不應該訪問Xamarin.Forms Application物件,然後向該Application物件的Properties字典新增專案(或從中檢索專案)。這對於Xamarin.Forms來說太具體了。
但是,在任何.NET環境中都可以使用名為SaveState和RestoreState的方法中的通用IDictionary物件,這就是AdderViewModel實現這些方法的方式:

public class AdderViewModel : ViewModelBase
{
    __ 
    public void SaveState(IDictionary<string, object> dictionary)
    {
        dictionary["CurrentEntry"] = CurrentEntry;
        dictionary["HistoryString"] = HistoryString;
        dictionary["isSumDisplayed"] = isSumDisplayed;
        dictionary["accumulatedSum"] = accumulatedSum;
    }
    public void RestoreState(IDictionary<string, object> dictionary)
    {
        CurrentEntry = GetDictionaryEntry(dictionary, "CurrentEntry", "0");
        HistoryString = GetDictionaryEntry(dictionary, "HistoryString", "");
        isSumDisplayed = GetDictionaryEntry(dictionary, "isSumDisplayed", false);

        accumulatedSum = GetDictionaryEntry(dictionary, "accumulatedSum", 0.0);
        RefreshCanExecutes();
 }
    public T GetDictionaryEntry<T>(IDictionary<string, object> dictionary, 
                                   string key, T defaultValue)
    {
        if (dictionary.ContainsKey(key))
            return (T)dictionary[key];
        return defaultValue;
    }
}

涉及儲存和恢復此狀態的AddingMachine中的程式碼主要在App類中實現。 App類使用當前Application類的Properties字典例項化AdderViewModel並呼叫RestoreState。 然後將AdderViewModel作為引數傳遞給AddingMachinePage建構函式:

public class App : Application
{
    AdderViewModel adderViewModel;
    public App()
    {
        // Instantiate and initialize ViewModel for page.
        adderViewModel = new AdderViewModel();
        adderViewModel.RestoreState(Current.Properties);
        MainPage = new AddingMachinePage(adderViewModel);
    }
    protected override void OnStart()
    {
        // Handle when your app starts.
    }
    protected override void OnSleep()
    {
        // Handle when your app sleeps.
        adderViewModel.SaveState(Current.Properties); 
    }
    protected override void OnResume()
    {
        // Handle when your app resumes.
    }
}

App類還負責在處理OnSleep方法期間在AdderViewModel上呼叫SaveState。
AddingMachinePage建構函式只需要將AdderViewModel的例項設定為頁面的BindingContext屬性。 程式碼隱藏檔案還管理縱向和橫向佈局之間的切換:

public partial class AddingMachinePage : ContentPage
{
    public AddingMachinePage(AdderViewModel viewModel)
    {
        InitializeComponent();
        // Set ViewModel as BindingContext.
        BindingContext = viewModel;
    }
    void OnPageSizeChanged(object sender, EventArgs args)
    {
        // Portrait mode.
        if (Width < Height)
        {
            mainGrid.RowDefinitions[1].Height = GridLength.Auto;
            mainGrid.ColumnDefinitions[1].Width = new GridLength(0, GridUnitType.Absolute);
            Grid.SetRow(keypadGrid, 1);
            Grid.SetColumn(keypadGrid, 0);
        }
        // Landscape mode.
        else
        {
            mainGrid.RowDefinitions[1].Height = new GridLength(0, GridUnitType.Absolute);
            mainGrid.ColumnDefinitions[1].Width = GridLength.Auto;
            Grid.SetRow(keypadGrid, 0);
            Grid.SetColumn(keypadGrid, 1);
        }
    }
}

AddingMachine程式演示了一種處理ViewModel的方法,但這不是唯一的方法。或者,App可以例項化AdderViewModel,但是可以定義AddingMachineMode的建構函式可以訪問的AdderViewModel型別的屬性。
或者,如果您希望頁面完全控制ViewModel,您也可以這樣做。 AddingMachinePage可以定義自己的OnSleep方法,該方法是從App類中的OnSleep方法呼叫的,頁面類也可以處理AdderViewModel的例項化以及呼叫RestoreState和SaveState方法。但是,對於多頁應用程式,這種方法可能會變得有些笨拙。
在多頁面應用程式中,每個頁面可能都有單獨的ViewModel,可能來自ViewModel,其屬性適用於整個應用程式。在這種情況下,您需要使用相同的字典鍵來避免使用相同名稱的屬性來儲存每個ViewModel的狀態。您可以使用包含類名的更廣泛的字典鍵,例如“AdderViewModel.CurrentEntry”。
雖然資料繫結和ViewModel的功能和優勢現在應該很明顯,但是當與Xamarin.Forms ListView一起使用時,這些功能真的很開心。 這將在下一章中討論。


相關文章