程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Java Focus實現紀要一

Java Focus實現紀要一

編輯:關於JAVA

窗口系統一般包含一個桌面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. 最簡單的設計思路是提供一個setfocus調用API,該API來更新一個全局變量。EDT每次處理一個keyevent將根據當前全局變量進行target.最後給各類組件注冊合適的事件監聽,比如mouse press listener,在listen響應處理中調用setfocus.

要提供setfocus指定某組件聚焦。Setfocus一旦成功返回,該組件將接受後繼發生的所有的鍵盤事件,直到再次失去焦點。

然而問題是輕量級組件的容器是一個重量級組件,而在對輕量級組件調用setfocus時它的本地對等組件在系統中很可能還沒有獲得焦點。若實現上只是簡單的把java的全局變量更新了,那系統就會出現兩個聚焦組件:一個是底層系統承認的原來的某底層對等組件,一個是Java裡認為的現在的jtextfIEld.而本地系統始終把鍵盤事件派發到它認可的聚焦組件上,如果這個聚焦組件屬於另外一個C++進程,那麼這些鍵盤事件就會分發給C++進程,而不會被JVM的awt-Windows loop到。也就是說,雖然setfocus成功返回了,但並不代表隨後的鍵盤事件會target到這個組件上。所以不能采用這樣的設計思路。

盡管如此,實際上我們的組件的監聽一般是在mouse_press上。而這個鼠標按下動作各類底層操作系統處理時一般首先分發mouse_press底層事件,然後切換焦點,再分發focus事件。隨後的鍵盤事件會在底層切換焦點後分發出去。假如我們確定下來所有GUI應用只在EDT線程在mouse_press監聽處理中setfocus,實際上不會丟失鍵盤事件。但是如果我們要在其他情況,比如某worker 線程中setfocus,那麼setfocus就不再可靠了。

那麼,根據前面的分析,現在更改設計,在setfocus處理中調用底層API要求其重量級容器對應的本地對等組件聚焦並等到它確實聚焦完成了再更新JAVA的全局變量。但這樣也有問題。即使底層系統根據底層調用通知更新了focus,馬上還會繼續對可能的焦點切換操作響應(可以認為有一個系統進程在處理外設的響應),很有可能別的C++應用就在此時再要求focus,於是接著就更新了底層的focus登記;而我們的setfocus調用卻是在jvm進程的某線程中,顯然這就是個並發的情景,這樣,很有可能我們的對本地對等組件的通知發過去並返回了,那邊底層系統就馬上切換到了C++的某個組件focus,而我們的線程繼續更新Java的全局focus變量,於是雖然setfocus成功返回了,但並不代表隨後的鍵盤事件會target到這個組件上。

現在看來,除非我們同步這兩個進程,讓系統進程等待我們的調用setfocus的線程返回,顯然那樣是不合理的。(JAVA只能服從OS,不能讓OS服從Java.——出自《英雄亂語》J)。

鑒於以上的分析,根本無法實現一個setfocus來完成一個切換焦點的原子性操作。jre1.7的實現為不存在setfocus,而只有requestfocus,意思是只是將這個切換焦點的請求登記上但並不進行實際切換focus;隨後等收到相應的事件通知後再處理request並徹底完成一次focus切換。

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

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

鑒於以上的分析,jre1.7的實現是requestfocus時,只要這個請求滿足必要條件,那麼在其返回前就登記一個時間戳,在這個時間戳之後在下一個requestfocus時間戳之前,EDT 逐個取的keyevent都將target到該組件並登記,直到該組件徹底聚焦完成後,馬上把這些keyevent dispatch.

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

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

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

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved