WPF 弱事件

因为在接触WPF的过程中追查INotifyPropertyChanged的通知原理的时候,发现了 PropertyChangedEventManager这个类,它是继承与WeakEventManager,也就是弱事件管理器,另外在学习MVVM的时候,其类库中也有关于弱引用弱事件方面的代码,然后我又非常的不熟悉,今天我打算深入了解下这个方面。

首先得了解下事件,事件在我的另外一篇里面已经详细讲了,这里就不重复了,但是事件其实有可能会导致内存泄露的,下面先看一个例子:

先申明一个Spy类,里面订阅按钮的Click事件:

 1  public class Spy {
 2         public void MonitorButton(Button button) {
 3             button.Click += new RoutedEventHandler(button_Click);
 4         }
 5
 6         private void button_Click(object sender, RoutedEventArgs e) {
 7             Button button = sender as Button;
 8             MessageBox.Show(string.Format("You have just clicked button {0}", button.Content),
 9                             "小报告", MessageBoxButton.OK, MessageBoxImage.Information);
10         }
11
12         private ArrayList _weapons = new ArrayList(1024 * 1024 * 100);
13     }

然后在main里面侦听4个按钮:

1            _strongSpy = new Spy();
2             _strongSpy.MonitorButton(_btnA);
3             _strongSpy.MonitorButton(_btnB);
4             _strongSpy.MonitorButton(_btnC);
5             _strongSpy.MonitorButton(_btnD);

然后在第五个按钮的Click事件里面把Spy类对象清理掉:

1 private void KillSpy(object sender, RoutedEventArgs e) {
2             _strongSpy = null;
3             GC.Collect();
4             GC.WaitForPendingFinalizers();
5
6             MessageBox.Show("任务完成");
7             UpdateTitle();
8             _btnKillSpy.IsEnabled = false;
9         }

销毁掉Spy类对象后,再去点击按钮,发现事件依然得到响应,为什么呢?就算_strongSpy销毁,但是因为在_strongSpy里面订阅了按钮的click事件,这种订阅是属于强引用的订阅,除非去除订阅关系。貌似这有点不好理解,下面我先谈谈一般我们事件的一个过程:

1)定义事件委托。

 2)申明事件。

 3)触发事件。

4)订阅事件。其实也就是侦听事件。

 针对这个过程我举个2个例子:

  第一个例子是我另外一篇里面的那个热水器的例子,定义事件委托BoiledEventHandler,可以在很多地方都可以,申明事件对象在热水器类里面,事件名为Boiled,然后在另外调用的类里面,订阅这个事件,也就是观察或者侦听这个热水器对象(观察对象),在热水的过程中,超过95度,就触发Boiled事件。事件源指的应该就是热水器这个对象,因为事件是在热水器这个对象里面,侦听者应该就是调用这个热水器对象的类。

  第二个例子是按钮的Click事件,Click事件的委托类型在某个地方定义,这不重要,然后Click事件申明在按钮控件类里面,那个这个按钮类就是Click事件的源,谁来侦听谁就来订阅这个事件,假设form1来侦听,我们一般在按钮上双击,VS自动帮我们做好了这一切,我们直接写事件处理方法即可。

我们再回到_strongSpy销毁了但是按钮事件却仍然得到响应的这个问题上来,到底为什么呢?我下断点,发现_strongSpy确实为null了,但是点击按钮的事件处理方法依然存在,但是这个方法又是在Spy这个类里面,这岂不矛盾?如果一直点击按钮,内存的占用量会一直增长下去,这是为什么呢?其实原理是这样的:如果通过+=的方式来订阅了事件,那么就隐式创建了一个对这个侦听者的强引用(还有弱引用的概念),任凭怎么GC,实际的侦听对象在事件源没有销毁之前是不会真正的回收的,就跟这个例子一样,真正的源头是button,如果按钮没有销毁,那么_strongSpy是不会真正回收的,特别是当一个类要是侦听多个其他对象的事件的时候,那情况就更糟糕了,因为其他对象如果没有销毁,销毁这个类对象其实是不会被真正回收的。那么如何解决这种问题呢?肯定是要通过-=的方式来去除侦听关系,通过弱引用,弱事件模式来加强GC的回收。WPF提供了一个接口和一个类来实现该模式,我们就先看看代码吧:

 1 namespace WeakEventDemo {
 2     /// <summary>
 3     /// 按钮Click事件的管理类,负责以弱事件模式来分发事件。
 4     ///
 5     /// 单态。
 6     /// </summary>
 7     public class ButtonClickManager : WeakEventManager {
 8         /// <summary>
 9         /// 供事件源调用,为管理的弱事件添加侦听者。
10         /// </summary>
11         /// <param name="source"></param>
12         /// <param name="listener"></param>
13         public static void AddListener(Button source, IWeakEventListener listener) {
14             CurrentManager.ProtectedAddListener(source, listener);
15         }
16
17         /// <summary>
18         /// 供事件源调用,为管理的弱事件移除侦听者。
19         /// </summary>
20         /// <param name="source"></param>
21         /// <param name="listener"></param>
22         public static void RemoveListener(Button source, IWeakEventListener listener) {
23             CurrentManager.ProtectedRemoveListener(source, listener);
24         }
25
26         /// <summary>
27         /// 挂接处理方法到事件源,并开始侦听。
28         /// </summary>
29         /// <param name="source"></param>
30         protected override void StartListening(object source) {
31             Button button = source as Button;
32             button.Click += new RoutedEventHandler(OnButtonClick);
33         }
34
35         /// <summary>
36         /// 终止对事件的侦听。
37         /// </summary>
38         /// <param name="source"></param>
39         protected override void StopListening(object source) {
40             Button button = source as Button;
41             button.Click -= new RoutedEventHandler(OnButtonClick);
42         }
43
44         /// <summary>
45         /// 事件处理方法,负责转发事件给侦听者。
46         /// </summary>
47         /// <param name="sender"></param>
48         /// <param name="e"></param>
49         private void OnButtonClick(object sender, RoutedEventArgs e) {
50             DeliverEvent(sender, e);
51         }
52
53         /// <summary>
54         /// 返回当前的管理类实例。
55         /// </summary>
56         /// <remarks>
57         /// 单态实现。
58         /// </remarks>
59         private static ButtonClickManager CurrentManager {
60             get {
61                 lock (obj) {
62                     Type managerType = typeof (ButtonClickManager);
63                     ButtonClickManager clickManager = GetCurrentManager(managerType) as ButtonClickManager;
64                     if (clickManager == null) {
65                         clickManager = new ButtonClickManager();
66                         SetCurrentManager(managerType, clickManager);
67                     }
68                     return clickManager;
69                 }
70             }
71         }
72
73         private static readonly object obj = new object();
74     }
75 }

 1 namespace WeakEventDemo {
 2     /// <summary>
 3     /// 应用弱事件模式的侦听类。
 4     /// </summary>
 5     public class WeakSpy : IWeakEventListener {
 6         /// <summary>
 7         /// 添加要侦听的button。
 8         /// </summary>
 9         /// <param name="button"></param>
10         public void MonitorButton(Button button) {
11             ButtonClickManager.AddListener(button, this);
12         }
13
14         /// <summary>
15         /// 打小报告^_^
16         /// </summary>
17         /// <param name="sender"></param>
18         /// <param name="e"></param>
19         private void button_Click(object sender, RoutedEventArgs e) {
20             Button button = sender as Button;
21             MessageBox.Show(string.Format("You have just clicked button {0}", button.Content),
22                             "小报告", MessageBoxButton.OK, MessageBoxImage.Information);
23         }
24
25         #region IWeakEventListener Members
26
27         /// <summary>
28         /// 接收事件。
29         /// </summary>
30         /// <param name="managerType"></param>
31         /// <param name="sender"></param>
32         /// <param name="e"></param>
33         /// <returns>如果事件得到处理,则返回true,否则返回false</returns>
34         public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) {
35             if (managerType == typeof (ButtonClickManager)) {
36                 button_Click(sender, (RoutedEventArgs) e);
37                 return true;
38             } else {
39                 return false;
40             }
41         }
42
43         #endregion
44
45         /// <summary>
46         /// 间谍的私人武器库。
47         /// </summary>
48         private ArrayList _weapons = new ArrayList(1024*1024*100);
49     }
50 }

调用的代码跟之前强引用的时候一样。

我们发现通过弱事件模式,成功的解决了这个内存泄露的问题。侦听者得到了真正的销毁,成功的回收了。

我们再来分析其原理:

ButtonClickManger是继承与WeakEventManager类,干什么事情了?从名字上看就是管理弱事件的,有哪些管理任务了?

首先增加侦听者,这个侦听者负责侦听一些其他对象的事件,侦听的参数肯定要有源头,侦听谁,然后还有这个侦听者对象必须要实现IWeakEventListener接口,这样管理器就记录了这样的集合:侦听者与侦听对象的一个对照表:

public static void AddListener(Button source, IWeakEventListener listener) {
            CurrentManager.ProtectedAddListener(source, listener);
        }

其次要启动开始侦听,要启动侦听事件源的事件,并负责订阅事件,在事件处理程序里面分发事件,这个会触发执行侦听者的接口函数ReceiveWeakEvent,然后我们就可以在这个函数里面做我们想做的事情了。

protected override void StartListening(object source) {
            Button button = source as Button;
            button.Click += new RoutedEventHandler(OnButtonClick);
        }

   private void OnButtonClick(object sender, RoutedEventArgs e) {
            DeliverEvent(sender, e);
        }

有开始就有停止:

protected override void StopListening(object source) {
            Button button = source as Button;
            button.Click -= new RoutedEventHandler(OnButtonClick);
        }

WeakSpy这个类实现的是IWeakEventListener这个接口

作为一个侦听者,首先要把要侦听的对象通过弱事件管理器添加进来:

public void MonitorButton(Button button) {
            ButtonClickManager.AddListener(button, this);
        }

然后接口函数里面的方法中执行事件处理程序:

public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) {
            if (managerType == typeof (ButtonClickManager)) {
                button_Click(sender, (RoutedEventArgs) e);
                return true;
            } else {
                return false;
            }
        }

在WeakEventManager中,如果AddListener,就会自动调用StartListening.

在这里我串一下整个过程:

  侦听者添加侦听的对象(WeakSpy.MonitorButton)->弱事件管理者增加侦听者(ButtonClickManager.AddListener)->弱事件管理者开始侦听,并确定要侦听的事件,及订阅事件(ButtonClickManager.StartListening)

点击按钮->触发到弱事件管理器的分发事件函数(DeliverEvent)->触发监听者的接口函数(IWeakEventListener.ReceiveWeakEvent)->调用监听者的私有函数(这里是button_Click)

当我们清理了WeakSpy这个对象后再点击按钮->触发到弱事件管理器的分发事件函数(DeliverEvent)->发现不存在这个监听对象就调用清理操作最终调用StopListening。

我再回到最开始的话题,我是在研究INotifyPropertyChanged这个课题的时候研究的弱事件这个问题,那么我们就先看看这个接口,这个接口只有一个成员,那就是一个事件event PropertyChangedEventHandler PropertyChanged;还是先看代码:

 1  public class Student : INotifyPropertyChanged
 2     {
 3         private event PropertyChangedEventHandler _propertyChanged;
 4         public event PropertyChangedEventHandler PropertyChanged
 5         {
 6             add
 7             {
 8                 this._propertyChanged += value;
 9             }
10             remove
11             {
12                 this._propertyChanged -= value;
13             }
14         }
15         private string  name;
16         public string  Name
17         {
18             get { return name; }
19             set {
20                 name = value;
21                 if (_propertyChanged != null)
22                 {
23                     _propertyChanged(this, new PropertyChangedEventArgs("Name"));
24                 }
25             }
26         }
27
28         public Student()
29         {
30             Name = "Wuming";
31         }
32     }

我们在实现这个接口的时候其实可以只照抄了一遍,在编译后,那么其实这个类就只是增加了Add, Remove函数,另外还有一个私有的委托对象成员(我这里显示写出来,方便我看代码图),在构造函数执行完后,PropertyChanged是为NULL的。那么我就有疑问:在属性变化的时候判断PropertyChanged是不为NULL的,那么到底什么时候把这个事件给订阅的呢?经过查看是绑定完成后PropertyChanged就有值了。那到底为什么呢?这个其实牵扯比较多,我们先看一个图:

从图中我们可以看出在设置绑定的时候,依赖对象(这里是TextBox)设置值,设置值的时候势必要调用Binding,最终要调用PropertyChangedEventManager.StartListening(object source),这个源自然是student这个对象了,开始监听里面的代码如

下:

1 protected override void StartListening(object source)
2         {
3             INotifyPropertyChanged notifyPropertyChanged = (INotifyPropertyChanged)source;
4             notifyPropertyChanged.PropertyChanged += new PropertyChangedEventHandler(this.OnPropertyChanged);
5         }

PropertyChangedEventManager也就是一个弱事件管理器,到这里我就这样串以下,绑定完成后为什么可以实现数据与UI的同步呢?显然是通过事件,那么只要实现了INotifyPropertyChanged这个事件,在绑定的时候就会在弱事件管理器里面订阅事件,这样当我们属性更新的时候自然就会执行这个事件,另外一个问题就来了,虽然事件是执行了,但是源的改变为什么通过这个事件改变目标呢?我想应该是这样子的:

  在设置binding的时候,目标相当于监听者的地位,监听的对象就是源,这样当源有变化的时候,目标当然也会改变,当然这只是我的想象。求证未果。

到目前为止,我明白了这样一些事情:INotifyPropertyChanged接口的事件成员在什么时候订阅的,大概知道了数据同步的原理,了解了弱事件管理器的大致工作原理。这对于以后的学习打下了基础。

引用了这位仁兄的博客:

http://www.cnblogs.com/rickiedu/archive/2007/03/15/676021.aspx

Demo:

http://files.cnblogs.com/files/monkeyZhong/WeakEventDemo.zip

时间: 2024-10-07 21:48:03

WPF 弱事件的相关文章

C#中的弱事件(Weak Events in C#)

(原创翻译文章·转载请注明来源:http://blog.csdn.net/hulihui) 原文:Weak Events In C#: Different approaches to weak events. by Daniel Grunwald.  Download source code - 15.5 KB 翻译前序 翻译后记 目录 引言 究竟什么是事件? 第1部分:监听方(Listener-side)的弱事件 解决方案0:仅仅注销 解决方案1:事件调用后注销 解决方案2:带弱引用(Weak

WPF 在事件中绑定命令(可以在模版中绑定命令)

其实这也不属于MVVMLight系列中的东东了,没兴趣的朋友可以跳过这篇文章,本文主要介绍如何在WPF中实现将命令绑定到事件中. 上一篇中我们介绍了MVVMLight中的命令的用法,那么仅仅知道命令是如何构建使用的还不够,很多情况下我们都需要在某个事件触发的时候才去触发命令,所以将命令绑定到事件上是非常有效的做法,下面我们来接着实现将命令绑定到事件中. WPF实现命令绑定到事件 使用 System.Windows.Interactivity.dll 中的 Interaction 可以帮助我们实现

WPF路由事件二:路由事件的三种策略

一.什么是路由事件 路由事件是一种可以针对元素树中的多个侦听器而不是仅仅针对引发该事件的对象调用处理程序的事件.路由事件是一个CLR事件. 路由事件与一般事件的区别在于:路由事件是一种用于元素树的事件,当路由事件触发后,它可以向上或向下遍历可视树和逻辑树,他用一种简单而持久的方式在每个元素上触发,而不需要任何定制的代码(如果用传统的方式实现一个操作,执行整个事件的调用则需要执行代码将事件串联起来). 路由事件的路由策略: 所谓的路由策略就是指:路由事件实现遍历元素的方式. 路由事件一般使用以下三

事件和弱事件的示例解说

1 using System; 2 using System.Windows; // 实现弱事件需要引用 WindowsBase 程序集 3 4 namespace 事件和弱事件的示例解说 5 { 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 var dealer = new CarDealer(); // 这个类包含一个事件处理器,以及对其的调用方法 11 12 var michael = new Consumer("

弱事件

----------------------------------------------------主程序 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApplication10 {     class Program     {         static void M

[总结]使用WPF路由事件过程中遇到的一些小问题

写在前面 本文一开始会给出一个使用WPF路由事件的实例,因为本文所有的表述都将基于该实例.而本文所给实例来自于<WPF自定义路由事件>一文,在<WPF自定义路由事件>一文中会对实例代码做详细说明,所以,大家在阅读本文实例代码期间若存在疑问,可以先去看看<WPF自定义路由事件>一文,看是否能从中获得你想要的解答. 本文实例 1 新建DetailReportEventArgs类,该类派生自RoutedEventArgs类,RoutedEventArgs类包含与路由事件相关的

WPF 在事件中绑定命令

导航:MVVMLight系列文章目录:<关于 MVVMLight 设计模式系列> 其实这也不属于MVVMLight系列中的东东了,没兴趣的朋友可以跳过这篇文章,本文主要介绍如何在WPF中实现将命令绑定到事件中. 上一篇中我们介绍了MVVMLight中的命令的用法,那么仅仅知道命令是如何构建使用的还不够,很多情况下我们都需要在某个事件触发的时候才去触发命令,所以将命令绑定到事件上是非常有效的做法,下面我们来接着实现将命令绑定到事件中. WPF实现命令绑定到事件 使用 System.Windows

弱引用和弱事件

默認對象實例化後得到的都是強引用,不過有時候對於一些複雜的對象,出於性能考慮,并不希望進行頻繁的初始化,此時弱引用就可以派上用場. 用法:先用WeakReference包裝複雜對象,到需要該複雜對象的時候,檢查一下弱引用的IsAlive屬性,如果true,就可以通過Target直接得到複雜對象,省去了實例化的過程. 簡單的例子: static void Main(string[] args) { var weakRef = GetWeakRef(); GC.Collect(); if (weak

[AaronYang]C#人爱学不学8[事件和.net4.5的弱事件深入浅出]

没有伟大的愿望,就没有伟大的天才--Aaronyang的博客(www.ayjs.net)-www.8mi.me 1. 事件-我的讲法 老师常告诉我,事件是特殊的委托,为委托提供了一种发布/订阅机制. 自定义事件:自定义一个类,继承EventArgs 使用泛型委托EventHandler<T>,本质:public delegate void EventHandler<TEventArgs>(object sender,TEventArgs e) where  TEventHandle