全面介绍Draw2d的Blog 中国Eclipse社区 论坛

来源:百度文库 编辑:神马文学网 时间:2024/04/28 14:46:35
http://blog.csdn.net/javamxj/category/117594.aspx
余学锋的专栏
Draw2D--序言
Draw2D--1. 设计思想和相关模式
Draw2D--2. 图形元素(Figure)类设计层次(1)
Draw2D--2. 图形元素(Figure)类设计层次(2)
Draw2D--2. 图形元素(Figure)类设计层次(3)
Draw2D设计--3. LightweightSystem设计和实现剖析(1)
Draw2D设计--3. LightweightSystem设计和实现剖析(2)
Draw2D设计--3. LightweightSystem设计和实现剖析(3)
 Draw2D--序言
首先,先介绍一下我自己。我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
 Draw2D--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用默认值启动剩余的过程。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构件系统,所以图形元素能够引发事件,也能够接收事件。
 Draw2D--2. 图形元素(Figure)类设计层次(1)
第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中的根图形元素,这是不允许的。
前面屡次提到内容图形元素(contentsfigure),它是什么呢?可以把它理解成代表某个具体模型的图形元素。下面以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这五部分的内容,它们记录了我们所需要的图形信息。其中HEADERsection(标题节)中说明了有关图形的一般信息。比较重要的信息是:文件的版本、图形的大小、绘图所用的单位、角度的单位、角度的精度、角度的方向;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代表图层,DxfFigure代表所有图形元素的基类。DXF模型中的每个图形元素模型都同时含有呈现属性和表示图形元素位置的几何属性,所以比较容易将DXF模型中定义的图形元素模型映射到相应的图形元素上去。

模型到视图(draw2d)映射图
上图表示:
1. 以DXFModel作为输入,MapDxfToDraw2d映射器要负责完成从模型到呈现它的视图的变换,其输出就是变换结果:代表DXF图形的图形元素DxfDiagram。
2. DxfDiagram、DXFLayer、DXFFigure都从draw2d中的Figure中派生而来,但DxfDiagram由DXFLayer组成,DXFLayer由DXFFigure组成。
在获得了DxfDiagram后,就大功告成了。这个DxfDiagram对象就是要设置给LightWeightSystem的内容。下面的代码是Draw2d LightWeightSystem类中设置内容的源码:
public void setContents(IFigure figure) {
if (contents != null)
root.remove(contents);
contents = figure;
root.add(contents);
}
代码表明,当设置LightWeightSystem中待显示的内容时,LightWeightSystem首先要检查当前LightWeightSystem中是否已经显示了其它的内容,如果是,那么就从根图形中删除掉以前存在的内容。最后将内容作为孩子增加到根图形元素。前面已经提到过,根图新元素对于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中也定义了大量的RemoveMouseListener之类的方法。
3.IFigure中没有定义与选择相关的内容,因为选择已经属于编辑的范畴了,而draw2d只负责呈现而不负责编辑,所以干脆就没有在IFigure中定义与选择相关的内容。例如,通常,在图形元素中都要定义一个字段以表明图形元素是否处于被选中状态,但在draw2d中却没有。(GEF中定义了如何记录被选中的图形元素方面内容,请参考EditPart接口)。
4. IFigure有聚焦、使能、遍历等只有控件才需要的属性,我刚开始时也被这些属性给搞蒙了,但不要奇怪,因为draw2d是一个轻量级的Widget系统,它要用图形元素模拟控件,所以在IFigure中包含这些属性就可以理解了。
5.每个图形元素都有一个确切的约束范围,这个约束范围就是它在绘图表面上呈现时的呈现位置。既然有约束范围,它就应该有边界。显然可以用一个图形元素来表示边界,因为边界是可以图形化的。在draw2d中,边界并没有被当作图形元素来处理,也就是说它并不是从Figure中派生而来的。每个图形元素可以有边界,也可以没有边界,可以调用setBorder(Border border) 为图形元素设置边界。
6. 因为一个Figure是一个复合对象,它可以包含子图形元素,那么这些子图形元素该如何布局呢?这就设计到布局的概念了。每个Figure都可以拥有一个布局管理器,以对它所拥有的孩子进行布局。在draw2d中定义中定义了大量的布局管理器。
 Draw2D--2. 图形元素(Figure)类设计层次(2)
下面开始描述draw2d中定义的图形元素类层次。
1、可点击图形元素的设计
一个Clickable(可点击对象)要以某种方式响应鼠标点击(由ClickBehavior确定)并且触发动作事件。它并不提供可视化反馈。依赖模型持有者和理解并更新这个模型的事件处理器,默认使用ButtonModel。任何图形元素都可以被设置为Clickable的内容。当Clickable被点击后,事件处理器被调用并修改模型,在模型被修改后,模型观察器被通知,然后模型观察器执行某种动作。
一个Button(按钮)通常有一个边界并且作为对被按下动作的响应按钮会上下移动。它能够还有文本或图象。
ArrowButton(箭头按钮)含有一个箭头并为箭头提供方向支持。
Toggle对象的基本规则:无论谁创建toggle对象,他都要对它的响应改变负责(选择等)。只有CheckBox自己监听事件。
一个CheckBox是一个toggle图形元素,它在checked和uncheced状态之间变换以模拟一个check box。一个check box含有一个代表它的文本标签。
ToggleButton代表一个象三维按钮的Toggle对象。

可点击物图形元素类设计
2,文本图形元素设计 类名 类描述
FlowFigure 是文本流图形元素的基本实现。一个流图形元素被用来呈现一个文档,在文档中元素在一行内水平布局直到该行被填充满为止。布局在下一行继续。
BlockFlow 代表一个BlockBox段的图形元素,它含有一行或多行。一个BlockFlow是LineBoxes的创建器,BlockFlow的孩子在布局期间需要LineBoxes。一个BlockFlow可以被认为是一个文本段落。BlockFlows应给被嵌套在其它的BlockFlows中,但是把它们放在InlineFlows中也是有效的。FlowPage能够被用来作为“根”块并能被增加到常用的Draw2d图形元素中。
FlowPage 是流层次的根。一个FlowPage被作为一个正常的图形元素对待,但是含有FlowFigure。当调用一个FlowPage直到它在一个其布局提供宽度暗示的图形元素中时才有被定义的宽度。
InlineFlow 代表多个LineBox段的流图形元素。一个InlineFlow的父亲一定要是BlockFlow或另一个InlineFlow。一个InlineFlow可以含有其它的InlineFlow图形元素。
TextFlow 是一个跨一行或多行呈现一个文本串的InlineFlow。一个TextFlow一定不能有任何孩子。它不提供FlowContext

文本流类层次
FlowBox 是一个代表一行文本所占据区域的几何对象。这个类将基线的概念增加到了矩形中。上坡(ascent)是基线上部的距离。下坡(descent)是距离基线下面的距离。这个类不应该被作一个矩形对待。
CompositeBox 是一个能够含有其它BlockInfos的FlowBox。被含有的BlockInfos被称之为段。
BlockBox 是一个含有多个LineBox段的CompositeBox。
LineBox 是一个代表一行的的CompositeBox。LineBox从它含有的子boxes中计算它的ascent和descent。客户可以在任何时间调用GetAscent()或FlowBox.GetHeight()并期望返回有效的值。被增加到行的子boxes的位置并不确定直到commit被调用为止,此时子boxes被从左到右布局并且他们的基线被在垂向上对齐。
TextFragmentBox 代表一行文本占据的TextFragment区域的集合对象。

文本流盒子类层次
显示文本时,一定会涉及到布局文本的问题。Draw2d涉及了一个布局类层次专门负责文本元素的布局。所有的布局都必须从AbstractLayout派生,AbstractLayout实现了接口LayoutManager。
FlowFigureLayout 是专门用来布局FlowFigure图形元素的布局。
FlowContainerLayout 是专门用来布局含有孩子的FlowFigure图形元素。它实现了FlowContext接口。通过FlowContext,FlowFigureLayout可以执行具体的布局。
BlockFlowLayout 是用来对BlockFlow图形元素布局。
InlineFlowLayout 是用来对InlineFlow图形元素布局。
PageFlowLayout 是用来对PageFlow图形元素布局。
TextLayout 用来对文本串进行布局。
ParagraphTextLayout 用来对TextFlow进行布局。

文本布局帮助类类层次
3,形状图形元素设计
形状指椭圆等几何形状
Shape 为各种形状提供抽象支持。
Ellipse 代表一个椭圆。
Triangle 代表一个三角形。
RoundedRectangle 代表一个圆角矩形。
RectangleFigure 代表一个矩形。
Polyline 将一个点列表呈现为一个线段序列。一个折线可以通过操纵它的点进行定位,不要调用Figure.setBounds(Rectangle)。一个折线的边界会自动基于它的点列表被计算。这个边界是折线的最小约束矩形范围。不应该想折线中增加孩子。
Polygon 代表一个多边形。
PolygonDecoration 代表一个可旋转的多边形修饰,它最常用于修饰折线的端点。
PolylineDecoration 是一个打算被放置在折线上的修饰性图形元素。它的默认形状是一个指向右边的三角形。

形状类层次
PolylineConnection是基于折线的连接的实现。它实现了Connection接口,所以它表示一个折线连接;它实现了AnchorListener接口,所以当它的父层次的图形元素被移动后,它可以得到通知。PolylineConnection增加了下面的特征:
1. 可以提供一个连接路由器以确定连接点。
2. 可以增加孩子。约束计算被扩展以致约束是最小的矩形框但却能够足够大到显示折线和它的所有的子图形元素。
3. 一个DelegatingLayout布局被设置为默认布局。DelegatingLayout允许孩子通过Locators定位它们自己
 Draw2D--2. 图形元素(Figure)类设计层次(3)
4,接口继承层次设计
IFigure定义了一个图形元素必须要实现的基本功能,通过对IFigure进行扩展可以为图形元素提供额外的功能。

FreeFormFigrue允许它的孩子向负坐标空间扩展。这个图形元素一定要被放置在FreeformViewport中。另外,你不能对这个图形元素调用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对象,然后调用它的SetView(IFigure)方法传递给它想要有滚动能力的图形元素。ViewPort是一个位于ScrollPane 之上的灵活窗口,代表ScrollPane 的可见部分。
· FreeformViewport是一个专门用于FreeformFigures的视口。FreeformFigures只能驻留在这种类型的视口中。
· ViewPort实现了PropertyChangeListener,所以它是一个属性监听器。当ScrollPane的属性发生变化时(当然要先把它注册到ScrollPane中),它就会得到通知,然后重新计算ScrollPane可见区域并更新显示。

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

· Border接口表示一个恰好被绘制在图形元素外边缘上的图形修饰物。
· LabeledBorder表示一个在它的某个地方有文本消息的边界修饰物。可以设置文本的字体。当label发生变化时,LabeledBorder不应该改变它的Insets,因而使用这个边界的Figures应该在更新label时重绘这个边界,在改变文本的字体时,要使边界重新有效。
· AbstractBorder提供了边界的通用实现。
· AbstractLabeledBorder为带有描述它所包围的内容的标签的边界提供支持。它实现了LabeledBorder接口。
· GroupBoxBorder是一个带标签的打算用于容纳带一组孩子的图形元素。这个标签充当这个组的描述。它是AbstractLabeledBorder的子类。
· TitleBarBorder提供一个关于它所包围的图形元素的标题条。通常被同其它的边界一起用来创建一个类窗口的效果。它也提供在标题条上的文本对齐能力。它是AbstractLabeledBorder的子类。
· CompoundBorder允许两个边界的嵌套。被嵌套的边界被称之为内外边界。
· FrameBorder提供一个类框架的含有一个标题条以容纳图形元素标题的边界。它是CompoundBorder的子类并实现了LabeledBorder接口。
· FocusBorder看起来象系统的聚焦矩形。
· LineBorder提供一个各边宽度相等的线边界。
· MarginBorder提供空白padding的边界。
· SchemeBorder允许创建基于方案的边界。一个方案是一个其唯一作用是携带边界相关信息的类。SchemeBorder 基于被设置到它的方案所给定的信息呈现边界。
· ButtonBorder为可点击类型图形元素创建一个边界,它要同这个图形元素和它的模型一道完成这个过程。这个边界调整它自己到各种不同的状态以与图形元素的模型的状态保持一致。这个边界使用一个被称之为ButtonBorder.ButtonScheme 的的扩展方案:
SimpleEtchedBorder提供两个象素宽的边界,有一种被蚀刻的视觉效果。这个类从SchemeBorder派生而来。
SimpleLoweredBorder提供一个凹进去的边界。这个类从SchemeBorder派生而来。
SimpleRaisedBorder提供一个突出来的边界,这个类从SchemeBorder派生而来。
SchemeBorder.Scheme对象携带绘制方案边界所需要的信息。它持有关于边界的信息集,这些信息能够被改变以创建广范围的方案。它为边界透明度、大小、高亮边和阴影边的颜色提供支持。ButtonBorder.ButtonScheme从SchemeBorder.Scheme派生;它提供一个方案来表示象button之类的可点击物的边界。尽管跟Scheme 相似,但它为被按下的状态提供额外的边界集合。
这章就先写到这儿,太多了。下次该剖析draw2d内部运行的过程了,主要包括:更新管理器是如何设计的?事件派发器是如何设计的?如何定义抽象的图形对象并实现具体的图形对象?IFigure是如何被绘制的以及与IFigure相关的一些有一定难度的问题。
这章本来打算提供一个dxf浏览器和其相关源代码的,但是因为还没有整理好,只有等下次再提供了。打算提供的内容如下:
1. 一个我用.Net改写的draw2d库,但抱歉的是,暂不能提供源代码。
2. 一个解析DXF文件层dxf模型的解析库以及描述dxf文件格式的CHM文件。该解析库是我写的,提供所有的源代码。
3. 提供一个基于draw2d库的dxf文件浏览器以及源代码。
 Draw2D设计--3. LightweightSystem设计和实现剖析(1)
draw2d设计内幕之一:LightweightSystem设计和实现剖析
作者:余学锋      编辑:javamxj   发布:javamxj    源站点:分享Java快乐
这篇文章假设读者在浏览本章之前,已经对LightweightSystem有了一定的了解,基本上应该要明白LightweightSystem究竟是个什么?在分享Java快乐 的 Draw2d专栏 中有关于LightweightSystem介绍的文章。在本文章中,提供了一个dxf文件解析器和dxf浏览器。dxf浏览器就是利用draw2d编写的。本文章力图言简意赅的解释与LightweightSystem相关的设计和实现,所以对一些基本的技术名词和术语并没有或没有详细的解释。
1. LightweightSystem设计和实现
1.1 LightweightSystem设计

LightWeightSystem设计图
设计图表明,一个LightweightSystem由四个不可缺少的部分组成:画布控件、图形元素更新管理器、事件分发器、根图形元素。内容是需要在画布上显示的与应用相关的图新元素,并不要求LightweightSystem一定就要拥有内容图形元素;对LightweightSystem而言,内容图形元素可有可无,但如果内容图形元素为空,那么LightweightSystem就什么也不显示。向LightweightSystem设置内容的动作,实际上就是将内容图形元素作为孩子增加到根图形元素中,并且在LightweightSystem中记录下对内容图形元素的直接引用(由变量contents引用)。
UpdateManager是一个抽象类,它为具体的更新管理器提供基本实现。DeferredUpdateManager是UpdateManager类的具体实现,它以异步更新图形元素的方式执行更新过程,这种方式在许多软件中,被叫做多线程呈现;如果读者有MFC或win32开发经验,比较容易了解DeferredUpdateManager的实现原理了:在MFC或Win32中,如果要使某个区域无效,需要调用Invalidate通知系统某个区域无效,在系统接到这个通知后,并不是立即去更新无效区域而是将无效区域放入到系统保持的一个无效区域列表中,以后在程序空闲时再一并处理无效区域列表中的所有脏区域(取它们的并集),所以有时即使在程序中调用许多次invalidate,但并不会显著影响系统的呈现效率,原因就是如此。当使用DeferredUpdateManager管理图形元素的更新时,图形元素的更新总是要延迟于使图形元素无效的动作,例如,如果用户用鼠标resize化应用程序窗口,就会发现窗口在被resize后,在靠近被鼠标一侧的窗口区域有一块灰色区域,但随即该区域就被正常的被填充上了;这块灰色区域就是用户resize窗口过程中产生的脏区域,在拖动的过程中,应用程序已经向DeferredUpdateManager报告了有脏区域产生的事实,但DeferredUpdateManager仅仅只是创建一个线程,但因为涉及到更新UI的问题,该线程必须在UI线程中被执行;但此时UI主线程正忙于处理用户的resize动作,所以不会执行DeferredUpdateManager的更新请求;无论用户以多快的速度执行resize动作,在两次resize过程中总会有空隙,在这个空隙中,UI主线程就会执行DeferredUpdateManager的更新请求。异步更新图形元素的方式并不会提高图形元素呈现的速度,但它会极大的改善UI界面的响应能力。
UpdateManager持有根图形元素的应用,因为只有通过根图形元素,才能够获取待更新的图形元素,请参见对根图形元素作用的描述。
EventDispatcher是一个抽象类,它为具体的事件分发器提供基本实现。SWTEventDispatcher是EventDispatcher类的具体实现。顾名思义,SWTEventDispatcher与SWT控件套件有关。draw2d的SDK文档是这样描述SWTEventDispatcher的:SWTEventDispatcher使draw2d具有分发SWT事件的能力;LightweightSystem增加SWT事件监听器到它的画布控件;当画布控件接受到一个SWT事件后,它就调用SWTEventDispatcher中定义的一个合适的分发器方法。在设计良好的图形系统中,自定义一套于平台无关的事件处理机制是一种通行的做法。Draw2d也不例外,在Draw2d中定义了与操作系统无关的事件序列,SWTEventDispatcher的主要职责之一就是要将来自于SWT的事件转换层draw2d中定义的内部事件,然后再将内部事件分发给恰当的图形元素。
可以LightweightSystem看成一个Canvas的适配器,它扩展了canvas的功能,并完全隐藏了与canvas的通信过程。可以直接将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.  具备布局属性。因为它是内容图形元素的父亲,所以可以对内容图形元素进行布局。在当前的LightWeightSystem实现中,根图形元素的默认布局是堆叠布局(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类(事件处理器)的定义
protectedclass EventHandler implements MouseMoveListener, MouseListener,AccessibleControlListener, KeyListener, TraverseListener,FocusListener, AccessibleListener, MouseTrackListener
{

}
在EventHandler类中的各个方法实现基本上都是直接调用getEventDispatcher().dispatchMouseMoved(event)分发事件。这个过程实际上就是,LightweightSystem接收SWT事件(通过EventHandler类事件的诸多接口方法),然后EventHandler再直接将事件转发到事件分发器中;然后事件分发器将swt事件转换成draw2d事件并分发到恰当的图形元素中。
通过对LightweightSystem类初始化过程的跟踪分析,基本上可以了解到组成LightweightSystem的各个部分是如何被创建的,更重要的是,通过跟踪分析,可以很清晰的了解到draw2d中事件的接收、分发过程。
 Draw2D设计--3. LightweightSystem设计和实现剖析(2)
2. SWTEventDispatcher实现
EventDispatcher定义了事件分发器的抽象实现,它要监听各种不同的SWT事件并将这些事件分发到感兴趣的draw2d对象。对于任何EventDispatcher的实现者,必须要管理下面的内容:(括号中的内容是实际的变量定义)
· 鼠标是否被捕获(captured)
· 根图形(root)
· 鼠标操作目标(mouseTarget)
· 光标显示针对的目标(cursorTarget)
· 当前的聚焦图形(focusOwner)
· 鼠标悬停源(hoverSource)
· 当前的鼠标事件(currentEvent)
· 需要显示的光标(cursor)
· 引发事件的源控件(事件源control)
· 工具提示创建器(toolTipHelper)
EventDispatcher的实现者要负责将来自于SWT控件的事件转换成draw2d自定义的事件。SWTEventDispatcher就是EventDispatcher的一个具体实现EventDispatcher,它要负责处理SWT事件,并将处理后的事件转发给恰当的图形元素。
因为我自身兴趣的关心在于如何用draw2d中显示图形元素,所以仅仅只对与键盘、鼠标相关的事件做了研究,所以这里只能对SWTEventDispatcher是如何处理键盘、鼠标事件的过程进行了分析。
在读这节内容之前,请参阅draw2d系列文章中与图形元素类层次设计相关的文章,以了解各种draw2d事件的定义和含义。网站:http://blog.csdn.net/javamxj/。
现在首先看SWTEventDispatcher的实现者是如何处理键盘事件的:
1. 键盘按下处理
基本逻辑过程: 如果聚焦图形不等于空,就创建一个draw2d 键盘事件对象(KeyEvent),然后让聚焦图形处理该事件。
核心代码:
if (focusOwner != null)
{
KeyEvent event = new KeyEvent(this, focusOwner, e);
focusOwner.handleKeyPressed(event);
}
2,键盘释放处理
处理逻辑过程与键盘按下处理过程基本一样。
if (focusOwner != null)
{
KeyEvent event = new KeyEvent(this, focusOwner, e);
focusOwner.handleKeyReleased(event);
}
看SWTEventDispatcher的实现者是如何处理鼠标事件的
对于大多数鼠标事件,都要先进行一些初始化动作后才能开始处理这些事件。这个初始化过程被封装在Receive(org.eclipse.swt.events.MouseEvent me)方法中,该方法执行的逻辑过程如下:
/*更新光标下的图形元素,变量cursorTarget记录了当前光标下的图形元素*/
if( 鼠标没有被捕获 )
{
1.    从根图形中找到鼠标点下的图形元素f。
2.    将图形元素f设置成光标显示针对的图形元素(cursorTarget)并更新光标显示。
3.    如果cursorTarget不是鼠标悬停源(hoverSource),就将cursorTarget设置为鼠标悬停源(悬停源一定要能够提供工具提示信息)。
}
if( 鼠标被捕获 )
{
如果鼠标操作目标(mouseTarget)不为空,那么就将SWT事件转换成draw2d事件用它设置当前鼠标事件currentEvent。
}
else
{
首先,从根图形中找到鼠标点下的图形元素f。
if( f == mouseTarget )  //f就是鼠标操作目标
{
if( mouseTarget == null )
{
将SWT鼠标事件转换成draw2d事件用它设置当前鼠标事件currentEvent。
}
Return;
}
if( mouseTarget != null )  //f不是鼠标操作目标并且鼠标操作目标不为空
{
1.  将SWT鼠标事件转换成draw2d事件用它设置当前鼠标事件currentEvent。
2.  对鼠标操作目标调用“鼠标退出处理“操作。
}
mouseTarget = f; //将图形元素f设置为当前鼠标操作目标。
If( mouseTarget != null )
{
1. 将SWT鼠标事件转换成draw2d事件用它设置当前鼠标事件currentEvent。
2. 对鼠标操作目标调用“鼠标进入处理“操作。
}
}
Receive方法的主要目的是:
1,更新鼠标点下的图形元素并更新悬停源。
2,将SWT鼠标事件转换成Draw2d事件。
3,更新鼠标操作目标,在更新鼠标操作目标之前,务必要对上次receive调用中产生的鼠标目标进行“鼠标退出处理”,然后更新鼠标操作目标并对新的鼠标操作目标调用“鼠标进入处理”操作。
对鼠标悬停事件的处理逻辑过程:
1. 调用Receive对鼠标事件进行预处理。
2. 如果鼠标操作目标不为空,那么对鼠标操作目标调用“鼠标悬停处理操作。
3. 如果鼠标悬停源不为空,那么显示鼠标悬停源的工具提示信息。
鼠标双击处理过程:
1. 调用Receive对鼠标事件进行预处理。
2. 如果鼠标操作目标不为空,那么对鼠标操作目标调用“鼠标双击处理操作。
鼠标进入处理过程:
1. 调用Receive对鼠标事件进行预处理。
鼠标退出事件处理过程:
1. 将鼠标悬停源设置为空。
2. 如果鼠标操作目标不为空,那么
a) 将SWT鼠标事件转换成draw2d事件并用它设置当前鼠标事件currentEvent。
b) 如果鼠标操作目标不为空,那么对鼠标操作目标调用“鼠标退出处理“操作。
3. 将鼠标操作目标设置为空
鼠标按下事件处理过程:
1. 调用Receive对鼠标事件进行预处理。
2. 如果鼠标操作目标不为空,那么
a) 对鼠标操作目标调用“鼠标按下处理”操作。
b) 如果当前事件的被消费标志为真,那么就将捕获标志设置为真。
鼠标移动事件处理过程:
1. 调用Receive对鼠标事件进行预处理。
2. 如果鼠标操作目标不为空,那么
如果有任何鼠标键被按下,那么就对鼠标操作目标调用“鼠标拖动处理“操作;否则就对鼠标操作目标调用”鼠标移动处理“操作。
鼠标释放事件处理过程:
1. 调用Receive对鼠标事件进行预处理。
2. 如果鼠标操作目标不为空,那么对鼠标操作目标调用“鼠标释放处理“操作。
3. 释放捕获。
4. 调用Receive对鼠标事件进行预处理。
从上面的事件处理逻辑过程来看,除了Receive事件预处理过程比较复杂外,无论是键盘还是鼠标事件的处理过程都较为简单。
事件分发器在将SWT事件转换成draw2d事件后就将该事件转交给目标图形元素。那么图形元素又是如何处理这个事件的呢?
先看回顾以下SWTEventDispatcher对键盘按下事件的处理代码:
if (focusOwner != null)
{
KeyEvent event = new KeyEvent(this, focusOwner, e);
focusOwner.handleKeyPressed(event);
}
上面的代码表明,如果聚焦拥有者不为空,那么就对聚焦拥有者调用handleKeyPressed()方法。focusOwner是个Figure对象,下面是Figure类中handleKeyPressed()方法的实现代码:
public void handleKeyPressed(KeyEvent event)
{
Iterator iter = eventListeners.getListeners(KeyListener.class);
while (!event.isConsumed() && iter.hasNext())
{
((KeyListener)iter.next()).keyPressed(event);
}
}
从代码中,可以看出Figure类本身并没有具体的处理这个键盘按下事件,而是将这个事件又转交了注册到该Figure对象中的各个键盘按键监听器。至于具体如何处理这个键盘事件完全是监听器的事情了,与图形元素figure一点关系也没有。在Figure类中定义了许多的形如Add***Listener()的方法,对图形元素Figure的鼠标移动等事件感兴趣的对象可以通过调用这些方法将自己作为监听器注册到Figure对象中;只要Figure中的这些事件发生,这些外部对象就会得到通知。
 Draw2D设计--3. LightweightSystem设计和实现剖析(3)
3.更新管理器的设计和实现
Draw2dSDK这样描述更新管理器:更新管理器要负责处理重绘图形元素并布局它们的任务。一个恰当的实现是批处理待做的工作并合并任何冗余的工作。更新管理器可以含有0或多个被嵌套的更新管理器。在所有的请求已经被批处理后可以执行一些优化。因为这个原因,一个更新管理器应该在完成自己的更新之前要对它所嵌套的更新管理器调用PerformUpdate()操作。在被嵌套的更新期间,可以增加新的请求。
对于Draw2d SDK关于更新管理器的描述,我没有完全理解,到现在也没有。但我认为对UpdateManager的具体实现DeferredUpdateManager还是有一定了解的,下面就谈谈我的认识。
UpdateManager定义了更新管理器的抽象实现,DeferredUpdateManager和SubordinateUpdateManager是它的两个具体实现。下面以DeferredUpdateManager为例探讨更新管理器的实现,它能异步更新受到影响的图形元素。SubordinateUpdateManager我也不太明白,如果有哪位朋友能够将它解释清楚,那将是一件很高兴的事情。
· 该类定义了如下的字段:
private boolean updating;               //表明更新正在进行之中。
private GraphicsSource graphicsSource;  //图形源
private IFigure root;                  //根图形元素。
private boolean updateQueued = false;  //是否有更新被排队
private List invalidFigures = new ArrayList();  //无效的图形元素列表。
private Map dirtyRegions = new HashMap();      //脏区域图
private Rectangle damage;                    //受到破坏的区域
关于这些字段的含义,似乎没有什么可进一步解释,好像比较直接了当。
· 这个类中最重要的函数是performUpdate,该方法的逻辑过程如下:
public synchronized void performUpdate()
{
//如果更新管理器被处置了或者正在执行更新任务的话,就直接返回。
if (isDisposed() || updating)
return;
updating = true;  //将正在更新标志设置为true。
try
{
validateFigures();          //使所有的图形元素有效
updateQueued = false;      //表明没有更新正在排队
repairDamage();           //修补被毁坏的区域
}
finally
{
updating = false;    //清除正在被更新标志。
}
}
performUpdate方法受到临界区保护,也就是说,当performUpdate方法被执行的过程中,该方法不可能有机会再被其它的线程调用。
validateFigures()方法的执行逻辑过程如下,它的任务是使更新队列中的无效的图形元素有效并调用fireValidating (),除非更新队列中没有无效的图形元素。
protected void validateFigures()
{
//如果无效的图形元素队列为空,那么就直接返回。
if (invalidFigures.isEmpty())  return;
try
{
IFigure fig;
fireValidating();  //通知对图形元素有效感兴趣的监听器
for (int i = 0; i < invalidFigures.size(); i++)
{
fig = (IFigure) invalidFigures.get(i);
invalidFigures.set(i, null);
fig.validate();  //使图形元素有效
}
}
finally
{
invalidFigures.clear();  //清空无效图形元素队列。
}
}
有一段时间,我对图形元素的有效和无效非常困惑,因为图形元素的Validate和Invalidate与平常意义上的控件的有效和无效很不一样。图形元素的有效和无效通常与重绘联系再一起。但draw2d中图形元素的有效与无效却不是这样的,下面对draw2d中的有效与无效等之类的概念进行一下解释;在draw2d中到处都是Invalidate(无效),Validate(有效),ReValidate(重新有效)等概念,这里对这些概念进行总结性描述。
1.在AbstractLayout中的Invalidate()方法:告诉布局管理器抛弃它所缓存的关于它所负责布局的图形元素的所有缓存信息。只要拥有这种布局的图形元素被无效,这个方法就必须被调用。布局要负责计算拥有这个布局的图形元素的最小大小(MimSize)和合适的大小(PreferredSize),这些信息被计算一次后,就被缓存起来了,以后就使用这些被缓存的信息而不必重新计算它们。AbstractLayout中的Invalidate()方法实际上就是要抛弃这些信息。
2. 在Figure中的Invalidate()方法:使图形无效。这个无效过程要完成两个事情:1,调用该图形的布局管理器,使布局管理器无效。2,将图形的有效性标志设置为false。
3.在Figure中的validate()方法:使图形元素的布局管理器布局该图形元素以及它的子图形元素。这个方法要做三件事:(1),将图形元素有效性标志设置为true。 (2),调用布局管理器布局本图形元素。(3),对图形元素的的子图形元素依次调用Validate()方法。
4.在Figure中的revalidate()方法:使本图形元素无效并重新有效化它的父亲。这个方法要做两件事:(1),使本图形元素无效。(2),如果本图形元素的父亲存在,就对它的父亲调用revalidate()方法;否则,如果本图形元素是根图形元素,就将本图形元素增加到更新管理器中的无效图形元素队列中去。很明显,这是一个递推的过程,最终必然会递推到根图形元素并将根图形元素放置到更新管理器的无效图形元素队列中去。
对图形元素调用validate会使该图形元素被重新布局,而重新布局会导致图形元素向更行管理器报告脏区域,然后更新管理器会再次绘制被报告的脏区域,但需要说明的时,这个过程不会导致无限循环。
现在解释与fireValidating()方法调用相关的问题
UpdateManager是一个事件容器,能够产生“正在被有效化”和“正在被绘制”事件。到目前位置,我所知道的,只有FigureCanvas对更新管理器的“正在被有效化”事件感兴趣。因为有效化的结果会导致内容图形元素的大小发生变化,而视口是用来显示部分内容图形元素的,当内容图形元素的大小发生变化时,一定也要调整视口的布局,使它能够正确的显示内容图形元素。所以FigureCanvas会将自身注册到更行管理器中,每当UpdateManager有效化无效图形元素之前,FigureCanvas都会得到通知,以重新布局视口。
repairDamage()方法的执行逻辑过程如下,它的任务是重绘更新队列中的脏区域并调用firePainting(Rectangle, Map),除非没有脏区域。
protected void repairDamage()
{
1. 定义一个矩形对象contribution,该对象要负责承载需要被绘制的精确矩形。
2. 在一个迭代循环中完成如下事情:
a,获取图形元素figure,将contribution设置为figure的脏区域与其约束边界的交集。
b,在考虑f的祖先层次的的情况下计算计算figure贡献的脏区域矩形范围,这个范围被累积到contribution中,这个逻辑过程是在一个循环中完成的。
c,将contribution累积到记录累积被毁坏区域的变量damage中
3. 如果dirtyRegions的元素个数不为0,那么
a,调用firePainting(damage, dirtyRegions)通知对绘制被毁坏的区域感兴趣的外部对象。
b,清除脏区域图
4. 如果damage不为空,那么
a, 获取damage中的Graphics绘图对象。
b, 如果绘图对象不为空,那么就对root图形元素调用paint()方法并释放Graphics对象。
5. 将damage设置为null。
}
· 步骤2的目的是合并所有的脏区域以获得所有脏区域的并集。
· 步骤3的目的是向外界通知更新管理器即将更新脏区域,并将脏区域列表复位。
· 步骤4的目的是通过获取绘图上下文对象绘制脏区域。读者对“获取damage中的Graphics绘图对象”一话估计会难以理解,这句话的意思实际上就是:获取一个恰当的绘图上下文对象并将该damage区域设置为绘图上下文的剪切区。
现在对于更新管理器是如何绘制脏区域的,那么如何向更新管理器提交需要被绘制的脏区域呢?DeferredUpdateManager定义了两个方法:AddDirtyRegion和AddInvalidFigure。
先看AddDirtyRegion是如何实现的:
/*该函数表明与图形元素figure相关的由左上角点(x,y),宽w,高h构成的脏区域需要重绘。*/
public synchronized void addDirtyRegion(IFigure figure, int x, int y, int w, int h)
{
//如果图形元素没有被显示,那么就谈不上重绘了,所以就直接返回。
if (!figure.isShowing())  return;
//如果脏区域的宽度和高度为0,那么表明脏区域为空,就直接返回。
if (w == 0 || h == 0)             return;
//从脏区域图中获取与图形元素figure相关的已经存在的脏矩形。
Rectangle rect = (Rectangle)dirtyRegions.get(figure);
//如果脏矩形区域rect为空,那么就将Rectangle(x,y,w,h)设置为figure的脏矩形区域;否则,将Rectangle(x,y,w,h)同rect合并。(注意:rect对象是个引用对象)。
if (rect == null)
{
rect = new Rectangle(x, y, w, h);
dirtyRegions.put(figure, rect);
}
else
{
rect.union(x, y, w, h);
}
//将更新操作排队,使操作系统在空闲时通过用户接口线程调用本对象的PerformUpdate()方法。
queueWork();
}
再看看AddInvalidFigure的实现,该方法要负责将给定的图形元素增加到更新队列。无效的图形元素将在给毁坏的区域被重绘之前被有效化。
public synchronized void addInvalidFigure(IFigure f)
{
//如果无效图形元素列表中已经含有了图形元素f,那么就直接返回。
if (invalidFigures.contains(f))        return;
//将更新操作排队,使操作系统在空闲时通过用户接口线程调用本对象的PerformUpdate()方法。
queueWork();
//将图形元素f增加到无效图形元素列表。
invalidFigures.add(f);
}
这个方法的实现似乎有些不可理解,虽然该方法声称将图形元素增加到了图形元素更新队列,但实际上在repairDamage根本就没有用到无效图形元素列表,repairDamage仅仅利用dirtyRegions来计算需要被重新绘制的精确区域;无效图形元素列表对计算需要被重新绘制的精确区域没有做任何贡献。但不要忘了,addInvalidFigure方法调用了queueWork(),而该调用会导致PerformUpdate()被调用,PerformUpdate()的调用会导致所有的无效图形元素被有效化,图形元素有效化的过程会导致图形元素向更新管理器报告脏区域,然后PerformUpdate()会再次被调用。结果,无效图形元素被正确绘制了。
4.结束语
本来打算在本章介绍图形上下文抽象、图形上下文源、图形上下文实现中种种需要考虑的问题以及figure的整个呈现过程的,但考虑到这些方面的内容足以再写成一篇文章了,所以打算把这方面的内容放到下一文章来完成。
在上期文章中,曾经说过要提供dxf浏览器给读者的,请从文章末尾的链接现在它们。这些材料被组织在一个.Net工作空间中, 通过VisualStudio2003可以直接打开它,工作空间文件在DxfModel文件夹下。在打开工作空间后,一定要重新修改DxfViewer工程中对Graphstone2d的引用。Graphstone2d动态库位于与DxfModel文件夹同级目录下。具体操作过程:
1,首先将压缩文件解压,假设文件被解压到d:\dxf例子工程。
2,进入”d:\dxf例子工程“目录,然后将"mapgis陈家庄.rar"解压,将解压后的文件直接放置在d:\dxf例子工程目录下。
3,启动visual studio2003,然后打开工作空间dxfmodel.sln。工作空间在d:\dxf例子工程\DxfModel目录下。如果通过直接双击dxfmodel.sln的方式打开工作空间的话,有可能打不开,出现什么版本不匹配之类的错误提示,所以不要通过这种方式打开dxfmodel.sln。
4,在打开工作空间后,将dxfViewer工程设为启动工程,并重新设置该工程对Graphstone2d的引用,该Graphstone2d就位于d:\dxf例子工程目录下。
5,运行dxfViewer工程。
如果读者觉得dxf文件解析器是可用的或者可部分使用的,可以随意使用;但如果能够在引用处注明代码来源的话,本人认为更合适一些。
dxf浏览器软件包下载链接  Draw2D目录下的“dxf例子工程.zip”
下面介绍几个与GEF和draw2d或图形框架开发相关的站点,希望对大家有用:
1,http://eclipsewiki.editme.com/GefDescription
2,http://c2.com/cgi/wiki?DirectManipulation
3,http://www.osgi.org/
作者简介:
余学锋1996年毕业于江汉石油学院。在大学时就对软件开发非常感兴趣,在大学时通过程序员中级水平考试;后来在2001年通过国家高级程序员水平考试。在2001年正式转行做软件开发,在做软件开发的将近5年的时间里,对软件开发的认识、态度、理念都发生了许多的变化,基本上完成了从程序开发到软件开发的过渡和转变。
从事过unix下c高级编程、tcp/ip网络编程、COM+开发。熟悉c++、VC++, .Net,了解java,目前正从事图形软件领域的开发.
e_mail:yxuefeng@slof.com
感谢javamxj同意发布、管理我的文章。javamxj创建的blog的站点:分享Java快乐
转贴网址:
http://blog.csdn.net/javamxj/archive/2005/05/19/376530.aspx