欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java的Swing編程中使用SwingWorker線程模式及頂層容器

 更新時(shí)間:2016年01月21日 09:15:10   作者:zhangjunhd  
這篇文章主要介紹了在Java的Swing編程中使用SwingWorker線程模式及頂層容器的方法,適用于客戶端圖形化界面軟件的開(kāi)發(fā),需要的朋友可以參考下

使用SwingWorker線程模式

謹(jǐn)慎地使用并發(fā)機(jī)制對(duì)Swing開(kāi)發(fā)人員來(lái)說(shuō)非常重要。一個(gè)好的Swing程序使用并發(fā)機(jī)制來(lái)創(chuàng)建不會(huì)失去響應(yīng)的用戶接口-不管是什么樣的用戶交互,程序總能夠?qū)ζ浣o出響應(yīng)。創(chuàng)建一個(gè)有響應(yīng)的程序,開(kāi)發(fā)人員必須學(xué)會(huì)如何在Swing框架中使用多線程。
一個(gè)Swing開(kāi)發(fā)人員將會(huì)與下面幾類線程打交道:
(1)Initial threads(初始線程),此類線程將執(zhí)行初始化應(yīng)用代碼。
(2)The event dispatch thread(事件派發(fā)線程),所有的事件處理代碼在這里執(zhí)行。大多數(shù)與Swing框架交互的代碼也必須執(zhí)行這個(gè)線程。
(3)Worker threads(工作線程),也稱作background threads(后臺(tái)線程),此類線程將執(zhí)行所有消耗時(shí)間的任務(wù)。
開(kāi)發(fā)人員不需要在代碼中顯式的創(chuàng)建這些線程:它們是由runtime或Swing框架提供的。開(kāi)發(fā)人員的工作就是利用這些線程來(lái)創(chuàng)建具有響應(yīng)的,持久的Swing程序。
如同所有其他在Java平臺(tái)上運(yùn)行的程序,一個(gè)Swing程序可以創(chuàng)建額外的線程和線程池,這需要使用本文即將介紹的方法。本文將介紹以上這三種線程。工作線程的討論將涉及到使用javax.swing.SwingWorker類。這個(gè)類有許多有用的特性,包括在工作線程任務(wù)與其他線程任務(wù)之間的通信與協(xié)作。
1.初始線程
每個(gè)程序都會(huì)在應(yīng)用邏輯開(kāi)始時(shí)生成一系列的線程。在標(biāo)準(zhǔn)的程序中,只有一個(gè)這樣的線程:這個(gè)線程將調(diào)用程序主類中的main方法。在applet中初始線程是applet對(duì)象的構(gòu)造子,它將調(diào)用init方法;這些actions可能在一個(gè)單一的線程中執(zhí)行,或在兩個(gè)或三個(gè)不同的線程中,這些都依據(jù)Java平臺(tái)的具體實(shí)現(xiàn)。在本文中,我們稱這類線程為初始線程(initial threads)。
在Swing程序中,初始線程沒(méi)有很多事情要做。它們最基本的任務(wù)是創(chuàng)建一個(gè)Runnable對(duì)象,用于初始化GUI以及為那些用于執(zhí)行事件派發(fā)線程中的事件的對(duì)象編排順序。一旦GUI被創(chuàng)建,程序?qū)⒅饕蒅UI事件驅(qū)動(dòng),其中的每個(gè)事件驅(qū)動(dòng)將引起一個(gè)在事件派發(fā)線程中事件的執(zhí)行。程序代碼可以編排額外的任務(wù)給事件驅(qū)動(dòng)線程(前提是它們會(huì)被很快的執(zhí)行,這樣才不會(huì)干擾事件的處理)或創(chuàng)建工作線程(用于執(zhí)行消耗時(shí)間的任務(wù))。
一個(gè)初始線程編排GUI創(chuàng)建任務(wù)是通過(guò)調(diào)用javax.swing.SwingUtilities.invokeLater或javax.swing.SwingUtilities.invokeAndWait。這兩個(gè)方法都帶有一個(gè)唯一的參數(shù):Runnable用于定義新的任務(wù)。它們唯一的區(qū)別是:invokerLater僅僅編排任務(wù)并返回;invokeAndWait將等待任務(wù)執(zhí)行完畢才返回。
看下面示例:

SwingUtilities.invokeLater(new Runnable()) {
 public void run() {
  createAndShowGUI();
 }
}

 在applet中,創(chuàng)建GUI的任務(wù)必須被放入init方法中并且使用invokeAndWait;否則,初始過(guò)程將有可能在GUI創(chuàng)建完之前完成,這樣將有可能出現(xiàn)問(wèn)題。在其他的情況下,編排GUI創(chuàng)建任務(wù)通常是初始線程中最后一個(gè)被執(zhí)行的,所以使用invokeLater或invokeAndWait都可以。
為什么初始線程不直接創(chuàng)建GUI?因?yàn)閹缀跛械挠糜趧?chuàng)建和交互Swing組件的代碼必須在事件派發(fā)線程中執(zhí)行。這個(gè)約束將在下文中討論。
 2.事件派發(fā)線程
Swing事件的處理代碼在一個(gè)特殊的線程中執(zhí)行,這個(gè)線程被稱為事件派發(fā)線程。大部分調(diào)用Swing方法的代碼都在這個(gè)線程中被執(zhí)行。這樣做是必要的,因?yàn)榇蟛糠諷wing對(duì)象是“非線程安全的”。
可以將代碼的執(zhí)行想象成在事件派發(fā)線程中執(zhí)行一系列短小的任務(wù)。大部分任務(wù)被事件處理方法調(diào)用,諸如ActionListener.actionPerformed。其余的任務(wù)將被程序代碼編排,使用invokeLater或invokeAndWait。在事件派發(fā)線程中的任務(wù)必須能夠被快速執(zhí)行完成,如若不然,未經(jīng)處理的事件被積壓,用戶界面將變得“響應(yīng)遲鈍”。
如果你需要確定你的代碼是否是在事件派發(fā)線程中執(zhí)行,可調(diào)用javax.swing.SwingUtilities.isEventDispatchThread。
 3.工作線程與SwingWorker
當(dāng)一個(gè)Swing程序需要執(zhí)行一個(gè)長(zhǎng)時(shí)間的任務(wù),通常將使用一個(gè)工作線程來(lái)完成。每個(gè)任務(wù)在一個(gè)工作線程中執(zhí)行,它是一個(gè)javax.swing.SwingWorker類的實(shí)例。SwingWorker類是抽象類;你必須定義它的子類來(lái)創(chuàng)建一個(gè)SwingWorker對(duì)象;通常使用匿名內(nèi)部類來(lái)這做這些。
SwingWorker提供一些通信與控制的特征:
(1)SwingWorker的子類可以定義一個(gè)方法,done。當(dāng)后臺(tái)任務(wù)完成的時(shí)候,它將自動(dòng)的被事件派發(fā)線程調(diào)用。
(2)SwingWorker類實(shí)現(xiàn)java.util.concurrent.Future。這個(gè)接口允許后臺(tái)任務(wù)提供一個(gè)返回值給其他線程。該接口中的方法還提供允許撤銷后臺(tái)任務(wù)以及確定后臺(tái)任務(wù)是被完成了還是被撤銷的功能。
(3)后臺(tái)任務(wù)可以通過(guò)調(diào)用SwingWorker.publish來(lái)提供中間結(jié)果,事件派發(fā)線程將會(huì)調(diào)用該方法。
(4)后臺(tái)任務(wù)可以定義綁定屬性。綁定屬性的變化將觸發(fā)事件,事件派發(fā)線程將調(diào)用事件處理程序來(lái)處理這些被觸發(fā)的事件。
4.簡(jiǎn)單的后臺(tái)任務(wù)
下面介紹一個(gè)示例,這個(gè)任務(wù)非常簡(jiǎn)單,但它是潛在地消耗時(shí)間的任務(wù)。TumbleItem applet導(dǎo)入一系列的圖片文件。如果這些圖片文件是通過(guò)初始線程導(dǎo)入的,那么將在GUI出現(xiàn)之前有一段延遲。如果這些圖片文件是在事件派發(fā)線程中導(dǎo)入的,那么GUI將有可能出現(xiàn)臨時(shí)無(wú)法響應(yīng)的情況。
為了解決這些問(wèn)題,TumbleItem類在它初始化時(shí)創(chuàng)建并執(zhí)行了一個(gè)StringWorker類的實(shí)例。這個(gè)對(duì)象的doInBackground方法,在一個(gè)工作線程中執(zhí)行,將圖片導(dǎo)入一個(gè)ImageIcon數(shù)組,并且返回它的一個(gè)引用。接著done方法,在事件派發(fā)線程中執(zhí)行,得到返回的引用,將其放在applet類的成員變量imgs中。這樣做可以允許TumbleItem類立刻創(chuàng)建GUI,而不必等待圖片導(dǎo)入完成。
下面的示例代碼定義和實(shí)現(xiàn)了一個(gè)SwingWorker對(duì)象。

SwingWorker worker = new SwingWorker<ImageIcon[], Void>() {
 @Override
 public ImageIcon[] doInBackground() {
  final ImageIcon[] innerImgs = new ImageIcon[nimgs];
  for (int i = 0; i < nimgs; i++) {
   innerImgs[i] = loadImage(i+1);
  }
  return innerImgs;
 }
 
 @Override
 public void done() {
  //Remove the "Loading images" label.
  animator.removeAll();
  loopslot = -1;
  try {
   imgs = get();
  } catch (InterruptedException ignore) {}
  catch (java.util.concurrent.ExecutionException e) {
   String why = null;
   Throwable cause = e.getCause();
   if (cause != null) {
    why = cause.getMessage();
   } else {
    why = e.getMessage();
   }
   System.err.println("Error retrieving file: " + why);
  }
 }
};

     所有的繼承自SwingWorker的子類都必須實(shí)現(xiàn)doInBackground;實(shí)現(xiàn)done方法是可選的。
注意,SwingWorker是一個(gè)范型類,有兩個(gè)參數(shù)。第一個(gè)類型參數(shù)指定doInBackground的返回類型。同時(shí)也是get方法的類型,它可以被其他線程調(diào)用以獲得來(lái)自于doInBackground的返回值。第二個(gè)類型參數(shù)指定中間結(jié)果的類型,這個(gè)例子沒(méi)有返回中間結(jié)果,所以設(shè)為void。
使用get方法,可以使對(duì)象imgs的引用(在工作線程中創(chuàng)建)在事件派發(fā)線程中得到使用。這樣就可以在線程之間共享對(duì)象。
實(shí)際上有兩個(gè)方法來(lái)得到doInBackground類返回的對(duì)象。
(1)調(diào)用SwingWorker.get沒(méi)有參數(shù)。如果后臺(tái)任務(wù)沒(méi)有完成,get方法將阻塞直到它完成。
(2)調(diào)用SwingWorker.get帶參數(shù)指定timeout。如果后臺(tái)任務(wù)沒(méi)有完成,阻塞直到它完成-除非timeout期滿,在這種情況下,get將拋出java.util.concurrent.TimeoutException。
5.具有中間結(jié)果的任務(wù)
讓一個(gè)正在工作的后臺(tái)任務(wù)提供中間結(jié)果是很有用處的。后臺(tái)任務(wù)可以調(diào)用SwingWorker.publish方法來(lái)做到這個(gè)。這個(gè)方法接受許多參數(shù)。每個(gè)參數(shù)必須是由SwingWorker的第二個(gè)類型參數(shù)指定的一種。
可以覆蓋(override)SwingWorker.process來(lái)保存由publish方法提供的結(jié)果。這個(gè)方法是由事件派發(fā)線程調(diào)用的。來(lái)自publish方法的結(jié)果集通常是由一個(gè)process方法收集的。
我們看一下Filpper.java提供的實(shí)例。這個(gè)程序通過(guò)一個(gè)后臺(tái)任務(wù)產(chǎn)生一系列的隨機(jī)布爾值測(cè)試java.util.Random。就好比是一個(gè)投硬幣試驗(yàn)。為了報(bào)告它的結(jié)果,后臺(tái)任務(wù)使用了一個(gè)對(duì)象FlipPair。

private static class FlipPair {
 private final long heads, total;
 FlipPair(long heads, long total) {
  this.heads = heads;
  this.total = total;
 }
}

heads表示true的結(jié)果;total表示總的投擲次數(shù)。
后臺(tái)程序是一個(gè)FilpTask的實(shí)例:
private class FlipTask extends SwingWorker<Void, FlipPair> {
因?yàn)槿蝿?wù)沒(méi)有返回一個(gè)最終結(jié)果,這里不需要指定第一個(gè)類型參數(shù)是什么,使用Void。在每次“投擲”后任務(wù)調(diào)用publish:

@Override
protected Void doInBackground() {
 long heads = 0;
 long total = 0;
 Random random = new Random();
 while (!isCancelled()) {
  total++;
  if (random.nextBoolean()) {
   heads++;
  }
  publish(new FlipPair(heads, total));
 }
 return null;
}

由于publish時(shí)常被調(diào)用,許多的FlipPair值將在process方法被事件派發(fā)線程調(diào)用之前被收集;process僅僅關(guān)注每次返回的最后一組值,使用它來(lái)更新GUI:

protected void process(List pairs) {
 FlipPair pair = pairs.get(pairs.size() - 1);
 headsText.setText(String.format("%d", pair.heads));
 totalText.setText(String.format("%d", pair.total));
 devText.setText(String.format("%.10g",
   ((double) pair.heads)/((double) pair.total) - 0.5));
}

 6.取消后臺(tái)任務(wù)
調(diào)用SwingWorker.cancel來(lái)取消一個(gè)正在執(zhí)行的后臺(tái)任務(wù)。任務(wù)必須與它自己的撤銷機(jī)制一致。有兩個(gè)方法來(lái)做到這一點(diǎn):
(1)當(dāng)收到一個(gè)interrupt時(shí),將被終止。
(2)調(diào)用SwingWorker.isCanceled,如果SwingWorker調(diào)用cancel,該方法將返回true。
 7.綁定屬性和狀態(tài)方法
SwingWorker支持bound properties,這個(gè)在與其他線程通信時(shí)很有作用。提供兩個(gè)綁定屬性:progress和state。progress和state可以用于觸發(fā)在事件派發(fā)線程中的事件處理任務(wù)。
通過(guò)實(shí)現(xiàn)一個(gè)property change listener,程序可以捕捉到progress,state或其他綁定屬性的變化。
 
7.1The progress Bound Variable
Progress綁定變量是一個(gè)整型變量,變化范圍由0到100。它預(yù)定義了setter (the protected SwingWorker.setProgress)和getter (the public SwingWorker.getProgress)方法。
 
7.2The state Bound Variable
State綁定變量的變化反映了SwingWorker對(duì)象在它的生命周期中的變化過(guò)程。該變量中包含一個(gè)SwingWorker.StateValue的枚舉類型??赡艿闹涤校?br /> (1)PENDING
這個(gè)狀態(tài)持續(xù)的時(shí)間為從對(duì)象的建立知道doInBackground方法被調(diào)用。
(2)STARTED
這個(gè)狀態(tài)持續(xù)的時(shí)間為doInBackground方法被調(diào)用前一刻直到done方法被調(diào)用前一刻。
(3)DONE
對(duì)象存在的剩余時(shí)間將保持這個(gè)狀態(tài)。
需要返回當(dāng)前state的值可調(diào)用SwingWorker.getState。
 
7.3Status Methods
兩個(gè)由Future接口提供的方法,同樣可以報(bào)告后臺(tái)任務(wù)的狀態(tài)。如果任務(wù)被取消,isCancelled返回true。此外,如果任務(wù)完成,即要么正常的完成,要么被取消,isDone返回true。


使用頂層容器

Swing提供3種頂層容器類:JFrame,JDialog,JApplet。當(dāng)使用這三個(gè)類時(shí),你必須注意以下幾點(diǎn):
 
(1).為了顯示在屏幕上,每個(gè)GUI組件必須是包含層次(containment hierarchy)的一部分。包含層次是組件的一個(gè)樹型結(jié)構(gòu),最頂層的容器是它的根。

(2).每個(gè)GUI組件只能被包含一次。如果一個(gè)組件已經(jīng)在一個(gè)容器中,這時(shí)試圖將它加入到一個(gè)新的容器,則這個(gè)組件會(huì)從第一個(gè)容器移除,并加入到第二個(gè)容器中。

(3).每個(gè)頂層容器都有一個(gè)內(nèi)容面板(content pane),一般情況下,這個(gè)內(nèi)容面板會(huì)包含(直接或間接地)所有頂層容器GUI的可視組件。

(4).可以在頂層容器中加入一個(gè)菜單條(menu bar)。通常這個(gè)菜單條被放置在頂層容器中,但在內(nèi)容面板外。

1.頂層容器與包含層次
每個(gè)使用Swing組件的程序都至少有一個(gè)頂層容器。這個(gè)頂層容器是包含層次的根節(jié)點(diǎn)—這個(gè)層次會(huì)包含所有將在這個(gè)頂層容器中出現(xiàn)的Swing組件。
    通常情況下,一個(gè)單獨(dú)的基于Swing GUI的應(yīng)用程序至少有一個(gè)包含層次,且它的根節(jié)點(diǎn)是JFrame。舉例來(lái)說(shuō),如果一個(gè)應(yīng)用程序擁有一個(gè)窗口和兩個(gè)對(duì)話框,那么這個(gè)應(yīng)用程序?qū)?huì)有三個(gè)包含層次,也即會(huì)有三個(gè)頂層容器。一個(gè)包含層次將JFrame作為它的根節(jié)點(diǎn),兩外兩個(gè)包含層次各有一個(gè)JDialog作為它的根節(jié)點(diǎn)。
一個(gè)基于Swing組件的小程序(applet)至少含有一個(gè)包含層次,并且可以確定其中必有一個(gè)是以JApplet作為其根節(jié)點(diǎn)的。例如,一個(gè)小程序帶有一個(gè)對(duì)話框,則它會(huì)有兩個(gè)包含層次。在瀏覽器窗口中的組件將會(huì)置于一個(gè)包含層次,它的根節(jié)點(diǎn)是一個(gè)JApplet對(duì)象。對(duì)話框會(huì)有一個(gè)包含層次,它的根節(jié)點(diǎn)是一個(gè)JDialog對(duì)象。
 
2.將組件加入到內(nèi)容面板中
下面的代碼操作是上面的例子中得到frame的內(nèi)容面板并加入黃色標(biāo)簽:
frame.getContentPane().add(yellowLabel, BorderLayout.CENTER);
 
如代碼所示,必須先找到頂層容器的內(nèi)容面板,通過(guò)方法getContentPane實(shí)現(xiàn)。默認(rèn)的內(nèi)容面板是一個(gè)簡(jiǎn)單的中間容器,它繼承自JComponent,使用一個(gè)BorderLayout作為它的面板管理器。
定制一個(gè)內(nèi)容面板很簡(jiǎn)單—設(shè)置面板管理器或添加邊框。這里必須注意,getContentPane方法將返回一個(gè)Container對(duì)象,而不是JComponent對(duì)象。這意味著如果需要利用JComponent的部分功能,還必須將返回值進(jìn)行類型轉(zhuǎn)換或創(chuàng)建你自己的組件來(lái)作為內(nèi)容面板。我們的實(shí)例通常采用的是第二種方式. 因?yàn)榈诙N方法比較清楚明朗。 另一種我們有時(shí)會(huì)使用的方法就是簡(jiǎn)單地將一個(gè)自己定義組件添加進(jìn)內(nèi)容面板, 完全遮蓋住內(nèi)容面板。
如果你創(chuàng)建你自己的內(nèi)容面板, 那么請(qǐng)注意確認(rèn)它是不透明的. 一個(gè)不透明的JPanel將是一個(gè)不錯(cuò)的選擇. 注意, 默認(rèn)情況下JPanel的布局管理為FlowLayout, 你或許會(huì)想要用其它的布局管理器替換它。
為了使一個(gè)組件成為內(nèi)容面板, 你需要使用頂層容器的setContentPane方法, 例如:

//Create a panel and add components to it.
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.setBorder(someBorder);
contentPane.add(someComponent, BorderLayout.CENTER);
contentPane.add(anotherComponent, BorderLayout.PAGE_END);
//Make it the content pane.
//contentPane.setOpaque(true);
topLevelContainer.setContentPane(contentPane);

 
注意: 不要使用透明的容器作為內(nèi)容面板, 如JScrollPane, JSplitPane和JTabbedPane. . 一個(gè)透明的內(nèi)容面板將導(dǎo)致組件混亂. 盡管你可以使任何的透明的Swing組件通過(guò)setOpaque(true)方法來(lái)使其不透明化, 但當(dāng)一些組件被設(shè)置成完全不透明后看上去會(huì)不太對(duì)勁. 例如, 一個(gè)標(biāo)簽面板.
 
3.添加一個(gè)菜單欄 (Adding a Menu Bar)
從理論上來(lái)講每一個(gè)頂層容器都可以有一個(gè)菜單欄. 但事實(shí)表明菜單欄僅出現(xiàn)于Frame或者Applet中. 為達(dá)到添加一個(gè)菜單欄到頂層容器, 你需要?jiǎng)?chuàng)建一個(gè)JMenuBar對(duì)象, 組裝上一些菜單, 然后呼叫setJMenuBar方法. TopLevelDemo實(shí)例通過(guò)以下代碼添加一個(gè)菜單欄到它的Frame中.

frame.setJMenuBar(cyanMenuBar);

 
4.根容器 (The Root Pane)
每個(gè)頂層容器都依賴于一個(gè)隱式的稱為根容器的中間容器. 這個(gè)根容器管理著內(nèi)容面板和菜單欄, 并且連同兩個(gè)或者兩個(gè)以上的其它容器(見(jiàn)圖中Layered Pane等). 你通常不需要了解關(guān)于使用Swing組件根容器方面的知識(shí).  然而, 如果你想截獲鼠標(biāo)的點(diǎn)擊或者在多重組件上進(jìn)行繪畫動(dòng)作, 那么你需要知曉根容器.

上文已經(jīng)講述了關(guān)于內(nèi)容面板與可選的菜單欄的內(nèi)容,此處不再?gòu)?fù)述. 根容器中包含的另外兩個(gè)組件, 是布局面板和玻璃面板. 布局面板直接包含菜單欄和內(nèi)容面板, 并且允許你對(duì)所添加的其它組件進(jìn)行Z坐標(biāo)排序. 玻璃面板通常用來(lái)截獲發(fā)生在頂層中的輸入動(dòng)作, 并且同樣可以用來(lái)在多重組件上進(jìn)行繪畫.

相關(guān)文章

最新評(píng)論