Buoy 是一个构建在 Swing 之上的免费用户界面(UI)工具包,它为 UI 开发人员提供了方便性和简单性。在本文中作者用一个简单的 fractal 用户界面程序,介绍了 Buoy 可以做什么、为什么这么做。
第一次尝试用 java 语言构建简单的用户界面时,我对 Swing 接口的复杂性感到有些惊奇。老实说,有点想打退堂鼓。最近,一个朋友向我提到,他使用的渲染程序 Art of Illusion(请参阅 参考资料)基于一个不同的工具包:Buoy。推荐它的原因之一是它的界面更友好。当他第一次提到它时,我以为他在谈 "BUI",而它与 GUI 这个名字的相似是故意的。在这里 B 代表 better(更好),但是名字 Buoy 并不是缩写。
Buoy 是免费的。实际上,它是公共的东西。它并没有在某个开放程度合理的许可下发布,实际上它根本不受任何许可控制。这意味着在任何用 Java 语言编写的能够运行 Buoy 的项目中都可以使用 Buoy,而不用考虑许可问题。因为提供了完整的源代码,所以这个工具包很轻易修改和扩展。本文基于 Buoy 1.3 发行版,要求读者对 Swing 有基本的了解,虽然不了解也能对付过去。
示例程序
我曾经尝试用 Swing 构建的第一个应用程序最后以失败告终。为了看出工具包之间的对比情况,我决定使用 Buoy 来构建这同一个程序。文章中的代码示例全部来自该程序的 Buoy 版本。程序生成了一些分形,具体地说,是迭代的分形。基本思想很简单:在平面上定义一系列的线条区段,从(0,0) 到(1,0),围绕任意一个单位线条定位。绘制这些区段之后,绘制同一套变形线条,用这个区段作为单位向量。做起来比说的更轻易,就像在图 1 中看到的。
图 1. 分形编辑器中的分形
这个程序的界面相当简单。它有一些界面小部件,有一个画布,在画布上绘制漂亮的图片,还支持用鼠标操纵图片。实际上,必须要做的全部工作就是操纵构成原始曲线的点,原始曲线会迭代地绘制出来。界面还有一个最小化的菜单;它可以打开和关闭文件,关闭窗口,或者把当前图像保存为 PNG 格式的文件。虽然简单,但是这个界面简要地提供了一个 Buoy 小部件的合理示例,还有相当数量对事件处理系统的体验。
程序实际的核心代码 —— 分形生成器 —— 已经写好了,这把这个示例变成一个很好的测试程序。当然,在更新它的过程中,我也发现并且修补了一些 bug。
发行包中包含示例程序的源代码,还有编译好的类文件和 Buoy 的 JAR 文件(单击本文顶部或底部的 Code 图标,下载 factal.tar)。包中还包含一个叫做 frac 的目录,里面包含一些示例分形。假如使用一台 UNIX 风格的机器,在路径中有 Java 编译器,那么只要运行 make 就能运行它。否则,需要设置 classpath 包含当前路径和 Buoy 的 JAR 文件所在的目录,然后运行 FractalViewer 类。在 Windows 系统上,正确的命令行应当是 java -classpath .;Buoy.jar FractalViewer。
sed -e s/J/B/g
在第一次深入研究代码时,也许会形成这样的印象:把 Swing 代码转换成 Buoy 代码简单得就像把 UI 元素名称中的字母 J 换成 B 一样简单。例如, FractalViewer 类不再扩展 JFrame;它现在扩展的是 BFrame。主要的小部件名称也可以照此推测得到。Spinner 和 slider 像以前一样有相同的名字,只是换了一个字母。 MenuBar(菜单条) 仍然由 Menus(菜单)构成,菜单则容纳 MenuItems。
有些命名转换略有不同。在 Swing 引用 BorderLayout 的地方,Buoy 有 BorderContainer。一般来说,Buoy 的命名转换相当统一,虽然不总是与 Swing 的命名一样。一个明显的区别是 Buoy 几乎组合了容器和布局治理器的概念;每种容器类型都知道自己如何布局。这大大简化了设计。例如,在分形生成器中使用的 LabelWidget 类是一个 BorderContainer;在 Swing 中,这可能是一个带有 BorderLayout 布局治理器的 JPanel。
但是,两者还是有许多相似之处。这对适应新东西有很大帮助。更重要的是,Buoy 构建在 Swing 之上。这意味着,一般来说,假如需要做的事不能轻松地用 Bouy 完成时,可以把 Buoy 对象传递给它包装的 Swing 对象。对于这种情况,假如想访问一些没有 Buoy 对应物的 Swing 对象,可以简单地把它包装在 AWTWidget 对象中,这个对象提供了非常薄的包装器,通过它,不仅 Buoy 自己的小部件,而且所有的小部件都能访问 Buoy 的小部件 API。例如,假如发现确实需要 GridBagLayout,可能就需要这样做。
例如,FractalPanel 类是一个 AWTWidget。在早期设计中, 它是 JPanel 的子类, 但实际上我并不需要 JPanel 代码。相反,我构建了包装定制类的类 FractalCanvas, 它本身是普通的 Canvas 类的一个子类。把它变成一个 AWTWidget,就可以在它上面利用 Buoy 高效的事件处理机制。
事件处理代码非常简单。在按下鼠标按钮时,通过 addEventLink() 的魔力,Buoy 发送一个新的 MousePRessedEvent 事件到 mousePressed() 函数。我忽略了按下哪个按钮这个问题,只考虑按住 shift 单击或普通单击。普通单击选择最靠近的点,而按住 shift 单击则重新把显示居中。然后,假如鼠标移动,那么每次 Buoy 注重到移动时都会开始发送 MouseDraggedEvent 事件。在处理这些事件时,FractalPanel 会生成自己的事件。
近观 PointChangedEvent
为了让一些讨论更加具体,请来看 PointChangedEvent。这是一个试验性的类,假如不喜欢它,那也只能怪老天了。这个类的想法是:让一个类来表示状态点中的变化。编辑器跟踪“当前”点 —— 也就是编辑器小部件目前正在编辑的点。可以用这些小部件或在分形面板中单击选择新的点,选择的是最靠近的点。
我得出这样一个结论:在代码中,大概有三类涉及到点的事件需要从一个类发送到另一个类。
一个是改变某个点的特征: POINT 事件类型。假如由编辑器发送,就是告诉分形改变原型线条上的点,并要求重画线条。假如由分形发送,则是告诉编辑器刚刚选中的点的特性。
下一个是选择某个点。可以按索引或位置进行选择。所以,假如只提供了索引或位置,那么构造函数会认为意图是填充其他值。有一点非凡的地方,点索引 -1 用来表示没有选中的点,所以必须用 -2表示编辑器正在寻找指定位置的点。这可能不漂亮,但是有效。
有点意思的是 Fractal 类响应 SELECT 事件的方式。假如成功地选择了一个点,就会发回一个新的 POINT 类型的 PointChangedEvent 事件,如清单 1 所示。
清单 1. 用事件回答事件