2008年10月20日 星期一

Java Focus 簡談

視窗系統一般包含一個桌面GUI若干應用程序GUI。每個GUI都由組件構成,每個組 件都可以獲得focus,獲得focus的組件將獲得之後的鍵盤事件,而任意時刻只有一個組件能獲得focus。這個設計適用在當前所有的視窗系統,而跨 各種系統的JAVA應用,其focus的表現也要遵循這個設計目標。

JAVA的組件分為重量級和輕量級組件,區別在於重量級組件實例的成員peer-對等體,其行為緊密依託本地系統的GUI行為函數庫來進行實現。比如一個 JFRAME,當setvisible時,會依托peer.show進行屏幕繪製行為,該行為會通過本地系統GUI行為函數庫完成;這樣一來,當其被點擊 時,本地系統會依據最初調用本地GUI函數繪製時留下的信息,從而能夠經底層處理後(比如將該鼠標事件附加peer標記信息,同時可能經底層分析需要構造 出一個可能的focus_gain事件,則在操作系統層面登記當前聚焦GUI組件等)準確將底層GUI事件派送給該JVM進程,該事件因而在jvm進程中 的AWT-Windows線程loop獲取到,並通過事件提供的peer標記最終確定目標為重量級組件JFRAME,因此一個source== JFRAME的AWTEvent被構造出來並最終分派給EDT進行後續處理。

事件機制是程序中家喻戶曉的設計模式了。但是,看java的focus實現中對這個機制似乎多少有些不那麼絕對的清晰J。

個 人理解,事件的含義就是某種定義的情況發生了。比如點擊鼠標這個動作可以說觸發了多個事件,如press,release,click等,分別指發生了鼠 標button1按下,放開,完成點擊的情況。 button1按下這個事件比起完成點擊就要更基礎一些,因為完成點擊指的是一個由按下,放開動作序列組合的情況發生了。

那麼對於focus,focus_gained,focus_lost這兩個事件應該是指某組件獲得焦點或失去焦點的情況發生了,反映在機器裡,應該是某種指向當前聚焦組件的全局變量發生了更新。

然而在java awt實現裡,概念混亂出現啦。

如 果awt_windows loop到了focus事件,一,這個事件一定是目標向重量級組件的;二,此時,這個事件對於底層系統的對等組件,focus_gainded是發生了 (底層系統標記當前聚焦組件的全局變量已經更新;底層操作系統沒有mess,總是在真正focus改變後才分發focus事件),然而在java層面,截 至到awt_windows loop到底層focus事件並包裝成FocusEvent放置到EVENT QUEUE時,java層面並沒有更新jvm裡的全局變量。所以我個人認為這個時候就不應該包裝成FocusEvent,至少不應該叫這個名字,應該叫 PrepareFocusEvent,嘿嘿。

澄清事件機制的概念後,回頭看java focus要實現的目標。

1.當用戶從別處聚焦點切換到某文本框時(通過約定好的一些鍵盤鼠標操作,比如鼠標點擊或其他可能的動作),需要更新JAVA全局變量使該文本框成為聚焦組件,而後通知該組件的事件監聽focusgainedlistener。

當 前的難點是,輕量級組件雖可以設計在組件切換操作(如mouse_press)的listener中實現全局變量更新,然而問題是如果輕量級組件的容器是 一個重量級組件,比如一個Panel,而此時它的本地對等組件在本地系統中很可能還沒有獲得焦點。若實現上只是在切換操作監聽中簡單的把java的全局變 量更新了,那系統中就會在此時間出現兩個聚焦組件:一個是底層系統承認的原來的某底層對等組件,一個是java裡認為的現在的jtextfield。在這 個時空裡界面將很難不混亂,你很可能會看到最顯眼的是有一個亮亮的TextField,而那個上面有“已經聚焦”的jtextfield的panel面板 卻灰灰的,而你輸入的一個個字符竟然在灰灰的jtextfield中顯示出來。

2.可以提供某方法供應用調用,比如setfocus指定某組件聚焦。

難 點是根據前面的分析,指定輕量級組件聚焦,則要在處理中要通知其重量級容器對應的本地對等組件聚焦並等到它確實聚焦完成了再更新JAVA的全局變量。若實 現上真就是在setfocus調用中直接調用底層setfocus,問題出現在即使底層系統根據調用通知更新了focus,馬上還會繼續對外設可能的焦點 切換操作響應(可以認為有一個系統進程在處理外設的響應),很有可能別的C應用就在此時再要求focus,於是接著就更新了底層的focus登記;而我們 的setfocus調用卻是在jvm進程的某線程中,顯然這就是個並發的情景,這樣,很有可能我們的對本地對等組件的通知發過去並返回了,那邊底層系統就 馬上切換到了C的某個組件focus,而我們的線程繼續更新JAVA的全局focus變量並隨後發布 focus_gained(target==jcomponent),於是又出現了不一致的場景。難道我們要同步這兩個進程,讓系統進程等待我們的調用 setfocus的線程返回再繼續運行,顯然那樣是不合理的。 (JAVA只能服從OS,不能讓OS服從JAVA。---出自《英雄亂語》J)

鑑於1,2的分析,java focus的實現是setfocus是requestfocus,意思是只是將這個切換焦點的請求登記上但並不實際切換focus;隨後如果重量級組件聚焦了再處理request並徹底完成focus切換。

3.聚焦組件後馬上獲得隨後的鍵盤事件。

難 點是按用戶的實際想法,mouse_press後,馬上就要鍵盤拼寫,鍵盤的輸入應該target到mouse_press的jtextfield。根據 前面的分析,mouse_press響應中requestfocus/setfocus後並沒有意味著切換焦點已經完成。若實現上對於後續的鍵盤事件只是 簡單地根據JAVA的那個全局focus變量target,則這些鍵盤事件將不會target到期待的組件上。

鑑於3的分 析,java focus的實現是requestfocus時,如果這個請求按普遍規則一定通過而只是在等待時機,那麼在處理這個請求返回前就登記一個時間戳,在這個時 間戳之後在下一個requestfocus時間戳之前,EDT逐個取的keyevent都將target到該組件並登記,直到該組件徹底聚焦完成後,馬上 把這些keyevent dispatch。

4.需要支持TAB鍵等焦點遍歷操作。

這一點JAVA有一個遍歷模型,如下:

具體參照http://java.sun.com/javase/6/docs/api/java/awt/doc-files/FocusSpec.html

該要求並沒有難點。實現上只要對keyevent監聽,並根據規則進行合適處理即可。

沒有留言: