隨筆-46  評(píng)論-64  文章-2  trackbacks-0

          作者簡(jiǎn)介

          徐皓,北京航空航天大學(xué)計(jì)算機(jī)系本科生,你可以通過ertri@163.com與他聯(lián)系。

          正文

          不靈敏的圖形用戶界面會(huì)降低應(yīng)用程序的可用性。當(dāng)以下現(xiàn)象出現(xiàn)的時(shí)候,我們通常說這個(gè)用戶界面反應(yīng)不靈敏。

          1. 不響應(yīng)事件的現(xiàn)象;
          2. 沒有更新的現(xiàn)象
          [@more@]

          這些現(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è)組件有兩種方式:

          1. 對(duì)頂層組件調(diào)用show(), pack(), 或者setVisible(true);
          2. 將一個(gè)組件加到一個(gè)已經(jīng)被實(shí)現(xiàn)的容器中。

          單一線程規(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)行討論。

          posted on 2008-03-13 17:53 jht 閱讀(585) 評(píng)論(0)  編輯  收藏 所屬分類: J2SE
          主站蜘蛛池模板: 南木林县| 资兴市| 景泰县| 玉溪市| 临朐县| 麻栗坡县| 鲁山县| 东乌| 苏尼特右旗| 汾阳市| 治县。| 龙川县| 获嘉县| 平武县| 平山县| 盱眙县| 乐陵市| 阳新县| 广元市| 措勤县| 驻马店市| 横峰县| 顺平县| 剑河县| 海宁市| 安远县| 拉孜县| 高淳县| 镇安县| 封丘县| 两当县| 漠河县| 宜宾市| 海兴县| 西丰县| 铜鼓县| 谷城县| 上林县| 汉源县| 阳西县| 玉溪市|