Xamarin.Forms - 在頁面間傳遞資料透過 WebView 顯示

昨天學習了實際透過 HttpClient 由行動裝置讀取發行在 Azure Web App 的 ASP.NET Core Web API 所實作的 REST API 回傳儲存於 Azure SQL Database 資料示在 ListView 上,今天就再進一步學習如下圖所示,當按下 ListView 的項目(Item)時可將該項目所含的資料帶到下一頁,並在下一頁使用該資料的地址資訊顯示出 Google 地圖:

繫結事件處理常式

首先在 MyFirstPage.xaml 的 ListView 加入 ItemTapped="OnItemTapped" 也就是當項目(Item)被按下時執行命名為 OnItemTapped (可自行命名)的事件處理常式,如下所示:

<ListView RowHeight="90" 
          ItemsSource="{Binding Addresses}" 
          ItemTapped="OnItemTapped">

     .......
     .......
     .......
        
</ListView>

接著在 MyFirstPage.xaml.cs 裡加入如下的事件處理常式實作:

namespace Demae.App.Views
{
    public partial class MyFirstPage : ContentPage
    {
        ....
        ....
        ....

        private void OnItemTapped(object sender, ItemTappedEventArgs e)
        {
            ((MyFirstPageViewModel)BindingContext).AddressSelectedCommand.Execute((AddressModel)e.Item);
        }
    }
}

請回憶一下,在 Prism MVVM Framework 裡 View 與 ViewModel 會自動繫結(Binding)所以頁面的繫結內容(BindingContext) 是 MyFirstPageViewModel 因此將 BindingContext 轉型MyFirstPageViewModel 是合適的 。 而項目所包含的資料型別為 AddressModel 因此將其轉型傳遞給實作在 MyFirstPageViewModel 的 AddressSelectedCommand 執行。

註:
目前這個在後置(Code Behind)中直接繫結事件處理常式的作法,在 MVVM Pattern 中,認為耦合度過高,應該是要使用 EventToCommandBehavior 將程式碼寫在 ViewModel 內,目前為了方便解說暫時這麼做,等過段時間再來學習比較正確(理想?)的作法。

實作命令

接著在 MyFirstPageViewModel.cs 加入 AddressSelectedCommand 的實作,如下所示:

using Demae.App.Models;
using Demae.App.Services;
using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using System.Collections.ObjectModel;

namespace Demae.App.ViewModels
{
    public class MyFirstPageViewModel : BindableBase, INavigationAware
    {
        ......
        ......

        private INavigationService _navigationService;

        public MyFirstPageViewModel(IAddressService apiService,INavigationService navigationService)
        {
            _apiService = apiService;
            _navigationService = navigationService;
        }

        private DelegateCommand<AddressModel> _addressSelectedCommand;        

        public DelegateCommand<AddressModel> AddressSelectedCommand =>
            _addressSelectedCommand ?? (_addressSelectedCommand = new DelegateCommand<AddressModel>(AddressSelected));

        private void AddressSelected(AddressModel obj)
        {
            var p = new NavigationParameters
            {
                {"addressModel", obj }
            };
            _navigationService.NavigateAsync("GoogleMapPage", p);
        }

        
        .....
        .....
        .....
        
    }
}

比較值的留意是如下的 NavigationParameters 可組出如 {"addressModel", obj } 的 { 參數名稱, 傳遞物件} 將本例的 AddressModel 傳遞到命名為 GoogleMapPage 的頁面:

var p = new NavigationParameters
{
    {"addressModel", obj }
};

實作 GoogleMapPage 

接著再新增一個命名為 GoogleMapPage 的 View 並在 ContentPage 內加入用來嵌入網頁瀏覽器的 WebView 元件,並將 WebView 的來源(也就是網址)繫結到對應 ViewModel 的 MapUrl 屬性,而 ContentPage 的 Title 繫結到 AddressModel.Address 屬性:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="Demae.App.Views.GoogleMapPage"
             Title="{Binding AddressModel.Address}">
    <WebView Source="{Binding MapUrl}" />
</ContentPage>

實作 GooleMapPageViewModel 內的對應屬性

接者在 GoogleMapPageViewModel 內新增兩個屬性,並於 OnNavigatingTo() 事件處理常式中,由傳遞過來的參數設定這兩個屬性值:

using Demae.App.Models;
using Prism.Mvvm;
using Prism.Navigation;

namespace Demae.App.ViewModels
{
    public class GoogleMapPageViewModel : BindableBase, INavigationAware
    {
        private AddressModel _addressModel;
        public AddressModel AddressModel
        {
            get { return _addressModel; }
            set { SetProperty(ref _addressModel, value); }
        }
        private string _mapUrl;
        public string MapUrl
        {
            get { return _mapUrl; }
            set { SetProperty(ref _mapUrl, value); }
        }

        ......
        ......

        public void OnNavigatingTo(NavigationParameters parameters)
        {
            if (parameters.ContainsKey("addressModel"))
            {
                AddressModel = (AddressModel)parameters["addressModel"];
                MapUrl = string.Format("https://www.google.com.tw/maps/place/{0}", AddressModel.Address);
            }

        }
    }
}

OK!今天就學習到這裡,明天再繼續吧。