将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写的有些问题,请仔细检查。

怎么样,很容易吧!

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

有不少朋友问到如何把一个已有的Eclipse插件转换为RCP应用程序,其实这个过程并不复杂,因为RCP应用也是基于插件的结构,可以说RCP 就是精简后的Eclipse平台,只是我们要对这个平台做一些定制工作。将任何一个传统的Eclipse插件项目转换到RCP可以分为两个步骤,这篇先介 绍第一个步骤:建立应用程序。

GEF入门系列(三、应用实例)里我曾做过一个精简的GEF应用程序(下载),这一篇里我就一步一步的把这个例子转换为RCP应用程序(点击下载转换后的项目打包)。应用程序(Application)是通过扩展org.eclipse.core.runtime.applications扩展点建立的,其作用 是让Eclipse知道你的RCP需要什么样的功能,比如界面上有哪些视图,菜单和工具条,应用程序窗口的初始大小等等。在plugin.xml里添加应 用程序的定义很简单,像下面这样指定一个id和一个类名就可以了。

<extension
      id="myapplication"
      point="org.eclipse.core.runtime.applications">
   <application>
      <run class="com.example.application.MyApplication"/>
   </application>
</extension>

接下来我们的主要任务是实现这个类,MyApplication必须实现 org.eclipse.core.runtime.IPlatformRunnable接口,这个接口只定义了一个run()方法,对于Eclipse Platform来说这个方法就相当于传统java程序的main()方法,是入口方法。所有RCP应用程序里这个方法的实现几乎是完全一样的,即启动 Workbench,并把一个WorkbenchAdvisor实例作为参数传给它,如下所示:

public class MyApplication implements IPlatformRunnable {

    public Object run(Object args) throws Exception {
        Display display = PlatformUI.createDisplay();
        try {
            int returnCode = PlatformUI.createAndRunWorkbench(display, new MyWorkbenchAdvisor());
            if (returnCode == PlatformUI.RETURN_RESTART) {
                return IPlatformRunnable.EXIT_RESTART;
            }
            return IPlatformRunnable.EXIT_OK;
        } finally {
            display.dispose();
        }
    }
}

所以应用程序的定制实际上是通过这个WorkbenchAdvisor实例实现的。现在我们要构造 org.eclipse.ui.application.WorkbenchAdvisor类的一个子类,也就是上面代码里出现的 MyWorkbenchAdvisor,然后覆盖它的一些方法。比较重要的是这两个方法:createWorkbenchWindowAdvisor() 返回一个WorkbenchWindowAdvisor实例,从类名不难看出它的作用是定制应用程序窗口,包括菜单和工具条,稍后将详细介绍; getInitialWindowPerspectiveId()返回一个透视图的id字符串,这个透视图定义RCP应用程序的界面布局,所以如果在原来 的插件里你没有定义透视图,现在必须要新定义一个了。

public class MyWorkbenchAdvisor extends WorkbenchAdvisor {

    private static final String PERSPECTIVE_ID = "com.example.ui.MyPerspective";

    public WorkbenchWindowAdvisor createWorkbenchWindowAdvisor(
            IWorkbenchWindowConfigurer configurer) {
        return new MyWorkbenchWindowAdvisor(configurer);
    }

    public String getInitialWindowPerspectiveId() {
        return PERSPECTIVE_ID;
    }

    public void initialize(IWorkbenchConfigurer configurer) {
        super.initialize(configurer);

        //The workaround call
        WorkbenchAdapterBuilder.registerAdapters();
    }
}

注意:因为我们这个RCP里用到了Resource视图,而这个视图依赖org.eclipse.ui.ide,所以要在上面的 initialize()方法里手动注册一下Adapter,否则Resource视图里无法显示现有项目。(Resource视图在RCP里不推荐使 用,这个调用是无奈之举,请参考这条bug报告

现在来看一下前面代码里MyWorkbenchWindowAdvisor是怎样实现的,它继承自 org.eclipse.ui.application.WorkbenchWindowAdvisor类,为了定义窗口大小和标题要覆盖 preWindowOpen()方法,可以看到我们还顺便隐藏了工具条;要定义窗口的菜单和工具条,应该覆盖 createActionBarAdvisor()方法,返回的ActionBarAdvisor实例马上会介绍到。

public class MyWorkbenchWindowAdvisor extends WorkbenchWindowAdvisor {

    public MyWorkbenchWindowAdvisor(IWorkbenchWindowConfigurer configurer) {
        super(configurer);
    }

    public ActionBarAdvisor createActionBarAdvisor(
            IActionBarConfigurer configurer) {
        return new MyActionBarAdvisor(configurer);
    }

    public void preWindowOpen() {
        IWorkbenchWindowConfigurer configurer = getWindowConfigurer();
        configurer.setInitialSize(new Point(700, 500));
        configurer.setShowCoolBar(false);
        configurer.setShowStatusLine(false);
        configurer.setTitle("My RCP Application");
    }
}

有没有注意到,我们新建(和即将新建)的几个类有这样的引用关系:MyApplication->MyWorkbenchAdvisor- >MyWorkbenchWindowAdvisor->MyActionbarAdvisor,在3.1M5以前的Eclipse RCP版本中,还没有ActionbarAdvisor这个类,大部分应用程序定制工作都是在WorkbenchWindowAdvisor这一个类中做的,带来的问题是这个类的代码很长,可复用的程度比较低;采用现在这种方式就方便多了,比如可以定义几个ActionbarAdvisor然后在 WorkbenchWindowAdvisor中根据需要做出选择,得到的应用程序就具有不同的功能,等等。

现在就来看看MyActionbarAdvisor是怎么实现的,它继承 org.eclipse.ui.application.ActionBarAdvisor类,我们先在makeActions()里构造需要出现在菜单 或工具条上的命令,注意要调用register()方法注册这些命令,作用是在应用程序结束后释放资源,同时支持快捷键操作;然后在 fillMenuBar()方法里把这些命令加入主菜单,因为我们隐藏了工具条,所以没有覆盖fillCoolBar()方法,另外你还可以通过覆盖 fillStatusLine()定义自己的状态栏。我们的这个类实现得很简单,只是一个退出程序菜单项,你应该根据需要添加自己的命令。

public class MyActionBarAdvisor extends ActionBarAdvisor {

    private IWorkbenchAction exitAction;

    public MyActionBarAdvisor(IActionBarConfigurer configurer) {
        super(configurer);
    }

    protected void makeActions(final IWorkbenchWindow window) {
        exitAction = ActionFactory.QUIT.create(window);
        register(exitAction);
    }

    protected void fillMenuBar(IMenuManager menuBar) {
        MenuManager fileMenu = new MenuManager("&File",
                IWorkbenchActionConstants.M_FILE);
        menuBar.add(fileMenu);
        fileMenu.add(exitAction);
    }
}

现在,应用程序需要的类都写好了,让我们检查一下应用程序是否可以正常启动。在Eclipse主菜单上选择Run->Debug...命令, 在对话框左边的Eclipse Application组下新建一个运行项gefpractice-rcp,在Program to Run组下选择Run an application,然后在下拉列表里找到我们的应用程序id,要说明的是在applications扩展点里我们指定的id是 myapplication,而这里列出的id则添加了插件id作为前缀,变成了GefPractice-RCP.myapplication, 如图1所示。

file
图1 设置为运行应用程序

因为缺省运行会启动Eclipse的全部插件,这样在应用程序里会出现多余的菜单项和功能,所以要设置为只启动我们的这一个插件,方法是切换到 Plug-ins属性页,选择Choose plug-ins and fragments to launch from the list,点击右边的Deselect All按钮清空选择列表,勾选上我们的插件项目,再按Add Required Plug-ins让Eclipse自动添加它依赖的其他插件就可以了,如图2所示。

file
图2 只启动我们的这一个插件

现自使用这个运行配置启动我们的应用程序,会得到一个很“干净”的界面,如图3所示,如果不是那些Eclipse特有的编辑器/视图的标题栏,你能猜出它是一个Eclipse应用程序吗?作为对比,这是Eclipse插件的版本的运行截图

file
图3 运行中的应用程序

建立了应用程序,代码的部分就算是完成了,但要得到一个完整的可独立运行的产品这样还不够,下一个帖子里将介绍另一个步骤:将应用程序包装为产品。如果等不及可以先看Branding Your Application这篇文章,只是这篇文章写得比较早,我下个部分要写的是使用.product配置产品,可以更方便的达到相同的目的。

参考资料

关于建立应用程序的更多内容请参考Rich Client Tutorial,这个教程共有三个部分,我当时就是通过它学习的,后来它按照RCP API的发展又及时更新了内容,是难得的入门材料。

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

[Eclipse]使用osgi.util.NLS简化资源文件访问

Eclipse 3.1提供了新的资源文件访问方式,就是通过osgi.util.NLS类。具体方法是构造一个NLS的子类,如下:

public class MyMessages extends NLS {
  private static final String BUNDLE_NAME = "gr.scharf.MyMessages"; //$NON-NLS-1$

  public static String HELLO_WORLD;
  public static String HELLO_SOMETHING;

  static {
  // initialize resource bundles
  NLS.initializeMessages(BUNDLE_NAME, MyMessages.class);
  }
}

资源文件的内容还是和以前一样:

HELLO_WORLD=Hello world!  
HELLO_SOMETHING=Hello {0}!

在程序里使用资源的方式如下:

System.out.println(MyMessages.HELLO_WORLD);  
System.out.println(MyMessages.bind(MyMessages.HELLO_SOMETHING,"world"));

这样一来代码简单了不少,但eclipse jdt提供的externalize strings功能生成的类还是以前的方式,而不是生成这样的NLS子类。

小Tip:在.java文件编辑器里输入nls,再自动完成(ctrl+space),这样可以快速生成"//$NON-NLS-N$"标记。

参考资料

原文地址(访问不到,似乎被删除了)

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

SWT里Slider和Scale的区别

以前以为SliderScale之间只是外观的区别,今天发现不是这样的,因为Slider有一个特点:getSelection()能得到的最 大值并不是getMaximum()的值,要减去getThumb()值,后者是中间的滑块所拥有的值,缺省为10,最小为1。运行这个程序观察控制台的 输出。

import org.eclipse.swt.*;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.*;

public class SliderTest {

    public static void main(String[] args) {
        Display display = new Display();
        Shell shell = new Shell(display);

        //Slider
        final Slider slider = new Slider(shell, SWT.HORIZONTAL);
        slider.setBounds(10, 10, 200, 32);
        slider.setMinimum(0);
        slider.setMaximum(100);
        slider.setThumb(5);
        slider.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                System.out.println("Slider Selection:" + slider.getSelection());
            }
        });

        //Scale
        final Scale scale = new Scale(shell, SWT.HORIZONTAL);
        scale.setBounds(10, 50, 200, 72);
        scale.setMinimum(0);
        scale.setMaximum(100);
        scale.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                System.out.println("Scale Selection:" + scale.getSelection());
            }
        });

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

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

EMF+GEF的属性页问题

最近有朋友问使用EMF作为GEF模型时,如何在选中editpart时在属性页里显示属性的问题。是的,因为GEF是这样判断是否填充属性 页的:

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;
}

所以,一般(不使用EMF)我们让模型类实现IPropertySource接口即可看到属性。而用EMF生成的模型类是不实现这个接口的,因此用户在界面上选中 editpart时属性页里只能是空白。

要解决这个问题,一种方式是覆盖editpart的getAdapter()方法,返回一个自定义的PropertySource, 这个办法比较直接,但那么多属性写起来很麻烦,更重要的是当ecore模型改变后这些属性是不会跟着变的;另一种方式是在editor类里作文章,工作量 比较小,具体办法如下:

ModelItemProviderAdapterFactory adapterFactory;
AdapterFactoryContentProvider adapterFactoryConentProvider;

//Constructor of the editor
public TobeEditor() {
    setEditDomain(new DefaultEditDomain(this));
    //For getting propertysource from emf.edit
    adapterFactory = new ModelItemProviderAdapterFactory();
    adapterFactoryConentProvider = new AdapterFactoryContentProvider(adapterFactory);
}

public Object getAdapter(Class type) {
    if (type == IContentOutlinePage.class)
        return new OutlinePage();
    if (type == org.eclipse.ui.views.properties.IPropertySheetPage.class) {
        PropertySheetPage page = new PropertySheetPage();
        UndoablePropertySheetEntry root = new UndoablePropertySheetEntry(getCommandStack());
        root.setPropertySourceProvider(new IPropertySourceProvider() {
            public IPropertySource getPropertySource(Object object) {
                if (object instanceof EditPart) {
                    Object model = ((EditPart) object).getModel();
                    return new PropertySource(model, (IItemPropertySource) adapterFactory.adapt(model,
                            IItemPropertySource.class));
                } else {
                    return adapterFactoryConentProvider.getPropertySource(object);
                }
            }
        });
        page.setRootEntry(root);
        return page;
    }
    return super.getAdapter(type);
}

也就是对UndoablePropertySheetEntry做一些处理,让它能够适应editpart的选择(GEF里选中元素的都是 editpart而非model本身)。这个方法在显示属性方面没有什么问题,但在属性页里修改属性值后,是不能undo的,而且不会显示表示dirty 的*号,所以还有待改进。

EMF+GEF里像这种别扭的地方还远不只这一处,不过我相信大部分都是可以适当修改一些代码解决的,希望它们之间增加一些合作,同时继续期待GMF

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

把GEF放在ViewPart里

其实GEF可以放在任何Composite上,当然也就可以放在视图里了。关键任务是创建GraphicalViewer、RootEditPart、EditDomain和EditPartFactory这些对象,下面的代码是我从别处拷来的,稍微修改了一下。

public class TestView extends ViewPart {
    ScrollingGraphicalViewer graphicalViewer;
    FigureCanvas canvas;
    Diagram diagram;

    public void createPartControl(Composite parent) {
        graphicalViewer = new ScrollingGraphicalViewer();
        canvas = (FigureCanvas) graphicalViewer.createControl(parent);
        ScalableFreeformRootEditPart root = new ScalableFreeformRootEditPart();
        graphicalViewer.setRootEditPart(root);
        graphicalViewer.setEditDomain(new EditDomain());
        graphicalViewer.setEditPartFactory(new PartFactory());
        graphicalViewer.setContents(diagram);
    }
}

运行结果如下,这个基本上只有视图的功能,也可以增加编辑功能,例如对GraphicalViewer加一个DropTargetListener就可以从调色板里拉对象上来了,等等。这个代码有点问题,就是打开View后要调整一下大小才能显示出图形,该怎么解决呢……

file

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

把第三方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

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