Swing應用程序處理函數(shù)
Swing的事件處理過程為:事件調(diào)度線程(Event Dispatch Thread)從事件隊列(EventQueue)中獲取底層系統(tǒng)捕獲的原生事件,如鼠標、鍵盤、焦點、PAINT事件等。接著調(diào)用該事件源組件的 dispachEvent。該方法過濾出特殊事件后,調(diào)用processEvent進行處理。processEvent方法根據(jù)事件類型調(diào)用注冊在這個組件上的相應事件處理器函數(shù)。事件處理器函數(shù)根據(jù)這些事件的特征,判斷出用戶的期望行為,然后根據(jù)期望行為改變組件的狀態(tài),然后根據(jù)需要刷新組件外觀,觸發(fā)帶有特定語義的高級事件。此事件繼續(xù)傳播下去,直至調(diào)用Swing應用程序注冊在該組件上的處理器函數(shù).
事件調(diào)度線程在Swing應用程序事件處理函數(shù)actionPerformed沒有完成之前是不能處理下一個事件的,如果Swing應用程序處理函數(shù)是一個時間復雜的任務(比如查詢數(shù)據(jù)庫并將結果顯示到表格中),后面包括PAINT事件將在長時間內(nèi)得不到執(zhí)行。由于PAINT事件負責將界面更新,所以這就使用戶界面失去響應。
打一個比方,事件處理線程就像進入某城唯一的單行道一樣,事件相當于汽車。有種PAINT汽車負責為城市運輸非常重要的生活物資。但是有一天,PAINT 前面有一輛汽車突然壞掉了,司機下來修車。但是這車太難修,一修就是幾天,結果后面的PAINT汽車無法前進,物資無法按時運到城里。市民急了,市長雖然不停的打電話催PAINT公司,但即使PAINT公司多添加幾輛車也沒用。由于進城的唯一條路被那輛車給占著,所以再多的PAINT車也只能堵在路上。
不了解Swing的這種事件處理模型的人往往將時間復雜的任務放在處理函數(shù)中完成,這是造成Swing應用程序速度很慢的原因。用戶觸發(fā)這個動作,用戶界面就失去了響應,于是給用戶的感覺就是Swing太慢了。其實這個錯誤是程序員造成的,并不是Swing的過失。
那么如何避免這個問題,編寫響應速度快的Swing應用程序呢?在SwingWorker的javadoc中有這樣兩條原則:
◆Time-consuming tasks should not be run on the Event Dispatch Thread. Otherwise the application becomes unresponsive. 耗時任務不要放到事件調(diào)度線程上執(zhí)行,否則程序就會失去響應。
◆Swing components should be accessed on the Event Dispatch Thread only. Swing組件只能在事件調(diào)度線程上訪問。
因此處理耗時任務時,首先要啟動一個專門線程,將當前任務交給這個線程處理,而當前處理函數(shù)立即返回,繼續(xù)處理后面未決的事件。這就像前面塞車的例子似的,那個司機只要簡單的把車開到路邊或者人行道上修理,整個公路系統(tǒng)就會恢復運轉(zhuǎn)。
其次,在為耗時任務啟動的線程訪問Swing組件時,要使用SwingUtilties. invokeLater或者SwingUtilities.invokeAndWait 來訪問,invokeLater和invokeAndWait的參數(shù)都是一個Runnable對象,這個Runnable對象將被像普通事件處理函數(shù)一樣在事件調(diào)度線程上執(zhí)行。這兩個函數(shù)的區(qū)別是,invokeLater不阻塞當前任務線程,invokeAndWait阻塞當前線程,直到Runnable 對象被執(zhí)行返回才繼續(xù)。在前面塞車的例子中,司機在路邊修車解決了塞車問題,但是他突然想起來要家里辦些事情,這時他就可以打個電話讓家里開車來。假如修車不受這件事情的影響,比如叫家人送他朋友一本書,他可以繼續(xù)修車,這時就相當于invokeLater;假如修車受影響,比如缺少某個汽車零件,叫家人給他送過來,那么在家人來之前,他就沒法繼續(xù)修車,這時就相當于invokeAndWait。
下面舉一個例子說明這兩點,比如按下查詢按鈕,查詢數(shù)據(jù)量很大的數(shù)據(jù)庫,并顯示在一個表中,這個過程需要給用戶一個進度提示,并且能動態(tài)顯示表格數(shù)據(jù)動態(tài)增加的過程。假設按鈕的處理函數(shù)是myButton_actionPerformed,則:
- voidmyButton_actionPerformed(ActionEventevt){
- newMyQueryTask().start();
- }
- publicclassMyQueryTaskextendsThread{
- publicvoidrun(){
- //查詢數(shù)據(jù)庫
- finalResultSetresult=...;
- //顯示記錄
- for(;result.next();){
- //往表的Model中添加一行數(shù)據(jù),并更新進度條,注意這都是訪問組件
- SwingUtilities.invokeLater(newRunnable(){
- publicvoidrun(){
- addRecord(result);
- }
- });
- }
- ....
- }
- voidaddRecord(ResultSetresult){
- //往表格中添加數(shù)據(jù)
- jTable.add....
- //更新進度條
- jProgress.setValue(....);
- }
- }
【編輯推薦】