把第三方jar文件包装为plugin

前面说过,输出eclipse插件的时候问题比较多(google搜索3rd party jars site:dev.eclipse.org),特别是使用了第三方jar文件的时候。有一个比较方便的办法是把这些jar文件包装为一个单独的插件,然后 你的功能插件用dependencies的方式引用它们,这也是eclipse推荐的方式。

在eclipse里包装jar包很容易:按ctrl+N,在新建对话框里选择Plug-in from existing JAR archives,按下一步选择你需要的jar文件,再下一步指定这个plugin的名称,注意这一步里一般要把最下面的Unzip the JAR archives into the project选项清除,否则eclipse会把jar文件全部展开为.class文件树,最后按Finish就可以了。

奇怪的是,我不使用上面这种方式,用建立普通plugin项目的方式怎么也无法建立好包装plugin,表现为其它plugin依赖它后找不到里面的那些类。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/10/17/256330.html

[翻译]Eclipse里的IAdaptable是什么?

原文地址:http://www.eclipsezone.com/articles/what-is-iadaptable/

IAdaptable在Eclipse里是一个非常重要的接口。对于Eclipse开发老手来说,它就像异常处理和抽象类一样寻常;但是对新手而言,它却令人感到困惑和畏惧。这篇文章将向你解释IAdaptable到底是什么,以及它在Eclipse里起到的作用。

类型转换

Java是所谓的强类型语言,也就是说,每个实例都对应一个类型。其实类型分为两种:声明类型和运行时类型(也分别被称为静态类型和动态类型)。像Python这样的弱类型语言常被称为无类型的语言,其实严格说来不是这样,因为每个实例都对应一个运行时类型,只是你并不需要声明这一点而已。

现在回到Java,为了能够执行一个类的某个方法,这个方法必须在声明类型中可见,换句话说,即使在运行时实例是某个子类型,你也只能执行那些父类型里定义的方法。

List list = new ArrayList();
list.add("data");       // 正确,add是List里定义的方法
list.ensureCapacity(4); // 不正确,ensureCapacity()只在ArrayList被定义

如果一定要执行特定类型的方法,我们必须先强制转换这个实例到正确的类型。对于上面的例子,我们可以将list转换为ArrayList(译注:原文In this case, we can cast ArrayList to List,怀疑是笔误),因为ArrayList实现了List接口,你甚至可以在运行时通过instanceof关键字检验list是否为ArrayList的一个实例。

可扩展的接口

不幸的是,一个类可能并没有实现你需要的接口,这样就无法进行强制类型转换了。原因有很多,比如只在少数情况下才需要这个接口,或者你需要的接口是在另一个不相关的库里,又或者接口是有了类以后才开发出来的,等等。

这时你就需要IAdaptable了。可以把IAdaptable想象为一个能够动态进行类型转换的途径。对比下面的直接类型转换:

Object o = new ArrayList();
List list = (List)o;

换一种方式,我们可以这样做:

IAdaptable adaptable = new ArrayList();//译注:这里的ArrayList应该不是指java.util.ArrayList
List list = (List)adaptable.getAdapter(java.util.List.class);

这就是上面所说的动态类型转换,我们所做的事情是试图把adaptable转换为一个List实例。

那么,当可以直接转换的时候为什么要费这个力气通过getAdapter()来转换呢?其实这种机制可以让我们将目标类转换为它并没有实现的接口。举个例子,我们可能想把一个HashMap当作List来用,尽管这两个类的性质并不相同,可以这么做:

IAdaptable adaptable = new HashMap();//译注:这里的HashMap应该不是指java.util.HashMap
List list = (List)adaptable.getAdapter(java.util.List.class);

实现IAdaptable接口

大部分IAdaptable的实现是一些if语句的叠加,比如我们现在要实现HashMap的getAdapter()方法,它看起来可能是这样:

public class HashMap implements IAdaptable {
  public Object getAdapter(Class clazz) {
    if (clazz == java.util.List.class) {
      List list = new ArrayList(this.size());
      list.addAll(this.values());
      return list;
    }
    return null;
  }
  ...
}

所做的就是返回一个适配器(adapter,更确切的说是一个副本),而不是进行直接的类型转换。如果参数类型没有被支持,惯例是返回null值(而非抛出异常),代表这个方法失败了。因此,在调用这个方法时,不应该假定它总是返回非null值。

PlatformObject

当然,如果你希望增加一个新的被支持的adapter类型时必须编辑这个类才行(译注:在getAdapter()里增加更多的if语句),这会比较辛苦。而且,既然你已经知道了这个类型,何不直接修改接口声明呢?其实有很多原因使得你并不希望直接编辑这个类(例如更容易保持向下兼容性),也不想改变它的类型(HashMap虽然不是一个List,但可以转换过去)。

Eclipse通过PlatformObject抽象类来解决以上问题,它为你实现了IAdaptable接口,Eclipse平台(Platform)提供了IAdapterManager的一个实现,并且可以通过Platform.getAdapterManager()访问到,它把所有对getAdapter()的请求(调用)委托给一个名为IAdapterManager的东西。你可以将它想象为一个巨大的保存着类和adapter信息的Map,而PlatformObject的getAdapter()方法会查找这个Map。

适配已存在的类

这样,PlatformObject不需要重新编译就能够支持新的adapter类型,这一点在Eclipse里被大量使用以支持workspace的扩展点。

现在假设我们想要将一个只包含String类型元素的List转换为一个XMl节点,这个节点的格式如下:

<List>
  <Entry>First String</Entry>
  <Entry>Second String</Entry>
  <Entry>Third String</Entry>
</List>

因为toString()方法可能有其他用途,我们不能通过覆盖toString()方法来实现这个功能。所以,我们要给List关联一个工厂类以处理XML节点类型的适配请求。要管理工厂类需要以下三个步骤:

1、由List生成一个Node,我们把这个转换过程用IAdapterFactory包装起来:

import nu.xom.*;
public class NodeListFactory implements IAdapterFactory {
  /* 可以转换到的类型 */
  private static final Class[] types = {
    Node.class,
  };
  public Class[] getAdapterList() {
    return types;
  }
  /* 转换到Node的功能代码 */
  public Object getAdapter(Object list, Class clazz) {
    if (clazz == Node.class && list instanceof List) {
      Element root = new Element("List");
      Iterator it = list.iterator();
      while(it.hasNext()) {
        Element item = new Element("Entry");
        item.appendChild(it.next().toString());
        root.appendChild(item);
      }
      return root;
    } else {
      return null;
    }
  }
}

2、把这个工厂类注册到Platform的AdapterManager,这样当我们希望从List的实例中获得一个Node实例时,就会找到我们的工厂类。注册一个工厂类的方式也很简单:

Platform.getAdapterManager().registerAdapters(
  new NodeListFactory(), List.class
);

这条语句将NodeListFactory关联到List类型。当从List里请求adapter时,Platform的AdapterManager会找到NodeListFactory,因为在后者的getAdapterList()方法的返回结果里包含了Node类,所以它知道从List实例得到一个Node实例是可行的。在Eclipse里,这个注册步骤一般是在plugin启动时完成的,但也可以通过org.eclipse.core.runtime.adapters扩展点来完成。

3、从List获得Node,下面是例子代码:

Node getNodeFrom(IAdaptable list) {
  Object adaptable = list.getAdapter(Node.class);
  if (adaptable != null) {
    Node node = (Node)adaptable;
    return node;
  }
  return null;
}

总结

综上所述,要在运行时为一个已有的类增加功能,所要做的只是定义一个用来转换的工厂类,然后把它注册到Platform的AdapterManager即可。这种方式在保持UI组件和非UI组件的分离方面特别有用。例如在org.rcpapps.rcpnews.ui和org.rcpapps.rcpnews这两个plugin里,前者的IPropertySource需要与后者的数据对象(data object)相关联,当前者初始化时,它将IPropertySource注册到Platform,当数据对象在导航器(navigator)里被选中的时候,属性视图里就会显示正确的属性。

显然,java.util.List并不是PlatformObject的子类,所以如果你希望能够编译这里所说的例子,必须建立一个List的子类型。注意,可以直接实现IAdaptable接口,而非必须继承PlatformObject抽象类。

public class AdaptableList implements IAdaptable, List {
  public Object getAdapter(Class adapter) {
     return Platform.getAdapterManager().getAdapter(this, adapter);
  }
  private List delegate = new ArrayList();
  public int size() {
    return delegate.size();
  }
  ...
}

最后,例子里生成XML的部分使用了XOM的类库。

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

SWT的PaintListener

以前很少用到这个类(org.eclipse.swt.events.PaintListener),利用它可以用来在control上画一些东西,基本方法是在control上 addPaintListener()一个PaintListener,然后在这个listener里做具体的画图工作,listener在control需要绘制的时候调用。

下面例子代码用来在一个composite的中央绘制一行文字。

package com.test;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class Test3 {

    public static void main(String[] args) {
        Display display = new Display();
        Shell shell = new Shell(display);
        shell.setLayout(new FillLayout());
        final Button button = new Button(shell, SWT.PUSH);
        button.setText("This is a button");
        final Composite comp2 = new Composite(shell, SWT.BORDER);
        comp2.addPaintListener(new PaintListener() {
            public void paintControl(PaintEvent e) {
                String text = "This is a composite";
                Rectangle area = comp2.getClientArea();//client area
                int tw = calcTextWidth(e.gc, text);//text width
                int th = e.gc.getFontMetrics().getHeight();//text height
                Rectangle textArea = new Rectangle(area.x + (area.width - tw) / 2,
                        area.y + (area.height - th) / 2, 
                        tw,
                        th);//centerized text area
                e.gc.drawString(text, textArea.x, textArea.y);
                e.gc.drawRectangle(textArea);
            }

            private int calcTextWidth(GC gc, String text) {
                int stWidth = 0;
                for (int i = 0; i < text.length(); i++) {
                    char c = text.charAt(i);
                    stWidth += gc.getAdvanceWidth(c);
                }
                return stWidth;
            }
        });
        shell.open();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch())
                display.sleep();
        }
        display.dispose();
    }
}

运行结果如下图:

file

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

Draw2d里的Invalidating和Updating

本文部分内容来自Building a Database Schema Diagram Editor with GEF和GEF and Draw2d Plug-in Developer Guide,是对Draw2D里一些基本概念的说明。

LayoutManager(布局管理器)

  • 布局管理器通过Figure#setBounds()改变子图形的位置和大小。
  • 根据布局算法和子图形决定当前图形的preferredSize。
  • 布局的过程是先确定图形的大小,再计算每个子图形的新位置和大小。 

Figure#invalidate()

  • 若valid属性已经是false则直接返回。
  • 如果图形拥有LayoutManager,则调用LayoutManager的invalidate()方法,在XYLayout里作用是将preferredSize重置为null值,在FlowLayout里还要把minimumSize置为null值。
  • 将图形的valid属性置为false。

Figure#revalidate()

  • 我觉得它实际代表"recursive invalidate"的意思。这个方法的功能是首先将图形自己invalidate(),然后递归的将图形的父图形invalidate(),一直到根图形为止,这个根图形会被加入到UpdateManager的一个列表中。
  • 在Figure的很多方法里,如setBorder()、setContstraint()、setLayoutManager()、add()、remove()等,会自动调用revalidate()方法。因此,大部分情况下我们不需要手动调用这个方法。

Figure#validate()

  • 若valid属性已经是true则直接返回。
  • 将图形的valid属性置为true。
  • 如果图形拥有LayoutManager,则调用LayoutManager的layout()方法。
  • 对图形的每个子图形,调用validate()方法。

Figure#repaint()

  • 在图形的UpdateManager里,将图形所处的区域标记为“脏”区域,这个区域将由UpdateManager(定期)重画。
  • 在图形的setVisible()、setOpaque()、setForegroundColor()、setBounds()、setBackgroundColor()等方法里会自动调用repaint()方法。

Figure#paint()

  • 虽然名称相似,但这个方法和repaint()关系不大。在Figure里这个方法按顺序调用paintFigure()、paintClientArea()和paintBorder()这三个方法,当实现自己的Figure时,绝大多数情况下应该只覆盖paintFigure()而不是paint()本身。

Figure#getPreferredSize()

  • 对于Label这样的图形,它的preferredSize由它所显示的文本和图标所占空间决定;如果一个图形包含子图形,则它的preferredSize要考虑子图形的排列方式,所以要由LayoutManager来决定。
  • LayoutManager的getPreferredSize()方法还有两个参数:wHint和hHint,它们分别代表图形的已知长(宽)度,如果其中一个值是大于零的,则在另一个方向上子图形将换行(列)排列,以保证长(宽)度不大于这个已知值。

基本上来说,validate是对于尺寸的调整,而repaint()是对颜色的调整。当我们把一个图形C作为子图形拖到另一个图形P里的时候(想象P为UML类图里表示类的矩形,C为表示属性或方法的矩形),因为调用了P的add()方法,所以P及P的所有“祖先”图形都将通过revalidate()被置为invalid状态。UpdateManager随后在performUpdate()里对这些图形进行validate(),在validate()的过程中,每个图形将通过自己的LayoutManager重新计算自己的尺寸。这样就实现了P随子图形的多少自动改变大小。

file

上面左图是在子图形上发生改变时,自动调用了Fig4的invalidate()方法,导致到根图形之间的所有图形的invalidate()方法被触发。右图则是UpdateManager对这些invalid图形进行validate(),并且是自上而下进行的(几乎可以认为validate()方法就是对layout()方法的调用)。注意到由于对Fig2进行了layout(),Fig5的尺寸也可能因此发生改变,如果发生了这种情况,则Fig5的invalidate()方法也会被调用。

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

[GEF]实现模板功能

最近遇到一个需求是要在GEF应用里实现“模板”的功能,也就是像word那样在新建一个文件的时候,用户可以选择一个模板,然后以这个模板为基础进行编辑,而不用每次都从头开始。同时,用户要能够创建新模板和对模板进行编辑,在模板里可以指定一些对模型的限制,例如树的最大高度、子节点数目的限制,等等。

最简单的模板可以就是一个实例,以它为模板时创建的新实例就是这个实例的副本。这种情况下,模板编辑器就是实例编辑器。问题主要有两个,一是无法通过模板实现上述对实例的限制,二是除了扩展名以外,用户难以通过编辑器外观区分是在编辑模板还是编辑实例(本文中实例指原有的那个模型)。

更一般的模板可以这样设计:首先,模板模型和实例模型是很相似的,只是在实例模型的基础上,每个模板类多了一些用来限制实例的属性,因此,模板模型中的每个类应该是实例模型中对应类的子类,这样就解决了限制实例的问题。其次,对模板的编辑不能使用现有的Editor,而应该是对现有Editor的扩展,目的是区分两种编辑器的外观。

等一下,按照这个设计在实现上会有一些问题,在根据某个模板编辑实例的时候,必须知道模板信息,这样才能判断比如新增子节点操作是否可用。但是如果你某天打开一个实例时模板早已被删除了呢?我们实际需要的是,一旦实例被创建,它就与模板脱离关系,即使模板被修改和删除。因此,我认为模板和实例必须共享完全相同的模型,这样实例中就有完整的限制信息,并且还有一个额外的好处,它们的读取方法是完全一致的,可以节约不少工作量。

现在来看看实现。为了更加灵活,我们把模板单独作为一个新的Plugin,这样一是便于分开代码,二是很方便添加或移除模板功能(只要删掉模板Plugin就能移除模板功能)。因此,在模板Plugin里要额外指定对原来Plugin的依赖(在Plugin编辑器的dependencies属性页里),而原来Plugin里要确保Export了模板Plugin需要的所有包(在runtime属性页里)。

在这个Plugin里,创建一个继承实例Editor的新Editor(名为TemplateEditor)。怎样让这个Editor里显示的界面与实例Editor有所区别呢?最简单的一个方法是设置viewer的背景颜色,也就是覆盖configureGraphicalViewer()方法,加上下面这句:

getGraphicalViewer().getControl().setBackground(new Color(null, 250, 250, 200)); 

如此一来,这个Editor的背景就是淡黄色了。但我还想进一步改造它,让每个节点的显示都与实例编辑器中不同。因为节点的figure是在editpart里创建的,所以就必须使用与原来不同的editpart,而editpart是通过partfactory创建的,所以要让viewer使用与原来不同的partfactory:

getGraphicalViewer().setEditPartFactory(new TemplatePartFactory()); 

当然,我们还要通过继承原来的那些editpart得到一套新的editpart,在TemplatePartFactory的createEditPart()方法里返回它们。在新的editpart的createFigure()方法里,返回你希望的图形。

file

图:模板编辑(左图)和实例编辑(右图)

图形的问题解决了,还剩下另一个问题,应该只在编辑模板的时候在properties view里显示那些限制属性,而在编辑实例时不显示(这些属性在起作用,但不应该能被编辑)。按照GEF提供的例子,一般是在model类里定义用来控制哪些属性显示在properties view的IPropertyDescriptor数组,我们现在的情况是两个Plugin使用同一个model,这样就造成了矛盾。解决的办法是改为在editpart里定义IPropertyDescriptor数组。GEF的editpart通过getAdapter()方法返回实现IPropertySource接口的对象:

public Object getAdapter(Class key) {
    if (IPropertySource.class == key) {
        if (getModel() instanceof IPropertySource)
            return getModel();
        if (getModel() instanceof IAdaptable)
            return ((IAdaptable)getModel()).getAdapter(key);
    }
    if (AccessibleEditPart.class == key)
        return getAccessibleEditPart();
    return null;
}

可以看到当model类实现了IPropertySource时,就会返回model类。我们要覆盖这个方法,让它返回editpart本身,同时让editpart实现IPropertySource并把原来放在model类里的那些代码移动到editpart里。最好在原来的editpart里就做出修改,这样模板部分只要简单的继承下来并修改getPropertyDescriptors()等几个相关方法即可:

public Object getAdapter(Class key) {
    if (IPropertySource.class == key) {
        return this;
    }
    return super.getAdapter(key);
}

file

图:模板模型的属性视图(左图)和实例模型的属性视图(右图)

要记住,为了让这些限制属性在实例编辑中起作用,必须修改原来的代码,增加这些额外的判断。之后,再在原来的CreationWizard里增加一个选择模板的page,模板功能就算实现了。按照这个思路,不需要额外写很多代码。

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

导出Plugin的一点心得

Eclipse升级到了3.1以后,在Plugin manifest editor上做了相当大的改动,可能是刚刚完全支持OSGi的缘故吧,感觉这个editor的小bug挺多的。比如很多情况下表示dirty的星号不会消失,有时会弹出带红叉的对话框等等,不过我相信在eclipse 3.2版本里会得到解决,因为关于它的bug report已经不少了。

除了一些小bug以外,我发现在3.1里导出一个Plugin也变得有些“莫名其妙”,最近经过实验摸索,得到以下一些心得:

1、开发plugin的时候,不要在project properties的java build path里做任何修改,应该在plugin配置文件的dependencies、runtime和build这几个部分添加。

2、在build页上面部分,Library的缺省情况是".",我发现有时候指定为"."会造成class不被输出。比较保险的方法是指定一个.jar名字,对应的folder还是src/,勾中下面的Include selected library选项。

3、在build页中间部分指定要输出的文件,一般除了缺省的那些以外,还要包含如lib、icons、log4j.properties等目录或文件。

4、在build页下面部分,也就是“Extra Classpath Entries”,指定你的应用程序用到的外部包,例如log4j.jar等等。

5、需要注意的是,如果Library里指定了abc.jar,则输出时会生成abc.jar这个文件,但Plugin在运行时会报ClassNotFound异常,原因是你没有把abc.jar指定到这个Plugin的classpath里。解决的办法是在Manifest.mf文件的Bundle-ClassPath项里手动加上abc.jar,例如“Bundle-ClassPath: abc.jar,lib/log4j-1.2.11.jar”这样。

6、若你有两个Plugin,多个的情况也一样,假设B依赖A,那么不仅在B的dependencies里要加上A,还要保证A的Runtime页里export了B所需的所有包,也就是在A的Manifest.mf文件里指定正确的“Export-Package”项。

7、输出为目录或输出为单个jar包在运行时没有什么区别,感觉jar包方便一些,输出的jar包名就是项目名。

希望对你有帮助。

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

写代码的代码:JET

用过EMF的人想必都对它的代码生成功能印象深刻吧,有没有想过这是怎样实现的呢?

代码生成一般是通过写好的模板,在用户输入一些限制条件后,由程序把这两者结合起来得到需要的代码。EMF也是这样,它内置了一些模板(放在 org.eclipse.emf.codegen.ecore里),我们通过Java Interface或XML Schema文件建立ecore模型后(这一步由org.eclipse.emf.codegen.ui支持),插件 org.eclipse.emf.codegen帮我们生成model、edit、editor和test这几个新的插件,这些生成的插件就可以被用来构 造应用程序了。

EMF使用JET(Java Emitter Templates)进行代码生成,JET使用的模板文件与JSP格式几乎完全一致,像JSP里有application这样的隐含变量一样,在JET模 板文件里可以使用argument这个隐含变量,它代表用户的输入参数(对EMF来说就是ecore模型)。下面是一个最简单的带有参数的模板文件 hello.txtjet,JET模板文件的扩展名一般是要生成文件类型再加上jet。

<%@ jet package="hello" class="GreetingTemplate" %>
Hello, <%=argument%>!

只要给这个模板文件一个参数,JET就能帮我们生成一个字符串,其内容是“Hello,参数!”。如果我们定义一个.javajet的模板,配合适当的参数,再把生成的字符串保存为文件,就得到了java代码,EMF的代码生成过程就是如此。

自己实现代码生成器的过程如下:创建一个Plugin项目,编写模板文件.javajet,一般放在templates目录下;定义元模型,这个元 模型要与模板相匹配,模板的argument一般就是元模型最外层的container;构造一个用户界面,让用户可以构造元模型的实例;提供一个 Action比如按钮,用户按下即开始生成代码。

代码的生成是由JETEmitter完成的,它会在workbench里先生成一个.JETEmitter项目,把模板转换为java类(这个过程类似JSP转换为Servlet),然后调用这些类的generate()方法得到结果。

我做了一个生成.txt文件的生成器插件,点此下载。安装后在Eclipse主菜单里选File->New->Others,在New对话框里选择Sample Wizards下的Echo filename text file,新建的文本文件内容里会包含文件名。

因为用途有限,JET的资料不是很多,这里有两篇:链接1链接2,其中后者在EMF的帮助里也找得到。

最后补充一个Tip,在plugin里访问一个相对路径文件的方法如下:

String base = Platform.getBundle(pluginId).getEntry("/").toString();
String relativeUri = "templates/echo.txtjet";
JETEmitter emitter = new JETEmitter(base + relativeUri, getClass().getClassLoader());

更正(08/23),上面说的方法只对JETEmitter有效,得到的路径是Platform内部路径而非本地路径,这里提供另一个方式可以得到本地路径:

Platform.asLocalURL(Platform.getBundle(pluginId).getEntry("/log4j.properties"))

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

由模型生成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

[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文件中的内容转移过去)。

[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