天天看點

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

屬性和方法調用

調用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按鈕儲存更改。

這樣的程式還應具有删除項目的便利。 在本章的後面,你會看到這樣一個程式。

繼續閱讀