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

Eclipse Workbench 編輯器與視圖標籤拖放行為深度定制

     編輯:Java 相關知識

需求背景:超越默認拖放

Eclipse Workbench 默認支持編輯器/視圖標籤的:移動停靠、標籤頁重排、創建快速視圖、分欄並排編輯。但實際項目中常需將標籤拖放到特定視圖觸發業務邏輯 (如:拖放編輯器到「屬性檢查器」顯示元數據、拖放視圖到「任務列表」創建關聯任務)。這要求攔截拖放過程,提取 editor-id/editor-input 或 view-id,交由目標視圖處理。

核心技術棧

  • SWT Drag & Drop:DragSource、DropTarget、Transfer 類型系統
  • EditorInputTransfer:標準轉移類型,封裝 editor-id + IEditorInput
  • Display 事件過濾器:Display.addFilter(SWT.DragDetect, ...) 全局攔截拖拽起始事件
  • CTabFolder:標籤容器,內部無 DragSource,默認用 DragDetect/MouseMove/MouseUp 監聽器實現

關鍵挑戰:動態創建 DragSource

問題:編輯器標籤所在的 CTabFolder 動態創建/銷毀 (平鋪時新建、標籤全關閉時銷毀),無法靜態綁定 DragSource。
洞察:CTabFolder 內部未創建 DragSource,默認拖放完全由底層鼠標事件監聽器實現 (查閱 org.eclipse.swt.custom.CTabFolder 源碼可證)。因此可在拖拽瞬間動態創建 DragSource。

解決方案:Display 過濾器攔截 DragDetect

步驟 1:註冊全局 DragDetect 過濾器

PlatformUI.getWorkbench().getDisplay().addFilter(SWT.DragDetect, new Listener() {
    public void handleEvent(Event event) {
        // 1. 過濾非 CTabFolder 組件
        if (!(event.widget instanceof CTabFolder)) return;
        CTabFolder draggedFolder = (CTabFolder) event.widget;
        if (draggedFolder.getItemCount() < 1) return; // 空標籤組忽略

        // 2. 創建 DragSource,綁定 EditorInputTransfer
        int ops = DND.DROP_COPY | DND.DROP_DEFAULT;
        final DragSource dragSource = new DragSource(draggedFolder, ops);
        dragSource.setTransfer(new Transfer[] { EditorInputTransfer.getInstance() });

        // 3. 獲取正在拖拽的工作台部件 (編輯器/視圖)
        IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
        final IWorkbenchPart part = window.getActivePage().getActivePart();

        dragSource.addDragListener(new DragSourceListener() {
            public void dragStart(DragSourceEvent e) {}
            
            public void dragSetData(DragSourceEvent e) {
                if (part instanceof IEditorPart) {
                    // 編輯器:提取 editor-id + editor-input
                    String editorId = part.getSite().getId();
                    IEditorInput input = ((IEditorPart) part).getEditorInput();
                    EditorInputTransfer.EditorInputData data = 
                        EditorInputTransfer.createEditorInputData(editorId, input);
                    e.data = new EditorInputTransfer.EditorInputData[] { data };
                } else if (part instanceof IViewPart) {
                    // 視圖:僅傳遞 view-id (TextTransfer)
                    e.data = part.getSite().getId();
                }
            }
            
            public void dragFinished(DragSourceEvent e) {
                dragSource.dispose(); // 單次用完即銷毀
                // 4. 關鍵:抑制 CTabFolder 默認拖放行為 (重排/平鋪)
                draggedFolder.notifyListeners(SWT.MouseUp, new Event()); // 模擬鼠標釋放取消默認操作
            }
        });
    }
});

關鍵技術點解析

事件分發順序保證

SWT 事件分發:Display Filters → Widget Listeners。過濾器最先收到 DragDetect,創建 DragSource 會向同一 CTabFolder 註冊內部 DragSourceListener (稱 drag-listener-x)。隨後默認監聽器處理完,drag-listener-x 接收後續 DragOver/DragDrop 事件,完成自定義邏輯。時序圖見原文 Figure 3。

編輯器/視圖雙模式支持

動態判斷 part 類型:

  • 編輯器 → EditorInputTransfer (豐富數據)
  • 視圖 → TextTransfer (僅 view-id 字符串)

目標 DropTarget 需同時支持兩種 Transfer 類型。

抑制默認行為的巧思

dragFinished 中調用 draggedFolder.notifyListeners(SWT.MouseUp, null) 模擬鼠標釋放,導致 CTabFolder 默認監聽器認為拖放結束/取消,不再執行重排/平鋪邏輯。等同於用戶按 Esc 或右鍵取消。

完整整合代碼 (清單 6 精簡版)

PlatformUI.getWorkbench().getDisplay().addFilter(SWT.DragDetect, e -> {
    if (!(e.widget instanceof CTabFolder)) return;
    CTabFolder folder = (CTabFolder) e.widget;
    if (folder.getItemCount() < 1) return;

    IWorkbenchPart part = PlatformUI.getWorkbench()
        .getActiveWorkbenchWindow().getActivePage().getActivePart();
    if (part == null) return;

    DragSource ds = new DragSource(folder, DND.DROP_COPY | DND.DROP_DEFAULT);
    boolean isEditor = part instanceof IEditorPart;
    ds.setTransfer(new Transfer[] { isEditor ? EditorInputTransfer.getInstance() : TextTransfer.getInstance() });
    
    ds.addDragListener(new DragSourceAdapter() {
        @Override
        public void dragSetData(DragSourceEvent dse) {
            if (isEditor) {
                IEditorPart ep = (IEditorPart) part;
                dse.data = new EditorInputTransfer.EditorInputData[] {
                    EditorInputTransfer.createEditorInputData(ep.getSite().getId(), ep.getEditorInput())
                };
            } else {
                dse.data = part.getSite().getId(); // view-id
            }
        }
        @Override
        public void dragFinished(DragSourceEvent dse) {
            ds.dispose();
            folder.notifyListeners(SWT.MouseUp, new Event());
        }
    });
});

插件化交付:DragDropWorkbenchParts

  • 菜單/工具欄按鈕:「Enable Drag-n-Drop of Editor/View Parts」動態註冊/注銷過濾器
  • 定義「Drop Window」視圖,註冊 DropTarget 支持 EditorInputTransfer + TextTransfer
  • 未啟用時恢復原生拖放行為 (重排/平鋪)

避坑指南

  1. 假設風險:依賴 CTabFolder 無 DragSource 的內部實現細節,未來版本可能變更 (但可能性極低)
  2. 活動部件獲取:getActivePart() 依賴焦點狀態,極少數情況下可能不準 (如鍵盤導航中),可輔以 folder.getSelection() 交叉驗證
  3. dragFinished 中 dispose() 必須調用,否則洩漏監聽器導致內存洩漏與行為異常
  4. 目標視圖 DropTarget 需正確實現 drop() 解析數據並執行業務邏輯

總結

通過 Display 事件過濾器 + 動態 DragSource + EditorInputTransfer/TextTransfer + 默認行為抑制,實現了對 Eclipse Workbench 標籤拖放的完全可控定制。該模式可擴展至:跨窗口拖放、拖放到外部應用、構建可視化工作流編輯器等高級場景。

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