第二十四章:頁面導航(十四)

切換到ViewModel
此時應該很明顯,Information類應該真正實現INotifyPropertyChanged。 在DataTransfer5中,Information類已成爲InformationViewModel類。 它派生自Xamarin.FormsBook.Toolkit庫中的ViewModelBase,以減少過度使用:

public class InformationViewModel : ViewModelBase
{
    string name, email, language;
    DateTime date = DateTime.Today;
    public string Name
    {
        set { SetProperty(ref name, value); }
        get { return name; }
    }
    public string Email
    {
        set { SetProperty(ref email, value); }
        get { return email; }
    }
    public string Language
    {
        set { SetProperty(ref language, value); }
        get { return language; }
    }
    public DateTime Date    
    {
        set { SetProperty(ref date, value); }
        get { return date; }
    }
}

DataTransfer5中添加了一個名爲AppData的新類。 該類包括ListView的ObservableCollection信息對象以及信息頁面的單獨Information實例:

public class AppData
{
    public AppData()
    {
        InfoCollection = new ObservableCollection<InformationViewModel>();
    }
    public IList<InformationViewModel> InfoCollection { private set; get; }
    public InformationViewModel CurrentInfo { set; get; }
}

App類在實例化主頁之前實例化AppData並使其可用作公共屬性:

public class App : Application
{
    public App()
    {
        // Ensure link to Toolkit library.
        new Xamarin.FormsBook.Toolkit.ObjectToIndexConverter<object>();
        // Instantiate AppData and set property.
        AppData = new AppData();
        // Go to the home page.
        MainPage = new NavigationPage(new DataTransfer5HomePage());
    }
    public AppData AppData { private set; get; }
    __
}

DataTransfer5HomePage的XAML文件爲頁面設置BindingContext,其綁定包含靜態Application.Current屬性(返回App對象)和
AppData實例。 這意味着ListView可以將其ItemsSource屬性綁定到AppData的Info Collection屬性:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DataTransfer5.DataTransfer5HomePage"
             Title="Home Page"
             BindingContext="{Binding Source={x:Static Application.Current},            
                                      Path=AppData}">
    <Grid>
        <Button Text="Add New Item"
                Grid.Row="0"
                FontSize="Large"
                HorizontalOptions="Center"
                VerticalOptions="Center"
                Clicked="OnGetInfoButtonClicked" />
        <ListView x:Name="listView"
                  Grid.Row="1"
                  ItemsSource="{Binding InfoCollection}"
                  ItemSelected="OnListViewItemSelected">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <StackLayout Orientation="Horizontal">
                            <Label Text="{Binding Name}" />
                            <Label Text=" / " />
                            <Label Text="{Binding Email}" />
                            <Label Text=" / " />
                            <Label Text="{Binding Language}" />
                            <Label Text=" / " />
                            <Label Text="{Binding Date, StringFormat='{0:d}'}" />
                        </StackLayout>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</ContentPage>

該程序的早期版本依賴於信息中的ToString覆蓋來顯示項目。 既然Information已被InformationViewModel替換,那麼ToString方法就不夠了,因爲沒有通知ToString方法可能會返回一些不同的東西。 相反,ListView使用包含元素的ViewCell,這些元素綁定到InformationViewModel的屬性。
代碼隱藏文件繼續實現Button的Clicked處理程序和ListView的ItemSelected處理程序,但現在它們非常相似,可以使用名爲GoToInfoPage的常用方法:

public partial class DataTransfer5HomePage : ContentPage
{
    public DataTransfer5HomePage()
    {
        InitializeComponent();
    }
    // Button Clicked handler.
    void OnGetInfoButtonClicked(object sender, EventArgs args)
    {
        // Navigate to the info page.        
        GoToInfoPage(new InformationViewModel(), true);
    }
    // ListView ItemSelected handler.
    void OnListViewItemSelected(object sender, SelectedItemChangedEventArgs args)
    {
        if (args.SelectedItem != null)
        {
            // Deselect the item.
            listView.SelectedItem = null;
            // Navigate to the info page.
            GoToInfoPage((InformationViewModel)args.SelectedItem, false);
        }
    }
    async void GoToInfoPage(InformationViewModel info, bool isNewItem)
    {
        // Get AppData object (set to BindingContext in XAML file).
        AppData appData = (AppData)BindingContext;
        // Set info item to CurrentInfo property of AppData.
        appData.CurrentInfo = info;
        // Navigate to the info page.
        await Navigation.PushAsync(new DataTransfer5InfoPage());
        // Add new info item to the collection.
        if (isNewItem)
        {
            appData.InfoCollection.Add(info);
        }
    }
}

對於這兩種情況,GoToInfoPage方法設置AppData的CurrentInfo屬性。對於Clicked事件,它被設置爲新的InformationViewModel對象。對於ItemSelected事件,它設置爲ListView集合中的現有InformationViewModel。 GoToInfoPage方法的isNewItem參數指示是否還應將此InformationViewModel對象添加到AppData的InfoCollection中。
請注意,在PushAsync任務完成後,新項目將添加到InfoCollection中。如果在PushAsync調用之前添加了該項,那麼 - 根據平臺 - 您可能會注意到此新項突然出現在頁面轉換之前的ListView中。這可能有點令人不安!
DataTransfer5InfoPage的XAML文件將頁面的BindingContext設置爲AppData的CurrentInfo屬性。 (主頁在實例化信息頁面之前設置AppData的CurrentInfo屬性,因此AppData不必實現INotifyPropertyChanged。)BindingContext的設置允許頁面上的所有可視元素綁定到InformationViewModel類中的屬性:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:toolkit=
                 "clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
             x:Class="DataTransfer5.DataTransfer5InfoPage"
             Title="Info Page"
             BindingContext="{Binding Source={x:Static Application.Current},
                                     Path=AppData.CurrentInfo}">
 
    <StackLayout Padding="20, 0"
                  Spacing="20">
        <Entry Text="{Binding Name}"
               Placeholder="Enter Name" />
        <Entry Text="{Binding Email}"
               Placeholder="Enter Email Address" />
        <Picker x:Name="languagePicker"
                Title="Favorite Programming Language">
            <Picker.Items>
                <x:String>C#</x:String>
                <x:String>F#</x:String>
                <x:String>Objective C</x:String>
                <x:String>Swift</x:String>
                <x:String>Java</x:String>
            </Picker.Items>
        <Picker.SelectedIndex>
            <Binding Path="Language">
                <Binding.Converter>
                    <toolkit:ObjectToIndexConverter x:TypeArguments="x:String">
                        <x:String>C#</x:String>
                        <x:String>F#</x:String>
                        <x:String>Objective C</x:String>
                        <x:String>Swift</x:String>
                        <x:String>Java</x:String>
                   </toolkit:ObjectToIndexConverter>
                </Binding.Converter>
            </Binding>
       </Picker.SelectedIndex>
    </Picker>
 
    <DatePicker Date="{Binding Date}" />
    </StackLayout>
</ContentPage>

注意在Picker的SelectedIndex屬性和InformationViewModel的字符串Language屬性之間的綁定中使用ObjectToIndexConverter。 這個綁定轉換器在第19章“集合視圖”中的“數據綁定選擇器”一節中介紹。
DataTransfer5InfoPage的代碼隱藏文件實現了MVVM的目標,即只是對InitializeComponent的調用:

public partial class DataTransfer5InfoPage : ContentPage
{
    public DataTransfer5InfoPage()
    {
        InitializeComponent();
    }
}

DataTransfer5的另一個方便的方面是不再需要覆蓋OnAppearing和OnDisappearing方法,也不需要在頁面導航期間想知道這些方法調用的順序。
但真正好的是,在程序終止時將DataTransfer5遷移到保存應用程序數據的版本很容易,並在下次運行程序時將其恢復。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章