专注于互联网--专注于架构

最新标签
网站地图
文章索引
Rss订阅

首页 »编程综合 » anchor用法:GEF 进阶 第一部分: Anchor »正文

anchor用法:GEF 进阶 第一部分: Anchor

来源: 发布时间:星期六, 2009年12月19日 浏览:0次 评论:0
  GEF(Graphical Editing Framework)是Eclipse Tools子项目它在底层使用Draw2D作为布局和渲染引擎在整体上使用MVC模式管理模型和视图利用GEF开发者可以从应用模型开始迅速构造个可视化编辑环境正如其名字所说它只是个框架很多具体事情仍然要靠开发者完成但这也是GEF灵活方面只要你掌握了相关概念你就可以对个GEF应用进行充分定制本系列就是介绍GEF相关概念并在GEF些举例基础上演示如何定制、扩展自己GEF应用这是本系列主要介绍了Anchor(锚点)概念以及如何自定义个锚点并替代GEF缺省实现

Anchor(锚点)

  在个典型GEF我们通常会在画板上放上些图形然后用些线连接这些图形这些线两个端点就是Anchor(锚点)而锚点所在图形叫做锚点Owner更细化条线起点叫做Source Anchor(源锚点)终结点叫做Target Anchor(目标锚点)如图1中黑色小方块所示


图1. 源锚点和目标锚点


  不难看出锚点具体位置和两个图形位置以及连线方式有关这两个前提确定的后锚点可以通过思路方法计算得出对于图1情况两个图形的间连线是由两个图形中心点确定那么锚点计算思路方法就是找到这条中心线和图形边界交点Draw2D缺省为我们提供了些Anchor实现最常用大概是ChopboxAnchor它只是简单取两个图形中心线和图形边界交点做为锚点(正是图1 情况)对于简单应用来说ChopboxAnchor可以满足我们需要但是它锚点计算思路方法导致锚点在任何时候都是唯如果这两个图形的间存在多条连线它们会相互重叠使得看上去只有于是用户不可能用鼠标选择到被覆盖连线

  解决这个问题办法有两个:

  1. 提供个自定义Connection Router(连线路由器)以便能尽量避免线的间重合甚至也可以每条线都有区别Router

  2. 实现个自定义锚点可以让用户自己拖动锚点到区别位置避免线的间重合

  对于思路方法1我们在以后系列中会有介绍这里我们考虑思路方法2

Shapes Example

  GEFShapes举例是个很基础GEF用户可以在其上放置椭圆和长方形图形然后可以用两种样式线连接它们由于其使用了ChopboxAnchor它不支持在两个图形的间创建多条连线也不能移动锚点我们将在它基础上实现个可移动锚点

确定锚点表示策略

  设计自定义Anchor个问题是"我想把什么位置做为Anchor?"比如对于个矩形你可以选择图形中心或者 4条边中心或者边界上任何点在我们这个例子里我们希望是椭圆边界任何点因此我们可以考虑用角度来表示Anchor位置如图2所示:


图2. Anchor表示方式


  查看原图(大图)

  我们可以用个变量表示角度从而计算出中心射线和边界交点把这个交点作为图形锚点通过这样方式边界上点都可以成为锚点可以通过手工调整锚点避免连线重叠

第 2步修改Model

  为了表示锚点我们需要个表示角度变量这个变量应该放到模型中以便能够把锚点信息记录到文件中对于条来说它有两个锚点所以应该在连线对象中添加两个成员在Shapes例子中连线对象是org.eclipse.gef.examples.shapes.model.Connection, 我们修改它添加两个成员和相应Getter和Setter思路方法:


private double sourceAngle; 
private double targetAngle; 
 
public double getSourceAngle { 
  sourceAngle; 
} 
 
public void SourceAngle(double sourceAngle) { 
 this.sourceAngle = sourceAngle; 
} 
 
public double getTargetAngle { 
  targetAngle; 
} 
 
public void TargetAngle(double targetAngle) { 
 this.targetAngle = targetAngle; 
} 


  sourceAngle保存了源锚点角度targetAngle保存了目标锚点角度使用弧度表示

第 3步实现ConnectionAnchor接口

  锚点接口是由org.eclipse.draw2d.ConnectionAnchor定义我们需要实现这个接口但是般来说我们不用从头开始可以通过继承其它类来减少我们工作由于存在椭圆和长方形两种图形所以我们还需要实现两个子类最终我们定义了基础类BorderAnchor和 RectangleBorderAnchorEllipseBorderAnchor两个子类BorderAnchor代码如下:


package org.eclipse.gef.examples.shapes.anchor; 
 
import org.eclipse.draw2d.ChopboxAnchor; 
import org.eclipse.draw2d.IFigure; 
import org.eclipse.draw2d.geometry.Po; 
 
public abstract  BorderAnchor extends ChopboxAnchor { 
 protected double angle; 
 
 public BorderAnchor(IFigure figure) { 
 super(figure); 
 angle = Double.MAX_VALUE; 
 } 
 
 public abstract Po getBorderPo(Po reference); 
 
 public Po getLocation(Po reference) { 
 // 如果angle没有被使用缺省ChopboxAnchor否则计算个边界锚点 
 (angle  Double.MAX_VALUE) 
   super.getLocation(reference); 
  
   getBorderPo(reference); 
 } 
 
 public double getAngle { 
  angle; 
 } 
 
 public void Angle(double angle) { 
 this.angle = angle; 
 } 
} 


  重要是getLocation思路方法它有个参数"Po reference"个参考点在计算锚点时我们可以根据参考点来决定锚点位置对于ChopboxAnchor来说参考点就是另外个图形中心点BorderAnchor类有个angle成员保存了锚点角度它会被化为Double.MAX_VALUE所以我们判断 angle是否等于Double.MAX_VALUE如果是则BorderAnchor相当于个ChopboxAnchor如果否则个抽象思路方法getBorderPo来计算我们锚点BorderAnchor两个子类分别实现了计算椭圆和长方形锚点算法EllipseBorderAnchor代码如下所示:


package org.eclipse.gef.examples.shapes.anchor; 
 
import org.eclipse.draw2d.IFigure; 
import org.eclipse.draw2d.geometry.Po; 
import org.eclipse.draw2d.geometry.PrecisionPo; 
import org.eclipse.draw2d.geometry.Rectangle; 
 
public  EllipseBorderAnchor extends BorderAnchor { 
 public EllipseBorderAnchor(IFigure figure) { 
 super(figure); 
 } 
 
 @Override 
 public Po getBorderPo(Po reference) { 
 //得到owner矩形转换为绝对坐标 
 Rectangle r = Rectangle.SINGLETON; 
 r.Bounds(getOwner.getBounds); 
 getOwner.translateToAbsolute(r); 
  
 // 椭圆方程和直线方程解2元2次方程 
 double a = r.width >> 1; 
 double b = r.height >> 1; 
 double k = Math.tan(angle);  
 double dx = 0.0, dy = 0.0; 
  
 dx = Math.sqrt(1.0 / (1.0 / (a * a) + k * k / (b * b))); 
 (angle > Math.PI / 2 || angle < -Math.PI / 2) 
  dx = -dx; 
 dy = k * dx; 
  
 // 得到椭圆中心点加上锚点偏移得到最终锚点坐标 
 PrecisionPo pp =  PrecisionPo(r.getCenter); 
 pp.translate(()dx, ()dy); 
   Po(pp); 
 } 
} 


  值注意地方是我们可以通过 getOwner.getBounds来得到Owner边界矩形这是我们能够计算出锚点重要前提此外我们要注意是必须把坐标转换为绝对坐标这是通过getOwner.translateToAbsolute(r)来实现最后我们返回了锚点绝对坐标中间具体计算过程只不过是根据椭圆方程和射线方程求值而已在我们实现中并没有用到参考点如果你想有更多变数可以把参考点考虑进去

  同样RectangleBorderAnchor也是如此只不过求长方形边界点思路方法稍微不样而已我们就不解释了代码如下:


package org.eclipse.gef.examples.shapes.anchor; 
 
import org.eclipse.draw2d.IFigure; 
import org.eclipse.draw2d.geometry.Po; 
import org.eclipse.draw2d.geometry.PrecisionPo; 
import org.eclipse.draw2d.geometry.Rectangle; 
 
public  RectangleBorderAnchor extends BorderAnchor { 
 
 public RectangleBorderAnchor(IFigure figure) { 
 super(figure); 
 } 
 
 @Override 
 public Po getBorderPo(Po reference) { 
 // 得到owner矩形转换为绝对坐标 
 Rectangle r = Rectangle.SINGLETON; 
 r.Bounds(getOwner.getBounds); 
 getOwner.translateToAbsolute(r); 
  
 // 根据角度计算锚点相对于owner中心点偏移 
 double dx = 0.0, dy = 0.0; 
 double tan = Math.atan2(r.height, r.width); 
 (angle >= -tan && angle <= tan) { 
  dx = r.width >> 1; 
  dy = dx * Math.tan(angle); 
 }  (angle >= tan && angle <= Math.PI - tan) { 
  dy = r.height >> 1; 
  dx = dy / Math.tan(angle); 
 }  (angle <= -tan && angle >= tan - Math.PI) { 
  dy = -(r.height >> 1); 
  dx = dy / Math.tan(angle); 
 }  { 
  dx = -(r.width >> 1); 
  dy = dx * Math.tan(angle); 
 } 
 
 // 得到长方形中心点加上偏移得到最终锚点坐标 
 PrecisionPo pp =  PrecisionPo(r.getCenter); 
 pp.translate(()dx, ()dy); 
   Po(pp); 
 } 
} 


  这样我们就完成了自定义锚点实现在ConnectionAnchor接口中还有其他4个思路方法虽然我们没有用到但是有必要了解下它们:


void addAnchorListener(AnchorListener listener); 
void removeAnchorListener(AnchorListener listener); 
Po getReferencePo; 
IFigure getOwner; 


  addAnchorListener 和removeAnchorListener可以添加或删除个锚点监听器这样我们可以知道锚点何时发生了移动getOwner则是返回锚点Onwer图形显然我们可以指定另外个图形为锚点Owner虽然这种需求可能不太多而getReferencePo则是返回个参考点要注意这个参考点不是给自己用而是给另外个锚点用比如对于源锚点来说它会目标锚点getReferencePo 思路方法而对于目标锚点来说它会源锚点getReferencePo思路方法我们可以看看ChopboxAnchor getReferencePo实现它返回就是它Owner中心

第 4步修改EditPart

  锚点实现完成后我们需要修改ShapeEditPart使它能够使用我们定义锚点EditPart中 getSourceConnectionAnchor(ConnectionEditPart connection)和getTargetConnectionAnchor(ConnectionEditPart connection)是决定使用哪种锚点关键思路方法它们还有个重载版本用来处理Reconnect时锚点更新这 4个思路方法我们都需要修改同时为了减少对象创建次数我们可以在ConnectionEditPart里面添加两个成员用来保存源锚点对象和目标锚点对象如下:


/* In ConnectionEditPart.java */ 
 
private BorderAnchor sourceAnchor; 
private BorderAnchor targetAnchor; 
 
public BorderAnchor getSourceAnchor { 
  sourceAnchor; 
} 
 
public void SourceAnchor(BorderAnchor sourceAnchor) { 
 this.sourceAnchor = sourceAnchor; 
} 
 
public BorderAnchor getTargetAnchor { 
  targetAnchor; 
} 
 
public void TargetAnchor(BorderAnchor targetAnchor) { 
 this.targetAnchor = targetAnchor; 
} 


  这样在ShapeEditPart 中应该检查下ConnectionEditPart中成员是否有效如果有效则直接返回无效则创建个新锚点对象而Reconnect时代码稍微复杂我们需要根据鼠标当前位置重新计算angle鼠标当前位置是包含在ReconnectRequest里面我们给出 getSourceConnectionAnchor代码对于getTargetConnectionAnchor只要将Source换成 Target即可


/* In ShapeEditPart.java */ 
 
/* 
 * (non-Javadoc) 
 * @see org.eclipse.gef.NodeEditPart#getSourceConnectionAnchor 
 (org.eclipse.gef.ConnectionEditPart) 
 */ 
public ConnectionAnchor getSourceConnectionAnchor 
(ConnectionEditPart connection) { 
 org.eclipse.gef.examples.shapes.parts.ConnectionEditPart con = 
 (org.eclipse.gef.examples.shapes.parts.ConnectionEditPart)connection; 
 BorderAnchor anchor = con.getSourceAnchor; 
 (anchor  null || anchor.getOwner != getFigure) { 
 (getModel instanceof EllipticalShape) 
  anchor =  EllipseBorderAnchor(getFigure); 
  (getModel instanceof RectangularShape) 
  anchor =  RectangleBorderAnchor(getFigure); 
  
  throw  IllegalArgumentException("unexpected model"); 
  
 Connection conModel = (Connection)con.getModel; 
 anchor.Angle(conModel.getSourceAngle); 
 con.SourceAnchor(anchor); 
 } 
  anchor; 
} 
 
/* 
 * (non-Javadoc) 
 * @see org.eclipse.gef.NodeEditPart#getSourceConnectionAnchor 
 (org.eclipse.gef.Request) 
 */ 
public ConnectionAnchor getSourceConnectionAnchor(Request request) { 
 (request instanceof ReconnectRequest) { 
 ReconnectRequest r = (ReconnectRequest)request; 
 org.eclipse.gef.examples.shapes.parts.ConnectionEditPart con = 
 (org.eclipse.gef.examples.shapes.parts.ConnectionEditPart)r. 
 getConnectionEditPart; 
 Connection conModel = (Connection)con.getModel; 
 BorderAnchor anchor = con.getSourceAnchor; 
 GraphicalEditPart part = (GraphicalEditPart)r.getTarget; 
 (anchor  null || anchor.getOwner != part.getFigure) { 
  (getModel instanceof EllipticalShape) 
  anchor =  EllipseBorderAnchor(getFigure); 
   (getModel instanceof RectangleBorderAnchor) 
  anchor =  RectangleBorderAnchor(getFigure); 
   
  throw  IllegalArgumentException("unexpected model"); 
  
  anchor.Angle(conModel.getSourceAngle); 
  con.SourceAnchor(anchor); 
 } 
  
 Po loc = r.getLocation; 
 Rectangle rect = Rectangle.SINGLETON; 
 rect.Bounds(getFigure.getBounds); 
 getFigure.translateToAbsolute(rect); 
 Po ref = rect.getCenter; 
 double dx = loc.x - ref.x; 
 double dy = loc.y - ref.y; 
 anchor.Angle(Math.atan2(dy, dx)); 
 conModel.SourceAngle(anchor.getAngle); 
  anchor;  
 }  { 
 (getModel instanceof EllipticalShape) 
    EllipseBorderAnchor(getFigure); 
  (getModel instanceof RectangularShape) 
    RectangleBorderAnchor(getFigure); 
  
  throw  IllegalArgumentException("unexpected model"); 
 } 
} 




  到这里我们修改就完成了但是由于 Shapes举例不允许创建多条连线所以我们还需要把ConnectionCreateCommand和 ConnectionReconnectCommand中些代码注释掉这个内容就不做更多介绍了大家可以下载本文附带代码查看具体修改最终我们修改后Shapes可以创建多条连线并且可以手动调整它们锚点以避免重叠如图3所示:


图3. 新Shapes举例


结束语

  个灵活锚点实现对于复杂图形编辑来说是必须我们所要做仅仅只是实现ConnectionAnchor接口本文实现BorderAnchor是个通用锚点实现你可以随意应用到自己GEF或者在此基础上实现更为灵活锚点功能



标签:anchor用法
0

相关文章

读者评论

发表评论

  • 昵称:
  • 内容: