用 @Action 标注定义动作
@Action标注标记打算作为Action的ctionPerformed方法的方法。ApplicationContext.getActionMap方法建立了包含由某些类定义的每个@Action的Action对象的ActionMap。ActionMap将父类链接起来,并且每个Application的子类都有父类。这样,所有的应用程序继承了从Application基类定义的cut,copy,paste,delete和quit动作。
SingleFrameExample4.java定义了两个@Actions,open和close:
@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
}
}
@Action public void close() {
ApplicationContext ac = ApplicationContext.getInstance();
String ac.getResourceMap(getClass()).getString( " defaultText " );
textPane.setText(defaultText);
}
使用ApplicationContext的getActionMap方法来建立包含open和close的ActionMap。为了简化查询,通过建立一个私有的工具方法来完成,这和上面的查询app类的ResourceMap相似。
ApplicationContext ac = ApplicationContext.getInstance();
return ac.getActionMap(getClass(), this ).get(actionName);
}
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 documentclose.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只是睡眠大约150毫秒。
@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:
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(火星探测器)的着陆视图