如何使用EclipseCNF的SaveableProtocol实现对View的保存

EdiTor 和 View 是 Eclipse 中用于展示和管理资源的两种 UI 元素。EdiTor 提供了一套方便的机制帮助用户实现对资源的修改及保存。但对于 View,它在很大程度上提供是对资源的树形展示,那如何将对资源的修改在 View 上反映出来,并通过对 View 的操作来保存 View 中的资源呢? Common NavigaTor Framework(CNF)提供了不同于 EdiTor 的资源保存机制 (Saveable Protocol) 来帮助用户完成对 View 中资源的保存。

CNF 的介绍

Common NavigaTor Framework (CNF) 是一套帮助用户开发基于 eclipse 的内容导航视图的框架,通过这套框架开发者可以迅速地将特定的资源与模型无缝地集成到 eclipse 中,并利用其提供的的 API 以树型的结构展示出来。CNF 最初来源于 Rational® Application Developer (RAD) v6.0 项目,并使用于 Eclipse 3.2。

接下来,简要地介绍如何使用 CNF 为已存在的模型构造资源导航视图。首先,利用 org.eclipse.ui.navigaTor 扩展点指定资源导航器所使用的 View,通过 CNF 框架,用户不必自已重新实现一个新的 View,只需将扩展点的 View 实现类指明为 org.eclipse.ui.navigaTor.CommonNavigaTor,如下图所示。

图 1. org.eclipse.ui.navigaTor.CommonNavigaTor 扩展点

接着,通过 org.eclipse.ui.navigaTor.navigaTorContent 指明将要在 View 中展现的内容,包括 actionProvider,commonFilter, commonWizard, navigaTorContent. 其中,在 navigaTorContent 中,用户可以定义 ContentProvider 和 LabelProvider,来展示资源导航器中的不同结点,并通过指定触发条件来控制内容的展现时机。如下图所示,当定义的 triggerPoints 表达式为真时,provider 的 getElements() 和 getChildren() 的方法将会被调用。

图 2. triggerPoints 属性

然后,通过 org.eclipse.ui.navigaTor.viewer 扩展点,将要展现的内容绑定到 view 上,用户不再需要通过硬编程(hard-code)的方式将 ContentProvider 和 LabelProvider 注册到特定的 View 上。在 org.eclipse.ui.navigaTor.viewer 扩展点上,我们需要指定 viewerContentBinding 来设定导航器中内容的可见性,其中 includes 语句表明该内容在 view 上为可见,pattern 为预先定义好的展示内容的 id。

EdiTor 的保存

在 Eclipse 中,用于展示和修改模型内容的 UI 容器包括编辑器(ediTor)与视图(view),如下图所示。我们可以通过继承抽象类 EdiTorPart 和 ViewPart 来定制所需要编辑器与视图来完成模型的修改与保存。

图 3. WorkbenchPart 的继承关系

EdiTorPart 中几个重要的方法:

publicabstractbooleanisDirty(): 用于表明编辑器中的内容是否发生修改,当编辑器的内容发生修改时,编辑器的标题栏显式地出现“*”号,同时,主菜单下“文件”下的全局“保存”按钮变为可用。当编辑器中的内容发生改变时,isDirty 方法不会自动变调用。因此我们要对可修改的 UI 元素,如 Text, CheckBox 等注册事件监听器,当修改发生时,由监听器将编辑器的 dirty 标志位置为 true。由于 isDirty() 在编辑器的生命周期中会被频繁地调用,因此不宜在这种方法中加入过多的执行语句,否则会影响程序的执行速度。

publicabstractvoiddoSave(IProgressMoniTor moniTor): 在 isDirty 返回 true 的情况下,当用户点击保存或使用快捷键 Ctrl+S 时,该方法会被调用,当保存模型的代码成功执行时,我们需要将编辑器的 dirty 标志位重新设置为 false,同时调用 firePropertyChange() 方法将编辑器的界面状态更新,此时标题栏的星号(*)消失。

publicabstractbooleanisSaveAsAllowed(): 表明编辑器的“另存为”按钮是否可用。

publicabstractvoiddoSaveAs(): 在 isSaveAsAllowed() 返回 true 的情况下,用户点击“另存为”,doSaveAs() 方法将被调用。与 doSave 方法类似,用户可以在该方法里实现对模型的保存逻辑。一般情况下我们可能复用 doSave 的逻辑完成对模型内容的另存为。

protectedvoidfirePropertyChange(finalintpropertyId):当编辑器属性发生变化时,可以通过调用该方法通知所注册的监听器。例如,当修改发生时,在编辑器标题前出现的“*”前缀。

CNF 的 Saveable Protocol 的实现原理

与 EdiTor 的保存不同,View 往往是及时保存,即 view 上的修改在完成时就保存了,如我们选择了导航器上某个结点,并通过 PropertiesView 修改了结点的属性,例如结点的名字时,此时,属性的修改便及时地反映到导航器上。这是 Eclipse 应用开发所倡导的最佳实践之一,因为视图的主要用于对模型的导航,而不是对模型进行修改。因此,在 ViewPart 的实现上并不提供 doSave(),doSaveAs() 来对模型进行保存。

然而,一些 Eclipse 应用希望通过 view 来完成对模型结点的保存,例如,用户同时在 ediTor 上对几个不同的结点进行编辑,当编辑结束时,用户只想保存其中几个 ediTor 的修改,些时,如果只是通过逐一地对每个 ediTor 进行保存,这将大大地影响操作的效率。由于导航器起着对结点的导航功能,如果能通过在导航器上完成对多个不同结点的保存,将大大方便用户的操作。

ContentProvider 类用于帮助 CommonViewer 访问树型结点元素的,在 CNF 中,如果 Viewer 上的元素可以被保存,则该类必须实现 IAdaptable 可适配于 SaveablesProvider 实例。SaveablesProvider 将要保存的模型与树型结点元素进行映射,用于为导航器提供可保存的对象。SaveblesProvider 包含以下几个关键的方法:

public abstract Saveable[] getSaveables():返回该 provider 所能访问到的所有对象。

public abstract Object[] getElements(Saveable saveable):返回可保存对象所对应的树型结点上的模型元素。

public abstract Saveable getSaveable(Object element):返回树型结点元素所对应的可保存元素。

final protected void fireSaveablesOpened(Saveable[] models):通知所注册的监听器参数数组中的可保存的模型元素已经被打开。

final protected boolean fireSaveablesClosing(Saveable[] models, boolean force):通知所注册的监听器参数数组中的可保存的模型元素正在被关闭。

final protected void fireSaveablesClosed(Saveable[] models):通知所注册的监听器参数数组中的可保存的模型元素已经关闭。

其中,fire* 方法必须在 UI 线程中被执行。同时,在 CommonNavigaTor 实现了 ISaveablesSourcer 接口,用于提供可保存对象。

Saveable[] getSaveables():返回所有可保存的模型元素。当其中的元素发生改变时,navigaTor 会通知所注册的监听器做出相应的反应。Saveable[] getActiveSaveables():返回当前处于活动状态的可保存元素,所返回的元素基于用户当前所选择的元素。

图 4. Saveables 框架的调用过程

如上图所示,当所需要保存的元素发生改变时,调用 CommonNavigaTor 的 firePropertyChange 方法,表明其中的元素发生了变化,些时注册在其中的监听器,如 SaveAction, SaveAllAction 会通过 CommonNavigaTor 的 getActiveSaveables() 计算是否有可保存的元素发生修改,如果有元素发生修改,更新 SaveAction 与 SaveAllAction 的可用状态,如果有可保存的元素,NavigaTor 的标题栏也将出现“*”,表明其为可保存的状态。

当用户选择所需要保存的元素时,并选择保存时,由 SaveableProvider 返回可保存的 Saveable 对象,由 CommonNaviagaTor 的 Saveables 框架调用对象的 doSave 方法进行保存。

实例说明

本节通过一个简单的例子来说明白如何何使用 CommonNavigaTor 的 Saveable Protocol. 在这个例子中的模型部分,包括文件夹结点和文件结点,其中文件结点可以通过编辑器进行编辑,文件内容发生改变时,相应地导航器上的结点名称将发生变化,当焦点处于导航器结点视图时,Save 与 SaveAll 按钮状态将随着所选择的结点的变化而变化。

图 5. 可保存的 NavigaTor 视图

第一步:创建视图 (view),这部分通过视图扩展点的实现,其中对指定的视图实现类继承 CommonNavigaTor,并重写它的 getSaveables 方法,在本文的例子中,由于框架的 getActiveSaveables() 将返回处于活动状态的 getSaveables,因此我们将处于活动状态的 Saveables 返回。

public class SaveableView extends CommonNavigaTor {   public static String ID = "ViewSaveableProtocol.SaveableView";   public Saveable[] getSaveables() {     return this.getActiveSaveables();   }   public void fireSaveabelsChanged(){     this.firePropertyChange(IWorkbenchPartConstants.PROP_DIRTY);   }  }

第二步:为导航器上添加 ContentProvider 和 LabelProvider, 在 providesSaveables 属性上,将其值指明为 true. 同时 ContentProvider 属性所对应的类必须实现 IAdatpable 接口,能够适配于 SaveablesProvider 类型。

图 6. contentNavigaTor 扩展点的 providesSaveables 属性

清单 1. 样例代码

public class SaveableContentProvider extends SaveablesProvider implements             ITreeContentProvider, IAdaptable {   @Override   public Object[] getElements(Saveable saveable) {     if(saveable instanceof SaveablePart){       IWorkbenchPart part = ((SaveablePart)saveable).getWorkbenchPart();       IEdiTorInput ediTorInput = ((TextFileEdiTor)part).getEdiTorInput();       TextFile file = ((TextFileEdiTorInput)ediTorInput).getTextFile();       return new Object[]{ file };     }     return null;   }   @Override   public Saveable getSaveable(Object element) {     if (element instanceof TextFile) {       IWorkbenchPart part = FolderManager                       .getWorkbenchPart((TextFile) element);       if(part != null){         final SaveablePart saveable = new SaveablePart(part);         return saveable;       }     }     return null;   }   @Override   public Saveable[] getSaveables() {     Object [] parts = FolderManager.getAllOpenedWorkbenchPart();     final Saveable[] saveables = new Saveable[parts.length];     return saveables;   }  }

第三步:对视图中树型结点元素进行修饰,当对应的可保存元素发生修改后,其名称以“*”作为后缀,当修改被保存后,后缀“*”号消失。该功能主要通过 org.eclipse.ui.decoraTors 扩展点实现。

图 7. DecoraTor 扩展点

在上图中,objectClass 属性指明的是所要修饰对象的类型。class 属性指明的修饰的具体实现类,Eclipse 框架为我们提供了轻量级的修饰机制,只需将 lightweight 属性值指明为 true,同时,将所要提供的修饰类实现 ILightweightLabelDecoraTor 接口,框架就能对树型结点元素提供前缀、后缀、重叠图片的修饰。在本文的例子中,当模型元素对应的 eidTor 发生修改时,树型导航器上结点的名称将以“*”作为后缀。具体代码如下:

清单 2. 样例代码

public class FileModifiedDecoraTor extends LabelProvider implements        ILightweightLabelDecoraTor {   @Override   public void decorate(Object element, IDecoration decoration) {     if (element instanceof TextFile) {       TextFileEdiTor ediTor = (TextFileEdiTor) FolderManager                     .getWorkbenchPart((TextFile) element);       if (ediTor != null && ediTor.isDirty())          decoration.addSuffix("*");       }     }   public void refreshDecoraTor(final Object element) {     Display.getDefault().asyncExec(new Runnable() {       public void run() {         fireLabelProviderChanged(new LabelProviderChangedEvent(                     FileModifiedDecoraTor.this, element));       }     });   }  }

第四步,关联保存模型与 UI 展示,当所要保存的元素发生改变时,更新 NavigaTor 视图的标题的状态,同时 Save,SaveAll 菜单项将根据用户选择的结点,更新其状态。具体步骤如下:

当用户通过编辑器对模型元素内空进行修改时,通知编辑器、视图、元素修饰器,使其作出相应的变化,如编辑器与视图标题将以“*”作为前缀,树型结点上的名称将以“*”作为后缀。代码片段如下: 清单 3. 样例代码

public class TextFileEdiTor extends EdiTorPart{   @Override   public void doSave(IProgressMoniTor moniTor) {     dirty = false;     PlatformUI.getWorkbench().getDisplay().asyncExec( new Runnable() {       public void run() {         firePropertyChange(IEdiTorPart.PROP_DIRTY);         // Notify the decoraTor;         refreshDecoration();         // Notify the content navigaTor.         FolderManager.fireSaveablesDirtyChanged();       }     });   }  }  public class FolderManager {   public static void fireSaveablesDirtyChanged() {     final IViewPart view = PlatformUI.getWorkbench()        .getActiveWorkbenchWindow().getActivePage().findView(SaveableView.ID);     if (view != null) {       Display.getDefault().syncExec(new Runnable() {         public void run() {           ((SaveableView) view).fireSaveabelsChanged();         }       });     }    }  }

当元素保存时,由 SaveableContentProvider 返回可保存的实现 Saveable 对象。其中 Saveable 对象的实现类片段如下:

public class SaveablePart extends Saveable{    private IWorkbenchPart part;   public SaveablePart(IWorkbenchPart part) {     this.part = part;   }   public void doSave(IProgressMoniTor moniTor) {      if (part instanceof ISaveablePart) {       ISaveablePart saveable = (ISaveablePart) part;       saveable.doSave(moniTor);     }   }   public boolean isDirty() {     if (part instanceof ISaveablePart) {       return ((ISaveablePart) part).isDirty();     }     return false;   }   public IWorkbenchPart getWorkbenchPart(){     return this.part;   }  }

保存完毕后,通知编辑器、视图、模型元素标题作出相应的修改。代码片段如下:

清单 4. 样例代码

public class TextFileEdiTor extends EdiTorPart{   @Override     public void createPartControl(Composite parent) {     parent.setLayout(new FillLayout());     textSect = new Text(parent, SWT.MULTI);     textSect.addModifyListener( new ModifyListener() {       @Override       public void modifyText(ModifyEvent e) {         dirty = true;         PlatformUI.getWorkbench().getDisplay().asyncExec( new Runnable() {           public void run() {             firePropertyChange(IEdiTorPart.PROP_DIRTY);             refreshDecoration();             FolderManager.fireSaveablesDirtyChanged();           }         });       }     });   }  }

总结

本文分对 CommonNavigaor 的 Saveables Protocol 的实现原理进行说,并通过一个实例对其实现方法进行说明。通过该机制,开发者可以不用关注保存的具体机制,而将更多的精力投入到与具体业务流程的开发中,从而更加快速地实现在视图上完成对模型元素的保存。

幸福不是因为你拥有得多,而是由于你计较得少。

如何使用EclipseCNF的SaveableProtocol实现对View的保存

相关文章:

你感兴趣的文章:

标签云: