序言
借助WPF/Sliverlight強大的數據綁定功能,可以比實現比MFC,WinForm更加優雅輕松的數據綁定。但是在使用WPF/Silverlight綁定時,有件事情是很苦惱的:當ViewModel對象放生改變,需要通知UI。我們可以讓VM對象實現INotifyPropertyChanged接口,通過事件來通知UI。但問題就出現這里……
一,描述問題
情形:現在需要將一個Person對象的Name熟悉雙向綁定到UI中的TextBox,的確這是一件很簡單的事情,但還是描述下:
XAML:
<TextBox Text="{Binding Name,Mode=TwoWay}"/>
C# Code:
public class Person : INotifyPropertyChanged { private string m_Name; public string Name { get { return m_Name; } set { if (m_Name == value) return; m_Name = value; this.Notify("Name"); } } public Person() { this.m_Name = "墨梅,在這里......"; } public event PropertyChangedEventHandler PropertyChanged; public void Notify(string propertyName) { PropertyChangedEventHandler handler = this.PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } }
是的,這就可以實現了。但是這里一個問題困惑我,曾經就在this.Notify("Name"),將參數寫錯,UI遲遲得不到響應。這個錯誤很難發現!!!也很難跟蹤,但是這個細微的錯誤可以導致一個很嚴重的運行時錯誤。這的確是一件很苦惱的事情。
二解決問題
方法一:添加驗證
public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { this.VerifyPropertyName(propertyName); PropertyChangedEventHandler handler = this.PropertyChanged; if (handler != null) { var e = new PropertyChangedEventArgs(propertyName); handler(this, e); } } [Conditional("DEBUG")] [DebuggerStepThrough] public void VerifyPropertyName(string propertyName) { // Verify that the property name matches a real, // public, instance property on this object. if (TypeDescriptor.GetProperties(this)[propertyName] == null) { string msg = "Invalid property name: " + propertyName; if (this.ThrowOnInvalidPropertyName) throw new Exception(msg); else Debug.Fail(msg); } }
這里對驗證事件參數使用條件編譯[Conditional(“DEBUG”)],在release版本中這個函數是不會調用的,比使用#if 等有更明顯有優勢。
這個方法雖然可以達到目的,但是還是那么的別扭,必須到運行時才能知道是否有錯誤,所以還是不怎么好。
方法二,使用Lambda表達式,靜態擴展語法
public static class NotificationExtensions { public static void Notify(this PropertyChangedEventHandler eventHandler, Expression<Func<object>> expression) { if( null == eventHandler ) { return; } var lambda = expression as LambdaExpression; MemberExpression memberExpression; if (lambda.Body is UnaryExpression) { var unaryExpression = lambda.Body as UnaryExpression; memberExpression = unaryExpression.Operand as MemberExpression; } else { memberExpression = lambda.Body as MemberExpression; } var constantExpression = memberExpression.Expression as ConstantExpression; var propertyInfo = memberExpression.Member as PropertyInfo; foreach (var del in eventHandler.GetInvocationList()) { del.DynamicInvoke(new object[] {constantExpression.Value, new PropertyChangedEventArgs(propertyInfo.Name)}); } } }
這里用使用的靜態擴展語法,我還是比較喜歡這個的,但是並不是所有人都喜歡哦。如何使用呢:
public class Employee : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private string _firstName; public string FirstName { get { return this._firstName; } set { this._firstName = value; this.PropertyChanged.Notify(()=>this.FirstName); } } }
這里還可以添加一個很實用的擴展:
public static void SubscribeToChange<T>(this T objectThatNotifies, Expression<Func<object>> expression, PropertyChangedEventHandler<T> handler) where T : INotifyPropertyChanged { objectThatNotifies.PropertyChanged += (s, e) => { var lambda = expression as LambdaExpression; MemberExpression memberExpression; if (lambda.Body is UnaryExpression) { var unaryExpression = lambda.Body as UnaryExpression; memberExpression = unaryExpression.Operand as MemberExpression; } else { memberExpression = lambda.Body as MemberExpression; } var propertyInfo = memberExpression.Member as PropertyInfo; if(e.PropertyName.Equals(propertyInfo.Name)) { handler(objectThatNotifies); } }; }
通過上面的代碼,可以訂閱熟悉改變事件,如:
myObject.SubscripeToChange(()=>myObject.SomeProperty,SomeProperty_Changed); And then your handler would look like this: private void SomeProperty_Changed(MyObject myObject) { /* ... implement something here */ }
方法三,net4.5,框架提供的解決方法
private string m_myProperty; public string MyProperty { get { return m_myProperty; } set { m_myProperty = value; OnPropertyChanged(); } } private void OnPropertyChanged([CallerMemberName] string propertyName = "none passed") { // ... do stuff here ... }
屬性CallerMemberName的解決辦法和方法二是基本相同的,不同的是這個在net框架中解決的。更多信息可以查看CallerMemberName,net4.5還提供了
CallerFilePath,CallerLineNumber,這幾很有用的語法
方法四,這個也不錯哦
public static class SymbolExtensions { public static string GetPropertySymbol<T,R>(this T obj, Expression<Func<T,R>> expr) { return ((MemberExpression)expr.Body).Member.Name; } } public class ConversionOptions : INotifyPropertyChanged { private string _outputPath; public string OutputPath { get { return _outputPath;} set { _outputPath = value; OnPropertyChanged(o => o.OutputPath); } } private string _blogName; public string BlogName { get { return _blogName;} set { _blogName = value; OnPropertyChanged(o => o.BlogName); } } private string _secretWord; public string SecretWord { get { return _secretWord; } set { _secretWord = value; OnPropertyChanged(o => o.SecretWord); } } protected virtual void OnPropertyChanged<R>(Expression<Func<ConversionOptions, R>> propertyExpr) { OnPropertyChanged(this.GetPropertySymbol(propertyExpr)); } protected virtual void OnPropertyChanged(string propertyName) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } public event PropertyChangedEventHandler PropertyChanged; }
注釋:這里還有更多參考信息,您可以在這里了解更加清楚: