大家都知道, 在使用 Silverlight 数据绑定的时候, 为了使源对象的更改能够传播到目标,源必须实现 INotifyPropertyChanged 接口。INotifyPropertyChanged 具有 PropertyChanged 事件,该事件通知绑定引擎源已更改,以便绑定引擎可以更新目标值。
下面是一个典型的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
public
class
UserModel : INotifyPropertyChanged {
private
string
_firstName;
private
string
_lastName;
public
string
FirstName {
get
{
return
this
._firstName;
}
set
{
this
._firstName = value;
this
.NotifyPropertyChanged(
"FirstName"
);
}
}
public
string
LastName {
get
{
return
this
._lastName;
}
set
{
this
._lastName = value;
this
.NotifyPropertyChanged(
"LastName"
);
}
}
public
event
PropertyChangedEventHandler PropertyChanged;
protected
void
NotifyPropertyChanged(
string
propertyName) {
if
(
this
.PropertyChanged !=
null
) {
this
.PropertyChanged(
this
,
new
PropertyChangedEventArgs(propertyName));
}
}
}
|
在这个例子中,设置 FirstName 、LastName 时,需要手工激发 PropertyChanged 事件, 通知绑定引擎,因此, 如果数据源中属性比较多的时候, 是比较烦人的, 每个属性的 Setter 都需要激发一下 PropertyChanged 事件, 而且不能使用 C# 自带的自动属性特性。 当然, 可以自己设置一个代码段 snippet 来解决, 但是,重复的激发 PropertyChanged 事件的代码依然存在, 这不是我们的目标。
前段时间看到有人在抱怨 Silverlight 的数据绑定,说必须要实现 INotifyPropertyChanged 接口, 而且还要手工调用 NotifyPropertyChanged 事件等等, 我想说的是, 借助 Castal DynamicProxy 提供的拦截技术,可以把手工调用 NotifyPropertyChanged 事件的代码省掉。
INotifyPropertyChanged 接口是 Silverlight 数据绑定必须的, 这一点我们无法改变。因此需要先创建一个 BaseModel , 并让其实现 INotifyPropertyChanged 接口,代码如下:
1
2
3
4
5
6
7
8
9
10
|
public
class
BaseModel : INotifyPropertyChanged {
public
event
PropertyChangedEventHandler PropertyChanged;
public
void
NotifyPropertyChanged(
string
propertyName) {
if
(
this
.PropertyChanged !=
null
) {
this
.PropertyChanged(
this
,
new
PropertyChangedEventArgs(propertyName));
}
}
}
|
接下来为 BaseModel 写一个拦截器, 让所有继承自 BaseModel 的类在设置属性之后自动激发 NotifyPropertyChanged 事件, 拦截器代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public
class
NotifyPropertyChangedInterceptor : StandardInterceptor {
protected
override
void
PostProceed(IInvocation invocation) {
base
.PostProceed(invocation);
var
methodName = invocation.Method.Name;
// 这里可能不是很完善, 属性的 Setter 一般都是以 set_ 开头的,
// 应该有更好的判断方法。
if
(methodName.StartsWith(
"set_"
)) {
var
propertyName = methodName.Substring(4);
var
target = invocation.Proxy
as
BaseModel;
if
(target !=
null
) {
target.NotifyPropertyChanged(propertyName);
}
}
}
}
|
拦截器的代码很简单, 而且是可以扩展的, 相信都能看懂, 我们还需要一个 ModelHelper , 来方便的创建 Proxy , ModelHelper 的代码如下:
1
2
3
4
5
6
7
8
9
|
public
static
class
ModelHelper {
private
static
readonly
ProxyGenerator ProxyGenerator =
new
ProxyGenerator();
private
static
readonly
NotifyPropertyChangedInterceptor Interceptor =
new
NotifyPropertyChangedInterceptor();
public
static
T CreateProxy≶T>(T obj)
where
T :
class
, INotifyPropertyChanged {
return
ProxyGenerator.CreateClassProxyWithTarget(obj, Interceptor);
}
}
|
有了 ModelHelper , 可以说是万事俱备了, 我们来重写上边的 UserModel , UserModel 最终的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
public
class
UserModel : BaseModel {
public
virtual
string
FirstName {
get
;
set
;
}
public
virtual
string
LastName {
get
;
set
;
}
}
|
最后,使用 UserModel 的代码是这样的:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public
partial
class
MainPage : UserControl {
public
MainPage() {
InitializeComponent();
// 不能直接使用 UserModel, 要通过 ModelHelper 创建一个 Proxy 才行。
var
dataContext = ModelHelper.CreateProxy(
new
UserModel());
dataContext.FirstName =
"Zhang"
;
dataContext.LastName =
"ZhiMin"
;
this
.DataContext = dataContext;
}
}
|
我们不能改变环境, 但是可以改变自己, 因此,我们应该多一些思考,少一些抱怨。
本文的内容虽然是针对 Silverlight 数据绑定而写的, 对于 WPF 数据绑定也很适用。
张志敏所有文章遵循创作共用版权协议,要求署名、非商业 、保持一致。在满足创作共用版权协议的基础上可以转载,但请以超链接形式注明出处。
本博客已经迁移到 GitHub , 围观地址: http://beginor.github.io/