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