objectref |
... |
objectref |
objectref |
... |
"s2"_ref |
objectref |
objectref |
... |
objectref |
... |
???????????? |
... |
java.awt.Component.requestFocusInWindow
http://www.aygfsteel.com/Files/jht/MyScreenSnap_2.0.zip作者簡(jiǎn)介
徐皓,北京航空航天大學(xué)計(jì)算機(jī)系本科生,你可以通過ertri@163.com與他聯(lián)系。
正文
不靈敏的圖形用戶界面會(huì)降低應(yīng)用程序的可用性。當(dāng)以下現(xiàn)象出現(xiàn)的時(shí)候,我們通常說這個(gè)用戶界面反應(yīng)不靈敏。
這些現(xiàn)象在很大程度上與事件的處理方法相關(guān),而在編寫Swing應(yīng)用程序的時(shí)候,我們幾乎必然要編寫方法去響應(yīng)鼠標(biāo)點(diǎn)擊按鈕,鍵盤回車等事件。在這些方法中我們要編寫一些代碼,在運(yùn)行時(shí)去觸發(fā)一些動(dòng)作。常見動(dòng)作包括查找,更新數(shù)據(jù)庫(kù)等。在這篇文章中通過對(duì)一個(gè)實(shí)例的分析,介紹了一些基本概念,常見的錯(cuò)誤以及提出了一個(gè)解決方案。
event-dispatching thread
我們一定要記住,事件響應(yīng)方法的代碼都是在event-dispatching thread中執(zhí)行的,除非你啟用另一個(gè)線程。
那么,什么是event-dispatching thread呢?在《Java Tutorial》[1]中,作者給出了一條單一線程規(guī)則:一旦一個(gè)Swing組件被實(shí)現(xiàn)(realized),所有的有可能影響或依賴于這個(gè)組件的狀態(tài)的代碼都應(yīng)該在event-dispatching thread中被執(zhí)行。而實(shí)現(xiàn)一個(gè)組件有兩種方式:
單一線程規(guī)則的根源是由于Swing組件庫(kù)的大部分方法是對(duì)多線程不安全的,盡管存在一些例外。這些例外的情況可以在《Java Tutorial》[1]的相關(guān)章節(jié)找到,這里不再展開。
為了支持單一線程模型,Swing組件庫(kù)提供了一個(gè)專門來完成這些與Swing組件相關(guān)的操作的線程,而這一線程就是event-dispatching thread。我們的事件響應(yīng)方法通常都是由這一線程調(diào)用的,除非你自己編寫代碼來調(diào)用這些事件響應(yīng)方法。在這里初學(xué)者經(jīng)常犯的一個(gè)錯(cuò)誤就是在事件響應(yīng)方法中完成過多的與修改組件沒有直接聯(lián)系的代碼。其最有可能的效果就是導(dǎo)致組件反應(yīng)緩慢。比如以下響應(yīng)按鈕事件的代碼:
String str = null;
this.textArea.setText("Please wait...");
try {
//do something that is really time consuming
str = "Hello, world!";
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.textArea.setText(str); ? ?
執(zhí)行之后的效果就是按鈕似乎定住了一段時(shí)間,直到Done.出現(xiàn)之后才彈起來。原因就是Swing組件的更新和事件的響應(yīng)都是在event-dispatching thread中完成的,而事件響應(yīng)的時(shí)候,event-dispatching thread被事件響應(yīng)方法占據(jù),所以組件不會(huì)被更新。而直到事件響應(yīng)方法退出時(shí)才有可能去更新Swing組件。
為了解決這個(gè)問題,有人也許會(huì)試圖通過調(diào)用repaint()方法來更新組件:
final String[] str = new String[1];
this.jTextArea1.setText("Please wait...");
this.repaint();
try {
Thread.sleep(1000L);
}catch(InterruptedException e) {
e.printStackTrace();
}
str[0] = "Done.";
jTextArea1.setText(str[0]);
但是這一個(gè)方法沒有起到預(yù)期的作用,按鈕仍然定住一段時(shí)間,在察看了repaint()方法的源代碼之后就知道原因了。
PaintEvent e = new PaintEvent(this, PaintEvent.UPDATE,
new Rectangle(x, y, width, height));
Toolkit.getEventQueue().postEvent(e); ? ? ? ?
repaint()方法實(shí)際上是在事件隊(duì)列里加了一個(gè)UPDATE的事件,而沒有直接去重畫組件,而且這一個(gè)事件只能等待當(dāng)前的事件響應(yīng)方法結(jié)束之后才能被分配。因此只有繞過分配機(jī)制直接調(diào)用paint方法才能達(dá)到目的。
final String[] str = new String[1];
this.jTextArea1.setText("Please wait...");
this.paint(this.getGraphics());
try {
Thread.sleep(1000L);
}catch(InterruptedException e) {
e.printStackTrace();
}
str[0] = "Done.";
jTextArea1.setText(str[0]);
這樣卻是實(shí)現(xiàn)了更新,但是還存在著以下的問題。雖然從感覺上,按鈕已經(jīng)彈起來了,但是在Done.出現(xiàn)之前,我們卻無法按下這個(gè)按鈕。可以說按鈕還是定住了,只不過定在了彈起的狀態(tài)。調(diào)用重繪方法無法從根本上解決問題,因此我們需要尋求其他的方法。
使用多線程
有效的解決方法是使用多線程。首先看一看一個(gè)更好的解決方案,這一方案是在參考《Rethinking Swing Threading》[3]的一個(gè)程序片段完成的:
final String[] str = new String[1];
this.jTextArea1.setText("Please wait...");
this.repaint();
new Thread() {
public void run() {
try {
Thread.sleep(1000L);
}catch(InterruptedException e) {
e.printStackTrace();
}
str[0] = "Done.";
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
jTextArea1.setText(str[0]);
}
});
}
}.start();
在這個(gè)程序中,要花費(fèi)大量時(shí)間的操作被放到另一個(gè)線程當(dāng)中,從而使事件響應(yīng)方法能快速返回,event-dispatching thread就可以更新UI和響應(yīng)其它事件了。注意到這個(gè)程序使用了invokeLater()方法。invokeLater()方法的作用是讓event-dispatching thread去運(yùn)行制定的代碼。當(dāng)然也可以不使用invokeLater()方法,但是這樣就違背了單一線程原則,同時(shí)帶來了一定程度的相對(duì)多線程的不安全性。到現(xiàn)在,解決方案似乎是完美的了,但是我們看一看在原來的程序添加下面的代碼,盡管我們通常不這樣做。
public void paint(java.awt.Graphics g) {
super.paint(g);
g.drawRect(1, 1, 100, 100);
}
我們會(huì)發(fā)現(xiàn)以前畫的矩形被覆蓋了一部分,原因是由于我們沒用重畫這一個(gè)矩形,因此在結(jié)尾加上對(duì)repaint()方法的調(diào)用。
final String[] str = new String[1];
this.jTextArea1.setText("Please wait...");
this.repaint();
new Thread() {
public void run() {
try {
Thread.sleep(1000L);
}catch(InterruptedException e) {
e.printStackTrace();
}
str[0] = "Done.";
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
jTextArea1.setText(str[0]);
repaint();
}
});
}
}.start();
如果你認(rèn)為這段代碼過于缺乏可讀性,通過在《Java Tutorial》[1]里面介紹的SwingWorker來簡(jiǎn)化編程的方法。可以通過實(shí)現(xiàn)一個(gè)construct()方法來實(shí)現(xiàn)花費(fèi)大量時(shí)間的操作和重寫finished()方法來完成組件更新的工作。
this.jTextArea1.setText("Please wait...");
final SwingWorker worker = new SwingWorker() {
public Object construct() {
try {
Thread.sleep(1000L);
}catch(InterruptedException e) {
e.printStackTrace();
}
return "Done.";
}
public void finished() {
jTextArea1.setText(getValue().toString());
repaint();
}
};
worker.start();
在《Rethinking Swing Threading》[3],作者將以上的編程方式稱為同步方式。另外作者提出了一個(gè)通過消息機(jī)制來實(shí)現(xiàn)相同功能的更清晰,但是需要編寫更多代碼的"異步"的方法。
結(jié)論
總之,我們?cè)诰帉懯褂肧wing組件的程序是要記住以下幾點(diǎn):
1、不要過多地占用event-dispatching thread;
2、與更新組件相關(guān)的代碼要使用event-dispatching thread去執(zhí)行;
3、要更新組件。
編寫反應(yīng)靈敏的圖形用戶界面還需要考慮很多問題,以上只是最基本的一部分。歡迎有興趣的讀者來信進(jìn)行討論。
打開注冊(cè)表編輯器,進(jìn)入主鍵[HKEY_CURRENT_USER\Software\Microsoft\Command Processor],將“CompletionChar”鍵值設(shè)置為9。
先來看一個(gè)簡(jiǎn)單例子
1、資源文件錯(cuò)誤信息來源(其格式為 key = value )
???
?? error.test = this is a test error.
2、JSP頁(yè)面中用于顯示錯(cuò)誤信息標(biāo)簽
??
?? <html:errors property="testerror"/>
3、ActionFormBean的validate()方法中產(chǎn)生錯(cuò)誤信息
?? ActionErrors error = new ActionErrors();
?? error.add("testerror",new ActionMessage("error.test"))
??
?? return error;
?
這個(gè)例子的功能就是在ActionForm Bean的validate()方法中產(chǎn)生一條名為:testerror的錯(cuò)誤信息,錯(cuò)誤信息息是資源文件中key為error.test的值。然后在頁(yè)面上用html:errors標(biāo)簽輸出testerror這條錯(cuò)誤信息。
這是最常用的一種功能,所有的錯(cuò)誤信息都在資源文件里面。
有人會(huì)問,錯(cuò)誤信息只能存放在資源文件中嗎,其實(shí)不是這樣。不需要資源文件也可以產(chǎn)生錯(cuò)誤信息。
我們?cè)賮砜匆幌翧ctionMessage的另一種構(gòu)造方法:
ActionMessage(String key,boolean isresource)
如果isresource值為true,則表示key是資源文件中的key,產(chǎn)生的消息就是與key相對(duì)應(yīng)的消息
如果isresource值為false,則表示key為一條普通的消息。
如果上面的error.add改為error.add("testerror",new ActonMessage("這是一條自定義消息",false",));那么頁(yè)面上顯示的將是:這是一條自定義消息.
另外還可以用ActionMessage產(chǎn)生復(fù)合消息,比如我們要輸出:xxx不能用作用戶名,其中xxx是一個(gè)變量。
首先我們?cè)谫Y源文件中加一個(gè)條復(fù)合消息
testmsg = {0}不能用作用戶名。這里{0}是要被替換的參數(shù)。
我們?cè)賮砜匆幌翧ctionMessage的另一中構(gòu)造方法
ActionMessage(String key,Object value0);
也就是說用value0的值來替換{0}
我們修改error.add為error.add("testerror",new ActonMessage("testmsg","毛澤東"))
那么JSP頁(yè)面上將顯示:毛澤東不能用作用戶名。
當(dāng)然在一條復(fù)合消息中也可帶多個(gè)參數(shù),參數(shù)依次為{0},{1},{2}或更多
例如:loginUser = 用戶名:{0} 姓名:{1} 登錄次數(shù):{2}.....
那么在產(chǎn)生錯(cuò)誤消息時(shí)就用new ActionMessage(String key,Object value0,Object value1,Object? value2.....)或者使用對(duì)象數(shù)組new ActionMessage(String key,Object[] values)
String[] detail = {"Admin","王晶","12"};
error.add("testerror",new ActionMessage("loginUser",detail))
備忘: