16.监听INotifyPropertyChanged接口
虽然实现INotifyPropertyChanged
接口可能很容易,但是要监视实现该接口的对象的PropertyChanged
通知却常常有点棘手 - 您需要注册事件处理程序,检查属性名称以确定它是否是您所期望的属性,并在完成后取消注册事件处理程序。
INotifyPropertyChanged.Bind
这是订阅 PropertyChanged 事件的最简单方法,它使用对订阅者的强引用(与普通事件一样)。这意味着如果您打算在您订阅的内容仍然存在时被释放,您必须记得取消订阅。
用法非常简单。假设有一个形式的对象:
// 可以是INotifyPropertyChanged的任何实现-我使用PropertyChangedBase,因为它使示例更短
class Model : PropertyChangedBase
{
private string _stringProperty;
public string StringProperty
{
get {
return this._stringProperty; }
set {
SetAndNotify(ref this._stringProperty, value); }
}
}
并且您希望每次StringProperty
属性更改时都收到通知。你这样做是这样的:
var model = new Model();
// ...
model.Bind(x => x.StringProperty, (sender, eventArgs) => Debug.WriteLine(String.Format("New value for property {0} on {1} is {2}", eventArgs.PropertyName, sender, eventArgs.NewValue)));
x => x.StringProperty
部分是以类型安全的方式指定要观察的属性的一种方式。x可以是您喜欢的任何名称,当您输入x => x.
时,Intellisense将提示您属性列表。
(propertyName, newValue) => Debug.WriteLine(String.Format("New value is {0}", newValue))
部分是每次更改该属性时都会调用的内容。
Bind
方法实际上返回了IEventBinding
的实现,该实现只有一个Unbind
方法。要删除绑定,请调用该方法。示例:
var model = new Model();
// ...
var binding = model.Bind(x => x.StringProperty, (sender, eventArgs) => Debug.WriteLine(String.Format("New value for property {0} on {1} is {2}", eventArgs.PropertyName, sender, eventArgs.NewValue)));
// ...
binding.Unbind();
INotifyPropertyChanged.BindWeak
通常,当您订阅一个事件时,接收事件通知的事物将至少与发布事件的事物存在一样长的时间,因为发布事件的事物最终会引用接收事件通知的事物。
这通常是不希望的。例如,如果您有一个 ViewModel 想要监视它所依赖的某些服务上的 PropertyChanged 事件。
Stylet 在 INotifyPropertyChanged 上提供了一个名为 的扩展方法BindWeak
,它与 非常相似Bind
,只是它创建了一个弱绑定。语法同上Bind
,这里不再赘述。
请注意,不可能以弱方式绑定每个委托。捕获局部变量的委托通常会失败。这将在下面更详细地讨论。
技术:弱事件订阅
我将掩盖委托的一些细节,但在基本术语中,当您订阅一个事件时,您创建了一个新的委托实例,并将其传递给拥有该事件的事物。委托(基本上)由两部分组成:调用(属性)的方法Method
和调用(属性)方法的实例Target
。
如果您创建一个指向您类上的实例方法的委托,那么一切都很好且简单:
class MyClass
{
public MyClass(Model model)
{
model.PropertyChanged += new PropertyChangedEventHandler(this.PropertyChangedHandler);
// or, more concisely
model.PropertyChanged += this.PropertyChangedHandler;
{
public void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
{
// ...
}
}
在这种情况下,将创建一个新委托,并将其Target
设置为MyClass
实例,并将其Method
设置为MethodInfo
代表您的PropertyChangedHandler
方法。
然后该model
实例成为该委托的所有者。这意味着该model
实例拥有一个拥有该MyClass
实例的委托,这意味着该MyClass
实例不能被释放,直到该model
实例被释放。
当匿名委托/lambda 发挥作用时,事情开始变得有点复杂,例如:
class MyClass
{
public MyClass(Model model)
{
model.PropertyChanged += delegate {
Debug.WriteLine("Hi"); };
// 或者,使用lambas(首选)
model.PropertyChanged += (o, e) => Debug.WriteLine("Hi");
}
}
在这里,C# 编译器必须在您的类上创建一个新的特殊方法,如下所示:
class MyClass
{
public MyClass(Model model)
{
model.PropertyChanged += new PropertyChangedEventHandler(this.<.ctor>b__0);
}
[CompilerGenerated]
private void <.ctor>b__0(object sender, PropertyChangedEventArgs e)
{
Debug.WriteLine("Hi");
}
}
(请注意使用了一个“无法形容的”方法名称 - 一个包含字符 (<
和>
) 的字符在 C# 中无效,但在 CLR 中有效)。
如果我们有一个捕获局部变量的匿名委托/lambda,这会变得更加复杂。在这里,C# 编译器需要生成一个全新的嵌入式类,它保留对该变量的引用。例如:
class MyClass
{
public MyClass(Model model)
{
string test = "test";
model.PropertyChanged += (o, e) => Debug.WriteLine(test);
}
}
被编译成看起来有点像的东西:
class MyClass
{
public MyClass(Model model)
{
MyClass.<>c__DisplayClass1 c__DisplayClass1 = new MyClass.<>c__DisplayClass1();
c__DisplayClass1.test = "test";
model.PropertyChanged += new PropertyChangedEventHandler(c__DisplayClass1.<.ctor>b__0);
}
[CompilerGenerated]
private sealed class <>c__DisplayClass1
{
public string test;
public void <.ctor>b__0(object sender, PropertyChangedEventArgs e)
{
Debug.WriteLine(this.test);
}
}
在这里,创建的PropertyChangedEventHandler
委托将具有<>c__DisplayClass
的实例作为其Target
属性的值。
这意味着,当MyClass
的构造函数返回时,唯一引用<>c__DisplayClass1
实例的是该委托。<>c__DisplayClass1
实例的生命周期现在完全独立于MyClass
实例。
实现弱事件的方式是通过以某种方式将委托的Target
属性设置为WeakReference
,通常是通过将其指向一个中间类,该中间类又具有对“真实”目标的WeakReference
。这意味着委托不会保留目标。
如果这个目标是由编译器生成的内部类,那么除了我们创建的WeakReference
之外,没有其他东西会保留对它的引用。这意味着这个内部类会立即被回收,因此委托永远不会被调用。
因此,如果传递给BindWeak
的委托具有CompilerGenerated
属性的Target
,则会抛出异常。
项目原地址:https://github.com/canton7/Stylet
当前文档原地址:https://github.com/canton7/Stylet/wiki/The-ViewManager
上一篇:WPF的MVVM框架Stylet开发文档 15. 视图管理器 The ViewManager
下一篇:WPF的MVVM框架Stylet开发文档 17.设计模式支持