http://www.ibm.com/developerworks/cn/opensource/os-cn-eclcnfsp/index.html
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() 的方法将会被调用。
然后,通过 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 来定制所需要编辑器与视图来完成模型的修改与保存。
EditorPart 中几个重要的方法:
public
abstract
boolean
isDirty()
: 用于表明编辑器中的内容是否发生修改,当编辑器的内容发生修改时,编辑器的标题栏显式地出现“*”号,同时,主菜单下“文件”下的全局“保存”按钮变为可用。当编辑器中的内容发生改变时,isDirty 方法不会自动变调用。因此我们要对可修改的 UI 元素,如 Text, CheckBox 等注册事件监听器,当修改发生时,由监听器将编辑器的 dirty 标志位置为 true。由于 isDirty() 在编辑器的生命周期中会被频繁地调用,因此不宜在这种方法中加入过多的执行语句,否则会影响程序的执行速度。public
abstract
void
doSave(IProgressMonitor monitor)
: 在 isDirty 返回 true 的情况下,当用户点击保存或使用快捷键 Ctrl+S 时,该方法会被调用,当保存模型的代码成功执行时,我们需要将编辑器的 dirty 标志位重新设置为 false,同时调用 firePropertyChange() 方法将编辑器的界面状态更新,此时标题栏的星号(*)消失。public
abstract
boolean
isSaveAsAllowed()
: 表明编辑器的“另存为”按钮是否可用。public
abstract
void
doSaveAs()
: 在 isSaveAsAllowed() 返回 true 的情况下,用户点击“另存为”,doSaveAs() 方法将被调用。与 doSave 方法类似,用户可以在该方法里实现对模型的保存逻辑。一般情况下我们可能复用 doSave 的逻辑完成对模型内容的另存为。protected
void
firePropertyChange(
final
int
propertyId):
当编辑器属性发生变化时,可以通过调用该方法通知所注册的监听器。例如,当修改发生时,在编辑器标题前出现的“*”前缀。
与 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():
返回当前处于活动状态的可保存元素,所返回的元素基于用户当前所选择的元素。
如上图所示,当所需要保存的元素发生改变时,调用 CommonNavigator 的 firePropertyChange 方法,表明其中的元素发生了变化,些时注册在其中的监听器,如 SaveAction, SaveAllAction 会通过 CommonNavigator 的 getActiveSaveables() 计算是否有可保存的元素发生修改,如果有元素发生修改,更新 SaveAction 与 SaveAllAction 的可用状态,如果有可保存的元素,Navigator 的标题栏也将出现“*”,表明其为可保存的状态。
当用户选择所需要保存的元素时,并选择保存时,由 SaveableProvider 返回可保存的 Saveable 对象,由 CommonNaviagator 的 Saveables 框架调用对象的 doSave 方法进行保存。
本节通过一个简单的例子来说明白如何何使用 CommonNavigator 的 Saveable Protocol. 在这个例子中的模型部分,包括文件夹结点和文件结点,其中文件结点可以通过编辑器进行编辑,文件内容发生改变时,相应地导航器上的结点名称将发生变化,当焦点处于导航器结点视图时,Save 与 SaveAll 按钮状态将随着所选择的结点的变化而变化。
第一步:创建视图 (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 扩展点实现。
在上图中,objectClass 属性指明的是所要修饰对象的类型。class 属性指明的修饰的具体实现类,Eclipse 框架为我们提供了轻量级的修饰机制,只需将 lightweight 属性值指明为 true,同时,将所要提供的修饰类实现 ILightweightLabelDecorator 接口,框架就能对树型结点元素提供前缀、后缀、重叠图片的修饰。在本文的例子中,当模型元素对应的 eidtor 发生修改时,树型导航器上结点的名称将以“*”作为后缀。具体代码如下:
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 的实现原理进行说,并通过一个实例对其实现方法进行说明。通过该机制,开发者可以不用关注保存的具体机制,而将更多的精力投入到与具体业务流程的开发中,从而更加快速地实现在视图上完成对模型元素的保存。
- 请参考陈刚的 Eclipse 从入门到精通 。
- 请参考 Common Navigator Framework。
- 访问 developerWorks Open source 专区获得丰富的 how-to 信息、工具和项目更新以及最受欢迎的文章和教程,帮助您用开放源码技术进行开发,并将它们与 IBM 产品结合使用。