正如已经开始看到的,事件处理是 Buoy 与 Swing 最明显的不同之处。事件处理提供了大量灵活性。Buoy 本身的事件集相当丰富,且允许您挑选自己感兴趣的事件,从任何小部件向其他对象发送事件。例如,如果想在 Swing 中捕获鼠标事件,捕获事件的类需要实现 MouseListener 接口。这个接口有 5 个函数需要实现,即使它们就是摆设也必须实现。而且必须使用接口提供的函数名称。更糟的是,函数必须是侦听器接口的公共部分;要么把这作为公共接口的一部分公开,要么创建一个什么都不做、只是包装事件侦听器代码的内部类。
在 Buoy 中,每个小部件都是 EventSource 。这意味着可以从每个小部件侦听事件。什么类型的事件呢?任何类型都可以。关键的函数是 addEventLink()。这允许您指定类、侦听器以及可选的方法。每当 EventSource 分派这个类或它的子类的事件时,侦听器都会接收到事件,要么是通过一个叫做 processEvent()的方法,要么是通过在开始调用 addEventLink() 时提供的方法名称。提供的函数不能接受参数,也不能接受与指定事件类型兼容的类的对象;父类和接口可以。
这是一个方便的设置。可以把不同的事件路由到不同的函数或相同的函数。例如,MousePressedEvent 和 MouseReleasedEvent 会被分别处理。在示例程序中,鼠标的按下、释放和拖动分别有不同的线程,如清单 2 所示。注意,这远远超过 Swing 的 MouseListener 所能做的。如果用 Swing 编程的话,就需要实现 MouseListener 和 MouseMotionListener 这两个接口。
清单2. 只挑感兴趣的事件
2 this.addEventLink(MouseReleasedEvent.class, this, "mouseReleased");
3 this.addEventLink(MouseDraggedEvent.class, this, "mouseDragged");
4 [...]
5 public void mouseReleased(WidgetMouseEvent ev) {
6 lastCenter = null;
7 dispatchEvent(new FractalChangedEvent(FractalChangedEvent.SLOW));
8 setAntiAliasing(true);
9 }
10
mouseReleased() 函数只有最少的工作要做。它只是在 mousePressed() 函数之后进行清理,告诉 Fractal 对象到了开始全面重绘的时候了。
Buoy 的事件处理还有另外一个有趣的特性。如果愿意的话,可以创建新的事件类型。一个事件类型就是一个类。确实如此。它甚至不需要继承任何类或实现什么。它就是一个类。如果这个类的对象被发送到 dispatchEvent(),那么它或它的父类的侦听器就会被调用。在 Swing 中也可以创建新的事件类型,但是完全要自己进行;必须设计 Listener 接口,还要编写自己的代码生成事件并侦听事件。在示例程序中,设计了 Fractal 类,演示了可以相对容易地把事件处理功能加到任何原有的类中。只需要声明一个 FractalViewer 类用来添加侦听器的事件源 EventSource。FractalViewer 类就会把来自事件源(例如 FractalEditor)的事件链接设置到它们的侦听器,如清单 3 所示。
清单3. 绑定
2 // Set up event handling relations.
3 addEventLink(WindowResizedEvent.class, this, "layoutChildren");
4 addEventLink(WindowResizedEvent.class, panel, "repaint");
5 tieControlEvents();
6 tieFractalEvents();
7 tiePanelEvents();
8 }
9
定制事件类一般是为了表示用户行为。在 Buoy 中,一般只通过用户行为,而不是系统接口生成事件 —— 除非自己想显式地调用 dispatchEvent() 自行生成事件。当分形对象以某种会造成字段更新的方式变化的时候,所有部件的控制面板都会得到通知。这样,我们发明一个新类 ParameterChangedEvent,用它表示参数已经变化。或者,如果变化的是选中的点的位置或是索引,就发送一个新的 PointChangedEvent。如果行为足够明显的话,那么事件处理器甚至不需要接受参数。作为事件处理的一个示例,请看清单 4,它演示了 FractalEditor 的 parameterChanged() 方法的开始部分。
清单 4. 参数发生了变化
2 FractalParameters p = ev.getParams();
3 int v = ev.getValue();
4 switch (ev.getType()) {
5 case ParameterChangedEvent.ALL:
6 maxSlider.setValue(p.getMaxIterations());
7 minSlider.setMaximum(p.getMaxIterations());
8 minSlider.setValue(p.getMinIterations());
9 maxSlider.setMinimum(p.getMinIterations());
10 zoomSlider.setValue(p.getZoom());
11 break;
12 [...]
13
在这个例子中,用事件处理系统把各种信息前后传递。在以前的版本中,每个类都有对其他每个类的引用,而且乱七八糟的 get 方法是按天排序的。而在目前的版本中,Buoy 的事件处理系统被用来处理各种通知。例如,FractalChangedEvent 类可以用来让代码的其他部分知道对分形的修改,可能是点的数量变化(编辑器用点的数量为点选择器定义正确的 SpinnerNumberModel),或者是需要重绘的通知,如清单 5 所示。
清单 5. 显然到了重绘的时候
2 switch (e.getType()) {
3 case FractalChangedEvent.REDRAW:
4 repaint();
5 break;
6 }
7 }
8
学习曲线
我曾经观察到,学习使用一个 GUI 工具,一下午的时间还不够长。对于 Buoy,我大概需要 6 个小时或者差不多一整个工作日。我确实从更有经验的 Buoy 用户那里得到了很棒的帮助。以前学习 Swing 的经验也是有帮助的,但实际上,我并不认为 Swing 的经验是必需的。Buoy 的文档相当好,而它的简单性确实有帮助。对于基本的 UI 事物,没有太多要学的东西。
Buoy 的文档并不像 Swing 文档那样完整,但是覆盖了许多细节,而且非常好。另外,源代码也在那儿,所以回答一些关于界面的简单问题非常容易。具有更完整的文档当然是好事。但是,既然这个项目放在 SourceForge 上,所以如果您愿意,您可以编写更多的东西为它做贡献。
Buoy 的学习曲线比起 Swing 是一个很大的优势。用相当简单的界面就能让大多数界面小部件正确工作。要使用 Buoy 文档中的一个示例:在 Swing 中,JList 要求要么使用静态列表,要么构建一个实现 ListModel 接口的新类。在 Buoy 中,只需向列表中添加项目;在大多数常见情况下,艰巨的工作已经由 Buoy 替您做了。
Buoy 相当小。完整的发行包中包含源代码、JAR文件和文档,总共不到 1 MB。代码的组织良好,可以容易地找到任何特定的代码段,如果需要调整设计,也不困难。