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

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

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

1.重新显示TableFigure

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

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



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

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

数据库字段的显示以及增加_第1张图片


看来我们以前写的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( this this
                .getFigure(),
new  Rectangle(p, size));
    }


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

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

数据库字段的显示以及增加_第2张图片


补充一下:篇幅问题,我只捡我认为重要的说,其他的一些细节,比如,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. class return   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;
    }


运行一下:

数据库字段的显示以及增加_第3张图片

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 可以在它可以插入的位置显示一条黑线:

数据库字段的显示以及增加_第4张图片

我们的这个类就命名为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了。

让我们看看整体效果:


数据库字段的显示以及增加_第5张图片


6 . 结束语

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

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

你可能感兴趣的:(数据库字段的显示以及增加)