关于实现StarGen的思考记录

毕业设计的内容是Web应用程序的代码生成器,因为接触emf有一段时间了,我觉得用emf完全可以很方便的实现这个程序。这是更全面了解emf特别是codegen部分的一个好机会。这个帖子将记录这个过程的点滴,所以会比较琐碎,也许这些文字能被用在毕业论文里(不希望论文里都是google来的东西)。我发现cnblogs的帖子被修改后在rss里会重新出现,所以订阅我的blog的读者可能要经常被“骚扰”了:P

为了方便起见,我给这个代码生成器起了一个名字叫做“StarGen”,完全是一个代号,不包含任何意义。而且很幸运,这个名字在sourceforge.net上没有被注册过,等有了时间我要申请一下,所以程序里的包名都先以net.sf.stargen开头了。(请不要和我抢注这个名字啊,否则光refactor的工作就不少了,也许我就不开源了。)

闲话少说,以下是在实现StarGen过程中对一些问题的思考:

1、(3-24)首先我从使用者的角度审视一下这个应用程序完成后应该具有的功能,StarGen的使用者其实就是Web应用的开发者,他通过StarGen可以定义一些元素,这些元素将被转换为代码。粗略想了一下,可以定义的元素包括Class、Attribute、Operation、Reference等等,很像是一张类图。每个类将被转换为Web应用的一个模块,包含增、删、改、查询、列表、查看这些功能,这些功能分别由一个或多个jsp页面和Struts的Action(目前考虑生成基于Struts和Hibernate的应用,因为实验室一直使用它们)。

那么是否有了类图就够了呢,这样只能实现简单的数据操作,基本没有什么业务逻辑在里面。为了能让生成的Web应用具有逻辑性,我初步为StarGen的输入除类图以外增加了一个流程图,流程图里定义的流程会在生成的代码里得到体现。这个图和工作流里的那种图还是要有所区别的,我以前开发过工作流程序,在流程引擎里都要处理条件、会签、回退等情况,实际上是非常复杂的。因此流程这部分还需要花时间考虑一下怎样简化比较合适。

file
图1 初步的StarGen使用步骤(六个步骤)

2、(3-24)既然明确了StarGen的输入,一个是类图,另一个是暂时称作流程图的东西,那么使用者应该怎样提供这些输入呢?首先看一下类图方面,这里使用者至少要能够定义出应用里应该有哪些对象类型,它们的成员变量、方法等等,还有类与类之间的联系。如果说他(使用者)定义的是一个模型,那么我首先要有一个元模型,元模型好比模型的语言,在元模型里我要定义“类”、“成员变量”、“方法”等等这些概念。按照这个想法,我想到平时使用emf建模时用的“语言”不就是ecore吗,没错,ecore是一个元模型,和uml一样,都可以用来定义模型。那么我可不可以就让使用者直接用ecore建模呢,因为使用ecore建模的工具已经有不少了,最起码也有随emf一起来的“Sample Ecore Model Editor”,想图形化可以用EclipseUML或者gmf的ecore editor都行,这样我可以节约很多工作量。

3、(3-24)图1中步骤3和步骤5是StarGen的主要工作所在。就像emf里有genmodel一样,StarGen也需要有自己的genmodel,不妨暂时称作“StarModel”吧。使用者输入的两个model被合并为一个starmodel,在starmodel里包含了它们的全部信息,此外还有一些如包名、类名这样的信息,这些信息和Web应用的业务逻辑没有什么关系。生成starmodel的部分我将主要org.eclipse.emf.importer这个项目,它可以从包括ecore模型在内的几种模型里生成genmodel。

另一项重要工作就是从starmodel生成可以执行的代码,这里我将参考以org.eclipse.emf.codegen开头的四个项目,特别是Generator类,我会从它“开刀”看看有什么值得利用。另外org.eclipse.uml2.codegen开头的几个项目也值得一看,uml2的代码生成借用了很多emf的东西,例如uml2的genmodel就完全是从emf的genmodel继承来的,只是增加了一些新的元素,我可能也会采取这种方式而非从头开始定义一个和emf的genmodel类似的starmodel。BTW,我觉得这不是走捷径,这叫“Leverage”!

4、(3-27)在emf里,从ecore生成genmodel的主要工作是在EcoreImporter中完成的,这个类继承ModelImporter类。在模型导入向导ModelImporterWizard的performFinish()方法里会调用prepareGenModelAndEPackages()和saveGenModelAndEPackages()方法,前者负责构造genmodel实例,后者负责保存这个实例。

ModelImporter的prepareGenModelAndEPackages()方法的主要过程是这样的:计算这个genmodel引用到的其他genmodel,将这些genmodel连同自己都加到同一个Resource内,调用genmodel的initialize()方法,这个方法会导致genmodel里的GenPackage的initialize()方法被调用,再导致GenClassifier的initialize()方法被调用,如此类推,直到整个genmodel构造完毕。然后,通过调用traverseGenPackages()和adjustGenModel()对得到的genmodel做一些调整,主要是一些属性的赋值,traverseGenPackages()的作用还不太明白。最后,调用reconcile()方法将原来的genmodel里被修改过的值应用到新生成的genmodel上,否则用户在Genmodel Editor里所做的修改在reload一次后就丢失了。

ModelImporter的saveGenModelAndEPackages()则十分直白,它会判断是否需要新建project,以及根据需要reference的其他genmodel决定在project里增加对哪些project的依赖(一般情况下好像都是在已有project里建立genmodel的),然后对需要保存的resource调用Resource#save()即可。

对于StarGen来说,保存的那部分应该是一样的,主要的修改会在生成genmodel的prepareGenModelAndEPackages()方法里,因为StarGen的genmodel(StarModel)在emf的genmodel基础上增加了Process等类,所以很可能需要在StarPackage的initialize()里对它们进行处理,这通过覆盖GenPackage()的initialize()方法就可以实现。

5、(3-29)EMF Model的新建向导(File->New->Others->Eclipse Modeling Framework->EMF Model)对应的类是EMFModelWizard,整个向导由两部分构成,第一部分是前两页,第一页叫NewGenModelFileCreationPage,在这里用户指定.genmodel文件的名字和所在项目,第二页是一个ModelConverterDescriptorSelectionPage的子类,它搜索注册到Eclipse上的org.eclipse.emf.importer.modelImporterDescriptors扩展点提供一个ModelImporter列表,例如EcoreImporter、RoseImporter等等。注意,在ModelConverterDescriptorSelectionPage的getNextPage()方法里会决定使用哪个Wizard作为向导第二部分,同时会调用adjustModelConverterWizard()方法做一些赋值工作。

向导的第二部分就由Importer决定,对于Ecore的情况对应EcoreImporterWizard,它是ModelImporterWizard的子类,包含两个向导页ModelImporterDetailPage和ModelImporterPackagePage,分别让用户选择一个.ecore文件和选择要生成或引用的包。ModelImporterWizard的子类都要实现createModelConverter()方法,返回一个ModelConverter类的实例,例如return new EcoreImporter(),EcoreImporter是ModelImporter的子类,ModelImporter则是ModelConverter的子类。

向导负责收集必要的信息,具体的生成.genmodel文件的工作在ModelImporter里完成,主要的步骤就是前面说过的prepareGenModelAndEPackages()和saveGenModelAndEPackages()方法。

6、(3-30)为了实现StarGen区别于emf的功能(流程等等),我“继承”emf的genmodel模型(genmodel.ecore)构造了StarGen的模型(stargen.ecore),这个过程参考了uml2对codegen的实现。StarGen的genmodel如下图所示:

需要注意的是,图中很多元素都要继承两个类,一个是emf中的GenXXX,另一个可能是StarBase,StarBase是GenBase的子类。这时要把GenXXX放在继承列表的第一位,否则生成的代码会缺少一些方法如getName()和reconcil()而无法编译通过。另外一点要注意的就是,在genmodel.genmodel(由genmodel.ecore生成,位于org.eclipse.emf.codegen.ecore/model)中,GenModel的Creation Commands属性是false,继承的stargen.genmodel里也要做同样的设置,否则在生成的editor里会出现MissingResourceException。不过,这样设置以后editor里就没有New Child/Sibling菜单选项了。

7、(3-31)初步使用自己的Template生成代码。实际上emf从大约2.2.0开始支持了Dynamic Template,只要把genmodel的“Dynamic Templates”属性设置为true,并在适当的位置放置另外的Template即可按这个Template生成代码。不过,为了更大的灵活性以及我暂时没有想到的情况,我没有使用这种机制。在emf的GenClassImpl这个类里有很多getXXXEmitter()方法,它的返回值决定了genmodel里各个元素使用哪个模板,例如GenClassImpl的getFactoryClassEmitter()是这样写的:

 public JETEmitter getFactoryClassEmitter()
  {
    if (factoryClassEmitter == null)
    {
      factoryClassEmitter = createJETEmitter(factoryClassTemplateName);
      setMethod(factoryClassEmitter, "org.eclipse.emf.codegen.ecore.templates.model.FactoryClass");
    }
    return factoryClassEmitter;
  } 

可以看到emf生成Factory类是通过org.eclipse.emf.codegen.ecore.templates.model.FactoryClass这个(经过编译的)模板完成的,如果想使用自己的模板,只要在StarModelImpl类里覆盖这个方法(StarModelImpl是GenModelImpl的子类,参考前面stargen的模型图),把setMethod()的参数改为"net.sf.stargen.templates.FactoryClass"即可。

模板是一些.javajet文件,在一个具有jet nature的project里,你写的所有.XXXjet文件在保存时都会被编译为一个.java类,该类的generate()方法生成.XXX文件的内容。emf的那些.javajet文件的内容还是比较复杂的,需要花些时间看看怎么利用,其中最主要是Class.javajet,因为StarGen不会去生成.edit和.editor的代码。再接下来就是要构造与Web应用有关的Template了,例如Action.javajet或是Mapping.hbm.xmljet等等,工作量也许很大,但一些问题应该可以先简化处理。

8、(4-5)现在StarGen可以从.ecore文件生成.starmodel文件了,并且可以生成一个Factory和n个bean-like类(n=ecore里EClass的数量),我对emf的Class.javajet做了巨大的简化,代码从1800多行减为不到100行,汗,真难想象emf的Class.javajet当时是怎样写出来的。下一步的工作是生成struts的配置文件,这里有一个之前没有考虑到的困难,那就是在xml文件里怎样控制jmerge的行为,应该与在java类里有所不同,还是要学习一下。

9、(4-10)生成.jsp文件的时候遇到一些困难。一是jsp与jet的控制标签都是"<% %>",必须在jet模板里指定使用不同的标签,例如startTag="<$" endTag="$>";二是GenBaseImpl类提供了多个generate()方法,用于生成.java文件的那个generate()方法不能用于生成.jsp文件,因为输出的文件名的扩展名在方法里是写死为".java"的,要使用具有五个参数的那个generate()方法,并自己计算输出文件的全名。

10、(4-11)StarGen生成如下代码:

模型元素 生成的代码
StarModel web.xml, hibernate.properties
StarPackage Factory, struts-config.xml, Resource.properties(4-17), IndexAction(4-15), index.jsp(4-15)
StarClass Bean-like Class/Enum(4-18), Helper(4-14新增), XXXAction, XXXForm, JSPs(List, Create, Edit), struts-config-xxx.xml, ApplicationResources.properties, xxx.hbm.xml, validation.xml(4-18)

接下来要花几天的时间对模板内容进行调整,这样就可以初步得到可以运行的生成结果。(PS.昨天看到一个很像的代码生成工具Modelstry,也是生成基于struts的web应用,还利用了spring来管理hibernate。但它好像不是用jet生成的代码,不知道merge方面是否有问题。刚下载了试用版本,有时间研究一下,取长补短。)

11、(4-13)生成了可以列表和添加新记录的web应用程序。第三方类库,包括Struts和Hibernate用到的那些jar包和tld文件,则需要用户手动添加到项目。GenFeature有一个方法是getGetAccessor(),得到的是bean里的getter方法名字,但却没有getSetAccessor(),为什么呢,因为getter不一定是以"get"开头的,还有"is"开头的情况,所以在getGetAccessor()里做了处理,而需要setXXX方法的地方直接写set<%=genFeature.getAccessorName()%>即可。

12、(4-14)实现了简单类的编辑和删除,jsp和action。为每个ecore类新生成一个Helper类,功能类似emf生成的ItemProvider,目前主要提供getText()方法,用户可以修改这个方法显示需要的内容。缺省的这个类返回<%=genClass.getSafeUncapName()%>.<%=genClass.getLabelFeature().getGetAccessor()%>()。

13、测试用的ecore模型,十分简单。

file

14、(4-17)处理了jsp页面里用checkbox显示GenFeature#isBooleanType()返回true的类型,在StarFeature里增加了password布尔变量,此值为true的用代替。非String类型的GenFeature,如int型和boolean型,不需特殊处理即可正常使用,原因是在ActionForm里已经是这种类型,本来担心的在Action的postCreate()/postEdit()方法里需要parse的问题并不存在。

15、(4-18)对特殊的GenFeature(即不是普通String类型的属性)做如下处理:

GenFeature情况 处理方法
transient 在hibernate映射文件里去掉对这类属性的声明
volatile 待处理
boolean 在create.jsp/edit.jsp页面中以checkbox展示
contains 在container的编辑和查看页面中增加一个创建child的link
ListType 在create.jsp/edit.jsp页面中以multiple的select展示,在hibernate映射文件里使用代替
reference   在hibernate映射文件里使用代替
MapType 这一版本暂时不作处理
Bidirectional 在hibernate映射文件里设置,在一方的column名称与一方的column保持一致
EnumType 假设GenFeature名为location,在模型类里增加一个int型的locationValue属性,mapping里只对这个属性持久化,而getEnum()里通过locationValue计算得到Enum类型返回值。生成继承AbstractEnumerator的类,生成一个Resource文件(为了对Enumerator的Literals实现国际化),在具有Enum类型成员变量的GenClass生成的struts-config-xxx.xml里要引用这个Resource文件。在Create.jsp/Edit.jsp里用下拉框展示,View.jsp/List.jsp里显示Resource里对应的文字内容(而非直接显示Literal),ActionForm里与模型类相对应,要有locationValue属性。(4-19)
notNull 在hibernate映射文件里设置 ,在validation.xml文件里增加required约束
其他非String类型 在validation.xml文件里增加相应约束

16、(4-18)GenFeature常用的方法:getTypeGenClass()得到与GenFeature关联的“对面”的GenClass;

17、(4-19)让生成的java代码能够拥有适当的import声明的方法:在jet文件前部增加类似下面的语句:

<%genModel.addImport("java.util.Arrays");%>
<%genModel.addImport("java.util.List");%>
<%genModel.addImport("java.util.Collections");%>
<%genModel.markImportLocation(stringBuffer, genPackage);%>

再在jet文件最后一行增加<%genModel.emitSortedImports();%>这句即可。

 18、(4-19)实现了对Enum类型GenFeature的处理,但生成的代码并不完美,在JSP页面里有类似下面的语句(因为用struts的标签无法很方便的表示,所以用

<html:select property="locationValue">
    <%for (Iterator iter = Location.VALUES.iterator(); iter.hasNext();) {
        Location location = (Location) iter.next();%>
    <option value="<%=location.getValue()%>"
        <bean:message key="<%=location.getName()%>" bundle="location"/>
    </option>
    <%}%>
</html:select>

19、(4-20)生成的列表支持排序了。下面要处理的是分页问题,会稍微麻烦一些。

20、(5-10)针对流程的设计:流程(Process)由活动(Activity)组成,每个活动可以等价为对数据库的一个操作,例如增加一个用户就是在用户表里增加一条记录;又因为使用了Hibernate,所以对数据库的操作就是对持久对象的操作,例如增加用户就是增加一个User对象。

为了简化实现,暂时不考虑在流程中引入条件。

每个活动应该具有以下属性:

  • 操作(Operation),枚举类型,可选值为Select、Create、Delete、Modify之一;在实现中也可以使用活动不同的子类达到同样目的。
  • 目标类型(TargetType),它的值是数据模型里的一个类,例如User、Product,等等;因此流程模型有对数据模型的依赖。
  • 目标对象(TargetObject),该活动中处理的实例,例如用户张三;

活动间目标对象的传递问题:例如第一个活动创建了Product1,第二个活动的内容是编辑Product1的属性,在流程设计中,如何告知第二个活动Product1的ID号?另外,会不会存在有多个ID号需要告知的情况?现在的想法是在session里维护这个对象,Select操作为这个对象赋值,流程结束时对其清空。

21、(5-11)因为流程里没有分支结构,所以Begin和End这两个Activity似乎没有必要存在了。今天完成了StarFlowModel的原始编辑器,用它创建的流程模型可以通过“Load Resources...”载入数据模型,并建立两个模型之间的关联。同时删除了原来设计在数据模型里的Process和Activity这两个类型。

接下来要考虑具体的流程模板内容了。

22、(5-13)基本完成了Select和Modify这两个活动的模板,利用它们可以组成“修改产品信息”这样的简单流程。剩下的活动类型还有Create、Delete和Custom这几种,其中Custom将生成没有任何功能的代码框架,让用户自己实现任意功能。

另外,流程对应的模板也已经完成了。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2006/03/24/357784.html

自动换行的draw2d标签

Draw2D里的Label不支持自动换行,虽然可以插入换行符手动换行。用TextFlow和适当的Layout可以实现文字的自动换行。以下代码由sean朋友贡献,原文链接

class LabelEx extends FlowPage {

    private TextFlow contents;

    public LabelEx() {
        this("");
    }

    public LabelEx(String text) {
        contents = new TextFlow();
        contents.setLayoutManager(new ParagraphTextLayout(contents, ParagraphTextLayout.WORD_WRAP_SOFT));
        contents.setText(text);
        add(contents);
    }

    public void setText(String text) {
        contents.setText(text);
    }

    public String getText() {
        return contents.getText();
    }
}

关于Free版的EclipseUML

Omondo EclipseUML分为Studio版和Free版两种,我只用过Free版,对于创建EMF类图来说感觉已经够用了。不过和Eclipse的版本比起来,EclipseUML的升级比较缓慢,目前为止最新的版本还是2005年9月27日放出的,这就造成在新版本Eclipse里EclipseUML可能无法正常运行。

20050927版本是针对Eclipse 3.1开发的,现在Eclipse已经出到M5版本,我在Eclipse 3.2M4里运行这个版本的EclipseUML基本没有出现问题,但到了M5就出现了不能创建Enumeration的问题,表现为无法添加Enum项,所报异常如下:

java.lang.NoSuchMethodError: org.eclipse.jdt.internal.ui.wizards.dialogfields.ListDialogField.addElement(Ljava/lang/Object;)V
    at com.omondo.uml.obf.bte.a(SourceFile:352)
    at com.omondo.uml.obf.bte.a(SourceFile:63)
    at com.omondo.uml.obf.yt.customButtonPressed(SourceFile:315)
    at org.eclipse.jdt.internal.ui.wizards.dialogfields.ListDialogField.buttonPressed(ListDialogField.java:204)
    at org.eclipse.jdt.internal.ui.wizards.dialogfields.ListDialogField.doButtonSelected(ListDialogField.java:458)
    at org.eclipse.jdt.internal.ui.wizards.dialogfields.ListDialogField.access$0(ListDialogField.java:454)
    at org.eclipse.jdt.internal.ui.wizards.dialogfields.ListDialogField$2.widgetSelected(ListDialogField.java:420)
    at org.eclipse.swt.widgets.TypedListener.handleEvent(TypedListener.java:90)
    at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:66)
    at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1074)
    at org.eclipse.swt.widgets.Display.runDeferredEvents(Display.java:3158)
    at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:2838)
    at org.eclipse.jface.window.Window.runEventLoop(Window.java:811)
    at org.eclipse.jface.window.Window.open(Window.java:789)
    at com.omondo.uml.obf.ahk.c(SourceFile:167)
    at com.omondo.uml.obf.le.d(SourceFile:572)
    at com.omondo.uml.obf.fkr.execute(SourceFile:86)
    at org.eclipse.gef.commands.CommandStack.execute(CommandStack.java:149)
    at org.eclipse.gef.tools.AbstractTool.executeCommand(AbstractTool.java:388)
    at org.eclipse.gef.tools.AbstractTool.executeCurrentCommand(AbstractTool.java:400)
    at org.eclipse.gef.tools.CreationTool.performCreation(CreationTool.java:254)
    at org.eclipse.gef.tools.CreationTool.handleButtonUp(CreationTool.java:178)
    at org.eclipse.gef.tools.AbstractTool.mouseUp(AbstractTool.java:1053)
    at org.eclipse.gef.EditDomain.mouseUp(EditDomain.java:259)

在为了提高兼容性而特别制作的Eclipse 3.2M5a版本里也是同样的现象。不过已经创建好的Enumeration在Eclipse M5下则显示正常。因此提醒大家在使用这个插件的时候要特别注意多备份自己的ecd和ecore文件,以免造成不可挽回的后果。

另外,Omondo最近可能会有新的版本放出了,毕竟已经过了半年这么长的时间。很多朋友反映上不去www.omondo.com的问题,其实用国外代理是可以上的,不知道为什么这个网站也被禁掉了。Update(2012/11/12): 今天测试omondo网站已经可以从国内直接访问了。

补充:Omondo EclipseUML是支持cross reference的,见这个链接

补充:在EclipseUML的类图编辑器里删除一个类的时候要特别小心,如果其他类有以这个类为返回值或参数类型的时候,这个类图保存关闭后将无法再次打开

[Eclipse]通过TreeColumn实现“表格树”TableTree

Eclipse 3.1里deprecate了TableTree这个控件,与之对应的jface的TableTreeViewer虽然没有deprecate,但使用它会得到很多警告。在TableTreeViewer的第一列里是不能显示图标的,因为这个位置被+/-符号占用了,而且TableTree是显示不出 Tree的层次的,也就是没有缩进。

SWT 3.1里的Tree控件新支持了列的显示,是通过TreeColumn来实现的。在jface里则没有添加新的viewer,使用原先的TreeViewer即可支持,下面是一段例子代码,注意如果在windows里运行要修改一下setInput()这条语句的参数,例如改为setInput(new File("c:\\"))

import java.io.File;

import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TreeColumn;

public class TreeColumnTest {

    public void run(){
        final Display display = new Display();
        final Shell shell = new Shell(display);
        shell.setLayout(new FillLayout());

        final TreeViewer viewer = new TreeViewer(shell, SWT.FULL_SELECTION);
        viewer.getTree().setHeaderVisible(true);
        TreeColumn column = new TreeColumn(viewer.getTree(), SWT.LEFT);
        column.setText("Name");
        column.setWidth(200);
        column = new TreeColumn(viewer.getTree(), SWT.LEFT);
        column.setText("Size");
        column.setWidth(100);
        column = new TreeColumn(viewer.getTree(), SWT.LEFT);
        column.setText("Hidden");
        column.setWidth(100);
        viewer.setContentProvider(new MyTreeContenetProvider());
        viewer.setLabelProvider(new MyTableLableProvider());
        viewer.setInput(new File("/"));

        shell.open();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch())
                display.sleep();
        }
        display.dispose();
    }

    public static void main(String[] args) {
        new TreeColumnTest().run();
    }

    class MyTreeContenetProvider implements ITreeContentProvider{

        public Object[] getChildren(Object parentElement) {
            File file=(File)parentElement;
            if(file.isDirectory())
                return file.listFiles();
            else
                return null;
        }

        public Object getParent(Object element) {
            File file=(File)element;
            return file.getParentFile();
        }

        public boolean hasChildren(Object element) {
            File file=(File)element;
            return file.isDirectory()/*&&file.list().length>0*/;
        }

        public Object[] getElements(Object inputElement) {
            File file=(File)inputElement;
            return file.isDirectory()?file.listFiles():new Object[]{file};
        }

        public void dispose() {

        }

        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {

        }

    }

    class MyTableLableProvider implements ITableLabelProvider{

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

        public String getColumnText(Object element, int columnIndex) {
            File file=(File)element;
            switch (columnIndex) {
            case 0:
                return file.getName();
            case 1:
                return ""+file.length();
            case 2:
                return ""+file.isHidden();
            default:
                return "";
            }
        }

        public void addListener(ILabelProviderListener listener) {

        }

        public void dispose() {

        }

        public boolean isLabelProperty(Object element, String property) {
            return false;
        }

        public void removeListener(ILabelProviderListener listener) {

        }

    }
}

下面是运行画面:

file

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2006/03/07/344853.html

给表格的单元格增加编辑功能(补充)

这一篇是对“给表格的单元格增加编辑功能”的补充,目的是让表格列显示Checkbox并允许单击改变选中状态,例子中的表格共有三列,其中后两列均需要显示为Checkbox。

步骤一,构造TableViewer;

final String[] columnNames = new String[] { "Project", "Must", "Must Not" };//columnNames在后面也要用到,所以专门定义为一个数组
TableColumn column = new TableColumn(tbv.getTable(), SWT.NONE);
column.setText(columnNames[0]);
tbv.setColumnProperties(columnNames);//给每个列指定一个字符串属性值
column.setWidth(200);
column = new TableColumn(tbv.getTable(), SWT.NONE);
column.setText(columnNames[1]);
column.setWidth(100);
column = new TableColumn(tbv.getTable(), SWT.NONE);
column.setText(columnNames[2]);
column.setWidth(100);
tbv.setContentProvider(...);
tbv.setLabelProvider(...);
tbv.setInput(...);

步骤二,定义CellEditor数组并指定给前面的TableViewer:

final CellEditor[] editors = new CellEditor[tbv.getTable().getColumnCount()];
//editors[0]保留为空,因为第一列不需要显示为Checkbox
editors[1] = new CheckboxCellEditor(tbv.getTable());
editors[2] = new CheckboxCellEditor(tbv.getTable());
tbv.setCellEditors(editors);

步骤三,定义TableViewer的CellModifier,作用是告诉表格如何改变对象的属性值,注意在modify()方法里参数element可能是org.eclipse.swt.widgets.Item类型,如果是这种情况要通过Item#getData()得到实际的对象:

tbv.setCellModifier(new ICellModifier() {
    public boolean canModify(Object element, String property) {
        return property.equals(columnNames[1]) || property.equals(columnNames[2]);
    }

    public Object getValue(Object element, String property) {
        PortfolioItem item = (PortfolioItem) element;
        if (property.equals(columnNames[1])) {
            return Boolean.valueOf(item.isMust());
        }
        if (property.equals(columnNames[2])) {
            return Boolean.valueOf(item.isMustNot());
        }
        return null;
    }

    public void modify(Object element, String property, Object value) {
        if (element instanceof Item)
            element = ((Item) element).getData();
        PortfolioItem item = (PortfolioItem) element;
        if (property.equals(columnNames[1])) {
            item.setMust(((Boolean) value).booleanValue());
        }
        if (property.equals(columnNames[2])) {
            item.setMustNot(((Boolean) value).booleanValue());
        }
    }
});

步骤四,这时单击表格可以改变选中状态了,但显示的是True/False(或其他在LableProvider里定义的内容),见图1,而非Checkbox控件选中/清空的样子。

file
图1 单击表格单元可改变T/F

解决的办法很简单,在LabelProvider里根据属性值True/False显示不同的图片即可,这两个图片可以在这里下载(鼠标右键另存为):

public Object getColumnImage(Object object, int columnIndex) {
    PortfolioItem item=(PortfolioItem)object;
    switch (columnIndex) {
    case 1:
        return item.isMust()?PortfolioEditPlugin.getPlugin().getImage("checked"):PortfolioEditPlugin.getPlugin().getImage("unchecked");
    case 2:
        return item.isMustNot()?PortfolioEditPlugin.getPlugin().getImage("checked"):PortfolioEditPlugin.getPlugin().getImage("unchecked");
    default:
        return null;
    }        
}

public String getColumnText(Object object, int columnIndex) {
    PortfolioItem item=(PortfolioItem)object;
    switch (columnIndex) {
    case 0:
        return item.getProject()==null?"N/A":item.getProject().getName();
//在显示为Checkbox的两列里不需要文字
//        case 1:
//            return item.isMust()?"True":"False";
//        case 2:
//            return item.isMustNot()?"True":"False";
    default:
        return "";
    }
}

最后是运行结果:

file

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

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

在Eclipse的About对话框上添加自己的图标

在Eclipse的About对话框上添加自己的图标的步骤如下:

1、建立一个feature;

2、在feature.xml的Overview属性页里设定一个Branding Plug-in

3、把一个32x32的图片(名字可以任意起,例如mylogo.gif)放在Branding Plug-in的根目录下;

4、在Branding Plug-in的根目录下建立about.ini文件,内容为featureImage=mylogo.gif

5、最后,输出这个feature即可。

file
图1 添加在About对话框里的图标

注意1:Branding Plug-in的MANIFEST.MF的Build页里一定要选择输出mylogo.gifabout.ini这两个文件

注意2:我输出feature时如果选中Package features and plug-ins as individual JAR archives则feature不会被安装(在About的Feature Details列表里没有出现),勾掉这个选项则正常。

参考资料

How Can I Give My Eclipse Blob An Icon In The Flippin' About Dialog?

代码下载

包含feature和branding plug-in的工程打包

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2006/01/20/320759.html

将Eclipse插件转换为RCP应用程序(下)

上一篇里我们为一个普通的Eclipse插件添加了Application扩展,剩下来的工作就很简单了,甚至不需要再编写一行代码。在 Eclipse 3.1里,把具有Application的插件包装成RCP并输出的过程是通过建立产品配置文件(Product Configuration)来完成的。

在主菜单选择File->New->Other命令,在对话框里选择新建一个产品配置文件,这个文件可以建立在任何位置,为方便起见我 们就把它放在需要转换为RCP的插件的主目录下好了。产品配置文件是一个xml格式的文件,不过Eclipse 3.1提供了一个编辑器界面来编辑它的内容,所以不用像以前那样记住所有的tag了。这个编辑器分为三个页面:Overview、 Configuration和Branding。

首先在Overview页面指定产品的ID,按下“Product ID”右边的New...按钮,在对话框里输入插件的ID、新的产品ID以及缺省的Application的ID,见图1。关闭对话框后,选择一个要 运行的Application,并填写产品名称。下面有一个选项让你选择产品基于plug-in还是feature,feature是多个插件的集合,如 果只包含一个插件,选择基于plug-in即可;如果包含多个插件,利用feature可以让这些插件按功能分类,便于管理,建议使用基于feature 的方式,不过你要先建立feature才行。

file
图1 新建Product ID对话框

然后,来到Configuration页面,先把我们的插件添加到左边的插件列表里(如果前面选择了基于features方式,这里是 feature列表),再按Add Required Plug-ins按钮让Eclipse自动添加被依赖的其他插件。config.ini文件的作用是设置了一些变量值,RCP程序运行时会根据它们改变 一些外观或行为,例如可以在这里规定透视图切换器的停靠位置(org.eclipse.ui/DOCK_PERSPECTIVE_BAR=left)等 等;在页面的右下方可以设置一些运行参数。

file
图2 选择需要的插件

最后翻到Branding页面,这个页面的功能就是定制一些外观元素,例如启动时显示的splash图像,将想显示的图像以 splash.bmp命名保存到插件的根目录下,然后在“Splash Screen”里指定这个插件ID即可;定制窗口的图标,包括16x16和32x32两种格式,都是.gif文件;还有就是关于...对话框里的图片 和文字,根据自己的需要填写即可。启动器名称(Launcher Name)就是启动RCP的命令名称,在Linux里是一个脚本文件,在Windows里则是一个.exe文件。还可以定制启动器的图标,由于时间关系我 的例子里省去了这个定制项目。

需要注意一点,这些图像无论放在哪个目录里(比如icons目录)都应该确保会被输出到产品里,否则运行产品时会看不到它或看到红色的小方块,方法是在插件的build.properties编辑器里勾选需要输出的文件和目录。

上面这些都配置好以后,回到Overview页面,先点击Launch the product看一下产品运行后的效果,确认没有问题后,就可以点击Eclipse Product export wizard输出你的产品了,在弹出的对话框里填写要输出的位置,可以选择输出为目录或是打包为单个文件(.zip格式),见图3。

file
图3 输出产品

输出后最好再确认一下运行效果,如果和刚才有所不同,则很可能是build.properties写的有些问题,请仔细检查。

怎么样,很容易吧!