本文是原创作品,如有转载,请注明出处!
 
作GEF编辑器的人,不知道有没有发现这样一个问题:每当作一个新的编辑器的时候,有很多代码都和以前的类似。
我发现了这个问题,很多Command、Policy,包括EditPart都很类似,所以我经常采用Copy&Paste然后修改的方法来加快开发速度。
Eclipse采用的是插件扩展机制,做一次扩展就可以向Eclipse贡献一个新的功能。同理,GEF编辑器中,画板里的可编辑模型,是否也能这样添加呢?
让我们沿着这个思路走下去。
 
一 定义模型
初步的构想,仍然是继续前两篇文章的足迹,更改原来的例子。
 
模型的定义:

 
最开始做这个例子的时候,我将Node的Abstract设置为了true,因为我希望它就是一个抽象类,我将写RectangleNode和EllipseNode继承Node,他们才会出现在编辑器里。但是后来发现那样做的话,文件确实可以编辑,但是文件保存之后,再次用EMF序列化为对象的时候会出错。
什么?不信?那你自己试试。
这里,我给Node加了一个属性instance,它表示这个对象的实现类。它将给我们代来一些麻烦。
 
二 创建模型工程
 
与前面的文章一样,利用这个ecore文件创建一个EMF项目,并生成模型代码。Ok,就把代码放在这里,不再改动它了。这个工程的名称是nodenew。
 
三 创建编辑器工程
 
如果说前面的工程仅创建了“抽象模型”,那么本工程就仅创建了基于前面模型的一个编辑器框架。
创建一个名为nodenew.gef的插件项目,其他的全部按默认设置。首先,这个插件依赖于nodenew。将nodetest中的command、editpart、connectionhelp、properties、ui包copy过来,将Connection.java和ModelManager.java也copy过来,修改代码,尽可能的减少那些红叉。
创建一个扩展点,我们将利用它来初始化编辑器画板。

 
这个扩展点的用意很明确,就是贡献模型并设置模型与EditPart的对应关系。
 
创建一个接口类,所有使用这个扩展点的EditPart需要实现这个接口:
MyEditPart.java

public interface MyEditPart extends NodeEditPart
{
    public void setConnection(Object model);
    public void removeConnection(Object model);
}

 
 
创建一个类,从扩展点中找到模型与EditPart的对应关系:
 
ModelToEditPartMap.java

public class ModelToEditPartMap 
{
    
    private static Hashtable map = getTable();
    

    public static Hashtable getTable()
{

        if(map==null)
{  
            map=new Hashtable();
            IExtensionRegistry registry = Platform.getExtensionRegistry();
            IExtensionPoint point = registry.getExtensionPoint(MyConstants.FACTORY_EXTIONPOINT_ID);

            if (null == point)
{
                System.err.println("No extension point called "+MyConstants.FACTORY_EXTIONPOINT_ID+" is found!");
                return null;
            }
            IExtension[] extensions = point.getExtensions();

            for (int i = 0; i < extensions.length; i++)
{
                IConfigurationElement[] elements = extensions[i].getConfigurationElements();

                for (int j = 0; j < elements.length; j++)
{

                    if (elements[j].getAttribute(MyConstants.ATTR_TARGETEDITORID).equals(MyConstants.EDITOR_ID))
{
                        String eleType = elements[j].getName();

                        if (eleType.equals(MyConstants.ELEMENT_NODEPART))
{

                            try
{
                                map.put(elements[j].getAttribute(MyConstants.ATTR_MODELCLASS), elements[j]);
                                System.out.println("add a node " + elements[j].getAttribute(MyConstants.ATTR_NAME) + "to map");
                            }

                            catch(Exception e)
{
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
        return map;
    }

}

 
 
创建一个类,从扩展点中得到编辑器画板里的元素。
 
FactoryExtension.java

public class FactoryExtension 
{    
    private static ArrayList factory = getFactory();
    

    public static ArrayList getFactory() 
{

        if(factory==null)
{  
            factory=new ArrayList();
            IExtensionRegistry registry = Platform.getExtensionRegistry();
            IExtensionPoint point = registry.getExtensionPoint(MyConstants.FACTORY_EXTIONPOINT_ID);

            if (null == point)
{
                System.err.println("No extension point called "+MyConstants.FACTORY_EXTIONPOINT_ID+" is found!");
                return null;
            }
            IExtension[] extensions = point.getExtensions();

            for (int i = 0; i < extensions.length; i++)
{
                IConfigurationElement[] elements = extensions[i].getConfigurationElements();
                String pluginId = extensions[i].getNamespace();
                Plugin a = null;

                try 
{
                    a = extensions[i].getDeclaringPluginDescriptor().getPlugin();

                } catch (InvalidRegistryObjectException e1) 
{
                    // TODO Auto-generated catch block
                    e1.printStackTrace();

                } catch (CoreException e1) 
{
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }
                a.getBundle();
                
                
                

                for (int j = 0; j < elements.length; j++)
{

                    if (elements[j].getAttribute(MyConstants.ATTR_TARGETEDITORID).equals(MyConstants.EDITOR_ID))
{
                        String eleType = elements[j].getName();

                        if (eleType.equals(MyConstants.ELEMENT_NODEPART))
{

                            if(!Boolean.parseBoolean(elements[j].getAttribute(MyConstants.ATTR_VISIBLE)))
{
                                continue;
                            }

                            try
{
                                ImageDescriptor descriptor = null;

                                try
{
                                    if(elements[j].getAttribute(MyConstants.ATTR_ICON)!=null)
                                        descriptor = AbstractUIPlugin.imageDescriptorFromPlugin(pluginId, elements[j].getAttribute(MyConstants.ATTR_ICON));

                                } catch(Exception e)
{
                                    e.printStackTrace();
                                }

                                if(descriptor == null)
{
                                    descriptor = GefPlugin.getImageDescriptor(MyConstants.IMG_DEFAULT);
                                }
                                
                                ToolEntry tool = new CombinedTemplateCreationEntry(
                                        elements[j].getAttribute(MyConstants.ATTR_NAME), 
                                        "Create a new Node", 
                                        elements[j].getAttribute(MyConstants.ATTR_MODELCLASS), 
                                        new MyCreationFactory(elements[j]), descriptor,descriptor);

                                factory.add(tool);
                                System.out.println("add a node " + elements[j].getAttribute(MyConstants.ATTR_NAME));
                            }

                            catch(Exception e)
{
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
        return factory;
    }
    
    
}

 
 
上面的类中使用的CreationFactory是自己重写的一个类,它的目的在于,当用户选中画板中的一个对象的时候,发出了一个create的request,我们创建一个对象,封装在这个request里,当编辑器截获这个request的时候,就直接得到了新创建的这个模型对象。
 
MyCreationFactory.java

public class MyCreationFactory implements CreationFactory
{
    
    private IConfigurationElement element;
    

    public MyCreationFactory(IConfigurationElement element)
{
        this.element = element;
    }
    

    public Object getNewObject() 
{ 
                

        try 
{
            Object classname=null; 
            

            try 
{
                classname = WorkbenchPlugin.createExtension(element, MyConstants.ATTR_MODELCLASS);

            } catch (CoreException e) 
{
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            
            if(classname instanceof Class)
                System.out.println("class");
            
            return classname;
//            

        } catch (InvalidRegistryObjectException e) 
{
            // TODO Auto-generated catch block
            e.printStackTrace();
        } 
        return null;
    }

    public Object getObjectType() 
{        
        return element.getAttribute(MyConstants.ATTR_MODELCLASS); 
    }

}

 
 
在这里碰到了一个关于ClassLoader的问题。假如不使用WorkbenchPlugin.createExtension(element, MyConstants.ATTR_MODELCLASS);
从当前的插件项目中load这个class会出现ClassNotFound异常。目前的这个解决方法不是很好,正在寻找更好的解决方案。
 
接着,修改NodeEditorPaletteFactory,从扩展点中初始化编辑器画板:

private static PaletteContainer createShapesDrawer() 
{
    PaletteDrawer componentsDrawer = new PaletteDrawer("Shapes");
    CombinedTemplateCreationEntry component=null;
    ArrayList factory = FactoryExtension.getFactory();

    if(factory!=null && factory.size()>1)
{

        for(int i=0; i<factory.size(); i++)
{
            component = (CombinedTemplateCreationEntry) factory.get(i);
            componentsDrawer.add(component);
        }
    }
    return componentsDrawer;
}

 
 
最后,修改NodesEditPartFactory,有些对象需要从扩展点中得到EditPart对象:

private EditPart getPartForElement(Object modelElement) 
{

        if (modelElement instanceof Diagram) 
{
            return new DiagramEditPart();
        }

        if (modelElement instanceof Connection) 
{
            return new ConnectionEditPart();
        }
        
        EditPart result = null;

        if(modelElement instanceof Node)
{
            
            String classname = ((Node)modelElement).getInstance();
            IConfigurationElement element = (IConfigurationElement)ModelToEditPartMap.getTable().get(classname);

            try 
{                
                result = (EditPart) WorkbenchPlugin.createExtension(element, MyConstants.ATTR_PARTCLASS);

            } catch (CoreException e) 
{
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            
        }
        

        if(result==null)
{
        

            if(ModelToEditPartMap.getTable().containsKey(modelElement.getClass().getName()))
{

                try 
{
                    IConfigurationElement element = (IConfigurationElement)ModelToEditPartMap.getTable().get(modelElement.getClass().getName());
                
                    result = (EditPart) WorkbenchPlugin.createExtension(element, MyConstants.ATTR_PARTCLASS);


                }catch (CoreException e) 
{
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
        

        if(result!=null && result instanceof MyEditPart)
{
            ((MyEditPart)result).removeConnection(modelElement);
            ((MyEditPart)result).setConnection(modelElement);            
            return result;
        }
        
        throw new RuntimeException(
                "Can't create part for model element: "
                + ((modelElement != null) ? modelElement.getClass().getName() : "null"));
    }

 
 
这个时候,编辑器的框架已经搭建好了,运行一下看看吧。

 
可以看到,画板里的Connection是一直都有的,但是却没有Node对象。
 
四 创建自己的扩展
 
再建一个新的插件项目,名为nodenew.my,它依赖于前面的2个插件。
 
首先创建模型对象:
 
EllipseNode.java

public class EllipseNode extends NodeImpl
{

    public EllipseNode()
{
        super();
        this.instance = this.getClass().getName();
    }
}

 
RectangleNode.java

public class RectangleNode extends NodeImpl
{

    public RectangleNode()
{
        super();
        this.instance = this.getClass().getName();
    }
}

 
 
接着创建EditPart:
 
EllipseNodeEditPart.java

public class EllipseNodeEditPart extends NodesEditPart implements MyEditPart
{

    protected IFigure createFigure() 
{
        IFigure f = new Ellipse();
        
        f.setOpaque(true); // non-transparent figure
        f.setBackgroundColor(ColorConstants.green);
        return f;
    }
    

    public void removeConnection(Object model)
{

        if(TargetAddConnectionTable.getInstance().contains((Node) model))
{
            List l = TargetAddConnectionTable.getInstance().getValue( (Node)model);

            if(l!=null && l.size()>0)
{

                for(int i=0; i<l.size(); i++)
{
                    Connection c = (Connection) l.get(i);
                    this.getModelSourceConnections().add(c);
                    TargetAddConnectionTable.getInstance().remove(c);
                }
            }
        }
    }    
    

    public void setConnection(Object model)
{

        if(((Node)model).getNext()!=null && ((Node)model).getNext().size()>0)
{

            for(int i=0; i<((Node)model).getNext().size(); i++)
{
                Connection c = new Connection();
                c.setSource((Node) model);
                c.setTarget((Node) ((Node)model).getNext().get(i));
                this.getModelTargetConnections().add(c);
                TargetAddConnectionTable.getInstance().add(c, (Node) c.getTarget());
            }
        }
    }

}

 
 
RectangleNodeEditPart.java

public class RectangleNodeEditPart extends NodesEditPart implements MyEditPart
{

    protected IFigure createFigure() 
{
        IFigure f = new RectangleFigure();
        f.setOpaque(true); // non-transparent figure
        f.setBackgroundColor(ColorConstants.green);
        return f;
    }
    

    public void removeConnection(Object model)
{

        if(TargetAddConnectionTable.getInstance().contains((Node) model))
{
            List l = TargetAddConnectionTable.getInstance().getValue( (Node)model);

            if(l!=null && l.size()>0)
{

                for(int i=0; i<l.size(); i++)
{
                    Connection c = (Connection) l.get(i);
                    this.getModelSourceConnections().add(c);
                    TargetAddConnectionTable.getInstance().remove(c);
                }
            }
        }
    }    
    
    

    public void setConnection(Object model)
{

        if(((Node)model).getNext()!=null && ((Node)model).getNext().size()>0)
{

            for(int i=0; i<((Node)model).getNext().size(); i++)
{
                Connection c = new Connection();
                c.setSource((Node) model);
                c.setTarget((Node) ((Node)model).getNext().get(i));
                this.getModelTargetConnections().add(c);
                TargetAddConnectionTable.getInstance().add(c, (Node) c.getTarget());
            }
        }
    }

}

 
 
声明本次扩展:
 
plugin.xml
<extension
         point="nodenew.gef.editpartfactory">
      <nodepart
            modelclass="nodenew.model.EllipseNode"
            name="nodenew.my.nodepart1"
            partclass="nodenew.editpart.EllipseNodeEditPart"
            targetEditorId="nodenew.ui.NodesEditor"
            visible="true"/>
      <nodepart
            modelclass="nodenew.model.RectangleNode"
            name="nodenew.my.nodepart1"
            partclass="nodenew.editpart.RectangleNodeEditPart"
            targetEditorId="nodenew.ui.NodesEditor"
            visible="true"/>
   </extension>

 
 
看看结果吧

 
五 其他
 
本例仍然利用两个action来打开编辑器。
 
不知道这次的研究成果是不是可以发表成学术论文呢??我觉得挺有创意的,呵呵。
 
六 源码
 
点击下载
 
七 运行环境
 
JDK 1.4
Eclipse 3.1
EMF
GEF