Java Event Dispatch之旅
-----------------------
在之前笔者已经把Java Event的来源以及dispatch的方向点了出来,接
着将继续探讨当EventQueue中的Event被EventDispatchThread拿出来之
后的dispatch过程。还记得前面提过EventDispatchThread如何把Event
dispatch出去的吗?如果不记得的话,请往前回忆一下吧! 不管是什麽
Event,在它被丢往EventQueue时,有两个Event的属性已经被决定(id
和source),而EventDispatchThread会根据Event的source来决定接下
来要dispatch的目标,也就是发生Event的AWT元件。根据之前的讨论,
dispatch的目标分成两类,一个是Component,另一个则是MenuComponent
,为了方便起见,在此笔者仅以Component为例。
既然目标是Component,那麽首先当然是找Component.java囉!由于Event
最后是由source的dispatchEvent(event)这个method开始的,所以就从
dispatchEvent下手,结果发现dispatchEvent会再呼叫dispatchEventImpl
(event),从这个method当中,我们看到了Java对于不同的Event可能会
有不同的处理方式,为了简化起见,笔者只针对正常Event Model
(normal-processing)的处理流程来作说明(也就是透过processEvent),
部分code如下。
void dispatchEventImpl(AWTEvent e) {
…
// 3. Deliver event for normal processing
if (newEventsOnly) {
if (eventEnabled(e)) {
processEvent(e);
}
}
else if…
}
而processEvent会根据不同的Event物件继续dispatch的工作,可能继续被
呼叫到的方法为processXXXEvent(…),例如: processFocusEvent(...)、
processMouseEvent(...)、processMouseMotionEvent(...)、processKeyEvent(...)、processComponentEvent(...)。processEvent的部分code如下:
protected void processEvent(AWTEvent e) {
if (e instanceof FocusEvent) {
processFocusEvent((FocusEvent)e);
} else if (e instanceof KeyEvent) {
processKeyEvent((KeyEvent)e);
}else if(…)
…
}
接下来我们以processMouseEvent为例,code如下:
protected void processMouseEvent(MouseEvent e) {
MouseListener listener = mouseListener;
if (listener != null) { //检查是否有已经注册过的listener
int id = e.getID();
switch(id) {
case MouseEvent.MOUSE_PRESSED:
listener.mousePressed(e);
break;
case MouseEvent.MOUSE_RELEASED:
listener.mouseReleased(e);
break;
case MouseEvent.MOUSE_CLICKED:
listener.mouseClicked(e);
break;
case MouseEvent.MOUSE_EXITED:
listener.mouseExited(e);
break;
case MouseEvent.MOUSE_ENTERED:
listener.mouseEntered(e);
break;
}
}
}
看到这裡,不知道各位读者是否有那拨云见日的感觉,原来从dispatchEven
t一路下来,一直到processXXXEvent才会透过已经被注册过的listener来呼
叫对应的Event-Handler。这也就是为何在使用Event时,必须先以
addXXXlistener来注册的原因了。再来,我们看看Listener如何在Source达
成注册的动作!在Component.java中,有好几个用来注册listener的
addXXXlistener方法如: addComponentListener、addFocusListener、
addKeyListener和addMouseListener…等,因为Component是所有AWT元件的
老祖宗,所以这些用来注册的方法,其实代表着所有继承自Component的元件
,都能透过Event Delegation Model来handle这些Events(这其实就是物件继
承的优势)。由于这些addXXXlistener的行为大致相同,所以我们就随便挑一
个来看囉,就addMouseListener好了:
public synchronized void addMouseListener(MouseListener l) {
if (l == null) {
return;
}
mouseListener = AWTEventMulticaster.add(mouseListener,l);
newEventsOnly = true; //透过normal-processing的Event,newEventsOnly
//必须被设为true
…//以下有一些是跟lightweight component相关的code
…//不在本篇讨论范围内,所以就不多说囉!
}
嘿嘿…又有新的类别出现了,叫做”AWTEventMulticaster”,相信各位使用
Java Event写过程式的读者,应该会听过Java 1.1之后的Event Model是採用
Multicast的方式来通知处理事件的Event-Handler,没错,就是它啦!当元件
(Source)上有事件产生时,该Source会以Multicast的方式通知所有曾经在该
Source注册过的Listeners来呼叫对应的Event-Handler。透过AWTEventMulticaster
的add静态方法,会把所要注册的Listener加到Source的xxxListener(用来判
断是否有listener被注册)中,最后再由processXXXEvent来呼叫到对应的
Event-Handler。至于实际注册的过程,当执行add方法时,会再呼叫到addInternal(..)
方法:
protected static EventListener addInternal(EventListener a,EventListener b) {
if (a == null) return b;
if (b == null) return a;
return new AWTEventMulticaster(a, b);
}
我们可以看到当注册的Listener只有一个时,会直接把该Listener传回,否则
将会有一个AWTEventMulticaster的物件被产生并传回给Source的xxxListener
,至于AWTEventMulticaster的物件为何可以传回给xxxListener呢?主要是因
为AWTEventMulticaster实作了(implements)所有的Event Listeners。举例来
说,当只有一个ListenerA透过addMouseListener来注册,那麽透过
AWTEventMulticaster的add(..)会直接传回ListenerA给Source的mouseListener。
如果有两个Listeners注册时,会先产生一个AWTEventMulticaster物件,并把
这两个注册的Listeners分别放到物件的a与b(型别为EventListener),code如下:
protected final EventListener a, b;
protected AWTEventMulticaster(EventListener a, EventListener b) {
this.a = a; this.b = b;
}
最后再把AWTEventMulticaster物件传回给Source的mouseListener。如果有3个
Listeners要注册时,原理同上,只是此时要传回之AWTEventMulticaster物件的
a和b之中会有一个是单纯的Listener物件,另一个则是AWTEventMulticaster物
件…以此类推之。当所有注册手续完成,那麽就可以透过之前提及的processXXXEvent
来呼叫对应的Event-Handler了。
基本上Event的dispatch流程笔者就讲到这儿囉,最后同样用一张图(图5)来解释
这整个过程:
图5 (略)
从以上的说明,相信各位应该已经清楚Event最后究竟往哪裡去了!不过在图5中还
有一道关卡笔者尚未交代清楚,那就是怎样的Event才算是enabled Event呢?关于
这部分将在稍后揭晓。
非事件委派者(Event-Listener)不可吗?
----------------------------------
从以上的讨论,各位读者应该对于Java 1.1之后所採用的Event-Delegation
Model有了更进一步的认识,其实说穿了,”就是将Event委託/委派(delegate)
给Event-Listeners的Event-Handlers来处理,而这个委託的动作必须是透过
addXXXListener(Listener)的注册手续”。但是难道就一定得透过事件委派者
(Event-Listener)不可吗?如果Java Programmer硬是不愿用Event- Delegation
这套方式,那Event是不是就无从处理了呢? 当然不是。既然没有委派任何事
件处理者,那就自己来吧! 找不到人帮忙时,也只好自力救济囉! :)
从Event dispatch的流程中,我们知道Event离开EventQueue之后,第一站便
是Source(指发生Event的Component)的dispatchEvent方法,既然是Source本
身先接到要处理的Event,那麽实在没理由一定得透过其他的Listeners,由
Source自己拦下Event不就得了。从图5中,Event最后会被processXXXEvent
来决定是否将Event丢给适当的Listener (by multicast),如果当下没有任
何注册过的Listeners,那麽该Event就不被处理,因为此时的Event类别已经
确定,只要再根据Event的Type(id)来个别处理即可,所以processXXXEvent
正是拦截Event的适当位置。接下来笔者就以拦截WindowEvent的WINDOW_CLOSING
来结束程式的例子做说明。
有写过Java GUI程式的读者们,一定都知道程式的结束并不是单纯按下视窗
右上角的X按钮就可以的,而必须由Programmer自行拦截WindowEvent,并呼
叫System.exit(0)来结束程式(注3)。不过为了跟标准的Event-Delegation
Model做区别,在此笔者就以Override的方式由Source Component自行拦截
处理Event。程式码(MyFrame.java)如下:
public class MyFrame extends Frame { public MyFrame() { enableEvents(AWTEvent.WINDOW_EVENT_MASK); this.setBounds(10,10,200,200); this.setVisible(true); } public static void main(String[] args) { MyFrame myFrame1 = new MyFrame(); } // Overridden so we can exit on System Close protected void processWindowEvent(WindowEvent e) { super.processWindowEvent(e); if(e.getID() == WindowEvent.WINDOW_CLOSING) { System.exit(0); } }
public MyFrame() {
enableEvents(AWTEvent.WINDOW_EVENT_MASK);
this.setBounds(10,10,200,200);
this.setVisible(true);
}
public static void main(String[] args) {
MyFrame myFrame1 = new MyFrame();
}
//Overridden so we can exit on System Close
protected void processWindowEvent(WindowEvent e) {
super.processWindowEvent(e);
if(e.getID() == WindowEvent.WINDOW_CLOSING) {
System.exit(0);
}
}
}
这是一个很阳春的Java GUI程式,只new了一个Frame并显示出来,由于不
打算採用Event-Delegation Model,所以不会有呼叫addWindowListener注
册的动作。在此MyFrame直接override java.awt.Window的rocessWindowEvent
,所以当WindowEvent发生时,Event便会被MyFrame的processWindowEvent
拦截下来,如果该WindowEvent为WindowEvent.WINDOW_CLOSING,也就是使
用者按下了X钮时,那麽便呼叫System.exit(0)来结束程式。
从这个Sample中可以发现,WindowEvent的Source为自己(MyFrame),而
WindowEvent也是由MyFrame自己来处理,跟事件委派者一点关係也没有。
此外,在processWindowEvent中又呼叫了java.awt.Window的processWindowEvent
方法,基本上这是比较正规的写法,通常我们会先让WindowEvent循正常
管道分派处理过后,再针对要拦截的WindowEvent作特殊处理,否则的话,
其他以Event- Delegation Model运作的WindowEvent,将无法被送到适当的
Event-Listeners来处理(当然也可以先针对要拦截的WindowEvent作处理,
再循正常管道分派处理)。拦截WindowEvent的过程如图6:
图6 (略)
再来必须跟各位读者交代的是,上一个单元最后提到:究竟什麽样的Event
才算是enabled Event呢? 从Event Dispatch的过程中,如果该Event没
有被enabled,那麽Event将无法继续被处理(不管是透过Event-Delegation Model
或是Override的方式),而在Component.java中有个方法--eventEnabled
,就是用来检查Event是否为enabled,该方法部分code如下:
boolean eventEnabled(AWTEvent e) { switch(e.id) { case ComponentEvent.COMPONENT_MOVED: case ComponentEvent.COMPONENT_RESIZED: case ComponentEvent.COMPONENT_SHOWN: case ComponentEvent.COMPONENT_HIDDEN: if ((eventMask&AWTEvent.COMPONENT_EVENT_MASK)!=0|| componentListener != null) { return true; } break; case … } }
如果该Event为enabled则传回true,而Event是否为enabled可由两点决定:
(1)该Event所对应之Event Mask是否有被启动:(注4)
如果已经被启动,则发生Event的Source Component之eventMask跟对应的
EVENT_MASK常数(定义在AWTEvent.java)做’&’运算后,必不为零。例如: if(eventMask&AWTEvent.COMPONENT_EVENT_MASK)!=0)
=>表示ComponentEvent所对应之Event Mask已被启动,为Enabled Event
else
=>反之则表示ComponentEvent不为Enabled Event
(2) 该Event所对应之Event-Listener是否存在:
也就是说,只要有对应的Event Listeners曾经注册过,那麽该Event便是
Enabled Event。
现在我们再回到之前的Sample(MyFrame.java),由MyFrame的建构子中可
以发现到enableEvents(AWTEvent.WINDOW_EVENT_MASK),相信现在各位读
者已经有能力解释为何需要这段code了!因为MyFrame是透过Override的方
式来处理WindowEvent,根本不会有WindowListener前来注册,WindowEvent
自然也不会是Enabled Event,所以只好选择另一条路,透过enableEvents
来启动WindowEvent的Event Mask,这才使得WindowEvent能够正常被处理。
最后提醒各位读者,儘管在Java中可以透过这种Override的方式来拦截Event
,但是笔者在此并不鼓励各位读者使用这种方式,除非您有特殊的需求,例
如设计JavaBeans元件。毕竟标准的Event-Delegation Model在使用上还是比
较有弹性的,而且别忘了,Event-Delegation Model可是以multicast的方式
来呼叫对应的Event-Handler喔,所以如果直接以Override的方式来处理Event
,要达到同样的效果,恐怕要再多费点心思呢!所以囉,除非必要,不然的话
,其实把Events都委派给其他Listeners来处理,用起来也挺顺的啦:)
注3:其实Win32系统通常也是要自行拦截WM_DESTROY讯息,接着呼叫PostQuitMessage
函式将WM_QUIT讯息置于讯息伫列,当GetMessage遇上WM_QUIT时,才会离开讯
息迴圈,接着结束程式。
注4:”启动Event Mask”指的是呼叫enableEvents方法,并传入要enable之Event
Mask,Ex: enableEvents(AWTEvent.WINDOW_EVENT_MASK),就是用来启动WindowEvent
的Event Mask。
结论
----
有关Java Event的讨论就到此告一落了,限于篇幅,所以笔者主要只选择了
”Event的来源”以及”Event dispatch的流程”等基本议题向各位读者做
个报告,虽然这些议题其实都不难,不过却是学习Java Event的我们所不可
不知的。除此之外,给各位读者一个小小的建议,如果可以的话,花点时间
看看SUN的Class Library Source Code(安装JDK就有了),这对您绝对会有帮
助的,而笔者在这篇文章中所写的内容,其实大半都是来自Trace的心得喔!
…嗯…好啦…谢谢各位的捧场,希望这篇文章能对各位读者有所帮助,谢谢!
参考资料
-------
*Java(tm) Development Kit Version, Sun Microsystems, Inc.