用 @Action 標注定義動作
@Action標注打算作為Action的actionPerformed方法。ApplicationContext.getActionMap方法建立了包含由某些類定義的每個@Action的Action對象的ActionMap。ActionMap將父類鏈接起來,并且每個Application的子類都有父類。這樣,所有的應用程序繼承了從Application基類定義的cut,copy,paste,delete和quit動作。
SingleFrameExample4.java定義了兩個@Actions,open和close:
/**
* Load the specified file into the textPane or popup an error
* dialog if something goes wrong.
*/
@Action public void open() {
JFileChooser chooser = new JFileChooser();
int option = chooser.showOpenDialog(getMainFrame());
if (option == JFileChooser.APPROVE_OPTION) {
File file = chooser.getSelectedFile();
textPane.setPage(file.toURI().toURL());
// error handling omitted for clarity
}
}
/**
* Replace the contents of the textPane with the value of the
* "defaultText" resource.
*/
@Action public void close() {
ApplicationContext ac = ApplicationContext.getInstance();
String ac.getResourceMap(getClass()).getString("defaultText");
textPane.setText(defaultText);
}
使用ApplicationContext的getActionMap方法來建立包含open和close的ActionMap。為了簡化查詢,通過建立一個私有的工具方法來完成,這和上面的查詢app類的ResourceMap相似。
private javax.swing.Action getAction(String actionName) {
ApplicationContext ac = ApplicationContext.getInstance();
return ac.getActionMap(getClass(), this).get(actionName);
}
/* We use getAction to set the action property of menu items
* and buttons and so on:
*/
openMenuItem.setAction(getAction("open"));
closeMenuItem.setAction(getAction("close"));
以這種方法定義的Action的缺省表示特性當從類的ResourceBundle載入時初始化。這樣,Action的text, mnemonic, tooltip (shortDescription), and shortcut被定義在SingleFrameExample4.properties的ResourceBundle里:
open.Action.text = &Open...
open.Action.accelerator = control O
open.Action.shortDescription = open a document
close.Action.text = &Close
close.Action.shortDescription = close the document
如果運行,將看見Action及其所有資源。
SingleFrameExample4 屏幕截圖
編寫動作可能困難的一個方面是處理那些潛在占用漫長時間或者阻塞的任務,比如文件系統或者網絡訪問。應用程序必須在后臺線程里完成這些任務,讓Swing事件分派線程(EDT)不會阻塞。在本例中,由JTextPane類處理異步文件載入。在許多情況下,應用程序開發者必須直接處理在后臺上的運行任務。應用程序框架Task類(基于SwingWorker)簡化了異步執行的動作的編寫。
產生后臺任務的動作
SwingWorker類在后臺線程上計算數值,然后在事件分派線程上調用完成方法。應用程序通過覆蓋SwingWorker done方法,或者增加一個PropertyChangeListener,或者覆蓋process方法,能夠監視SwingWorker并安全刷新GUI。它們當后臺線程調用publish()方法時收到中間結果。能夠測量它們自己進度的SwingWorker,設置進度特性通知PropertyChangeListener已經完成工作的百分比。
應用程序框架定義一個叫做Task的SwingWorker的子類,Task加入了一些特性讓后臺線程比較容易監視和管理。任務自動初始化從ResourceBundle載入的描述性特性。@Action方法能夠返回一個Task對象,而框架將執行后臺線程的Task。例如,這里的Task只是睡眠大約1500毫秒。
class DoNothingTask extends Task {
@Override protected Void doInBackground() throws InterruptedException {
for(int i = 0; i < 10; i++) {
setMessage("Working
[" + i + "]");
Thread.sleep(150L);
setProgress(i, 0, 9);
}
Thread.sleep(150L);
return null;
}
@Override protected void done() {
setMessage(isCancelled() ? "Canceled." : "Done.");
}
}
盡管DoNothingTask產生一個消息并周期地更新進度特性,但是大多數情況下它只是睡眠。顯而易見,這類事情不必在事件分派線程上執行。啟動在后臺線程上DoNothingTask的@Action可能像這樣編寫:
@Action Task JustDoNothing() {
return new DoNothingTask();
}
ActionExample4.java提供了一個孵化Task的@Action的有趣示例。它使用一個遍歷枚舉目錄里所有文件的Task(ListFileTask),每次發布大約10個文件。Task使用publish方法將中間結果交付給運行在EDT上的process方法。ActionExample4.java通過建立一個覆蓋process方法的應用程序的子類使用ListFilesTask來更新GUI:
private class DoListFiles extends ListFilesTask {
public DoListFiles(File root) { // ctor runs on the EDT
super(root);
listModel.clear();
}
@Override protected void process(List files) {
if (!isCancelled()) {
listModel.addAll(files);
}
}
}
private Task doListFiles = null;
@Action
public Task go() {
stop(); // stop the pending DoListFiles task (if any)
setStopEnabled(true);
File root = new File(rootTextField.getText());
return new DoListFiles(root);
}
@Action(enabledProperty = "stopEnabled")
public void stop() {
if ((doListFiles != null) && !doListFiles.isCancelled()) {
if (doListFiles.cancel(true)) {
doListFiles = null;
setStopEnabled(false);
}
}
}
如果運行ActionExample4,將會注意到當應用程序后臺正忙于枚舉文件時,GUI依然保持可響應狀態。本例以PropertyChangeListener監視由后臺Task產生的消息并在窗口底部將它們顯示出來(這段代碼在上面未列出)。監視后臺任務的狀態典型地由TaskMonitor類處理。SingleFrameExample5使用TaskMonitor類,它是下節的主題。
ActionExample4 屏幕截圖
本例也引入了綁定啟用@Action狀態為特性當前特性值的enabledProperty的標注參數。也存在一個指示GUI在后臺任務運行時應當阻塞的block標注參數(演示未體現)。對示例中@Action(block = Block.ACTION)意味著當后臺任務正運行時Action對象自身應當禁止。為了用模態對話框阻塞全部GUI,通過指定block = lock.APPLICATION來替代。ActionExample5.java演示了所有的可能性。
一個小而全的應用程序
SingleFrameExample5.java是迄今為止提供的最接近完整的應用程序。通過從JPL photojournal網址上下載某些非常巨大的火星探測器的圖像,它打算突出后臺任務的重要性。應用程序允許用戶一步一步下載圖像,并且搶先或者取消當前的下載/顯示任務。應用程序的結構是典型的,包括了用將通用任務和應用程序GUI連接的子類(ShowImageTask)特化通用Task類(本例中的LoadImageTask)。
在前一節描述了Task管理大多數基礎。幾個額外的細節值得在此強調:
- StatusBar使用共享的TaskMonitor來顯示當前"前臺”任務。
- 通過顯式終止圖像讀取操作(如果操作正在進行),LoadImageTask需要特別處理取消任務。通過覆蓋Task.done(不是cancel,它是最終結構)方法來處理。
- 正如@Action所為,通過缺省的TaskService執行一個Task的ready()方法,顯示第一幅圖像。
SingleFrameExample5屏幕截圖: Mars Rover(火星探測器)的著陸視圖
(第一部分,請參見:Swing應用程序框架(Swing Application Framework)API緒論(JSR-296)之一(翻譯))