使用 Eclipse CNF 的 Saveable Protocol 实现对 View 的保存

小编辑 发布于 2010/05/18 01:48
阅读 616
收藏 0

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

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 扩展点
图 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 属性
图 2. triggerPoints 属性

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

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


图 3. WorkbenchPart 的继承关系
图 3. WorkbenchPart 的继承关系

EditorPart 中几个重要的方法:

  • public abstract booleanisDirty(): 用于表明编辑器中的内容是否发生修改,当编辑器的内容发生修改时,编辑器的标题栏显式地出现“*”号,同时,主菜单下“文件”下的全局“保存”按钮变为可 用。当编辑器中的内容发生改变时,isDirty 方法不会自动变调用。因此我们要对可修改的 UI 元素,如 Text, CheckBox 等注册事件监听器,当修改发生时,由监听器将编辑器的 dirty 标志位置为 true。由于 isDirty() 在编辑器的生命周期中会被频繁地调用,因此不宜在这种方法中加入过多的执行语句,否则会影响程序的执行速度。
  • public abstract voiddoSave(IProgressMonitor monitor): 在 isDirty 返回 true 的情况下,当用户点击保存或使用快捷键 Ctrl+S 时,该方法会被调用,当保存模型的代码成功执行时,我们需要将编辑器的 dirty 标志位重新设置为 false,同时调用 firePropertyChange() 方法将编辑器的界面状态更新,此时标题栏的星号(*)消失。
  • public abstract booleanisSaveAsAllowed(): 表明编辑器的“另存为”按钮是否可用。
  • public abstract voiddoSaveAs(): 在 isSaveAsAllowed() 返回 true 的情况下,用户点击“另存为”,doSaveAs() 方法将被调用。与 doSave 方法类似,用户可以在该方法里实现对模型的保存逻辑。一般情况下我们可能复用 doSave 的逻辑完成对模型内容的另存为。
  • protected voidfirePropertyChange(final intpropertyId):当编 辑器属性发生变化时,可以通过调用该方法通知所注册的监听器。例如,当修改发生时,在编辑器标题前出现的“*”前缀。

与 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 框架的调用过程
图 4. Saveables 框架的调用过程

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

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

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


图 5. 可保存的 Navigator 视图
图 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 属性
图 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 扩展点
图 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 菜单项将根据用户选择的结点,更新其状态。具体步骤如下:

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

    清单 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();
    }
    });
    }
    }
    }
  2. 当元素保存时,由 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;
    }

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



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

加载中
返回顶部
顶部