原文地址:http://www.cnblogs.com/xiwang/
序言
借助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;
}
注释:这里还有更多参考信息,您可以在这里了解更加清楚: