[zt]GEF 进阶,第二部分: Router

FROM:http://www.ibm.com/developerworks/cn/opensource/os-ecl-gef/part2/index.html

2006 年 11 月 30 日

Router(连线路由器)是对连线进行布局的重要组件,本文介绍了路由器的基本概念和应用场景,剖析了一个连线路由器的接口并给出了一个简单实例。最后演示了如何把自定义的路由器应用到图形中,使得不同的连线可以有不同的路由器。

ConnectionRouter(连线路由器)

图形之间连线的路线,是由连线路由器来决定的。在Shapes Examples中,使用了最短路径路由器,这个路由器会帮我们绕开图形之间的障碍,选择一条最短路径进行连接,如图1所示:


图1. ShortestPathConnectionRouter效果图
 图1. ShortestPathConnectionRouter 效果图 

我们看到左右两边图形的连线绕过了中间的图形,在两处发生了转折。这就是使用了最短路径路由器的效果。连线路由器可以安装到Connection Layer(连接层,关于层的概念我们在本系列下一篇中讲述),也可以针对某一条连接,所以只要你愿意,每条连线都可以有不同的路由器。如果你没有为某条连线指定一个路由器,那么缺省会使用连接层的路由器。Draw2D自带了一些路由器的实现,除了图1的ShortestPathConnectionRouter,还有ManhattanConnectionRouter等路由器实现,如果这些自带的路由器不能满足我们的需要,我们所要做的就是实现ConnectionRouter接口,实现一个自定义路由器。ConnectionRouter接口并不复杂,如下所示:

java 代码
  1. Object getConstraint(Connection connection);    
  2. void setConstraint(Connection connection, Object constraint);    
  3. void invalidate(Connection connection);    
  4. void route(Connection connection);    
  5. void remove(Connection connection);    

setConstraint和getConstraint用来设置/得到连接上的Constraint(约束),所谓Constraint是指加在某个连线上的一些参数。我们可以看到constraint是一个Object类型,因为不同的路由器可能对constraint有不同的要求,对于ShortestPathConnectionRouter来说,constraint需要是一个List对象,里面包含了所有的转折点。

invalidate方法可以将一个连线置为无效,这样在下一次布局操作时,无效的连接将被重新路由。remove方法是将连线从路由器中删除,也就是路由器不会再负责这条连线的布局,一般只有在删除一条连线的时候才会调用到,我们可以在里面做一些清除工作,比如释放和连线相关的cache。route方法是路由操作真正发生的地方,我们一般只需要实现route方法就可以了,如果你还想做一些其他的操作,可以考虑实现其他方法。同样,一般是不推荐直接实现ConnectionRouter接口的,我们可以继承AbstractRouter类,这个类提供了一些简单的或者空的实现,还提供了两个额外的方法getStartPoint()和getEndPoint()方便我们得到连线的两个端点。

 SingleBendpointConnectionRouter

我们将实现一个自定义的路由器,叫做SingleBendpointConnectionRouter,它采用一种走直角的方式连接两个图形,如图2所示:
图2. SingleBendpointConnectionRouter效果图
 图2. SingleBendpointConnectionRouter效果图 

也许这种路由器并没有太多的通用性,但是我们只是作为一个例子演示路由器的实现,了解了基本方法之后,再去实现更复杂更实用的路由器也就大同小异了。

路由器实现的前提

显然我们无法凭空的计算出线路的走向,一条连线的具体路线和很多因素有关,比如锚点、图形的位置和大小,图形之间的相互关系,等等。所以我们需要能够访问到这些必须的信息,在Connection接口中,我们有getTargetAnchor()和getSourceAnchor()可以让我们得到锚点,而在ConnectionAnchor接口中(参见本系列第一部分),我们有getOwner()这样的方法,可以得到图形。这些必要的方法为我们实现路由器提供了可能。

实现route方法

route方法的代码如下:

java 代码
  1. public void route(Connection conn) {   
  2.   // 清空连线的所有点   
  3.   PointList points = conn.getPoints();   
  4.   points.removeAllPoints();   
  5.      
  6.   // 得到目标和源参考点   
  7.   Point sourceRef = conn.getSourceAnchor().getReferencePoint();   
  8.   Point targetRef = conn.getTargetAnchor().getReferencePoint();   
  9.   A_POINT.setLocation(sourceRef.x, targetRef.y);   
  10.      
  11.   // 得到起始点和结束点   
  12.   Point startPoint = conn.getSourceAnchor().getLocation(A_POINT);   
  13.   Point endPoint = conn.getTargetAnchor().getLocation(A_POINT);   
  14.      
  15.   // 添加起始点   
  16.   A_POINT.setLocation(startPoint);   
  17.   conn.translateToRelative(A_POINT);   
  18.   points.addPoint(A_POINT);   
  19.      
  20.   // 添加转折点   
  21.   A_POINT.setLocation(sourceRef.x, targetRef.y);   
  22.   conn.translateToRelative(A_POINT);   
  23.   points.addPoint(A_POINT);   
  24.      
  25.   // 添加结束点   
  26.   A_POINT.setLocation(endPoint);   
  27.   conn.translateToRelative(A_POINT);   
  28.   points.addPoint(A_POINT);   
  29.      
  30.   // 设置连线经过的所有点   
  31.   conn.setPoints(points);   
  32.  }   


一条连线实际上是通过一系列的点来描述的,而route方法的实际任务也就是计算出这些点的位置。所以我们一开始就得到了这条连线的点序列(PointList对象),然后清空它,重新计算这些点。在我们这个路由器的设计里,一条连线由三个点组成:分别是起始点,转折点和结束点,它们构成了两条垂直的直线。起始点和结束点(也就是锚点)我们都已经了解如何得到了,中间的转折点,也很容易得出,我们就不解释了。要指出的是,我们需要把它们的坐标转换为相对坐标再添加,同时在添加完成之后,我们还需要调用setPoints()方法,这样才会生效。

所以说实现一个路由器的过程是很简单的,复杂之处在于路由算法,但这已经不属于GEF的范畴,所以我们就不讨论它了。

改变连接层的路由器

我们只是实现了路由器,还没有把这个路由器设置为缺省的路由器,所以我们还要做一点小修改,在DiagramEditPart的createFigure()方法里,将ShortestPathConnectionRouter替换为SingleBendpointConnectionRouter即可。

待改进的地方

我们的自定义路由器很简单,但是它也有一点小问题,当两个图形在垂直或水平方向有重叠时,连线看上去有点不正常,如图3所示:
图3. 一点小bug
 图3. 一点小bug 

这只是由于我们的路由器算法不是很完善,没有考虑到所有情况而已。你可以尝试修改一下route的算法,改正这个问题,我们这里就不详细演示了。

 为连线指定路由器

我们目前是将路由器安装到了连接层,于是所有的连线都会使用同一个路由器,有些时候为了让布局更加灵活,我们需要为一条或多条连线指定一个不同的路由器。由于Connection接口中提供了setConnectionRouter()方法,因此这是可以实现的。

修改model

为了让连线知道它要使用何种路由器,我们需要修改连线的model,把当前的路由器种类存进去,我们在Connection.java里面加上一个routerId的成员,同时再定义一些表示不同路由器的常量:

java 代码
  1. // router id constant   
  2. public static final int SHORTEST_PATH_ROUTER = 0;   
  3. public static final int MANHATTAN_ROUTER = 1;   
  4. public static final int SINGLE_BENDPOINT_ROUTER = 2;   
  5.   
  6. private int routerId;   
  7.   
  8. public int getRouterId() {   
  9.  return routerId;   
  10. }   
  11.   
  12. public void setRouterId(int routerId) {   
  13.  this.routerId = routerId;   
  14. }   
  15.   


添加属性

为了能够随时修改连线的路由器,我们为连线添加一个router属性,由于这些内容不在本文讨论范围中,所以不一一描述了。完成之后,我们可以在属性视图中看到连线的路由器属性和可选值:


图4. Router属性
 图4. Router属性 

于是我们就可以让多种连线方式共存了,对于复杂的图形来说这样可以尽量避免连线重叠,增加布局的美观程度。


图5. 使用多种路由器
 图5. 使用多种路由器 

结束语

GEF中的几乎一切东西都可以定制,本文介绍的是连线路由器的定制,我们可以把这些内容和上一部分的自定义锚点结合起来,构建更为灵活的布局。不过,不要忘记了Draw2D自带的那些路由器,实在找不到合适的,可以考虑使用自定义的路由器。

下载

名字大小下载方法
org.eclipse.gef.examples.shapes_anchor.zip HTTP

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值