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 的實現原理進行說,並通過一個實例對其實現方法進行說明。通過該機制,開發者可以不用關注保存的具體機制,而將更多的精力投入到與具體業務流程的開發中,從而更加快速地實現在視圖上完成對模型元素的保存。