1. Draw2d 设计思想
Draw2d是一个宿主在SWT Composite控件中的轻量级的构件(widge)系统。一个Draw2d应用程序由一个 SWT Composite控件, 一个轻量级系统, 以及其内容(figures)组成。Figures是Draw2d的建造块。下面的“Hello World”例子程序演示了如何实现一个最简单的draw2d程序。
Listing for "Hello World"
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.SWT;
import org.eclipse.draw2d.*;
import org.eclipse.draw2d.geometry.*;
public class HelloWorld {
public static void main(String args[]){
Shell shell = new Shell();
shell.open();
shell.setText("Draw2d Hello World");
LightweightSystem lws = new LightweightSystem(shell);
IFigure label = new Label("Hello World");
lws.setContents(label); //设置内容
Display display = Display.getDefault();
while (!shell.isDisposed ()) {
if (!display.readAndDispatch ())
display.sleep ();
}
}
}
运行这个程序的结果如下:
在上面例子程序的背后究竟发生了什么呢?先看下面的Draw2d设计图:
LightweightSystem是Draw2d的粘合剂。开发人员提供SWT composite和想要绘制的figure层次的根,然后LightweightSystem用默认值启动剩余的过程。LightweightSystem依赖Canvas,并含有一个EventDispatcher和一个UpdateManager。LightweightSystem本身是一个事件监听器,能够监听多种事件。在将Canvas传递给LightweightSystem(通常通过LightweightSystem的构造函数)时,LightweightSystem就将自身作为监听器注册到Canvas,当Canvas产生各种LightweightSystem感兴趣的事件后,作为监听器,LightweightSystem中定义的方法会被调用。在LightweightSystem中定义的各个监听器方法中,要将来自于SWT的事件转换成draw2d事件并通过EventDispatcher将draw2d事件分配到当前被选中的图形元素Figure。每个图形元素Figure都可以作为监听器容器。
SWT是IBM开发的一套GUI组件,与java中的SWING是同一类东西,只不过开发商不同罢了。在SWT中有一个名位Canvas的类,这个类是一个控件,根据它的名字,顾名思义,它是为绘图提供绘图表面;它引发各种鼠标事件和键盘事件,它是一个监听器容器,可以将它引发的交互事件发送给感兴趣的监听器。
LightweightSystem是draw2d中一个非常重要的类,它是draw2d能够运行的调度中枢,这个类的主要作用是:
1, 持有Canvas控件的引用。
2, 使自身成为交互事件监听器,并将自身注册到Canvas中;当Canvas中引发各种交互事件时,使得LightweightSystem能够得到通知。
3, 持有事件转发器。当LightweightSystem从Canvas中获得交互事件通知后,它直接将该事件转交给事件转发器,事件转发器首先将系统事件转换成draw2d自定义的内部事件,然后根据draw2d的状态对该事件进行分发。
4, 持有更新管理器。当Canvas无效并要求重绘时,LightweightSystem会从来自Canvas的重绘事件中获取无效矩形区域并请更新管理器更新该无效矩形区域;当draw2d中的某个图形元素无效时,draw2d也会请更新管理器更新无效的图形元素。但是,图形元素无效与控件无效是两个概念;当控件无效时,控件会引发OnPaint事件;当图形元素无效时,系统并不会引发OnPaint事件。
5, 持有根图形元素(RootFigure)。根图形元素在draw2d中有着非常重要的地位和作用,了解根图形元素的地位和作用,对于了解draw2d是非常重要的。根图形元素完全覆盖在Canvas上(与canvas的工作区域的大小一样大),它的背景色决定了应用程序的背景色。根图形元素处于图形元素树型层次的最顶端,它只有一个孩子,这个孩子就是要显示的内容。
Draw2d中另一个非常重要的接口是IFigure, 它表示图形元素抽象,所有的可以用图形显示的东西都要实现这个接口。Figure是IFigure的基本实现。Figure类的设计,涉及到一个非常著名的模式:Composite模式。
在Draw2d中,Figure中的所有的坐标都是int型的,没有使用float或double类型。我当时也是非常奇怪,但仔细想想,其实也是可以理解的,至于具体原因,我会在以后的文章中详细解释。
Draw2d只提供了显示模型的视图类,并没有提供与编辑相关的任何功能;如果开发人员打算开发一个不需要对模型执行编辑的图形显示软件,那么使用draw2d是合适的;如果要执行编辑动作,就需要同时使用GEF和draw2d了。
2. 几个相关模式
2.1 MVC模式
提到MVC模式,几乎所有的开发人员都会说,我知道,它不就是“模型-视图-控制器”吗?都知道,M表示Model, V表示视图,C表示Controller。模型要负责提供数据,视图要负责呈现模型(通常用图形元素表示模型中的数据),控制器要负责创建图形元素并修改模型。
确实如此,说起来,MVC是一个很简单的东西,不就是把数据、图形、控制分开吗?但说起来容易,做起来可就不容易了。在具体做程序时,通常的情况是,模型、视图和控制器是三位一体的,往往是一个类表示三个东西。一个类既是模型,也是视图,也是控制器。在draw2d中,大量使用了模型-视图的概念,但没有控制器,因为draw2d库的主要目的是显示模型,至于如何编辑模型是GEF的职责,如何定义模型并将模型映射到draw2d上是应用软件开发人员色职责。在draw2d中定义的所有从Figure派生的图形元素都是视图类,它们都是用来呈现模型的;当同GEF一道使用draw2d时,就能体会到draw2d中的“Figure是视图”这个概念的含义。
设计一个基于MVC的应用框架相对于设计一个基于MVC的类层次,要难得多;将编辑和显示独立到框架中的不同层上需要高超的设计水平。GEF和draw2d就是基于MVC设计的应用框架,GEF是控制层,draw2d是显示层。关于GEF,我会在以后的文章中介绍,GEF的设计非常精彩。
我对draw2d中的一个模型—视图设计印象非常深:范围模型和滚动条。对一个滚动条而言,它涉及到:最小范围,最大范围,当前值, thumb的大小,页增量。在draw2d中,定义了一个接口RangeModel表示这些数据,并且是一个监听容器。滚动条持有范围模型,并将自身作为监听器注册到范围模型中。当滚动条发生滚动时,它会修改范围模型,然后范围模型再通知其它对范围模型数据变化感兴趣的外部对象;当范围模型的数据被外部对象改变之后,它就通知滚动条改变自身的显示以保持与范围模型的数据相一致。
在以后的文章中,我会重点介绍视口,视口就非常精彩地使用了范围模型来与滚动条通信并保持同步。需要指出的是,这里介绍的滚动条实际上是视图和控制器地合成体,它是视图和控制器二者功能的合并。
在draw2d中,MVC设计模式比较多。
2.2 Composite模式
draw2d中的图形元素类层次采用Composite模式。用IFigure表示图形元素抽象,用Figure表示IFigure的基本实现并包含IFigure接口。Composite模式的动机是:将对象组合成树形结构以表示“部分-整体”的层次结构,Composite使得用户对单个对象和组合对象的使用具有一致性。
在.Net中的GUI控件库就是采用的Composite设计模式,因为各种控件之间存在明显的部分与整体关系,例如Form类从Control派生,但很明显它又是一个控件容器,可以包含各种控件。draw2d是一个轻量级的widget构件系统,它提供了几乎与常用的控件对等的各种图形元素构件(例如按钮、滚动条)等;但这些图形元素构件不是控件,它们紧紧只是从Figure派生的图形元素,这也是draw2d被称之为轻量级widget系统的原因。因为draw2d是一个构件库,而构件本质上就存在着“部分-整体”关系,所以draw2d中的图形元素也就自然而然的采用了Composite模式。
因为采用了Composite模式,所以在一个基于draw2d应用程序中,图形元素是呈树状的,严格来说,一个应用程序中只存在一个图形元素,这个图形元素就代表应用程序要显示的内容(称之为内容图形元素);内容图形元素可以包含孩子,孩子又可以包含孩子……,子孙无穷尽也。
Draw2d图形元素设计图
2.3 观察者模式
这个模式在Java中已经被提升到了一个很重要的位置,以至于Java类库都直接支持它。Java中的事件处理机制就是基于观察者模式实现的,因为draw2d是一套轻量级的widget构件系统,所以图形元素能够引发事件,也能够接收事件。