程序思路如下:
主窗体(frmMain :IParentForm)
事件成员:
public event ParentEventHandler OnUserListCreated;
 
事件处理方法:
void ToDoOnRequestUserList(object sender, EventArgs e){
         //创建DataTable dt
         …
         This.OnUserListCreated(this, new ParentEventArgs(dt));
}
 
某一行注册子窗体事件:
frmChild.OnRequestUserList += new EventHandler (ToDoOnRequestUserList);


子窗体(frmChild)
事件成员:
public event EventHandler OnUserListCreated;
 
事件处理方法:
void ToDoOnRequestUserReturned(object sender, ParentEventArgs e){}
 
在OnLoad事件处理方法中注册主窗体的事件:
(this.MdiParent as IParentForm). OnUserListCreated += new ParentEventHandler (ToDoOnRequestUserReturned);

主窗体对象为frmMain,它实现了IParentForm接口,该接口定义了事件成员OnUserListCreated(它的EventArgs为自定义的ParentEventArgs)。frmMain对象在某处创建了一个子窗体frmChild,并注册了frmChild的事件OnRequestUserList。
 
子窗体对象frmChild在载入时(OnLoad方法中)获得frmMain的引用,并注册了frmMain的事件OnUserListCreated。
 
根据业务逻辑,子窗体运行的某一时刻,用户行为触发了事件OnRequestUserList,此时frmMain将捕获此事件并调用自身的处理方法生成一个被请求的用户列表(DataTable)。然后,frmMain发出了事件OnUserListCreated以提示列表生成完毕,并将刚刚创建的DataTable作为ParentEventArgs参数插入事件中。随后,子窗体将接收到这个事件,并在自己的事件处理方法中对传来的DataTable进行自己的业务逻辑动作。
 
在接下来程序的运行中,可爱的代码心情愉悦地顺利执行…但是,好景不长!
 
当我将打开的子窗体关闭后再重新打开,主窗体在触发OnUserListCreated事件后发生调用目标异常,子窗体在该事件的处理方法中也抛出NullReferenceException异常(未将对象引用设置到对象实例)。当我在子窗体的事件处理方法ToDoOnRequestUserReturned中设置断点调试后发现:所有的控件、变量都为null!!
 
那叫郁闷,那叫惆怅…公车上、步行中、如厕时、入睡前,我估摸着这种灵异现象可能与最近隔壁邻居家小猫的突然消失有着千丝万缕的联系…当然,作为基督教徒的我,也后怕这是主,耶稣基督对于我大前天横闯马路的惩罚…
 
无助中,我极其盲目的在frmChild的ToDoOnRequestUserReturned方法中加入了一行语句:“MessageBox.ShowDialog(“So boring a thing!”)”以发泄心情。保存、编译、运行——大坏蛋的面目露了出来!当我第一次打开子窗体的时候,如我所料,程序正常运行并弹出了MessageBox。关键是,当我关闭子窗口并第二次打开它执行时,MessageBox弹出了两次!恩…
 
带着疑问,我重复了以上关闭、打开步骤,MessageBox弹出了三次!——事情已经有了眉目。在我辗转反复的思考后(也许有人会骂我菜鸟…),终于明白了所有事情的缘由:
 
因为程序一直处在运行中,所以主窗体对象一直驻留内存中并保持着自身的状态(它没有的disposed),所以,每次子窗体创建时,主窗体都会注册它的OnRequestUserList事件,同样的,该子窗体在加载时,自身也会把主窗体的OnUserListCreated事件注册一次。
 
问题就出在这里,虽然子窗体关闭了,并disposed了。但是,它关闭时并没有把在主窗体注册的事件同时注销。随着子窗体一次次的打开,主窗体的OnUserListCreated就被+=了N多了注册用户,其中的N-1个用户其实早已经不存在了,而主窗体全然不知。所以当发出OnUserListCreated事件后,主窗体还会以无反顾地去调用这N多个方法代理,这必然会导致异常抛出——唯一打开的那个子窗体接受到一次次传来的事件,并企图调用ToDoOnUserListReturned方法,如果此方法中包含着对本对象成员变量的操作,自然会引出“未将引用设置到对象实例”的异常。
 
也许有朋友会问,为什么主窗体调用那些早已disposed的frmChild的方法的代理时,会被当前存在的那个frmChild执行呢?我认为这可能是由于类实例的同一个方法在内存栈中共享空间造成的;而成员变量在堆中存放,各自维护其状态,当其所属的对象被释放回收时,其值也就置为null了。(个人观点,望兄弟姐们给予指正)
 
总结:
 
子窗体在关闭时,应当把自己注册的主窗体对象(或者是长久驻留内存对象)事件一一注销。例如本例中,应在子窗体的OnClosed事件处理方法中加入以下代码:
(this.MdiParent as IParentForm). OnUserListCreated  -=  new ParentEventHandler    (ToDoOnRequestUserReturned)
 
如果仅仅是为了在主窗体执行完某项操作后触发子窗体某一方法的执行,我们通常不采用事件机制,而采用以下两种方法:

A. 将此方法访问属性改为public,然后由主窗体适时调用。
B. 定义一个接口,子窗体对象实现这个接口,并把该目标方法提升为该接口的成员。由主窗体适时调用这个接口成员方法。
只有注册用户登录后才能发表评论。

posts - 6, comments - 4, trackbacks - 0, articles - 0

Copyright © 科艺