EclipseUML定义枚举项时慎用符号

今天用EclipseUML画类图遇到一个很郁闷的问题,为了保险起见我还是边画边保存的,画了一上午,有一次关闭了编辑器,再想打开时提示“Impossible to load the diagram xxx.ecd”。我这汗一下子就下来了,一上午的工作啊!赶紧查看一下Eclipse的errorlog,异常信息如下:

java.lang.NullPointerException
    at com.omondo.uml.obf.cba.setInput(SourceFile:1153)
    at com.omondo.uml.obf.cba.init(SourceFile:1109)
    at com.omondo.uml.emf.ClassDiagramEditor.init(SourceFile:1013)
    at org.eclipse.ui.internal.EditorManager.createSite(EditorManager.java:784)
    at org.eclipse.ui.internal.EditorReference.createPartHelper(EditorReference.java:585)
    at org.eclipse.ui.internal.EditorReference.createPart(EditorReference.java:374)
    at org.eclipse.ui.internal.WorkbenchPartReference.getPart(WorkbenchPartReference.java:552)
    at org.eclipse.ui.internal.PartPane.setVisible(PartPane.java:285)
    at org.eclipse.ui.internal.presentations.PresentablePart.setVisible(PresentablePart.java:140)
    at org.eclipse.ui.internal.presentations.util.PresentablePartFolder.select(PresentablePartFolder.java:264)
    at org.eclipse.ui.internal.presentations.util.LeftToRightTabOrder.select(LeftToRightTabOrder.java:65)
    at org.eclipse.ui.internal.presentations.util.TabbedStackPresentation.selectPart(TabbedStackPresentation.java:394)
    at org.eclipse.ui.internal.PartStack.refreshPresentationSelection(PartStack.java:1140)
    at org.eclipse.ui.internal.PartStack.setSelection(PartStack.java:1093)

找不到任何与类图有关的信息,试着修改.ecd和.ecore文件,几次均没有效果。想到昨天晚上做过备份(万幸),只好恢复到那时的类图重新画。画完了保存,试着关闭编辑器再打开,竟然又提示“Impossible to load the diagram xxx.ecd”!!

我快不行了,最后的办法,边画边保存边备份,想看看到底是什么操作引起的这个问题。终于找到根源了,原来是我定义了一个名为Operator的Enumeration,里面有“+”、“-”、“*”和“/”四个枚举项,其中的“/”号会引起.ecd文件失效,我猜想是没有转义的缘故,EclipseUML会把它认作分隔符。

最后建议大家一定要像对自己的眼睛一样爱护自己的模型,经常备份是非常必要的,现在免费的图形化编辑器普遍都不是很稳定。

用GMF生成简化的数据库设计器

Eclipse Graphical Modeling Framework (GMF)能 够帮助我们快速构造基于EMF和GEF的图形化编辑器,实际上对于不是很复杂的应用来说,开发人员并不需要了解EMF和GEF就可以使用GMF。这篇帖子 通过从零开始生成一个数据库设计器的全过程,演示了在使用GMF创建应用程序时,构造ecore模型、构造.gmfgraph文件、构造.gmftool 文件、构造.gmfmap文件和生成编辑器的这几个步骤。

一、开发环境

由于目前gmf还没有发布正式版,所以这篇帖子使用的是相对稳定的GMF 1.0M4版本,1.0正式版将在2006年7月初发布。gmf对eclipse平台和一些插件的要求比较高,所以你可能要对你的开发环境进行一些升级更新才能感受gmf带来的方便,具体要求是这样的:Eclipse 3.2M4EMF 2.2.0M4GEF 3.2M4UML2 2.0M2;此外还要下载一个名为ANTLR的包,解压后要把antlr.jar文件放在gmf插件目录的antlr/lib下,这个依赖只是暂时的,gmf正式版发布之前会去掉它。

二、构造ecore模型

因为只是为了演示gmf,这里构造的是一个非常简化的数据库设计器。用户通过设计器可以创建表格,为每个表格增加一些列,定义这些列的属性,以及在 表格之间建立外键关系。所以在ecore模型里应该有Database、Table和Column这几个类,此外还有一个FKRelation类代表表格 之间的连接,在Database类下有一个名为fkrelations的引用用来记录一个数据库设计中所有的这些连接。

创建名为com.my.dbdesigner的Empty EMF Project项目,有多种方式可以创建ecore文件,在gmf的example里有一个例子是ecore文件的图形编辑器,如果你安装了这个例子,可 以在项目的根目录下New->Other->Examples->Ecore Diagram创建名为dbdesigner的文件,这将生成dbdesigner.ecore和dbdesigner.ecore_diagram文 件。我在使用它编辑ecore文件时遇到了一些同步的问题,所以后来还是用eclipseUML来编辑的,不过这只是一个方法问题了。总之,我们这个数据 库设计器的ecore模型如图1所示(如果嫌麻烦,可以点这里下载现成的ecore文件)。

file
图1 数据库设计器的简化ecore模型图

三、构造.gmfgraph文件

主菜单New->Other->Example EMF Model Creation Wizards->GMFGraph Model创建名为dbdesigner.gmfgraph的文件,向导最后一步中Model Object选择为Canvas,然后按Finish按钮。在编辑器里,把Canvas命名为DBDesignerDiagram,这将成为数据库设计器 的画布。在Canvas下New Child创建一个名为Default的Figure Gallery,Figure Gallery的作用是容纳一些可供重用的Figure。在Figure Galley下创建一个名为BasicRectangle的Rectangle节点,在这个例子里大多数图形只用矩形就够了(除了连接线)。现在,在 Canvas下创建一个名为TableNode的Node节点,它代表数据库设计器里的表格,这个节点的Figure属性选择为刚才定义的 BasicRectangle,见图2,也就是指定在将来生成的数据库设计器里,表格显示为矩形。

file
图2 TableNode节点

可以想象,现在生成的数据库设计器里已经可以在画布上创建矩形的表格了,那么怎样实现在表格里创建列呢?这稍微麻烦一些,因为表格图形并不是全部面 积都用来放置列,而要留出顶部的一行用来显示表格名称,而且这些列也不是像表格在画布上那样随意放置,而是按由上到下的顺序排放的,这就需要在表格图形里 加一个隔间(Compartment),隔间的概念可以在图3中看到,它的作用就是放置子元素,但隔间本身一般不代表模型中的某个元素。

file
图3 红色虚线部分所示为表格图形里的隔间

创建一个与TableNode同级的名为ColumnCompartment的Compartment,意即用来放置列的隔间,在属性视图里把它的 Figure属性设置为BasicRectangle。再创建一个名为ColumnChild的同级Child节点,它的Figure属性同样为 BasicRectangle,这个ColumnChild就是作为子元素的列,如图4所示。

file
图4 ColumnChild节点

如前所述,数据库设计器里允许在不同表格之间创建连接线来表示外键关系,为简单起见,我们用连接线的标签定义作为外键的列名。因为现在我们的 Figure Gallery里只有矩形,所以先要给Figure Gallery增加一个Polyline Connection,命名为BasicPolyline。然后在Canvas下创建一个名为FKConnection的Connection,它的 Figure属性选为BasicPolyline,如图5所示。

file
图5 FKConnection节点

四、构造.gmftool文件

主菜单New->Other->Example EMF Model Creation Wizards->GMFTool Model创建名为dbdesigner.gmftool的文件,向导最后一步中Model Object选择为Tool Registry,然后按Finish按钮。在Tool Registry下创建Palette,在Palette下创建标题为DBDesigner的Tool Group,在这个Tool Group下为Table和Column分别创建一个Creation Tool,它们将成为数据库设计器中用来创建表格和列的那的两个按钮。同样在这个Tool Group下,为连线也创建一个Creation Tool,如图6所示。

file
图6 ForeignKey节点

五、构造.gmfmap文件

主菜单New->Other->Example EMF Model Creation Wizards->GMFMap Model创建名为dbdesigner.gmfmap的文件,向导最后一步中Model Object选择为Mapping,然后按Finish按钮。从主菜单GMF Editor里选择“Load Resource...”命令,在对话框里按Browse Workspace按钮,选中我们的dbdesigner.ecore、dbdesigner.gmfgraph和dbdesigner.gmftool 这三个文件,见图7,再按OK关闭对话框。

file
图7 为定义映射载入需要的资源

在编辑器的Mapping节点下创建一个Canvas Mapping,可以看到在属性视图里它的属性被分为三类,分别对应ecore模型、工具和图形这三个方面,对于Canvas Mapping,必须设置Domain Model、Element和Diagram Canvas这三个属性,值分别为EPackage dbdesigner、EClass Database和Canvas DBDesignerDiagram,它们都是下拉选项,所以很容易确定。

刚才的设置相当于告诉了GMF我们要把Database类映射为画布,现在要告诉GMF我们还要把Table类映射为画布上的矩形,所以要创建另一 个Mapping的子节点Node Mapping,它的属性见图8,注意可能要先选择了Element属性值后Edit Feature属性才可选。

file
图8 为数据库表格定义Node Mapping

还要告诉GMF表格里要能创建列,因此在Node Mapping下创建Compartment Mapping和Child Node Mapping各一个,前者只要将Compartment属性选择为在.gmfgraph里定义的ColumnCompartment即可;后者的属性如 图9所示,注意Compartment Mapping的Child Nodes属性与Child Node Mapping的Compartment属性是双向的,我们只用定义其中一个方向即可,另一个方向会自动填充。

file
图9 为列定义Child Node Mapping

最后要处理一下连接线,方法是在Mapping下创建一个Link Mapping,它的属性比较多,见图10。

file
图10 为外键关系定义Link Mapping

六、生成编辑器

该做的准备工作都已就绪,现在到了激动人心的最后一个步骤了。首先是要生成基本的EMF代码,包括核心模型代码和.Edit代码,因为gmf的图形 化编辑器依赖这两个部分,而EMF传统的Editor部分则并不需要。这个步骤在EMF的帖子里已经介绍过了,这里不再重复。接下来打开 dbdesigner.gmfmap文件,在编辑器里点右键,选择“Create generator model...”命令,在对话框里接受缺省的dbdesigner.gmfgen文件名,按OK确定后就会生成一个.gmfgen文件。打开这个文件, 还是在编辑器里点右键,选择“Create diagram code”命令,这样就会生成图形化编辑器的代码,这些代码放在名为com.my.dbdesigner.gmf.editor的项目中。

如果在执行上面步骤中出现了错误,就要检查那些模型文件是否正确,特别是.ecore文件的package中Ns Prefix和Ns URI这两个属性不应为空,如果错误信息为“java.lang.IllegalStateException: Can't find genFeature for feature 'XXX' in class XXX”则很可能是由于更改了.ecore文件后没有更新.genmodel文件。

运行这个生成的插件后,你就可以通过主菜单File->New->Example->DBDesigner Diagram创建数据库设计了,图11是它的工作界面。功能不错,但在我的机器上响应不是很快。

file
图11 数据库设计器的运行画面

代码下载

点此下载生成后的项目打包

参考资料

GMF Tutorial

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2006/02/12/329266.html

EMF介绍系列(七、.Edit初步)

EMF除了生成模型部分的接口和实现类(不妨称作“核心模型”)以外,还生成一个名称以.Edit结尾的项目,包含一些与核心模型和编辑器关系都十 分紧密的代码。这部分代码经过了精心设计,可重用的程度是相当的高。它们不仅在EMF生成的编辑器项目里大量被用到,我们自己在扩展编辑器的时候也应该充 分利用。

在线商店的例子里,com.my.shop.edit项目里包含一个ItemProviderAdapterFactory类和一组 ItemProviderAdapter的子类,后者是和核心模型的接口一一对应的,例如核心模型的Shop、Category和Product分别对应 ShopItemProvider、CategoryItemProvider和ProductItemProvider。这篇帖子主要介绍一下这些 ItemProvider,而关于ItemProviderAdapterFactory的内容将在以后的帖子里专门介绍,其实顾名思义, ItemProviderAdapterFactory的作用主要就是生成ItemProvider。事实上在构造EMF应用程序时,我们经常要修改 ItemProvider里的代码,而ItemProviderAdapterFactory则很少改动。

file
图1 EMF生成的.Edit项目

注意:.Edit项目里ItemProviderAdapter的子类名称里省略了Adapter这个单词,例如 CategoryItemProvider而非CategoryItemProviderAdapter,你心里应该清楚它是一个Adapter,因为它 确实实现了Adapter接口。EMF里另外专门有一个ItemProvider类是为非Adapter类型准备的,在这篇里说的 ItemProvider不是指它,而是指XXXItemProvider,也就是ItemProviderAdapter的子类。

注意:EMF里的Adapter接口和Eclipse Runtime的IAdaptable接口虽然名称相似,但并不是同一个概念(关于IAdaptable请参见前面的翻译帖子), EMF里的Adapter等同于监听器(Listener、Observer)的作用,它监听的对象是EMF的Notifier,在一个Notifier 上可以注册多个Adapter。另一方面,ItemProviderAdapterFactory则很像IAdaptable,它们都能够起到动态转换类 型的作用,只不过前者一般只用于Notifier到Adapter的转换,后者则没有什么限制,此外转换方法的名称也不同,前者是adapt(),后者为 getAdapter()。

从图1中不难看出,ItemProvider构成了.Edit项目的主要部分,这些ItemProvider具有以下几个作用。

一、实现了JFace中ContentProvider和LabelProvider的功能

JFace查看器(Viewer)是对swt中控件的一种包装,例如TableViewer是对Table的包装,TreeViewer是对Tree的包 装,等等,通过这种方式可以将控件与显示在控件中的数据在一定程度上分离,从而方便数据显示的更新。相当多的Eclipse应用程序都是通过JFace查 看器显示数据的,与查看器关联的ContentProvider和LabelProvider分别控制查看器中显示的哪些数据以及每条数据的显示方式。

以TreeViewer的ContentProvider为例,在JFace里应该实现ITreeContentProvider接口,这个接口定 义了getParent()、hasChildren()和getChildren()这三个方法;在EMF里有 ITreeItemContentProvider接口与之对应,这个接口同样具有这三个方法,.Edit部分的每个ItemProvider都实现了这 个接口,因为EMF已经完全知道我们的模型结构,所以这三个方法在ItemProviderAdapter类里已经实现好了。不过 ITreeItemContentProvider毕竟不能直接交给JFace的TreeViewer来使用,所以EMF提供了一个 AdapterFactoryContentProvider来做适配工作,你可以在编辑器的代码里看到如何使用它。

LabelProvider也是类似的,它主要控制显示的文字和图标。EMF生成的ItemProvider缺省没有实现 ITableItemLabelProvider,所以如果要使用TableViewer,要修改代码以实现 ITableItemLabelProvider接口和额外的方法,具体请参考在线商店例子中的ProductItemProvider。从 JFace的角度来说,ItemProvider相当于集成了各种查看器的ContentProvider和LabelProvider的代码,是一个通 用的“ContentLabelProvider”。因此利用它,开发人员在改变查看器的时候只需要修改很少的代码,而不像传统方式那样每换一个查看器还 要写新的ContentProvider和LabelProvider。

二、提供了关联对象的属性表

每个ItemProvider的getPropertyDescriptors()方法返回在属性视图里显示的属性列表,列表里的每个元素是一个 ItemPropertyDescriptor对象,它决定了每个属性的标签、描述、图标以及是否可编辑。EMF为生成的代码会帮我们把模型定义里的每个 属性都显示在属性列表里,如果希望隐藏某些属性,可以通过修改这个方法移除之。

以Product为例,ProductItemProvider的getPropertyDescriptors()方法里包含这样六条语句,分别代表产品名称、价格、描述、是否有货、评价以及颜色这六个属性,如果你想让颜色属性在属性列表里消失,只要删除最后一句即可。

addNamePropertyDescriptor(object);
addPricePropertyDescriptor(object);
addDescriptionPropertyDescriptor(object);
addAvaiablePropertyDescriptor(object);
addScorePropertyDescriptor(object);
addBackgroundPropertyDescriptor(object);

三、生成编辑模型的各种命令

在ItemProviderAdapter基类里有很多createXXXCommand()方法,如果你用过GEF应该对这些名称不陌生,因为在 EditPolicy里也有类似的方法。我们知道,为了实现Undo/Redo功能,对模型的每个改变都应该使用Command实现,然后把 Command保存在Command栈里,每个Command对象保存Undo/Redo自己的信息。ItemProviderAdapter相当于一个 生产这些Command的工厂,用户对模型编辑的请求都将通过它转换为对应的Command,例如用户在属性视图里修改了一个属性的值,当按下回车后,会 调用该对象关联的ItemProvider类的createSetCommand()方法生成一个SetCommand对象。

注意:在createCommand()方法里会调用getChildrenFeatures()方法,而在实现ContentProvider的getChildren()时也需要这个方法,因此这个方法的返回结果同时影响ItemProvider的这两项功能。

四、将模型的改变通知到负责显示模型的视图

在一个Eclipse应用程序里经常会有很多个查看器显示模型,无论用户怎样修改模型,要让这些查看器里显示的内容总是当前的模型,最好的办法是让查看器能够响应模型的变化。ItemProvider作为监听器可以很好的完成这个任务。

模型发生改变时,与被修改的对象相关联的ItemProvider的notifyChanged()方法被调用,事件立即被通知给 ItemProviderAdapterFactory,后者是整个模型的事件处理机构,所有的ItemProvider都是通过 ItemProviderAdapterFactory创建并注册为监听器的,因此ItemProviderAdapterFactory可以把事件通过 fireNotifyChanged()通知给所有这些监听器的notifyChanged()方法去消化。图2展示了这个通知过程,此图来自 《Eclipse Modeling Framework: A Developer's Guide》第3.2.4节中的图3.10。

file
图2 ItemProvider的通知流程

最后,ItemProvider还有一个collectNewChildDescriptors()方法,这个方法决定了在编辑器里,模型里对应的 那个对象可以创建哪些子元素。例如在线商店模型里,Category对象的子元素是Category和Product,那么用户在编辑器里右键点击一个 Category对象选择“New Child”时,就会出现“Category”和“Product”这两个选项。有些场合我们想隐藏其中一些选项时,就可以修改这里的代码。

参考资料

Eclipse Modeling Framework A Developers Guide,第3.2节、第10.1节。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2006/02/02/324909.html

EMF介绍系列(六、自定义命令)

EMF生成的应用程序里,用户的发出的每一条命令都是可以撤销(Undo)的,例如修改了产品的价格,按一下撤销按钮就能恢复原来的价格,当然还可 以通过重做(Redo)再回到新的价格。为了实现这个功能,应用程序里维护了一个用于存放命令的类似栈的数据结构(CommandStack),每一条执 行过的命令都被存放在那里,需要撤销时取出最近一条命令进行撤销。这个数据结构是由EditingDomain对象负责维护的, EditingDomain相当于编辑模型时的环境。

在EMF里命令框架实际上可以分为两大部分,一部分是与模型无关的通用命令,另一部分是.Edit命令,后者是建立在前者的基础之上的。EMF对模 型的任何修改都是通过命令完成的,例如当用户在属性视图里修改一个对象的属性时,会生成一个新的SetCommand实例,然后执行它的execute ()方法,这个方法里对模型进行修改(实际上是通过doExecute()方法),成功执行完成后这个命令被放入命令栈以便撤销时使用。

通用命令可以完全脱离EMF使用,也就是说,这个命令框架可以应用到任何需要命令框架的应用程序中,包括非EMF应用程序。它位于 org.eclipse.emf.common.command包里,其中Command接口定义了什么是“命令“,一个命令具有execute()、 undo()和redo()等方法,还有canExecute()和canUndo()方法用于判断命令是否可被执行或撤销(考虑到资源消耗,有些命令可 能设计为不可撤销更合理)。另一个重要的接口是前面提到过的CommandStack,它的作用是保存所有命令,可以通过 addCommandStackListener()方法注册监听器来观察CommandStack的状态变化。CompoundCommand接口可以 把多个命令按顺序包装成一个组合命令,它具有原子性,类似数据库里事务(Transaction)的概念,只有所有命令都可执行时这个组合命令才可执行, 撤销也是如此。

EMF在.Edit框架提供了针对EMF模型编辑所需要的一些命令(位于org.eclipse.emf.edit.command包),例如 SetCommand用于修改对象的属性,CreateChildCommand的作用是创建一个子元素,还有MoveCommand、 CopyCommand和CutToClipboardCommand等等。这些命令都实现Command接口,并且大部分继承自 AbstractOverrideableCommand这个抽象类,它给我们带来的影响是在Command接口里的方法名前面都加了一个do,比如 execute()变为doExecute()、canUndo()变为doCanUndo()等等,我们在扩展这些.Edit命令时要覆盖doXXX方 法。.Edit命令是通过反射的方式来修改模型的。

EMF提供的这些命令为我们完成基本的模型编辑功能,多数情况下直接使用它们就可以了,但有时通过自定义的命令可以实现一些特别的需求。举个例子来 说,在网上商店的例子里,假设要求产品的价格只精确到小数点后两位,那么我们要在用户输入新价格以后立刻对这个数值进行四舍五入处理,这个操作就可以利用 自定义命令完成。因为利用了.Edit提供的类,所以一般我们应该扩展.Edit命令,具体来说就是SetCommand。

首先通过继承SetCommand创建我们的SetPriceCommand,在这个方法里覆盖doExecute()方法,SetCommand 里有很多可供利用的环境变量,我们要用到的是owner和value这两项,前者是要修改的对象,在这里是产品对象,后者是属性的新值,在这里也就是新价 格。所以我们的SetPriceCommand可以像下面这样写(为了使代码最简,我们直接把EObject类型转换为Product类型,这样就不需要 用反射的方式了):

public class SetPriceCommand extends SetCommand {
    public SetPriceCommand(EditingDomain domain, EObject owner, EStructuralFeature feature, Object value) {
        super(domain, owner, feature, value);
    }
    public void doExecute() {
        Product product = (Product) owner;
        double newPrice = ((Double) value).doubleValue();
        newPrice = Math.max(0, newPrice);//New price value must >= 0
        newPrice=Math.round(newPrice*100)/100d;//Max fraction digits is 2    
        product.setPrice(newPrice);
    }
}

要让这个自定义命令生效,必须在ProductItemProvider里覆盖createSetCommand()方法,因为这个方法缺省是返回SetCommand的,我们要在这里做一个判断:如果修改的是价格属性,就返回我们自定义的这个命令,如下所示:

protected Command createSetCommand(EditingDomain domain, EObject owner, EStructuralFeature feature, Object value,
        int index) {
    if (feature == ShopPackage.eINSTANCE.getProduct_Price())
        return new SetPriceCommand(domain, owner, feature, value);
    return super.createSetCommand(domain, owner, feature, value, index);
}

这样当用户在属性页里改价格属性时,就会调用我们的SetPriceCommand了。顺便说一句,在GEF里 也有类似的EditDomain和Command,只是GEF里的Command一般通过EditPolicy的createXXXCommand()方 法来创建。因为GEF和EMF的两套Command机制没有实现统一的接口,所以结合GEF和EMF的时候常会遇到一些问题,需要额外的代码帮助解决,请 参考这两处讨论(讨论1,讨论2)。

最后要说一句,CreateChildCommand有点特殊,它是.Edit命令但不继承 AbstractOverrideableCommand,而且如果想在创建子元素时自动完成一些工作,不应该通过扩展这个类完成,而应该在 XXXItemProvider的collectNewChildDescriptors()方法里处理,这个方法决定每个对象可以创建哪些子元素,你可 以修改它的代码以对新建的元素做一些处理。

参考资料

Eclipse Modeling Framework A Developers Guide,第3.3节、第14.1节。

代码下载

点击下载源代码

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/12/31/309182.html

EMF介绍系列(五、定制应用程序界面)

第三篇帖子介绍了定制一个EMF应用程序的基本方法,这一篇让我们来看看怎样定制应用程序的使用界面。没有任何一个界面是万能的,所以定制工作不可避免,而大多数定制都是通过修改代码来实现的。在实际应用中,同一个需求可能有多种修改方式可以实现,我认为修改涉及的地方(类,方法)越少越有利于 发挥EMF的优势,因此我们应该对EMF生成的代码有一定的了解,这是发挥自己创造力的基础。

下面有几个常见的需求,通过对这些需求的实现,相信你会对EMF应用程序的开发过程有一个更具体的认识。

一、简化模型创建向导

EMF帮我们生成的模型创建向导(菜单File->New->Other->Shop Model)分为两步,第一步要用户输入文件名,对于商店的例子文件名是*.shop格式;第二步用户要选择以哪个对象作为根节点,同时要指定XML文件 的编码方式,商店例子里显然要以商店对象为根节点,所以其实第二步可以省去,以免造成使用者的困扰。

生成的向导类是ShopModelWizard,比起增加一个步骤来,去掉一个步骤要简单得多。首先找到addPages()方法,把最后四句关于initialObjectCreationPage最的语句都注释掉;

/**
 * The framework calls this to create the contents of the wizard.
 * <!-- begin-user-doc -->
 * <!-- end-user-doc -->
 * @generated NOT
 */
public void addPages() {
    ...
    //initialObjectCreationPage = new ShopModelWizardInitialObjectCreationPage("Whatever2");
    //initialObjectCreationPage.setTitle(ShopEditorPlugin.INSTANCE.getString("_UI_ShopModelWizard_label"));
    //initialObjectCreationPage.setDescription(ShopEditorPlugin.INSTANCE.getString("_UI_Wizard_initial_object_description"));
    //addPage(initialObjectCreationPage);
}

现在因为没有了这个向导页,原来由它提供的信息我们要改为在程序里提供,所以要修改另外两个方法:第一,createInitialModel()方法本来是建立一个用户选择的对象作为根节点的模型,我们把它改为直接建立一个Shop对象;

protected EObject createInitialModel() {
//    EClass eClass = (EClass)shopPackage.getEClassifier(initialObjectCreationPage.getInitialObjectName());
//    EObject rootObject = shopFactory.create(eClass);
    EObject rootObject=shopFactory.createShop();
    return rootObject;
}

第二,在performFinish()方法里设置文件编码的地方,改为使用UTF-8编码,当然你也可以规定使用其他编码,只是用户不能选择了:

public boolean performFinish() {
    ...
    options.put(XMLResource.OPTION_ENCODING, "UTF-8"/*initialObjectCreationPage.getEncoding()*/);
    ...
}

因此,这个类里我们总共修改了三个方法,一定记得要把每个方法前的@generated标记删除或修改。现在,用户只要简单的指定文件名后就可以Finish了,如图1所示。

file
图1 向导的最后一页

二、改造大纲视图的显示

对于EMF来说,在应用程序模型的根节点上还有两层,分别是Resource和ResourceSet,在商店的例子里,Category的父节点 是Shop,Shop的父节点是Resource(具体来说是XMLResource),Resource的父节点是ResourceSet,它们之间都 是多对一的关系。缺省情况下,大纲视图里显示的是完整的ResourceSet树(根节点不显示),显示出的最上层节点是“platform: /resource/Project3/My.shop”,它代表一个Resource,这个Resource(通过这个URI)指向保存着模型信息的 XML文件My.shop。对于使用者来说,这个节点显示在这里没有什么意义,用户看到的根节点应该是.shop文件里保存的Shop对象,见图2的对 比。

file
图2 在大纲视图里隐藏最上层节点

那么该修改哪些代码来实现这个需求呢?我们想到大纲视图里的内容是从ShopEditor的getAdapter()方法里得到的,通过查看 ShopEditor的getAdapter()方法发现名为getContentOutlinePage()的方法负责产生大纲模型,在这个方法里,变 量contentOutlineViewer是对大纲视图里的树控件的包装对象,它的输入(Input)是 editingDomain.getResourceSet(),我们要把它的输入改成ResourceSet的第一个Resource,修改后的代码如 下:

public IContentOutlinePage getContentOutlinePage() {
    ...
    //contentOutlineViewer.setInput(editingDomain.getResourceSet());
    contentOutlineViewer.setInput(editingDomain.getResourceSet().getResources().get(0));
    ...
}

你可能会问,那么从Resource怎样得到Shop对象呢?很简单,(Shop)yourResource.getContents().get (0)即可,有兴趣的话你可以试试把大纲视图的输入设为Shop对象会看到什么。最后说一次不要忘记修改@generated,以后不再提醒了。

三、移除编辑器里多余的Tab页

EMF生成的Editor为我们提供了六个Tab页,其主要目的是向我们演示如何以各种方式展示数据(例如在大纲视图里选择一个Category对 象,通过Parent页里可以很容易的看到前面说过的Category->Shop->Resource->ResourceSet关 系),在实际的应用里一般不会用到全部这些页,下面我们就只保留Table页而移除其他五页,利用大纲和Table页的组合,实现类似Windows资源 管理器的界面。

编辑器里的页面在createPages()方法里被添加,它虽然很长但EMF在这个方法里生成了不少注释,每段代码的作用都很明显,只要把我们不 需要的那五段注释掉即可。现在把程序运行起来,打开一个模型文件,稍微调整一下布局把大纲视图放在编辑器的旁边,如图3所示,有点资源管理器的样子了吧。

file
图3 和资源管理器类似的布局

但是很遗憾,现在的表格里只有两列,而且两列里显示的内容是相同的。按照资源管理器的设计,当用户在大纲视图里选择一个对象时,表格中应该显示该对象的子对象详细信息的列表,现在子对象列表已经有了(表格里每一行就是一个子对象),让我们做一些修改以显示详细信息。

首先增加一个表格列,还是在ShopEditor的createPages()方法里修改,搜索一下TableColumn很容易找到应该修改的位 置。新的三列标题分别为“Name”、“Children”和“Description”,其中Children列里显示子对象的数目。

public void createPages() {

    TableColumn objectColumn = new TableColumn(table, SWT.NONE);
    layout.addColumnData(new ColumnWeightData(3, 100, true));
    objectColumn.setText("Name");
    objectColumn.setResizable(true);

    TableColumn childrenColumn = new TableColumn(table, SWT.NONE);
    layout.addColumnData(new ColumnWeightData(2, 100, true));
    childrenColumn.setText("Children");
    childrenColumn.setResizable(true);

    TableColumn descColumn = new TableColumn(table, SWT.NONE);
    layout.addColumnData(new ColumnWeightData(2, 100, true));
    descColumn.setText("Description");
    descColumn.setResizable(true);
    ...
}

如果现在运行程序,看到的将是三列,但内容仍然是相同的。表格里显示的内容是由生成的XXXItemProvider类决定的,例如对于一个 Category对象在表格或树控件里怎样展示是由CategoryItemProvider来负责,你可以把它看作是JFace里的 ContentProvider加上LabelProvider,这些XXXItemProvider都被放在.edit项目里了。EMF生成的 CategoryItemProvider没有实现ITableItemLabelProvider接口,所以缺省情况下不能支持表格的展示(能够显示, 但每列的内容相同),所以我们要对代码进行一些修改,在CategoryItemProvider实现的接口列表里增加 ITableItemLabelProvider,并实现它的两个方法,修改后的代码如下:

public class CategoryItemProvider
    extends ItemProviderAdapter
    implements    
        IEditingDomainItemProvider,    
        IStructuredItemContentProvider,    
        ITreeItemContentProvider,    
        IItemLabelProvider,    
        IItemPropertySource,
        ITableItemLabelProvider{

    public Object getColumnImage(Object object, int columnIndex) {
        return null;
    }

    public String getColumnText(Object object, int columnIndex) {
        Category category=(Category)object;
        switch (columnIndex) {
        case 0:
            return category.getName();
        case 1:
            return category.getChildren().size()+"";
        case 2:
            return "";//Categories don't own descriptions
        default:
            return "";
        }
    }
    ...
}

现在只差一步就完成了,如果你注意看过ShopEditor的createPages()方法里定义TableViewer的代码,会发现这个 TableViewer的ContentProvider和LabelProvider都是一个 AdapterFactoryContentProvider对象,这个对象会把TableViewer对getText()、getElements ()的请求转发到XXXItemProvider上;转发之前它要得到这个XXXItemProvider,这是通过 ShopItemProviderAdapterFactory的adapt()方法实现的,而 ShopItemProviderAdapterFactory维护了一个supportTypes列表,只有注册到这个列表中的类型才能被adapt。 这里出现了不少新内容,可能不那么容易理解,没有关系,因为在以后的帖子里会专门介绍到它们,现在只要记住需要把我们新实现的接口类型注册到 ShopItemProviderAdapterFactory的supportTypes里即可,具体的方法是修改它的构造方法,如下所示:

public ShopItemProviderAdapterFactory() {
    supportedTypes.add(IEditingDomainItemProvider.class);
    supportedTypes.add(IStructuredItemContentProvider.class);
    supportedTypes.add(ITreeItemContentProvider.class);
    supportedTypes.add(IItemLabelProvider.class);
    supportedTypes.add(IItemPropertySource.class);    
    supportedTypes.add(ITableItemLabelProvider.class);//Added to support table
}

现在,表格里显示的Category对象已经按我们的要求列出其他信息了,如图4所示,Description列是空白因为Category没有这 个属性。我们还应该修改ProductItemProvider以展示产品的详细信息,方法和修改Category是类似的,而且增加 supportTypes的步骤不须要重复做,所以更加简单了,不妨就留作练习。

file
图4 经过定制的表格

经过上面的这些定制,我们就实现了应用程序从EMF缺省界面到资源管理器风格界面的转换,虽然文字比较多,但掌握以后这个过程是相当快速的,而且其他的定制也是同样的思路。

代码下载

点此下载代码

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/12/23/303508.html

在Debian里不重启切换两块声卡

在电磁干扰严重的狭小空间里,笔记本的内置声卡表现一般比较差,至少我的n600c底噪就很明显。要获得更好的效果或者需要数字输出时只能借助外置声卡了,包括pcmcia接口和usb接口两种,前者如创新的Audigy2 ZS Notebook,当然很贵了;后者如我的玲珑II,性价比还不错。

在Linux里不像Windows那样可以简单的在控制面板里选择使用哪个声卡作为首选设备,经过一段时间的研究,我找到一个还可以接受的办法,至少不需要重启系统。首先 将以下的文本保存为/etc/modutils/sound_cards,然后sudo update-modules,这个命令会处理/etc/modutils里的所有文件,合成/etc/modules.conf文件。

alias snd-card-0 snd-maestro
alias snd-card-1 snd-usb-audio
options snd-maestro index=0
options snd-usb-audio index=1
options snd-usb-audio enable="1"

要切换到usb声卡时:

sudo /etc/init.d/alsa force-unload
sudo modprobe snd-usb-audio

类似的,要切换到内置声卡(使用maestro3芯片)时:

/etc/init.d/alsa force-unload
modprobe snd-maestro3

注意force-unload的时候会先自动杀掉所有使用声卡的进程,如firefox、音量控制和bmp等等,如果没有这样的进程在运行,可以直接unload,或者rmmod snd-maestro3这样卸载模块。

另外一种方法是在应用程序里切换,例如bmp,在首选项->插件->输出里把输出插件选为ALSA,然后在ALSA的配置里选择希望使用的音频设备和混音设备即可。其他应用程序类似,其实如果只关心某个应用程序,这样更省事。

最后,Gentoo用户请参考这个Tip,感谢acura提供。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/12/16/298333.html

Windows分区转换为Debian分区

我的机器上是Debian和win2000双系统,今天把原来window的e盘转换成linux的分区了(因为PQMagic划分空间有一定的危险性),相当于给linux增加了10G容量,这下宽裕多了。

我是把/home放在了新分区上,因为用户要下载一些比较大的文件都放在这里面,方法如下:

1、在windows里把e盘上有用的东西移到其它盘上

2、重新启动进入Debian的recovery mode,即single模式

3、# mv /home /home2

4、# mkdir /home

5、# mkfs.ext3 /dev/hda6

6、# mount /dev/hda6 /home

7、# cp -ar /home2/* /home

最后再重启机器即可。

EMF不能载入schema问题的解决

在EMF里可以用XML Schema定义模型,然后转换为genmodel模型,但我这里在生成genmodel向导的第四步按Load后会产生一个NullPointerException如下,导致无法继续:

java.lang.NullPointerException
    at org.apache.crimson.tree.ElementNode2.getAttributeNodeNS(ElementNode2.java:432)
    at org.apache.crimson.tree.ElementNode2.hasAttributeNS(ElementNode2.java:388)
    at org.eclipse.xsd.ecore.XSDEcoreBuilder.getEcoreAttribute(XSDEcoreBuilder.java:2336)
    at org.eclipse.xsd.ecore.XSDEcoreBuilder.getEcoreAttribute(XSDEcoreBuilder.java:2329)
    at org.eclipse.xsd.ecore.XSDEcoreBuilder.getEStructuralFeature(XSDEcoreBuilder.java:2004)
    at org.eclipse.xsd.ecore.XSDEcoreBuilder.generate(XSDEcoreBuilder.java:2117)
    at org.eclipse.xsd.ecore.XSDEcoreBuilder.generate(XSDEcoreBuilder.java:2067)
    at org.eclipse.xsd.ecore.importer.XSDImporter.doComputeEPackages(XSDImporter.java:123)
    at org.eclipse.emf.importer.ModelImporter.computeEPackages(ModelImporter.java:664)
    at org.eclipse.emf.importer.ui.contribution.base.ModelDetailPage.refreshModel(ModelDetailPage.java:488)
    at org.eclipse.emf.importer.ui.contribution.base.ModelDetailPage$2.execute(ModelDetailPage.java:434)
    at org.eclipse.ui.actions.WorkspaceModifyOperation$1.run(WorkspaceModifyOperation.java:98)
    at org.eclipse.core.internal.resources.Workspace.run(Workspace.java:1719)
    at org.eclipse.ui.actions.WorkspaceModifyOperation.run(WorkspaceModifyOperation.java:110)
    at org.eclipse.jface.operation.ModalContext.runInCurrentThread(ModalContext.java:346)
    at org.eclipse.jface.operation.ModalContext.run(ModalContext.java:291)
    at org.eclipse.jface.wizard.WizardDialog.run(WizardDialog.java:830)
    at org.eclipse.emf.importer.ui.contribution.base.ModelDetailPage.refreshModel(ModelDetailPage.java:457)
    at org.eclipse.emf.importer.ui.contribution.base.ModelDetailPage.doHandleEvent(ModelDetailPage.java:241)
    at org.eclipse.xsd.ecore.importer.ui.XSDDetailPage.doHandleEvent(XSDDetailPage.java:91)
    at org.eclipse.emf.importer.ui.contribution.base.ModelImporterPage.handleEvent(ModelImporterPage.java:106)
    at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:66)
    at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1021)
    at org.eclipse.swt.widgets.Display.runDeferredEvents(Display.java:2867)
    at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:2572)
    at org.eclipse.jface.window.Window.runEventLoop(Window.java:809)
    at org.eclipse.jface.window.Window.open(Window.java:787)
    at org.eclipse.ui.actions.NewWizardAction.run(NewWizardAction.java:181)
    at org.eclipse.ui.actions.NewWizardDropDownAction.run(NewWizardDropDownAction.java:174)
    at org.eclipse.jface.action.Action.runWithEvent(Action.java:996)
    at org.eclipse.jface.action.ActionContributionItem.handleWidgetSelection(ActionContributionItem.java:538)
    at org.eclipse.jface.action.ActionContributionItem.access$2(ActionContributionItem.java:488)
    at org.eclipse.jface.action.ActionContributionItem$6.handleEvent(ActionContributionItem.java:441)
    at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:66)
    at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1021)
    at org.eclipse.swt.widgets.Display.runDeferredEvents(Display.java:2867)
    at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:2572)
    at org.eclipse.ui.internal.Workbench.runEventLoop(Workbench.java:1699)
    at org.eclipse.ui.internal.Workbench.runUI(Workbench.java:1663)
    at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:367)
    at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:143)
    at org.eclipse.ui.internal.ide.IDEApplication.run(IDEApplication.java:103)
    at org.eclipse.core.internal.runtime.PlatformActivator$1.run(PlatformActivator.java:226)
    at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:376)
    at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:163)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:324)
    at org.eclipse.core.launcher.Main.invokeFramework(Main.java:334)
    at org.eclipse.core.launcher.Main.basicRun(Main.java:278)
    at org.eclipse.core.launcher.Main.run(Main.java:973)
    at org.eclipse.core.launcher.Main.main(Main.java:948) 

这是由Sun JDK1.4的一个bug造成的,解决方法是通过参数指定eclipse使用第三方的XML Parser,例如Xerces-J。首先下载Xerces-J并解压缩,然后在eclipse的启动参数后加上“-vmargs -Djava.endorsed.dirs=”即可。

参考资料

EMF关于这个问题的说明:XSD Model Loading: Crimson DOM Bug, Workaround & Download

使用工作集(Working Set)整理项目

Eclipse鼓励将不同的功能模块划分为独立的项目存在,这样不但结构清晰,组织起来还非常灵活,因为我们可以用feature对这些项目进行不同的组合,输出后得到具有不同功能的产品。

不过这样一来Package Explorer里的项目会以更快的速度增加,当你面对几十上百个项目时,工作效率必然大打折扣。幸好Eclipse提供了工作集(Working Set)的功能,它可以用来划分这些项目。

在Package Explorer视图的下拉菜单里选择Show->Working Sets,然后还是在它的菜单里选择Select Working Sets,在对话框里定义你的工作集,每个工作集要对应至少一个项目,按OK后你的项目就分好类了,没有归类的项目都放在名为Other Projects的工作集里。

赶快试试吧!

file

补充(2006-4-11):另一个整理项目的办法是使用多个Workspace,似乎也不错,见Tips for using Eclipse effectively

EMF介绍系列(四、枚举类型、自定义类型和Map)

除了普通的类(接口)以外,在类图里可以定义一些特殊的元素,比较常见的是枚举类型、自定义类型,它们对于一个完整可用的模型也是必不可 少的,这篇帖子主要介绍EMF里它们的使用方法。另外,由于EMF对Map的支持比较特别,所以在这里也简要介绍一下Map类型的定义方法。

枚举类型

继续前面帖子的 例子,现在要为产品增加一个评分属性,评分值可以是好中差之一,像这样属性值只能是有限几个值之一的属性就应该定义为枚举类型 (Enumeration)。在类图里首先创建一个名为Score的枚举类型,然后为它增加三个可选值,每个值对应一个唯一的整数值作为标识;然后给 Product类型添加一个名为score的属性,这时的类型列表里已经比原来多了Score类型,我们就选择它作为score属性的类型。重新生成一遍 代码,你会发现增加了Score类(不是接口),运行新生成的编辑器会看到,产品对象的属性里增加了评级,见图1。

file
图1 枚举类型的属性以下拉列表方式编辑

自定义类型

EMF虽然对大多数java类型做了包装,但是有些情况需要我们使用没有被包含的类型,例如在设计图形化的编辑器(例如类图编辑器)时,图形节点一 般允许选择背景颜色,这就需要一个org.eclipse.swt.graphics.RGB类型的成员变量,而RGB类是SWT提供的类,所以不能通过 创建一个同名类的方式实现,这时就要使用自定义类型。类似的道理,在必须利用遗产项目(Legacy)代码的时候,自定义类型也是必须的。

现在为Product节点增加这样一个名为background的成员变量,步骤如下:首先在类图上新建一个名为RGB的自定义类型(data- type,见图2),将它的Instance Class属性设置为org.eclipse.swt.graphics.RGB;然后给Product类添加一个成员变量background,类型选 择为刚建立的RGB;现在重新生成一遍代码,可以看到Product.java里已经多了这个成员变量,其类型为 org.eclipse.swt.graphics.RGB(因为org.eclipse.swt.graphics.RGB是属于 org.eclipse.swt这个插件的,所以要为com.my.shop项目增加对org.eclipse.swt的依赖才能正确编译)。

file
图2 新建自定义类型

不过到这里还没有完成全部工作。以为EMF对这个RGB类一无所知,所以它不可能为我们实现RGB对象的持久化,即将RGB类型的数据保存到(缺省 格式的)xml文件中。要解决这个问题得在生成的ShopFactoryImpl里做一些定制,具体来说是修改createRGBFromString ()和convertRGBToString()这两个方法,很明显它们的作用分别是从字符串创建RGB对象以及将RGB对象转换成字符串,我们把它们改为下面这样:

/**
 * <!-- begin-user-doc -->
 * <!-- end-user-doc -->
 * @generated NOT
 */
public RGB createRGBFromString(EDataType eDataType, String initialValue) {
    String[] values = initialValue.split(",");
    int red=Integer.parseInt(values[0]);
    int green=Integer.parseInt(values[1]);
    int blue=Integer.parseInt(values[2]);
    RGB rgb = new RGB(red,green,blue);
    return rgb;
}

/**
 * <!-- begin-user-doc -->
 * <!-- end-user-doc -->
 * @generated NOT
 */
public String convertRGBToString(EDataType eDataType, Object instanceValue) {
    RGB rgb = (RGB) instanceValue;
    return rgb.red + "," + rgb.green + "," + rgb.blue;
}

这些代码可以把RGB对象转换为形如“255,90,150”的格式,EMF在持久化时遇到RGB类型的对象就按这样的格式写到文件里。

使用EMap

在类图里可以指定各种类型的成员变量,但使用Map类型则有点特别,直接定义一个EMap类型的成员变量是不可以的,因为EMap并不是继承自 java.util.Map而是EList,也就是说EMF使用EList来模拟实现Map的功能(为什么要这样实现我还没弄清楚)。要实现一个EMap 类型的成员变量,在类图里要参考下面的方式进行定义。

现在我们要为产品类增加一个EMap类型的属性,用这个属性来记录产品每月的销售数量,它维护一个日期字符串(如"2005-09")到当月销售数 量的映射。首先,定义一个名为StringToIntegerMapEntry的新类,这个类将作为Map里的项(Entry);为这个类增加两个属性: 字符串类型的key和整数类型(Integer或int均可)的value;在属性视图里把这个类的Instance Class Name属性设置为java.util.Map$Entry;这样MapEntry类就定义好了,在需要EMap类型变量的类里引用它就可以了,注 意对它的引用必须是包含关系(即“组合”而非“聚合”,类图上连接线用黑色菱形修饰),并且是一对多的。

重新生成一遍代码,在Product接口里salesMap成员变量是这样定义的(因为EMF会检测到我们正在定义一个Map):

/**
 * Returns the value of the '<em><b>Sales Map</b></em>' map.
 * The key is of type {@link java.lang.String},
 * and the value is of type {@link java.lang.Integer},
 * <!-- begin-user-doc -->
 * <p>
 * If the meaning of the '<em>Sales Map</em>' containment reference isn't clear,
 * there really should be more of a description here
 * </p>
 * <!-- end-user-doc -->
 * @return the value of the '<em>Sales Map</em>' map.
 * @see com.my.shop.ShopPackage#getProduct_SalesMap()
 * @model mapType="com.my.shop.StringToIntegerMapEntry" keyType="java.lang.String" valueType="java.lang.Integer"
 * @generated
 */
EMap getSalesMap();

这样,在生成的编辑器里可以对每个产品增加新的项,每项包含key和value两个值;在程序里,则应该使用 Product#getSalesMap().put()方法向这个Map里增加数据,因为我们定义的Map项是字符串到整数类型的,所以put()的时 候value参数必须是整数对象(java.lang.Integer),否则会抛出ClassCastException。请记住,不要直接指定 EMap类型,包含MapEntry即可,如果key或value不是普通类型,则使用名为key或value的引用。

file
图3 编辑器里的EMap类型属性

file
图4 修改后的模型

代码下载

经过上面几处改动,现在我们的商店模型如下图所示,点此下载项目打包

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