利用winrar自动备份重要资料(续,经验技巧)

以前帖子里说过用Winrar自动备份资料的方法,我现在平均每周备份两次,备份的内容包括日程(Rainlendar)、RSS频道(Rssowl)、Eclipse里的项目、常用IP设置、Outlook Express内容等等,压缩下来大约150M左右,其中主要是(带附件的)信件和项目文件占了大部分空间。

今天发现Eclipse里的一个CVS设置给搞错了,连接的是旧的CVS服务器,我在Eclipse里把服务器的地址改过来再进行Update操作,结果把我之后做的几处修改都覆盖了。还好我昨天晚上刚做了备份,这几天的工作没有白费。

类似这样的意外事件很难预料,事先做好充足的预防工作让我们有备无患,而付出的只是每周几分钟的时间,利当然远远大于弊。

关于Winrar备份我又总结了几条经验:首先可以设计两个备份任务,一个是全面备份,一个是快速备份,一些体积比较大又不是特别重要的不需要经常备份,平时每天进行快速备份,每周或每月全面备份一次就可以了;其次,在命令行里加上-ag参数可以让备份得到的压缩包文件名自动包含当前日期,从而不会覆盖掉以前的备份,该参数还可以指定日期格式,如YYYY-MM-DD等;另外,用-ms参数可以不对已经被压缩过的文件再次压缩,而是直接存放;最后,用-x命令可以排除一些我们不希望备份的文件,比如*.jar文件,一般都可以下载得到。我现在用的备份命令如下,其中backup-q.txt的内容是要备份文件的列表,得到的压缩包名像BAK20050223.rar这样:

"C:\Program Files\WinRAR\WinRAR.exe" a -agYYYYMMDD -ms -x*.jar -ep2 -ibck -t c:\backup\BAK.rar @c:\backup\backup-q.txt

这里要提醒一下,备份的文件最好定期转移到本地硬盘以外的存储地点,比如刻成光盘保存,还是不要过于相信硬盘了。

解决高分辨率看网页字体太小问题的一个方法

有些笔记本电脑或LCD显示器的最佳分辨率大于1024×768,例如SXGA+的1400×1050,我们一般会在Windows的显示选项里设置使用大字体,以便更容易阅读。但是IE浏览器缺省是使用96DPI分辨率来显示的,且不允许用户在选项里选择使用自定义的分辨率,所以看很多网页时字体会变得很小以至于影响阅读。

以前有一个办法是在IE选项的辅助功能里设置IE“不使用网页指定的字体大小”,然后就可以在查看菜单里选择自己想要的文字大小(较小、中、较大等等),但这只是文字的放大,会让那些格式比较复杂的页面变得极其混乱,比如新浪网。

我今天在网上找到一个相比起来更好些的方法,原文在MSDN上,其实只要在注册表里加一项就可以让IE使用我们设置的DPI了,这样的好处是页面会整体放大,不会对版式造成任何影响;问题有两个,一是因为图片被放大了所以能看出轻微的锯齿,二是更改设置后要重开IE才能看到效果。

我做了两个注册表片断,直接双击即可导入,其中一个是让IE使用自定义DPI,另一个是恢复为缺省设置(使用96 DPI),这里下载

有需要的朋友不妨试试,注意导入后一定要重开IE才能看到效果。

图1 1400×1050分辨率下96DPI的IE

图2 1400×1050分辨率下自定义为130DPI的IE

[Eclipse]GEF入门系列(三、应用实例)

构造一个GEF应用程序通常分为这么几个步骤:设计模型、设计EditPart和Figure、设计EditPolicy和Command,其中 EditPart是最主要的一部分,因为在实现它的时候不可避免的要使用到EditPolicy,而后者又涉及到Command。

现在我们来看个例子,它的功能非常简单,用户可以在画布上增加节点(Node)和节点间的连接,可以直接编辑节点的名称以及改变节点的位置,用户可以撤消/重做任何操作,有一个树状的大纲视图和一个属性页。点此下载(Update: For Eclipse 3.1的版本),这是一个Eclipse的项目打包文件,在Eclipse里导入后运行Run-time Workbench,新建一个扩展名为”gefpractice”的文件就会打开这个编辑器。

图1 Practice Editor的使用界面

你可以参考着代码来看接下来的内容了,让我们从模型开始说起。模型是根据应用需求来设计的,所以我们的模型包括代表整个图的Diagram、代表节 点的Node和代表连接的Connection这些对象。我们知道,模型是要负责把自己的改变通知给EditPart的,为了把这个功能分离出来,我们使 用名为Element的抽象类专门来实现通知机制,然后让其他模型类继承它。Element类里包括一个PropertyChangeSupport类型 的成员变量,并提供了addPropertyChangeListener()、removePropertyChangeListener()和 fireXXX()方法分别用来注册监听器和通知监听器模型改变事件。在GEF里,模型的监听器就是EditPart,在EditPart的active ()方法里我们会把它作为监听器注册到模型中。所以,总共有四个类组成了我们的模型部分。

在前面的贴子里说过,大部分GEF应用程序都是实现为Editor的,这个例子也不例外,对应的Editor名为PracticeEditor。这 个Editor继承了GraphicalEditorWithPalette类,表示它是一个具有调色板的图形编辑器。最重要的两个方法是 configureGraphicalViewer()和initializeGraphicalViewer(),分别用来定制和初始化 EditPartViewer(关于EditPartViewer的作用请查看前面的帖子),简单查看一下GEF的代码你会发现,在 GraphicalEditor类里会先后调用这两个方法,只是中间插了一个hookGraphicalViewer()方法,其作用是同步选择和把 EditPartViewer作为SelectionProvider注册到所在的site(Site是Workbench的概念,请查Eclipse帮 助)。所以,与选择无关的初始化操作应该在前者中完成,否则放在后者完成。例子中,在这两个方法里我们配置了RootEditPart、用于创建 EditPart的EditPartFactory、Contents即Diagram对象和增加了拖放支持,拖动目标是当前 EditPartViewer,后面会看到拖动源就是调色板。

这个Editor是带有调色板的,所以要告诉GEF我们的调色板里都有哪些工具,这是通过覆盖getPaletteRoot()方法来实现的。在这 个方法里,我们利用自己写的一个工具类PaletteFactory构造一个PaletteRoot对象并返回,我们的调色板里需要有三种工具:选择工 具、节点工具和连接工具。在GEF里,调色板里可以有抽屉(PaletteDrawer)把各种工具归类放置,每个工具都是一个ToolEntry,选择 工具(SelectionToolEntry)和连接工具(ConnectionCreationToolEntry)是预先定义好的几种工具中的两个, 所以可以直接使用。对于节点工具,要使用CombinedTemplateCreationEntry,并把节点类型作为参数之一传给它,创建节点工具的 代码如下所示。

ToolEntry tool = new CombinedTemplateCreationEntry("Node", "Create a new Node", Node.class, new SimpleFactory(Node.class), null, null);

在新的3.0版本GEF里还提供了一种可以自动隐藏调色板的编辑器GraphicalEditorWithFlyoutPalette,对调色板的外观有更多选项可以选择,以后的帖子里可能会提到如何使用。

调色板的初始化操作应该放在initializePaletteViewer()里完成,最主要的任务是为调色板所在的 EditPartViewer添加拖动源事件支持,前面我们已经为画布所在EditPartViewer添加了拖动目标事件,所以现在就可以实现完整的拖 放操作了。这里稍微讲解一下拖放的实现原理,以用来创建节点对象的节点工具为例,它在调色板里是一个 CombinedTemplateCreationEntry,在创建这个PaletteEntry时(见上面的代码)我们指定该对象对应一个 Node.class,所以在用户从调色板里拖动这个工具时,内存里有一个TemplateTransfer单例对象会记录下Node.class(称作 template),当用户在画布上松开鼠标时,拖放结束的事件被触发,将由画布注册的 DiagramTemplateTransferDropTargetListener对象来处理template对象(现在是Node.class), 在例子中我们的处理方法是用一个名为ElementFactory的对象负责根据这个template创建一个对应类型的实例。

以上我们建立了模型和用于实现视图的Editor,因为模型的改变都是由Command对象直接修改的,所以下面我们先来看都有哪些 Command。由需求可知,我们对模型的操作有增加/删除节点、修改节点名称、改变节点位置和增加/删除连接等,所以对应就有 CreateNodeCommand、DeleteNodeCommand、RenameNodeCommand、MoveNodeCommand、 CreateConnectionCommand和DeleteConnectionCommand这些对象,它们都放归类在commands包里。一个 Command对象里最重要的当然是execute()方法了,也就是执行命令的方法。除此以外,因为要实现撤消/重做功能,所以在Command对象里 都有Undo()和Redo()方法,同时在Command对象里要有成员变量负责保留执行该命令时的相关状态,例如RenameNodeCommand 里要有oldName和newName两个变量,这样才能正确的执行Undo()和Redo()方法,要记住,每个被执行过的Command对象实例都是 被保存在EditDomain的CommandStack中的。

例子里的EditPolicy都放在policies包里,与图形有关的(GraphicalEditPart的子类)有 DiagramLayoutEditPolicy、NodeDirectEditPolicy和 NodeGraphicalNodeEditPolicy,另外两个则是与图形无关的编辑策略。可以看到,在后一种类型的两个类 (ConnectionEditPolicy和NodeEditPolicy)中我们只覆盖了createDeleteCommand()方法,该方法用 于创建一个负责”删除”操作的Command对象并返回,要搞清这个方法看似矛盾的名字里create和delete是对不同对象而言的。

有了Command和EditPolicy,现在可以来看看EditPart部分了。每一个模型对象都对应一个EditPart,所以我们的三个模 型对象(Element不算)分别对应DiagramPart、ConnectionPart和NodePart。对于含有子元素的EditPart,必 须覆盖getModelChildren()方法返回子对象列表,例如DiagramPart里这个方法返回的是Diagram对象包含的Node对象列 表。

每个EditPart都有active()和deactive()两个方法,一般我们在前者里注册监听器(因为实现了 PropertyChangeListener接口,所以EditPart本身就是监听器)到模型对象,在后者里将监听器从列表里移除。在触发监听器事件 的propertyChange()方法里,一般是根据”事件名”称决定使用何种方式刷新视图,例如对于NodePart,如果是节点本身的属性发生变 化,则调用refreshVisuals()方法,若是与它相关的连接发生变化,则调用refreshTargetConnections()或 refreshSourceConnections()。这里用到的事件名称都是我们自己来规定的,在例子中比如Node.PROP_NAME表示节点的 名称属性,Node.PROP_LOCATION表示节点的位置属性,等等。

EditPart(确切的说是AbstractGraphicalEditpart)另外一个需要实现的重要方法是createFigure(), 这个方法应该返回模型在视图中的图形表示,是一个IFigure类型对象。一般都把这些图形放在figures包里,例子里只有NodeFigure一个 自定义图形,Diagram对象对应的是GEF自带的名为FreeformLayer的图形,它是一个可以在东南西北四个方向任意扩展的层图形;而 Connection对应的也是GEF自带的图形,名为PolylineConnection,这个图形缺省是一条用来连接另外两个图形的直线,在例子里 我们通过setTargetDecoration()方法让连接的目标端显示一个箭头。

最后,要为EditPart增加适当的EditPolicy,这是通过覆盖EditPart的createEditPolicies()方法来实现 的,每一个被”安装”到EditPart中的EditPolicy都对应一个用来表示角色(Role)的字符串。对于在模型中有子元素的 EditPart,一般都会安装一个EditPolicy.LAYOUT_ROLE角色的EditPolicy(见下面的代码),后者多为 LayoutEditPolicy的子类;对于连接类型的EditPart,一般要安装 EditPolicy.CONNECTION_ENDPOINTS_ROLE角色的EditPolicy,后者则多为 ConnectionEndpointEditPolicy或其子类,等等。

installEditPolicy(EditPolicy.LAYOUT_ROLE, new DiagramLayoutEditPolicy());

用户的操作会被当前工具(缺省为选择工具SelectionTool)转换为请求(Request),请求根据类型被分发到目标EditPart所安装的EditPolicy,后者根据请求对应的角色来判断是否应该创建命令并执行。

在以前的帖子里说过,Role-EditPolicy-Command这样的设计主要是为了尽量重用代码,例如同一个EditPolicy可以被安 装在不同EditPart中,而同一个Command可以被不同的EditPolicy所使用,等等。当然,凡事有利必有弊,我认为这种的设计也有缺点, 首先在代码上看来不够直观,你必须对众多Role、EditPolicy有所了解,增加了学习周期;另外大部分不需要重用的代码也要按照这个相对复杂的方 式来写,带来了额外工作量。

以上就是一个GEF应用程序里最基本的几个组成部分,例子中还有如Direct Edit、属性表和大纲视图等一些功能没有讲解,下面的帖子里将介绍这些常用功能的实现。

[Eclipse]GEF入门系列(二、GEF概述)

在前面的帖子已经提到,GEF(Graphical Editor Framework)是一个图形化编辑框架,它允许开发人员以图形化的方式展示和编辑模型,从而提升用户体验。这样的应用程序有很多,例如:UML类图编辑器、图形化XML编辑器、界面设计工具以及图形化数据库结构设计工具等等。归结一下,可以发现它们在图形化编辑方面具有以下共同之处:

  • 提供一个编辑区域和一个工具条,用户在工具条里选择需要的工具,以拖动或单击的方式将节点或连接放置在编辑区域;
  • 节点可以包含子节点;
  • 用户能够查看和修改某个节点或连接的大部分属性;
  • 连接端点锚定在节点上;
  • 提供上下文菜单和键盘命令;
  • 提供图形的缩放功能;
  • 提供一个大纲视图,显示编辑区域的缩略图,或是树状模型结构;
  • 支持撤消/重做功能;
  • 等等。

图1 基于GEF的界面设计工具(Visual Editor,VE)的工作界面

GEF最早是Eclipse的一个内部项目,后来逐渐转变为Eclipse的一个开源工具项目,Eclipse的不少其他子项目都需要它的支持。Eclipse 3.0版本花了很大功夫在从Platform中剥离各种功能部件上,包括GEF和IDE在内的很多曾经只能在Eclipse内部使用的工具成为可以独立使用的软件/插件包了。理论上我们是可以脱离Eclipse用GEF包构造自己的应用程序的,但由于它们之间天然的联系,而且Eclipse确实是一个很值得支持的开发平台,所以我还是推荐你在Eclipse中使用它。

GEF的优势是提供了标准的MVC(Model-View-Control)结构,开发人员可以利用GEF来完成以上这些功能,而不需要自己重新设计。与其他一些MVC编辑框架相比,GEF的一个主要设计目标是尽量减少模型和视图之间的依赖,好处是可以根据需要选择任意模型和视图的组合,而不必受开发框架的局限(不过实际上还是很少有脱离Draw2D的实现)。

现在来看看GEF是如何实现MVC框架的吧,在这个帖子里我们先概括介绍一下它的各个组成部分,以后将结合例子进行更详细的说明。

图2 GEF结构图

模型:GEF的模型只与控制器打交道,而不知道任何与视图有关的东西。为了能让控制器知道模型的变化,应该把控制器作为事件监听者注册在模型中,当模型发生变化时,就触发相应的事件给控制器,后者负责通知各个视图进行更新。

典型的模型对象会包含PropertyChangeSupport类型的成员变量,用来维护监听器成员即控制器;对于与其他对象具有连接关系的模型,要维护连入/连出的连接列表;如果模型对应的节点具有大小和位置信息,还要维护它们。这些变量并不是模型本身必须的信息,维护它们使模型变得不够清晰,但你可以通过构造一些抽象模型类(例如让所有具有连接的模型对象继承Node类)来维持它们的可读性。

相对来讲GEF中模型是MVC中最简单的一部分。

控制器:我们知道,在MVC结构里控制器是模型与视图之间的桥梁,也是整个GEF的核心。它不仅要监听模型的变化,当用户编辑视图时,还要把编辑结果反映到模型上。举个例子来说,用户在数据库结构图上删除一个表时,控制器应该从模型中删除这个表对象、表中的字段对象、以及与这些对象有关的所有连接。当然在GEF中这些操作不是由直接控制器完成的,这个稍后就会说到。

GEF中的控制器是所谓的EditPart对象,更确切的说应该是一组EditPart对象共同组成了GEF的控制器这部分,每一个模型对象都对应一个EditPart对象。你的应用程序中需要有一个EditPartFactory对象负责根据给定模型对象创建对应的EditPart对象,这个工厂类将被视图利用。

RootEditPart是一种特殊的EditPart,它和你的模型没有任何关系,它的作用是把EditPartViewer和contents(应用程序的最上层EditPart,一般代表一块画布)联系起来,可以把它想成是contents的容器。EditPartViewer有一个方法setRootEditPart()专门用来指定视图对应的RooEditPart。

图3 EditPart对象

用户的编辑操作被转换为一系列请求(Request),有很多种类的请求,这些种类在GEF里被称为角色(Role),GEF里有图形化和非图形化这两大类角色,前者比如Layout Role对应和布局有关的的操作,后者比如Connection Role对应和连接有关的操作等等。角色这个概念是通过编辑策略(EditPolicy)来实现的,EditPolicy的主要功能是根据请求创建相应的命令(Command),而后者会直接操作模型对象。对每一个EditPart,你都可以”安装”一些EditPolicy,用户对这个EditPart的特定操作会被交给已安装的对应EditPolicy处理。这样做的直接好处是可以在不同EditPart之间共享一些重复操作。

在GEF SDK提供的帮助文档(GEF开发指南)里有一份详细的EditPolicy、Role和Request类型列表,这里就不赘述了。

视图:前面说过,GEF的视图可以有很多种,GEF目前提供了图形(GraphicalViewer)和树状(TreeViewer)这两种,前者利用Draw2D图形(IFigure)作为表现方式,多用于编辑区域,后者则多用于实现大纲展示。视图的任务同样繁重,除了模型的显示功能以外,还要提供编辑功能、回显(Feedback)、工具提示(ToolTip)等等。

GEF使用EditPartViewer作为视图,它的作用和JFace中的Viewer十分类似,而EditPart就相当于是它的ContentProvider和LabelProvider,通过setContents()方法来指定。我们经常使用的Editor是一个GraphicalEditorWithPalette(GEF提供的Editor,是EditorPart的子类,具有图形化编辑区域和一个工具条),这个Editor使用GraphicalEditViewer和PaletteViewer这两个视图类,PaletteViewer也是GraphicalEditViewer的子类。开发人员要在configureGraphicalViewer()和initializeGraphicalViewer()这两个方法里对EditPartViewer进行定制,包括指定它的contents和EditPartFactory等等。

EditPartViewer同时也是ISelectionProvider,这样当用户在编辑区域做选择操作时,注册的SelectionChangeListener就可以收到选择事件。EditPartViewer会维护各个EditPart的选中状态,如果没有被选中的EditPart,则缺省选中的是作为contents的EditPart。

初步了解了GEF的MVC实现方式,让我们看看典型的GEF应用程序是什么样子的。大部分GEF应用程序都实现为Eclipse的Editor,也就是说整个编辑区域是放置在一个Editor里的。所以典型的GEF应用程序具有一个图形编辑区域包含在一个Editor(例如GraphicalEditorWithPalette)里,可能有一个大纲视图和一个属性页,一个用于创建EditPart实例的EditPartFactory,一些表示业务的模型对象,与模型对象对应的一些EditPart,每个EditPart对应一个IFigure的子类对象显示给用户,一些EditPolicy对象,以及一些Command对象。

GEF应用程序的工作方式如下: EditPartViewer接受用户的操作,例如节点的选择、新增或删除等等,每个节点都对应一个EditPart对象,这个对象有一组按操作Role分开的EditPolicy,每个EditPolicy会对应一些Command对象,Command最终对模型进行直接修改。用户的操作转换为Request分配给适当的EditPolicy,由后者创建适当的Command来修改模型,这些Command会保留在EditDomain(专门用于维护EditPartViewer、Command等信息的对象,一般每个Editor对应唯一一个该对象)的命令堆栈里,用于实现撤消/重做功能。

以上介绍了GEF中一些比较重要的概念,不知道看过之后你是否对它有了一个大概的印象。如果没有也没关系,因为在后面的帖子里将会有结合例子的讲解,我们使用的实例就是序言里提到的第六个项目。

参考资料:

  • GEF开发指南
  • Eclipse Development – Using the Graphical Editing Framework and the Eclipse Modeling Framework

[Eclipse]GEF入门系列(一、Draw2D)

鸡年第一天,首先向大家拜个年——恭祝新春快乐,万事如意。一年之计在于春,你对新的一年有什么安排呢?好的,下面还是进入正题吧。

关于Java2D相信大家都不会陌生,它是基于AWT/Swing的二维图形处理包, JDK附带的示例程序向我们展示了Java2D十分强大的图形处理能力。在Draw2D出现以前,SWT应用程序在这方面一直处于下风,而Draw2D这个SWT世界里的Java2D改变了这种形势。

可能很多人还不十分了解GEF和Draw2D的关系:一些应用程序是只使用Draw2D,看起来却和GEF应用程序具有相似的外观。原因是什么,下面先简单解释一下:

GEF是具有标准MVC(Model-View-Control)结构的图形编辑框架,其中Model由我们自己根据业务来设计,它要能够提供某种模型改变通知的机制,用来把Model的变化告诉Control层;Control层由一些EditPart实现,EditPart是整个GEF的核心部件,关于EditPart的机制和功能将在以后的帖子里介绍;而View层(大多数情况下)就是我们这里要说的Draw2D了,其作用是把Model以图形化的方式表现给使用者。

虽然GEF可以使用任何图形包作为View层,但实际上GEF对Draw2D的依赖是很强的。举例来说:虽然EditPart(org.eclipse.gef.EditPart)接口并不要求引入任何Draw2D的类,但我们最常使用的AbstractGraphicalEditPart类的createFigure()方法就需要返回IFigure类型。由于这个原因,在GEF的SDK中索性包含了Draw2D包就不奇怪了,同样道理,只有先了解Draw2D才可能掌握GEF。

这样,对于一开始提出的问题可以总结如下:Draw2D是基于SWT的图形处理包,它适合用作GEF的View层。如果一个应用仅需要显示图形,只用Draw2D就够了;若该应用的模型要求以图形化的方式被编辑,那么最好使用GEF框架。

现在让我们来看看Draw2D里都有些什么,请看下图。

图1 Draw2D的结构

Draw2D通过被称为LightweightSystem(以下简称LWS)的部件与SWT中的某一个Canvas实例相连,这个Canvas在Draw2D应用程序里一般是应用程序的Shell,在GEF应用程序里更多是某个Editor的Control(createPartControl()方法中的参数),在界面上我们虽然看不到LWS的存在,但其他所有能看到的图形都是放在它里面的,这些图形按父子包含关系形成一个树状的层次结构。

LWS是Draw2D的核心部件,它包含三个主要组成部分:RootFigure是LWS中所有图形的根,也就是说其他图形都是直接或间接放在RootFigure里的;EventDispatcher把Canvas上的各种事件分派给RootFigure,这些事件最终会被分派给适当的图形,请注意这个RootFigure和你应用程序中最顶层的IFigure不是同一个对象,前者是看不见的被LWS内部使用的,而后者通常会是一个可见的画布,它是直接放在前者中的;UpdateManager用来重绘图形,当Canvas被要求重绘时,LWS会调用它的performUpdate()方法。

LWS是连接SWT和Draw2D的桥梁,利用它,我们不仅可以轻松创建任意形状的图形(不仅仅限于矩形),同时能够节省系统资源(因为是轻量级组件)。一个典型的纯Draw2D应用程序代码具有类似下面的结构:

//创建SWT的Canvas(Shell是Canvas的子类) 
Shell shell = new Shell(); 
shell.open(); 
shell.setText("A Draw2d application"); 
//创建LightweightSystem,放在shell上 
LightweightSystem lws = new LightweightSystem(shell); 
//创建应用程序中的最顶层图形 
IFigure panel = new Figure(); 
panel.setLayoutManager(new FlowLayout()); 
//把这个图形放置于LightweightSystem的RootFigure里 
lws.setContents(panel);  
 
//创建应用程序中的其他图形,并放置于应用程序的顶层图形中 
panel.add(); 
while (!shell.isDisposed ()) { 
if (!display.readAndDispatch ()) 
   display.sleep (); 
}

接下来说说图形,Draw2D中的图形全部都实现IFigure(org.eclipse.draw2d.IFigure)接口,这些图形不仅仅是你看到的屏幕上的一块形状而已,除了控制图形的尺寸位置以外,你还可以监听图形上的事件(鼠标事件、图形结构改变等等,来自LWS的EventDispatcher)、设置鼠标指针形状、让图形变透明、聚焦等等,每个图形甚至还拥有自己的Tooltip,十分的灵活。

Draw2D提供了很多缺省图形,最常见的有三类:1、形状(Shape),如矩形、三角形、椭圆形等等;2、控件(Widget),如标签、按钮、滚动条等等;3、层(Layer),它们用来为放置于其中的图形提供缩放、滚动等功能,在3.0版本的GEF中,还新增了GridLayer和GuideLayer用来实现”吸附到网格”功能。在以IFigure为根节点的类树下有相当多的类,不过我个人感觉组织得有些混乱,幸好大部分情况下我们只用到其中常用的那一部分。

图2 一个Draw2D应用程序

每个图形都可以拥有一个边框(Border),Draw2D所提供的边框类型有GroupBoxBorder、TitleBarBorder、ImageBorder、ButtonBorder,以及可以组合两种边框的CompoundBorder等等,在Draw2D里还专门有一个Insets类用来表示边框在图形中所占的位置,它包含上下左右四个整型数值。

我们知道,一个图形可以包含很多个子图形,这些被包含的图形在显示的时候必须以某种方式被排列起来,负责这个任务的就是父图形的LayoutManager。同样的,Draw2D已经为我们提供了一系列可以直接使用的LayoutManager,如FlowLayout适合用于表格式的排列,XYLayout适合让用户在画布上用鼠标随意改变图形的位置,等等。如果没有适合我们应用的LayoutManager,可以自己定制。每个LayoutManager都包含某种算法,该算法将考虑与每个子图形关联的Constraint对象,计算得出子图形最终的位置和大小。

图形化应用程序的一个常见任务就是在两个图形之间做连接,想象一下UML类图中的各种连接线,或者程序流程图中表示数据流的线条,它们有着不同的外观,有些连接线还要显示名称,而且最好能不交叉。利用Draw2D中的Router、Anchor和Locator,可以实现多种连接样式,其中Router负责连接线的外观和操作方式,最简单的是设置Router为null(无Router),这样会使用直线连接,其他连接方式包括折线、具有控制点的折线等等(见图3),若想控制连接线不互相交叉也需要在Router中作文章。Anchor控制连接线端点在图形上的位置,即”锚点”的位置,最易于使用的是ChopBoxAnchor,它先假设图形中心为连接点,然后计算这条假想连线与图形边缘的交汇点作为实际的锚点,其他Anchor还有EllipseAnchor、LabelAnchor和XYAnchor等等;最后,Locator的作用是定位图形,例如希望在连接线中点处以一个标签显示此连线的名称/作用,就可以使用MidpointLocator来帮助定位这个标签,其他Locator还有ArrowLocator用于定位可旋转的修饰(Decoration,例如PolygonDecoration)、BendpointerLocator用于定位连接控制点、ConnectionEndpointLocator用于定位连接端点(通过指定uDistance和vDistance属性的值可以设置以端点为原点的坐标)。

图3 三种Router的外观

此外,Draw2D在org.eclipse.draw2d.geometry包里提供了几个很方便的类型,如Dimension、Rectangle、Insets、Point和PointList等等,这些类型既在Draw2D内部广泛使用,也可以被开发人员用来简化计算。例如Rectangle表示的是一个矩形区域,它提供getIntersection()方法能够方便的计算该区域与另一矩形区域的重叠区域、getTransposed()方法可以得到长宽值交换后的矩形区域、scale()方法进行矩形的拉伸等等。在自己实现LayoutManager的时候,由于会涉及到比较复杂的几何计算,所以更推荐使用这些类。

以上介绍了Draw2D提供的大部分功能,利用这些我们已经能够画出十分漂亮的图形了。但对大多数实际应用来说这样还远远不够,我们还要能编辑它,并把对图形的修改反映到模型里去。为了漂亮的完成这个艰巨任务,GEF绝对是不二之选。从下一次开始,我们将正式进入GEF的世界。

参考资料:

[Eclipse]GEF入门系列(序)

前些天换了新电脑,本人一直处于兴奋中,基本是”不务正业”的状态。快过年了,虽然没什么动力干活,但我玩游戏技术比较差,魔兽3打电脑一家还很费劲,干脆写写帖子就当是休息吧!

由于工作的需要,最近开始研究GEF(Graphical Editor Framework)这个框架,它可以用来给用户提供图形化编辑模型的功能,从而提升用户体验,典型的应用如图形化的流程设计器、UML类图编辑器等等。其实一年多来我们做的项目都是和它有关的,只是之前我具体负责的事情和它没什么关系。那时也看过黄老大写的代码,EMF和GEF混在一起特别晕,没能坚持看下去。这次自己要动手做了,正好趁此机会把它搞明白,感觉GEF做出来的东西给人很专业的感觉,功能也很强大,应该挺有前途的。此外,GEF里用到了很多经典模式,最突出的如大量应用Command模式,方便的实现Undo/Redo功能等等,通过学习GEF,等于演练了这些模式,比只是看看书写几个类那种学习方式的效果好很多。

现在网上关于GEF的文章和教程还不是很多(比起一年前还是增加了几篇),基本上都是eclipse.org上的那些,其中少数几篇有中文版,中文的原创就属于凤毛麟角了,市场上似乎也没有这方面的成书。GEF SDK里自带的文档则比较抽象,不适合入门。我觉得最好的入门方法是结合具体的例子,一边看代码,一边对照文档,然后自己再动手做一做。当然这个例子要简单点才好,像GEF的那个logic的例子就太复杂了,即使是flow(运行界面见下图)我觉得也有点大;另外例子要比较规范的,否则学成错误的路子以后还要花时间改就不值得了。

用GEF编写的流程编辑器

GEF的结构决定了GEF应用程序的复杂性,即使最最简单的GEF程序也包含五六个包和十几个类,刚开始接触时有点晕是很正常的。我找到一个还不错的例子,当然它很简单了,如果你现在就想自己试试GEF,可以点这里下载一个zip包(若已无法下载请用这个链接),展开后是六个项目(pt1,pt2,…,pt6),每一个是在前面一个的基础上增加一些功能得到的,pt1是最简单的一个,这样你就可以看到那些典型的功能(例如DirectEdit、Palette等等)在GEF里应该怎样实现了。关于这个例子的更多信息请看作者blog上的说明

“Back in March, I talked a little about my initial attempts writing an Eclipse Graphical Editor Framework (GEF) application. I wanted, then, to write a tutorial that essentially walked the reader through the various stages of the development of my first application. I even suggested some kind of versioned literate programming approach to writing the tutorial and the code at the same time.

I haven’t had time since then to make any progress, but I did get the GEF application to the stage where I had put together a snapshot at each of six milestones. A few people have written to me over the last six months asking the status of my tutorial and I’ve sent them my six snapshots as a starting point.

It makes sense for me to just to offer them here.

You can download a ZIP file with the six snapshots at http://jtauber.com/2004/gef/gef.zip.

Hopefully they are still useful, even without a surrounding tutorial.”

需要注意一点,这个例子应该是在Eclipse 2.1里写的,所以如果你想在Eclipse 3里运行这个例子,要修改plugin.xml里的dependencies为:

<import plugin="org.eclipse.core.resources"/> 
<import plugin="org.eclipse.gef"/>
<import plugin="org.eclipse.ui"/> 
<import plugin="org.eclipse.core.runtime"/> 
<import plugin="org.eclipse.core.runtime.compatibility"/> 
<import plugin="org.eclipse.ui.views"/>

再修改一下DiagramCreationWizard这个类finish()方法里page.openEditor(newFile);这句改为page.openEditor(new FileEditorInput(newFile),”com.jtauber.river.editor”);,还有一些warning不太影响,可以不用管。

或者如果你不是特别着急的话,留意我这个半新手写的GEF入门系列帖子,说不定能引起你更多的共鸣,也是一个办法吧。

GEF的学习周期是比较长的,学之前应该有这个心理准备。特别是如果你没有开发过Eclipse插件,那么最好先花时间熟悉一下Eclipse的插件体系结构,这方面的文章还是很多的,也不是很难,基本上会开发简单的Editor就可以了,因为GEF应用程序一般都是在Editor里进行图形编辑的。另外,绝大多数GEF应用程序都是基于Draw2D的,可以说GEF离不开Draw2D,而后者有些概念很难搞明白,加上其文档比GEF更少,所以我会从Draw2D开始说起,当然不能讲得很深入,因为我自己也是略知皮毛而已。

说实话,我对写这个系列不太有信心,因为自己也是刚入门而已。但要是等到几个月后再写,很多心得怕是讲不出来了。所以还是那句话,有什么写错的请指正,并且欢迎交流。