月蚀传说

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

能够拖动的矩形

Posted on 2006-10-09 18:59 Dart 阅读(1647) 评论(6)  编辑  收藏 所属分类: GEF

这章我将说一下如何去实现一个数据表在编辑器上显示,并且能够进行位置和尺寸的改变。我们将涉及到的内容有:Figure,EditPolicy,Command。示例代码下载

1.在Editor上实现一个简单的数据表

上一章中,我们实现了一个空的Editor,画布上什么都没有,今天我会让它显示点东西。
我们已经说过了,Editor要显示一个图形,是根据我们给Viewer设置的模型,找到对应的EditPart,然后调用EditPart的createFigure得到图形,最后绘制在Editor上,大致如下:

http://album.sina.com.cn/pic/5414465b02000419



我们需要做的事情就让我们的TableEditPart复写基类的createFigure方法,然后返回一个Figure图形,最后再在代码中更改我们最初设置的模型内容。

创建TableFigure

一般来说,当我们需要生成一个Figure图形的时候,最好的办法就是从Figure类继承一个新的类出来,然后复写Figure的paintFigure方法,绘制出我们想要的图案。见下代码:

public   class  TableFigure extends Figure {

    
protected   void  paintFigure(Graphics graphics) {
        super.paintFigure(graphics);
        
//  得到Figure的Bounds
        Rectangle bounds  =  getBounds();
        
//  在它周围绘制一个矩形,宽度和高度稍微小一点,以便能全部显示
        graphics.drawRectangle(bounds.x,bounds.y,bounds.width  -   1  , bounds.height  -   1 );
    }
}


TableFigure 显示的是一个矩形图形。getBounds方法是得到的是Figure的范围对象,这个对象是一个Recangle类型,包括有图形的坐标(Point),以及尺寸(Dimension),而这个范围对象并不是我们去指定的,而是根据TableFigure的父Figure来给它设定的。这里需要说一下,在GEF中,EditPart在绘制Figure的时候,步骤是先绘制好自己的Figure,然后查看自己的子EditPart,获得他们的Figure,确定他们的大小位置(根据默认或者是本身Figure的布局管理器来得到)绘制在自身Figure之上,也就是说,这是一个递归的过程。

修改TableEditPart代码

我们已经生成了一个TableFigure类,现在我们需要让TableEditPart的createFigure方法返回这个类的一个实例。

public   class  TableEditPart extends DBEditPartBase {

    
protected  IFigure createFigure() {
        
//  返回Table的Figure
         return   new  TableFigure();
    }
}


重新设置模型

我们创建Editor的时候,在初始化Viewer的方法中,生成了一个Schema模型给它,现在我们需要把我们的Table模型添加上去,也就是说,让新的Table模型作为Schema的子对象添加进去

protected   void  initializeGraphicalViewer() {
        
//  硬编码生成一个数据库模型
        
//  这个数据库中有一个表
        Schema schema  =   new  Schema();
        Table table 
=   new  Table();
        
        schema.addChild(table);
        
this .getGraphicalViewer().setContents(schema);
    }


可以看得出来,我们其实只是修改了我们的模型,但是图形显示是和EditPart相关的,SchemaEditPart是如何得知它会拥有一个子EditPart —— TableEditPart的呢?仔细看下DBBaseEditPart就可以很清楚了。
由于我们复写了EditPart的getModelChildren,返回的是DBBaseEditPart对应模型的子模型,所以EditPart就可以通过这些子模型来得到子编辑单元(Chilren EditPart)

     protected  List getModelChildren() {

        
if (getModel() instanceof DBBase){
            
return  ((DBBase)getModel()).getChildren();
        }
        
return  super.getModelChildren();
    }


最后让我们运行一下,得到了一个绘制有矩形的Editor:

http://album.sina.com.cn/pic/5414465b0200041a



2.初步讨论EditPolicy

在刚才我们实现的编辑其中,已经将Table模型的图形显示了出来,但是仅仅只是有一个矩形画在画布上,当我们点击它的时候没有任何反应,好像完全只是一张静态的图片,我们要的效果的并不是这个,而是需要一个能够对其操作的图形。

第一章里我简单地提到了在GEF中事件处理的过程,看看前面的文章可以知道,我们想要能够对我们显示的TableFigure图形进行编辑,需要的是一个能够处理相关Request的EditPolicy。

EditPolicy的类型有很多,大致分为图形相关和图形无关两大类,我在这个例子中使用到了这几个图形相关的EditPolicy:LayoutEditPolicy ,NonResizeableEditPolicy。

LayoutEditPolicy 一般是作为父EditPart所具有的EditPolicy,就是说,如果我们的Figure需要对它的子EditPart进行一些图形方面管理的话,使用这个EditPolicy比较合适。它能够处理一些容器类EditPart的应该具备的操作:增加一个EditPart,删除一个EditPart,移动子EditPart对应图形等。

并且它还能够为它的子EditPart设置对应的EditPolicy,这样一来就统一了子EditPart的一些行为。一会我讲具体说一下这个问题。

NonResizeableEditPolicy,顾名思义,它是一个不处理尺寸变化的EditPolicy,但是它能够处理EditPart对应Figure位置变化,由于我们的数据表的大小需要根据它所拥有列来决定,外界不应该对它的尺寸进行修改,所以我在这里选用了它。

怎么使用EditPolicy呢?EditPolicy是被"安装"到EditPart上的,在EditPart中,有一个接口方法:createEditPolicies,我们要在这里面进行安装。安装EditPolicy使用installEditPolicy方法。

回过头再看看我们所要用的这两个EditPolicy,他们两个都应该安装到哪个EditPart上呢。很显然,LayoutEditPolicy应该安装到父EditPart,也就是SchemaEditPart,这样一来,SchemaEditPart就能对TableEditPart进行管理了; NonResizeableEditPolicy就应该属于TableEditPart,我们需要用它来改变TableEditPart对应Figure 的位置。

当然,LayoutEditPolicy和NonResizeableEditPolicy不能实例化后直接安装到 EditPart上,因为我们还需要复写他们的一些方法,稍后我们会提到。我们先创建两个类,分别继承LayoutEditPolicy, NonResizeableEditPolicy:

public   class  SchemaLayoutEditPolicy extends LayoutEditPolicy {

    
protected  EditPolicy createChildEditPolicy(EditPart child) {
               
return   null ;
    }
    
protected  Command getCreateCommand(CreateRequest request) {
               
return   null ;
    }

    
protected  Command getDeleteDependantCommand(Request request) {
                
return   null ;
    }

   
protected  Command getMoveChildrenCommand(Request request) {
               
return   null ;
    }

}

 

public   class  TableNonResizableEditPolicy extends NonResizableEditPolicy {

}


很明显,这辆个类只是继承了基类,并没有复写或实现基类的方法。
姑且这样,我们先把SchemaLayoutEditPolicy安装到SchemaEditPart上:

     protected   void  createEditPolicies() {
        
this .installEditPolicy(EditPolicy.LAYOUT_ROLE, new  SchemaLayoutEditPolicy());
    }

installEditPolicy 的第一个参数其实并没有什么用,这个参数是一个String类型,随便写个字符串也不会影响到我们EditPolicy的安装以及它的工作的(也有人说,如果第一个参数重复的话,EditPolicy会被覆盖掉。我没有研究过,各位朋友可以试一下)。

好了,SchemaEditPart所需要的EditPolicy已经搞定,剩下TableEditPart了。大家可能会认为,安装它的EditPolicy也和 SchemaEditPart一样,复写createEditPolicies方法,然后installEditPolicy即可。是的,这样没有问题,但是由于我们的SchemaLayoutEditPolicy中有这么一个接口方法:createChildEditPolicy,这就是我在前面所说的,为了统一管理,给子EditPart安装所对应的EditPolicy。虽然这样做和直接安装的效果差不多(应该是一样,但是也有可能有一些差别),但是我认为还是把子EditPolicy的安装交给父EditPolicy吧:

     protected  EditPolicy createChildEditPolicy(EditPart child) {
        
if (child instanceof TableEditPart)  return   new  TableNonResizableEditPolicy();
        
return   new  NonResizableEditPolicy();
    }

好了,让我们运行一下。呵呵,是不是可以点击我们的“数据表”了。但是这样还是不能移动我们的数据表。

3. 修改我们的类 ;Command 的使用


第一章我已经讲过了,我们的模型有时需要添加一些和模型本身无关但和图形有关的属性。因为这样一来我们就能够记录我们的图形发生的位置变化,再通过模型的改变去通知EditPart刷新我们的图形。
我们先为Table增加一个属性:location

     protected  Point location  =   new  Point( 0 , 0 );
    
/* *
     * @return 返回 location.
     
*/
    
public  Point getLocation() {
        
return  location;
    }
    
/* *
     * @param location 设置 location 
     
*/
    
public   void  setLocation(Point location) {
        Point old 
=   this .location;
        
this .location  =  location;
    }


这个属性代表了图形目前所在的位置坐标。

有朋友要问:这里只是有了属性,那当属性改变的时候怎么去通知呢?我记得我也在第一章讲了,一般的做法是为我们的模型增加一个属性更改的事件发生源:PropertyChangeSupport

我们把事件发生源写到基类DBBase中,并增加几个方法去发送事件以及添加删除监听器:

     public   static  final String PRO_FIGURE  =   " __figure__property " ;
    
    
private  PropertyChangeSupport support  =   new  PropertyChangeSupport( this );

 
public   void  addPropertyChangeListener(PropertyChangeListener l){
        support.addPropertyChangeListener(l);
    }
    
    
public   void  removePropertyChangeListener(PropertyChangeListener l){
        support.removePropertyChangeListener(l);
    }
    
    
public   void  fireFigurePropertyChange(Object old,Object now){
        support.firePropertyChange(PRO_FIGURE,old,now);
    }


好了,我们的事件源做好了,下面该想想让谁去监听了。

毫无疑问,我们的监听器应该是DBBaseEditPart,因为它才有能力去刷新Figure,所以我们需要更改DBBasEditPart代码,如下:

public   class  DBEditPartBase extends AbstractGraphicalEditPart implements PropertyChangeListener{
    
public   void  activate() {
        
if (getModel()  !=   null   &&  getModel() instanceof DBBase){
            ((DBBase)getModel()).addPropertyChangeListener(
this );
        }
        super.activate();
    }
     
public   void  deactivate() {
        
if (getModel()  !=   null   &&  getModel() instanceof DBBase){
            ((DBBase)getModel()).removePropertyChangeListener(
this );
        }
        super.deactivate();
    }
    
public   void  propertyChange(PropertyChangeEvent evt) {
       String pName 
=  evt.getPropertyName();
       
if (pName.equals(DBBase.PRO_FIGURE)){
           
this .refreshVisuals();
       }
    }
}


大家注意下propertyChange方法,当我们在获得事件类型为PRO_FIGURE后,就会直接去调用refreshVisuals去刷新我们的Figure。
但是refreshVisuals其实是空方法,它什么都没有做!

所以我们必须在TableEditPart中要复写它:

    protected   void  refreshVisuals() {
        super.refreshVisuals();
        
//  得到当前Figure的位置和大小
        Rectangle rect  =   this .getFigure().getBounds();
        
        
//  获得更改后的位置
        Point p  =  ((Table) getModel()).getLocation();
        
        
//  我们只更改Table的位置
        ((GraphicalEditPart)  this .getParent()).setLayoutConstraint( this this
                .getFigure(),
new  Rectangle(p, rect.getSize()));
    }

最后一句代码是什么含义呢?这是让TableEditPart去找到它的父EditPart,也就是SchemaEditPart,再让它去“约束” TableEditPart图形的位置和大小,当然了,我们这里没有改变大小,只是通过Table模型的Location属性去更改它的位置而已。当调用了setLayoutConstraint方法后,我们的图形就会自动进行重绘。

接下来,让Table模型中更改location属性时将更改事件发送出来,以便EditPart能够截获并处理:

    public void setLocation(Point location) {
        Point old 
= this.location;
        
this.location = location;
        
this.fireFigurePropertyChange(old,this.location);
    }


我们已经做了很多调整,改了不少代码了,这会运行看看吧!

对不起,我们的TableFigure还是不会移动!
这是由于我们忘记了在TableNonResizeableEditPolicy中做文章。

刚才已经说过了,TableNonResizeableEditPolicy能够处理对图形移动的,但是它只是通知我们图形移动了,要找我们索取一个 Command去执行这种变化。所以我们还需要写一个Command类,让这个Command去执行对模型位置的更改(关于Command的介绍请回过头看第一章):

public class TableMoveCommand extends Command {

    
private ChangeBoundsRequest request;
    
    
private Table model;

    
public void execute() {
      Point old 
= getModel().getLocation();
      
int x = request.getMoveDelta().x;
      
int y = request.getMoveDelta().y;
      
      getModel().setLocation(
new Point(old.x+x,old.y+y));
    }
}

然后我们在TableNonResizeableEditPolicy中复写getMoveCommand方法:

 protected Command getMoveCommand(ChangeBoundsRequest request) {

        TableMoveCommand command 
= new TableMoveCommand();
        command.setModel((Table)getHost().getModel());
        command.setRequest(request);
        
return command;
    }

好了!这会再运行看看,是不是能移动了?

4.结束语

我们今天把上次例子代码进行了一些修改,得到了一个能够随意改变位置的矩形,其中主要简单地讲述了一些EditPolicy如何使用。
本人文笔比较烂,说事情总说不清,如果有不清楚的地方请留言,我会进行修改,尽量让大家都能看懂。
以后的章节,我们会继续讨论EditPolicy以及Figure布局等问题。

评论

# re: 能够拖动的矩形  回复  更多评论   

2007-01-12 16:57 by 过客[匿名]
编辑器中,通过实现PaletteRoot工具,可以将自己定义的模型拖入到编辑器中,该模型以图形化的形式在编辑器中显示

那如何将视图中TreeViewer的节点(每个节点都有相应的模型对应)拖入到编辑器中呢?

# re: 能够拖动的矩形  回复  更多评论   

2007-03-13 15:58 by 匿名
根本允许起来后看不到矩形啊

# re: 能够拖动的矩形  回复  更多评论   

2007-03-13 17:16 by Dart
先确认,你用的Eclipse和GEF是什么版本的,这篇文章写得比较老了,Eclipse版本应该是3.0.1,GEF版本也应该是对应当时Eclipse版本的.
代码应该不会有问题,因为在这篇文章还没有丢失之前很多人都已经用过了

# re: 能够拖动的矩形  回复  更多评论   

2007-03-13 17:33 by 匿名
在3.2上面能运行起来嘛,第五篇的代码可以运行,但第二章的运行了没有看不到矩形.

# re: 能够拖动的矩形  回复  更多评论   

2007-03-14 13:01 by Dart
3.2可能跑不起来,我没试过

# re: 能够拖动的矩形  回复  更多评论   

2007-04-06 12:54 by xx
看不到矩形是因为没有设置矩形大小,默认的大小为0,0,看不到而已

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


网站导航: