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

SWT/JFace 文本編輯器完整實戰:三版本演進與專業列印整合

     編輯:Java 相關知識

前言

本文呈現一個文本編輯器的三個演進版本,展示 SWT/JFace 應用開發的最佳實踐路徑:從手動控件綁定(v1)→ JFace Action/Manager 解耦(v2)→ 整合 StyledText 專業列印(v3)。涵蓋選單/工具列、檔案 IO、編碼、修改追蹤、字體/顏色/換行設置、列印分頁/頁碼/頁首尾等完整功能。

版本 1:手動綁定

核心特點

  • 直接建立 Menu、MenuItem、ToolBar、ToolItem。
  • 用 Listener 手動綁定事件。
  • ImageRegistry 管理圖示資源。
  • GridLayout 佈局,StyledText 編輯區。

關鍵代碼片段

// 工具列建立
ToolBar toolBar = new ToolBar(shell, SWT.FLAT | SWT.RIGHT);
ToolItem tiNew = new ToolItem(toolBar, SWT.PUSH);
tiNew.setText("&New"); tiNew.setImage(getImage("new.gif"));
tiNew.addListener(SWT.Selection, e -> { if (handleChangesBeforeDiscard()) { file = null; text.setText(""); } });

// 選單建立
Menu menuBar = new Menu(shell, SWT.BAR);
MenuItem fileMenuItem = new MenuItem(menuBar, SWT.CASCADE);
fileMenuItem.setText("&File");
Menu fileMenu = new Menu(shell, SWT.DROP_DOWN);
MenuItem miNew = new MenuItem(fileMenu, SWT.PUSH);
miNew.setText("&New\tCtrl+N");
miNew.setAccelerator(SWT.CTRL | 'N');
miNew.addListener(SWT.Selection, e -> { ... });
fileMenuItem.setMenu(fileMenu);

// 修改追蹤
text.addModifyListener(e -> hasUnsavedChanges = true);

// 關閉前確認
boolean handleChangesBeforeDiscard() {
    if (!hasUnsavedChanges) return true;
    MessageBox box = new MessageBox(shell, SWT.ICON_WARNING | SWT.YES | SWT.NO | SWT.CANCEL);
    box.setMessage("Save changes to " + (file == null ? "untitled?" : file.getName()));
    int ret = box.open();
    if (ret == SWT.YES) return saveTextToFile();
    return ret == SWT.NO;
}

版本 2:JFace Action + Manager 解耦

核心改進

  • Action 封裝命令:文字、圖示、快捷鍵、啟用狀態、執行邏輯集中管理。
  • MenuManager/ToolBarManager:聲明式構建選單/工具列,自動處理分隔符、分組。
  • Action 復用:同一 Action 同時加入選單與工具列。
  • AS_CHECK_BOX:actionWrap.setChecked() 雙向同步選單/工具列勾選狀態。

關鍵代碼

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

Action actionWrap = new Action("&Wrap", IAction.AS_CHECK_BOX) {
    public void run() { text.setWordWrap(isChecked()); }
};
actionWrap.setAccelerator(SWT.CTRL | 'W');

// Manager 組裝
ToolBarManager tbm = new ToolBarManager(toolBar);
tbm.add(actionNew); tbm.add(actionOpen); tbm.add(actionSave);
tbm.add(new Separator());
tbm.add(actionCopy); tbm.add(actionCut); tbm.add(actionPaste);
tbm.add(new Separator());
tbm.add(actionWrap);
tbm.update(true);

MenuManager barMenu = new MenuManager();
MenuManager fileMenu = new MenuManager("&File");
MenuManager editMenu = new MenuManager("&Edit");
MenuManager formatMenu = new MenuManager("&Format");
barMenu.add(fileMenu); barMenu.add(editMenu); barMenu.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);

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

版本 3:整合 StyledText 專業列印

新增功能

  • 列印 Action:普通文字列印(自定義分頁)與 StyledText.print() 內建列印雙模式。
  • PrintMargin:精確邊距計算(英寸轉像素、DPI、trim 修正)。
  • 分頁邏輯:單詞不拆分、頁碼居中、頁首/頁尾。
  • 非阻塞列印:背景執行緒執行,UI 保持響應。

核心列印實現

// 普通文字列印
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 吋邊距
    
    Font font = new Font(printer, "Arial", 12, SWT.NORMAL);
    gc.setFont(font);
    int lineHeight = gc.getFontMetrics().getHeight();
    
    printer.startPage();
    // 繪製頁碼
    String page = "- 1 -";
    gc.drawString(page, (margins.right - margins.left - gc.textExtent(page).x) / 2 + margins.left, margins.bottom + gc.textExtent(page).y);
    
    // 逐字符處理換行/分頁
    StringBuffer buffer = new StringBuffer();
    int x = margins.left, y = margins.top;
    for (int i = 0; i < text.length(); ) {
        char c = text.charAt(i++);
        if (c == '\r' || c == '\n') {
            if (c == '\r' && i < text.length() && text.charAt(i) == '\n') i++;
            printLine(gc, printer, buffer.toString(), x, y, margins, lineHeight);
            buffer.setLength(0);
            newLine(gc, printer, margins, lineHeight, x, y, pageNumber);
        } else if (c == ' ' || c == '\t') {
            // 空白處理:看後續單詞決定是否換行
            buffer.append(c);
            if (i < text.length() && !Character.isWhitespace(text.charAt(i))) {
                String word = readWord(text, i);
                if (gc.textExtent(buffer + word).x > margins.right - margins.left) {
                    printLine(gc, printer, buffer.toString(), x, y, margins, lineHeight);
                    buffer.setLength(0);
                }
            }
        } else {
            buffer.append(c);
        }
    }
    if (buffer.length() > 0) printLine(gc, printer, buffer.toString(), x, y, margins, lineHeight);
    if (y + lineHeight <= margins.bottom) printer.endPage();
    printer.endJob();
    // 釋放資源...
}

// StyledText 內建列印(一鍵)
Action actionPrint2 = new Action("Print (StyledText)") {
    public void run() {
        StyledTextPrintOptions options = new StyledTextPrintOptions();
        options.header = "SWT";
        options.footer = "Page ";
        options.jobName = "Text";
        Runnable runnable = text.print(new Printer(), options);
        runnable.run(); // 或提交執行緒池
    }
};

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 clientArea = printer.getClientArea();
        Rectangle trim = printer.computeTrim(0, 0, 0, 0);
        Point dpi = printer.getDPI();
        
        int leftMargin = (int)(mL * dpi.x) - trim.x;
        int rightMargin = clientArea.width + trim.width - (int)(mR * dpi.x) - trim.x;
        int topMargin = (int)(mT * dpi.y) - trim.y;
        int bottomMargin = clientArea.height + trim.height - (int)(mB * dpi.y) - trim.y;
        
        return new PrintMargin(leftMargin, rightMargin, topMargin, bottomMargin);
    }
}

三版本架構對比

特性v1 手動綁定v2 Action+Managerv3 +專業列印
代碼結構單類、耦合度高命令解耦、Manager 組裝功能模組化、列印獨立
Action 復用不可(選單/工具列各寫)同一 Action 多處使用同 v2
狀態同步手動同步勾選項AS_CHECK_BOX 自動同步同 v2
擴展性差良(新增 Action 即可)優(列印模組可插拔)
列印能力無無專業分頁/頁碼/樣式保留
學習曲線低中(理解 JFace 命令模式)中高

StyledTextPrintOptions 詳細配置

選項類型說明
headerString頁首,支援 <page>、<date>、<time>
footerString頁尾,同支援變數
jobNameString列印作業名稱
lineStyleint行樣式常數

檔案 IO 最佳實踐(三版本共用)

// 讀取:指定編碼、緩衝讀取
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8));
StringBuffer sb = new StringBuffer();
String line;
while ((line = reader.readLine()) != null) { sb.append(line).append("\r\n"); }
text.setText(sb.toString());

// 寫入:原子性寫入(寫臨時檔再 rename)
File temp = File.createTempFile("edit", ".tmp", file.getParentFile());
try (FileWriter fw = new FileWriter(temp, StandardCharsets.UTF_8)) {
    fw.write(text.getText());
}
Files.move(temp.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING);

總結

文本編輯器三版本演進展示了 SWT/JFace 應用開發的成長路徑:

  1. v1 手工打造:理解基礎控件、佈局、事件、資源管理。
  2. v2 架構解耦:採用 JFace Action/Manager 命令模式,實現 UI 與邏輯分離、狀態自動同步、聲明式組裝。
  3. v3 功能專業化:整合 StyledText.print() 內建列印與自定義分頁列印,解決文檔輸出的專業需求。

核心最佳實踐:

  • 資源(Image/Font/Color/Printer)建立即規劃釋放,用 dispose() 或 try-with-resources。
  • 長時間操作(列印、大檔 IO)必須背景執行緒,UI 保持響應。
  • 編碼顯式指定 UTF-8,避免跨平台亂碼。
  • 修改狀態追蹤 + 關閉確認,防止資料遺失。
  • 列印邊距計算需考慮 DPI、trim、clientArea 三要素。

此系列代碼可作為 SWT/JFace 桌面應用的標準起始模板,直接擴展為筆記本、代碼編輯器、日誌查看器等產品級工具。

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