实战 Groovy(六)- 事件分派线程

实战 Groovy(6)- 事件分派线程

更新的详细信息请参考  java进阶网 www.javady.com


Swing 的缺点在于,它期望图形设计师能够应付多线程问题,而这是应该由软件工程师处理的,或者期望软件工程师理解图形设计和易用性问题。

我不可能在短短几段文字中讨论 Swing 应用程序中的线程问题这么复杂的主题。只需指出基本的 Swing 应用程序本质上是单线程的。所有活动都在事件分派线程 (EDT) 上进行。当用户抱怨 Swing 应用程序反应迟缓或完全没有反应时,往往是因为某个开发新手在 EDT 上执行长时间的计算密集型的数据库查询或 Web 服务调用 — 这个线程也负责处理屏幕刷新、菜单单击等。我们无意中在搜索按钮的 actionPerformed 处理函数上犯了同样的错误。(您可以看出多么容易犯这种错误)。

好在 javax.swing.SwingUtilities 类提供了几个方便的方法 — invokeAndWait() 和 invokeLater(),它们可以消除某些线程问题。可以使用这两个方法在 EDT 上同步或异步地执行操作。(关于 SwingUtilities 类的更多信息见 参考资料)。SwingBuilder 让我们很容易调用这两个方法,还提供了第三个选择:可以简便地生成新线程以执行处理时间长的操作。

要想在 EDT 上执行同步调用 (SwingUtilities.invokeAndWait()),可以把调用放在 edt{} 闭包中。要想在 EDT 上执行异步调用 (SwingUtilities.invokeLater()),就把调用放在 doLater{} 闭包中。但是,我想让您体验一下第三个选择:生成新线程来处理Search.byKeyword() 方法调用。为此,需要把代码放在 doOutside{} 闭包中,见清单 14:


清单 14. 使用 doOutside 闭包
				
def searchPanel = {
  swingBuilder.panel(constraints: BorderLayout.NORTH){
    searchField = textField(columns:15)
    button(text:"Search", actionPerformed:{ 
      doOutside{ 
        resultsList.listData = Search.byKeyword(searchField.text)
      }
    } )
  }
}

在像 Gwitter 这样简单的应用程序中,在 EDT 上执行 Web 服务调用很可能没什么不好的效果。但是,如果把这样的代码拿给 Swing 专家看,他们会用鄙视的目光看您,就像是您在快车道里慢慢地开车,或者把车停在商店停车场的残疾人专用车位上了。因为通过使用 SwingBuilder 很容易正确地处理线程,完全没有理由不这么做。

既然解决了线程问题,下面就让这个应用程序更漂亮一些。

给列表增加条纹效果

坦率地说,Gwitter 目前很难看。我要使用一些 HTML 代码做两个简单的改进,让外观和感觉好一些。JLabel 可以显示基本的 HTML。按清单 15 调整 Tweet.groovy 的 toString() 方法。JList 调用 toString() 方法显示结果。


清单 15. 在 toString() 方法中返回 HTML
				
class Tweet{
  String content
  String published
  String author
  
  String toString(){
    //return "${author}: ${content}"

    return """<html>
         <body>
           <p><b><i>${author}:</i></b></p>
           <p>${content}</p>
         </body>
       </html>"""
  }
}

下一个改进略微有点复杂。一种常用的 GUI 技巧是给长的列表或表格加上条纹效果。用不同的颜色显示奇数行和偶数行,这样读者更容易阅读。我在搜索引擎中搜索了 JList stripes,采纳了找到的第一篇文章中的建议。作者建议创建一个定制的DefaultListCellRenderer。我完全赞同他的意见并按原样借用他的示例代码(完整的文章见 参考资料)。

因为 Groovy 语法是 Java 语法的超集,所以可以把 Java 代码复制到 Groovy 文件中,不需要修改。如果有功能全面的构建系统,可以编译 Java 和 Groovy 代码,那么只需把这段代码留在 Java 文件中。但是,通过把代码文件的扩展名改为 .groovy,我可以运行所有未编译的 Gwitter 代码。我再次利用了 Java 语言和 Groovy 之间的无缝集成。可以在 Groovy 应用程序中不加修改地使用任何 Java 解决方案。

创建文件 StripeRenderer.groovy,添加清单 16 中的代码:


清单 16. 创建有条纹效果的 CellRenderer 
				
import java.awt.*;
import javax.swing.*;

class StripeRenderer extends DefaultListCellRenderer {
    public Component getListCellRendererComponent(JList list, Object value,
            int index, boolean isSelected, boolean cellHasFocus) {
        JLabel label = (JLabel) super.getListCellRendererComponent(list, value,
                index, isSelected, cellHasFocus);
       
        if(index%2 == 0) {
            label.setBackground(new Color(230,230,255));
        }
        
        label.setVerticalAlignment(SwingConstants.TOP);
        return label;
    }
}

有了 StripeRenderer 类之后,最后需要让 JList 使用它。按清单 17 调整 resultsPanel


清单 17. 在 JList 中添加定制的 CellRenderer 
				
def resultsPanel = {
  swingBuilder.scrollPane(constraints: BorderLayout.CENTER){
    //resultsList = list()
    resultsList = 
       list(fixedCellWidth: 380, fixedCellHeight: 75, cellRenderer:new StripeRenderer())
  }
}

在命令提示上再次输入 groovy Gwitter。搜索 thirstyhead 应该会产生图 16 所示的结果:


图 6. 有条纹效果的结果
实战 Groovy(六)- 事件分派线程 

我可以花更多时间美化 Gwitter 的外观和感觉,但是我希望您对大约 50 行 Swing 代码(当然不包括支持类)所实现的效果印象深刻。

结束语

正如本文中指出的,Groovy 并不能降低 Swing 内在的复杂性,但是它可以显著降低语法复杂性。这让您能够留出时间应付更重要的问题。

如果本文引起了您对 Groovy 和 Swing 的兴趣,您应该好好研究一下 Griffon 项目(见 参考资料)。它提供许多优于 Grails 项目的功能和惯例,但是它基于 SwingBuilder 和 Groovy 而不是 Spring MVC 和 Hibernate。这个项目仍然处于早期阶段(到编写本文时最新版本是 0.2),但是它已经很出色了,在 JavaOne 2009 上赢得了 Scripting Bowl for Groovy。另外,它提供的示例项目之一是 Greet,这是一个用 Groovy 实现的完整的 Twitter 客户机。

下一次,我将在 Gwitter 中添加一些必备特性:发布新 Tweet 的功能。在此过程中,您将学习如何处理基本的 HTTP 身份验证、执行 HTTP POST 以及使用与 XmlSlurper 相似的 ConfigSlurper。在此之前,我希望您探索应用 Groovy 的各种可能性。


下载

描述 名字 大小 下载方法
文章示例的源代码 j-groovy09299.zip 24KB HTTP

更新的详细信息请参考  java进阶网 www.javady.com