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

SWT/JFace 文字編輯器演進:從基礎版到 JFace Action 架構與列印功能

     編輯:Java 相關知識

前言

文字編輯器是桌面應用的經典範例。本文展示三個版本的演進:

  1. BasicEditor:純 SWT API,手動建立選單、工具列、事件處理
  2. BasicEditor2:引入 JFace Action、MenuManager、ToolBarManager 解耦邏輯與 UI
  3. BasicEditor3:新增列印功能(Printer、StyledTextPrintOptions)與自訂分頁列印邏輯

版本一:BasicEditor(純 SWT)

核心架構

  • StyledText:多行、自動換行、捲動、等寬字型
  • ToolBar + ToolItem:新增、開啟、儲存、複製、剪下、貼上、自動換行切換
  • MenuBar + MenuItem:對應工具列功能,含快捷鍵(Accelerator)
  • MessageBox:未儲存變更確認對話框
  • FileDialog:開啟/儲存檔案
  • ImageRegistry:圖示快取管理

關鍵邏輯:未儲存變更攔截

boolean handleChangesBeforeDiscard() {
    if (!hasUnsavedChanges) return true;
    MessageBox box = new MessageBox(shell, SWT.ICON_WARNING | SWT.YES | SWT.NO | SWT.CANCEL);
    box.setMessage("Do you want to save the changes to " + (file == null ? "a file?" : file.getName()));
    box.setText(APP_NAME);
    int ret = box.open();
    if (ret == SWT.YES) return saveTextToFile();
    else if (ret == SWT.NO) return true;
    else return false; // CANCEL
}

所有「新增、開啟、結束」操作前均呼叫此方法,確保資料不遺失。

檔案讀寫

boolean loadTextFromFile() {
    FileDialog dialog = new FileDialog(shell, SWT.OPEN);
    if (lastOpenDirectory != null) dialog.setFilterPath(lastOpenDirectory);
    String selected = dialog.open();
    if (selected == null) return false;
    file = new File(selected);
    lastOpenDirectory = file.getParent();
    try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) { sb.append(line).append("\r\n"); }
        text.setText(sb.toString());
        hasUnsavedChanges = false;
        return true;
    } catch (IOException e) { log(e); return false; }
}

版本二:BasicEditor2(JFace Action 架構)

Action 封裝命令邏輯

Action actionNew = new Action("&New", ImageDescriptor.createFromFile(null, "icons/new.gif")) {
    public void run() { if (handleChangesBeforeDiscard()) { file = null; text.setText(""); } }
};
actionNew.setAccelerator(SWT.CTRL | 'N');

// 核取方塊型 Action(自動換行)
Action actionWrap = new Action("&Wrap", IAction.AS_CHECK_BOX) {
    public void run() { text.setWordWrap(isChecked()); }
};
actionWrap.setAccelerator(SWT.CTRL | 'W');
  • IAction.AS_CHECK_BOX:工具列顯示為核取按鈕,選單顯示為勾選項目
  • setAccelerator:註冊全域快捷鍵
  • ImageDescriptor:延遲載入圖示,支援插件化部署

MenuManager / ToolBarManager 組裝 UI

ToolBar toolBar = new ToolBar(shell, SWT.FLAT | SWT.RIGHT);
ToolBarManager toolBarManager = new ToolBarManager(toolBar);
toolBarManager.add(actionNew); toolBarManager.add(actionOpen); toolBarManager.add(actionSave);
toolBarManager.add(new Separator());
toolBarManager.add(actionCopy); toolBarManager.add(actionCut); toolBarManager.add(actionPaste);
toolBarManager.add(new Separator()); toolBarManager.add(actionWrap);
toolBarManager.update(true);

MenuManager barMenuManager = new MenuManager();
MenuManager fileMenu = new MenuManager("&File");
MenuManager editMenu = new MenuManager("&Edit");
MenuManager formatMenu = new MenuManager("&Format");
barMenuManager.add(fileMenu); barMenuManager.add(editMenu); barMenuManager.add(formatMenu);
fileMenu.add(actionNew); fileMenu.add(actionOpen); fileMenu.add(actionSave);
fileMenu.add(new Separator()); fileMenu.add(actionExit);
editMenu.add(actionCopy); editMenu.add(actionCut); editMenu.add(actionPaste);
formatMenu.add(actionWrap);

barMenuManager.updateAll(true);
shell.setMenuBar(barMenuManager.createMenuBar(shell));

優勢: 同一個 Action 同時貢獻至工具列與選單,狀態(啟用/勾選)自動同步,程式碼大幅減少且易維護。

版本三:BasicEditor3(列印功能)

新增列印相關 Action

Action actionPrint = new Action("&Print@Ctrl+P") {
    public void run() { printText(text.getText()); }
};
Action actionPrint2 = new Action("Print (StyledText)") {
    public void run() {
        StyledTextPrintOptions options = new StyledTextPrintOptions();
        options.header = "SWT"; options.footer = "Page <page>"; options.jobName = "Text";
        Runnable runnable = text.print(new Printer(), options);
        runnable.run(); // 同步列印
    }
};
  • actionPrint:自訂分頁邏輯(支援換行、分頁、頁碼)
  • actionPrint2:使用 StyledText.print() 內建分頁(標頭/頁尾/頁碼)

自訂分頁列印核心邏輯

void printText(String text) {
    PrintDialog dialog = new PrintDialog(shell);
    PrinterData data = dialog.open(); if (data == null) return;
    Printer printer = new Printer(data);
    if (!printer.startJob("text")) return;
    GC gc = new GC(printer);
    PrintMargin margins = PrintMargin.getPrintMargin(printer, 1.0); // 1 吋邊距
    x = margins.left; y = margins.top;
    Font font = new Font(printer, "Arial", 12, SWT.NORMAL);
    gc.setFont(font);
    lineHeight = gc.getFontMetrics().getHeight();
    
    printer.startPage();
    drawPageNumber(gc, margins, pageNumber);
    
    StringBuffer buffer = new StringBuffer();
    for (int i = 0; i < text.length(); ) {
        char c = text.charAt(i);
        switch (c) {
            case '\r': // CRLF 處理
                if (i+1 < text.length() && text.charAt(i+1) == '\n') {
                    printNewLine(printer, gc, buffer.toString()); buffer.setLength(0); i += 2;
                } break;
            case '\t': case ' ':
                // 寬度檢查:超過右邊距則換行
                if (gc.textExtent(buffer.toString() + ' ').x > margins.right - margins.left) {
                    printNewLine(printer, gc, buffer.toString()); buffer.setLength(0);
                }
                buffer.append(c);
                // 預讀下一個單字,決定是否在此換行
                if (i+1 < text.length() && !Character.isWhitespace(text.charAt(i+1))) {
                    String word = readWord(text, i+1);
                    if (gc.textExtent(buffer.toString() + word).x > margins.right - margins.left) {
                        printNewLine(printer, gc, buffer.toString()); buffer.setLength(0);
                    }
                }
                i++; break;
            default: buffer.append(c); i++;
        }
    }
    if (buffer.length() > 0) printNewLine(printer, gc, buffer.toString());
    if (y + lineHeight <= margins.bottom) printer.endPage();
    printer.endJob(); gc.dispose(); font.dispose(); printer.dispose();
}

void printNewLine(Printer printer, GC gc, String line) {
    if (y + lineHeight > margins.bottom) { // 頁底換頁
        printer.endPage();
        x = margins.left; y = margins.top; pageNumber++; lineNumber = 1;
        printer.startPage(); drawPageNumber(gc, margins, pageNumber);
    }
    gc.drawString(line, x, y); y += lineHeight;
}

PrintMargin 類別根據印表機 DPI 與指定邊距(吋)計算像素邊界,支援非對稱邊距。

PrintMargin 工具類別

public class PrintMargin {
    public int left, right, top, bottom;
    static PrintMargin getPrintMargin(Printer printer, double margin) {
        return getPrintMargin(printer, margin, margin, margin, margin);
    }
    static PrintMargin getPrintMargin(Printer printer, double ml, double mr, double mt, double mb) {
        Rectangle client = printer.getClientArea();
        Rectangle trim = printer.computeTrim(0,0,0,0);
        Point dpi = printer.getDPI();
        int left = (int)(ml * dpi.x) - trim.x;
        int right = client.width + trim.width - (int)(mr * dpi.x) - trim.x;
        int top = (int)(mt * dpi.y) - trim.y;
        int bottom = client.height + trim.height - (int)(mb * dpi.y) - trim.y;
        return new PrintMargin(left, right, top, bottom);
    }
}

架構演進總結

特性BasicEditorBasicEditor2BasicEditor3
UI 建構手動 SWT APIJFace Action + Manager繼承 v2 + 列印
命令封裝匿名 ListenerAction 子類別同上
狀態同步手動 (toolBar/item 對應)自動 (單一 Action 多重貢獻)同上
圖示管理ImageRegistryImageDescriptor + Registry同上
列印支援無無自訂 + StyledText 內建
程式碼行數~350~300~450

最佳實務建議

  1. 優先使用 JFace Action 架構: 專案一旦超過 3-4 個命令,Action + Manager 結構即可回收投資。
  2. 列印選擇策略: 簡單文字用 StyledText.print();複雜排版(表格、圖片、多欄)需自訂 GC 繪製。
  3. 資源管理: Font、Image、GC、Printer 皆需在 finally 或 try-with-resources 中釋放。
  4. 編碼處理: 讀寫檔案建議明確指定 StandardCharsets.UTF_8,避免跨平台亂碼。

總結

從 BasicEditor 到 BasicEditor3,展示了 SWT/JFace 應用從「手工組裝」走向「架構化開發」的典型演進路徑。掌握 Action 架構與列印子系統,能讓開發者快速構建具備專業級文件處理能力的桌面應用。

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