其实这一章我们介绍gef的核心控制器了,在写之前,我们先介绍一下GEF要用到的设计模式.
在学GEF中给我感受最深的是,设计模式无处不在,除了上章中说的观察者模式外.GEF框架还运用了COMMAND(命令)模式, Policy(策略)模式.这两个模式,策略模式,貌似本团对有人介绍过了,而command模式是我现研究的(也许会单独写个随笔,谈谈心得).
好了,我们言归正传.先说一下,GEF控制器的实现原理.
控制器是由一组EditPart对象共同组成的,每一个模型对象都对应一个EditPart对象。应用程序中需要有一个EditPartFactory对象负责根据给定模型对象创建对应的EditPart对象,这个工厂类将在创建模型时被调用。
控制器是GEF中最复杂的一部分,GEF把控制器完成的工作又分成了几个部分,包括请求和编辑策略及引申出来的命令模式,如下图:
对每一个EditPart,用户都可以“安装”一些EditPolicy。用户对这个EditPart的特定操作会被交给已安装的对应EditPolicy处理。这样做的直接好处是可以在不同EditPart之间共享一些重复操作。
这样说有点抽象,还是用代码说话,接着上回那个DEMO来所,该是editpart部分了.
对于模型的每个独立部分,我们都必须定义控制器。所谓“独立”,指的是这个实体 都可以作为用户操作的对象。一个比较好的原则就是任何可以被选择,或删除的对象 都应该有它自己的编辑部件(来自于IBM)。
所有的部件均实现了createFigure()(返回模型在视图中的图形表示), createEditPolicies()(安装相应的策略),refreshVisuals() (刷新视图), propertyChange()(接受模型改变并执行不同的操作,刷新视图) ,activate()(自 己加入设为监听器) 和 deactivate()(将自己从监听器的列表中移除)五个方法。
所以由上回的模型,我们就可以建立控制器了,首先我们将他也抽象化:
现建基类SpecificPart
import hya.gef.demo.shapes.models.ElementModel;
import java.beans.PropertyChangeListener;
import org.eclipse.gef.editparts.AbstractGraphicalEditPart;
/** */ /**
* editpart基类
* 主要为注册自己为模型改变的监听者
* @author hya
* */
public abstract class SpecificPart extends AbstractGraphicalEditPart
implements PropertyChangeListener {
/** *//**
* 将自己注册为模型的属性修改事件的接收者
*/
public void activate() {
if (!isActive()) {
super.activate();
((ElementModel) getModel()).addPropertyChangeListener(this);
}
}
/** *//**
* 将自己从监听器的列表中移除
*/
public void deactivate(){
super.deactivate();
((ElementModel)getModel()).removePropertyChangeListener(this);
}
}
//连接锚点
private ConnectionAnchor anchor;
/** *//**
*@Override
* 建立视图
* */
protected IFigure createFigure() {
IFigure f = createFigureByGetModel();
f.setOpaque(true); // non-transparent figure
f.setBackgroundColor(ColorConstants.red);
return f;
}
/** *//**根据得到的模型实例,画出不同的图形*/
private IFigure createFigureByGetModel() {
if (getModel() instanceof RadioModel) {
return new Ellipse();//圆形
} else if (getModel() instanceof RectangularModel) {
return new RectangleFigure();//矩形
} else if (getModel() instanceof TriangleModel) {
return new Triangle();//三角形
} else {
throw new IllegalArgumentException();
}
}
/** *//**安置策略*/
protected void createEditPolicies() {
//安装删除策略
installEditPolicy(EditPolicy.COMPONENT_ROLE, new ShapeComponentEditPolicy());
//安装建立,更改连接策略
installEditPolicy(EditPolicy.GRAPHICAL_NODE_ROLE, new ShapeConnectionEditPolicy());
}
/** *//**
* @Override
* 刷新视图
* */
protected void refreshVisuals() {
Rectangle bounds = new Rectangle(((ShapeModel) getModel()).getLocation(),
((ShapeModel) getModel()).getSize());
((GraphicalEditPart) getParent()).setLayoutConstraint(this, getFigure(), bounds);
}
/** *//**
*接受修改事件,刷新视图
* */
public void propertyChange(PropertyChangeEvent arg0) {
String p = arg0.getPropertyName();
if (ShapeModel.SIZE_PROP.equals(p) || ShapeModel.LOCATION_PROP.equals(p)) {
refreshVisuals();
}else if (ShapeModel.SOURCE_CONNECTIONS_PROP.equals(p)) {
refreshSourceConnections();
} else if (ShapeModel.TARGET_CONNECTIONS_PROP.equals(p)) {
refreshTargetConnections();
}
}
//------------------------------------------------------------------------
//---
//连接设置
//得到相应图形的锚点
protected ConnectionAnchor getConnectionAnchor() {
if (anchor == null) {
if (getModel() instanceof RadioModel)
anchor = new EllipseAnchor(getFigure());
else if (getModel() instanceof RectangularModel)
anchor = new ChopboxAnchor(getFigure());
else if(getModel()instanceof TriangleModel)
anchor = new ChopboxAnchor(getFigure());
else
throw new IllegalArgumentException("unexpected");
}
return anchor;
}
//得到以模型作为源的连接列表
protected List getModelSourceConnections() {
return ((ShapeModel) getModel()).getSourceConnections();
}
//得到以模型作为目标的连接列表
protected List getModelTargetConnections() {
return ((ShapeModel) getModel()).getTargetConnections();
}
//当需要画连接的时候,获取连接的源锚点
public ConnectionAnchor getSourceConnectionAnchor(ConnectionEditPart connection) {
return getConnectionAnchor();
}
//准备创建连接的时候,通过Request来获取新连接的源锚点
public ConnectionAnchor getSourceConnectionAnchor(Request request) {
return getConnectionAnchor();
}
//获取连接的目标锚点
public ConnectionAnchor getTargetConnectionAnchor(ConnectionEditPart connection) {
return getConnectionAnchor();
}
public ConnectionAnchor getTargetConnectionAnchor(Request request) {
return getConnectionAnchor();
}
}
由于图形之间是可以连接的所以我们还同时让其实 NodeEditPart 接口。 通过实现这个接口,编辑部件可以定义源锚点和目标锚点,锚点就是图形和连接接触的连接点 。重写了getModelSourceConnections方法和getModelTargetConnections方法。这两个方法的任务就是要通知GEF有关该图形的源连接和目标连接
createFigure()方法,根据返回的对象不同,返回 不同的图形实例(矩形,三角形和圆形)。主要通过方法createFigureByGetModel()来判断。
createEditPolicies()安装了两个策略,ShapeComponentEditPolicy提供命 令将一个 图形从图删除。第二个策略处理图形间连接的创建和转移,它的索引字是 GRAPHICAL_NODE_ROLE。
其他的两个模型对应的part也差不多
DiagamEditPart
import hya.gef.demo.shapes.models.DiagramModel;
import hya.gef.demo.shapes.parts.policy.ShapesXYEditPolicy;
import java.beans.PropertyChangeEvent;
import java.util.List;
import org.eclipse.draw2d.ConnectionLayer;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.FreeformLayer;
import org.eclipse.draw2d.FreeformLayout;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.MarginBorder;
import org.eclipse.draw2d.ShortestPathConnectionRouter;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gef.LayerConstants;
import org.eclipse.gef.editpolicies.RootComponentEditPolicy;
public class DiagamEditPart extends SpecificPart {
/** *//**
*@Override
* 建立视图
* 建立容器,设置布局管理器
* */
protected IFigure createFigure() {
Figure f = new FreeformLayer();
f.setBorder(new MarginBorder(3));
//FreeformLayout布局管理器,这是一种XY型的布局管理器
f.setLayoutManager(new FreeformLayout());
// Create the static router for the connection layer
ConnectionLayer connLayer = (ConnectionLayer)getLayer(LayerConstants.CONNECTION_LAYER);
connLayer.setConnectionRouter(new ShortestPathConnectionRouter(f));
return f;
}
/** *//**
* 得到子对象列表
* */
protected List getModelChildren() {
return ((DiagramModel) getModel()).getChildren(); // return a list of shapes
}
protected void createEditPolicies() {
//阻止模型的根被删除,它重写了createDeleteCommand方法,并返回一个不能被执行的命令
installEditPolicy(EditPolicy.COMPONENT_ROLE, new RootComponentEditPolicy());
//安装添加和更改元素策略
installEditPolicy(EditPolicy.LAYOUT_ROLE, new ShapesXYEditPolicy());
}
/** *//**模型改变时,刷新视图*/
public void propertyChange(PropertyChangeEvent arg0) {
String p = arg0.getPropertyName();
if (DiagramModel.CHILD_ADDED_PROP.equals(p)
|| DiagramModel.CHILD_REMOVED_PROP.equals(p)) {
refreshChildren();
}
}
}
package hya.gef.demo.shapes.parts;
import hya.gef.demo.shapes.commands.ConnectionDeleteCommand;
import hya.gef.demo.shapes.models.Connection;
import org.eclipse.draw2d.BendpointConnectionRouter;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.PolygonDecoration;
import org.eclipse.draw2d.PolylineConnection;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.editparts.AbstractConnectionEditPart;
import org.eclipse.gef.editpolicies.ConnectionEditPolicy;
import org.eclipse.gef.editpolicies.ConnectionEndpointEditPolicy;
import org.eclipse.gef.requests.GroupRequest;
class ConnectionEditPart extends AbstractConnectionEditPart {
protected void createEditPolicies() {
//安装与连接有关的策略
installEditPolicy(EditPolicy.CONNECTION_ENDPOINTS_ROLE,
new ConnectionEndpointEditPolicy());
installEditPolicy(EditPolicy.CONNECTION_ROLE, new ConnectionEditPolicy() {
protected Command getDeleteCommand(GroupRequest request) {
return new ConnectionDeleteCommand(getCastedModel());
}
});
}
protected IFigure createFigure() {
PolylineConnection conn = new PolylineConnection();
conn.setTargetDecoration(new PolygonDecoration());
conn.setConnectionRouter(new BendpointConnectionRouter());
return conn;
}
private Connection getCastedModel() {
return (Connection) getModel();
}
}
连接编辑部件,它继承自AbstractConnectionEditPart类。createFigure()方法返回了一个带箭头的连接线。
他安装了两个策略第一个是ConnectionComponentPolicy,它提供删除命令给Delete菜单项 所需要的action。第二个策略提供了一种视觉上的选择反馈,即当一个连接被拖动时,GEF 没有办法获取连接两端的标识.
那么GEF是怎么知道模型与PART之间的对应关系的呢,所以应该建立模型与part之间的映射,这个我们用到了工厂模式
建立part工厂:ShapesEditPartFactory
package hya.gef.demo.shapes.parts;
import hya.gef.demo.shapes.models.Connection;
import hya.gef.demo.shapes.models.DiagramModel;
import hya.gef.demo.shapes.models.ShapeModel;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPartFactory;
/** */ /**
* 控制器工厂
* @author hya
* */
public class ShapesEditPartFactory implements EditPartFactory {
public EditPart createEditPart(EditPart context, Object modelElement) {
EditPart part = getPartForElement(modelElement);
part.setModel(modelElement);
return part;
}
/** *//**建立模型与EditPart的映射*/
private EditPart getPartForElement(Object modelElement) {
if (modelElement instanceof DiagramModel) {
return new DiagamEditPart();
}
if (modelElement instanceof ShapeModel) {
return new ShapeEditPart();
}
if (modelElement instanceof Connection) {
return new ConnectionEditPart();
}
throw new RuntimeException(
"Can't create part for model element: "
+ ((modelElement != null) ? modelElement.getClass().getName() : "null"));
}
}
这是,核心的part就建立完了.我们可以看到他们当中的方几乎一样,狭义回我们将加入策略和命令部分,并更加详细的通过代码来说明GEF的工作过程.