由模型生成GEF应用程序:Merlin

接触和使用过EMF的朋友都知道,只要定义好ecore模型,就能够利用EMF的代码生成工具得到一个可用编辑器,而ecore模型可以从rose模型、java接口或xml schema很方便的转换生成。不过,虽然得到的编辑器从功能上来说是足够了,但针对不同的需求还须要进行定制,这里的工作并不少,如果想定制为图形化编辑器要改的就更多了,基本上相当于重写的工作量。

Merlin开源项目可以为我们节约不少的时间,它是一个基于EMF的模型转换和代码生成工具。EMF本身具有生成代码的功能(例如生成模型java代码、编辑框架和编辑器),Merlin是对它的增强,支持生成GEF实现的图形化编辑器。在介绍EMF的各种资料里经常可以看到图书馆的例子,它的rose模型可以通过这个链接下载到,这里就简单介绍一下利用这个模型生成GEF应用程序的过程。我用的环境是Eclipse 3.1M7、GEF 3.1M7和EMF 2.1.0(注意:EMF用3月份版本会报错,升级为5月26的版本正常)。

首先新建一个EMF项目,名称叫library就可以,选择rose模型导入方式,然后输入URL或本地文件名(library.mdl)就可以得到library.ecore和library.genmodel文件,其中前者是ecore模型(元模型为ecore),后者是与其同步的供EMF代码生成器使用的一个模型。需要的话,可以利用缺省编辑器对.genmodel文件里的各个节点进行一些设置,如包名、属性的性质等等。

然后利用genmodel编辑器从library.genmodel生成模型代码、编辑框架和编辑器,这样我们就得到了三个完整的项目(library、library.edit和library.editor),后面将要生成的GEF编辑器会依赖前两个。

现在利用新建向导创建一个GEF Generation Model(在Merlin Tools->GEF类别下),名字取为library.gefgenmodel,输入文件就是上一步得到的library.genmodel文件,向导结束后即可得到用来生成GEF编辑器的模型文件。在这个文件里可以对将要生成的程序进行初步设置,例如各种类名、包名、Palette里的工具项、EditPart上都安装哪些EditPolicy以及对应的图形等等。

最后,在.gefgenmodel文件编辑器里选中Library:Gen GEF Model节点,点鼠标右键激活上下文菜单,执行里面的“Generate”命令即可生成新的library.gef项目,这个项目包含一个完整GEF应用程序应该具有的各部分代码,但其中一些类是与merlin相关的,例如生成的LibraryEditor继承自com.metys.merlin.generation.gef.editor.GEFEditor,LibraryEditPart也是如此,我觉得如果是完全独立的就更好了,否则还要对Merlin有所了解……

一般的步骤就是上面这样,如果有更详细的修改就要在得到的代码里进行了,和修改EMF生成的编辑器是一样的,修改后不要忘记去掉前面的“generated”标记。其实如果不对.genmodel和.gefgenmodel做任何修改,全部使用缺省设置也能够得到可以工作的代码,只是没有“个性”而已,对图书馆模型的这种“标准”实现如下图。

file
图1 用Merlin生成的图书馆编辑器,基本功能都有了

生成GEF编辑器只是Merlin的一个小功能而已,Merlin的作者一定是Jet高手,并且对模型映射有所研究,而后者在MDA中具有重要作用。从Merlin的包名来看,它以前大概是某公司的产品,后来转为开源项目继续开发的,虽然目前开发者名单里只有两个成员,但版本的发布却十分频繁,在我看来质量也不错,对于使用EMF的开发者来说应该是个有用的工具。

BTW,Eclipse.org上新增了一篇教你如何结合EMF与GEF的文章:Using GEF With EMF,我就是从这里面发现Merlin的。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/06/09/171423.html

关于本体编程的实现

临近期末,我有一门课程的期末项目是做一个教育领域的本体应用系统,所以最近经常思考本体在这样一个系统中所起的作用,以及该如何实现。(本体是否只能在web环境下发挥作用,使用本体描述一个独立系统的模型是否值得?)

假设要做的是选课系统,很容易看出系统里应该有这些对象:课程、学生、教师,它们之间互有联系。现在的问题是,本体、Java类和数据库各扮演怎样的角色?我目前想到的方法有以下几个:

  1. 在本体(owl)里建立这些类和关系,在Java里建立同样的Bean类,运行时系统把本体里的individuals转换为Java类的实例,也就是在内存里得到一个Java实例的集合,选课的各种操作就是对这个集合的修改,退出系统时再进行反向转换,把修改反映到本体里。在这样的实现方法中,本体实际上扮演了数据库的角色,所以当数据(individuals)很多的时候,效率会很成问题。(Protege的owl编辑工具提供了从本体生成EMF模型代码的功能,虽然目前还是alpha版,这也许将成为同步两种模型的不错选择。)

  2. 对上面的方法进行修改,让本体不保存individuals,对Java实例集合的修改最终反映到关系数据库里(利用hibernate不会很困难),运行时系统直接从数据库里取得数据转换为Java实例。这样的实现可以解决效率问题,但和传统应用区别不大,本体的作用几乎为零。

  3. 另一种比较极端的做法是只用本体维护类和individuals,Java方面没有任何与应用有关的模型,系统一开始把本体读入内存以rdf图的方式存在,选课操作转换为对此图的修改(利用Jena等API)。这个方法存在两个问题,一是效率,二是Java代码的可读性下降,这就相当于直接使用JDBC而不是hibernate对数据库操作的区别。

在solo项目里我使用的是第一种方式,各方面效果还可以接受,但本体的作用发挥得很不够。这是很重要的方面,因为如果和传统项目没有区别,辛辛苦苦引入本体又是为了什么,特别是对本体推理的功能,我想最关键的问题还是要找出最适合应用的本体,定义本体的确是一门学问。

Update:IBM Alphaworks也提供了一组本体工具(包括Orient、EODM和RStar),对EMF的支持应该不错。今天初步试了一下Orient,它只支持RDF(S),而不支持OWL,所以无法满足课程项目的要求,Orient的目前版本还有一些小bug,除已知的那些以外,我把.ontology文件输出为ecore模型总是不成功,而输出为rdf是可以的。

Update:推荐一个关于本体和模型驱动的幻灯片,主要内容是介绍应该如何利用UML的可视化编辑功能和元模型的扩展功能来构造本体,这里面介绍了相当多的相关概念(其中很多我甚至没听说过),以及它们出现的原因,比较有利于我们理清思路。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/06/05/168240.html

巧遇老同事

今天中午吃完饭在公司楼下闲聊的时候,有个人走到我面前说:“还记得我吗?”。我记性很差的,认人的能力也不行,不过一看到他我马上说出了他的名字(CQD),真没想到在这里能遇到他。说来也奇怪,上周五在回家的路上我还忽然想起他,不知道他最近混得怎么样。

我们俩原来在同一家公司干过几个月,当时在同一个项目小组里搞开发,后来由于各自的原因先后离开,时间一久就断了联系,到现在有两三年没见面了。其实我对他了解的也不多,但在我看来他是个很淳朴的人,不仅外表看起来忠厚老实,讲起话来也不急不躁,很平和,完全没有“社会上”的感觉,今天和他聊了一会儿竟然觉得心里一丝安慰。

他现在发展得很不错,在这里工作两年,工资已经是那时的好几倍了,他说:“这已经是我第五个工作了!”,看得出他对目前的状况很满意,他还说不打算再跳了。我也挺替他感到高兴的,不过想想自己,同样的年龄,却还处于艰苦的学习和积累经验阶段,即将面对的社会充满竞争和变数,一年后我能追上他吗,两年?

最后我们互留了MSN,我看到他的昵称是"Working is beautiful"。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/05/30/165204.html

[Eclipse]Plugin.xml转换为Manifest.mf的规则

为了更好的实现动态加载/卸载插件,Eclipse从3.0开始实现OSGI规范,原先在plugin.xml文件里定义的很多内容都被推荐放在manifest.mf文件里,只有是例外。下面是两种文件格式中各元素的对照表:

plugin.xml tag/attribute
manifest.mf header
<plugin id=> Bundle-SymbolicName
<plugin version=> Bundle-Version
<plugin name=> Bundle-Name
<plugin provider=> Bundle-Vendor
<plugin class=> Bundle-Activator
<fragment plugin-id=> Fragment-Host
<fragment plugin-version=> Fragment-Host: ; bundle-version=
, Require-Bundle
, Bundle-ClassPath

具体的转换方法在这里可以看到,也就是说,我们在Eclipse 3.0以上版本中开发插件的时候,最好使用manifest.mf文件的方式(实际上Eclipse自带的Plugin Manifest Editor在这方面已经帮我们做了不少工作,新版本的Eclipse在创建新项目时会自动把可以放在manifest.mf文件中的内容转移过去)。

让我试试”eRCP”

我对掌上电脑有兴趣已经好几年了,不过到现在都没能拥有一台实物,毕竟价格挺高的,而且也不是缺它不可的情况。今天在Eclipse网站上下载了一个eRCP,这好象是去年底开始的新项目,是Eclipse RCP向嵌入式应用的发展,相当于对RCP中各个组成部分的精简实现,因为那些设备对应用程序的大小十分敏感。

由于以前没有实际做过这方面的开发,对掌上电脑的使用也不了解,所以让eRCP自带的例子在模拟器上运行起来还花了些功夫,主要是环境麻烦点,这里记下来以免下次忘记,有一些链接我感觉还是比较有用的。

首先从eclipse.org/ercp下载eRCP程序包,它是一个只有4M多的.zip文件,打开一看里面包含了x86(即我们大部分人用的PC机)、wm2003(MS Windows Mobile 2003)和series80(Symbian OS series80)这三种环境的支持,其中前二者的每一个文件夹的结构都和标准RCP应用程序差不多,在eRCP目录下是startup.jar和一个链接文件,然后有plugins和configuration目录;后者这种操作系统我不熟悉,目录里包含的主要是两个.sis文件。

在每个版本里都包含了一个很简单的例子,我先在电脑上试了一下x86版本,因为机器上已经装有jre,所以只要系统path变量里有java.exe所在路径,直接运行j2se.bat即可。(这里还有点问题,在公司的电脑上一切正常,但在我笔记本上提示“An unexpected exception has been detected in native code outside the VM.”,原因未知。)

因为没有掌上电脑,所以要下载一个模拟器,其实搞开发用模拟器应该比较方便。想起前几天在Hi!PDA上看到微软新出了一个wm2003模拟器(查看原贴,下载地址在这里),这回正好用来当环境!还要下载一个ActiveSync软件用来同步和控制PocketPC,同步这个模拟器的方法在原贴里已经有人提到,这里就不重复了,后面一些步骤须要把电脑里的文件复制到PocketPC上就可以利用ActiveSync的Explorer功能。

然后要下载一个PocketPC上的java环境,比较常用的是IBM的j9虚拟机,下载地址在这里(需要登录),下载页面会有很多个文件,不过只下载名称以“weme-wm2003-arm-ppro10”开头(Personal Profile)的那个.exe或.bin文件就够了。在电脑上运行这个程序,会得到一个jre以及一些.cab文件,把名为weme-ppro10-wm2003-arm_22.cab的文件(在我的电脑上该文件位于“C:Program FilesIBMWEME572WM2003armPPro10cab”目录)复制到PocketPC上运行,就会在PocketPC上安装java运行环境了。

现在要把eRCP程序包里wm2003那部分复制到PocketPC的根目录,注意要复制的目录从eRCP开始,即复制后在PocketPC上形成eRCP目录,如下图所示。

file
图1 从电脑里把wm2003下的eRCP目录复制到下

不过现在还不能直接点j9foun链接,因为我们在PocketPC上安装的是j9ppro,链接里的路径和参数都是无效的。解决方法是新建(或修改它为)一个链接把原来内容里的两处foun都改为ppro,修改后如下:

37#"\Program Files\J9\PPRO10\bin\j9.exe" -jcl:ppro10 -cp \eRCP\startup.jar org.eclipse.core.launcher.Main -application org.eclipse.ercp.example.ercpHello.ercpHello -console

假设新的链接名为j9ppro,这时就可以点它运行eRCP例子程序了。执行画面如下图所示。我感觉在模拟器上运行速度慢了许多,不知道在真机上效果会是怎样的,应该会有所提高,毕竟执行速度对PocketPC上的应用来说是至关重要的。

file
图2 eRCP例子在PocketPC上运行效果

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/05/28/163928.html

[Eclipse]GEF入门系列(十一、树的一个实现)

两天前GEF发布了3.1M7版本,但使用下来发现和M6没有什么区别,是不是主要为了和Eclipse版本相配套?希望3.1正式版早日发布,应该会新增不少内容。上一篇帖子介绍了如何实现表格功能,在开发过程中,另一个经常用到的功能就是树,虽然SWT提供了标准的树控件,但使用它完成如组织结构图这样的应用还是不够直观和方便。在目前版本(3.1M7)的GEF中虽然没有直接支持树的实现,但Draw2D提供的例子程序里却有我们可以利用的代码(org.eclipse.draw2d.examples.tree.TreeExample,运行界面见下图),通过它可以节约不少工作量。

file
图1 Draw2D例子中的TreeExample

记得数年前曾用Swing做过一个组织结构图的编辑工具,当时的实现方式是让画布使用XYLayout,在适当的时候计算和刷新每个树节点的位置,算法的思想则是深度优先搜索,非树叶节点的位置由其子节点的数目和位置决定。我想这应该是比较直观的方法吧,但是这次看了Draw2D例子里的实现觉得也很有道理(递归),以前没想到过。在这个例子里树节点图形称为TreeBranch,它包含一个PageNode(表现为带有折角的矩形)和一个透明容器contentsPane,(一个Layer,用来放置子节点)。在一般情况下,TreeBranch本身使用名为NormalLayout的布局管理器将PageNode放在子节点的正上方,而contentsPane则使用名为TreeLayout的布局管理器计算每个子节点应在的位置。所以我们看到的整个树实际上是由很多层子树叠加而成的,任何一个非叶节点对应的图形的尺寸都等于以它为根节点的子树所占区域的大小。

从这个例子里我们还看到,用户可以选择使用横向或纵向组织树(见图2),可以压缩各节点之间的空隙,每个节点可以横向或纵向排列子节点,还可以展开或收起子节点,等等,这为我们实现一个方便好用的树编辑器提供了良好的基础(视图部分的工作大大简化了)。

file
图2 纵向组织的树

这里要插一句,Draw2D例子中提供的这些类的具体内容我没有仔细研究,相当于把它们当作Draw2D API的一部分来用了(包括TreeRoot、TreeBranch、TreeLayout、BranchLayout、NormalLayout、HangingLayout、PageNode等几个类,把代码拷到你的项目中即可使用),因为按照GEF 3.1的计划表,它们很有可能以某种形式出现在正式版的GEF 3.1里。下面介绍一下我是如何把它们转换为GEF应用程序的视图部分从而实现树编辑器的。

首先从模型部分开始。因为树是由一个个节点构成的,所以模型中最主要的就是节点类(我称为TreeNode),它和例子里的TreeBranch图形相对应,它应该至少包含nodes(子节点列表)和text(显示文本)这两个属性;例子里有一个TreeRoot是TreeBranch的子类,用来表示根节点,在TreeRoot里多了一些属性,如horizontal、majorSpacing等等用来控制整个树的外观,所以模型里也应该有一个继承TreeNode的子类,而实际上这个类就应该是编辑器的contents,它对应的图形TreeRoot也就是一般GEF应用程序里的画布,这个地方要想想清楚。同时,虽然看起来节点间有线连接,但这里我们并不需要Connection对象,这些线是由布局管理器绘制的,毕竟我们并不需要手动改变线的走向。所以,模型部分就是这么简单,当然别忘了要实现通知机制,下面看看都有哪些EditPart。

与模型相对应,我们有TreeNodePart和TreeRootPart,后者和前者之间也是继承关系。在getContentPane()方法里,要返回TreeBranch图形所包含的contentsPane部分;在getModelChildren()方法里,要返回TreeNode的nodes属性;在createFigure()方法里,TreeNodePart应返回TreeBranch实例,而TreeRootPart要覆盖这个方法,返回TreeRoot实例;另外要注意在refreshVisuals()方法里,要把模型的当前属性正确反映到图形中,例如TreeNode里有反映节点当前是否展开的布尔变量expanded,则refreshVisuals()方法里一定要把这个属性的当前值赋给图形才可以。以下是TreeNodePart的部分代码:

public IFigure getContentPane() {
    return ((TreeBranch) getFigure()).getContentsPane();
}

protected List getModelChildren() {
    return ((TreeNode) getModel()).getNodes();
}

protected IFigure createFigure() {
    return new TreeBranch();
}

protected void createEditPolicies() {
    installEditPolicy(EditPolicy.COMPONENT_ROLE, new TreeNodeEditPolicy());
    installEditPolicy(EditPolicy.LAYOUT_ROLE, new TreeNodeLayoutEditPolicy());
    installEditPolicy(EditPolicy.SELECTION_FEEDBACK_ROLE, new ContainerHighlightEditPolicy());
}

上面代码中用到了几个EditPolicy,这里说一下它们各自的用途。实际上,从Role中已经可以看出来,TreeNodeEditPolicy是用来负责节点的删除,没有什么特别;TreeNodeLayoutEditPolicy则复杂一些,我把它实现为ConstrainedLayoutEditPolicy的一个子类,并实现createAddCommand()和getCreateCommand()方法,分别返回改变节点的父节点和创建新节点的命令,另外我让createChildEditPolicy()方法返回NonResizableEditPolicy的实例,并覆盖其createSelectionHandles()方法如下,以便在用户选中一个节点时用一个控制点表示选中状态,不用缺省边框的原因是,边框会将整个子树包住,不够美观,并且在多选的时候界面比较混乱。

protected List createSelectionHandles() {
    List list=new ArrayList();
    list.add(new ResizeHandle((GraphicalEditPart)getHost(), PositionConstants.NORTH));
    return list;
}

选中节点的效果如下图,我根据需要改变了树节点的显示(修改PageNode类):

file
图3 同时选中三个节点(Node2、Node3和Node8)

最后一个ContainerHighlightEditPolicy的唯一作用是当用户拖动节点到另一个节点区域中时,加亮显示后者,方便用户做出是否应该放开鼠标的选择。它是GraphicalEditPolicy的子类,部分代码如下,如果你看过Logic例子的话,应该不难发现这个类就是我从那里拿过来然后修改一下得到的。

protected void showHighlight() {
    ((TreeBranch) getContainerFigure()).setSelected(true);
}

public void eraseTargetFeedback(Request request) {
    ((TreeBranch) getContainerFigure()).setSelected(false);
}

好了,现在树编辑器应该已经能够工作了。为了让用户使用更方便,你可以实现展开/收起子节点、横向/纵向排列子节点等等功能,在视图部分Draw2D的例子代码已经内置了这些功能,你要做的就是给模型增加适当的属性。我这里的一个截图如下所示,其中Node1是收起状态,Node6纵向排列子节点(以节省横向空间)。

file
图4 树编辑器的运行界面

这个编辑器我花一天时间就完成了,但如果不是利用Draw2D的例子,相信至少要四至六天,而且缺陷会比较多,功能上也不会这么完善。我感觉在GEF中遇到没有实现过的功能前最好先找一找有没有可以利用的资源,比如GEF提供的几个例子就很好,当然首先要理解它们才谈得上利用。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/05/27/163277.html

[Eclipse]GEF入门系列(十、表格的一个实现)

在目前的GEF版本(3.1M6)里,可用的LayoutManager还不是很多,在新闻组里经常会看到要求增加更多布局的帖子,有人也提供了自己的实现,例如这个GridLayout,相当于SWT中GridLayout的Draw2D实现,等等。虽然可以肯定GEF的未来版本里会增加更多的布局供开发者使用(可能需要很长时间),然而目前要用GEF实现表格的操作还没有很直接的办法,这里说说我的做法,仅供参考。

实现表格的方法决定于模型的设计,初看来我们似乎应该有这些类:表格(Table)、行(Row)、列(Column)和单元格(Cell),每个模型对象对应一个EditPart,以及一个Figure,TablePart应该包含RowPart和ColumnPart,问题是RowFigure和ColumnFigure会产生交叉,想象一下你的表格该使用什么样的布局才能容纳它们?使用这样的模型并非不能实现(例如使用StackLayout),但我认为这样的模型需要做的额外工作会很多,所以我使用基于列的模型。

在我的表格模型里,只有三种对象:Table、Column和Cell,但Column有一个子类HeaderColumn表示第一列,同时Cell有一个子类HeaderCell表示位于第一列里的单元格,后面这两个类的作用主要是模拟实现对行的操作--把对行的操作都转换为对HeaderCell的操作。例如,创建一个新行转换为在第一列中增加一个新的单元格,当然在这同时我们要让程序给其余每一列同样增加一个单元格。

file
图1 表格编辑器

现在的问题就是怎样让用户察觉不到我们是在对单元格而不是对行操作。需要修改的地方有这么几处:一是创建新行或改变行位置时显示与行宽一致的插入提示线,二是在用户点击位于第一列中的单元格(HeaderCell)时显示为整个行被选中,三是允许用户通过鼠标拖动改变行高度,最后是在改变行所在位置或大小的时候显示正确的回显(Feedback)图形。下面依次介绍它们的实现方法。

调整插入线的宽度

在我们的调色板里有一个Row工具项,代表表格中的一个行,它的作用是创建新的行。注意这个工具项的名字虽然叫Row,实际上用它创建的是一个HeaderCell对象,创建它的代码如下:

tool = new CombinedTemplateCreationEntry("Row", "Create a new Row", HeaderCell.class, new SimpleFactory(HeaderCell.class), CbmPlugin.getImageDescriptor(IConstants.IMG_ROW), null);

创建新行的方式是从调色板里拖动它到想要的位置。在拖动过程中,随着鼠标所在位置的变化,编辑器应该能显示一条直线,用来表示如果此时放开鼠标新行将插入的位置。由于这个工具代表的是一个单元格,所以缺省情况下GEF会显示一条与单元格长度相同的插入线,为了让用户感觉到是在插入行,我们必须改变插入线的宽度。具体的方法是在HeaderColumnPart的负责Layout的那个EditPolicy(继承FlowLayoutEditPolicy)中覆盖showLayoutTargetFeedback()方法,修改后的代码如下:

protected void showLayoutTargetFeedback(Request request) {
    super.showLayoutTargetFeedback(request);
    // Expand feedback line's width
    Diagram diagram = (Diagram) getHost().getParent().getModel();
    Column column = (Column) getHost().getModel();
    Point p2 = getLineFeedback().getPoints().getPoint(1);
    p2.x = p2.x + (diagram.getColumns().size() - 1) * (column.getWidth() + IConstants.COLUMN_SPACING);
    getLineFeedback().setPoint(p2, 1);
}

其中p2代表插入线中右边的那个点,我们将它的横坐标加上一个量即可增加这条线的长度,这个量和表格当前列的数目有关,和列间距也有关,计算的方法看上面的代码很清楚。这样修改后的效果如下图所示,拖动行到新的位置时也会使用同样的插入线。

file
图2 与表格同宽的插入线

选中整个行

缺省情况下,鼠标点击一个单元格会在这个单元格四周产生一个黑色的边框,用来表示被选中的状态。为了让用户能选中整个行,要修改HeaderCell上的EditPolicy。在前面一篇帖子里已经专门讲过,单元格作为列的子元素,要修改它的EditPolicy就要在ColumnPart的EditPolicy的createChildEditPolicy()方法里返回自定义的EditPolicy,这里我返回的是自己实现的DragRowEditPolicy,它继承自GEF内置的ResizableEditPolicy类,它将被HeaderColumnPart加到子元素HeaderCellPart的EditPolicy列表。现在就来修改DragRowEditPolicy以实现整个行的选中。

首先要说明,在GEF里一个图形被选中时出现的黑边和控制点称为Handle,其中黑边称为MoveHandle,用于移动图形;而那些控制点称为ResizeHandle,用于改变图形的尺寸。要改变黑边的尺寸(由单元格的宽度扩展为整个表格的宽度),我们得继承MoveHandle并覆盖它的getLocator()方法,下面的代码是我的实现:

public class RowMoveHandle extends MoveHandle {
    public RowMoveHandle(GraphicalEditPart owner, Locator loc) {
        super(owner, loc);
    }
    public RowMoveHandle(GraphicalEditPart owner) {
        super(owner);
    }
    //计算得到选中行所占的位置,传给MoveHandleLocator作为参考
    public Locator getLocator() {
        IFigure refFigure = new Figure();
        Rectangle rect=((HeaderCellPart) getOwner()).getRowBound();
        translateToAbsolute(rect);
        refFigure.setBounds(rect);
        return new MoveHandleLocator(refFigure);
    }
}

在getLocator()方法里,我们调用了HeaderCellPart的getRowBound()方法用于得到选中行的位置和尺寸,这个方法的代码如下(放在HeaderCellPart里是因为在Handle里通过getOwner()可以很容易得到EditPart对象),行尺寸的计算方法与前面插入线的情况类似:

public Rectangle getRowBound(){
    Rectangle rect = getFigure().getBounds().getCopy();
    Diagram diagram = (Diagram) getParent().getParent().getModel();
    Column column = (Column) getParent().getModel();
    rect.setSize(diagram.getColumns().size() * column.getWidth() + (diagram.getColumns().size() - 1) * IConstants.COLUMN_SPACING, rect.getSize().height);
    return rect;
}

有了这个RowMoveHandle,只要把它代替原来缺省的MoveHandle加到HeaderColumnCell上即可,具体的方法就是覆盖DragRowEditPolicy的createSelectionHandles()方法,ResizableEditPolicy对这个方法的缺省实现是加一个黑框和八个控制点,而我们要改成下面这样:

protected List createSelectionHandles() {
    List l = new ArrayList();
    //四周的黑色边框
    l.add(new RowMoveHandle((GraphicalEditPart) getHost()));
    //下方的控制点
    l.add(new RowResizeHandle((GraphicalEditPart) getHost(), PositionConstants.SOUTH));
    return l;
}

代码里用到的RowResizeHandle类是控制点的自定义实现,在下面很快会讲到。现在,用户可以看到整个行被选中的效果了。

file
图3 选中整个行

改变行的高度

改变行高度比较自然的方式是让用户选中行后自由拖动下面的边。前面说过,GEF里的ResizeHandle具有调整图形尺寸的功能,美中不足的是ResizeHandle表现为黑色(或白色,非主选择时)的小方块,而我们希望它是一条线就好了,这样鼠标指针只要放在选中行的下边上就会变成改变尺寸的样子。这就需要我们实现刚才提到的RowResizeHandle类了,它是ResizeHandle的子类,代码如下:

public class RowResizeHandle extends ResizeHandle {
    public RowResizeHandle(GraphicalEditPart owner, int direction) {
        super(owner, direction);
        //改变控制点的尺寸,使之变成一条线
        setPreferredSize(new Dimension(((HeaderCellPart) owner).getRowBound().width, 2));
    }
    public RowResizeHandle(GraphicalEditPart owner, Locator loc, Cursor c) {
        super(owner, loc, c);
    }
    //缺省实现里控制点有描边,我们不需要,所以覆盖这个方法
    public void paintFigure(Graphics g) {
        Rectangle r = getBounds();
        g.setBackgroundColor(getFillColor());
        g.fillRectangle(r.x, r.y, r.width, r.height);
    }
    //与前面RowMoveHandle类似,但返回RelativeHandleLocator以使线显示在图形下方
    public Locator getLocator() {
        IFigure refFigure = new Figure();
        Rectangle rect=((HeaderCellPart) getOwner()).getRowBound();
        translateToAbsolute(rect);
        refFigure.setBounds(rect);
        return new RelativeHandleLocator(refFigure, PositionConstants.SOUTH);
    }
    //不论是否为主选择,都使用黑色填充
    protected Color getFillColor() {
        return ColorConstants.black;
    }
}

这样,我们就把控制点拉成了控制线,因为它的位置与选择框(RowMoveHandle)的一部分重合,所以在界面上感觉不到它的存在,但用户可以通过它控制行的高度,见下图。

file
图4 改变行高的提示

正确的回显图形

我们知道,在拖动图形和改变图形尺寸的时候,GEF会显示一个"影图"(Ghost Shape)作为回显,也就是显示图形的新位置和尺寸信息。因为操作行时目标对象实际是单元格,所以在缺省情况下回显也是单元格的样子(宽度与列宽相同)。为此,在DragRowEditPolicy里要覆盖getInitialFeedbackBounds()方法,这个方法返回的Rectangle决定了鼠标开始拖动时回显图形的初始状态,见以下代码:

protected Rectangle getInitialFeedbackBounds() {
    return ((HeaderCellPart) getHost()).getRowBound();
}

这时的回显见下图,在拖动行时也使用同样的回显。

file
图5 改变行高时的回显

经过上面的修改,对HeaderCell的操作在界面上已经完全表现为对表格行的操作了。这些操作的结果会转换为一些Command,包括CreateHeaderCellCommand(创建新行,你也可以命名为CreateRowCommand)、MoveHeaderCellCommand(移动行)、DeleteHeaderCellCommand(删除行)和ChangeHeaderCellHeightCommand(改变行高)等,在这些类里要对所有列执行同样的操作(例如改变HeaderCell的高度的同时改变同一行中其他单元格的高度),这样在界面上才能保持表格的外观,详细的代码没有必要贴在这里了。

P.S.曾经考虑过另一种实现表格的方法,就是模型里只有Table和Cell两种对象,然后自己写一个TableLayout负责单元格的布局。同样是因为修改的工作量相对比较大而没有采用,因为那样的话行和列都要使用自定义方式处理,而这篇贴子介绍的方法只关心行的处理就可以了。当然,这里说的也不是什么标准实现,不过效果还是不错的,而且确实可以实现,如果你有类似的需求可以作为参考。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/05/20/159423.html

管理Oracle数据库要注意的一些问题

以下针对Oracle9i数据库:

  1. Commit操作不强制将database buffer内容写到文件;Commit操作后,server process在提示用户“操作已完成”之后才释放相关资源的锁;
  2. 创建数据库时,数据文件和日志文件必须用size指定大小,或用reuse指定到某个已有文件,否则命令会失败;
  3. 若丢失了所有control file的副本,可以通过重建control file恢复数据库;
  4. 在创建unique或primary key约束时,Oracle自动创建索引(名称同约束名),删除这个索引前必须先删除对应约束;
  5. Partitioned table可以被多个进程同时查询或操作;
  6. 当前的log sequence number被存放在control file和每个data file的头部(由CKPT进程写入),log group里的每个member具有同样的log sequence number;
  7. 通过DBA_OBJECTS表可以找到哪些对象存在错误,例如视图所依赖的表已被删除,这个视图的status会被标为invalide;
  8. Read-Only的表空间里的对象可以被Drop,因为该操作只影响dictionary;
  9. 当使用external验证方式时,不能指定password expire;
  10. DBA是role,而不是privilege(SYSDBA和SYSOPER是privilege);
  11. 使用truncate table命令后,并非所有extents都被释放,table将保留个数为minextents的extents;
  12. SMON后台进程负责数据库的recovery,以及合并碎片;
  13. 查询USER_XXX和dictionary表不需要具有select any table权限;
  14. 对于标志为unused的列,可以在同一张表中创建与它同名的列;
  15. Undo段在刚创建时是offline状态的;
  16. 用户自己唯一可以使用alter user命令修改的属性是identified by;
  17. 要修改tablespace的数据文件为自动扩展,应该使用alter database而不是alter tablespace命令;
  18. 创建用户和创建表的动作不记录在alertSID.log文件里;
  19. 没有"Create Index"权限,因为"Create Table"包含了它,Alter和Drop也一样;
  20. 具有"Create Any XXX"权限的用户在其他用户下创建的对象,其所有者是那个用户;
  21. Unlimited tablespace系统权限,只能直接授给用户,而不能授给角色;
  22. Externally管理的角色不能设置为default role;
  23. 没有"Truncate Table"权限,用户拥有"Drop Table"权限即可执行Truncate操作;
  24. Create SPFILE From PFILE命令可以反过来用,即Create PFILE From SPFILE,若不指定PFILE/SPFILE的名称则使用缺省名称;
  25. TableSpace的Storage选项里的Initial和Next值应为Minimum Extent值的整数倍,否则创建语句会失败;
  26. LogSwitch事件会记录在alert_log文件里;
  27. Oracle缺省使用AL16UTF16作为National Character Set,Database Character Set用于SQL和PL/SQL代码,National Character Set用于NChar、NVarchar2和NClob字段;Database Character Set不能使用固定长度多字节类型字符集;
  28. 对于Rollback Segment: minextents至少为2(推荐20),pctincrease永远为零,可以是public或private但一旦确定就不能更改,optimal参数至少为该segment的initial size,initial和next最好相等已保证每个extent具有相同的大小,maxextents最好不要设置为unlimited;刚创建时的roolback segment是offline状态的,必须手工设置为online才可用;drop前必须先设置为offline;
  29. 对于Temporary Tablespace和Temporary Segment: UNIFORM SIZE值最好是SORT_AREA_SIZE的整数倍;Tempfiles不能改名,不能只读,不会进行recovery处理,不能用alter database命令创建,总是nologging模式;
  30. 字典区管理的表空间可以指定自定义的storage选项;本地区管理的表空间的storage选项不可更改,可以指定为自动段空间管理;

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/05/06/150073.html

世界地球日

今天是第36个世界地球日,也是我26岁生日,今年的生日有个惊喜。

file

早上刷牙时发现,有只大白猫在窗台上生下两只小猫,小猫身体小小的眼睛紧闭,简直就像两只小白老鼠。九点钟左右母猫离开不知去哪了,等了两个小时还没回来,可能是发现我在看她觉得不安全吧,所以我决定自己先给小猫喂点奶。谁知小猫用的东西很难买,连跑了三个地方,才在宠物医院买到猫牛奶(要医生开才行)和专用奶瓶。等我兑好温牛奶,都下午两点钟了,谁知小猫虽然五个小时没吃东西但就是不吃我拿的奶,把它拿在手里它就拼命挣扎和叫唤,试了一个多钟头还是没用,还是把它们放回原处让它们睡觉吧。

过了一会儿听到窗台上有动静,悄悄过去一看,原来是那只母猫回来了,她先是对两只小猫叫了几声,然后一次一个把小猫叼在嘴里带走了。

file

虽然一天没能去上班,但小猫终于和妈妈团聚了,我也放心了,祝你们一生平安。

Update04/23: 没想到今天(也许是昨天晚上)母猫带着小猫们又回来了,一共有四只小猫呢,它们好象把窗台当成自己的窝了。GF盛了一些牛奶和猫粮放在窗台上,刚才去看母猫已经吃掉了:)

file

Update:更多照片见http://whitecats.mblogger.cn/

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/04/22/143469.html

[Eclipse]GEF入门系列(九、增加易用性)

当一个GEF应用程序实现了大部分必需的业务功能后,为了能让用户使用得更方便,我们应该在易用性方面做些考虑。从3.0版本开始, GEF增加了更多这方面的新特性,开发人员很容易利用它们来改善自己的应用程序界面。这篇帖子将介绍主要的几个功能,它们有些在GEF 2.1中就出现了,但因为都是关于易用性的而且以前没有提到,所以放在这里一起来说。( 下载示例代码

可折叠调色板

在以前的例子里,我们的编辑器都继承自GraphicalEditorWithPalette。GEF 3.0提供了一个功能更加丰富的编辑器父类:GraphicalEditorWithFlyoutPalette,继承它的编辑器具有一个可以折叠的工具条,并且能够利用Eclipse自带的调色板视图,当调色板视图显示时,工具条会自动转移到这个视图中。

file
图1 可折叠和配置的调色板

与以前的GraphicalEditorWithPalette相比,继承 GraphicalEditorWithFlyoutPalette的编辑器要多做一些工作。首先要实现getPalettePreferences() 方法,它返回一个FlyoutPreferences实例,作用是把调色板的几个状态信息(位置、大小和是否展开)保存起来,这样下次打开编辑器的时候就可以自动套用这些设置。下面使用偏好设置的方式保存和载入这些状态,你也可以使用其他方法,比如保存为.properties文件:

protected FlyoutPreferences getPalettePreferences() {
    return new FlyoutPreferences() {
        public int getDockLocation() {
            return SubjectEditorPlugin.getDefault().getPreferenceStore().getInt(IConstants.PREF_PALETTE_DOCK_LOCATION);
        }
        public void setDockLocation(int location) {
            SubjectEditorPlugin.getDefault().getPreferenceStore().setValue(IConstants.PREF_PALETTE_DOCK_LOCATION,location);
        }
        …
    };
}

然后要覆盖缺省的createPaletteViewerProvider()实现,在这里为调色板增加拖放支持,即指定调色板为拖放源(之所以用这样的方式,原因是在编辑器里没有办法得到它对应的调色板实例),在以前这个工作通常是在initializePaletteViewer ()方法里完成的,而现在这个方法已经不需要了:

protected PaletteViewerProvider createPaletteViewerProvider() {
    return new PaletteViewerProvider(getEditDomain()) {
        protected void configurePaletteViewer(PaletteViewer viewer) {
            super.configurePaletteViewer(viewer);
            viewer.addDragSourceListener(new TemplateTransferDragSourceListener(viewer));
        }
    };
}

GEF 3.0还允许用户对调色板里的各种工具进行定制,例如隐藏某个工具,或是修改工具的描述等等,这是通过给PaletteViewer定义一个 PaletteCustomizer实例实现的,但由于时间关系,这里暂时不详细介绍了,如果需要这项功能你可以参考Logic例子中的实现方法。

缩放

由于Draw2D中的图形都具有天然的缩放功能,因此在GEF里实现缩放功能是很容易的,而且缩放的效果不错。GEF为我们提供了 ZoomInAction和ZoomOutAction以及对应的RetargetAction(ZoomInRetargetAction和 ZoomOutRetargetAction),只要在编辑器里构造它们的实例,然后在编辑器的ActionBarContributer类里将它们添加到想要的菜单或工具条位置即可。因为ZoomInAction和ZoomOutAction的构造方法要求一个ZoomManager类型的参数,而后者需要从GEF的RootEditPart中获得(ScalableRootEditPart或 ScalableFreeformRootEditPart),所以最好在编辑器的 configureGraphicalViewer()里构造这两个Action比较方便,请看下面的代码:

protected void configureGraphicalViewer() {
    super.configureGraphicalViewer();
    ScalableFreeformRootEditPart root = new ScalableFreeformRootEditPart();
    getGraphicalViewer().setRootEditPart(root);
    getGraphicalViewer().setEditPartFactory(new PartFactory());
    action = new ZoomInAction(root.getZoomManager());
    getActionRegistry().registerAction(action);
    getSite().getKeyBindingService().registerAction(action);
    action = new ZoomOutAction(root.getZoomManager());
    getActionRegistry().registerAction(action);
    getSite().getKeyBindingService().registerAction(action);
}

假设我们想把这两个命令添加到主工具条上,在DiagramActionBarContributor里应该做两件事:在 buildActions()里构造对应的RetargetAction,然后在contributeToToolBar()里添加它们到工具条(原理请参考前面关于菜单和工具条的 帖子):

protected void buildActions() {
    //其他命令
    …
    //缩放命令
    addRetargetAction(new ZoomInRetargetAction());
    addRetargetAction(new ZoomOutRetargetAction());
}

public void contributeToToolBar(IToolBarManager toolBarManager) {
    //工具条中的其他按钮
    …
    //缩放按钮
    toolBarManager.add(getAction(GEFActionConstants.ZOOM_IN));
    toolBarManager.add(getAction(GEFActionConstants.ZOOM_OUT));
    toolBarManager.add(new ZoomComboContributionItem(getPage()));
}

请注意,在contributeToToolBar()方法里我们额外添加了一个ZoomComboContributionItem 的实例,这个类也是GEF提供的,它的作用是显示一个缩放百分比的下拉框,用户可以选择或输入想要的数值。为了让这个下拉框能与编辑器联系在一起,我们要修改一下编辑器的getAdapter()方法,增加对它的支持:

public Object getAdapter(Class type) {
    …
    if (type == ZoomManager.class)
        return getGraphicalViewer().getProperty(ZoomManager.class.toString());
    return super.getAdapter(type);
}

现在,打开编辑器后主工具条中将出现下图所示的两个按钮和一个下拉框:

file
图2 缩放工具条

有时候我们想让程序把用户当前的缩放值记录下来,以便下次打开时显示同样的比例。这就须要在画布模型里增加一个zoom变量,在编辑器的初始化过程中增加下面的语句,其中diagram是我们的画布实例:

ZoomManager manager = (ZoomManager) getGraphicalViewer().getProperty(ZoomManager.class.toString());
if (manager != null)
    manager.setZoom(diagram.getZoom());

在保存模型前得到当前的缩放比例放在画布模型里一起保存:

ZoomManager manager = (ZoomManager) getGraphicalViewer().getProperty(ZoomManager.class.toString());
if (manager != null)
    diagram.setZoom(manager.getZoom());

辅助网格

你可能用过一些这样的应用程序,画布里可以显示一个灰色的网格帮助定位你的图形元素,当被拖动的节点接近网格线条时会被"吸附"到网格上,这样可以很容易的把画布上的图形元素排列整齐,GEF 3.0里就提供了显示这种辅助网格的功能。

file
图3 辅助编辑网格

是否显示网格以及是否打开吸附功能是由GraphicalViewer的两个布尔类型的属性(property)值决定的,它们分别是 SnapToGrid.PROPERTY_GRID_VISIBLE和SnapToGrid.PROPERTY_GRID_ENABLED,这些属性是通过GriaphicalViewer.getProperty()和setProperty()方法来操作的。GEF为我们提供了一个 ToggleGridAction用来同时切换它们的值(保持这两个值同步确实符合一般使用习惯),但没有像缩放功能那样提供对应的 RetargetAction,不知道GEF是出于什么考虑。另外因为这个Action没有预先设置的图标,所以把它直接添加到工具条上会很不好看,所以要么把它只放在菜单中,要么为它设置一个图标,至于添加到菜单的方法这里不赘述了。

要想在保存模型时同时记录当前网格线是否显示,必须在画布模型里增加一个布尔类型变量,并在打开模型和保存模型的方法中增加处理它的代码。

几何对齐

这个功能也是为了方便用户排列图形元素的,如果打开了此功能,当用户拖动的图形有某个边靠近另一图形的某个平行边延长线时,会自动吸附到这条延长线上;若两个图形的中心线(通过图形中心点的水平或垂直线)平行靠近时也会产生吸附效果。例如下图中,Subject1的左边与 Subject2的右边是吸附在一起的,Subject3原本是与Subject2水平中心线吸附的,而用户在拖动的过程中它的上边吸附到 Subject1的底边。

file
图4 几何对齐

几何对齐也是通过GraphicalViewer的属性来控制是否打开的,属性的名称是 SnapToGeometry.PROPERTY_SNAP_ENABLED,值为布尔类型。在程序里增加吸附对齐切换的功能和前面说的增加网格切换功能基本是一样的,记住GEF为它提供的Action是ToggleSnapToGeometryAction。

要实现对齐功能,还有一个重要的步骤,那就是在画布所对应的EditPart的getAdapter()方法里增加对 SnapToHelper类的回应,像下面这样:

public Object getAdapter(Class adapter) {
    if (adapter == SnapToHelper.class) {
        List snapStrategies = new ArrayList();
        Boolean val = (Boolean)getViewer().getProperty(RulerProvider.PROPERTY_RULER_VISIBILITY);
        if (val != null && val.booleanValue())
            snapStrategies.add(new SnapToGuides(this));
        val = (Boolean)getViewer().getProperty(SnapToGeometry.PROPERTY_SNAP_ENABLED);
        if (val != null && val.booleanValue())
            snapStrategies.add(new SnapToGeometry(this));
        val = (Boolean)getViewer().getProperty(SnapToGrid.PROPERTY_GRID_ENABLED);
        if (val != null && val.booleanValue())
            snapStrategies.add(new SnapToGrid(this));

        if (snapStrategies.size() == 0)
            return null;
        if (snapStrategies.size() == 1)
            return (SnapToHelper)snapStrategies.get(0);

        SnapToHelper ss[] = new SnapToHelper[snapStrategies.size()];
        for (int i = 0; i < snapStrategies.size(); i++)
            ss[i] = (SnapToHelper)snapStrategies.get(i);
        return new CompoundSnapToHelper(ss);
    }
    return super.getAdapter(adapter);
}

标尺和辅助线

标尺位于画布的上部和左侧,在每个标尺上可以建立很多与标尺垂直的辅助线,这些显示在画布上的虚线具有吸附功能。

file
图5 标尺和辅助线

标尺和辅助线的实现要稍微复杂一些。首先要修改原有的模型,新增加标尺和辅助线这两个类,它们之间的关系请看下图:

file
图6 增加标尺和辅助线后的模型

与上篇帖子里的 模型图比较后可以发现,在Diagram类里增加了四个变量,其中除rulerVisibility以外三个的作用都在前面部分做过介绍,而rulerVisibility和它们类似,作用记录标尺的可见性,当然只有在标尺可见的时候辅助线才是可见的。我们新增了Ruler和 Guide两个类,前者表示标尺,后者表示辅助线。因为辅助线是建立在标尺上的,所以Ruler到Guide有一个包含关系(黑色菱形);画布上有两个标尺,分别用topRuler和leftRuler这两个变量引用,也是包含关系,也就是说,画布上只能同时具有这两个标尺;Node到Guide有两个引用,表示Node吸附到的两条辅助线(为了简单起见,在本文附的例子中并没有实际使用到它们,Guide类中定义的几个方法也没有用到)。Guide类里的map变量用来记录吸附在自己上的节点和对应的吸附边。要让画布上能够显示标尺,首先要将原先的GraphicalViewer改放在一个 RulerComposite实例上(而不是直接放在编辑器上),后者是GEF提供的专门用于显示标尺的组件,具体的改变方法如下:

// 定义一个RulerComposite类型的变量
private RulerComposite rulerComp;

// 创建RulerComposite,并把GraphicalViewer创建在其上
protected void createGraphicalViewer(Composite parent) {
    rulerComp = new RulerComposite(parent, SWT.NONE);
    super.createGraphicalViewer(rulerComp);
    rulerComp.setGraphicalViewer((ScrollingGraphicalViewer) getGraphicalViewer());
}

// 覆盖getGraphicalControl返回RulerComposite实例
protected Control getGraphicalControl() {
    return rulerComp;
}

然后,要设置GraphicalViewer的几个有关属性,如下所示,其中前两个分别表示左侧和上方的标尺,而最后一个表示标尺的可见性:

getGraphicalViewer().setProperty(RulerProvider.PROPERTY_VERTICAL_RULER,new SubjectRulerProvider(diagram.getLeftRuler()));
getGraphicalViewer().setProperty(RulerProvider.PROPERTY_HORIZONTAL_RULER,new SubjectRulerProvider(diagram.getTopRuler()));
getGraphicalViewer().setProperty(RulerProvider.PROPERTY_RULER_VISIBILITY,new Boolean(diagram.isRulerVisibility()));

在前两个方法里用到了SubjectRulerProvider这个类,它是我们从RulerProvider类继承过来的, RulerProvider是一个比较特殊的类,其作用有点像EditPolicy,不过除了一些getXXXCommand()方法以外,还有其他几个方法要实现。需要返回Command的方法包括:getCreateGuideCommand()、getDeleteGuideCommand()和 getMoveGuideCommand(),分别返回创建辅助线、删除辅助线和移动辅助线的命令,下面列出创建辅助线的命令,其他两个的实现方式是类似的,你可以在本文所附例子中找到它们的代码:

public class CreateGuideCommand extends Command {
    private Guide guide;
    private Ruler ruler;
    private int position;

    public CreateGuideCommand(Ruler parent, int position) {
        setLabel("Create Guide");
        this.ruler = parent;
        this.position = position;
    }

    public void execute() {
        guide = ModelFactory.eINSTANCE.createGuide();//创建一条新的辅助线
        guide.setHorizontal(!ruler.isHorizontal());
        guide.setPosition(position);
        ruler.getGuides().add(guide);
    }

    public void undo() {
        ruler.getGuides().remove(guide);
    }
}

接下来再看看RulerProvider的其他方法,SubjectRulerProvider维护一个Ruler对象,在构造方法里要把它的值传入。此外,在构造方法里还应该给Ruler和Guide模型对象增加监听器用来响应标尺和辅助线的变化,下面是Ruler监听器的主要代码(因为使用了EMF作为模型,所以监听器实现为Adapter。如果你不用EMF,可以使用PropertyChangeListener实现):

public void notifyChanged(Notification notification) {
    switch (notification.getFeatureID(ModelPackage.class)) {
        case ModelPackage.RULER__UNIT:
            for (int i = 0; i < listeners.size(); i++)
                ((RulerChangeListener) listeners.get(i)).notifyUnitsChanged(ruler.getUnit());
            break;
        case ModelPackage.RULER__GUIDES:
            Guide guide = (Guide) notification.getNewValue();
            if (getGuides().contains(guide))
                guide.eAdapters().add(guideAdapter);
            else
                guide.eAdapters().remove(guideAdapter);
            for (int i = 0; i < listeners.size(); i++)
                ((RulerChangeListener) listeners.get(i)).notifyGuideReparented(guide);
            break;
    }
}

可以看到监听器在被触发时所做的工作实际上是触发这个RulerProvider的监听器列表(listeners)里的所有监听器,而这些监听器就是RulerEditPart或GuideEditPart,而我们不需要去关心这两个类。Ruler的事件有两种,一是单位(象素、厘米、英寸)改变,二是创建辅助线,在创建辅助线的情况要给这个辅助线增加监听器。下面是Guide监听器的主要代码:

public void notifyChanged(Notification notification) {
    Guide guide = (Guide) notification.getNotifier();
    switch (notification.getFeatureID(ModelPackage.class)) {
        case ModelPackage.GUIDE__POSITION:
            for (int i = 0; i < listeners.size(); i++)
                ((RulerChangeListener) listeners.get(i)).notifyGuideMoved(guide);
            break;
        case ModelPackage.GUIDE__MAP:
            for (int i = 0; i < listeners.size(); i++)
                ((RulerChangeListener) listeners.get(i)).notifyPartAttachmentChanged(notification.getNewValue(),guide);
            break;
    }
}

Guide监听器也有两种事件,一是辅助线位置改变,二是辅助线上吸附的图形的增减变化。请注意,这里的循环一定不要用 iterator的方式,而应该用上面列出的下标方式,否则会出现ConcurrentModificationException异常,原因和 RulerProvider的notifyXXX()实现有关。我们的SubjectRulerProvider构造方法如下所示,它的主要工作就是增加监听器:

public SubjectRulerProvider(Ruler ruler) {
    this.ruler = ruler;
    ruler.eAdapters().add(rulerAdapter);
    //载入模型的情况下,ruler可能已经包含一些guides,所以要给它们增加监听器< span style="color: #008000;">
    for (Iterator iter = ruler.getGuides().iterator(); iter.hasNext();) {
        Guide guide = (Guide) iter.next();
        guide.eAdapters().add(guideAdapter);
    }
}

在RulerProvider里还有几个方法要实现才能正确使用标尺:getRuler()返回RulerProvider维护的 Ruler实例,getGuides()返回辅助线列表,getGuidePosition(Object)返回某条辅助线在标尺上的位置(以pixel 为单位),getPositions()返回标尺上所有辅助线位置构成的整数数组。以下是本例中的实现方式:

public Object getRuler() {
    return ruler;
}
public List getGuides() {
    return ruler.getGuides();
}
public int[] getGuidePositions() {
    List guides = getGuides();
    int[] result = new int[guides.size()];
    for (int i = 0; i < guides.size(); i++) {
        result[i] = ((Guide) guides.get(i)).getPosition();
    }
    return result;
}
public int getGuidePosition(Object arg0) {
    return ((Guide) arg0).getPosition();
}

有了这个自定义的RulerProvider类,再通过把该类的两个实例被放在GraphicalViewer的两个属性(PROPERTY_VERTICAL_RULER和PROPERTY_HORIZONTAL_RULER)中,画布就具有标尺的功能了。GEF提供了用于切换标尺可见性的命令:ToggleRulerVisibilityAction,我们使用和前面同样的方法把它加到主菜单即可控制显示或隐藏标尺和辅助线。

位置和尺寸对齐

图形编辑工具大多具有这样的功能:选中两个以上图形,再按一下按钮就可以让它们以某一个边或中心线对齐,或是调整它们为同样的宽度高度。GEF提供AlignmentAction和MatchSizeAction分别用来实现位置对齐和尺寸对齐,使用方法很简单,在编辑器的 createActions()方法里构造需要的对齐方式Action(例如对齐到上边、下边等等),然后在编辑器的 ActionBarContributor里通过这些Action对应的RetargetAction将它们添加到菜单或工具条即可。编辑器里的代码如下,注意最后一句的作用是把它们加到selectionAction列表里以响应选择事件:

IAction action=new AlignmentAction((IWorkbenchPart)this,PositionConstants.LEFT);
getActionRegistry().registerAction(action);
getSelectionActions().add(action.getId());
…

AlignmentAction的构造方法的参数是编辑器本身和一个代表对齐方式的整数,后者可以是 PositionConstants.LEFT、CENTER、RIGHT、TOP、MIDDLE、BOTTOM中的一个; MatchSizeAction有两个子类,MatchWidthAction和MatchHeightAction,你可以使用它们达到只调整宽度或高度的目的。下图是添加在工具条中的按钮,左边六个为位置对齐,最后两个为尺寸对齐,请注意,当选择多个图形时,被六个黑点包围的那个称为"主选择",对齐时以该图形所在位置和大小为准做调整。

file
图7 位置对齐和尺寸对齐

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/04/21/142558.html