首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Eclipse CNF Saveable Protocol 實戰:讓 Navigator View 也能優雅保存模型

     編輯:Java 相關知識

Eclipse 中 Editor 內建完整的儲存機制(isDirty()、doSave() 等),但 View 主要用於樹狀導航,預設不支援「另存新檔」等操作。然而實際開發中,常需在導航視圖直接保存多個編輯器的修改。Common Navigator Framework (CNF) 提供了 Saveable Protocol 解決此需求。本文深入剖析其原理並附完整實作範例。

Editor 儲存機制回顧

EditorPart 核心方法:

  • isDirty():回傳 true 時標題顯示 *,工具列「儲存」啟用
  • doSave(IProgressMonitor):執行儲存邏輯,成功後將 dirty 旗標重設為 false 並觸發 firePropertyChange()
  • isSaveAsAllowed() / doSaveAs():支援「另存新檔」

ViewPart 則無此類方法,因設計定位為「導航」而非「編輯」。

CNF Saveable Protocol 核心架構

關鍵介面與類別:

  • ISaveablesSource:CommonNavigator 實作,提供可儲存物件清單
  • SaveablesProvider:ContentProvider 需適配此介面,負責樹節點與可儲存物件映射
  • Saveable:代表可儲存單元,含 doSave()、isDirty() 等方法

SaveablesProvider 關鍵方法:

public abstract Saveable[] getSaveables();           // 所有可儲存物件
public abstract Object[] getElements(Saveable s);    // Saveable 對應的樹節點模型
public abstract Saveable getSaveable(Object element); // 樹節點對應的 Saveable
// 通知方法(必須在 UI 執行緒呼叫)
fireSaveablesOpened(Saveable[]);
fireSaveablesClosing(Saveable[], boolean);
fireSaveablesClosed(Saveable[]);

流程圖解:

  1. 模型變更 → 呼叫 firePropertyChange(PROP_DIRTY)
  2. 註冊監聽器(SaveAction、SaveAllAction)透過 getActiveSaveables() 計算 dirty 狀態
  3. 工具列「儲存」啟用,Navigator 標題顯示 *
  4. 用戶點擊儲存 → 框架呼叫 Saveable.doSave() 執行持久化

完整實作範例四步驟

步驟 1:建立支援 Saveable 的 View

public class SaveableView extends CommonNavigator {
    public static String ID = "ViewSaveableProtocol.SaveableView";

    public Saveable[] getSaveables() {
        return this.getActiveSaveables(); // 僅回傳活動狀態的 Saveable
    }

    public void fireSaveabelsChanged() {
        this.firePropertyChange(IWorkbenchPartConstants.PROP_DIRTY);
    }
}

在 org.eclipse.ui.navigator 擴展點指定 View 類別為 org.eclipse.ui.navigator.CommonNavigator。

步驟 2:ContentProvider 實作 IAdaptable 適配 SaveablesProvider

在 org.eclipse.ui.navigator.navigatorContent 擴展點設定 providesSaveables="true"。ContentProvider 需繼承 SaveablesProvider 並實作 ITreeContentProvider、IAdaptable:

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) return new SaveablePart(part);
        }
        return null;
    }

    @Override
    public Saveable[] getSaveables() {
        Object[] parts = FolderManager.getAllOpenedWorkbenchPart();
        return new Saveable[parts.length]; // 實際需填入 SaveablePart 陣列
    }
}

步驟 3:Decorator 視覺化 Dirty 狀態

使用 org.eclipse.ui.decorators 擴展點,實作 ILightweightLabelDecorator:

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(() -> 
            fireLabelProviderChanged(new LabelProviderChangedEvent(this, element)));
    }
}

步驟 4:聯動 Editor、View、Decorator 狀態同步

Editor 修改時通知所有相關方:

public class TextFileEditor extends EditorPart {
    @Override
    public void doSave(IProgressMonitor monitor) {
        dirty = false;
        Display.getDefault().asyncExec(() -> {
            firePropertyChange(IEditorPart.PROP_DIRTY);
            refreshDecoration(); // 通知 Decorator
            FolderManager.fireSaveablesDirtyChanged(); // 通知 Navigator
        });
    }

    @Override
    public void createPartControl(Composite parent) {
        text.addModifyListener(e -> {
            dirty = true;
            Display.getDefault().asyncExec(() -> {
                firePropertyChange(IEditorPart.PROP_DIRTY);
                refreshDecoration();
                FolderManager.fireSaveablesDirtyChanged();
            });
        });
    }
}

FolderManager 統一廣播:

public class FolderManager {
    public static void fireSaveablesDirtyChanged() {
        IViewPart view = PlatformUI.getWorkbench()
            .getActiveWorkbenchWindow().getActivePage().findView(SaveableView.ID);
        if (view != null) {
            Display.getDefault().syncExec(() -> 
                ((SaveableView) view).fireSaveabelsChanged());
        }
    }
}

SaveablePart 包裝 Editor 實作 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) part).doSave(monitor);
    }
    public boolean isDirty() {
        return part instanceof ISaveablePart && ((ISaveablePart) part).isDirty();
    }
    public IWorkbenchPart getWorkbenchPart() { return part; }
}

總結

CNF Saveable Protocol 將「可儲存性」從 Editor 抽離,讓 Navigator View 也能成為儲存操作的入口。開發者只需專注業務模型與 Saveable 映射,框架自動處理狀態同步、UI 更新、儲存動作分發。這不僅提升使用者效率(一次儲存多個編輯器),更符合 Eclipse 「關注點分離」的設計哲學。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved