本文是原創(chuàng)作品,如有轉(zhuǎn)載,請(qǐng)注明出處!
作GEF編輯器的人,不知道有沒(méi)有發(fā)現(xiàn)這樣一個(gè)問(wèn)題:每當(dāng)作一個(gè)新的編輯器的時(shí)候,有很多代碼都和以前的類似。
我發(fā)現(xiàn)了這個(gè)問(wèn)題,很多Command、Policy,包括EditPart都很類似,所以我經(jīng)常采用Copy&Paste然后修改的方法來(lái)加快開(kāi)發(fā)速度。
Eclipse采用的是插件擴(kuò)展機(jī)制,做一次擴(kuò)展就可以向Eclipse貢獻(xiàn)一個(gè)新的功能。同理,GEF編輯器中,畫(huà)板里的可編輯模型,是否也能這樣添加呢?
讓我們沿著這個(gè)思路走下去。
一 定義模型
初步的構(gòu)想,仍然是繼續(xù)前兩篇文章的足跡,更改原來(lái)的例子。
模型的定義:

最開(kāi)始做這個(gè)例子的時(shí)候,我將Node的Abstract設(shè)置為了true,因?yàn)槲蚁M褪且粋€(gè)抽象類,我將寫(xiě)RectangleNode和EllipseNode繼承Node,他們才會(huì)出現(xiàn)在編輯器里。但是后來(lái)發(fā)現(xiàn)那樣做的話,文件確實(shí)可以編輯,但是文件保存之后,再次用EMF序列化為對(duì)象的時(shí)候會(huì)出錯(cuò)。
什么?不信?那你自己試試。
這里,我給Node加了一個(gè)屬性instance,它表示這個(gè)對(duì)象的實(shí)現(xiàn)類。它將給我們代來(lái)一些麻煩。
二 創(chuàng)建模型工程
與前面的文章一樣,利用這個(gè)ecore文件創(chuàng)建一個(gè)EMF項(xiàng)目,并生成模型代碼。Ok,就把代碼放在這里,不再改動(dòng)它了。這個(gè)工程的名稱是nodenew。
三 創(chuàng)建編輯器工程
如果說(shuō)前面的工程僅創(chuàng)建了“抽象模型”,那么本工程就僅創(chuàng)建了基于前面模型的一個(gè)編輯器框架。
創(chuàng)建一個(gè)名為nodenew.gef的插件項(xiàng)目,其他的全部按默認(rèn)設(shè)置。首先,這個(gè)插件依賴于nodenew。將nodetest中的command、editpart、connectionhelp、properties、ui包copy過(guò)來(lái),將Connection.java和ModelManager.java也copy過(guò)來(lái),修改代碼,盡可能的減少那些紅叉。
創(chuàng)建一個(gè)擴(kuò)展點(diǎn),我們將利用它來(lái)初始化編輯器畫(huà)板。

這個(gè)擴(kuò)展點(diǎn)的用意很明確,就是貢獻(xiàn)模型并設(shè)置模型與EditPart的對(duì)應(yīng)關(guān)系。
創(chuàng)建一個(gè)接口類,所有使用這個(gè)擴(kuò)展點(diǎn)的EditPart需要實(shí)現(xiàn)這個(gè)接口:
MyEditPart.java

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

創(chuàng)建一個(gè)類,從擴(kuò)展點(diǎn)中找到模型與EditPart的對(duì)應(yīng)關(guān)系:
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;
}

}

創(chuàng)建一個(gè)類,從擴(kuò)展點(diǎn)中得到編輯器畫(huà)板里的元素。
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是自己重寫(xiě)的一個(gè)類,它的目的在于,當(dāng)用戶選中畫(huà)板中的一個(gè)對(duì)象的時(shí)候,發(fā)出了一個(gè)create的request,我們創(chuàng)建一個(gè)對(duì)象,封裝在這個(gè)request里,當(dāng)編輯器截獲這個(gè)request的時(shí)候,就直接得到了新創(chuàng)建的這個(gè)模型對(duì)象。
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);
}

}

在這里碰到了一個(gè)關(guān)于ClassLoader的問(wèn)題。假如不使用WorkbenchPlugin.createExtension(element, MyConstants.ATTR_MODELCLASS);
從當(dāng)前的插件項(xiàng)目中load這個(gè)class會(huì)出現(xiàn)ClassNotFound異常。目前的這個(gè)解決方法不是很好,正在尋找更好的解決方案。
接著,修改NodeEditorPaletteFactory,從擴(kuò)展點(diǎn)中初始化編輯器畫(huà)板:

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,有些對(duì)象需要從擴(kuò)展點(diǎn)中得到EditPart對(duì)象:

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

這個(gè)時(shí)候,編輯器的框架已經(jīng)搭建好了,運(yùn)行一下看看吧。

可以看到,畫(huà)板里的Connection是一直都有的,但是卻沒(méi)有Node對(duì)象。
四 創(chuàng)建自己的擴(kuò)展
再建一個(gè)新的插件項(xiàng)目,名為nodenew.my,它依賴于前面的2個(gè)插件。
首先創(chuàng)建模型對(duì)象:
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();
}
}

接著創(chuàng)建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());
}
}
}

}

聲明本次擴(kuò)展:
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>

看看結(jié)果吧

五 其他
本例仍然利用兩個(gè)action來(lái)打開(kāi)編輯器。
不知道這次的研究成果是不是可以發(fā)表成學(xué)術(shù)論文呢??我覺(jué)得挺有創(chuàng)意的,呵呵。
六 源碼
點(diǎn)擊下載
七 運(yùn)行環(huán)境
JDK 1.4
Eclipse 3.1
EMF
GEF