2017/9/4

Xamarin.Forms 教學系列文(二十四.貳 - 1)Navigation 參數傳遞 - 建構子 & 方法呼叫




學習目標
  • 建構子參數傳遞
  • 呼叫上下頁方法

當我學會了頁面導覽後,心中還是充滿了疑問,若我兩個頁面之間要做參數的傳遞,到底要怎麼做?

App 又不像網頁的 Post、Get,甚至還用個 Session 來當作參數傳遞!!!

那這一小節會提到 六種 參數傳遞的方式,各有優缺點,我會分成三個部分來說。
  1. 直觀型傳遞 - 建構子 & 呼叫方法
  2. 中介型傳遞 - App & Events
  3. 特殊型傳遞 - Messaging Center & ViewModel

第一部分會先來談談最簡單的方式,利用建構子去傳遞參數,或是呼叫上下頁的方法

建構子傳值

道理很簡單,在 Xamarin.Forms,其實每個頁面都是一個 Class,

那我們建立這個 Class 時,就能在建構時把參數帶進去啦!!

來看範例,有兩個頁面,
第一頁 SchoolPage 包含著一個 ListView,
當點擊某一項目時,將資料導向下一頁 StudentPage,並顯示其資料:

SchoolPage
XAML:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             x:Class="SchoolAndStudents.SchoolPage" 
             Title="School">
    
    <StackLayout BindingContext="{Binding StudentBody}">
        
        <Label Text="{Binding School}" 
               FontSize="Large" 
               FontAttributes="Bold"
               HorizontalTextAlignment="Center" />
        
       <!--顯示資料的 ListView-->
        <ListView x:Name="listView"
                  ItemsSource="{Binding Students}" 
                  ItemSelected="OnListViewItemSelected">
            
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ImageCell ImageSource="{Binding PhotoFilename}"
                               Text="{Binding FullName}" 
                               Detail="{Binding GradePointAverage, StringFormat='G.P.A. = {0:F2}'}" />
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        
    </StackLayout>
</ContentPage>

.cs
public partial class SchoolPage : ContentPage
{
    public SchoolPage()
    {
        InitializeComponent();

        BindingContext = new SchoolViewModel();
    }

    async void OnListViewItemSelected(object sender, SelectedItemChangedEventArgs args)
    {
        // 選中的資料
        Student student = args.SelectedItem as Student;

        if (student != null)
        {
            // 清空選中的屬性
            listView.SelectedItem = null;

            // 導向下一頁並將 student 帶入
            await Navigation.PushAsync(new StudentPage(student));
        }
    }
}

執行結果:

從這頁點一筆資料後,將點選的資料帶入 StudentPage 頁。

StudentPage
.cs:
public partial class StudentPage : ContentPage
{
    //從前一頁接收 studetn 值
    public StudentPage(Student student)
    {
        InitializeComponent();
        BindingContext = student;
    }
}

XAML:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="SchoolAndStudents.StudentPage" 
             Title="Student">
    <StackLayout>
        <!-- Name -->
        <StackLayout Orientation="Horizontal"
                     HorizontalOptions="Center" 
                     Spacing="0">
            <StackLayout.Resources>
                <ResourceDictionary>
                    <Style TargetType="Label">
                        <Setter Property="FontSize" Value="Large" />
                        <Setter Property="FontAttributes" Value="Bold" />
                    </Style>
                </ResourceDictionary>
            </StackLayout.Resources>
            
            <Label Text="{Binding LastName}" />
            <Label Text="{Binding FirstName, StringFormat=', {0}'}" />
            <Label Text="{Binding MiddleName, StringFormat=' {0}'}" />
        </StackLayout>
        
        <!-- Photo -->
        <Image Source="{Binding PhotoFilename}" 
               VerticalOptions="FillAndExpand" />
        
        <!-- Sex -->
        <Label Text="{Binding Sex, StringFormat='Sex = {0}'}"
               HorizontalOptions="Center" />
        
        <!-- GPA -->
        <Label Text="{Binding GradePointAverage, StringFormat='G.P.A. = {0:F2}'}" 
               HorizontalOptions="Center" />
    </StackLayout>
</ContentPage>

執行結果:



*也可以在 Push 時就設定 BindingContext 如下:
await Navigation.PushAsync(new StudentPage { BindingContext = student });


呼叫方法

假設有兩個頁面,HomePageInfoPage,當 HomePage 導覽至 InfoPage 後,因為一些原因,必須將 InfoPage 的值回傳到 HomePage 上。

來看範例,
HomePage 可以導覽至 InfoPage 進行新增或編輯,
當新增或編輯結束後,要新增或更新 HomePage 內的 ListView:
(謎之聲:這種做完事情要另一個地方更改顯示內容的動作,是否可以用 ViewModel 處理呢?)

先建立一個等等會用到的 Information 類別:
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);
    }
}

HomePage
XAML:
<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要更新-->
        <ListView x:Name="listView" 
                  Grid.Row="1" 
                  ItemSelected="OnListViewItemSelected" />
    </Grid>
</ContentPage>

.cs
public partial class DataTransfer1HomePage : ContentPage
{
    ObservableCollection list = new ObservableCollection();

    public DataTransfer1HomePage()
    {
        InitializeComponent();

        listView.ItemsSource = list;
    }

    // 按鈕按下去導向 InfoPage 
    async void OnGetInfoButtonClicked(object sender, EventArgs args)
    {
        await Navigation.PushAsync(new DataTransfer1InfoPage());
    }

    // 準備好方法讓 InfoPage 呼叫
    public void InformationReady(Information info)
    {
        // 若物件已存在於 list 則取代 
        int index = list.IndexOf(info);
        if (index != -1) 
        {
             list[index] = info;
        }
        // 不存在就新增
        else
        {
            list.Add(info);
        }
    }
}

執行結果:


InfoPage
XAML:
畫面上有兩個 Entry 和一個 Picker
<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>

這邊輸入資料不會發生任何事…
直到按下返回鍵,才會覆寫 OnDisappearing 方法,並呼叫 HomePage 內的 InformationReady 方法

要注意的是用 NavigationStack 取得 HomePage 物件的方法。
public partial class DataTransfer1InfoPage : ContentPage
{
    // 初始化 Information 物件
    Information info = new Information();

    public DataTransfer1InfoPage()
    {
        InitializeComponent();
    } 
    
    //覆寫本頁消失時的事件
    protected override void OnDisappearing()
    {
        base.OnDisappearing();

        // 取得畫面上輸入的資料並指派給 info 物件
        info.Name = nameEntry.Text;
        info.Email = emailEntry.Text;
        int index = languagePicker.SelectedIndex;
        info.Language = index == -1 ? null : languagePicker.Items[index];
        info.Date = datePicker.Date;

        // 利用 Stack 取得 HomePage 物件
        NavigationPage navPage = (NavigationPage)Application.Current.MainPage;
        IReadOnlyList navStack = navPage.Navigation.NavigationStack;

        int lastIndex = navStack.Count - 1;
        DataTransfer1HomePage homePage = navStack[lastIndex] as DataTransfer1HomePage;

        if (homePage == null)
        {
            homePage = navStack[lastIndex - 1] as DataTransfer1HomePage;
        }

        // 呼叫 HomePage 的方法,並把要傳遞的物件帶入
        homePage.InformationReady(info);
    }
}

執行畫面:


最後順手來寫個編輯,

當 HomePage 點擊 List 的項目時導向 InfoPage,
這邊示範則從 HomePage 去呼叫 InfoPage 的方法,並把值帶入 InfoPage 的畫面:
public partial class DataTransfer1HomePage : ContentPage
{ 
    … 
    // List 點擊時的事件
     async void OnListViewItemSelected(object sender, SelectedItemChangedEventArgs args)
    {
        if (args.SelectedItem != null)
        {
            listView.SelectedItem = null;

            DataTransfer1InfoPage infoPage = new DataTransfer1InfoPage();

            await Navigation.PushAsync(infoPage);
            infoPage.InitializeInfo((Information)args.SelectedItem);
        }
    }
    … 
}

InfoPage 取得值並帶入畫面:
public partial class DataTransfer1InfoPage : ContentPage
{ 
    … 
    public void InitializeInfo(Information info)
    {
        // 取代 info 物件
        this.info = info;

        // 初始化畫面值
        nameEntry.Text = info.Name ?? "";
        emailEntry.Text = info.Email ?? "";

        if (!String.IsNullOrWhiteSpace(info.Language))
        {
            languagePicker.SelectedIndex = languagePicker.Items.IndexOf(info.Language);
        }

        datePicker.Date = info.Date;
    }
    … 
}


沒有留言:

張貼留言