SWT的SelectionEvent.stateMask不起作用问题

通常理解stateMask的作用是标志事件发生时是否有Shift、Ctrl等键同时按下,但SWT里绝大多数Control都不支持这个标志,无论按下什么键,事件对象里的stateMask都是0(不信可以运行下面的代码)。更奇怪的是这个问题已经被提出3年多还没有解决,stateMask只对MenuItem有用,那要怎么实现对Button的Ctrl+Click检测呢?

import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; public class ButtonTest { public static void main(String[] args) {
  Display display = new Display();
  Shell shell = new Shell(display);
  shell.setLayout(new GridLayout());
  shell.setSize(200, 200); final Button button = new Button(shell, SWT.NONE);
  button.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) {
    System.out.println(e.stateMask);//Always zero
 }
  });
  button.setText("Test");

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

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2007/08/28/873677.html

Eclipse 3.3里新TreeViewer给EMF应用程序带来的一个问题

以前在Eclipse 3.2里做的一个EMF应用程序,到3.3里发现一些TreeViewer里标签显示的格式不对,例如原来显示“Condition true”的,在3.3下可能显示“1”。调试了一下发现这些TreeViewer在为每个节点获得标签时,若相应的XXXItemProvider实现了ITableItemLableProvider(即应用程序里有TableViewer也用到这个XXXItemProvider)时,会调用getColumnText()而不是getText()来得到文本内容。

在Eclipse新闻组里搜到这个帖子讲的是同一件事(新闻组是遇到问题后第一反应),原因不在EMF,是Eclipse 3.3里对TreeViewer的实现有了变化,新的实现把原来的TreeViewer当成只有一列的特殊的TableViewer来对待,以致EMF也把TreeViewer当成了TableViewer,当然会去找getColumnText()了。解决的办法也不复杂,新闻组里那个帖子也提到了,我给帖到这方便大家参考吧。

/**
 * @Added
 * Solve a problem raised in jface 3.3 tree viewer
 * @see http://dev.eclipse.org/newslists/news.eclipse.tools.emf/msg25409.html
 *
 */
class WorkaroundAdapterFactoryLabelProvider extends AdapterFactoryLabelProvider {
    /**
    * @param adapterFactory
    */
    public WorkaroundAdapterFactoryLabelProvider(AdapterFactory adapterFactory) {
        super(adapterFactory);
    }

    @Override
    public Image getColumnImage(Object object, int columnIndex) {
        return super.getImage(object);
    }

    @Override
    public String getColumnText(Object object, int columnIndex) {
        return super.getText(object);
    }
}

有了上面这个类,然后把原来setLabelProvider()里的AdapterFactoryLabelProvider换成它就可以了。EMF以后的版本应该会解决这个问题。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2007/08/21/864579.html

GMF里实现editpolicyProviders扩展点

通过org.eclipse.gmf.runtime.diagram.ui.editpolicyProviders扩展点可以给GMF应用里的editpart增加所需要的editpolicy,通过editpolicy返回的command,就可以自由的控制editpart的行为。顺便说一句,GMF Runtime之所以提供这许多扩展点,是为了允许通过其他插件工程对GMF生成的应用进行各种定制。

例如现在要对一个GMF程序里的名为_Book_的图形元素增加双击打开一个对话框,在对话框里修改模型属性的功能。最直接的办法是找到GMF生成的BookEditPart,覆盖performRequest()方法,判断request.getType()是否为RequestConstatns.REQ_OPEN,若是则打开一个对话框。但这样要求我们把代码写在GMF生成的插件项目里,如果考虑到有时要为第三方插件做扩展,我们无法修改它的代码的情况呢?

使用editpolicyProviders扩展点则无此限制,还是同样的需求,扩展点的方式步骤如下:

创建一个插件项目,并依赖生成的GMF应用和GMF相关依赖项(特别是org.eclipse.gmf.runtime.diagram.ui.providers),在plugin.xmlextensions里添加org.eclipse.gmf.runtime.diagram.ui.editpolicyProviders项,通过class属性指定一个Provider类。

<extension
       point="org.eclipse.gmf.runtime.diagram.ui.editpolicyProviders">
    <editpolicyProvider
          class="com.my.diagram.custom.MyEditPolicyProvider">
       <Priority
             name="Lowest">
       </Priority>
    </editpolicyProvider>
</extension>

这里定义的Provider要自己写,它应该实现IEditPolicyProvider接口,它的provide()方法根据传入的operation判断对相关的editpart是否需要创建editpolicy,若是则createEditPolicies()里用installEditPolicy()方法添加。下面的代码实现了双击打开对话框的需求:

public class MyEditPolicyProvider implements IEditPolicyProvider {

    public void createEditPolicies(EditPart editPart) {
        editPart.installEditPolicy(EditPolicyRoles.OPEN_ROLE, new OpenConditionEditPolicy());
    }

    public void addProviderChangeListener(IProviderChangeListener listener) {

    }

    public boolean provides(IOperation operation) {
        if (operation instanceof CreateEditPoliciesOperation) {
            EditPart editPart = ((CreateEditPoliciesOperation) operation).getEditPart();
            if (editPart instanceof ConditionEditPart)
                return true;
        }
        return false;
    }

    public void removeProviderChangeListener(IProviderChangeListener listener) {

    }

}

参考链接

http://dev.eclipse.org/newslists/news.eclipse.modeling.gmf/msg05684.html
http://dev.eclipse.org/newslists/news.eclipse.modeling.gmf/msg00931.html

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

GEF常见问题8:导出到图片

利用org.eclipse.draw2d.SWTGraphics类和org.eclipse.swt.graphics.ImageLoader类可以实现把画布导出到图片文件的功能,原理是在内存里创建一个空白的Image,然后把Diagram画到它上面,最后保存到指定文件和格式。

我们可以把导出工作分为两部分,第一部分负责提供要导出的IFigure实例(若要导出整个画布,应从GraphicalViewer获得PRINTABLE_LAYERS,否则会丢失画布上的连线),并负责将得到的图片数据写入文件;第二部分负责IFigure实例到图片数据的转换过程。以下是前者的示例代码:

String filename = ...;
PracticeEditor editor = ...;
ScalableFreeformRootEditPart rootPart = ...;
IFigure figure = rootPart.getLayer(ScalableFreeformRootEditPart.PRINTABLE_LAYERS);//To ensure every graphical element is included
byte[] data = createImage(figure, SWT.IMAGE_PNG);
try {
    FileOutputStream fos = new FileOutputStream(filename);
    fos.write(data);
    fos.close();
} catch (IOException e) {
    e.printStackTrace();
}

上面代码里调用的createImage()方法是实际在内存里作画并转换为可写入为文件的二进制流的地方,代码如下所示:

private byte[] createImage(IFigure figure, int format) {
    Rectangle r = figure.getBounds();
    ByteArrayOutputStream result = new ByteArrayOutputStream();
    Image image = null;
    GC gc = null;
    Graphics g = null;
    try {
        image = new Image(null, r.width, r.height);
        gc = new GC(image);
        g = new SWTGraphics(gc);
        g.translate(r.x * -1, r.y * -1);
        figure.paint(g);
        ImageLoader imageLoader = new ImageLoader();
        imageLoader.data = new ImageData[] { image.getImageData() };
        imageLoader.save(result, format);
    } finally {
        if (g != null) {
            g.dispose();
        }
        if (gc != null) {
            gc.dispose();
        }
        if (image != null) {
            image.dispose();
        }
    }
    return result.toByteArray();
}

点此下载工程,此工程修改自GEF应用实例中的GefPractice,目标文件的扩展名为.gefpractice不变。

file
图1 运行后增加了导出功能按钮

参考资料

GEF新闻组里相关链接:
http://dev.eclipse.org/newslists/news.eclipse.tools.gef/msg05012.html
http://dev.eclipse.org/newslists/news.eclipse.tools.gef/msg06329.html

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2007/08/01/838278.html

继承PageBookView实现自己的“属性视图”

很多Eclipse应用程序在提供一个Editor的同时还提供一些View,这些View监视Editor的Selection,提供一些上下文相关的信息。Eclipse自带的PropertySheet(属性视图)就是这样一个例子,Outline(大纲视图)也是如此,从功能上看,不妨把这类用途的视图称为“广义的属性视图”。

以前我都是直接继承ViewPart来实现自己的属性视图的,但我发现要花不少功夫在处理View与Editor的协调上。例如当Editor被关闭时,View里的信息也应该隐藏,切换Editor时也需要进行处理,等等。最近看一个项目的代码时发现他们是继承org.eclipse.ui.part.PageBookView来实现同样功能的,就像PropertySheet所做的一样,效果很不错,而且需要自己写的代码量不是很多,因为PageBookView里已经帮助处理了以上提到的大部分功能。因此这里介绍一下使用PageBookView的方法供大家参考。

顾名思义,PageBookView是这样一种View,它就像一本书,里面可以容纳多个Page,但同一时间只给用户显示一个Page。PageBookView负责根据当前活动的WorkbenchPart切换到合适的Page,实际显示的内容主要由Page提供,这些Page一般是通过WorkbenchPart的getAdapter()方法提供的。

继承PageBookView实现自己的属性视图时,PageBookView子类的实现几乎是固定的,如果你的View与WorkbenchPart里的Selection有关(上下文敏感),则继承PageBookView的时候应顺便实现ISelectionListener,然后在init()方法里注册自己为WorkbenchPage的选择监听器,并在selectionChanged()方法里把事件转发给当前显示的Page,下面的代码是一个典型的上下文敏感的PageBookView子类:

import org.eclipse.jface.viewers.ISelection;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.part.IPage;
import org.eclipse.ui.part.IPageBookViewPage;
import org.eclipse.ui.part.MessagePage;
import org.eclipse.ui.part.PageBook;
import org.eclipse.ui.part.PageBookView;

public class ExampleView extends PageBookView implements ISelectionListener{

    public ExampleView() {
    }

    @Override
    protected IPage createDefaultPage(PageBook book) {
        MessagePage page = new MessagePage();
        initPage(page);
        page.createControl(book);
        page.setMessage("An example view is not available.");
        return page;
    }

    @Override
    public void init(IViewSite site) throws PartInitException {
        super.init(site);
        site.getPage().addSelectionListener(this);
    }

    @Override
    public void dispose() {
        getSite().getPage().removeSelectionListener(this);
        super.dispose();
    }

    @Override
    protected PageRec doCreatePage(IWorkbenchPart part) {
        // Try to get a custom page
        Object obj = part.getAdapter(IExamplePage.class);
        if (obj instanceof IExamplePage) {
            IExamplePage page = (IExamplePage)obj;
            if (page instanceof IPageBookViewPage) 
                initPage((IPageBookViewPage)page);
            page.createControl(getPageBook());
            return new PageRec(part, page);
        }
        // Use the default page
        return null;
    }

    @Override
    protected void doDestroyPage(IWorkbenchPart part, PageRec pageRecord) {
        IExamplePage page = (IExamplePage) pageRecord.page;
        page.dispose();
        pageRecord.dispose();
    }

    @Override
    protected IWorkbenchPart getBootstrapPart() {
        IWorkbenchPage page = getSite().getPage();
        if (page != null)
            return page.getActiveEditor();
        else
            return null;
    }

    @Override
    protected boolean isImportant(IWorkbenchPart part) {
        return (part instanceof IEditorPart);
    }

    @Override
    public void createPartControl(Composite parent) {
        super.createPartControl(parent);
    }

    public void selectionChanged(IWorkbenchPart part, ISelection selection) {
        // we ignore our own selection or null selection
        if (part == this || selection == null) {
            return;
        }

        // pass the selection to the page
        if (!(getCurrentPage() instanceof IExamplePage))
            return;
        IExamplePage page = (IExamplePage) getCurrentPage();
        if (page != null) {
            page.selectionChanged(part, selection);
        }
    }
}

在上面这个View里,createDefaultPage()方法创建的Page是当活动WorkbenchPart不是我们所关心类型时所显示的信息,由MessagePage实现;isImportant()规定了这个View对什么样的WorkbenchPart感兴趣,在上面的例子里是只处理IEditorPart,作为对照,Eclipse的PropertySheet对所有类型的WorkbenchPart都有兴趣,除了它自己;如果活动WorkbenchPart是"Important"的,并且通过它的getAdapter()方法能得到我们需要的Page,则这个Page会显示在这个View里,否则还将显示缺省的MessagePage。

现在来看一下怎样实现自己的Page。一般要先定义一个继承自IPage的接口,它的一个作用是给WorkbenchPart#getAdapter()方法作为参数以标识我们需要的Page类型,如果前面PageBookView子类已经实现了ISelectionListener则它多半也要实现此接口,如下所示:

import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.part.IPage;

public interface IExamplePage extends IPage, ISelectionListener{

}

然后是具体的Page类,这个类其实和以前我们实现ViewPart的代码很相似,它继承自org.eclipse.ui.part.Page并实现刚才定义的接口,最重要的当然就是createControl()方法,如果需要还有selectionChanged()方法:

import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.part.Page;

/**
 * @author zhanghao
 *
 */
public class ExamplePage extends Page implements IExamplePage {

    Text txtName;

    public ExamplePage() {
        super();
    }

    @Override
    public void createControl(Composite parent) {
        //Create your autual view UI here
        txtName = new Text(parent, SWT.BORDER);
    }

    @Override
    public Control getControl() {
        return txtName;
    }

    @Override
    public void setFocus() {

    }

    public void selectionChanged(IWorkbenchPart part, ISelection selection) {
        String name = ; //Get information from selection
        txtName.setText(name);
    }
}

补充一点,每个Page具有独立的ActionBar,也就是说要为PageBookView添加菜单或工具条项应该在Page里完成,一般实现在init()方法里。

现在还差一个步骤没做,那就是要在WorkbenchPart的getAdapter()方法里对这个View做出反应(这要求WorkbenchPart可访问自定义的Page类),典型情况下我们用自己的Page接口作为参数,代码类似下面这样:

public Object getAdapter(Class type) {
    if (type == IExamplePage.class) {
        return new ExamplePage();
    }
    return super.getAdapter(type);
}

当然,和以前一样还是要在plugin.xml里注册这个View,以便使用者能够在Eclipse里通过Window->Show View菜单命令把它显示出来。

最后,和PropertySheet相比,这样的实现还有一个小缺陷,那就是第一次打开这个View的时候不能显示Editor里当前选择的信息,大家不妨试着解决这个问题。(提示:参考PropertySheet的partActivated()方法)

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2007/07/23/828850.html

[Eclipse]GEF入门系列(十二、自定义Request)

先简单回顾一下Request在GEF里的作用。Request是GEF里一个比较重要的角色,Tool将原始的鼠标事件转换为EditPart可以识别的请求,Request则承载了这些请求信息。举例来说,用户在调色板(Palette)里选择了创建节点工具(CreationTool),然后在画布区域按下鼠标左键,这时产生在画布上的鼠标单击事件将被CreationTool转换为一个CreateRequest,它里面包含了要创建的对象,坐标位置等信息。 EditPart上如果安装了能够处理CreateRequest的EditPolicy,则相应的EditPolicy会根据这个 CreateRequest创建一个Command,由后者实际执行创建新对象的必要操作。

GEF已经为我们提供了很多种类的Request,其中最常用的是CreateRequest及其子类 CreateConnectionRequest,其他比较常见的还有SelectionRequest,ChangeBoundsRequest和 ReconnectRequest等等。要实现一个典型的图形化应用程序,例如UML类图编辑器,这些预定义的Request基本够用了。然而各种稀奇古怪的需求我相信大家也见过不少,很多需求不太符合约定俗成的使用习惯,因此实现起来更多依赖开发人员的编码,而不是开发框架带来的便利。在这种时候,我们唯一的期望就是开发框架提供足够的扩展机制,以便让我们额外编写的代码能和其他代码和平共处,幸好GEF是具有足够的扩展性的。

再回到Request的问题上,为了说明什么情况下需要自定义Request,我在前文“应用实例”里的示例应用基础上假设一个新的需求:

在Palette里增加三个工具,作用分别是把选中节点的背景颜色改变为红色、绿色和蓝色。

假如你用过Photoshop或类似软件,这个需求很像给节点上色的“油漆桶”或“上色工具”,当然在用户界面的背后,实际应用里这些颜色可能代表一个节点的重要程度,优先级或是异常信息等等。现在,让我们通过创建一个自定义的Request来实现这个需求,还是以前文中的示例项目为基础。

一、首先,原来的模型里节点(Node)类里没有反映颜色的成员变量,所以先要在Node类里添加一个color属性,以及相应的 getter/setter方法,注意这个setter方法里要和其他成员变量的setter方法一样传递模型改变的消息。仿照其他成员变量,还应该有一个静态字符串变量,用来区分消息对应哪个属性。

final public static String PROP_COLOR = "COLOR";

protected RGB color = new RGB(255, 255, 255);

public RGB getColor() {
    return color;
}

public void setColor(RGB color) {
    if (this.color.equals(color)) {
        return;
    }
    this.color = color;
    firePropertyChange(PROP_COLOR, null, color);
}

二、然后,要让Node的color属性变化能够反映到图形上,因此要修改NodePart里的propertyChanged()和 refreshVisuals()方法,在前者里增加对color属性的响应,在后者里将NodeFigure的背景颜色设置为Node的color属性对应的颜色。(注意,Color对象是系统资源对象,实际使用里需要缓存以避免系统资源耗尽,为节约篇幅起见,示例代码直接new Color()了)

public void propertyChange(PropertyChangeEvent evt) {

    if (evt.getPropertyName().equals(Node.PROP_COLOR))//Response to color change
        refreshVisuals();
}

protected void refreshVisuals() {

    ((NodeFigure) this.getFigure()).setBackgroundColor(new Color(null, node.getColor()));//TODO cache color instances
}

三、现在来创建我们自己的Request,因为目的是改变颜色,所以不妨叫做ChangeColorRequest。它应当继承自org.eclipse.gef.Request,我们需要ChangeColorRequest上带有两样信息:1.需要改变颜色的节点;2.目标颜色。因此它应该有这两个成员变量。

import org.eclipse.gef.Request;
import org.eclipse.swt.graphics.RGB;
import com.example.model.Node;

public class ChangeColorRequest extends Request{
    final static public String REQ_CHANGE_COLOR="REQ_CHANGE_COLOR";
    private Node node;
    private RGB color;

    public ChangeColorRequest(Node node, RGB color) {
        super();
        this.color = color;
        this.node = node;
        setType(REQ_CHANGE_COLOR);
    }

    public RGB getColor() {
        return color;
    }

    public Node getNode() {
        return node;
    }

    public void setNode(Node node) {
        this.node = node;
    }

    public void setColor(RGB color) {
        this.color = color;
    }

}

ChangeColorRequest看起来和一个JavaBean差不多,的确如此,因为Request的作用就是传递翻译后的鼠标事件。如果你看一下org.eclipse.gef.Request的代码,你会发现Request还有一个type属性,这个属性一般是一个字符串(在gef的RequestConstants里预定义了一些,如RequestConstants.REQ_SELECTION_HOVER),EditPolicy可以根据它决定是否处理这个Request。在我们的例子里,顺便定义了这样一个常量字符串REQ_CHANGE_COLOR,在后面的 ChangeColorEditPolicy里会用到它。

四、现在有一个问题,这个Request的实例应该在哪里生成?答案是在Tool里,用户在画布区域按下鼠标左键时,当前 Palette里被选中的Tool负责创建一个Request。我们现在面对的这个需求需要我们创建一种新的Tool:ChangeColorTool。我们让ChangeColorTool继承org.eclipse.gef.tools.SelectionTool,因为“上色工具”的用法和“选择工具”基本上差不多。显然,我们需要覆盖的是handleButtonDown()方法,用来告诉gef如果用户当前选择了这个工具,在画布区域按下鼠标会发生什么事情。代码如下:

import org.eclipse.gef.EditPart;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.tools.SelectionTool;
import org.eclipse.swt.graphics.RGB;
import com.example.model.Node;
import com.example.parts.NodePart;

public class ChangeColorTool extends SelectionTool {
    private RGB color;

    public ChangeColorTool(RGB color) {
        super();
        this.color = color;
    }

    /**
     * If target editpart is an {@link NodePart}, create a {@link ChangeColorRequest} instance, 
     * get command from target editpart with this request and execute.
     */
    @Override
    protected boolean handleButtonDown(int button) {
        //Get selected editpart
        EditPart editPart = this.getTargetEditPart();

        if (editPart instanceof NodePart) {
            NodePart nodePart = (NodePart) editPart;
            Node node = (Node) nodePart.getModel();

            //Create an instance of ChangeColorRequest
            ChangeColorRequest request = new ChangeColorRequest(node, color);

            //Get command from the editpart
            Command command = editPart.getCommand(request);

            //Execute the command
            this.getDomain().getCommandStack().execute(command);

            return true;
        }
        return false;
    }

}

五、有了Tool,还需要用ToolEntry把它包装起来添加到Palette里。所以我们创建一个名为 ChangeColorToolEntry并继承org.eclipse.gef.palette.ToolEntry的类,覆盖createTool()方法,让它返回我们的ChangeColorTool实例。这个ChangeColorToolEntry代码应该很容易理解:

import org.eclipse.gef.SharedCursors;
import org.eclipse.gef.Tool;
import org.eclipse.gef.palette.ToolEntry;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.graphics.RGB;

public class ChangeColorToolEntry extends ToolEntry {
    private RGB color;

    public ChangeColorToolEntry(RGB color, String label, String shortDesc, ImageDescriptor iconSmall,
            ImageDescriptor iconLarge) {
        super(label, shortDesc, iconSmall, iconLarge);
        this.color = color;
    }

    @Override
    public Tool createTool() {
        ChangeColorTool tool = new ChangeColorTool(color);
        tool.setUnloadWhenFinished(false);//Switch to selection tool after performed?
        tool.setDefaultCursor(SharedCursors.CROSS);//Any cursor you like
        return tool;
    }

}

六、要把三个这样的ToolEntry添加到Palette里,当然是通过修改原来的PaletteFactory类。为节约篇幅,这里就不帖它的代码了,可以下载并参考示例代码PaletteFactory.java里的createCategories()createColorDrawer()方法。

到目前为止,ChangeColorRequest已经可以发出了,接下来要解决的问题是如何让EditPart处理这个请求。

七、我们知道,GEF里任何对模型的修改都是通过command完成的,因此定义一个ChangeColorCommand肯定是需要的。它的execute()方法和undo()方法如下所示:

public class ChangeColorCommand extends Command{

    private RGB oldColor;

    @Override
    public void execute() {
        oldColor = node.getColor();
        node.setColor(color);
    }

    @Override
    public void undo() {
        node.setColor(oldColor);
    }
}

八、EditPolicy负责接收所有的Request,所以还要创建一个ChangeColorEditPolicy。在下面列出的代码里,你会看到我们定义了一个新的Role字符串,过一会儿我们在EditPart上安装这个EditPolicy的时候要以这个字符串作为Key,以避免覆盖EditPart上已有的其他EditPolicy。

import org.eclipse.gef.Request;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.editpolicies.AbstractEditPolicy;
import org.eclipse.swt.graphics.RGB;

import com.example.model.Node;

public class ChangeColorEditPolicy extends AbstractEditPolicy {
    final static public String CHANGE_COLOR_ROLE = "CHANGE_COLOR_ROLE";

    @Override
    public Command getCommand(Request request) {
        //Judge whether this request is intersting by its type
        if (request.getType() == ChangeColorRequest.REQ_CHANGE_COLOR) {
            ChangeColorRequest theRequest = (ChangeColorRequest) request;

            //Get information from request
            Node node = theRequest.getNode();
            RGB color = theRequest.getColor();

            //Create corresponding command and return it
            ChangeColorCommand command = new ChangeColorCommand(node, color);
            return command;
        }
        return null;
    }
}

九、最后还是回到EditPart,前面在第二个步骤里我们曾经修改过的NodePart里还有最后一处需要添加,那就是在installEditPolicies()方法里添加刚刚创建的ChangeColorEditPolicy:

protected void createEditPolicies() {

    //Add change color editpolicy
    installEditPolicy(ChangeColorEditPolicy.CHANGE_COLOR_ROLE, new ChangeColorEditPolicy());
}

现在我们已经完成了所有必要的修改,来看一下运行结果。

file

总结一下,需要创建的类有:ChangeColorRequest, ChangeColorTool, ChangeColorToolEntry, ChangeColorCommand, ChangeColorEditPolicy;需要修改的类有:Node, NodePart, PaletteFactory。在实例项目里,为了方便大家浏览,所有新创建的类都放在com.example.request包里,实际项目里还是建议分别放在对应的包里。

下载示例代码(在eclipse3.2.1和gef3.2下编译通过)

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2007/06/21/792446.html

[SWT]动态生成WizardPage

Eclipse的Wizard是由一系列WizardPage组成的。缺省情况下,WizardDialog在初始化的时候就会调用每个WizardPage的createControl()方法来布局这些WizardPage,这是为了方便决定WizardDialog窗口的大小。但如果某个WizardPage里的控件是需要动态生成的,例如,用户在Page1里输入一个整数n,Page2里要根据这个整数生成n个文本框,由于Page2的createControl()只有一次被调用的机会,并且在得到n之前就被WizardDialog调用过了,集中布局的方式就为生成Page2的界面带来了困难。

和问题的描述比起来,解决的方法简单很多,只要覆盖Wizard的createPageControls()方法让它什么都不要做就可以了:

@Override
public void createPageControls(Composite pageContainer) {
    //super.createPageControls(pageContainer);
}

理论上讲,这样做带来的问题将是WizardDialog的大小不一定能容纳所有的控件,但在实际应用中我还没遇到,只要动态生成的控件不要太多,或者使用滚动的方式容纳即可。

参考资料

http://dev.eclipse.org/newslists/news.eclipse.tools/msg02641.html

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2007/06/07/775314.html

脱离Eclipse环境使用EMF

一旦熟练使用EMF后,有新项目我总是习惯于先使用工具构造出数据模型,然后让EMF帮我生成java代码。当模型需要修改时,也是用工具修改模型本身,然后让EMF把改动更新到java代码,从而保证模型与代码的同步。最近的一个基于struts的Web项目里我试验了脱离Eclipse运行环境使用EMF,发现比想象中的要更容易,以下是一些经验总结。

  1. EMF可以脱离Eclipse环境使用,所以不论你要做的项目是纯swt应用程序、swing应用程序、基于web的应用程序甚至没有GUI的应用程序,这些非Eclipse插件的项目也都可以利用EMF生成的模型代码。不过EMF生成的.edit和.editor部分就难以利用了,因为.edit主要为jface的各种viewer提供支持,.editor则是基于Eclipse的一个编辑器。

  2. 要脱离Eclipse独立使用EMF生成的代码,需要在项目里包含EMF的运行库,可以在EMF下载页面找到这个名为Standalone的下载项,下载以后要让里面的.jar文件都包含在项目classpath里。

  3. 除添加这些库文件以外,还有少量初始化工作需要在你的应用程序启动时执行。在EMF-FAQ页面专门介绍了初始化的方法,基本上要做的就是在程序启动时执行下面两条命令:

XXXPackage xxxPackage = XXXPackage.eINSTANCE;
Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("xxx", new XMIResourceFactoryImpl());

Update(2009-2-19): 如果模型文件包含了除emf以外的名称空间声明(例如要打开一个.emx文件),则需要对它们进行注册,下面的代码注册了gmf1.0的名称空间:

((Delegator)EPackage.Registry.INSTANCE).put("http://www.eclipse.org/gmf/runtime/1.0.0/notation", NotationPackage.eINSTANCE);
  1. 用EMF生成模型代码的步骤和平时基本没有区别,仍然是".ecore->.genmodel->java代码"。一般来说,直接在这个EMF项目里以EMF生成的java代码为基础继续开发就可以了,如果你用其他IDE则可以把这些java代码复制过去使用,只是注意一下每次修改ecore后要重复这个生成和复制代码的过程。

  2. 关于多个模型和跨模型引用的处理。EMF的一个好处是可以把整个项目的模型保存为多个文件,并且允许文件之间的相互依赖。例如一个在线商店系统里,在设计的时候可以把模型分为用户信息、商品信息和订单信息三大部分,显然订单信息依赖用户和商品信息,所以对用户和商品的管理可以独立进行开发,一方面提高了模块化程度,也增加了重用的可能性,例如在另外一个项目里可以只做很小的修改甚至不做修改的重用用户管理部分或是商品管理部分。

EMF是通过ResourceSet处理多个模型之间的引用的,以上面的在线商店为例,当从一个文件(一般是xmi格式)里载入一个订单实例时,EMF并不会去实际读取存储用户或商品信息的文件,而是使用代理(Proxy)的方式生成一个替代品,当程序里需要得到订单对应的用户的详细信息时(例如执行了theOrder.getCustomer().getAddress()方法),才会实际读取用户信息文件,并根据订单的用户id找到对应的用户信息。

因此,当你的项目里有多个模型并且存在相互引用的这种情况时,建议维护一个全局的ResourceSet实例,可以在程序启动时创建:

ResourceSet resourceSet = new ResourceSetImpl();

对于普通应用程序,可以用单例模式(Singleton)维护ResourceSet实例;若在web应用程序里,也可以利用application维护它。在程序里,每载入一个模型都要把它加到这个ResourceSet实例上。具体方法如下:

//get global ResourceSet instance
ResourceSet resourceSet = ;
//file to load
String filename = ;//e.g. "c:\\work\\test.xxx"
URI fileURI = URI.createFileURI(filename);
//create resource
XMIResource resource = (XMIResource)resourceSet.createResource(fileURI);
//load from file
resource.load(null);
//add resource to global ResourceSet instance
resourceSet.getResources().add(resource);
//get your model
XXXModel xxxModel = (XXXModel) resource.getContents().get(0); 
  1. 其他常用方法,以下仍以在线商店里的模型(见此帖)为例:

创建一个新产品(Product),通过ShopFacotory的createProduct()方法而不要直接new ProductImpl():

Product p = ShopFactory.eINSTANCE.createProduct();

Product p = (Product)ShopFactory.eINSTANCE.create(ShopPackage.Literals.Product);

把新创建的产品添加到商店(Shop):

Shop model = …;
Product p = …;
model.getProducts().add(p);

通过EMF反射机制得到Product类型的所有属性(非引用类型),任何一个EObject都可以通过这种方式得到其所有属性:

Product p = …;
EClass eclass = p.eClass(); 
for (Iterator iterator = eclass.getEAllAttributes().iterator(); iterator.hasNext();) {
  EAttribute attr = (EAttribute)iterator.next(); //属性,如Product#name
  Object value = p.eGet(attr); //属性值,如“walkman”
  … //do whatever with the value
}

通过EMF反射机制得到Category类型的所有引用,任何一个EObject都可以通过这种方式得到其所有引用:

Category category = …;
EClass eclass = category.eClass();
for (Iterator iterator = eclass.getEAllReferences().iterator(); iterator.hasNext();) {
  EReference ref = (EReference)iterator.next(); //引用,如Category#products
  Object value = category.eGet(ref); //引用值,如EList<Product>(一对多的情况)
  … //do whatever with the value
}

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2007/05/07/737869.html

GEF常见问题7:计算字符串在画布上占据的空间

要准确的计算文字在画布上占据的空间,可以利用org.eclipse.swt.graphics.GC的stringExtent()方法实现,见下面的代码:

GC gc = new GC(Display.getDefault());
gc.setFont(yourFont);//这一步不可缺少,因为有些字体里各字符的宽度是不同的
Point size = gc.stringExtent(text);//得到文字占据的尺寸
label.setPreferredSize(size.x + 16, size.y + 10);//让标签的尺寸比文字稍大
gc.dispose();

运行时的效果:

file

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2007/04/14/712794.html

为GMF应用程序设置背景图片

要为GMF应用程序的画布设置背景图片,可以覆盖XXXDiagramEditor的configureGraphicalViewer()方法,加入如下代码即可。不过加入背景图片后,网格线无法显示,有可能是图层顺序的问题。

/**
 * @generated NOT
 */
protected void configureGraphicalViewer() {
    super.configureGraphicalViewer();

    //////Background Layer//////////////////
    Layer backgroundLayer = new Layer() {
        @Override
        protected void paintFigure(Graphics graphics) {
            super.paintFigure(graphics);
            graphics.drawImage(NetworkDiagramEditorPlugin.getInstance().getBundledImage("images/worldmap_no_text.gif"), 0,
                    0);
        }
    };
    backgroundLayer.setSize(4990, 2484);
    ////////////////////////////////////////

    DiagramRootEditPart root = (DiagramRootEditPart) getDiagramGraphicalViewer().getRootEditPart();
    LayeredPane printableLayers = (LayeredPane) root.getLayer(LayerConstants.PRINTABLE_LAYERS);
    FreeformLayer extLabelsLayer = new FreeformLayer();
    extLabelsLayer.setLayoutManager(new DelegatingLayout());
    printableLayers.addLayerAfter(extLabelsLayer, NetworkEditPartFactory.EXTERNAL_NODE_LABELS_LAYER,
            LayerConstants.PRIMARY_LAYER);

    //////Insert Background Layer///////////
    printableLayers.addLayerBefore(backgroundLayer, NetworkEditPartFactory.EXTERNAL_NODE_LABELS_LAYER,
            LayerConstants.PRIMARY_LAYER);
    ////////////////////////////////////////

    LayeredPane scalableLayers = (LayeredPane) root.getLayer(LayerConstants.SCALABLE_LAYERS);
    FreeformLayer scaledFeedbackLayer = new FreeformLayer();
    scaledFeedbackLayer.setEnabled(false);
    scalableLayers.addLayerAfter(scaledFeedbackLayer, LayerConstants.SCALED_FEEDBACK_LAYER,
            DiagramRootEditPart.DECORATION_UNPRINTABLE_LAYER);
}

参考资料

为图形编辑器设置背景图片

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2007/03/13/673273.html