[Eclipse]GEF入门系列(五、浅谈布局)

简介: 虽然很多GEF应用程序里都会用到连接(Connection),但也有一些应用是不需要用连接来表达关系的,我们目前正在做的这个项目就是这样一个例子。在这类应用中,模型对象间的关系主要通过图形的包含来表达,所以大多是一对多关系。

虽然很多GEF应用程序里都会用到连接(Connection),但也有一些应用是不需要用连接来表达关系的,我们目前正在做的这个项目就是这样一个例子。在这类应用中,模型对象间的关系主要通过图形的包含来表达,所以大多是一对多关系。

cbm1.gif
图1 不使用连接的GEF应用

先简单描述一下我们这个项目,该项目需要一个图形化的模型编辑器,主要功能是在一个具有三行N列的表格中自由增加/删除节点,节点可在不同单元格间拖动,可以合并相邻节点,表格列可增减、拖动等等。由于SWT/Jface提供的表格很难实现这些功能,所以我们选择了使用GEF开发,目前看来效果还是很不错的(见下图),这里就简单介绍一下实现过程中与图形和布局有关的一些问题。

在动手之前首先还是要考虑模型的构造。由于Draw2D只提供了很有限的Layout,如ToolbarLayout、FlowLayout和XYLayout,并没有一个GridLayout,所以不能把整个表格作为一个EditPart,而应该把每一列看作一个EditPart(因为对列的操作比对行的操作多,所以不把行作为EditPart),这样才能实现列的拖动。另外,从需求中可以看出,每个节点都包含在一个列中,但仔细再研究一下会发现,实际上节点并非直接包含在列中,而是有一个单元格对象作为中间的桥梁,即每个列包含固定的三个单元格,每个单元格可以包含任意个节点。经过以上分析,我们的模型、EditPart和Figure应该已经初步成形了,见下表:

 

模型

EditPart

Figure

画布

Diagram

DiagramPart

FreeformLayer

Column

ColumnPart

ColumnFigure

单元格

Cell

CellPart

CellFigure

节点

Node

NodePart

NodeFigure

表中从上到下是包含关系,也就是一对多关系,下图简单显示了这些关系:

cbm2.gif
图2 图形包含关系图

让我们从画布开始考虑。在画布上,列显示为一个纵向(高大于宽)的矩形,每个列有一个头(Header)用来显示列名,所有列在画布上是横向排列的。因此,画布应该使用ToolbarLayout或FlowLayout中的一种。这两种Layout有很多相似之处,尤其它们都是按指定的方向排列显示图形,不同之处主要在于:当图形太多容纳不下的时候,ToolbarLayout会牺牲一些图形来保持一行(列),而FlowLayout则允许换行(列)显示。

对于我们的画布来说,显然应该使用ToolbarLayout作为布局管理器,因为它的子图形ColumnFigure是不应该出现换行的。以下是定义画布图形的代码:

Figure f = new FreeformLayer();
ToolbarLayout layout=new ToolbarLayout();
layout.setVertical(false);
layout.setSpacing(5);
layout.setStretchMinorAxis(true);
f.setLayoutManager(layout);
f.setBorder(new MarginBorder(5));

其中setVertical(false)指定横向排列子图形,setSpacing(5)指定子图形之间保留5象素的距离,setStretchMinorAxis(true) 指定每个子图形的高度都保持一致。

ColumnFigure的情况要稍微复杂一些,因为它要有一个头部区域,而且它的三个子图形(CellFigure)合在一起要能够充满下部区域,并且适应其高度的变化。一开始我用Draw2D提供的Label来实现列头,但有一个不足,那就是你无法设置它的高度,因为Label类覆盖了Figure的getPreferedSize()方法,使得它的高度只与里面的文本有关。解决方法是构造一个HeaderFigure,让它维护一个Label,设置列头高度时实际设置的是HeaderFigure的高度;或者直接让HeaderFiguer继承Label并重新覆盖getPreferedSize()也可以。我在项目里使用的是前者。

第二个问题花了我一些时间才搞定,一开始我是在CellPart的refreshVisuals()方法里手动设置CellFigure的高度为ColumnFigure下部区域高度的三分之一,但这样很勉强,而且还需要额外考虑spacing带来的影响。后来通过自定义Layout的方式比较圆满的解决了这个问题,我让ColumnFigure使用自定义的ColumnLayout,这个Layout继承自ToolbarLayout,但覆盖了layout()方法,内容如下:

class ColumnLayout extends ToolbarLayout {
    public void layout(IFigure parent) {
        IFigure nameFigure=(IFigure)parent.getChildren().get(0);
        IFigure childrenFigure=(IFigure)parent.getChildren().get(1);
        Rectangle clientArea=parent.getClientArea();
        nameFigure.setBounds(new Rectangle(clientArea.x,clientArea.y,clientArea.width,30));
        childrenFigure.setBounds(new Rectangle(clientArea.x,nameFigure.getBounds().height+clientArea.y,clientArea.width,clientArea.height-nameFigure.getBounds().height));
    }
}

也就是说,在layout里控制列头和下部的高度分别为30和剩下的高度。但这还没有完,为了让单元格正确的定位在表格列中,我们还要指定列下部图形(childrenFigure)的布局管理器,因为实际上单元格都是放在这个图形里的。前面说过,Draw2D并没有提供一个像SWT中FillLayout那样的布局管理器,所以我们要再自定义另一个layout,我暂时给它起名为FillLayout(与SWT的FillLayout同名),还是要覆盖layout方法,如下所示(因为用了transposer所以horizontal和vertical两种情况可以统一处理,这个transposer只在horizontal时才起作用):

public void layout(IFigure parent) {
    List children = parent.getChildren();
    int numChildren = children.size();
    Rectangle clientArea = transposer.t(parent.getClientArea());
    int x = clientArea.x;
    int y = clientArea.y;
    for (int i = 0; i < numChildren; i++) {
        IFigure child = (IFigure) children.get(i);
        Rectangle newBounds = new Rectangle(x, y, clientArea.width, -1);

        int divided = (clientArea.height - ((numChildren - 1) * spacing)) / numChildren;
        if (i == numChildren - 1)
            divided = clientArea.height - ((divided + spacing) * (numChildren - 1));
        newBounds.height = divided;
        child.setBounds(transposer.t(newBounds));
        y += newBounds.height + spacing;
    }
}

上面这些语句的作用是将父图形的高(宽)度平均分配给每个子图形,如果是处于最后的一位的子图形,让它占据所有剩下的空间(防止除不尽的情况留下空白)。完成了这个FillLayout,只要让childrenFigure使用它作为布局管理器即可,下面是ColumnFigure的大部分代码,列头图形(HeaderFigure)和列下部图形(ChildrenFigure)作为内部类存在:

private HeaderFigure name = new HeaderFigure();
private ChildrenFigure childrenFigure = new ChildrenFigure();
public ColumnFigure() {
    ToolbarLayout layout = new ColumnLayout();
    layout.setVertical(true);
    layout.setStretchMinorAxis(true);
    setLayoutManager(layout);
    setBorder(new LineBorder());
    setBackgroundColor(color);
    setOpaque(true);
    add(name);
    add(childrenFigure);
    setPreferredSize(100, -1);
}
class ChildrenFigure extends Figure {
    public ChildrenFigure() {
        ToolbarLayout layout = new FillLayout();
        layout.setMinorAlignment(ToolbarLayout.ALIGN_CENTER);
        layout.setStretchMinorAxis(true);
        layout.setVertical(true);
        layout.setSpacing(5);
        setLayoutManager(layout);
    }
}
class HeaderFigure extends Figure {
    private String text;
    private Label label;
    public HeaderFigure() {
        this.label = new Label();
        this.add(label);
        setOpaque(true);
    }
    public String getText() {
        return this.label.getText();
    }
    public Rectangle getTextBounds() {
        return this.label.getTextBounds();
    }
    public void setText(String text) {
        this.text = text;
        this.label.setText(text);
        this.repaint();
    }
    public void setBounds(Rectangle rect) {
        super.setBounds(rect);
        this.label.setBounds(rect);
    }
}

单元格的布局管理器同样使用FillLayout,因为在需求中,用户向单元格里添加第一个节点时,该节点要充满单元格;当单元格里有两个节点时,每个节点占二分之一的高度;依次类推。下面的表格总结了各个图形使用的布局管理。由表可见,只有包含子图形的那些图形才需要布局管理器,原因很明显:布局管理器关心和管理的是"子"图形,请时刻牢记这一点。

 

布局管理器

直接子图形

画布

ToolbarLayout

ColumnLayout

列头部、列下部

-列头部

-列下部

FillLayout

单元格

单元格

FillLayout

节点

节点

这里需要特别提醒一点:在一个图形使用ToolbarLayout或子类作为布局管理器时,图形对应的EditPart上如果安装了FlowLayoutEditPolicy或子类,你可能会得到一个ClassCastException异常。例如例子中的CellFigure,它对应的EditPart是CellPart,其上安装了CellLayoutEditPolicy是FlowLayoutEditPolicy的一个子类。出现这个异常的原因是在FlowLayoutEditPolicy的isHorizontal()方法中会将图形的layout强制转换为FlowLayout,而我们使用的是ToolbarLayout。我认为这是GEF的一个疏忽,因为作者曾说过FlowLayout可应用于ToolbarLayout。幸好解决方法也不复杂:在你的那个EditPolicy中覆盖isHorizontal()方法,在这个方法里先判断layout是ToolbarLayout还是FlowLayout,再根据结果返回合适的boolean值即可。

最后,关于我们的画布还有一个问题没有解决,我们希望表格列增多到一定程度后,画布可以向右边扩展尺寸,前面说过画布使用的是FreeformLayer作为图形。为了达到目的,还必须在editor里设置rootEditPart为ScalableRootEditPart,要注意不是ScalableFreeformRootEditPart,后者在需要各个方向都能扩展的画布的应用程序中经常被使用。关于各种RootEditPart的用法,在后续帖子里将会介绍到。

以上结合具体实例讲解了如何在GEF中使用ToolbarLayout以及自定义简单的布局管理器。我们构造图形应该遵守一个原则,那就是尽量让布局管理器决定每个子图形的位置和尺寸,这样可以避免很多麻烦。当然也有例外,比如在XYLayout这种只关心子图形位置的布局管理器中,就必须为每个子图形指定尺寸,否则图形将因为尺寸过小而不可见,这也是一个开发人员十分容易疏忽的地方。

本文转自博客园八进制的博客,原文链接:[Eclipse]GEF入门系列(五、浅谈布局),如需转载请自行联系原博主。

相关文章
|
7月前
|
Android开发
10activiti - 入门demo(Eclipse)
10activiti - 入门demo(Eclipse)
25 0
10activiti - 入门demo(Eclipse)
|
6月前
|
Android开发
eclipse怎么调出server布局窗口
eclipse怎么调出server布局窗口
50 0
|
12月前
|
Go Android开发
开心档-软件开发入门之Eclipse 添加书签
Eclipse 中可以在编辑器的任意一行添加书签。 您可以使用书签作为提示信息,或者使用书签快速定位到文件中的指定的行。
|
Java 应用服务中间件 Apache
Java Web入门之开发环境的搭建(包括下载Tomcat和 Eclipse for Java EE)
Java Web入门之开发环境的搭建(包括下载Tomcat和 Eclipse for Java EE)
198 0
Java Web入门之开发环境的搭建(包括下载Tomcat和 Eclipse for Java EE)
|
Android开发
成功解决Eclipse窗口布局混乱或者Eclipse窗口布局出现单独独立小窗口的问题(图文教程)
成功解决Eclipse窗口布局混乱或者Eclipse窗口布局出现单独独立小窗口的问题(图文教程)
成功解决Eclipse窗口布局混乱或者Eclipse窗口布局出现单独独立小窗口的问题(图文教程)
|
XML Java API
Spring框架入门以及Eclipse创建 Spring项目
Spring框架入门以及Eclipse创建 Spring项目
Spring框架入门以及Eclipse创建 Spring项目
|
XML Java Android开发
SpringBoot入门:使用IDEA和Eclipse构建第一个SpringBoot项目
Spring Boot是一个简化Spring开发的框架,用来监护spring应用开发,约定大于配置,去繁就简,just run 就能创建一个独立的,产品级的应用。我们在使用Spring Boot时只需要配置相应的Spring Boot就可以用所有的Spring组件,简单的说,spring boot就是整合了很多优秀的框架,不用我们自己手动的去写一堆xml配置然后进行配置。所以springboot在java开发中也变得越来越重要,这里总结一下如何使用IDEA和Eclipse创建一个简单的springboot项目。
388 0
SpringBoot入门:使用IDEA和Eclipse构建第一个SpringBoot项目
|
Java Maven Android开发
Maven入门及在eclipse中配置
Maven入门及在eclipse中配置
156 0
Maven入门及在eclipse中配置
|
Android开发 数据库
GEF入门实例_总结_04_Eclipse插件启动流程分析
一、前言 本文承接上一节:GEF入门实例_总结_03_显示菜单和工具栏 注意到app目录下的6个类文件。 这6个文件对RCP应用程序而言非常重要,可能我们现在对这几个文件的理解还是云里雾里,这一节我们将通过这几个文件来了解Eclipse插件的启动过程。
1388 0
|
Android开发
Eclipse插件开发_学习_02_GEF入门实例
一、前言 这一节,我们将会创建一个GEF入门实例     二、新建RCP项目 1. New 一个 Plug-in Project     2.输入项目名 项目名:com.ray.gef.
1750 0