Eclipse Workbench 默認支持編輯器/視圖標籤的:移動停靠、標籤頁重排、創建快速視圖、分欄並排編輯。但實際項目中常需將標籤拖放到特定視圖觸發業務邏輯 (如:拖放編輯器到「屬性檢查器」顯示元數據、拖放視圖到「任務列表」創建關聯任務)。這要求攔截拖放過程,提取 editor-id/editor-input 或 view-id,交由目標視圖處理。
DragSource、DropTarget、Transfer 類型系統editor-id + IEditorInputDisplay.addFilter(SWT.DragDetect, ...) 全局攔截拖拽起始事件DragSource,默認用 DragDetect/MouseMove/MouseUp 監聽器實現問題:編輯器標籤所在的 CTabFolder 動態創建/銷毀 (平鋪時新建、標籤全關閉時銷毀),無法靜態綁定 DragSource。
洞察:CTabFolder 內部未創建 DragSource,默認拖放完全由底層鼠標事件監聽器實現 (查閱 org.eclipse.swt.custom.CTabFolder 源碼可證)。因此可在拖拽瞬間動態創建 DragSource。
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 或右鍵取消。
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());
}
});
});
DropTarget 支持 EditorInputTransfer + TextTransferCTabFolder 無 DragSource 的內部實現細節,未來版本可能變更 (但可能性極低)getActivePart() 依賴焦點狀態,極少數情況下可能不準 (如鍵盤導航中),可輔以 folder.getSelection() 交叉驗證dragFinished 中 dispose() 必須調用,否則洩漏監聽器導致內存洩漏與行為異常DropTarget 需正確實現 drop() 解析數據並執行業務邏輯通過 Display 事件過濾器 + 動態 DragSource + EditorInputTransfer/TextTransfer + 默認行為抑制,實現了對 Eclipse Workbench 標籤拖放的完全可控定制。該模式可擴展至:跨窗口拖放、拖放到外部應用、構建可視化工作流編輯器等高級場景。