使用 Castal DynamicProxy 簡化 Silverlight 數據綁定
大家都知道, 在使用 Silverlight 數據綁定的時候, 為了使源對象的更改能夠傳播到目標,源必須實現 INotifyPropertyChanged 接口。INotifyPropertyChanged 具有 PropertyChanged 事件,該事件通知綁定引擎源已更改,以便綁定引擎可以更新目標值。
下面是一個典型的例子:
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 接口,代碼如下:
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 事件, 攔截器代碼如下:
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 的代碼如下:
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 最終的代碼如下:
public class UserModel : BaseModel {
public virtual string FirstName {
get;
set;
}
public virtual string LastName {
get;
set;
}
}
最后,使用 UserModel 的代碼是這樣的:
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 , 圍觀地址: https://beginor.github.io/
浙公網安備 33010602011771號