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

SWT/JFace 書籤管理器實作:Tree、Drag&Drop、檔案匯入匯出完整教學

     編輯:Java 相關知識

前言

書籤管理器是展示 SWT/JFace 進階功能的經典範例:樹狀結構(Tree)、拖放(Drag & Drop)、自訂傳輸類型(ByteArrayTransfer)、檔案解析與序列化(Netscape 書籤 HTML 格式)。本文完整解析 BookmarkOrganizer 實作細節。

核心資料模型

class Bookmark {
    public String name, href, addDate, lastVisited, lastModified;
}

資料夾節點不攜帶 Bookmark 物件,改以 TreeItem.setData(KEY_ADD_DATE, addDate) 標記;書籤節點以 setData(bookmark) 儲存完整資訊。

UI 結構與元件

  • ToolBar:Load(匯入)、Save as(匯出)
  • Tree:根節點 "BOOKMARKS",資料夾圖示、書籤圖示區分
  • Label (狀態列):顯示選中書籤網址或資料夾資訊
  • DragSource / DropTarget:支援樹內拖放重新排序(移動/複製/連結)

佈局程式碼

shell.setLayout(new GridLayout(1, true));
ToolBar toolBar = new ToolBar(shell, SWT.FLAT);
// ... 加入 Load、Save 工具項

tree = new Tree(shell, SWT.BORDER);
tree.setLayoutData(new GridData(GridData.FILL_BOTH));
rootItem = new TreeItem(tree, SWT.NULL);
rootItem.setText("BOOKMARKS");
rootItem.setImage(iconRoot);

label = new Label(shell, SWT.BORDER);
label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

拖放實作詳解

自訂傳輸類型:BookmarkTransfer

public class BookmarkTransfer extends ByteArrayTransfer {
    private static final String BOOKMARK_TRANSFER_NAME = "BOOKMARK";
    private static final int BOOKMARK_TRANSFER_ID = registerType(BOOKMARK_TRANSFER_NAME);
    private static final BookmarkTransfer instance = new BookmarkTransfer();
    public static BookmarkTransfer getInstance() { return instance; }

    protected int[] getTypeIds() { return new int[] { BOOKMARK_TRANSFER_ID }; }
    protected String[] getTypeNames() { return new String[] { BOOKMARK_TRANSFER_NAME }; }

    protected void javaToNative(Object object, TransferData transferData) {
        if (!(object instanceof Bookmark)) return;
        Bookmark b = (Bookmark) object;
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             DataOutputStream out = new DataOutputStream(bos)) {
            out.writeUTF(b.name);
            out.writeUTF(b.href);
            out.writeUTF(b.addDate);
            out.writeUTF(b.lastVisited);
            out.writeUTF(b.lastModified);
            out.close();
            super.javaToNative(bos.toByteArray(), transferData);
        } catch (IOException e) { e.printStackTrace(); }
    }

    protected Object nativeToJava(TransferData transferData) {
        if (!isSupportedType(transferData)) return null;
        byte[] raw = (byte[]) super.nativeToJava(transferData);
        if (raw == null) return null;
        try (ByteArrayInputStream bis = new ByteArrayInputStream(raw);
             DataInputStream in = new DataInputStream(bis)) {
            Bookmark b = new Bookmark();
            b.name = in.readUTF();
            b.href = in.readUTF();
            b.addDate = in.readUTF();
            b.lastVisited = in.readUTF();
            b.lastModified = in.readUTF();
            return b;
        } catch (IOException e) { e.printStackTrace(); return null; }
    }
}

繼承 ByteArrayTransfer 以 UTF-8 序列化五個欄位,確保跨平台相容。

DragSource:拖曳來源

DragSource dragSource = new DragSource(tree, DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK);
dragSource.setTransfer(new Transfer[] { BookmarkTransfer.getInstance() });
dragSource.addDragListener(new DragSourceAdapter() {
    TreeItem dragSourceItem;
    public void dragStart(DragSourceEvent e) {
        TreeItem[] sel = tree.getSelection();
        if (sel.length > 0 && sel[0].getData() != null) { // 僅書籤節點可拖曳
            e.doit = true; dragSourceItem = sel[0];
        } else e.doit = false;
    }
    public void dragSetData(DragSourceEvent e) {
        if (BookmarkTransfer.getInstance().isSupportedType(e.dataType))
            e.data = dragSourceItem.getData();
    }
    public void dragFinished(DragSourceEvent e) {
        if (e.detail == DND.DROP_MOVE) dragSourceItem.dispose(); // 移動模式刪除來源
        dragSourceItem = null;
    }
});

DropTarget:放置目標

DropTarget dropTarget = new DropTarget(tree, DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK);
dropTarget.setTransfer(new Transfer[] { BookmarkTransfer.getInstance() });
dropTarget.addDropListener(new DropTargetAdapter() {
    public void dragOver(DropTargetEvent e) {
        e.feedback = DND.FEEDBACK_EXPAND | DND.FEEDBACK_SCROLL | DND.FEEDBACK_SELECT;
    }
    public void dropAccept(DropTargetEvent e) {
        // 僅允許放在資料夾節點(無 Bookmark 資料)或根節點
        if (e.item == null || ((TreeItem)e.item).getData() != null)
            e.detail = DND.DROP_NONE;
    }
    public void drop(DropTargetEvent e) {
        if (e.data == null) { e.detail = DND.DROP_NONE; return; }
        TreeItem target = (TreeItem) e.item;
        Bookmark b = (Bookmark) e.data;
        TreeItem newItem = new TreeItem(target, SWT.NULL);
        newItem.setText(b.name);
        newItem.setImage(iconURL);
        newItem.setData(b);
    }
});

關鍵邏輯: dropAccept 限制只能放入資料夾(getData() == null 且非書籤節點),避免書籤嵌套書籤。

書籤檔案解析與匯出

Netscape 書籤 HTML 格式

<DT><H3 FOLDED ADD_DATE="1234567890">資料夾名稱</H3>
<DL><p>
<DT><A HREF="https://example.com" ADD_DATE="..." LAST_VISIT="..." LAST_MODIFIED="...">書籤名稱</A>
</DL><p>

載入(解析)

private void loadBookmark(File file, TreeItem rootItem) {
    TreeItem parent = rootItem;
    try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
        String line;
        while ((line = reader.readLine()) != null) {
            line = line.trim();
            if (line.startsWith(folderLinePrefix)) { // <DT><H3 FOLDED
                Matcher m = folderPattern.matcher(line); // "\"(\\d+)\">(.*)<"
                if (m.find()) {
                    String addDate = m.group(1), name = m.group(2);
                    TreeItem item = new TreeItem(parent, SWT.NULL);
                    item.setText(name);
                    item.setData(KEY_ADD_DATE, addDate);
                    item.setImage(iconFolder);
                    parent = item; // 進入子層
                }
            } else if (line.startsWith(urlLinePrefix)) { // <DT><A HREF
                Matcher m = urlPattern.matcher(line); // "\"(.*)\".*\"(.*)\".*\"(.*)\".*\"(.*)\">(.*)<"
                if (m.find()) {
                    Bookmark b = new Bookmark();
                    b.href = m.group(1); b.addDate = m.group(2);
                    b.lastVisited = m.group(3); b.lastModified = m.group(4);
                    b.name = m.group(5);
                    TreeItem item = new TreeItem(parent, SWT.NULL);
                    item.setText(b.name); item.setData(b); item.setImage(iconURL);
                }
            } else if (line.equals("</DL><p>")) { // 資料夾結束
                parent = parent.getParentItem(); // 返回上層
            }
        }
    } catch (IOException e) { e.printStackTrace(); }
}

使用 Pattern 正則解析,透過 parent 變數追蹤當前資料夾層級,遇到 </DL><p> 回溯父節點。

匯出(序列化)

private void saveBookmark(BufferedWriter writer, TreeItem item) throws IOException {
    if (item.getData() == null && item.getData(KEY_ADD_DATE) == null) { // 根節點
        writer.write("<!DOCTYPE NETSCAPE-Bookmark-file-1>\n<DL><p>\n");
        for (TreeItem child : item.getItems()) saveBookmark(writer, child);
        writer.write("</DL><p>\n");
    } else if (item.getData() == null) { // 資料夾
        writer.write("\t<DT><H3 FOLDED ADD_DATE=\"" + item.getData(KEY_ADD_DATE) + "\">" + item.getText() + "</H3>\n");
        writer.write("\t<DL><p>\n");
        for (TreeItem child : item.getItems()) saveBookmark(writer, child);
        writer.write("\t</DL><p>\n");
    } else { // 書籤
        Bookmark b = (Bookmark) item.getData();
        writer.write("\t\t<DT><A HREF=\"" + b.href + "\" ADD_DATE=\"" + b.addDate
            + "\" LAST_VISIT=\"" + b.lastVisited + "\" LAST_MODIFIED=\"" + b.lastModified
            + "\">" + b.name + "</A>\n");
    }
}

遞迴遍歷樹結構,依節點類型輸出對應 HTML 片段,保持原始格式相容性。

選取事件與狀態列

tree.addSelectionListener(new SelectionAdapter() {
    public void widgetSelected(SelectionEvent e) {
        TreeItem item = (TreeItem) e.item;
        Bookmark b = (Bookmark) item.getData();
        if (b != null) setStatus(b.href);
        else if (item.getData(KEY_ADD_DATE) != null) setStatus("Folder: " + item.getText());
    }
});

資源管理

Image iconRoot = new Image(display, "C:/icons/icon.gif");
Image iconFolder = new Image(display, "C:/icons/folder.gif");
Image iconURL = new Image(display, "C:/icons/file.gif");
// 關閉時
iconRoot.dispose(); iconFolder.dispose(); iconURL.dispose();

總結

本範例整合了 SWT/JFace 多項核心技術:

  1. Tree 階層資料呈現與圖示區分
  2. 自訂 Transfer 實現型別安全的拖放
  3. 正則解析與序列化 Netscape 書籤格式
  4. 資源正確釋放與事件處理

開發者可在此基礎上擴充:右鍵選單編輯/刪除、搜尋過濾、多書籤檔案管理、同步雲端等功能。

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