連續一個多月都在學習由《前端》到《後端》有整體關聯的各項實作,在切換到另一項主題:《使用者驗證與授權》之前,或許切出幾個獨立小專案的基本功系列,也算是複習先前所學的。
問題:為什麼在 ViewModel 裡的屬性宣告是這樣:
private string _title;
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
而不是像 Entity Model 的宣告:
public string Title {get; set;}
呢?
建立 Prism Xamarin.Forms 專案
阿源哥哥不直接說出答案,請讀者跟著文章說明動手實作一次,到最後聰明的讀者應該可以自行找到答案(找不到答案也沒關係,文章最後會公佈答案),當然已經知道答案的讀者也鼓勵你跟著實作一次,因為本篇文章主要也要用來說明 MVVM 的一個要點。
好吧!開始動手了,請新增一個 Prism Xamarin.Forms 專案:
視覺元件與資料繫結
接著請在原有的 MainPage.xaml 中加入兩個元件並繫結屬性和命令,加入後的程式碼如下所示:
<?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="PropertyChanged.Views.MainPage"
Title="MainPage">
<StackLayout HorizontalOptions="Center" VerticalOptions="Center">
<Label Text="{Binding Title}" />
<Entry Text="{Binding MyEntry}" Placeholder="請輸入通關密語"/>
<Button Text="{Binding ButtonText}" Command="{Binding GotoNextPageCommand}"/>
</StackLayout>
</ContentPage>
上述程式碼加入了:
- 一個 Entry 元件,其內文(Text)繫結對應 ViewModel 的 MyEntry 屬性。
- 一個 Button 元件,其按鈕文字(Text)繫結對應 ViewModel 的 ButtonText 屬性,而命令(Command)繫結 GotoNextPageCommand 命令。
接下來就是在對應 ViewModel 中實作被繫結的兩個屬性和命令,請回想一下命名規則 MainPage 的對應 ViewModel 是 MainPageViewModel 所以實作的程式碼要加在 MainPageViewModel.cs 中。
程式碼片段
在實作屬性和命令之前,先介紹 Prism Template Pack 所提供的兩個程式碼片段:
- propp
- cmd
善用程式碼片段(往後的文章也會介紹如何做自己的程式碼片段),可以結省很多打字的時間,至於程式碼片段如何使用,請看下列圖示說明:
propp 的用法:
打完 propp 後,連按兩次【tab】鍵,打完對應型別或名稱後,按一下【tab】鍵。順道一提,請留意一下 Field Name 與 Property Name 的命名原則,雖然該命名原則不一定要遵守,但是同一個團隊有相同的命名原則,將來程式交接時,一看名稱立刻就能了解含意,可省下許多解說的時間。
cmd 的用法:
加入 ViewModel 的屬性後,程式碼如下所示:
namespace PropertyChanged.ViewModels
{
public class MainPageViewModel : BindableBase, INavigationAware
{
private INavigationService _navigationService;
private string _title;
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
private string _myEntry;
public string MyEntry
{
get { return _myEntry; }
set { SetProperty(ref _myEntry, value); }
}
private string _buttonText;
public string ButtonText
{
get { return _buttonText; }
set { SetProperty(ref _buttonText, value); }
}
......
......
......
}
}
實作命令
加入 ViewModel 內的命令後,程式碼如下所示:
namespace PropertyChanged.ViewModels
{
public class MainPageViewModel : BindableBase, INavigationAware
{
.....
.....
.....
private DelegateCommand _gotoNextPageCommand;
public DelegateCommand GotoNextPageCommand =>
_gotoNextPageCommand ?? (_gotoNextPageCommand = new DelegateCommand(GotoNextPage, CanGotoNextPage).ObservesProperty(() => MyEntry));
private bool CanGotoNextPage()
{
if (MyEntry == "Keigen is a good man.")
{
ButtonText = "答對了,過關!";
return true;
}
return false;
}
private void GotoNextPage()
{
var p = new NavigationParameters
{
{ "password", MyEntry }
};
_navigationService.NavigateAsync("NextPage", p);
}
......
......
}
}
上述程式碼比較值得留意的是:new DelegateCommand(GotoNextPage, CanGotoNextPage)
其中的 GotoNextPage
指出當命令被執行時,實際上是去執行哪一個方法,以目前的案例,該方法是將頁面導到名稱為 NextPage 的頁面,並將 MyEntry 的內容,以名稱為 password 的參數帶到下一頁。
而 CanGotoNextPage 指出哪一個方法負責檢查該命令在何種情況下可被執行(按鈕可被按下),當回傳 true 時,可被執行,回傳 false 時,不可被執行。
另一個值得留意的是 .ObservesProperty(() => MyEntry)
指出 MyEntry 這個屬性值要被觀查,一有變化就要被處理。目前的處理方式是檢查該 MyEntry 的值是否為 Keigen is a good man. 如果是,改變按鈕文字,並回傳 true (也就是命令可被執行了)。
實作承接參數的頁面
接著在新增一個 Prism ContentPage 如下圖所示:
接著在新增的頁面加入視覺元件,並將 ContentPage 的 Title 繫結 ViewModel 的 Title 屬性,程式碼如下所示:
<?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="PropertyChanged.Views.NextPage"
Title="{Binding Title}">
<StackLayout
HorizontalOptions="Center"
VerticalOptions="Center">
<Label Text="天啊!你怎麼知道這個密秘。" />
</StackLayout>
</ContentPage>
接著在 ViewModel 加入屬性,以及在頁面導入的同時,接收參數,並指定給 Title 屬性。
using Prism.Mvvm;
using Prism.Navigation;
namespace PropertyChanged.ViewModels
{
public class NextPageViewModel : BindableBase, INavigatedAware
{
private string _title;
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
public NextPageViewModel()
{
}
public void OnNavigatedFrom(NavigationParameters parameters)
{
}
public void OnNavigatedTo(NavigationParameters parameters)
{
if (parameters.ContainsKey("password"))
{
Title = (string)parameters["password"];
}
}
}
}
執行程式,如果沒意外的話,程式的執行結果應該如下圖所示:
公佈答案
聰明的讀者應該想到了吧,因為 Prism MVVM 的 BindableBase 實作了 INotifyPropertyChanged 介面,所以當 MyEntry 內容一有變動(使用者輸入字串)就會隨時觸發 SetProperty() 方法,通知相關聯的元件(屬性或方法),應該要做出適當的對應,如果只是 public string MyEntry {get; set;}
就不會有通知功能了。
public class MainPageViewModel : BindableBase
{
private string _myEntry;
public string MyEntry
{
get { return _myEntry; }
set { SetProperty(ref _myEntry, value); }
}
}
還是有一個稱為 PropertyChanged.Fody 的套件,用一般的屬性宣告方式即可達到通知功能,但是阿源哥哥不喜歡,因為善用程式碼片段,也不會多打幾個字,也不會太麻煩。
學到了什麼
雖然這個範例有點搞笑,但是這個練習除了複習一下先前所學的,主要也是用來說明,使用者介面也有商業邏輯存在(或許不稱為商業邏輯,反正就是有邏輯啦),用來控制使用者的操作行為,比如說使用者在填寫表單時出貨日期不能早於訂貨日期,商品售價不能低於進貨價格(賠錢生意應該沒人要做吧)。範例中說明邏輯判斷要寫在哪裡(邏輯的內容是將來接案時與客戶談完後才知道的),以及如何觸發邏輯驗證。
好吧,今天就複習到這裡,明天再見了。