接續上一小節,單純想獨立出來以方便資料查找與引用~
前一小節的兩支範例可以發現兩個問題:
- OnPropertyChanged 使用 弱型別 當作參數 (程式常常死在這種地方,打錯一個字,幹)
- set 有太多重複的程式碼,每次都要寫判斷值是否相同這件事
解決弱型別
一般來說寫 OnPropertyChanged 時會像前一小節這樣的寫法
public double Number { set { if (number != value) { number = value; OnPropertyChanged("Number"); } } get { return number; } }
這種寫法有個很大的問題,就是當代入的參數打錯字時 (弱型別),程式不會產生例外錯誤,但就是無法正常運作。
這個問題可以藉由 C# 5.0 一個特殊的方法來解決,CallerMemberNameAttribute,可以讓你在參數取得 "呼叫此方法的屬性名稱"。
protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } }
用法如下,就不用自行帶入弱型別的字串了:
public double Number { set { if (number != value) { number = value; OnPropertyChanged(); // Do something with the new value. } } get { return number; } }
接著為了讓我們的程式更精簡,
由於每個 set 都有重複的商業邏輯,不如直接寫一支泛型方法來使用,
這裡將 判斷新舊值是否更改 這件事改成泛型方法,並結合上方寫好的新 OnPropertyChanged:
bool SetProperty(ref T storage, T value, [CallerMemberName] string propertyName = null) { if (Object.Equals(storage, value)) return false; storage = value; OnPropertyChanged(propertyName); return true; } protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } }
用法如下:
public double Number { set { if (SetProperty(ref number, value)) { // Do something with the new value. } } } get { return number; } }
如果沒有其他邏輯可以更縮減:
public double Number { set { SetProperty(ref number, value); } get { return number; } }
最後,乾脆將這兩個方法寫在 ViewModelBase 類別內,以便 ViewModel 繼承使用:
namespace Xamarin.FormsBook.Toolkit { public class ViewModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected bool SetProperty< T>(ref T storage, T value, [CallerMemberName] string propertyName = null) { if (Object.Equals(storage, value)) return false; storage = value; OnPropertyChanged(propertyName); return true; } protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } } }
作者已經移除這則留言。
回覆刪除羅根大你好
回覆刪除我在https://forums.xamarin.com/discussion/comment/371718#Comment_371718
有發問,Picker相關的問題,同樣是用mvvm的架構,其中有一個citypicker可以binding到viewmodel的資料
但是regionpicker則binding不到,想請教是為什麼呢?
綁定的觀念不對,程式碼內 x:Reference 意思是將 RegionPicker 與 CityPicker 綁定,但是 CityPicker 並沒有 MyRegion 這個屬性
刪除所以思考要回到 ViewModel 內,我的 RegionPicker 的 ItemSource 應該要跟 ViewModel 內的某個屬性綁定,例如 MyRegion (注意這個屬性是能夠做雙向溝通的,意思就是當 MyRegion 更改時有辦法去通知 View),當我 CityPicker 的 SelectedItem 做更改時,同時去更改 MyRegion
刪除完整 ViewModel
刪除class CityViewModel : ViewModelBase
{
private City _selectedCity;
public City SelectedCity
{
get { return _selectedCity; }
set
{
if (_selectedCity != value)
{
_selectedCity = value;
MyRegion = CitiesList
.Where(x => x.Name == _selectedCity.Name).SelectMany(s => s.Regions).ToList();
}
}
}
List _myregion;
public List MyRegion
{
set { SetProperty(ref _myregion, value); }
get { return _myregion; }
}
public List CitiesList
{
get
{
return new List()
{
new City() { Key = 1, Name = "Keelung", Regions = { "CenterK", "EastK", "NorthK", "WestK", "SouthK" } },
new City() { Key = 2, Name = "Hsinchu", Regions = { "HEast", "HNorth", "HCenter" } },
new City() { Key = 3, Name = "Chiayi", Regions = { "CEast", "CWest" } }
};
}
}
}
View
刪除Picker x:Name="CityPicker"
Title= "City"
ItemsSource="{Binding CitiesList}"
ItemDisplayBinding="{Binding Name}"
SelectedItem="{Binding SelectedCity}"
Picker x:Name="RegionPicker"
Title="Region"
ItemsSource="{Binding MyRegion}"
ItemDisplayBinding="{Binding .}"
感謝您的回覆,我會試試看。
刪除「CityPicker 的 SelectedItem 做更改時,同時去更改 MyRegion」我在程式碼使用foreach的方式將Regions = { "CenterK", "EastK", "NorthK", "WestK", "SouthK" }填入_myregion,在debug model中是可以看到public List MyRegion的值的確被正確填入,而在xaml的RegionPicker的ItemSource是Binding MyRegion,而ItemDisplayBinding是Binding RegionName(有宣告在MyRegion的屬性),我最不解的是,『在debugmodel底下,可以看到MyRegion的值,在xaml也宣告的沒問題,為何會Binding不到,有可能是被GC了嘛?
刪除羅根你好,我再次去詳讀了關於Picker的xamarin文件,在第二個段落的第一句「Xamarin.Forms 2.3.4,填入的程序之前 Picker 的資料「已加入的資料顯示為唯讀 Items 集合」,其中的型別IList. 集合中的每個項目必須是型別string。 」Prior to Xamarin.Forms 2.3.4, the process for populating a Picker with data was to add the data to be displayed to the 「read-only」 Items collection, which is of type IList.
刪除我解讀成Picker只能夠Binding readonly的資料。在我的行為中,MyRegion並不是readonly而是會跟著selcetitem改變的資料,因此無法藉由Picker完成,也許要用listView來取代RegionPicker才能夠完成。
有試上面我給的程式碼嗎? 我實際執行後是沒問題的...
刪除羅根大我真的感到很不好意思!!!,先跟你說聲抱歉。
刪除我看了你的程式碼,發現一個我之前沒想過得語法
ItemsSource="{Binding MyRegion}"
ItemDisplayBinding="{Binding .}"
就是ItemDisplayBinding裡面的「.」,我程式碼都沒更改只是在
xaml的RegionPicker的Binding改成
Picker x:Name="RegionPicker"
Title="鄉鎮區選擇"
ItemsSource="{Binding SelectedCity.Regions}"
ItemDisplayBinding="{Binding .}"/>
在之前我宣告一個model為
public class City
{
public int Key { get; set; }
public string Name { get; set; }
public List Regions { get; set; }
public City()
{
Regions = new List();
}
}
問題解決了,非常感謝你。你的回覆是我思考的一個正確的方向!!
有試過羅大的程式碼,但是MyRegion依然是binding不到資料
刪除羅根大你好,你的程式碼也是可以用的,但我之前試的時候,可能有多宣告一些model導致
刪除無法binding,在此跟你通知。在一次的感謝您的回覆及建議
作者已經移除這則留言。
回覆刪除