Java Focus实现纪要二
1. 在Jre1.7版本中,KeyboardFocusManager,DefaultKeyboardFocusManager这两个类,与Component, WComponentPeer类一起完成了focus的主要逻辑实现。
DefaultKeyboardFocusManager是前者的系统默认实现。其单例注册在appcontext中,如果需要,程序员可以替代它,以扩展focus的逻辑实现。
appcontext.put(KeyboardFocusManager.class, new SelfKeyboardFocusManager());
2. Component中提供了requestFocus方法。而各个组件在初始化时都会安装默认的Listener。当这些Listener收到适当的事件通知后(比如mouse_press)即会调用这个方法。该方法首先判断该组件是否focusable,组件所依托窗口是否focusable,当前聚焦组件的InputVerifier是否验收输入等等,判断通过后请求重量级组件容器的peer.requestFocus。WComponentPeer中提供该requestFocus方法。该方法首先调用native processSynchronousLightweightTransfer,其会调用KeyboardFocusManager .processSynchronousLightweightTransferr,作用是如果当前request组件的重量级组件容器正对应当前底层系统的聚焦组件,而且当前没有任何切换焦点的heavyweightRequests,这时将直接切换focus变量KeyboardFocusManager.focusOwner。
如果上述调用没有顺利完成并返回true,则会调用native _requestFoucs。该方法会调用KeyboardFocusManager .shouldNativelyFocusHeavyweight,其作用就是完成request登记,并在登记时间戳以正确缓存处理后续进入EDT的Keyevent处理。
Request登记的结构为KeyboardFocusManager.heavyweightRequests=
LinkedList< HeavyweightFocusRequest >
-- HeavyweightFocusRequest{
Component heavyweight;
LinkedList<LightweightFocusRequest> lightweightRequests,登记方式分为3种:
a. 如果发出requestFocus的组件的重量级组件容器正对应当前底层系统的聚焦组件,而且当前没有任何切换焦点的heavyweightRequest,则增加一个heavyweightRequest并向Post-Qqeue post focus-event。
b. 如果发出requestFocus组件的重量级组件容器不对应当前底层系统的聚焦组件,而且当前没有任何切换焦点的heavyweightRequest;或者当前存在切换焦点的heavyweightRequest,而最后一个heavyweightRequest. Heavyweight!=当前request组件的重量级组件容器,则要增加一个heavyweightRequest,并同步通知底层系统进行重量级对等组件的focus切换。
c. 如果当前存在切换焦点的heavyweightRequest,而且最后一个heavyweightRequest. Heavyweight==当前requestFocus的组件的重量级组件容器,则直接在request.lightweightRequests追加一个LightweightFocusRequest。
3. EDT在逐个处理AWTEvent时,委托给EventQueue.dispatchEvent,继而委托给Component. dispatchEventImpl,该方法顺序执行下面的代码片段:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~·
/* focusManagerIsDispatching标志了该event;如果==true意味着该event不会交由KeyboardManager进行retarget及dispatch。而这两个动作主要完成的功能就是刷新java的全局focus变量。因此可以想象focusManagerIsDispatching==true的Focus_event是focus发生切换后的event,而focusManagerIsDispatching==false的是PrepareFocusEvent。*/
if (!e.focusManagerIsDispatching) {//----------PrepareFocusEvent
// Invoke the private focus retargeting method which provides
// lightweight Component supportF
/*通过retargetFocus,处理之前注册的request请求,最终激发出合适的CausedFocusEvent,交给下面的dispatch.
*/
if (e.isPosted) {
e = KeyboardFocusManager.retargetFocusEvent(e);
e.isPosted = true;
}
// Now, with the event properly targeted to a lightweight
// descendant if necessary, invoke the public focus retargeting
// and dispatching function
/*通过dispatch给注册的DefaultKeyboardFocusManager,最终更新了java的全局focus变量
*/
if (KeyboardFocusManager.getCurrentKeyboardFocusManager().
dispatchEvent(e))
{
return;
}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~·
4. 总之:
a. 轻量级组件的Mouse_Press Listener会requestFocus。request通过必要条件检查后会在KeyboardFocusManager.heavyweightRequests缓存列表登记,同时在一个列表中登记一个时间戳marker=当前系统时间。
b. 每一个KeyEvent都有一个发生时间when,这个发生时间认为是AWT-Windows loop底层event形成KeyEvent的时间。EDT在调用KeyboardFocusManager 。dispatchEvent处理一个KeyEvent时只要发现when晚于时间戳缓存列表中登记的第一个时间戳,就充分说明这是在某焦点切换请求发出后发生的键盘事件,则不应该将这个KeyEvent target到当前全局focus变量,因而这时暂将此KeyEvent缓存在另一个列表。
c. 根据前面的分析,在requestFocus时有3种情况,一种是新增heavyweightRequest,同时post给post-queue一个FocusEvent,一种是新增heavyweightRequest,同时因为要求底层系统切换重量级对等体而awt-loop到一个FocusEvent,这两种情况的request都在列表中期待对应FocusEvent到来再切换焦点。从登记时间戳开始,被awt-loop到的KeyEvent进入EDT时都将缓存下来,而一段时间后当期待的FocusEvent从post-queue进入event-queue并要在EDT中处理时,有充分的条件可以判断出此后再进入EDT的KeyEvent,都至少是request登记时间戳后发生的,则这时可以完成此request-删除该heavyweightRequest缓存条目,做focus实际切换,并将缓存的KeyEvent 及时间戳记录处理掉。而第3种情况是在requestFocus时可以在最末一个heavyweightRequest上直接追加LightWeightReuquest,那么当该heavyweightRequest期待的FocusEvent到来时,按前面所述处理完该request,再将后续LightWeightRequest保存引用到一个全局变量KeyboardFocusManager.currentLightweightRequests,再将此刻为止awt-loop至的post-queue的所有event完全flush到event-queue,再把一个要求循环处理所有currentLightweightRequests指向的LightWeightRequests的InvocationEvent post 到event-queue之后。这样当EDT开始处理该InvocationEvent时,有充分的条件可以判断出此后再进入EDT的KeyEvent,都至少是最后一个后续LightWeightRequest登记时间戳后发生的,则这时只需按该InvocationEvent执行即可,及逐个清理LightWeightRequest完成focus切换及处理时间戳和缓存KeyEvent。如果在循环处理过程中发生对某一个组件requestFocus调用,这时会根据处理之初currentLightweightRequests中是否只有单独1个request来确定能否processSynchronousLightweightTransferr,即如果有多个,则这时禁止processSynchronousLightweightTransferr以防止破坏了切换焦点的顺序。
d. 重量级组件不需要在Mouse_Press Listener request Focus,当被进行Mouse Press时,底层系统分发一个Focus Event,当进入EDT处理时,在jre1.7中通过KeyboardFocusManager.retargetUnexpectedFocusEventretarget,首先逐个剔除request后进行期待匹配(针对可能的底层分发-post-queue-event-queue中间环节Event的遗漏等例外情形),如果最后没有一个request匹配,则直接形成CausedFocusEvent交给后继dispatch完成焦点切换。更确切地说,对于jre1.7而言组件聚焦应该都通过requestFocus完成切换,不通过该方式的聚焦切换在retarget时将归属到Unexpected被处理,而重量级组件的这种聚焦正好通过unexpected完成。
5. 最后,个人认为jre1.7中存在一个可能的问题:每次dispatchEvent时都会在retargetFocusEvent里processCurrentLightweightRequests,这样不久破坏了4-c分析的时机逻辑了么?为什么要这样呢?