WPF 弱事件
因为在接触WPF的过程中追查INotifyPropertyChanged的通知原理的时候,发现了 PropertyChangedEventManager这个类,它是继承与WeakEventManager,也就是弱事件管理器,另外在学习MVVM的时候,其类库中也有关于弱引用弱事件方面的代码,然后我又非常的不熟悉,今天我打算深入了解下这个方面。
首先得了解下事件,事件在我的另外一篇里面已经详细讲了,这里就不重复了,但是事件其实有可能会导致内存泄露的,下面先看一个例子:
先申明一个Spy类,里面订阅按钮的Click事件:
public class Spy {
public void MonitorButton(Button button) {
button.Click += new RoutedEventHandler(button_Click);
}
private void button_Click(object sender, RoutedEventArgs e) {
Button button = sender as Button;
MessageBox.Show(string.Format("You have just clicked button {0}", button.Content),
"小报告", MessageBoxButton.OK, MessageBoxImage.Information);
}
private ArrayList _weapons = new ArrayList( * * );
}
然后在main里面侦听4个按钮:
_strongSpy = new Spy();
_strongSpy.MonitorButton(_btnA);
_strongSpy.MonitorButton(_btnB);
_strongSpy.MonitorButton(_btnC);
_strongSpy.MonitorButton(_btnD);
然后在第五个按钮的Click事件里面把Spy类对象清理掉:
private void KillSpy(object sender, RoutedEventArgs e) {
_strongSpy = null;
GC.Collect();
GC.WaitForPendingFinalizers();
MessageBox.Show("任务完成");
UpdateTitle();
_btnKillSpy.IsEnabled = false;
}
销毁掉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提供了一个接口和一个类来实现该模式,我们就先看看代码吧:
namespace WeakEventDemo {
/// <summary>
/// 按钮Click事件的管理类,负责以弱事件模式来分发事件。
///
/// 单态。
/// </summary>
public class ButtonClickManager : WeakEventManager {
/// <summary>
/// 供事件源调用,为管理的弱事件添加侦听者。
/// </summary>
/// <param name="source"></param>
/// <param name="listener"></param>
public static void AddListener(Button source, IWeakEventListener listener) {
CurrentManager.ProtectedAddListener(source, listener);
}
/// <summary>
/// 供事件源调用,为管理的弱事件移除侦听者。
/// </summary>
/// <param name="source"></param>
/// <param name="listener"></param>
public static void RemoveListener(Button source, IWeakEventListener listener) {
CurrentManager.ProtectedRemoveListener(source, listener);
}
/// <summary>
/// 挂接处理方法到事件源,并开始侦听。
/// </summary>
/// <param name="source"></param>
protected override void StartListening(object source) {
Button button = source as Button;
button.Click += new RoutedEventHandler(OnButtonClick);
}
/// <summary>
/// 终止对事件的侦听。
/// </summary>
/// <param name="source"></param>
protected override void StopListening(object source) {
Button button = source as Button;
button.Click -= new RoutedEventHandler(OnButtonClick);
}
/// <summary>
/// 事件处理方法,负责转发事件给侦听者。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnButtonClick(object sender, RoutedEventArgs e) {
DeliverEvent(sender, e);
}
/// <summary>
/// 返回当前的管理类实例。
/// </summary>
/// <remarks>
/// 单态实现。
/// </remarks>
private static ButtonClickManager CurrentManager {
get {
lock (obj) {
Type managerType = typeof (ButtonClickManager);
ButtonClickManager clickManager = GetCurrentManager(managerType) as ButtonClickManager;
if (clickManager == null) {
clickManager = new ButtonClickManager();
SetCurrentManager(managerType, clickManager);
}
return clickManager;
}
}
}
private static readonly object obj = new object();
}
}
namespace WeakEventDemo {
/// <summary>
/// 应用弱事件模式的侦听类。
/// </summary>
public class WeakSpy : IWeakEventListener {
/// <summary>
/// 添加要侦听的button。
/// </summary>
/// <param name="button"></param>
public void MonitorButton(Button button) {
ButtonClickManager.AddListener(button, this);
}
/// <summary>
/// 打小报告^_^
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button_Click(object sender, RoutedEventArgs e) {
Button button = sender as Button;
MessageBox.Show(string.Format("You have just clicked button {0}", button.Content),
"小报告", MessageBoxButton.OK, MessageBoxImage.Information);
}
#region IWeakEventListener Members
/// <summary>
/// 接收事件。
/// </summary>
/// <param name="managerType"></param>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <returns>如果事件得到处理,则返回true,否则返回false</returns>
public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) {
if (managerType == typeof (ButtonClickManager)) {
button_Click(sender, (RoutedEventArgs) e);
return true;
} else {
return false;
}
}
#endregion
/// <summary>
/// 间谍的私人武器库。
/// </summary>
private ArrayList _weapons = new ArrayList(**);
}
}
调用的代码跟之前强引用的时候一样。
我们发现通过弱事件模式,成功的解决了这个内存泄露的问题。侦听者得到了真正的销毁,成功的回收了。
我们再来分析其原理:
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;还是先看代码:
public class Student : INotifyPropertyChanged
{
private event PropertyChangedEventHandler _propertyChanged;
public event PropertyChangedEventHandler PropertyChanged
{
add
{
this._propertyChanged += value;
}
remove
{
this._propertyChanged -= value;
}
}
private string name;
public string Name
{
get { return name; }
set {
name = value;
if (_propertyChanged != null)
{
_propertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
} public Student()
{
Name = "Wuming";
}
}
我们在实现这个接口的时候其实可以只照抄了一遍,在编译后,那么其实这个类就只是增加了Add, Remove函数,另外还有一个私有的委托对象成员(我这里显示写出来,方便我看代码图),在构造函数执行完后,PropertyChanged是为NULL的。那么我就有疑问:在属性变化的时候判断PropertyChanged是不为NULL的,那么到底什么时候把这个事件给订阅的呢?经过查看是绑定完成后PropertyChanged就有值了。那到底为什么呢?这个其实牵扯比较多,我们先看一个图:

从图中我们可以看出在设置绑定的时候,依赖对象(这里是TextBox)设置值,设置值的时候势必要调用Binding,最终要调用PropertyChangedEventManager.StartListening(object source),这个源自然是student这个对象了,开始监听里面的代码如
下:
protected override void StartListening(object source)
{
INotifyPropertyChanged notifyPropertyChanged = (INotifyPropertyChanged)source;
notifyPropertyChanged.PropertyChanged += new PropertyChangedEventHandler(this.OnPropertyChanged);
}
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
WPF 弱事件的更多相关文章
- 在WPF中应用弱事件模式
http://www.cnblogs.com/rickiedu/archive/2007/03/15/676021.html 在wpf中应用弱事件模式 感谢VS 的Intellisens ...
- WPF程序中的弱事件模式
在C#中,得益于强大的GC机制,使得我们开发程序变得非常简单,很多时候我们只需要管使用,而并不需要关心什么时候释放资源.但是,GC有的时并不是按照我们所期望的方式工作. 例如,我想实现一个在窗口的标题 ...
- 【转载】详细解读C#中的 .NET 弱事件模式
你可能知道,事件处理是内存泄漏的一个常见来源,它由不再使用的对象存留产生,你也许认为它们应该已经被回收了,但不是,并有充分的理由. 在这个短文中(期望如此),我会在 .Net 框架的上下文事件处理中展 ...
- C#中的 .NET 弱事件模式
引言 你可能知道,事件处理是内存泄漏的一个常见来源,它由不再使用的对象存留产生,你也许认为它们应该已经被回收了,但不是,并有充分的理由. 在这个短文中(期望如此),我会在 .Net 框架的上下文事件处 ...
- [AaronYang]C#人爱学不学8[事件和.net4.5的弱事件深入浅出]
没有伟大的愿望,就没有伟大的天才--Aaronyang的博客(www.ayjs.net)-www.8mi.me 1. 事件-我的讲法 老师常告诉我,事件是特殊的委托,为委托提供了一种发布/订阅机制. ...
- .net4.5的弱事件
.net4.5的弱事件 没有伟大的愿望,就没有伟大的天才--Aaronyang的博客(www.ayjs.net)-www.8mi.me 1. 事件-我的讲法 老师常告诉我,事件是特殊的委托,为委托提供 ...
- c#弱事件(weak event)
传统事件publisher和listener是直接相连的,这样会对垃圾回收带来一些问题,例如listener已经不引用任何对象但它仍然被publisher引用 垃圾回收器就不能回收listener所占 ...
- WPF 在事件中绑定命令(不可以在模版中绑定命令)
其实这也不属于MVVMLight系列中的东东了,没兴趣的朋友可以跳过这篇文章,本文主要介绍如何在WPF中实现将命令绑定到事件中. 上一篇中我们介绍了MVVMLight中的命令的用法,那么仅仅知道命令是 ...
- WPF自学入门(三)WPF路由事件之内置路由事件
有没有想过在.NET中已经有了事件机制,为什么在WPF中不直接使用.NET事件要加入路由事件来取代事件呢?最直观的原因就是典型的WPF应用程序使用很多元素关联和组合起来,是否还记得在WPF自学入门(一 ...
随机推荐
- json数值和结构
JSON 值可以是: l 数字(整数或浮点数) l 字符串(在双引号中) l 逻辑值(true 或 false) l 数组(在方括号中) l 对象(在花括号中) l null JSON建构 ...
- codeforce-191E-Thwarting Demonstrations(树状数组+二分+离散)
题意: 求第K 大连续区间 分析: 二分答案,再n * log(n)判断有几个区间的区间和大于mid,然后调整上下界,使这个值不断的接近k. 判断符合条件的区间总数:线性扫描sum[n](前n项和) ...
- 【转】中兴G718C卡刷刷机教程(青漾2 4G)--不错
原文网址:http://www.zterom.com/guide/2278.html 刷机包 B11纯净版 适合长久使用_B11_lite_0130.zip 刷机用了20多分钟. 在和大家分享过中兴G ...
- 枚举(分类讨论):BZOJ 1177: [Apio2009]Oil
1177: [Apio2009]Oil Time Limit: 15 Sec Memory Limit: 162 MBSubmit: 1477 Solved: 589[Submit] Descri ...
- 后缀自动机(SAM)模板
struct SAM{ ],fa[maxn],len[maxn],cnt,last; void Init() { memset(ch,,sizeof(ch)); memset(fa,,sizeof(f ...
- Introduction to Glide, Image Loader Library for Android, recommended by Google
In the passed Google Developer Summit Thailand, Google introduced us an Image Loader Library for And ...
- codevs3945 完美拓印
3945 完美拓印 codevs月赛 第一场 时间限制: 1 s 空间限制: 256000 KB 题目等级 : 黄金 Gold 题目描述 Description 小Q获得了一个神奇的印章,这个印章宽n ...
- js~this的陷阱
在JS中,当前对象一般用this表示,在jquery中,当前的对象是用$(this)表示,这些都是最基础的知识,没什么可说的,但我要说的是,当this出现在某个深度时,它的含义你自己要清楚,它是指离当 ...
- [置顶] Android JNI必须掌握的五点
1:JNI是什么? Java NativeInterface(JNI)是Java提供的一个很重要的特性.它使得用诸如C/C++等语言编写的代码可以与运行于Java虚拟机(JVM)中的 Java代码 ...
- 在安装软件CAJViewer时出现,“错误1327。无效驱动器:F:
解决的方法:DOS中输入例如以下命令: [plain] view plaincopy subst F: %TEMP% 回车退出就可以,必要时重新启动电脑. 软件成功安装之后能够执行下面命令,将该虚拟分 ...