原文链接:http://www.ibm.com/developerworks/cn/opensource/os-cn-zest/#ibm-pcon

此文为原本备份。

 

文章摘要

本文介绍了如何使用 Eclipse Visualization Toolkit(http://www.eclipse.org/gef/zest/)这个短小精悍的 Java 图形工具库来进行简单的工作流程图形化的应用开发。通过程序示例由浅入深,让读者在短时间内掌握 Zest 图形库的基本概念和实用开发技巧,学习本文,相信读者可以使用 Eclipse Visualization Toolkit 图形库为 Eclipse 或单独的程序开发出更加精彩和 UML 建模 ( 类图,对象关系图等 ),工作流程图形化表现相关的应用范例。

使用 Zest 开发应用程序,建议读者首先了解基本的 SWT(http://www.eclipse.org/swt/) 开发知识,在 DeveloperWorks 的网站上有许多和 SWT/JFace 相关的文章和教程,感兴趣的读者可以查找阅读。Zest的开发 SDK 可以从网站 (http://www.eclipse.org/gef/zest/) 下载。Zest SDK 需要依赖于 Draw2D,Zest 的网站上有整合所有内容的完整开发包供下载。Draw2D 主要作用是在 SWT 的画布 (Canvas) 上提供了一种轻量级呈现和布局管理功能。在开发环境中设置好所依赖的程序库 (org.eclipse.draw2d_Version.jar,org.eclipse.zest.core_Version.jar,org.eclipse.zest.layouts_Version.jar 和 swt.jar),就可以进行 Zest 程序的代码编写和编译开发了。



回页首

Zest 项目概述

Zest 库简介和优势

Zest(The Eclipse Visualization Toolkit) 是在 Eclipse 平台基础上开发的一套可视化图形构件集合,方便开发和 UML 相关的图形应用程序,但范围不限于 UML 相关的应用,也可以用来开发工作流程图形化建模,树状结构图等。本文的示例代码都是以开发简单工作流程图形建模为例子。

Zest 库是从 SWT 和 Draw2D 扩展开发而来,可以无缝的集成到 Eclipse 的应用当中。因为 Zest 是基于 SWT(JFace) 的,所以 Zest 遵循 Eclipse 平台视图 (View) 的相关标准和规范,可以很容易在开发 Eclipse 的各种视图应用当中被集成和扩展。

虽然 Eclipse 的图形编辑框架 (GEF, http://www.eclipse.org/gef/) 也能够开发出丰富的图形应用,但是基于 GEF 的应用程序无法脱离 Eclipse 平台而单独运行;而基于 Zest 的应用没有这个限制,可以作为独立的应用程序在存在,从而脱离庞大的 Eclipse 平台,让应用程序更加小巧和灵活。

Zest 的组件类型

Zest 库提供了如下几种最基本的组件。

  • 图形节点 (GraphNode):最基本的包含某些特性的节点图形,例如颜色,大小,位置和标签等。
  • 图形关联 (GraphConnections):存储关联两个节点之间关联关系的图形对象,也包含连线的一些属性信息,例如:连线的颜色,线条宽度等。
  • 图形容器 (GraphContainer):图形容器和图形节点类似,包含图形节点的所有属性,但图形容器支持折叠和展开的行为特性。
  • 图形 (Graph):一个容器,用来容纳图形节点,图形容器以及图形关联这些对象。
  • 样式常量 (ZestStyles):Zest 库默认设置的一些系统常量,例如线形等 ( 实线,虚线 ...)

Zest 的布局

Zest 库也提供了布局管理器,通过布局管理器来决定图形当中的节点,关联等这些图形对象如何在屏幕上显示分布。

表 1. Zest 布局管理器

布局管理器名称

描述

CompositeLayoutAlgorithm

组合其他布局方法来设置图形显示

DirectedGraphLayoutAlgorithm

以全部重叠的方式来设置图形显示

GridLayoutAlgorithm

以网格的布局方式来设置图形显示

HorizontalLayoutAlgorithm

以水平方式来设置图形显示

HorizontalShift

以重叠的方式依次向右水平来设置图形显示

HorizontalTreeLayoutAlgorithm

以水平树状方式来设置图形显示

RadialLayoutAlgorithm

以放射状的布局来设置图形显示

SpringLayoutAlgorithm

以相同关联长度,图形节点重叠最少来设置图形显示

TreeLayoutAlgorithm

以垂直树状方式来设置图形显示

VerticalLayoutAlgorithm

以垂直方式来设置图形的显示

 

用户也可以开发自定义的布局管理器。



 



回页首

Zest 基础组件开发

学习了 Zest 库当中的一些基本概念,就容易理解创建各种图形对象的类和方法,将这些图形对象通过合适的逻辑关系建立联系,就可以开发出一个简单的图形显示程序。

图形节点 (GraphNode) 的创建和属性

通常使用如下的构造函数来创建一个图形节点对象,需要传入 3 个参数。

GraphNode(IContainer graphModel, int style, String text)

各个参数的含义分别是:

设置图像节点对象所在的图形 (Graph) 对象,就是设置在哪个对象上面显示。

设置图形节点的风格,就是节点要显示成为什么样子。

设置图形节点上的标签,就是节点上要显示什么文字内容。

图形节点对象当中包含一系列的 setXX() 方法,通过这些方法,可以设置节点的大写,位置,颜色,字体等等。

图 1. 设置图形节点对象属性的方法

 

节点关联 (GraphConnection) 的创建和设置

创建节点关联对象只有一个构造函数,需要传入 4 个参数。

GraphConnection(Graph graphModel, int style, GraphNode source, GraphNode destination)

各个参数的含义分别是:

设置节点关联对象所在的图形 (Graph) 对象,就是设置在哪个对象上面显示。

设置节点关联的风格,就是节点关联要显示成为什么样子。

设置关联的源节点对象,从哪个节点开始。

设置关联的目标节点对象,到哪个节点结束。

节点关联对象的属性设置方法如下所示:

图 2. 设置节点关联对象属性的方法

 

第一个 Zest 程序

知道了如何创建节点对象和节点关系对象,就可以轻松创建出第一个基于 Zest 的程序。一个最简单的工作流程图形化建模程序,包含一个开始 (Start) 节点,一个结束 (End) 节点和两者之间的一条连线。

清单 1. 第一个 Zest 程序代码

import org.eclipse.zest.core.widgets.Graph; 
 import org.eclipse.zest.core.widgets.GraphConnection; 
 import org.eclipse.zest.core.widgets.GraphNode; 
 import org.eclipse.zest.layouts.LayoutStyles; 
 import org.eclipse.zest.layouts.algorithms.SpringLayoutAlgorithm; 
 import org.eclipse.swt.SWT; 
 import org.eclipse.swt.layout.FillLayout; 
 import org.eclipse.swt.widgets.Display; 
 import org.eclipse.swt.widgets.Shell; 

 public class FirstZest { 

       public static void main(String[] args) { 
              // SWT 


图 3. 第一个 Zest 程序

 

通过简单的几行代码,就创建了 2 个图形节点,并在它们之间建立了关联。你会发现,任何一个图形节点可以随意的通过鼠标拖动,而且连线也会随着节点的移动而移动。把很多复杂的功能的实现进行了封装和屏蔽。代码简单,但是功能强大。

也很容易的通过设置图形节点和节点关联的风格,来改变显示的样子。

清单 2. 设置图形节点和节点关联显示风格示例代码

 Graph graph = new Graph(shell, SWT.NONE); 
 // 设置连线风格
 graph.setConnectionStyle(ZestStyles.CONNECTIONS_DIRECTED); 
 // 设置图像
 Image startIcon = new Image(display,"StartIcon.png"); 
 // 创建图形节点
 GraphNode startNode = new GraphNode(graph, SWT.NONE, "Start", startIcon); 

 

显示的效果如下图所示:

图 4. 显示风格示例

 

为了美化这个最简单的工作流程图形化建模程序,给连线增加了箭头,给图形节点增加了图标。

 


回页首

Zest 事件驱动开发

Zest 库当中组件的行为也是通过事件的方式进行驱动的。用户可以通过键盘,鼠标来操作 Zest 的图形组件,通过触发相应的事件来完成用户期望的业务行为。Zest 库的事件模型和编程方式和 SWT 是完全相同的。

元素的事件驱动

对于图形节点 (GraphNode) 和图形关联 (GraphConnection) 对象可以通过调用下面的方法

addListener (int eventType, Listener listener)

为对象注册相应的事件,当这个事件发生的时候,就会触发相应的操作。具体的用法和接口参数可以参考 SWT 的文档。

对于图形 (Graph) 对象 Zest 提供了更多和更便利的事件使用注册方法。

图 5. Graph 对象提供的事件注册方法

 

下面通过代码来演示一下,在图形当中,当某个对象被选中的事件发生的时候,如何来触发特定的逻辑。

事件注册和触发示例

当某个对象被选中的时候,通过调用 addSelectionListener() 方法来注册事件。

清单 3. Graph 对象注册事件代码示例

// 创建 Graph 
 Graph graph = new Graph(shell, SWT.NONE); 
 // 注册对象选择侦听事件
 graph.addSelectionListener(new SelectionAdapter() { 
       @Override 
       public void widgetSelected(SelectionEvent e) { 
              List selection = ((Graph) e.widget).getSelection(); 
              // 确认只选择了一个对象
              if (selection.size() == 1) { 
                     Object o = selection.get(0); 
                     // 图形节点对象
                     if (o instanceof GraphNode) { 
                            // 改变边线宽度
                            ((GraphNode) o).setBorderWidth(3); 

 

工作流程图形化建模程序的运行效果进行美化,当图形节点或者图形关联被选中的时候,边线会被自动加粗。

图 6. Graph 注册事件运行结果

 

 


回页首

Zest 布局管理开发

Zest 库已经提供了多种图形显示的布局管理方式,需要用户根据图形应用的具体的情况来选择合适的布局管理算法。如果在程序当中没有设置任何布局管理,那么所有的图形节点和图形关联对象都将重合在一起。

常用布局管理的示例

下面分别给出了 Zest 当中常用布局管理的显示效果。

清单 4. 常用布局管理代码示例

Graph graph = new Graph(shell, SWT.NONE); 
 graph.setConnectionStyle(ZestStyles.CONNECTIONS_DIRECTED); 
 for (int i = 0; i < 10; i++) { 
    GraphNode node1 = new GraphNode(graph, ZestStyles.NODES_FISHEYE, 
            "Begin"); 
    GraphNode node2 = new GraphNode(graph, ZestStyles.NODES_FISHEYE, 
            "Middle"); 
    GraphNode node3 = new GraphNode(graph, ZestStyles.NODES_FISHEYE, 
            "Finish"); 
    new GraphConnection(graph, SWT.NONE, node1, node2); 
    new GraphConnection(graph, SWT.NONE, node2, node3); 
 } 
 graph.setLayoutAlgorithm(new GridLayoutAlgorithm( 
        LayoutStyles.NO_LAYOUT_NODE_RESIZING), true); 
 /* 

 

布局显示的效果依次为 Gridlayout, SpringLayout, Radialayout, TreeLayout 和 DirectedFraphLayout。

图 7. 不同布局管理器的显示效果对比

 

如何开发自定义的布局管理

除了系统内置的布局管理器,Zest 也支持自定义的布局管理器。要实现自定义的布局管理器需要继承一个抽象类 AbstractLayoutAlgorithm。这个抽象类当中,要实现 7 个抽象方法,它们的名称和作用分别是:

void setLayoutArea()

设置布局的区域

boolean isValidConfiguration()

判断对于当前的布局配置是否正确

void applyLayoutInternal(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider, double boundsX, double boundsY, double boundsWidth, double boundsHeight)

把给定的对象进行布局,需要布局的对象会按照设定的算法被重新排列。这是最核心的一个方法。各个参数的含义分别是:

InternalNode[] entitiesToLayout:需要重新布局的对象数组

InternalRelationship[] relationshipsToConsider:重新布局对象之间关系的数组

double boundsX:布局区域可以放置对象的横 (X) 坐标开始位置

double boundsY:布局区域可以放置对象的纵 (Y) 坐标开始位置

double boundsWidth:布局区域的宽度

double boundsHeight:布局区域的高度

void preLayoutAlgorithm()

新布局算法之前调用的方法

postLayoutAlgorithm()

新布局算法之后调用的方法

getTotalNumberOfLayoutSteps()

获取布局当中总的步骤

int getCurrentLayoutStep()

获取当前的布局步骤

下面演示一个自定义的布局算法,让所有图形节点按照水平方式以相同间隔依次排列。

清单 4. 自定义布局算法代码示例

Graph graph = new Graph(shell, SWT.NONE);  graph.setConnectionStyle(ZestStyles.CONNECTIONS_DIRECTED);  GraphNode node1 = new GraphNode(graph, SWT.NONE, "Node 1");  GraphNode node2 = new GraphNode(graph, SWT.NONE, "Node 2");  GraphNode node3 = new GraphNode(graph, SWT.NONE, "Node 3");  new GraphConnection(graph, SWT.NONE, node1, node2);  new GraphConnection(graph, SWT.NONE, node2, node3);  graph.setLayoutAlgorithm(new AbstractLayoutAlgorithm(SWT.NONE) {     int totalNodes;     protected void applyLayoutInternal(InternalNode[] entitiesToLayout,             InternalRelationship[] relationshipsToConsider,             double boundsX, double boundsY, double boundsWidth,             double boundsHeight) {         // 需要布局的对象数量        totalNodes = entitiesToLayout.length;         // 设置固定间隔距离        double spcaing = 100;         // 开始位置坐标        int startX = 10;         // 触发布局进程        fireProgressStarted(totalNodes);         // 循环所有对象,按照设定的布局算法来排列对象        for (int curNode = 0; curNode < entitiesToLayout.length; curNode++) {             LayoutEntity layoutEntity = entitiesToLayout[curNode]                     .getLayoutEntity();             // 设置一个对象显示的位置            layoutEntity.setLocationInLayout(startX, layoutEntity                     .getYInLayout()+10);             // 保持相同的间隔            startX += spcaing;             // 让对象在新位置上显示            fireProgressEvent(curNode, totalNodes);         }         // 结束布局进程        fireProgressEnded(totalNodes);     }     protected int getCurrentLayoutStep() {         return 0;     }     protected int getTotalNumberOfLayoutSteps() {         return totalNodes;     }     protected boolean isValidConfiguration(boolean asynchronous,             boolean continuous) {         return true;  }     protected void postLayoutAlgorithm(InternalNode[] entitiesToLayout,             InternalRelationship[] relationshipsToConsider) {     }     protected void preLayoutAlgorithm(InternalNode[] entitiesToLayout,             InternalRelationship[] relationshipsToConsider, double x,             double y, double width, double height) {     }     public void setLayoutArea(double x, double y, double width,             double height) {     }  }, true);

 


 


回页首

Zest 开发小技巧

下面是一些 Zest 开发当中小技巧的代码示例。

如何更改图形节点的默认形状

在 Zest 当中,图形节点的默认形状是矩形的,其实可以根据需求,来更改节点的默认形状,可变成为圆形,方形或者菱形等等。下面的示例是将图形节点的形状显示为椭圆。

需要创建用户自定义的图形节点,一般来说需要继承 CGraphNode 这个类

清单 5. 自定义图形节点 EllipseGraphNode 代码示例

import org.eclipse.draw2d.IFigure;  import org.eclipse.zest.core.widgets.CGraphNode;  import org.eclipse.zest.core.widgets.IContainer;  public class EllipseGraphNode extends CGraphNode {     public EllipseGraphNode(IContainer graphModel, int style, IFigure figure) {        super(graphModel, style, figure);     }  }

 

同时也需要继承 Figure 这个类

清单 6. 自定义图形节点 EllipseFigure 的代码示例

import org.eclipse.draw2d.ColorConstants;  import org.eclipse.draw2d.Figure;  import org.eclipse.draw2d.Graphics;  import org.eclipse.draw2d.Label;  import org.eclipse.draw2d.LineBorder;  import org.eclipse.draw2d.ToolbarLayout;  import org.eclipse.draw2d.geometry.Rectangle;  public class EllipseFigure extends Figure {        public EllipseFigure(String name) {                   // 定义显示名称              Label label = new Label(name);                   // 定义图形节点的显示布局              ToolbarLayout layout = new ToolbarLayout();               setLayoutManager(layout);               setBorder(new LineBorder(ColorConstants.white, 1));               setOpaque(true);               add(label);        }        // 重写这个方法,实现自己需要显示的图形,更多的信息可以参考 Zest 的源代码       @Override        public void paint(Graphics graphics) {               super.paint(graphics);               // 获取默认的矩形信息              Rectangle rectangle = getBounds().getCopy();               graphics.setLineWidth(2);                   // 画椭圆              graphics.drawOval(rectangle);        }  }

 

同时简要修改一下主程序,在程序当中添加一个新的方法,用来创建椭圆图形对象 .

清单 7. 创建 EllipseFigure 对象的代码示例

// 创建 EllipseFigure 对象的方法 private static IFigure createEllipseFigure(String name) {        EllipseFigure circleFigure = new EllipseFigure(name);        circleFigure.setSize(-1, -1);        return circleFigure;  }  GraphNode endNode = new GraphNode(graph, SWT.NONE, "End", stopIcon);  // 创建椭圆图形节点对象 EllipseGraphNode ellipseNode = new EllipseGraphNode(graph, SWT.NONE,                      createEllipseFigure("ellipse"));  // 创建节点关联 new GraphConnection(graph, SWT.NONE, startNode, ellipseNode);  new GraphConnection(graph, SWT.NONE, ellipseNode, endNode);

 

运行程序得到如下的显示效果。在开始节点和结束节点之间增加了一个椭圆形的节点。

图 8. 椭圆图形节点显示效果

 

如何禁止图形节点的移动事件

Zest 图形 (Graph) 当中默认所有的组件对象都是可以被用户拖动。某些情况下,希望把某个元素固定不动,不可以被用户随意改变位置,实现的方法可以参考如下的代码:

清单 8. 禁止图形节点 (GraphNode) 被移动的代码示例

// 创建 Graph  Graph graph = new Graph(shell, SWT.NONE);  // 设置事件分发 graph.getLightweightSystem().setEventDispatcher(  new SWTEventDispatcher() {     @Override     public void dispatchMouseMoved(MouseEvent me) {           List selection = ((Graph) me.widget).getSelection();           for (Iterator iterator = selection.iterator(); iterator.hasNext();) {                   Object object = (Object) iterator.next();                   if (object instanceof GraphNode) {                          String nodeName = ((GraphNode) object).getText();                          if ("Start".equalsIgnoreCase(nodeName)) {                                 // 如果是 Start 图形节点,就无法被移动                         } else {                                 // 其他图形节点,不受影响                                super.dispatchMouseMoved(me);                          }                   }            }     }  });

 

这样显示名字为 Start 的图形节点就无法被用户移动,而其他工作节点不受任何影响。


 


回页首

结束语

关于 Zest 的开发入门介绍到此就结束了,希望本篇文章能够对您的了解 Zest 库的使用和编程开发有所帮助和启发。

 

参考资料

学习

讨论