当前位置:文档之家› Draw2D

Draw2D

Draw2D
Draw2D

Draw2D--序言

新一篇: Draw2D--1. 设计思想和相关模式

首先,先介绍一下我自己。我1996年毕业于江汉石油学院,在大学时就对软件开发非常感兴趣,在大学时通过程序员中级水平考试;后来在2001年通过国家高级程序员水平考试。在2001年正式转行做软件开发,在做软件开发的将近5年的时间里,对软件开发的认识、态度、理念都发生了许多的变化,基本上完成了从程序开发到软件开发的过渡和转变。

因为接触到许多unix上的大型地学软件(进行地质分析、地震分析)的缘故,所以我对开发图形软件很感兴趣,对这方面也有一定程度的了解和研究。在2004年初,我在Google上搜索“图形编辑框架”时,无意中发现了GEF,然后是draw2d,最后发现它们是Eclipse工程中的一部分(其实在几年前Ellipse就已经声明远扬)。我一直在寻找一个完整的框架性质的图形编辑框架,在找到GEF后,我意识到,我找到了。

经过大约半年的辛苦后,我理解了draw2d和GEF的设计思想并将它们的部分用.Net重写了,因为我开发的软件打算运行在windows平台上;在理解和移植Draw2d和GEF的过程中,我对这两个库的设计思路佩服之至,这些库的设计人员和编码人员的水平实在是远胜于我。为了帮助中国对draw2d和GEF的内幕也感兴趣的开发人员,我决定将我对draw2d的认识写成系列文章,希望能够对他们有所帮助。

我写的这些文章并不是指导如何用draw2d和GEF在Eclipse框架下开发应用程序的,事实上,我并不懂JAVA,更没有用JAVA开发过任何商用软件;但我用.Net编写的从Draw2d和GEF移植过来的库是可以运行的,这说明我对draw2d等的理解基本是正确的。我正在我从Draw2d和GEF移植过来的.Net库上开发基于石油行业的软件系统。

非常感谢javamxj愿意提供地方放置我的文章。

欢迎各位提出批评和各种建议,我的QQ:275060857

1. 设计思想和相关模式

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用默认值启动剩余的过程。Lightweight System依赖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.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构件系统,所以图形元素能够引发事件,也能够接收事件。

2.图形元素(Figure)类设计层次

Draw2d是一个轻量级widget系统,定义了类似控件的一些图形元素,也定义了一些形状。图形元素能够相应各种事件,可以直接在事件处理函数中处理这些事件并对模型进行修改。

如果不需要对编辑图形元素执行编辑过程,只需要应用draw2d就可以完成显示目的。使用draw2d开发图形显示应用程序一般需要三个步骤:

1. 创建一个画布控件。画布控件是一个容器控件,充当一个绘图表面。

2. 创建一个LightWeightSystem对象并将创建的画布对象传递给它。LightWeightSystem对象本身是一个事件监听器。LightWeightSystem对象的主要职责之一是监听画布中的与系统相关的交互事件并将它们转换成draw2d中自定义的事件,然后再将这些事件分发到各个图形元素。另一个主要职责是负责更新画布显示,也就是更新图形元素的显示。

3. 设置LightWeightSystem对象的内容(呈现模型的图形元素),LightWeightSystem要利用LightWeightSystem对象中设置的布局管理器布局这些图形元素。LightWeightSystem持有根图形元素的应用,根图形元素只知道根图形元素,它并不关心内容。当向LightWeightSystem设置内容时,实际上是将内容设置为根图形元素的孩子,并且根图形元素只能有一个孩子,它就是待显示的内容。

在LightWeightSystem对象中有一个根图形元素,这个根图形元素是LightWeightSystem对象中必不可少的一部分,创建根图形元素是创建LightWeightSystem对象过程的其中一个环节。在前面的章节中,多次提到过根图形元素但却没有对根对象进行描述,这里将补上这方面的内容。根图形的职责:

1. 作为应用程序的背景。根图形元素完全覆盖在画布上,用户可以设置根图形元素的显示属性,改变根图形元素的显示属性就可以改变应用程序的背景。

2. 作为内容图形元素的父亲。

3. 作为搜索某个图形元素的搜索入口点。

4. 具备布局属性。因为它是内容图形元素的父亲,所以可以对内容图形元素进行布局。在当前的LightWeightSystem实现中,根图形元素的默认布局是堆叠布局(StackLayout)。

5. 通过绘制根图形元素中某个区域,可以绘制位置落在这个区域之内所有的图形元素。

应用程序不要期望重新定义LightWeightSystem中的根图形元素,这是不允许的。

前面屡次提到内容图形元素(contents figure),它是什么呢?可以把它理解成代表某个具体模型的图形元素。下面以DXF文件为例来描述内容图形元素究竟何方神圣。对于一个DXF文件而言(AutoCAD图形交换格式),该文件是许多图形元素的持久化存储,在DXF文件中定义了每个图形元素该如何显示、它们在模型中的位置等,但是现在不要将DXF文件中定义的图形元素理解成可以直接用draw2d中的图形元素显示的图形元素,而是要将DXF文件中定义的图形元素理解成模型,至于将图形元素模型映射到draw2d中的哪个具体图形元素上,是应用程序的职责,可以有很多不同的映射方法,具体采用那种方法取决于应用程序开发人员。

Dxf文件由7部分组成:

1. HEADER section(标题节)

2. CLASSES section(类节)

3. TABLES section (表节)

4. BLOCKS section(块节)

5. ENTITIES section(实体节)

6. OBJECTS section(非实体节)

7. END OF FILE(文件结束)

对于应用开发人员而言,只需要关心1、3、4、5、7这五部分的内容,它们记录了我们所需要的图形信息。其中HEADER section(标题节)中说明了有关图形的一般信息。比较重要的信息是:文件的版本、图形的大小、绘图所用的单位、角度的单位、角度的精度、角度的方向;ENTITIES详细定义了各种图形元素模型,例如多边形、矩形等。

从DXF文件的HEADER节中,就可以知道DXF文件所代表的模型的大小。谈到模型,我需要谈谈我对模型的定义和理解:模型是无限2D平面中的一个有限矩形区域,模型有约束矩形框,该约束矩形框定义了应用程序建模所定义的2D平面的矩形区域。DXF文件的HEADER节定义了DXF模型所代表的2D平面中的一个有限矩形区域,在ENTITIES节中所定义的所有的图形元素模型都位于该矩形区域范围之内。解析DXF文件的过程,实际上就是将DXF从文件映射到内存中表示的模型序列,也就是在解析文件的过程中,用代表DXF 的模型重构DXF文件。下图就表示了代表DXF文件的模型,因为只是示意的缘故,我只在模型中展示了HEADER 节和ENTITIES节。

DXF模型图

DXFModel代表整个DXF文件,DXFHeader代表dxf文件中的HEADER节,DXFEntities代表DXF文件中定义的各种图形元素模型。的集合,DXFEntity代表DXF文件中定义的各种图形元素模型。

现在定义一个类,用来解析DXF文件:DXFParser,该内中定义了一个方法read()。

Public class DXFParser

{

public DXFModel Parse(string filename);

}

通过DXFParser类的一个实例并对该实例调用Parse方法,就可以获取一个代表DXF文件的DXFModel 对象即DXF模型。

在获取了代表DXF文件大的DXFModel模型对象后,就需要用图形库呈现DXFModel了,应用程序开发人员把可以用不同的图形库去呈现它,但这里我将介绍如何用draw2d去呈现它。Draw2d定义了用于可以执行呈现任务的图形元素,DXFModel代表DXF数据模型,那么如果打算用Draw2d呈现DXFModel对象,必须定义一个执行从模型到视图(draw2d中定义的各种图形元素或开发人员自定义的但从draw2d中派生的图形元素)映射任务的中间层或类。这里用类来完成模型到视图的映射,将这个映射类定义为:MapDxfToDraw2d。在映射过程中有许多方面需要考虑:

1. DXFModel代表一个完整的DXF数据模型,那么肯定需要定义一个图形元素来表示代表整个DXF图形的图形元素,这里将这个图形元素定义为DxfDiagram。

2. 因为DXF Header模型所代表的数据并不代表图形,它只是对DXFModel对象的属性描述,所以可以在DxfDiagram中定义一个属性类,该属性类中的属性直接映射到DXFHeader图形元素。

3. 在DXF模型中,每个图形元素模型都被指定存在于某个特定的图层上,也就是说,DxfDiagram由许多图层组成,而每个图层中含有多个图形元素模型。根据这个关系,可以定义DxfLayer代表图层,Dx fFigure代表所有图形元素的基类。DXF模型中的每个图形元素模型都同时含有呈现属性和表示图形元素位置的几何属性,所以比较容易将DXF模型中定义的图形元素模型映射到相应的图形元素上去。

模型到视图(draw2d)映射图

上图表示:

1. 以DXFModel作为输入,MapDxfToDraw2d映射器要负责完成从模型到呈现它的视图的变换,其输出就是变换结果:代表DXF图形的图形元素DxfDiagram。

2. DxfDiagram、DXFLayer、DXFFigure都从draw2d中的Figure中派生而来,但DxfDiagram由DXFLayer组成,DXFLayer由DXFFigure组成。

在获得了DxfDiagram后,就大功告成了。这个DxfDiagram对象就是要设置给LightWeightSys tem的内容。下面的代码是Draw2d LightWeightSystem类中设置内容的源码:

public void setContents(IFigure figure) {

if (contents != null)

root.remove(contents);

contents = figure;

root.add(contents);

}

代码表明,当设置LightWeightSystem中待显示的内容时,LightWeightSystem首先要检查当前L ightWeightSystem中是否已经显示了其它的内容,如果是,那么就从根图形中删除掉以前存在的内容。最后将内容作为孩子增加到根图形元素。前面已经提到过,根图新元素对于LightWeightSystem而言,非常重要;当LightWeightSystem接到通知,需要绘制某个范围的图形元素时,它总是直接调用根图形元素的Paint()方法,在Paint()方法中,根图形元素会调用内容图形元素的Paint()方法,内容图形元素然后在依次调用其孩子的Paint()方法……。

上面讲了一大堆,主要是说明什么是内容图形元素、如何将模型映射到draw2d以及内容图形元素根图形元素之间的关系。

下面该所说什么是图形元素了,这也是本章的终点,呵呵,到现在为止才切入正体。

前面提到过,draw2d的图形元素采用Composite模式设计,最核心的设计图如下所示:

IFigure接口中定义了图形元素的所有的公共抽象,Figure是IFigure的基本实现。强烈建议不要直接实现IFigure接口以定义新的图形元素,而是要从Figure派生,因为Figure对于IFigure中的许多方法提供了很好的缺省实现,如果你觉得哪个方法不合适,你可以重写它。

在讨论draw2d中的完整的图形元素类层次之前,需要对draw2d中的图形元素的特点进行一番描述,为更好的理解draw2d中的类层次铺平道路。

1. IFigure可以处理来自LightWeightSystem中的事件派发器中派发过来的事件,所以在IFigure类中定义了许多的handleMouseDown()等之类的方法。LightWeightSystem中的事件派发器向IFigure 派发事件的过程实际上就是调用IFigure 的handle…方法序列。

2. IFigure是事件监听器容器。IFigure并不直接处理事件,它将实际的事件处理委托给外部的事件监听器。假如某个图形元素需要对鼠标右键作出响应,例如弹出针对当前图形元素的鼠标右键等,那么就需要定义一个专门处理鼠标右键事件的类(实现MouseListener接口),然后将该类注册到IFigure中。所以在IFigure中定义了大量的addMouseListener之类的方法;既然能够向IFigure中注册事件处理器,那么也需要能够从IFigure中注销事件处理器,所以在IFigure中也定义了大量的RemoveMouseListe ner之类的方法。

3. IFigure中没有定义与选择相关的内容,因为选择已经属于编辑的范畴了,而draw2d只负责呈现而不负责编辑,所以干脆就没有在IFigure中定义与选择相关的内容。例如,通常,在图形元素中都要定义一个字段以表明图形元素是否处于被选中状态,但在draw2d中却没有。(GEF中定义了如何记录被选中的图形元素方面内容,请参考EditPart接口)。

4. IFigure有聚焦、使能、遍历等只有控件才需要的属性,我刚开始时也被这些属性给搞蒙了,但不要奇怪,因为draw2d是一个轻量级的Widget系统,它要用图形元素模拟控件,所以在IFigure中包含这些属性就可以理解了。

5. 每个图形元素都有一个确切的约束范围,这个约束范围就是它在绘图表面上呈现时的呈现位置。既然有约束范围,它就应该有边界。显然可以用一个图形元素来表示边界,因为边界是可以图形化的。在dra w2d中,边界并没有被当作图形元素来处理,也就是说它并不是从Figure中派生而来的。每个图形元素可以有边界,也可以没有边界,可以调用setBorder(Border border) 为图形元素设置边界。

6. 因为一个Figure是一个复合对象,它可以包含子图形元素,那么这些子图形元素该如何布局呢?这就设计到布局的概念了。每个Figure都可以拥有一个布局管理器,以对它所拥有的孩子进行布局。在dr aw2d中定义中定义了大量的布局管理器。

1、可点击图形元素的设计

一个Clickable(可点击对象)要以某种方式响应鼠标点击(由ClickBehavior确定)并且触发动作事件。它并不提供可视化反馈。依赖模型持有者和理解并更新这个模型的事件处理器,默认使用ButtonModel。任何图形元素都可以被设置为Clickable的内容。当Clickable被点击后,事件处理器被调用并修改模型,在模

型被修改后,模型观察器被通知,然后模型观察器执行某种动作。

一个Button(按钮)通常有一个边界并且作为对被按下动作的响应按钮会上下移动。它能够还有文本或图象。

ArrowButton(箭头按钮)含有一个箭头并为箭头提供方向支持。

Toggle对象的基本规则:无论谁创建toggle对象,他都要对它的响应改变负责(选择等)。只有CheckBox 自己监听事件。

一个CheckBox是一个toggle图形元素,它在checked和uncheced状态之间变换以模拟一个check box。一个check box含有一个代表它的文本标签。

ToggleButton代表一个象三维按钮的Toggle对象。

可点击物图形元素类设计

2,文本图形元素设计

文本流盒子类层次

显示文本时,一定会涉及到布局文本的问题。Draw2d涉及了一个布局类层次专门负责文本元素的布局。所有的布局都必须从AbstractLayout派生,AbstractLayout实现了接口LayoutManager。

3,形状图形元素设计

形状指椭圆等几何形状

PolylineConnection是基于折线的连接的实现。它实现了Connection接口,所以它表示一个折线连接;它实现了AnchorListener接口,所以当它的父层次的图形元素被移动后,它可以得到通知。PolylineConnection增加了下面的特征:

1. 可以提供一个连接路由器以确定连接点。

2. 可以增加孩子。约束计算被扩展以致约束是最小的矩形框但却能够足够大到显示折线和它的所有的子图形元素。

3. 一个DelegatingLayout布局被设置为默认布局。DelegatingLayout允许孩子通过Locators定位它们自己

4.接口继承层次设计

IFigure定义了一个图形元素必须要实现的基本功能,通过对IFigure进行扩展可以为图形元素提供额外的功能。

FreeFormFigrue允许它的孩子向负坐标空间扩展。这个图形元素一定要被放置在FreeformViewpor t 中。另外,你不能对这个图形元素调用IFigure.setBounds(Rectangle)。它的边界将基于它的孩子的范围被计算。一个FreeFormFigrue图形元素的边界将是含有它的所有孩子的最小矩形。

· Orientable表示可以水平或垂向放置的图形元素。

· Connection表示两个图形元素之间的连接。

· RotatableDecoration表示可以旋转的图形元素。

· ScalableFigure表示可以被比例化的图形元素。

5,图层设计

图层是图形元素的容器,通过使用图层可以方便对图形元素的管理。

· Layer是一透明的只能被增加到LayeredPane 的图形元素,LayeredPane 要负责管理它的layers。· FreeFormLayer是一个能够向所有的4个方向扩展的Layer。

· ConnectionLayer是一个专门设计用来处理连接的Layer。这么做的原因是考虑到要为连接增加一个路由器的必要性。

· LayeredPane是一个能够持有任何数目的layer的图形元素。只有Layer能够被增加到这个图形元素。· Layer在被增加到这个图形元素时一定要赋予一个key,这个key唯一的标识了这个Layer。· ScalableLayeredPane代表一个非自由的可比例化的LayeredPane。

· FreeformLayeredPane是一个含有FreeformLayer的LayeredPane 。

· ScalableFreeformLayeredPane是一个含有FreeformLayer的可比例化的LayeredPane 。

图层图形元素的设计类图

6,其它的图形元素的实现

LightweightSystem.RootFigure是LightweightSystem中的根图形元素。

LightweightSystem.RootFigure是LightweightSystem中的根图形元素。

· ScrollBar为ScrollPane 提供滚动条。一个ScrollBar由5个必须的图形元素组成:一个“up”箭头按钮、一个“down”箭头按钮、一个可拖动的“thumb”,一个“Pageup”按钮、一个“Pagedown”按钮。因为滚动条既可水平放置又可垂直放置,所以它实现了Orientable接口。

· ImageFigure简单的含有一个图象的图形元素。当显示没有任何文本与其伴随的图象时要使用这个图形元素而不是label。注意,处置这个图象是客户的职责。

· Label表示一个能够显示文本或图象(可以同时显示)的图形元素。因为需要对label的文本或图象的位置进行布局,所以Label实现了PositionConstants接口。

· Thumbnail代表一个能够以较小的尺寸显示它的源图形元素的图象的图形元素。这个Thumbnail将维护源图形元素的面貌比率(也就是高和宽的缩放比例相同)。

· ScrollableThumbnail表示一个图形元素的可比例化的图象表示。如果源图形元素完全是不可见的,那么一个SelectorFigure 被放置在thumbnail上以代表可浏览区域的并且能够被四处拖动来滚动源图形元素。

· Panel是一个通用的容器。这个图形元素默认是不透明的并且将要么用被设置在该图形元素上的背景色要么用IGraphics中当前的背景色填充它的整个约束范围。不透明的图形元素有助于优化绘制。注意到,在panel超类中的paintFigure()方法实际上已经填充了这个图形元素的约束范围。

· LabeledContainer 是一个带有描述容器内容的标题条的容器。这个框架是被一个LabeledBorder生成的。

7,视口设计

有时在画布上的图形元素过多,在控件窗口中显示不下,那么就需要利用视口技术显示需要被显示的图形元素。

· ScrollPane是一个实现了自动水平或(和)垂直滚动的类。ScrollPane中的滚动条的可见性是用整数代表的:NEVER:从来不显示滚动条;AUTOMATIC:当ScrollPane不再能够容纳它的视图时按需显示滚动条;ALWAYS:总是显示滚动条。为了使用它,先实例化一个ScrollPane对象,然后调用它的Set View(IFigure)方法传递给它想要有滚动能力的图形元素。ViewPort是一个位于ScrollPane 之上的灵活

窗口,代表ScrollPane 的可见部分。

· FreeformViewport是一个专门用于FreeformFigures的视口。FreeformFigures只能驻留在这种类型的视口中。

· ViewPort实现了PropertyChangeListener,所以它是一个属性监听器。当ScrollPane的属性发生变化时(当然要先把它注册到ScrollPane中),它就会得到通知,然后重新计算ScrollPane可见区域并更新显示。

当显示图形元素的过程中,可以为图形元素增加一些修饰,其中的一个修饰是边界,例如在一个图形元素的周围显示其约束矩形等。

· Border接口表示一个恰好被绘制在图形元素外边缘上的图形修饰物。

· LabeledBorder表示一个在它的某个地方有文本消息的边界修饰物。可以设置文本的字体。当label发生变化时,LabeledBorder不应该改变它的Insets,因而使用这个边界的Figures应该在更新label时重绘这个边界,在改变文本的字体时,要使边界重新有效。

· AbstractBorder提供了边界的通用实现。

· AbstractLabeledBorder为带有描述它所包围的内容的标签的边界提供支持。它实现了LabeledBorde r接口。

· GroupBoxBorder是一个带标签的打算用于容纳带一组孩子的图形元素。这个标签充当这个组的描述。它是AbstractLabeledBorder的子类。

· TitleBarBorder提供一个关于它所包围的图形元素的标题条。通常被同其它的边界一起用来创建一个类窗口的效果。它也提供在标题条上的文本对齐能力。它是AbstractLabeledBorder的子类。

· CompoundBorder允许两个边界的嵌套。被嵌套的边界被称之为内外边界。

· FrameBorder提供一个类框架的含有一个标题条以容纳图形元素标题的边界。它是CompoundBorde r的子类并实现了LabeledBorder接口。

· FocusBorder看起来象系统的聚焦矩形。

· LineBorder提供一个各边宽度相等的线边界。

· MarginBorder提供空白padding的边界。

· SchemeBorder允许创建基于方案的边界。一个方案是一个其唯一作用是携带边界相关信息的类。Sch emeBorder 基于被设置到它的方案所给定的信息呈现边界。

· ButtonBorder为可点击类型图形元素创建一个边界,它要同这个图形元素和它的模型一道完成这个过程。这个边界调整它自己到各种不同的状态以与图形元素的模型的状态保持一致。这个边界使用一个被称之为ButtonBorder.ButtonScheme 的的扩展方案:

SimpleEtchedBorder提供两个象素宽的边界,有一种被蚀刻的视觉效果。这个类从SchemeBorde r派生而来。

SimpleLoweredBorder提供一个凹进去的边界。这个类从SchemeBorder派生而来。

SimpleRaisedBorder提供一个突出来的边界,这个类从SchemeBorder派生而来。

SchemeBorder.Scheme对象携带绘制方案边界所需要的信息。它持有关于边界的信息集,这些信息能够被改变以创建广范围的方案。它为边界透明度、大小、高亮边和阴影边的颜色提供支持。ButtonBor der.ButtonSchem e从Schem eBorder.Scheme派生;它提供一个方案来表示象button之类的可点击物的边界。尽管跟Scheme 相似,但它为被按下的状态提供额外的边界集合。

这章就先写到这儿,太多了。下次该剖析draw2d内部运行的过程了,主要包括:更新管理器是如何设计的?事件派发器是如何设计的?如何定义抽象的图形对象并实现具体的图形对象?IFigure是如何被绘制的以及与IFigure相关的一些有一定难度的问题。

这章本来打算提供一个dxf浏览器和其相关源代码的,但是因为还没有整理好,只有等下次再提供了。打算提供的内容如下:

1. 一个我用.Net改写的draw2d库,但抱歉的是,暂不能提供源代码。

2. 一个解析DXF文件层dxf模型的解析库以及描述dxf文件格式的CHM文件。该解析库是我写的,提供所有的源代码。

3. 提供一个基于draw2d库的dxf文件浏览器以及源代码。

draw2d设计内幕之一:LightweightSystem设计和实现剖析

这篇文章假设读者在浏览本章之前,已经对LightweightSystem有了一定的了解,基本上应该要明白LightweightSystem究竟是个什么?在分享Java快乐的Draw2d专栏中有关于LightweightSys tem介绍的文章。在本文章中,提供了一个dxf文件解析器和dxf浏览器。dxf浏览器就是利用draw2d 编写的。本文章力图言简意赅的解释与LightweightSystem相关的设计和实现,所以对一些基本的技术名词和术语并没有或没有详细的解释。

1. LightweightSystem设计和实现

1.1 LightweightSystem设计

设计图表明,一个LightweightSystem由四个不可缺少的部分组成:画布控件、图形元素更新管理器、事件分发器、根图形元素。内容是需要在画布上显示的与应用相关的图新元素,并不要求LightweightSy stem一定就要拥有内容图形元素;对LightweightSystem而言,内容图形元素可有可无,但如果内容图形元素为空,那么LightweightSystem就什么也不显示。向LightweightSystem设置内容的动作,实际上就是将内容图形元素作为孩子增加到根图形元素中,并且在LightweightSystem中记录下对内容图形元素的直接引用(由变量contents引用)。

UpdateManager是一个抽象类,它为具体的更新管理器提供基本实现。DeferredUpdateManager 是UpdateManager类的具体实现,它以异步更新图形元素的方式执行更新过程,这种方式在许多软件中,被叫做多线程呈现;如果读者有MFC或win32开发经验,比较容易了解DeferredUpdateManager的实现原理了:在MFC或Win32中,如果要使某个区域无效,需要调用Invalidate通知系统某个区域无效,在系统接到这个通知后,并不是立即去更新无效区域而是将无效区域放入到系统保持的一个无效区域列表中,以后在程序空闲时再一并处理无效区域列表中的所有脏区域(取它们的并集),所以有时即使在程序中调用许多次invalidate,但并不会显著影响系统的呈现效率,原因就是如此。当使用DeferredUp dateManager管理图形元素的更新时,图形元素的更新总是要延迟于使图形元素无效的动作,例如,如果用户用鼠标resize化应用程序窗口,就会发现窗口在被resize后,在靠近被鼠标一侧的窗口区域有一块灰色区域,但随即该区域就被正常的被填充上了;这块灰色区域就是用户resize窗口过程中产生的脏区域,在拖动的过程中,应用程序已经向DeferredUpdateManager报告了有脏区域产生的事实,但Def erredUpdateManager仅仅只是创建一个线程,但因为涉及到更新UI的问题,该线程必须在UI线程中

被执行;但此时UI主线程正忙于处理用户的resize动作,所以不会执行DeferredUpdateManager的更新请求;无论用户以多快的速度执行resize动作,在两次resize过程中总会有空隙,在这个空隙中,U I主线程就会执行DeferredUpdateManager的更新请求。异步更新图形元素的方式并不会提高图形元素呈现的速度,但它会极大的改善UI界面的响应能力。

UpdateManager持有根图形元素的应用,因为只有通过根图形元素,才能够获取待更新的图形元素,请参见对根图形元素作用的描述。

EventDispatcher是一个抽象类,它为具体的事件分发器提供基本实现。SWTEventDispatcher是E ventDispatcher类的具体实现。顾名思义,SWTEventDispatcher与SWT控件套件有关。draw2d的SDK文档是这样描述SWTEventDispatcher的:SWTEventDispatcher使draw2d具有分发SWT事件的能力;LightweightSystem增加SWT事件监听器到它的画布控件;当画布控件接受到一个SWT事件后,它就调用SWTEventDispatcher中定义的一个合适的分发器方法。在设计良好的图形系统中,自定义一套于平台无关的事件处理机制是一种通行的做法。Draw2d也不例外,在Draw2d中定义了与操作系统无关的事件序列,SWTEventDispatcher的主要职责之一就是要将来自于SWT的事件转换层draw 2d中定义的内部事件,然后再将内部事件分发给恰当的图形元素。

可以LightweightSystem看成一个Canvas的适配器,它扩展了canvas的功能,并完全隐藏了与c anvas的通信过程。可以直接将LightweightSystem看成一个拥有绘图表面、能够管理图形元素的绘制并处理各种用户交互事件的控件。

1.2 LightweightSystem实现

在LightweightSystem中定义了如下的变量:

private Canvas canvas; //充当绘图表面

IFigure contents; //需要在Canvas中显示的图形元素

private IFigure root; //LightweightSystem中的根元素

private EventDispatcher dispatcher; //事件分配器

private UpdateManager manager = new DeferredUpdateManager(); //更新管理器private Rectangle oldControlSize = new Rectangle(); //画布控件的旧大小

除root外,其它几个图形元素外都比较容易理解。在前几篇文章中,已经对root的作用进行了描述,但为了保持文章的完整性,这里在重复一下对它的描述。根图形的职责如下:

1. 作为应用程序的背景。根图形元素完全覆盖在画布上,用户可以设置根图形元素的显示属性,改变根图形元素的显示属性就可以改变应用程序的背景。

2. 作为内容图形元素的父亲。

3. 作为搜索某个图形元素的搜索入口点。

4. 具备布局属性。因为它是内容图形元素的父亲,所以可以对内容图形元素进行布局。在当前的Light WeightSystem实现中,根图形元素的默认布局是堆叠布局(StackLayout)。

5. 通过绘制根图形元素中某个区域,可以绘制位置落在这个区域之内所有的图形元素。

应用程序不要期望重新定义LightWeightSystem中的根图形元素,这是不允许的。

实际上,从1.1节中的LightWeightSystem中的设计图中,比较容易理解LightWeightSystem为什么要定义这些变量。在某些可将设计转换成代码的CASE工具中,可以直接生成这些变量定义。

从LightWeightSystem的初始化过程中,可以对LightWeightSystem的逻辑结构有一个比较好的理解,· LightWeightSystem的初始化序列如下:

public LightWeightSystem(Canvas c)

{

//首先创建根图形元素并用该根图形元素初始化更新管理器和LightweightSystem对象的根图形元素。

RootFigure tmpRoot = createRootFigure();

GetUpdateManager().SetRoot( tmpRoot );

This.root = tmpRoot;

//更新管理器要更新图形元素时,需要一个具体的绘图上下文。为getUpdateManager设置图形上下文来源的目的就是

//告知更新管理器,当其执行更新时,如何获取图形上下文。通常,图形上下文是从控件中获取的。

canvas = c;

getUpdateManager().setGraphicsSource(new BufferedGraphicsSource(canvas));

//根据canvas的大小调整root图形元素的约束范围(使root与canvas的客户区一样大)

controlResized();

//接下来将LightweightSystem作为监听器注册到Canvas

addListeners();

}

·controlResized()方法方法体如下:

{

Rectangle r = new Rectangle(canvas.getClientArea());

r.setLocation(0, 0);

root.setBounds(r); //重新设置根图形元素的范围,使其大小等于画布控件的客户区大小,并完全覆盖在画布之上。

//使根图新元素重新有效,该调用会使根图新元素重新布局内容图形元素。

root.revalidate();

//通知更新管理器,更新根图形元素。

manager.performUpdate();

oldControlSize = r; //保存老的控件大小。

}

·addListeners()方法要负责将LightweightSystem对象作为监听器注册到Canvas中,该方法方法体如下:

protected void addListeners()

{

//首先创建一个事件处理器,这个事件处理器实现了许多的接口,这些接口中方法负责接受SWT的各种交互事件。

EventHandler handler = createEventHandler();

//处理辅助交互事件(为残疾人准备的)

canvas.getAccessible().addAccessibleListener(handler);

canvas.getAccessible().addAccessibleControlListener(handler);

//处理鼠标按下

canvas.addMouseListener(handler);

//处理鼠标移动等事件。

canvas.addMouseMoveListener(handler);

//处理鼠标跟踪事件。

canvas.addMouseTrackListener(handler);

//处理键盘事件。

canvas.addKeyListener(handler);

//处理遍历事件(模拟控件被遍历的情况)

canvas.addTraverseListener(handler);

//处理聚焦事件。

canvas.addFocusListener(handler);

//重新设置根图形元素的约束范围。

root.setBounds(oldControlSize);

//通知更新管理器,更新根图形元素。

getUpdateManager().performUpdate();

//初始化事件分发器。

setEventDispatcher(getEventDispatcher());

}

//GetEventDispatcher()函数体

protected EventDispatcher getEventDispatcher()

{

if (dispatcher == null)

dispatcher = new SWTEventDispatcher();

return dispatcher;

}

·EventHandler类(事件处理器)的定义

protected class EventHandler implem ents MouseMoveListener, MouseListener, Accessible ControlListener, KeyListener, TraverseListener, FocusListener, AccessibleListener, MouseT rackListener

{

}

在EventHandler类中的各个方法实现基本上都是直接调用getEventDispatcher().dispatchMous eMoved(event)分发事件。这个过程实际上就是,LightweightSystem接收SWT事件(通过EventH andler类事件的诸多接口方法),然后EventHandler再直接将事件转发到事件分发器中;然后事件分发器将swt事件转换成draw2d事件并分发到恰当的图形元素中。

通过对LightweightSystem类初始化过程的跟踪分析,基本上可以了解到组成LightweightSyste m的各个部分是如何被创建的,更重要的是,通过跟踪分析,可以很清晰的了解到draw2d中事件的接收、分发过程。

2. SWTEventDispatcher实现

EventDispatcher定义了事件分发器的抽象实现,它要监听各种不同的SWT事件并将这些事件分发到感兴趣的draw2d对象。对于任何EventDispatcher的实现者,必须要管理下面的内容:(括号中的内容是实际的变量定义)

·鼠标是否被捕获(captured)

·根图形(root)

·鼠标操作目标(mouseT arget)

·光标显示针对的目标(cursorT arget)

·当前的聚焦图形(focusOwner)

·鼠标悬停源(hoverSource)

·当前的鼠标事件(currentEvent)

·需要显示的光标(cursor)

·引发事件的源控件(事件源control)

·工具提示创建器(toolTipHelper)

EventDispatcher的实现者要负责将来自于SWT控件的事件转换成draw2d自定义的事件。SWTE ventDispatcher就是EventDispatcher的一个具体实现EventDispatcher,它要负责处理SWT事件,并将处理后的事件转发给恰当的图形元素。

因为我自身兴趣的关心在于如何用draw2d中显示图形元素,所以仅仅只对与键盘、鼠标相关的事件做了研究,所以这里只能对SWTEventDispatcher是如何处理键盘、鼠标事件的过程进行了分析。

在读这节内容之前,请参阅draw2d系列文章中与图形元素类层次设计相关的文章,以了解各种dra w2d事件的定义和含义。网站:https://www.doczj.com/doc/53945185.html,/javamxj/。

现在首先看SWTEventDispatcher的实现者是如何处理键盘事件的:

1. 键盘按下处理

基本逻辑过程:如果聚焦图形不等于空,就创建一个draw2d 键盘事件对象(KeyEvent),然后让聚焦图形处理该事件。

核心代码:

if (focusOwner != null)

{

KeyEvent event = new KeyEvent(this, focusOwner, e);

focusOwner.handleKeyPressed(event);

}

相关主题
文本预览
相关文档 最新文档