属性和方法调用
调用PushAsync或PushModalAsync的页面显然可以直接访问它导航到的类,因此它可以设置属性或调用该页面对象中的方法以将信息传递给它。但是,调用PopAsync或PopModalAsync的页面还有一些工作要做,以确定它返回的页面。在一般情况下,不能总是希望页面熟悉导航到它的页面的页面类型。
在设置属性或将方法从一个页面调用到另一个页面时,您需要谨慎。您不能对OnAppearing和OnDisappearing覆盖的调用顺序以及PushAsync,PopAsync,PushModalAsync和PopModalAsync任务的完成做出任何假设。
假设您有名为HomePage和InfoPage的页面。顾名思义,HomePage使用PushAsync导航到InfoPage以从用户获取一些信息,并且InfoPage必须以某种方式将该信息传输到HomePage。
以下是HomePage和InfoPage可以交互(或不交互)的一些方法:
HomePage可以访问InfoPage中的属性,或在实例化InfoPage后或在PushAsync任务完成后调用InfoPage中的方法。这很简单,您已经在SinglePageNavigation程序中看到了一个示例。
InfoPage可以访问HomePage中的属性,也可以在HomePage中随时调用其中的方法。最方便的是,InfoPage可以在其OnAppearing覆盖(用于初始化)或OnDisappearing覆盖(用于准备最终值)期间执行这些操作。在其存在的持续时间内,InfoPage可以从NavigationStack集合中获取HomePage实例。但是,根据相对于PushAsync或PopAsync任务的补充的OnAppearing和OnDisappearing调用的顺序,HomePage可能是NavigationStack中的最后一项,或者InfoPage可能是NavigationStack中的最后一项,在这种情况下,HomePage是最后一项的下一个。
可以通过覆盖其OnAppearing方法通知HomePage InfoPage已将控制权返回到HomePage。 (但请记住,当模态页面返回到调用它的页面时,不会在Android设备上调用此方法。)但是,在HomePage的OnAppearing覆盖期间,HomePage无法完全确定InfoPage的实例仍然存在在NavigationStack集合中,甚至它根本就存在。 HomePage可以在导航到InfoPage时保存InfoPage的实例,但如果应用程序需要在终止时保存页面状态,则会产生问题。
让我们检查一个名为DataTransfer1的程序,该程序使用第二个页面从用户获取信息,然后将该信息作为项添加到ListView。用户可以向ListView添加多个项目,也可以通过点击它来编辑现有项目。为了完全关注页间通信机制,程序不使用数据绑定,存储信息的类不实现INotifyPropertyChanged:
public class Information
{
public string Name { set; get; }
public string Email { set; get; }
public string Language { set; get; }
public DateTime Date { set; get; }
public override string ToString()
{
return String.Format("{0} / {1} / {2} / {3:d}",
String.IsNullOrWhiteSpace(Name) ? "???" : Name,
String.IsNullOrWhiteSpace(Email) ? "???" : Email,
String.IsNullOrWhiteSpace(Language) ? "???" : Language,
Date);
}
}
ToString方法允许ListView以最小的麻烦显示项目。
DataTransfer1程序有两个页面,名为DataTransfer1HomePage和DataTransfer1InfoPage,它们通过调用公共方法相互通信。 DataTransfer1HomePage有一个XAML文件,其中一个Button用于调用页面以获取信息,一个ListView用于显示每个项目并允许编辑项目:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataTransfer1.DataTransfer1HomePage"
Title="Home Page">
<Grid>
<Button Text="Add New Item"
Grid.Row="0"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="Center"
Clicked="OnGetInfoButtonClicked" />
<ListView x:Name="listView"
Grid.Row="1"
ItemSelected="OnListViewItemSelected" />
</Grid>
</ContentPage>
让我们在两个类之间来回反复来检查数据的传输。 这是代码隐藏文件的一部分,显示了带有ObservableCollection的ListView的初始化,以便ListView在集合发生更改时更新其显示:
public partial class DataTransfer1HomePage : ContentPage
{
ObservableCollection<Information> list = new ObservableCollection<Information>();
public DataTransfer1HomePage()
{
InitializeComponent();
// Set collection to ListView.
listView.ItemsSource = list;
}
// Button Clicked handler.
async void OnGetInfoButtonClicked(object sender, EventArgs args)
{
await Navigation.PushAsync(new DataTransfer1InfoPage());
}
__
}
此代码还通过实例化DataTransfer1InfoPage并导航到它来实现Button的Clicked处理程序。
DataTransfer1InfoPage的XAML文件有两个Entry元素,一个Picker和一个与Information属性对应的DatePicker。 此页面依赖于每个平台的标准用户界面来返回主页:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataTransfer1.DataTransfer1InfoPage"
Title="Info Page">
<StackLayout Padding="20, 0"
Spacing="20">
<Entry x:Name="nameEntry"
Placeholder="Enter Name" />
<Entry x:Name="emailEntry"
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>
<DatePicker x:Name="datePicker" />
</StackLayout>
</ContentPage>
信息页面的代码隐藏文件实例化与此页面实例关联的信息对象:
public partial class DataTransfer1InfoPage : ContentPage
{
// Instantiate an Information object for this page instance.
Information info = new Information();
public DataTransfer1InfoPage()
{
InitializeComponent();
}
__
}
用户通过输入一些信息与页面上的元素进行交互:
在DataTransfer1InfoPage调用其OnDisappearing覆盖之前,类中没有其他任何事情发生。这通常表示用户已按下后退按钮,该按钮是导航栏的一部分(在iOS和Android上)或在屏幕下方(在Android和Windows Phone上)。
但是,您可能知道在Xamarin.Forms(Windows Phone Silverlight)不再支持的平台中,当用户调用Picker或DatePicker时会调用OnDisappearing,并且您可能会担心在当前其他情况下调用它平台。这意味着当OnDisappearing作为正常导航回到主页的一部分被调用时,OnDisappearing覆盖中无法撤消任何操作。这就是DataTransfer1InfoPage在首次创建页面时实例化其Information对象而不是在OnDisappearing覆盖期间实例化的原因。
OnDisappearing覆盖从四个视图设置Information对象的属性,然后获取从NavigationStack集合调用它的DataTransfer1HomePage的实例。然后它在该主页中调用名为InformationReady的方法:
public partial class DataTransfer1InfoPage : ContentPage
{
__
protected override void OnDisappearing()
{
base.OnDisappearing();
// Set properties of Information object.
info.Name = nameEntry.Text;
info.Email = emailEntry.Text;
int index = languagePicker.SelectedIndex;
info.Language = index == -1 ? null : languagePicker.Items[index];
info.Date = datePicker.Date;
// Get the DataTransfer1HomePage that invoked this page.
NavigationPage navPage = (NavigationPage)Application.Current.MainPage;
IReadOnlyList<Page> navStack = navPage.Navigation.NavigationStack;
int lastIndex = navStack.Count - 1;
DataTransfer1HomePage homePage = navStack[lastIndex] as DataTransfer1HomePage;
if (homePage == null)
{
homePage = navStack[lastIndex - 1] as DataTransfer1HomePage;
}
// Transfer Information object to DataTransfer1HomePage.
homePage.InformationReady(info);
}
}
DataTransfer1HomePage中的InformationReady方法检查Information对象是否已经在设置为ListView的ObservableCollection中,如果是,则替换它。 否则,它将对象添加到该集合:
public partial class DataTransfer1HomePage : ContentPage
{
__
// Called from InfoPage.
public void InformationReady(Information info)
{
// If the object has already been added, replace it.
int index = list.IndexOf(info);
if (index != -1)
{
list[index] = info;
}
// Otherwise, add it.
else
{
list.Add(info);
}
}
}
检查Information对象是否已存在于ListView集合中有两个原因。 如果信息页面收到之前对其OnDisappearing覆盖的调用,则可能已存在,然后在主页中调用InformationReady。 此外 - 您将看到 - 可以编辑ListView中的现有项目。
在ObservableCollection中用自身替换Information对象的代码似乎是多余的。 但是,替换项的操作会导致ObservableCollection触发CollectionChanged事件,并且ListView会重绘自身。 另一个解决方案是使用Information实现INotifyPropertyChanged,在这种情况下,属性值的更改将导致ListView更新该项的显示。
此时,我们回到主页,ListView显示新添加的项目:
您现在可以再次点击按钮以创建新项目,或者您可以点击ListView中的现有项目。 ListView的ItemSelected处理程序也导航到DataTransfer1InfoPage:
public partial class DataTransfer1HomePage : ContentPage
{
__
// ListView ItemSelected handler.
async void OnListViewItemSelected(object sender, SelectedItemChangedEventArgs args)
{
if (args.SelectedItem != null)
{
// Deselect the item.
listView.SelectedItem = null;
DataTransfer1InfoPage infoPage = new DataTransfer1InfoPage();
await Navigation.PushAsync(infoPage);
infoPage.InitializeInfo((Information)args.SelectedItem);
}
}
__
}
但是,在PushAsync任务完成后,处理程序将使用所选项调用名为InitializeInfo的DataTransfer1InfoPage中的方法。
DataTransfer1InfoPage中的InitializeInfo方法将最初创建的Information对象替换为具有此现有实例的字段,并使用对象的属性初始化页面上的视图:
public partial class DataTransfer1InfoPage : ContentPage
{
__
public void InitializeInfo(Information info)
{
// Replace the instance.
this.info = info;
// Initialize the views.
nameEntry.Text = info.Name ?? "";
emailEntry.Text = info.Email ?? "";
if (!String.IsNullOrWhiteSpace(info.Language))
{
languagePicker.SelectedIndex = languagePicker.Items.IndexOf(info.Language);
}
datePicker.Date = info.Date;
}
__
}
现在,用户正在编辑现有项而不是新实例。
通常,允许编辑现有项目的程序还将使用户有机会放弃已对该项目进行的任何更改。 为了实现这一点,DataTransfer1InfoPage需要区分通过更改返回主页并取消编辑操作。 至少需要一个Button或ToolbarItem,它应该是一个Cancel按钮,以便标准的Back按钮保存更改。
这样的程序还应具有删除项目的便利。 在本章的后面,你会看到这样一个程序。