切换到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迁移到保存应用程序数据的版本很容易,并在下次运行程序时将其恢复。