GEF理解系列三

经过以上步骤,我们已经有办法显示多个图形了。还是重提一下:Editor的初始状态由 initializeGraphicalViewer()方法决定。不过在我们已经完成的过程,我们会发现,初始状态,也是最终状态,我们甚至不能移动任何一个图,当然了也不能增加和删除。下面我们要逐一的实现这些功能。
在继续之前,我们先了解一下GEF执行操作的过程,看下图:

gefmvc

图一

从上图可以看到。GEF把SWT事件包装成一个个的request发送到Controller(EditPart)上,通过Controller(EditPart)来操作、修改模型和视图。但是Controller(EditPart)并不直接进行操作。而是由安装在Controller(EditPart)上的一个个Policy来完成的。每个Controller(EditPart)上会安若干个Policies。当然了Policy其实也不直接完成操作,而只是返回一个对应的Command,由Command去实现真正的操作。

editPartPolicies

图二

在GEF中,实现任何操作,都是同样的路子:安装对应的Policy-->返回某个Command-->执行(由框架调)

我们应该还记得在之前的篇段里,我们一直没讲到EditPart的一个方法:createEditPolicies()。下面,就要开始用到这个方法了。

 

一、图形的移动与缩放

首先需要明白的一点就是:对子图形的改变大小、变换位置的操作都是由父模型来负责完成的,因为子图形是附着在父图形上来显示。如果要实现对子模型的改变大小、变换位置的操作,我们需要在父模型的editpart上安装一个policy;关于这一点,勇哥给了我一个很好的启示:这就像是Windows的文件系统一些,所以添加、删除文件,都是在它的父文件夹中进行的,否则的话,你就变成修改文件了。

可能初学的一个难点就是要找到一个直接对应的Policy。在EditPolicy里已经定义了一些Policy的Key常量。每个常量对应的Policy的名字通常和常量的名字相同。例如:

常量名

类名

CONNECTION_ROLE

ConnectionEditPolicy

GRAPHICAL_NODE_ROLE

GraphicalNodeEditPolicy

COMPONENT_ROLE

ComponentEditPolicy

CONTAINER_ROLE

ContainerEditPolicy

DIRECT_EDIT_ROLE

DirectEditPolicy

。。。。。。

。。。。。。

表一
所以如果我们选择了某个常量,就能知道应该选择哪个对应的EditPolicy;另外Policy名字有时也就决定了它的作用,所以要找到一个正确的Policy也不是太难。我个人觉得如果真的不知道应该选择哪个Policy,那就把觉得可能的一个一个的试,因为每个EditPolicy继承时都会有一些要求实现的方法,例如GraphicalNodeEditPolicy需要实现的方法有:
  • protected Command getConnectionCompleteCommand(CreateConnectionRequest request) ;
  • protected Command getConnectionCreateCommand(CreateConnectionRequest request) ;
  • protected Command getReconnectSourceCommand(ReconnectRequest request) ;
  • protected Command getReconnectTargetCommand(ReconnectRequest request) ;

很显然,如果是要实现创建连接和重连的话,应该安装这个Policy,所以对应的安装就应该是:

installEditPolicy(EditPolicy.GRAPHICAL_NODE_ROLE, new **GraphicalNodeEditPolicy());

好了,废话少说。继续我们的事情。

要实现图形的移动和缩放,需要安装的Policy是LayoutEditPolicy,所以我们需要实现一个LayoutEditPolicy的子类,不过这里,我们要注意的是,我们应该继承的类为:XYLayoutEditPolicy。这是LayoutEditPolicy的一个子类。

在实现之前,我们先想一下,要实现移动或者是缩放,我们需要哪些东西。很显示,我们要有一个需要将要移动或缩放的结点对象吧,对吧?另外,我们得知道移动或缩放后的位置大小吧,对吧?所以我们的command如果想完成这个任务,那么它至少需要知道这两个东西。所以我们的Policy可以如下实现了:

 

public class DiagramLayoutPolicy extends XYLayoutEditPolicy {
 @Override
 protected Command createChangeConstraintCommand(EditPart child,
   Object constraint) {
  ResizeHelloWorldCommand command = new ResizeHelloWorldCommand(
    (HelloWorldModel) child.getModel(), (Rectangle) constraint);
  return command;
 }
 @Override
 protected Command getCreateCommand(CreateRequest request) {
  return null;
 }
}
 

 

然后完成createChangeConstraintCommand(EditPart child,   Object constraint) 方法,返回一个Command即可。如上我们已经返回了一个Command。下面我们就要实现这个Command。实现Command本身是很简单的。就是把模型设置为当前的大小。如下:

 

public class ResizeHelloWorldCommand extends Command {
 private HelloWorldModel model;
 private Rectangle constraints;
 private Rectangle old;
 public ResizeHelloWorldCommand(HelloWorldModel model, Rectangle constraints) {
  super();
  this.model = model;
  this.constraints = constraints;
 }
 @Override
 public void execute() {
  old = model.getConstraints();
  model.setConstraints(constraints);
 }
 @Override
 public void undo() {
  constraints = model.getConstraints();
  model.setConstraints(old);
 }
}
 

 

这里我们加了一个对象:old。这是为了实现重做与撤消用的。

现在我们试一下效果:

resize1

图三

图形已经可以缩放和拖动了。但是当我们把鼠标松开时,模型又回到了原处,变回了原来的大小了;打印一下可以知道:在模型中,图形的位置确实变了。这是怎么回事呢?

二、增加通知机制

如上,模型是发生了变化。因为这是一个MVC结构,图形并不知道模型的变化,所以图形不发生改变。要让图形发生变化。我们就需要有一个通知机制了。

一般来说,对模型有改变,都需要通过一种机制去通知视图进行刷新。在GEF中,通常这是通过属性改变监听机制来完成的。例如,当模型的constraint改变时,我们可以加一个监听。

这里的一个标准模式是:

1.模型做为事件源,由模型添加事件和发出事件

2.模型对应的EditPart作为事件处理者(因为模型不知道视图的存在)

通常我们可以用一个抽象类来专门处理属性改变,让所以模型继承这个类,例如:  

 

public abstract class AbstractModel {
       private PropertyChangeSupport support = new PropertyChangeSupport(this);
 
       public void firePropertyChange(String propertyName, Object oldValue,
                     Object newValue) {
              support.firePropertyChange(propertyName, oldValue, newValue);
       }
 
       public void addPropertyChangeListener(PropertyChangeListener listener) {
              support.addPropertyChangeListener(listener);
       }
 
       public void removePropertyChangeListener(PropertyChangeListener listener) {
              support.removePropertyChangeListener(listener);
       }
}

 

 

添加监听和删除监听的地方在对应的editpart里,此时editpart本身要实现监听接口PropertyChangeListener,然后让模型的监听对象设为模型对应的EditPart。在EditPart里重写active()和deactive()方法,分别加上监听和移除监听;然后实现propertyChange方法,例如:

@Override
public void activate() {
       super.activate();
       ((HelloWorldModel)getModel()).addPropertyChangeListener(this);
}
 
@Override
public void deactivate() {
              ((HelloWorldModel)getModel()).removePropertyChangeListener(this);
       super.deactivate();
}
public void propertyChange(PropertyChangeEvent evt) {
              if(evt.getPropertyName().equals(HelloWorldModel.P_CONSTRAINT)){
                     refreshVisuals();
       }
}
 

 

这里的active()方法是当EditPart激活时调用,deactive()方法当EditPart钝化时调用,有点像Plugin类里的start()和stop()方法。

上面说的有点乱,实际上那就是一个固定的样子,稍后我们会总结,理解后就很好写了。 

好,为了实现更改大小和位置,我们首先要修改我们的模型,我们按照上面的样子写一个AbstractModel,然后让所有的模型继承这个类。

现在,我们有了一套通过机制了。下一步就是在我们所有修改了模型属性的方法里,利用这套机制,生成事件,例如HelloWorldModel更改大小事件:

 public static final String P_CONSTRAINT = "p_constraint";
 public void setConstraints(Rectangle constraints) {
  Rectangle old = getConstraints();
  this.constraints = constraints;
  firePropertyChange(P_CONSTRAINT, old, constraints);
 }
 

修改HelloWorldEditPart:

1.首先让它实现接口:PropertyChangeListener

2.实现方法propertyChange:根据对应的属性事件名,执行不同的方法。例如:

 public void propertyChange(PropertyChangeEvent evt) {
  if(evt.getPropertyName().equals(HelloWorldModel.P_CONSTRAINT)){
   refreshVisuals();
  }
 }

我们先把这个refreshVisuals()方法写出来:

 @Override
 protected void refreshVisuals() {
  super.refreshVisuals();
  HelloWorldModel helloWorldModel = (HelloWorldModel) getModel();
  ((GraphicalEditPart) getParent()).setLayoutConstraint(this,
    getFigure(), helloWorldModel.getConstraints());
 }
 

这里,按照我们之前说过的,子模型视图的大小和位置是通过父对象来设置的。因此我们重写refreshVisuals()方法,在方法里用父对象来更改子对象的图形大小和位置。这样在系列二中的那个(临时方式)就可以了不要了。因为每次创建图形的时候refreshVisuals()方法会自动被调到。

OK,最后就是添加监听事件了。

 @Override
 public void activate() {
  super.activate();
  ((HelloWorldModel)getModel()).addPropertyChangeListener(this);
 }
 
 @Override
 public void deactivate() {
  ((HelloWorldModel)getModel()).removePropertyChangeListener(this);
  super.deactivate();
 }
 

最后再运行试一下效果:

resize2

图四

再总结一下要实现某个编辑操作的整个过程:

1。在对应的EditPart上安装某个Policy。

2。在Policy的实现里实现对应的方法。

3。在方法实现中,返回某个具体实现功能的Command。

4。在Command的实现中,通过调用模型提供的接口方法实现对模型的修改。

5。给模型添加一个PropertyChangeSupport对象,和三个方法:发出事件,添加监听,移除监听。每个方法调用PropertyChangeSupport对象的对应的方法。

6。在模型中的每个接口方法中都调用上面的Support对象发出事件

7。模型对应的EditPart实现PropertyChangeListener,然后完成方法propertyChange():根据不同的事件名,执行不同的操作。

8。添加监听:重写EditPart的active()方法和deactive()方法。其中在active()方法中添加监听,在deactive()方法中移除监听。

that's all!

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值