學習目標
- Messaging center 應用程式內部推播
- ViewModel ... 不熟的請回去看 18章
這一小節提到的兩個都是較特別的方法,
例如 ViewModel,我認為不太算正統的參數傳遞,而是一種利用 MVVM 達到我們目的的方法。
而 Messaging center 有點像是 App 內部推播,利用接收者(Subscribe) 與發送者(Send) 的關係來傳遞參數,其用法也相當特別~
Messaging Center
Messaging Center 有三個方法可用:
- Subscribe (接收者)
- Send (發送者)
- Unsubscribe (釋放接收者的資源)
你可以同時寫多個接收者(Subscribe),發送者(Send) 只要一發送訊息,所有 ID 一樣的接收者就能同時接到訊息。
行為就很像是推播,只是限於 App 內部使用。訊息的內容不限制任何型態的物件。
而接收者需要有 兩個條件 成立,才能接收到發送者送出的訊息:
- ID 一樣
- 傳送接收的參數型態一樣
//資料型態不一樣,即使 ID 一樣,接收者也收不到資料~
寫好接收者後,他會一直等待發送者發送訊息,那如果不會再接收訊息了,可以用 Unsubscribe 方法將資源釋放掉。
來看程式,一樣是 HomePage 和 InfoPage寫好接收者後,他會一直等待發送者發送訊息,那如果不會再接收訊息了,可以用 Unsubscribe 方法將資源釋放掉。
我們在 HomePage 點選 ListView 項目時,放了一個 發送者,去通知 InfoPage 的接收者,並更改畫面上的資料。
HomePage
public partial class DataTransfer2HomePage : ContentPage { var list = new ObservableCollection<Information>(); public DataTransfer2HomePage() { InitializeComponent(); listView.ItemsSource = list; // 等待 ID為"InformationReady" 的發送者送出訊息. MessagingCenter.Subscribe<Datatransfer2infopage,Information> (this, "InformationReady", (sender, info) => { int index = list.IndexOf(info); if (index != -1) { list[index] = info; } else { list.Add(info); } }); } async void OnGetInfoButtonClicked(object sender, EventArgs args) { await Navigation.PushAsync(new DataTransfer2InfoPage()); } // ListView 項目點選時 async void OnListViewItemSelected(object sender, SelectedItemChangedEventArgs args) { if (args.SelectedItem != null) { // Deselect the item. listView.SelectedItem = null; DataTransfer2InfoPage infoPage = new DataTransfer2InfoPage(); await Navigation.PushAsync(infoPage); // 發送訊息給 ID為"InitializeInfo"的接收者,並把 SelectedItem 物件傳遞出去 MessagingCenter.Send<Datatransfer2homepage, Information> (this, "InitializeInfo", (Information)args.SelectedItem); } } }
InfoPage
public partial class DataTransfer2InfoPage : ContentPage { Information info = new Information(); public DataTransfer2InfoPage() { InitializeComponent(); // 接收 "InitializeInfo" 發送的訊息. MessagingCenter.Subscribe<Datatransfer2homepage, Information> (this, "InitializeInfo", (sender, info) => { // 處理從 HomePage 傳過來的 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; // 釋放 Subscribe MessagingCenter.Unsubscribe<Datatransfer2homepage, Information> (this, "InitializeInfo"); }); } 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; // 發送訊息回 HomePage 的接收者,更新 ListView 資料 MessagingCenter.Send<Datatransfer2infopage, Information> (this, "InformationReady", info); } }
ViewModel
InfoPage 新增或編輯完,再通知 HomePage 的 ListView 做修改,這樣的特性本身就有改為 MVVM 的潛能...
這段範例比較特殊的是,新增時會先在 HomePage 的 ListView 內加一筆空的 info 物件,
當 InfoPage 輸入資料時,MVVM 的機制會去通知 HomePage 的那筆 info 物件做同步更新。
這邊提出一個問題,若使用者今天點新增後沒有做任何事情就返回上一頁,是否要再加入刪除的動作?
範例結合了兩個學過的技能,一個是 App 全域變數,另一個則是 MVVM,
既然要用 MVVM 就得先建立 ViewModel~
ViewModel
public class InformationViewModel : ViewModelBase { string name, email, language; DateTime date = DateTime.Today; public string Name { set { SetProperty(ref name, value); } get { return name; } } public string Email { set { SetProperty(ref email, value); } get { return email; } } public string Language { set { SetProperty(ref language, value); } get { return language; } } public DateTime Date { set { SetProperty(ref date, value); } get { return date; } } }
為了等等方便 Binding,再來建立一個 AppData 類別,裡面包含 DataSource 和 CurrentInfo:
public class AppData { public AppData() { InfoCollection = new ObservableCollection<Informationviewmodel>(); } public IList<Informationviewmodel> InfoCollection { private set; get; } public InformationViewModel CurrentInfo { set; get; } }
App
定義全域變數並初始化 AppData 物件
public class App : Application { public App() { // 確保有連結到 toolkit library. 頗特別的這裡 new Xamarin.FormsBook.Toolkit.ObjectToIndexConverter<object>(); // 初始化 AppData AppData = new AppData(); // Go to the home page. MainPage = new NavigationPage(new DataTransfer5HomePage()); } public AppData AppData { private set; get; } … }
HomePage
XAML:
直接綁定 Application.Current 的 AppData
<!--直接綁定 Application.Current 的 AppData--> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="DataTransfer5.DataTransfer5HomePage" Title="Home Page" BindingContext="{Binding Source={x:Static Application.Current},Path=AppData}"> <Grid> <Button Text="Add New Item" Grid.Row="0" FontSize="Large" HorizontalOptions="Center" VerticalOptions="Center" Clicked="OnGetInfoButtonClicked" /> <!--可以注意下 ItemSource--> <ListView x:Name="listView" Grid.Row="1" ItemsSource="{Binding InfoCollection}" ItemSelected="OnListViewItemSelected"> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <StackLayout Orientation="Horizontal"> <Label Text="{Binding Name}" /> <Label Text=" / " /> <Label Text="{Binding Email}" /> <Label Text=" / " /> <Label Text="{Binding Language}" /> <Label Text=" / " /> <Label Text="{Binding Date, StringFormat='{0:d}'}" /> </StackLayout> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </Grid> </ContentPage>
.cs
多了一個 GoToInfoPage 方法,執行新增或編輯並導向 InfoPage:
public partial class DataTransfer5HomePage : ContentPage
{
public DataTransfer5HomePage()
{
InitializeComponent();
}
// Button Clicked handler.
void OnGetInfoButtonClicked(object sender, EventArgs args)
{
// 執行新增並導向下一頁.
GoToInfoPage(new InformationViewModel(), true);
}
// ListView ItemSelected handler.
void OnListViewItemSelected(object sender, SelectedItemChangedEventArgs args)
{
if (args.SelectedItem != null)
{
listView.SelectedItem = null;
// 執行編輯並導向下一頁
GoToInfoPage((InformationViewModel)args.SelectedItem, false);
}
}
async void GoToInfoPage(InformationViewModel info, bool isNewItem)
{
// 從 BindingContext 取得 AppData
AppData appData = (AppData)BindingContext;
// 指派給 AppData.CurrentInfo
appData.CurrentInfo = info;
// 導向 InfoPage
await Navigation.PushAsync(new DataTransfer5InfoPage());
// 若是新增
if (isNewItem)
{
// 新增一筆空 item 到 ListView 內
appData.InfoCollection.Add(info);
}
}
}
要注意的是新增一筆 appData.InfoCollection.Add(info) 在 PushAsync 後面執行,這樣才不會下一頁還沒出現,ListView 就先多了一筆空項目...InfoPage
只要!!!!! 將 XAML 綁定好就結束了
<!--綁定 AppData.CurrentInfo--> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:toolkit= "clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit" x:Class="DataTransfer5.DataTransfer5InfoPage" Title="Info Page" BindingContext="{Binding Source={x:Static Application.Current}, Path=AppData.CurrentInfo}"> <StackLayout Padding="20, 0" Spacing="20"> <Entry Text="{Binding Name}" Placeholder="Enter Name" /> <Entry Text="{Binding Email}" 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.SelectedIndex> <Binding Path="Language"> <Binding.Converter> <toolkit:ObjectToIndexConverter x:TypeArguments="x:String"> <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> </toolkit:ObjectToIndexConverter> </Binding.Converter> </Binding> </Picker.SelectedIndex> </Picker> <DatePicker Date="{Binding Date}" /> </StackLayout> </ContentPage>
MVVM 雖然前置作業多了一些 (建立 ViewModel),但實際上在撰寫和維護時會相對的較便利,不用陷入事件處理和顯示資料的泥沼~
不熟的同學再回 18 章複習一下!!
沒有留言:
張貼留言
注意:只有此網誌的成員可以留言。