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个位置分别创建一个连线锚点,然后在两个连线锚点间创建一条连线。注意:连线的创建方式为拖动鼠标划线来创建,而不是原来的先单击一个模型,然后单击另外一个模型才能创建一条连线的方式。
结束。