第二十四章:页面导航(十)

简介: 属性和方法调用调用PushAsync或PushModalAsync的页面显然可以直接访问它导航到的类,因此它可以设置属性或调用该页面对象中的方法以将信息传递给它。但是,调用PopAsync或PopModalAsync的页面还有一些工作要做,以确定它返回的页面。

属性和方法调用
调用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();
    }
    __
}

用户通过输入一些信息与页面上的元素进行交互:
2019_04_24_095815
在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显示新添加的项目:
2019_04_24_100740
您现在可以再次点击按钮以创建新项目,或者您可以点击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按钮保存更改。
这样的程序还应具有删除项目的便利。 在本章的后面,你会看到这样一个程序。

目录
相关文章
|
4月前
|
小程序 API
【微信小程序-原生开发】实用教程07 - Grid 宫格导航,详情页,侧边导航(含自定义页面顶部导航文字)
【微信小程序-原生开发】实用教程07 - Grid 宫格导航,详情页,侧边导航(含自定义页面顶部导航文字)
95 0
|
6月前
|
小程序 API
【微信小程序】-- 页面导航 -- 声明式导航(二十二)
【微信小程序】-- 页面导航 -- 声明式导航(二十二)
|
Android开发 索引 iOS开发
第二十四章:页面导航(十七)
像现实生活中的应用程序理想情况下,用户在终止并重新启动应用程序时不应该知道。应用程序体验应该是连续且无缝的。即使程序没有一直运行,一个半月进入的条目从未完成也应该在一周后处于相同的状态。NoteTaker程序允许用户记录由标题和一些文本组成的注释。
545 0
|
JavaScript 前端开发 Android开发
第二十四章:页面导航(十六)
保存和恢复导航堆栈 许多多页面应用程序的页面体系结构比DataTransfer6更复杂,您需要一种通用的方法来保存和恢复整个导航堆栈。此外,您可能希望将导航堆栈的保存与系统方式集成,以保存和恢复每个页面的状态,特别是如果您不使用MVVM。
493 0
|
XML JSON Android开发
第二十四章:页面导航(十五)
保存和恢复页面状态特别是当您开始使用多页面应用程序时,将应用程序的页面视为数据的主要存储库非常有用,而仅仅是作为底层数据的临时可视化和交互式视图。这里的关键词是暂时的。如果您在用户与之交互时保持基础数据是最新的,那么页面可以显示和消失而不必担心。
651 0
|
JavaScript Android开发
第二十四章:页面导航(十四)
切换到ViewModel此时应该很明显,Information类应该真正实现INotifyPropertyChanged。 在DataTransfer5中,Information类已成为InformationViewModel类。
603 0
|
JavaScript Android开发
第二十四章:页面导航(九)
数据传输模式 多页面应用程序中的页面通常需要共享数据,特别是一页面将信息传递到另一页面。 有时此过程类似于函数调用:当HomePage显示项目列表并导航到DetailPage以显示其中一个项目的详细视图时,HomePage必须将该特定项目传递给DetailPage。
630 0
|
Android开发
第二十四章:页面导航(八)
动态页面生成BuildAPage程序是一个多页面应用程序,但BuildAPage项目只包含一个名为BuildAPageHomePage的页面类。 顾名思义,该程序从代码构造一个新页面,然后导航到它。XAML文件允许您在此构造的页面上指定所需内容: <ContentPage xmlns="http://xamarin.
671 0
|
Android开发 索引
第二十四章:页面导航(十二)
事件在方法调用方法和消息中心通信方法中,信息页面需要知道主页的类型。 如果可以从不同类型的页面调用相同的信息页面,这有时是不合需要的。这个问题的一个解决方案是info类实现一个事件,这就是DataTransfer3中采用的方法。
507 0