2017/9/6

Xamarin.Forms 教學系列文(二十四.貳 - 2)Navigation 參數傳遞 - App & Events



學習目標
  • App 全域型變數
  • 委派 Event 變數 

為什麼我會稱這兩個為中介型參數傳遞呢?

因為這兩個方法都要依靠另一個類別內的變數,才有辦法達到 參數傳遞 這個目的。

第一種方法會先在 App 內設定好要使用的全域變數,

那第二種則是先在另一個頁面內設好要委派的事件 (Event) 變數。

先來看 App 的用法~ 相當簡單的

App 全域變數

在 Xamarin.Forms 應用程式內,第一個會被執行的就是 App()

又由於每一支程式內都可以取得 Application.Current 屬性,就可以透過這屬性得到 App() 內設定好的變數。

白話來說,我們可以在 App 類別內建立共用變數

來看程式,先建立一個 Information 類別:
public class Information
{
    public Information()
    {
        Date = DateTime.Today;
    }

    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);
    }
}

這邊的 Information 比之前的多了 Date 的屬性

再來看一下 App:
public class App : Application
{
    // 建立全域變數
    public IList InfoCollection { private set; get; }
    public Information CurrentInfoItem { set; get; } 

    public App()
    {
        // 初始化全域變數. 
        InfoCollection = new ObservableCollection();

        MainPage = new NavigationPage(new DataTransfer4HomePage());
    }

    … 
}

當我們在 App 建立好變數後,就能隨意的在 任一頁面 使用他們!


接著的範例和前一小節一樣,有兩個頁面 HomePage InfoPage

HomePage 可以選擇新增的動作,或點選一筆 ListView 的項目後導入 InfoPage 並做編輯

HomePage
public partial class DataTransfer4HomePage : ContentPage
{
    //建立 app 物件(重點)
    App app = (App)Application.Current;

    public DataTransfer4HomePage()
    {
        InitializeComponent();

        listView.ItemsSource = app.InfoCollection;
    }

    //新增按鈕
    async void OnGetInfoButtonClicked(object sender, EventArgs args)
    {
        // 建立新的 Information 物件給全域變數.
        app.CurrentInfoItem = new Information();

        await Navigation.PushAsync(new DataTransfer4InfoPage());
    }

    // ListView 點選時.
    async void OnListViewItemSelected(object sender, SelectedItemChangedEventArgs args)
    {
        if (args.SelectedItem != null)
        {
            listView.SelectedItem = null;

            // 取得點選項目值,並指派給全域變數
            app.CurrentInfoItem = (Information)args.SelectedItem;

            await Navigation.PushAsync(new DataTransfer4InfoPage());
        }
    }
}

總之,任何要傳遞的物件都交給 app 全域處理...

InfoPage 理所當然的使用全域變數,並取得 HomePage 指派的新值:

InfoPage
public partial class DataTransfer4InfoPage : ContentPage
{
    // 同樣要先建立 app 物件
    App app = (App)Application.Current;

    public DataTransfer4InfoPage()
    {
        InitializeComponent();

        // 從全域變數取出值,並帶入畫面
        Information info = app.CurrentInfoItem;

        nameEntry.Text = info.Name ?? "";
        emailEntry.Text = info.Email ?? "";

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

        datePicker.Date = info.Date;
    }

    protected override void OnDisappearing()
    {
        base.OnDisappearing();

        // 將畫面的值取出並指派給全域變數
        Information info = app.CurrentInfoItem;
        info.Name = nameEntry.Text;
        info.Email = emailEntry.Text;
        int index = languagePicker.SelectedIndex;
        info.Language = index == -1 ? null : languagePicker.Items[index];
        info.Date = datePicker.Date;

        // 如果值已經存在 list 則取代他
        IList list = app.InfoCollection;
        index = list.IndexOf(info);

        if (index != -1)
        {
            list[index] = info;
        }
        // 不存在則新增
        else
        {
            list.Add(info);
        }
    }
}

可以注意的是,因為 list 的型態是 ObservableCollection,當 InfoPage 去更改 list 時,就會自動去刷新 HomePage 的 ListView。

Event 事件委派

這方法和 App 有那麼一點點像,差在說我們把事件建立成一個變數,等待實際的方法委派給他,

痾... 聽不懂沒關係,來看程式碼:

但這次我們要倒回來看,先看一下 InfoPage,我們會先定義好 事件變數,並稍等讓 HomePage 委派實作:
    public partial class DataTransfer3InfoPage : ContentPage
    {
        // 定義一個事件,並等待委派
        public EventHandler< Information> InformationReady;
        
        Information info = new Information();

        public DataTransfer3InfoPage()
        {
            InitializeComponent();
        }

        public void InitializeInfo(Information 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;
        }

        protected override void OnDisappearing()
        {
            base.OnDisappearing();
            
            info.Name = nameEntry.Text;
            info.Email = emailEntry.Text;

            int index = languagePicker.SelectedIndex;
            info.Language = index == -1 ? null : languagePicker.Items[index];
            info.Date = datePicker.Date;

            // 若 InformationReady 事件非 null,則執行.
            InformationReady?.Invoke(this, info);
        }
    }

在 OnDisappearing 時,會去執行已經委派的 InformationReady 事件。

HomePage

本頁重點在於,我們會將方法委派給 InfoPage 定義好的 InformationReady 事件
    public partial class DataTransfer3HomePage : ContentPage
    {
        ObservableCollection list = new ObservableCollection();

        public DataTransfer3HomePage()
        {
            InitializeComponent();
            
            listView.ItemsSource = list;
        }

        // Button Clicked handler.
        async void OnGetInfoButtonClicked(object sender, EventArgs args)
        {
            DataTransfer3InfoPage infoPage = new DataTransfer3InfoPage();
            await Navigation.PushAsync(infoPage);

            // 委派方法給下一頁的事件
            infoPage.InformationReady += OnInfoPageInformationReady;

        }

        // ListView 點選項目時
        async void OnListViewItemSelected(object sender, SelectedItemChangedEventArgs args)
        {
            if (args.SelectedItem != null)
            {
                // Deselect the item. 
                listView.SelectedItem = null;
                DataTransfer3InfoPage infoPage = new DataTransfer3InfoPage();

                await Navigation.PushAsync(infoPage);

                // 呼叫下一頁的方法,上一小節有教學
                infoPage.InitializeInfo((Information)args.SelectedItem);

                // 委派方法給下一頁的事件
                infoPage.InformationReady += OnInfoPageInformationReady;
            }
        }

        void OnInfoPageInformationReady(object sender, Information info)
        {
            // 若物件已經存在,則取代他
            int index = list.IndexOf(info);
            if (index != -1)
            {
                list[index] = info;
            }
            // 不存在則新增
            else
            {
                list.Add(info);
            }
        }
    }

其實寫起來有點像是上一小節呼叫上頁方法,

但利用事件並委派的好處就是,
不需要事先知道上下頁的類別是什麼,就能執行另一頁的方法。
當然,也不用特地使用 Navigation Stack 去計算並取得頁面物件。



沒有留言:

張貼留言