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:
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()
方法显示结果。
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 中的代码:
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
:
JList
中添加定制的 CellRenderer
def resultsPanel = { swingBuilder.scrollPane(constraints: BorderLayout.CENTER){ //resultsList = list() resultsList = list(fixedCellWidth: 380, fixedCellHeight: 75, cellRenderer:new StripeRenderer()) } } |
在命令提示上再次输入 groovy Gwitter
。搜索 thirstyhead 应该会产生图 16 所示的结果:
我可以花更多时间美化 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 |