WPF的MVVM框架Stylet开发文档 16.监听INotifyPropertyChanged接口

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.设计模式支持

猜你喜欢

转载自blog.csdn.net/qq_39427511/article/details/130280765
今日推荐