在GEF中实现悬空的连线

 

GEF(图形编辑框架)提供了连线的默认实现,但这种连线必须依赖于模型才能存在,即创建连线时,必须首先创建连线两端的模型,之后才能创建连线。该限制在建模过程中存在一定的不方便。而Visio中的连线可以独立存在,不必依赖于模型。如果项目开发中存在这种需要,该如何实现呢?本文针对这一问题进行解答。

首先说明一点,本文并不是从GEF原理剖析然后进行实现,由于本人也只是GEF的初学者,文章提供的方法不一定是最好的做法,只是在参与的项目中碰到并解决了这个问题,就此分享。(注意,代码中有些类名与和方法与具体项目有关)

解决这个问题的主要思想是,设计一种锚点模型,连线两端依附于2个锚点模型,锚点模型的大小应设置为(6,6)左右,不能太大。以这种方式来实现可以悬空的连线。也就是说,连线并不是真正悬空存在的,而是它2端的模型很小,从而达到连线悬空存在的效果。

以这种方式来设计和实现连线之后,会给原来的GEF框架许多默认的东西带来影响。比如1)连线和模型的连接,现在变成了锚点和模型连线;2)移动模型时连线跟随移动,这本来是GEF框架默认就实现的,现在需要我们自己来实现这种效果。这就需要模型上也具有锚点(由于这种锚点是依附在模型上的,在本文中称之为附属锚点),以实现和连线上的锚点的连接;等等等等还有其他一些问题。

一、锚点模型。

锚点模型的类名为AnchorModel,它是连线锚点和附属锚点的父类。关系如下图所示。

AnchorModel的实现如下。

 

package model.anchorModel;

import model.TopModel;

import org.eclipse.draw2d.geometry.Point;

/**
 * 该类是这2中锚点模型的父类。<br>
 * 总共有2中锚点模型:连线上的锚点模型、附着于其它状态、活动等上的锚点模型;
 */
public class AnchorModel extends TopModel {

	final public static String PROP_LOCATION = "LOCATION";
	final public static String PROP_VISIBLE = "VISIBLE";

	/** anchor的位置坐标 */
	protected Point location;
	/** 是否可见 */
	protected boolean visible;

	/**
	 * 构造函数
	 */
	public AnchorModel() {
		this.visible = true;
		this.location = new Point();
	}

	public boolean isVisible() {
		return visible;
	}

	public void setVisible(boolean visible) {
		if (this.visible == visible) {
			return;
		}
		this.visible = visible;
		firePropertyChange(PROP_VISIBLE, null, Boolean.valueOf(visible));
	}

	public void setLocation(Point p) {
		if (this.location.equals(p)) {
			return;
		}
		if (p.x < 0) {
			p.x = 0;
		}
		if (p.y < 0) {
			p.y = 0;
		}
		this.location = p;
		firePropertyChange(PROP_LOCATION, null, p);

	}

	public Point getLocation() {
		return location;
	}

}

 

 

二,连线锚点。

连线锚点取名为ConnecterAnchorModel,实现如下。

package model.anchorModel;

import java.util.ArrayList;
import java.util.List;

import model.DefaultSizeLocation;
import model.TopModel;
import model.stateChartModel.StateMachineSymbolModel;
import model.stateChartModel.StateNestSymbolModel;

import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;

/**
 * 该类是连线上的锚点模型。为便于交流,一律称呼其为“连线锚点”。
 * 
 * @author
 */
public class ConnecterAnchorModel extends AnchorModel {

	final public static String PROP_INPUTS = "INPUTS";
	final public static String PROP_OUTPUTS = "OUTPUTS";
	final public static String PROP_SIZE = "SIZE";

	/** 指向该连线锚点的朋友锚点(朋友的类型是附属锚点) */
	private SubAnchorModel friend;
	/** 连线锚点的大小 */
	private Dimension size;

	protected List<TopModel> outputs;
	protected List<TopModel> inputs;

	protected TopModel parent = null;

	/**
	 * 用于标示连线锚点类型的枚举值
	 * 
	 * @author Licanhui
	 * 
	 */
	public enum ConnecterAnchorType {
		TAIL, HEAD
	}

	public ConnecterAnchorModel() {
		this.outputs = new ArrayList<TopModel>();
		this.inputs = new ArrayList<TopModel>();
		this.friend = null;
		parent = new TopModel();
		size = DefaultSizeLocation.connecterAnchorSize;
	}

	/**
	 * 获取该连线锚点的类型,判断它是箭尾锚点还是箭头锚点。<br>
	 * 
	 * @return 枚举值ConnecterAnchorType<br>
	 *         箭尾锚点:{@link ConnecterAnchorType}.TAIL<br>
	 *         箭头锚点: ConnecterAnchorType.HEAD
	 */
	public ConnecterAnchorType getType() {
		if (outputs.size() != 0)
			return ConnecterAnchorType.TAIL;
		return ConnecterAnchorType.HEAD;
	}

	/**
	 * 获取该连线锚点的连线
	 * 
	 * @return TopModel
	 */
	public TopModel getConnecter() {
		if (outputs.size() != 0)
			return outputs.get(0);
		if (inputs.size() != 0)
			return inputs.get(0);
		return null;
	}

	@Override
	public void setVisible(boolean visible) {
		if (visible)
			super.setVisible(visible);
		else {
			// 当连线锚点存在朋友时,才能将它设置为不可见
			if (hasFriend())
				super.setVisible(visible);
		}
	}
	
	public void setVisibleForcely(boolean visible){
		if (this.visible == visible) {
			return;
		}
		this.visible = visible;
		firePropertyChange(PROP_VISIBLE, null, Boolean.valueOf(visible));
	}

	/**
	 * 转为为折叠操作设计的设置连线锚点可见性的方法。
	 * 
	 * @param visible
	 */
	public void setVisibleForCollapse(boolean visible) {
		if (!hasFriend())
			super.setVisible(visible);
	}

	/**
	 * 设置朋友锚点
	 */
	public void setFriend(SubAnchorModel subAnchorModel) {
		this.friend = subAnchorModel;
		this.setVisible(false);
	}

	public void deleteFriend() {
		this.friend = null;
		this.setVisible(true);
	}

	/**
	 * 获取连线锚点的大小
	 * 
	 * @return
	 */
	public Dimension getSize() {
		return size;
	}

	/**
	 * 设置连线锚点的大小
	 * 
	 * @param size
	 */
	public void setSize(Dimension size) {
		if (this.size == size)
			return;
		this.size = size;
		firePropertyChange(PROP_SIZE, null, size);
	}

	/**
	 * 获取朋友锚点
	 * 
	 * @return
	 */
	public SubAnchorModel getFriend() {
		return this.friend;
	}

	/**
	 * 判断一个锚点是否有与其粘连的锚点
	 * 
	 * @return
	 */
	public boolean hasFriend() {
		if (this.friend == null)
			return false;
		else
			return true;
	}

	public void addInput(TopModel connecter) {
		if(inputs.size() > 0){
			return;
		}
		this.inputs.add(connecter);
		fireStructureChange(PROP_INPUTS, connecter);
	}

	public void addOutput(TopModel connecter) {
		if(outputs.size()> 0){
			return;
		}
		this.outputs.add(connecter);
		fireStructureChange(PROP_OUTPUTS, connecter);
	}

	public void removeInput(TopModel connection) {
		this.inputs.remove(connection);
		fireStructureChange(PROP_INPUTS, connection);
	}

	public void removeOutput(TopModel connection) {
		this.outputs.remove(connection);
		fireStructureChange(PROP_OUTPUTS, connection);
	}

	/**
	 * 获取连线锚点的父亲
	 * 
	 * @return
	 */
	public TopModel getParent() {
		return parent;
	}

	/**
	 * 设置连线锚点的父亲
	 * 
	 * @param parent
	 */
	public void setParent(TopModel parent) {
		this.parent = parent;
	}

	/**
	 * !!!该方法只允许在连线锚点的控制器内使用。<br>
	 * 其余地方获取连线请使用方法getConnecter();
	 * 
	 * @return
	 */
	public List<TopModel> getOutgoingConnections() {
		return this.outputs;
	}

	/**
	 * !!!该方法只允许在连线锚点的控制器内使用。<br>
	 * 其余地方获取连线请使用方法getConnecter();
	 * 
	 * @return
	 */
	public List<TopModel> getIncomingConnections() {
		return this.inputs;
	}

	/**
	 * 设置坐标。如果连线的另一端悬空,同时将另一端移动相同的位移。具体的功能实现在子类中
	 */
	public void setLocationAssociatively(Point p) {
		if (this.location.equals(p)) {
			return;
		}
		this.location = p;
		firePropertyChange(PROP_LOCATION, null, p); 
	}
	
	/**
	 * 获取绝对坐标
	 * @return
	 */
	public Point getAbsLocation(){
		if(this.parent instanceof StateMachineSymbolModel){
			return this.location.getCopy();
		}
		else if(this.parent instanceof StateNestSymbolModel){
			Point nestLoc = ((StateNestSymbolModel) parent).getAbsoluteLocation();
			return new Point(nestLoc.x + this.location.x, nestLoc.y + this.location.y);
		}
		return this.location.getCopy();
	}
}


 


三、附属锚点。

附属锚点的类名为SubAnchorModel,实现如下。

package model.anchorModel;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import model.Length;
import model.TopModel;
import model.anchorModel.ConnecterAnchorModel.ConnecterAnchorType;
import model.hardwareView.ChannelModel;
import model.hardwareView.HardwareConnecterModel;
import model.hardwareView.HardwareModel;
import model.hardwareView.HardwareViewModel;
import model.hardwareView.ProcessorModel;
import model.programFlowChartModel.FlowChartComponentModel;
import model.programFlowChartModel.FlowChartConnecterModel;
import model.programFlowChartModel.FlowChartDecisionModel;
import model.programFlowChartModel.FlowChartModel;
import model.programFlowChartModel.FlowChartProcessModel;
import model.programFlowChartModel.FlowChartStartModel;
import model.programFlowChartModel.FlowChartTerminalModel;
import model.programFlowChartModel.FlowChartUserDefinedCodeModel;
import model.programFlowChartModel.ProgramFlowChartModel;
import model.stateChartModel.StateConnecterModel;
import model.stateChartModel.StateMachineSymbolModel;
import model.stateChartModel.StateNestSymbolModel;
import model.stateChartModel.StateStartSymbolModel;
import model.stateChartModel.StateSymbolModel;
import model.stateChartModel.StateTerminateSymbolModel;

import org.eclipse.draw2d.geometry.Point;

import tools.DragConnecterAndConnectorAnchorHelper;
import tools.ModellingHelper;
import tools.ResourceHelper;
import fileOrganization.FileConnecterModel;
import fileOrganization.FileDiagram;
import fileOrganization.FileModel;
import fileOrganization.PrefixModel;
import flowChartGenerate.RealTimeCheck;

/**
 * 附属锚点模型<br>
 * 该类是附着于其它模型上的锚点模型,为便于交流,我们一律称呼其为“附属锚点”。<br>
 * 它不能被拖动,只能跟随它所附着的模型来移动;<br>
 * SubAnchorModel名字中的Sub取自单词subordinate附属物,附属的;
 * 
 * @author 李灿辉
 */
public class SubAnchorModel extends AnchorModel {

	final public static String PROP_HIGHLIGHT = "HIGHLIGHT";

	/** 高亮显示 */
	private boolean highlight;

	/** 附属锚点的朋友链表 */
	private ArrayList<ConnecterAnchorModel> friendList;

	/** 指向附属锚点所依附的模型 */
	private TopModel owner = null;// = new TopModel();	
	

	/**
	 * 构造函数
	 */
	public SubAnchorModel() {
		this.setHighlight(false);
		this.friendList = new ArrayList<ConnecterAnchorModel>();
		// owner = null;
	}

	/**
	 * 添加一个朋友锚点
	 * 
	 * @param connecterAnchor
	 */
	public void addFriend(ConnecterAnchorModel connecterAnchor) {
		if (!this.friendList.contains(connecterAnchor)) {
			this.friendList.add(connecterAnchor);

			// 调整连线锚点的位置
			if (owner instanceof FlowChartModel) {// 如果是程序流程图
				FlowChartModel flowChartModel = (FlowChartModel) owner;
				int index = flowChartModel.getGraphicalProperty()
						.getSubAnchorList().indexOf(this);
				Point newP = ModellingHelper.getFixedLocation(location, index);
				connecterAnchor.setLocation(newP);
			}
		}
	}

	/**
	 * 获取附属锚点的朋友锚点列表
	 * 
	 * @return
	 */
	public ArrayList<ConnecterAnchorModel> getFriendList() {
		return this.friendList;
	}

	/**
	 * 删除一个朋友锚点
	 * 
	 * @param connectorAnchor
	 */
	public void removeFriend(ConnecterAnchorModel connectorAnchor) {
		if(friendList.contains(connectorAnchor))
			this.friendList.remove(connectorAnchor);
		// this.setHighlight(false);
	}

	/**
	 * 设置Owner
	 * 
	 * @param owner
	 */
	public void setOwner(TopModel owner) {
		this.owner = owner;
	}

	/**
	 * 获取Owner
	 * 
	 * @return
	 */
	public TopModel getOwner() {
		return this.owner;
	}

	/**
	 * 设置是否高亮显示
	 * 
	 * @param
	 */

	public void setHighlight(boolean highlight) {
		this.highlight = highlight;
		firePropertyChange(PROP_HIGHLIGHT, null, highlight);
	}

	/**
	 * 
	 * @return the highlight
	 */
	public boolean isHighlight() {
		return highlight;
	}

	/**
	 * 将自己设置为朋友锚点的朋友
	 */
	public void restoreFriendRelation() {
		for (ConnecterAnchorModel connAnchor : this.friendList) {
			connAnchor.setFriend(this);
		}
	}

	
	public Point getFigureLocation(){
		TopModel parent = null;
		Point figureLoc = location.getCopy();
		if(owner instanceof StateSymbolModel){
			parent = ((StateSymbolModel) owner).getGraphicalProperty().getParent();
		}
		if(parent instanceof StateNestSymbolModel){
			Point absoluteLoc = ((StateNestSymbolModel) parent).getAbsoluteLocation();
			figureLoc.x += absoluteLoc.x;
			figureLoc.y += absoluteLoc.y;
		}
		return figureLoc;
	}
	
	/**
	 * 获取附属锚点的绝对坐标
	 * @return
	 */
	public Point getAbsoluteLocation(){
		if(owner instanceof StateSymbolModel){
			StateSymbolModel state = (StateSymbolModel) owner;
			TopModel ownerParent = state.getGraphicalProperty().getParent();
			if(ownerParent instanceof StateNestSymbolModel){
				Point parentLoc = ((StateNestSymbolModel) ownerParent).getAbsoluteLocation();
				return new Point(parentLoc.x + this.location.x, parentLoc.y + this.location.y);
			}
			else if(ownerParent instanceof StateMachineSymbolModel){
				return this.location.getCopy();
			}
		}
		return this.location;
	}

	public SubAnchorModel getCopy(){
		SubAnchorModel newSubAnchor = new SubAnchorModel();
		newSubAnchor.setLocation(this.location.getCopy());
		newSubAnchor.setOwner(this.owner);
		newSubAnchor.setVisible(this.highlight);
		return newSubAnchor;
	}
	
}


 

四、实现附属锚点跟随模型一起移动的功能。

一般而言,每个模型提供4个附属锚点,分别位于模型的上下左右的位置,以提供连线连接模型的位置。如下图。

在model类中,需要保存模型的4个附属锚点,代码如下。

 

protected ArrayList<SubAnchorModel> subAnchorList;// 附属锚点的列表


在model的构造函数中,需要创建4个附属锚点对象,并加入到subAnchorList中。

 

 

 

 

 

 

 

 

在设置模型位置的方法中,除了设置模型的location之外,还应该设置它的4个附属锚点的坐标。

 

五、实现连线和模型的连接

实现连线和模型的连接,其实质是设置连线锚点和附属锚点的朋友关系。在XYLayoutEditPolicy类createChangeConstraintCommand()方法中,在修改连线锚点的模型约束时,遍历图中所有的附属锚点,将与当前移动的连线锚点的距离小于定长(比如10个像素)的附属锚点设置为连线锚点朋友,同时将当前移动的连线锚点加入到附属锚点的朋友列表中。另外,当连线锚点与原来的朋友(附属锚点)的距离大于定长(比如10个像素)后,应解除它们的朋友关系,即,将连线锚点的朋友锚点设置为null,将连线锚点从附属锚点的朋友列表中删除。

六、创建连线时应做的改动

对GEF的默认行为而言,当在PaletteFactory中生成创建连线的小工具时,需要用ConnectionCreationToolEntry。根据前面的叙述,此处的ConnectionCreationToolEntry应该换成CombinedTemplateCreationEntry,即普通的创建一个模型的小工具。它的SimpleFactory的参数应该为ConnecterAnchorModel.class。在选择该小工具创建模型时,会创建一个连线锚点。我们需要捕获鼠标左键点下和放开的位置坐标,在2个位置分别创建一个连线锚点,然后在两个连线锚点间创建一条连线。注意:连线的创建方式为拖动鼠标划线来创建,而不是原来的先单击一个模型,然后单击另外一个模型才能创建一条连线的方式。

点此下载相关实现代码。

结束。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值