第十八章:MVVM(六)

簡化ViewModel
INotifyPropertyChanged的典型實現爲類定義的每個公共屬性都有一個私有支持字段,例如:

double number;

它還有一個OnPropertyChanged方法,負責觸發PropertyChanged事件:

protected void OnPropertyChanged(string propertyName)
{
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

典型的屬性定義如下所示:

public double Number
{
    set
    {
        if (number != value)
        {
            number = value;
            OnPropertyChanged("Number");
            // Do something with the new value.
        }
    }
    get
    {
        return number;
    }
} 

潛在問題涉及傳遞給OnPropertyChanged方法的文本字符串。 如果拼錯它,您將不會收到任何類型的錯誤消息,但涉及該屬性的綁定將不起作用。 此外,支持字段在此單個屬性中出現三次。 如果您有幾個類似的屬性並通過複製和粘貼操作定義它們,則可以省略重新命名後備字段的三個外觀之一,並且該錯誤可能很難追蹤。
您可以使用C#5.0中引入的功能解決第一個問題。 CallerMemberNameAttribute類允許您使用調用方法或屬性的名稱替換可選方法參數。
您可以通過重新定義OnPropertyChanged方法來使用此功能。 通過爲它賦值null並在其前面加上方括號中的CallerMemberName屬性,使參數可選。 您還需要System.Runtime.CompilerServices的using指令:

protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

現在,Number屬性可以調用OnPropertyChanged方法,而不使用指示屬性名稱的參數。 該參數將自動設置爲屬性名稱“Number”,因爲這是對OnPropertyChanged的調用所在的位置:

public double Number
{
    set
    {
        if (number != value)
        {
            number = value;
            OnPropertyChanged();
            // Do something with the new value.
        }
    }
    get
    {
        return number;
    } 
} 

此方法避免了拼寫錯誤的文本屬性名稱,並且還允許在程序開發期間更改屬性名稱,而無需擔心還會更改文本字符串。 實際上,發明CallerMemberName屬性的主要原因之一是簡化實現INotifyPropertyChanged的類。
但是,僅當從值正在更改的屬性調用OnPropertyChanged時,此方法纔有效。 在早期的ColorViewModel中,除了對OnPropertyChanged的一個調用之外,在所有調用中仍然需要顯式屬性名稱。
可以更進一步簡化set訪問器邏輯:您需要定義一個通用方法,可能名爲SetProperty或類似的東西。 此SetProperty方法也使用CallerMemberName屬性定義:

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

SetProperty的第一個參數是對支持字段的引用,第二個參數是設置爲該屬性的值。 SetProperty自動檢查和設置支持字段。 請注意,它在調用OnPropertyChanged時顯式包含propertyName參數。 (否則propertyName參數將成爲字符串“SetProperty”!)如果屬性已更改,則該方法返回true。 您可以使用此返回值來使用新值執行其他處理。
現在Number屬性如下所示:

public double Number
{
    set
    {
        if (SetProperty(ref number, value))
        {
            // Do something with the new value.
        }
    }
    get
    {
        return number;
    }
} 

雖然SetProperty是一個通用方法,但C#編譯器可以從參數中推斷出類型。 如果您不需要對屬性集訪問器中的新值執行任何操作,則甚至可以將兩個訪問器減少爲單行而不會模糊操作:

public double Number
{
    set { SetProperty(ref number, value); }
    get { return number; }
}

您可能希望這樣簡化以至於您希望將SetProperty和OnPropertyChanged方法放在它們自己的類中,並在創建自己的ViewModel時從該類派生。 這樣一個名爲ViewModelBase的類已經存在於Xamarin.FormsBook.Toolkit庫中:

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
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));
            }
        }
    }
}

本章將在本章的其餘兩個示例中使用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章