2012-11-17 62 views
3

到目前爲止,我的模型實現了INotifyPropertyChanged,每個屬性都引發了這個事件。幾乎所有ViewModel都通過PropertyChangedEventHandler來收聽這些更改。我應該創建大量的PropertyChangedEventHandler還是測試PropertyChangedEventArgs?

問題在於,即使屬性更改對視圖不重要,也會爲模型中的每個更改調用此處理函數。

一個選項是檢查哪個屬性引發了該事件。但是,我不喜歡這個想法來測試PropertyName字符串。它要求,我已經避免與調用模型像PropertyChanged.Notify(()=> PropertyName)

我看到的第二個選項是屬性名的硬編碼來實現我的所有屬性單一事件:

public event PropertyChangedEventHandler LayerChanged; 
public event PropertyChangedEventHandler FieldChanged; 
public event PropertyChangedEventHandler LinkDictionaryChanged; 

....

最佳做法是什麼?我寧願第二種選擇。

編輯:我嘗試更加具體

我的模型類的工作就像是:

public bool IsFeatureLayer 
     { 
      get { return _isFeatureLayer; } 
      set { PropertyChanged.ChangeAndNotify(ref _isFeatureLayer, value,() => IsFeatureLayer);} 
     } 

或者

PropertyChanged.Notify(() => LinkDictionary); 

所以,問題不是如何使電話通知更安全,因爲我已經使用擴展方法來做到這一點,而不需要屬性的字符串名稱。

問題是如何找出誰調用事件而不使用字符串。

void _MCDAExtensionPropertyChanged(object sender, PropertyChangedEventArgs e) 
     { 
      if(e.PropertyName.Equals("LinkDictionary"){ 
       //event handling 
      } 
     } 

這是完全不安全的,因爲我的模型中Property的名稱可能會改變,我必須在不同的地方修復它。

回答

2

如果我正確理解你的問題,你可以使用這樣的事情:

public static class PropertyChangedExtensions 
{ 
    public static void RegisterPropertyHandler<T, TProperty>(this T obj, Expression<Func<T, TProperty>> propertyExpression, PropertyChangedEventHandler handlerDelegate) 
     where T : class, INotifyPropertyChanged 
    { 
     if (obj == null) throw new ArgumentNullException("obj"); 

     var propertyName = GetPropertyName(propertyExpression); 

     obj.PropertyChanged += (sender, args) => 
      { 
       if (args.PropertyName == propertyName && handlerDelegate != null) 
        handlerDelegate(sender, args); 
      }; 
    } 

    public static void Notify<T>(this PropertyChangedEventHandler eventHandler, object sender, Expression<Func<T>> propertyExpression) 
    { 
     var handler = eventHandler; 
     if (handler != null) handler(sender, new PropertyChangedEventArgs(GetPropertyName(propertyExpression))); 
    } 

    private static string GetPropertyName(LambdaExpression propertyExpression) 
    { 
     var memberExpression = propertyExpression.Body as MemberExpression; 
     if (memberExpression == null) 
     { 
      var unaryExpression = propertyExpression.Body as UnaryExpression; 
      if (unaryExpression == null) 
       throw new ArgumentException("Expression must be a UnaryExpression.", "propertyExpression"); 

      memberExpression = unaryExpression.Operand as MemberExpression; 
     } 

     if (memberExpression == null) 
      throw new ArgumentException("Expression must be a MemberExpression.", "propertyExpression"); 

     var propertyInfo = memberExpression.Member as PropertyInfo; 
     if (propertyInfo == null) 
      throw new ArgumentException("Expression must be a Property.", "propertyExpression"); 

     return propertyInfo.Name; 
    } 
} 

RegisterPropertyHandler方法允許你註冊一個特定屬性的處理程序,而無需使用「魔術字符串」。您可以使用這樣的:

public class PersonViewModel : INotifyPropertyChanged 
{ 
    public PersonViewModel() 
    { 
     Address = new AddressViewModel(); 
     Address.RegisterPropertyHandler(a => a.ZipCode, ZipCodeChanged); 
    } 

    private AddressViewModel _address; 

    public AddressViewModel Address 
    { 
     get { return _address; } 
     set 
     { 
      _address = value; 
      PropertyChanged.Notify(this,() => Address); 
     } 
    } 

    private static void ZipCodeChanged(object sender, PropertyChangedEventArgs args) 
    { 
     // This will only be called when the 'ZipCode' property of 'Address' changes. 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 
} 

public class AddressViewModel : INotifyPropertyChanged 
{ 
    private string _zipCode; 

    public string ZipCode 
    { 
     get 
     { 
      return _zipCode; 
     } 
     set 
     { 
      _zipCode = value; 
      PropertyChanged.Notify(this,() => ZipCode); 
     } 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 
} 

我看你已經有一個Notify擴展方法,所以你只需要添加RegisterPropertyHandler。至少這是一個開始:)

+0

我已經在使用這個。問題在於如何區分一旦事件發生後可能發生變化的房產。 – steffan

+0

這正是'RegisterPropertyHandler'方法正在做的事情,通過檢查事件的'PropertyName'。 – khellang

+0

你是對的!對不起,我沒有仔細研究過你的例子。然而,它的功能就像一個魅力。 – steffan

1

只是爲了你的項目擴展方法是這樣的:

public static string GetPropertyName<TObj,TRet>(this TObj obj, Expression<Func<TObj,TRet>> expression) 
    { 
     MemberExpression body = GetMemberExpression(expression); 
     return body.Member.Name; 
    } 

這樣,您將擁有的屬性名稱,字符串中的小犧牲性能屬性名編譯檢查。有了這個,你可以撥打:

PropertyChanged.Notify(this.GetPropetyName(t=>t.PropertyName))

這是不理想,但不附帶任何條件,很難實現這一目標。

+1

我已經這樣做了:PropertyChanged.Notify((=)=> LinkDictionary);問題是如何在偵聽器端執行此操作:void _MCDAExtensionPropertyChanged(object sender,PropertyChangedEventArgs e) if(e.PropertyName.Equals(「MyPropertyName」) – steffan

+0

什麼是LinkDictionary正是某種字符串常量?從我的解決方案中得到的想法是,你沒有將字符串放在整個地方,你用擴展方法從實例中獲取字符串,在你的監聽器上你必須有監聽器的實例,所以添加它_MCDAExtensionPropertyChanged(object sender,PropertyChangedEventArgs e){if(e.PropertyName.Equals(listeningObj => listeningObj.GetPropertyName(l => l.MyPropertyName))''''''你的屬性'MyPropertyName'在偵聽器端 –

3

如果您的目標是.NET 4.5,則使用新的CallerMemberName屬性可以更輕鬆,更安全地實施INotifyPropertyChanged

簡而言之,CallerMemberName屬性允許您獲取調用成員的名稱作爲方法參數。這樣,你可以有這樣的事情:

private string name; 
public string Name 
{ 
    get { return name; } 
    set { SetProperty(ref name, value); } 
} 

private void SetProperty<T>(ref T field, T value, [CallerMemberName] string callerMemberName = "") 
{ 
    // callerMemberName = "Name" (the property that called it). 

    // Set the field value and raise PropertyChanged event. 
} 

你可以看到如何使用它的一個例子here

至於要選擇哪個選項 - 我相信執行時間方面的差異可以忽略不計,而不是編碼開銷和代碼混亂(無論是在代碼本身還是intellisense中),因爲您有額外的事件爲每個屬性。我肯定會選擇第一個選項。

編輯:

不幸的是,處理時PropertyChanged事件,你只能對PropertyName串測試,有沒有辦法讓的方式,字符串保持一致,即使屬性名稱更改。對於依賴項屬性,您有MyDependencyProperty.Name,但這不適用於常規屬性。

最終,您的選擇是爲每個屬性使用不同的事件,或者在定義包含屬性名稱的屬性的類中定義一個常量,希望在/如果更改了/屬性名稱。假設你沒有很多實現INotifyPropertyChanged的類,你自己附加一個處理程序,那麼爲這些特定類中的每個屬性設置一個事件並不會那麼糟糕。

+0

我正在做類似的事情。找出誰引用了這個事件,看看我更新的問題。 – steffan

+0

@ user1168261看到我的更新回答。 –

+0

它可能...看看khellang的答案。 – steffan

相關問題