月蚀传说

浮躁让人失去理智
posts - 25, comments - 101, trackbacks - 0, articles - 0
  BlogJava :: 首页 ::  :: 联系 :: 聚合  :: 管理

数据库字段的显示以及增加

Posted on 2006-10-09 19:02 Dart 阅读(3716) 评论(5)  编辑  收藏 所属分类: GEF

隔了将近一个月,我终于可以在家上网了——我又回来了。由于搬家后没有网上,本来这篇应该早就写完的文章也只能拖了这么久才“发布”。

在这一章,我说一下如何去利用布局,以及其他一些关于EditPolicy的用法和Palette的实现。说明一下,示例代码和本章内容中的代码有出入,这是因为以前我是做一点写一点,这次我完成得比较多,但是由于涉及技术有一些差异,所以我将分成两篇文章讲完,我建议大家看完第5章后再下代码。代码下载.

1.重新显示TableFigure

上一章中,我们简单地绘制了一个矩形,充当我们的数据库表的图形,现在让我们重新想想,如何显示这个TableFigure。

让我们看看Visio里面是怎么显示的吧:



我们也按照它的样子做一个类似的!

首先,我们需要显示我们的表名,其次,我们要将数据字段显示在表名下放的区域中,然后再在表名和存放数据库字段的区域之间画一条线,分割开。看下图:


看来我们以前写的TableFigure还差太远,仅仅一个矩形还不够。

先考虑一下如何显示表名。在TableFigure中显示一条文字很容易的,简单的方法就是在绘制它的时候,利用Graphics的drawText方法即可,但是这样做很繁琐,需要不停的计算表名显示位置,并且,当我们的表名发生改变的时候,表自身大小该如何调节呢。所以我们需要利用Label来显示,因为Label可以很容易地对显示的表名进行维护,包括计算文字长度获得Figure该具有的大小等;此外,当我们使用Label来显示的话, TableFigure还可以通过布局管理器来计算自己当前的大小,这又省得我们自己计算了。打开TableFigure类,增加以下代码:

public  TableFigure(Table model) {
        super();
        
this .model  =  model;

        tableNameLabel 
=   new  Label();
        tableNameLabel.setText(model.getTableName());
        FontData fd 
=   new  FontData();

        fd.setHeight(
10 );
        fd.setName(
" Arial " );
        fd.setStyle(SWT.BOLD);

        tableNameLabel.setFont(
new  Font( null , fd));
        tableNameLabel.setIcon(ImageProvider.TABLE_ICON.createImage());
        tableNameLabel.setLabelAlignment(PositionConstants.MIDDLE);

        
//  留出一点边距,会好看点
         this .setBorder( new  MarginBorder( 8 8 8 8 ));

        
this .add(tableNameLabel);
        
        
this .setOpaque( true );
    }

我们在TableFigure创建的时候,给它生成了一个Label,然后将这个Label作为TableFigure的子Figure添加到它上面,这样一来我们就让这个Label来显示数据表名。上面代码中用到了图片设置以及字体设置,这里我们只讨论GEF,所以就不讨论他们了。

名字能显示了,现在该让我们的字段容器出场了!

字段的图形需要有容器去维护显示它,刚才我们也看到了,Visio中的字段都是在字段容器中自上而下有序地排列在一起的,问题就来了:怎么去让它们如此听话地有序排列呢。布局管理器(LayoutManager),只要做过桌面应用的人都知道,不错,Draw2D也是利用布局管理器来对图形进行位置大小进行维护的。在Draw2D中有一个名为ToolbarLayout的布局管理器,它实现了让Figure自上而下有序地排列在一起,这正是我们需要的!

现在让我们在TableFigure构造函数中增加以下代码:

      containerFigure  =   new  Figure() ;

        ToolbarLayout tableLayout 
=   new  ToolbarLayout();
  
        
//  承载Column Figure的容器是ToolbarLayout
        ToolbarLayout containerLayout  =   new  ToolbarLayout();
        containerLayout.setMinorAlignment(ToolbarLayout.ALIGN_BOTTOMRIGHT);
        containerFigure.setLayoutManager(containerLayout);
     
        
this .add(containerFigure);


  这样我们的containerFigure就做好了。

但是光这样还不够。containerFigure 和tableNamelable这两个Figure作为TableFigure的子图形,应该显示在哪儿呢?怎么给他们定位?而且,当我们的这两个子图形大小发生改变了以后,TableFigure怎么办?
其实所有的一切问题都是由布局管理器来回答的。

在Figure 类中有一个名位getPreferredSize的方法,这个方法是去获得当前Figure大小的。默认的情况下,getPreferredSize方法会查看该Figure中是否注册有布局管理器,如果有,那么就会调用布局管理器的getPreferredSize方法获得大小。布局管理器的 getPreferredSize方法并不是简单地去查看当前Figure的Size,而是要通过该布局本身的特点,再通过该Figure中的子图形位置以及大小,按照一定的算法叠加而取得当前Figure的大小。

简单说,如果我们使用了ToolbarLayout,那么当我们调用注册有该 LayoutManager的Figure的getPreferredSize方法时,布局管理器就会将该Figure的高度设置为它所有子Figure 的高度和;宽度设为所有子Figure中宽度最大值。

这样一来,根据我们刚才图形所画的那样,tableNameFigure和 containerFigure也是自上而下排列的,所有,我们可以在TableFigure中使用ToolbarLayout,让布局管理器来管理它的大小。写到这里大家也都看出来了,我们在containerFigure中设置ToolbarLayout的,也是为了当我们增加字段图形的时候,让布局管理器去控制它的大小。

我们已经基本完成了TableFigure。但是如果在我们添加了数据库字段的时候,EditPart并不知道让字段的Figure是作为TableFigure中containerFigure的子图形而添加进去的,它默认是把字段图形直接绘制到 TableFigure上,那这样我们刚才的设想就完全实现不了了。所以,让我们复写TableEditPart的getContentPane方法:

  public  IFigure getContentPane() {
        
return  ((TableFigure)getFigure()).getContainerFigure();
    }

这是什么意思呢?
在前面的章节我好像说过了,GEF中对于某一个EditPart,它的子EditPart的图形是绘制在该EditPart的图形之上的——一个递归的过程。但是EditPart并不是把它的图形直接默认为子Fiuger的容器Figure,而是通过getContentPane方法来获得承载子 EditPart图形的Figure,当然,如果直接继承GraphicalEditPart的话,getContentPane方法直接返回的就是 getFigure,所以,当我们要重新定义EditPart的容器Figure的时候,就需要复写getContentPane。





2.显示出ColumnFigure

现在我们来实现字段图形。
字段图形很简单,只要实现字段名以及一个能表示字段的图标即可,所以我们将它继承自Label:

public   class  ColumnFigure extends Label {
    
private  Column model;
    
private  boolean selected;
    
private  boolean hasFocus;
      
public  ColumnFigure(Column model){
          super();
          
this .model  =  model;
          model.setColumnName(model.getColumnName());
          
          FontData fd 
=   new  FontData();
          
            fd.setHeight(
8 );
            fd.setName(
" Arial " );
            fd.setStyle(SWT.BOLD);
          
this .setIcon(ImageProvider.COLUMN_ICON.createImage());
          
this .setLabelAlignment(PositionConstants.LEFT);
          
this .setFont( new  Font( null ,fd));
      }
}

然后让ColumnEditPart的createFigure方法返回它:

    protected IFigure createFigure() {
        
// TODO Auto-generated method stub
        return new ColumnFigure((Column)getModel());
    }

ok,现在让我们在DbEditor中修改一下initializeGraphicalViewer方法:

protected void initializeGraphicalViewer() {
        
// 硬编码生成一个数据库模型
        Schema schema = new Schema();
        Table table 
= new Table();
        table.setTableName(
"Test");
        Column column 
= new Column();
        column.setColumnName(
"test");
        table.addChild(column);
        schema.addChild(table);
        
this.getGraphicalViewer().setContents(schema);
    }

OK,现在我们可以看到一个新的TableFigure了。

但是存在一个问题:我们的TableFigure大小自己不能计算获得,连字段都没显示出来。肯定有人要说了:不是说有了布局管理器就可以了吗!

等等,还记得上一章中,我们为了能移动矩形,而复写了refreshVisuals方法吧?现在我们重新写一遍:

 protected void refreshVisuals() {
        super.refreshVisuals();
        
// 得到当前TableFigure的大小,由于有Toolbar布局的约束,它会自动计算
       
        Dimension size 
= this.getFigure().getPreferredSize();
        
        
// 获得更改后的位置,位置是在Model进行维护的
        Point p = ((Table) getModel()).getLocation();
        
        
// 我们只更改Table的位置
        ((GraphicalEditPart) this.getParent()).setLayoutConstraint(thisthis
                .getFigure(),
new Rectangle(p, size));
    }


看过以前代码的朋友一眼就发现了不同:size不再是简单的去取得当前的bounds大小了,而是通过我们上面说的,利用getPreferredSize方法去让布局计算!

修改完毕后再看看我们的TableFigure:




补充一下:篇幅问题,我只捡我认为重要的说,其他的一些细节,比如,TableFigure尺寸最大和最小约束啊,怎么画tableNameLabel下的线啊,还有Label的停靠啊,border的使用啊,渐变矩形的绘制啊,这些我就没有提起了,大家可以自己看代码,如果有疑问可以发贴讨论。

3.重新生成PaletteRoot

在我们以前的例子中,工具面板生成的是一个很简单的空面板,上面光秃秃的,无法通过面板的工具往我们的Viewer中增加Figure图形,使得我们每次都需要在DbEditor中复写修改代码,用硬编码来实现模型的增加。

现在起我们要构造一个可以创建Table和Column的工具面板,让硬编码创建模型见鬼去吧。

先生成一个单态类:PaletteFactory,然后在我们在里面生成一个空的PaletteRoot,再弄两个PaletteDrawer添加到PaletteRoot上:

public class PaletteFactory {
    
    
private PaletteRoot root;
    
    
private PaletteDrawer defaultTools;
    
    
private PaletteDrawer dbTools;
    
    
private static PaletteFactory instance = null;
    
private PaletteFactory(){}
    
    
public static PaletteFactory INSTANCE(){
        
if(instance == null) instance = new PaletteFactory();
        
return instance;
    }
    
    
public PaletteRoot createPaletteRoot(){
//        if(root != null) return root;
        
        root 
= new PaletteRoot();
        root.add(createDefaultToolBox());
        root.add(createDbToolBox());
        
return root;
    }
    
    
private PaletteDrawer createDefaultToolBox(){
        defaultTools 
= new PaletteDrawer("Default tools");
        
        defaultTools.add(
new SelectionToolEntry());
        
        
return defaultTools;
    }
    
    
private PaletteDrawer createDbToolBox(){
        dbTools 
= new PaletteDrawer("DataBase tools");        
        
return dbTools;
    }
}

我简单说一下,PaletteDrawer是一种可以隐藏的容器,在它上面可以增加按钮等ToolEntry。什么是ToolEntry呢?大家可以理解为在Palette上面的任何元素:按钮、分割线、容器等,ToolEntry对应有一个Tool,通过ToolEntry的createTool返回的,如果我们需要一些特别处理的时候,可以直接去实现ToolEntry和Tool,但是在我们的例子中,我们需要的是可以生成模型的ToolEntry,所以我们就不必去研究ToolEntry和Tool的工作原理。


接着往下说。
上述代码中,我们给defaultTools容器增加了一个SelectionToolEntry,它是GEF自己提供的一个工具,是为点击选择图形使用的,对于它的作用这里就不废话了:)

从代码下面可以看出来,我们还没有增加创建Table和Column的ToolEntry,现在我们来实现他们。

创建两个ToolEntry,分别命名为TableToolEntry和ColumnToolEntry,他们都是继承了 CreationToolEntry。注意一下CreationToolEntry的构造函数,需要传入一个CreationFactory,这个工厂类就是创建我们所要返回的模型实例工厂,它的两个方法:getNewObject()和getObjectType()分别是返回创建的模型和模型的类型。

来看看我们的对该工厂的实现:

public class DbCreationFactory implements CreationFactory {

    
private Class type;
    
public DbCreationFactory(Class type){
        setType(type);
    }

    
public Object getNewObject() {
        
if(type == Table.class){
            
return new Table();
        }
        
if(type == Column.classreturn new Column();
        
return null;
    }

    
public Object getObjectType() {
        
// TODO Auto-generated method stub
        return getType();
    }
    .......
}

通过创建这个工 厂类的时候传入的参数,就可以生成对应的模型了

再看看我们的TableToolEntry和 ColumnToolEntry的实现的:
public class TableToolEntry extends CreationToolEntry {
    
public TableToolEntry() {
        super(
"Table""Create a table"new DbCreationFactory(Table.class), ImageProvider.TABLE_ICON, null);
    }
}


public class ColumnToolEntry extends CreationToolEntry {
    
public ColumnToolEntry(){
        super(
"Column""Create a column"new DbCreationFactory(Column.class), ImageProvider.COLUMN_ICON, null);
    }
}< /span>

大家看出来了,我们传入的DbCreationFactory 都是对应了他们所要生成模型需要的参数。

现在,我们的ToolEntry类创建好了,让我们把它们添加到 dbTools的PaletteDrawer上:

修改 PaletteFactory 类的 createDbToolBox()方法
    private PaletteDrawer createDbToolBox(){
        dbTools 
= new PaletteDrawer("DataBase tools");
        
        dbTools.add(
new TableToolEntry());
         dbTools.add(new ColumnToolEntry());
     
        
return dbTools;
    }


运行一下:



4.新的Command; FlowEditPolicy的用法

完成了上面的工作后,我们离从工具面板上创建模型还差点:EditPolicy中没有对应的Command。

以前的章节里,我们都知道了,GEF中的所有事件都封装成了Request向外发送,然后找到EditPolicy处理,EditPolicy再去索取Command来执行。我们在上一章就已经生成了一个TableMoveCommand,所以相信大家对Command应该不陌生了。

我们生成这样一个Command:DbItemCreationCommand

public class DbItemCreateCommand extends Command {
    
private DBBase parent;
    
private DBBase child;
    
private int index = -1;
    

    
public void execute() {
        Assert.isNotNull(parent);
        Assert.isNotNull(child);
        parent.addChild(index,child);
    }

    
public void redo() {
        execute();
    }

    
public void undo() {
        Assert.isNotNull(parent);
        Assert.isNotNull(child);
        parent.removeChild(child);
    }
......
  ......

}

现在,有了这个Command,我们就要考虑一下,看讲它交给谁的EditPolicy去返回。




通常情况下,如果我们要生成一个模型,那么我们就应该在它的父容器的EditPolicy注册一个 Command,因为绝大多数的容器类型的EditPart,都有安装有ContainerEditPolicy或者LayoutEditPolicy,而这两种EditPolicy恰恰就能对CreateRequest进行截获并进行处理。所以,结合我们的例子,要生成Table模型,就需要在他的父 EditPart——SchemaEditPart的SchemaLayoutEditPolicy里做文章。

打开这个类,发现里面有一个方法:getCreateCommand,我们就在这里面返回DbItemCreateCommand吧:

protected Command getCreateCommand(CreateRequest request) {
        Object obj 
= request.getNewObject();
        
if(obj != null && request.getNewObjectType() == Table.class){
            DbItemCreateCommand command 
= new DbItemCreateCommand();
            command.setParent((DBBase)
this.getHost().getModel());
            command.setChild((DBBase)obj);
            ((Table)obj).setLocation(request.getLocation());
            
return command;
        }
        
return null;
    }

看看上面的代码,发现了吗?CreateRequest携带有我们要生成的对象的类型以及实例,并且连我们在创建时点击在Viewer上的位置也有,所以,我们只需要设置一下DbItemCreateCommand中的父模型以及子模型即可,当然,我们还需要在生成模型的时候,将生成该模型时鼠标所点击的位置给Table模型,好让他一创建就处在该位置。太cool了!

但是光是添加了模型,EditPart是不知道的,所以我需要去通知EditPart刷新一下。还记得上一章中,矩形位置更改后是怎么通知EditPart的吗?我再罗嗦一下吧:利用我们在模型中的PropertyChangeSupport发出属性更改通知,然后然EditPart截获后去做相应的动作即可:

更改DBBase部分代码代码:
public void addChild(int index , DBBase child){
        
if(index == -1){
        getChildren().add(child);
        }
else{
            getChildren().add(index,child);
        }
        child.setParent(
this);
        
this.fireChildenChange(child);
    }
    
    
public void removeChild(DBBase child){
        child.setParent(
null);
        getChildren().remove(child);
        
this.fireChildenChange(child);
    }

    
public void fireChildenChange(DBBase child){
        support.firePropertyChange(PRO_CHILD,
null,child);
    }


让DBEditPartBase去截获PRO_CHILD事件:
 public void propertyChange(PropertyChangeEvent evt) {
       String pName 
= evt.getPropertyName();

       if(pName.equals(DBBase.PRO_CHILD)){
           
this.refreshChildren();
           
this.refreshVisuals();
       }
    }

最后让我们把以前在DbEditor中生成模型的代码删除掉,就让Viewer的Content设置为一个Schema即可,
好了,运行一下吧,是不是可以创建Table了?:)

5.FlowLayoutEditPolicy的应用以及Column的创建

我们已经能够创建Table了,现在需要创建一个Column。

在上面我们已经创造了生成Column模型的条件:ColumnToolEntry、DbCreateFactory还有DbItemCreateCommand,就差一样:我们把这个Command往哪儿放呢?

以前的经验告诉我们,这个Command是需要让ColumnEditPart的父EditPart的EditPolicy去返回的,但是它的父 EditPart,也是就TableEditPart,目前没有安装能够维护创建子模型的EditPolicy,所以我们需要创建一个给他安装上。

我们选用一个名为FlowLayoutEditPolicy的类作为我们新建EditPolicy的父类,这是因为 FlowLayoutEditPolicy专门针对具有FlowLayout以及ToolbarLayout布局管理器的Figure而做的,它可以对子 Figure的位置移动做出一些维护,比如当我们在TableFigure中拖动了ColumnFigure,FlowLayoutEditPolicy 可以在它可以插入的位置显示一条黑线:



我们的这个类就命名为TableFlowLayoutEditPolicy:

public class TableFlowLayoutEditPolicy extends FlowLayoutEditPolicy {

.......
 
    
protected Command getCreateCommand(CreateRequest request) {
        Object obj 
= request.getNewObject();
        
if(obj != null && request.getNewObjectType() == Column.class){
            DbItemCreateCommand command 
= new DbItemCreateCommand();
            command.setParent((DBBase)
this.getHost().getModel());
            command.setChild((DBBase)obj);
            
            EditPart after 
= getInsertionReference(request);
            
int index = getHost().getChildren().indexOf(after);
            command.setIndex(index);
            
return command;
        }
        
return null;
    }
  .......
 ............
    
protected boolean isHorizontal() {
        IFigure figure 
= ((GraphicalEditPart)getHost()).getContentPane();
        LayoutManager layout 
= figure.getLayoutManager();
        
if(layout instanceof FlowLayout)
        
return ((FlowLayout)figure.getLayoutManager()).isHorizontal();
        
if(layout instanceof ToolbarLayout)
            
return ((ToolbarLayout)figure.getLayoutManager()).isHorizontal();
        
return false;
    }
}


我们在getCreateCommand中返回了DbItemCreateCommand,将parent设为Table,child即为生成request携带的Column对象,因为Column是没有位置这个概念的,我们就不必把位置参数给它(给了也没变量保存啊)它只可能是在这个表中的第几个而已,并且,我们在上面也提到了,在移动ColumnFigure的时候 FlowLayoutEditPolicy可以绘制一条黑线,显示当前插入的位置,所以我们通过然后我们getInsertionReference方法得到当前黑线所在索引所对应的EditPart,然后得到该EditPart在TableEditPart的子EditPart中的位置,再传递给 Command,让我们新生成的EditPart添加到该位置上。

但是,要让FlowLayoutEditPolicy显示出黑线来,我们还需要复写它的 isHorizontal方法,因为默认情况下,FlowLayoutEidtPolicy的  isHorizontal方法在运行时,认为安装该EditPolicy的EditPart使用的是FlowLayout,但是我们这里用的是 ToolbarLayout,所以如果不复写的话,将会抛出类型转换的异常。

我们只要简单的进行一下修改即可,或者直接让他返回true得了 :)

由于我们已经在Column以及ColumnEditPart的基类中增加了添加子节点后的属性更改代码,所以这里就不用写了。

现在我们就可以通过点击工具栏的column工具在Table中创建Column了。

让我们看看整体效果:





6 . 结束语

这次我们基本上解决了数据库表以及数据库字段的实现问题,下一章我们会接着往下讲,其实代码里面已经有了,有兴趣的朋友可以看看。

下一章我会讲一下如何去在GEF应用中实现PropertyPage,以及Connection的一些问题


评论

# re: 数据库字段的显示以及增加  回复  更多评论   

2007-01-11 09:43 by nomigd
你好,我装你的源代码引入eclipse中,可是出现org.eclipse.core.internal.runtime.Assert类找不到,请问这个是什么类,在那个包中有。谢谢

# re: 数据库字段的显示以及增加  回复  更多评论   

2007-03-23 17:02 by amendar
请问:我若想放一张图片在编辑器中,这是如何实现的!!!
QQ:31395591
email:31395591@qq.com
谢谢

# re: 数据库字段的显示以及增加  回复  更多评论   

2007-03-23 17:34 by Dart
TO amendar:
Figure是可以进行绘制图片的,使用graphics类的drawImage即可

# re: 数据库字段的显示以及增加  回复  更多评论   

2007-04-05 14:37 by 尘封
我用GEF开发一个类似UML类图编辑功能,现在所有的例子都是
有一个Subject,subject有名字,可以增加子attribute,但是我还要为subject增加子opt,我现在面临的问题是,如果opt和attribute同时作为subject的子,用两个list在model中,只是在视图中用lay区分它们的化,在subjectPart中getchilden只能得到attribute的list或是opt的list,无法处理好它们,
另一个解决的方法是用一个Cell作为subject的子,而且subject只含有2个Cell,并且作为subject创建的时候同时创建的,每个Cell各含有自己的子,分别处理attribute和opt,这样可以各自控制自己的list,但是由于createFigure时,只能返回一个PART,这样我的CellPart应该如何创建出来呢

谢谢,cliff4@21cn.com

# re: 数据库字段的显示以及增加  回复  更多评论   

2007-04-05 16:21 by Dart
to 尘封:
其实没有必要再做两个Cell出来,我也常遇到这种问题,我来说说我的解决办法

首先要认定Subjuect维护了两个List,一个是存放了opt,一个是存放了Attr.
那可以将这两个List看成是Subject的childrenModel,也就是说Subject只维护了两个List而已,所以在Subject的EditPart中返回childrenModel的时候只需要将这两个list放在一个List中返回.

这样一来,在GEF通过EditPartFactory查找模型对应的Editpart时,就会出现两个List的模型,这是没有办法分清的.
我的做法是:使用EMF提供的List,因为这种list是可以携带其包含的元素的类型信息的;
或者是自己写一个List,让它携带一些标记信息来区分哪个是维护opt的,哪个是维护Attr的:

public MyList extends ArrayList implements List{
private Class elementClazz;

public Class getElementClass(){};
public void setElementClass(Class clazz){
..
}
}

当然,如果你使用的是JDK5.0的话,可以这么定义: ArrayList<elementClass>,这也可以取得元素类型.

再就是在确保List中有数据的时候随便其一个出来看看类型也行.

这两个List对应的EditPart就很简单了,就连figure也可以是简单的透明Rectangle.

上述方法虽然类似你的说的Cell的那种方案,但是你的模型就可以进行少量的更改

只有注册用户登录后才能发表评论。


网站导航: