序言
借助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);elseDebug.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;}
注释:这里还有更多参考信息,您可以在这里了解更加清楚: