【原创】GEF的模型和视图(二十三)

  GEF(Graphical Editor Framework)是一个图形化编辑框架,它允许开发人员以图形化的方式展示和编辑模型,从而提升用户体验。通过GEF,Eclipse的开发者能够快速开发出图形编辑器。

  GEF简介

  GEF最早是Eclipse的一个内部项目,后来逐渐转变为Eclipse的一个开源工具项目,Eclipse的不少其他子项目都需要它的支持。

  GEF的优势是提供了标准的MVC(Model-View-Control)结构,即模型、视图和控制器(在此视图和插件的视图是两个完全不同的概念)。开发人员可以利用GEF来完成以上这些功能,而不需要自己重新设计。与其他一些MVC编辑框架相比,GEF的一个主要设计目标是尽量减少模型和视图之间的依赖,好处是可以根据需要选择任意模型和视图的组合,而不必受开发框架的局限。

  GEF工作原理

  GEF中模型和视图是两个完全没有联系的实体,两者都是通过控制器间接进行通信的。这样很好地解决了模型和视图之间的耦合,但是也增加了控制器的负担。GEF通过请求和编辑策略及命令模式很好地解决了控制器的负担,使每一个部分保持最小的依赖关系。GEF的命令请求及响应流程如下。

  1. GEF框架接收用户的操作,并把操作转换成相应的请求。

  2. 控制器接收请求并把请求交由相应的编辑策略管理器处理。

  3. 编辑策略管理器根据请求的类型新建相应的命令处理请求。

  4. GEF框架执行命令,命令负责对模型的修改。

  5. 模型属性修改后,触发事件(控制器就是模型的监听器)。

  6. 监听器(控制器就是模型的监听器)监听到事件后处理相应的操作,并通知视图进行修改。

  7. 视图更新显示界面,使之与模型同步。

  创建GEF编辑器,首先Eclipse创建控制器(EditorPart)设置好控制器的输入(IEditorInput),另外控制器将初始化EditDomain、viewers和palette等,如图1所示。

13081368_200807150855481.jpg

图1 GEF结构图

  模 型

  GEF的模型只与控制器打交道,而不知道任何与视图有关的东西,具体介绍如下。

  模型的功能

  在GEF框架中,模型(Model)是非常简单的一部分,用户可把模型理解成一个简单的可持久化的实体。GEF的模型只与控制器打交道,而不知道任何与视图有关的东西。

  但为了能让控制器知道模型的变化,应该把控制器作为事件监听者注册在模型中,当模型发生变化时,就触发相应的事件给控制器,后者负责通知视图进行更新。

  模型的实现

  典型的模型对象会包含PropertyChangeSupport类型的成员变量,用来触发事件,通知监听器即控制器。一般来说,模型会实现一个基类,用来封装事件机制,主要包括添加监听器、删除监听器,以及模型属性改变触发的相应事件,代码如例程1所示。

  例程1 Element.java

  public abstract class Element implements Cloneable, Serializable {

  PropertyChangeSupport listeners = new PropertyChangeSupport(this);

  //添加监听器

  public void addPropertyChangeListener(PropertyChangeListener l) {

  listeners.addPropertyChangeListener(l);

  }

  //触发属性改变的事件

  protected void firePropertyChange(String prop, Object old, Object newValue) {

  listeners.firePropertyChange(prop, old, newValue);

  }

  //触发结构改变的事件

  protected void fireStructureChange(String prop, Object child) {

  listeners.firePropertyChange(prop, null, child);

  }

  //删除监听器

  public void removePropertyChangeListener(PropertyChangeListener l) {

  listeners.removePropertyChangeListener(l);

  }

  }

  另外,当用户希望通过属性视图编辑模型属性时,模型要实现IPropertySource接口,IPropertySource接口的方法解释如下。

  (1)getEditableValue:方法定义为“public Object getEditableValue();”,表示得到在属性页中能编辑的值,可以返回this表示当前模型。

  (2)getPropertyDescriptors:方法定义为“public IPropertyDescriptor[] getPropertyDescriptors();”,表示得到IPropertyDescriptor类型的数组,其中每一项为属性页中能编辑的项。

  (3)getPropertyValue:方法定义为“public Object getPropertyValue(Object id);”,表示通过id值得到某个属性,在添加每一项IPropertyDescriptor时都会指定id值。

  (4)isPropertySet:方法定义为“public boolean isPropertySet(Object id);”,表示特定属性id值是否改变。

  (5)setPropertyValue:方法定义为“public void setPropertyValue(Object id, Object value);”,表示通过id和值设置某一项属性的值。

  (6)resetPropertyValue:方法定义为“public void resetPropertyValue(Object id);”,通过id重置属性。

  如果模型和其他模型有关系(在视图上为连线),模型要维护这些关系,并适当地持久化,当模型的属性修改后,模型要触发相应的事件通知监听器。一个简单的模型实现如例程2所示。

  例程2 Node.java

  public class Node extends Element implements IPropertySource {

  //定义属性的常量ID

  final public static String PROP_LOCATION = "LOCATION";

  final public static String PROP_NAME = "NAME";

  final public static String PROP_VISIBLE = "VISIBLE";

  final public static String PROP_INPUTS = "INPUTS";

  final public static String PROP_OUTPUTS = "OUTPUTS";

  protected Point location = new Point(0, 0);

  protected String name = "Node";

  protected boolean visible = true;

  //定义属性的编辑项

  protected IPropertyDescriptor[] descriptors = new IPropertyDescriptor[] {

  new TextPropertyDescriptor(PROP_NAME, "Name"),

  new ComboBoxPropertyDescriptor(PROP_VISIBLE, "Visible", new String[]

  { "true", "false" }) };

  //定义模型连线的列表

  protected List utputs = new ArrayList(5);

  protected List inputs = new ArrayList(5);

  public void addInput(Connection connection) {

  this.inputs.add(connection);

  //当输入的连线改变后,触发结构改变的事件

  fireStructureChange(PROP_INPUTS, connection);

  }

  public void addOutput(Connection connection) {

  this.outputs.add(connection);

  //当输出的连线改变后,触发结构改变的事件

  fireStructureChange(PROP_OUTPUTS, connection);

  }

  public List getIncomingConnections() {

  return this.inputs;

  }

  public List getOutgoingConnections() {

  return this.outputs;

  }

  public void removeInput(Connection connection) {

  this.inputs.remove(connection);

  fireStructureChange(PROP_INPUTS, connection);

  }

  public void removeOutput(Connection connection) {

  this.outputs.remove(connection);

  fireStructureChange(PROP_OUTPUTS, connection);

  }

  public boolean isVisible() {

  return visible;

  }

  public void setVisible(boolean visible) {

  if (this.visible == visible) {

  return;

  }

  this.visible = visible;

  firePropertyChange(PROP_VISIBLE, null, Boolean.valueOf(visible));

  }

  public String getName() {

  return name;

  }

  public void setName(String name) {

  if (this.name.equals(name)) {

  return;

  }

  this.name = name;

  //当模型名字改变后,通知监听器更新名字的显示

  firePropertyChange(PROP_NAME, null, name);

  }

  public void setLocation(Point p) {

  if (this.location.equals(p)) {

  return;

  }

  this.location = p;

  //当模型位置改变后,通知监听器更新模型的位置

  firePropertyChange(PROP_LOCATION, null, p);

  }

  public Point getLocation() {

  return location;

  }

  public Object getEditableValue() {

  return this;

  }

  //得到属性编辑项

  public IPropertyDescriptor[] getPropertyDescriptors() {

  return descriptors;

  }

  //得到属性编辑项的值

  public Object getPropertyValue(Object id) {

  if (PROP_NAME.equals(id))

  return getName();

  if (PROP_VISIBLE.equals(id))

  return isVisible() ? new Integer(0) : new Integer(1);

  return null;

  }

  public boolean isPropertySet(Object id) {

  return true;

  }

  public void resetPropertyValue(Object id) {

  }

  //设置属性的值

  public void setPropertyValue(Object id, Object value) {

  if (PROP_NAME.equals(id))

  setName((String) value);

  if (PROP_VISIBLE.equals(id))

  setVisible(((Integer) value).intValue() == 0);

  }

  }

  上例中实现了模型对应的属性视图,另外当属性被修改后,模型将会调用“fire…”方法触发相应的模型改变事件。

  在GEF的模型中,要实现一种事件的通知机制,把模型相关的控制器注册为模型的监听器。当模型的属性被修改后,会触发相应的事件,监听器监听到事件后,会根据事件做出相应的处理。

  视 图

  GEF视图通常利用Draw2d图形(IFigure)作为表现方式,用户可以通过Draw2d的功能实现图形的展示、布局及图形的编辑。

  视图的功能

  视图(View)是模型图形方式的表现,它以图形的方式展示模型。视图的作用和JFace中的Viewer十分类似,而控制器(EditPart)就相当于是它的ContentProvider和LabelProvider,通过setContents()方法来指定视图的输入。

  GEF经常使用的Editor是一个GraphicalEditorWithPalette(GEF提供的Editor,是EditorPart的子类,具有图形化编辑区域和一个工具条)的实例,这个Editor使用GraphicalEditViewer和PaletteViewer这两个视图类,PaletteViewer也是GraphicalEditViewer的子类(表示选项板视图)。开发人员要在configureGraphicalViewer()和initializeGraphicalViewer()这两个方法里对EditPartViewer进行定制,包括指定它的contents和EditPartFactory等。

  EditPartViewer同时也是ISelectionProvider,这样当用户在编辑区域做选择操作时,注册的SelectionChangeListener就可以收到选择事件。EditPartViewer会维护各个EditPart的选中状态,如果没有被选中的EditPart,则默认选中的是作为contents的EditPart。

  视图的实现

  视图的实现相对模型来说较为复杂,除了模型的显示功能以外,还要提供编辑功能、回显(Feedback)、工具提示(ToolTip)等。

  在GEF中,通常视图是利用Draw2d图形(IFigure)作为表现方式,用户可以通过Draw2d的功能实现图形的展示、布局及图形的编辑。如例程3显示的是一个带边框的视图的实现。

  例程3 KCGContentFigure.java

  public class KCGContentFigure extends Figure {

  public static Color classColor = new Color(null, 255, 255, 206);

  public KCGContentFigure() {

  //指定视图的布局

  FillLayout layout = new FillLayout();

  layout.setMinorAlignment(ToolbarLayout.ALIGN_TOPLEFT);

  layout.setStretchMinorAxis(false);

  setLayoutManager(layout);

  //设置图形的边框

  setBorder(new CompartmentFigureBorder());

  }

  public class CompartmentFigureBorder extends AbstractBorder {

  public Insets getInsets(IFigure figure) {

  return new Insets(1, 0, 0, 0);

  }

  //重画图形的边框

  public void paint(IFigure figure, Graphics graphics, Insets insets) {

  graphics.drawLine(getPaintRectangle(figure, insets).getTopLeft(),

  tempRect.getTopRight());

  }

  }

  }

  视图是可以复用的,例如,如果有不同的列模型,分别表示整型、日期型和字符型等模型,它们的区别在于显示图标和显示文本,则控制器在创建视图时可以用同一视图类,设置不同的显示图标和显示文本即可。

fj.pngimage001.jpg

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/13081368/viewspace-401622/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/13081368/viewspace-401622/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值