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

SWT/JFace 圖片檢視器完整實作:開啟、列印預覽與分頁列印功能

     編輯:Java 相關知識

專案架構概覽

本範例實作一個功能完整的圖片檢視器 ImageViewer,包含三大核心類別:

  1. ImageViewer:主視窗,工具列 (開啟/預覽/列印)、Canvas 顯示圖片、列印邏輯 (含 DPI 縮放)。
  2. ImagePrintPreviewDialog:列印預覽對話框,支援選擇印表機、設定邊距 (英寸)、即時預覽頁面與邊框。
  3. PrintMargin:工具類,根據印表機 DPI、不可列印區域 計算實際可列印邊距 (像素)。

核心類別一:ImageViewer (主程式)

功能亮點

  • 工具列按鈕:開啟檔案 (FileDialog)、列印預覽、直接列印。
  • Canvas + PaintListener 繪製圖片,支援紅繪。
  • 列印執行緒分離:避免列印卡死 UI,計算 dpiScaleFactor 實現「所見即所得」尺寸縮放。
package swt_jface.demo11;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.printing.PrintDialog;
import org.eclipse.swt.printing.Printer;
import org.eclipse.swt.printing.PrinterData;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;

public class ImageViewer {
    Display display = new Display();
    Shell shell = new Shell(display);
    Canvas canvas;
    Image image;
    String fileName;

    public ImageViewer() {
        shell.setText("Image viewer");
        shell.setLayout(new GridLayout(1, true));

        // 工具列
        ToolBar toolBar = new ToolBar(shell, SWT.FLAT);
        createToolItem(toolBar, "Open", e -> openImage());
        createToolItem(toolBar, "Preview", e -> new ImagePrintPreviewDialog(this).open());
        createToolItem(toolBar, "Print", e -> print());

        // 畫布
        canvas = new Canvas(shell, SWT.BORDER);
        canvas.setBackground(display.getSystemColor(SWT.COLOR_WHITE));
        canvas.setLayoutData(new GridData(GridData.FILL_BOTH));
        canvas.addPaintListener(e -> {
            if (image == null) e.gc.drawString("No image", 0, 0);
            else e.gc.drawImage(image, 0, 0);
        });

        // 載入預設圖片
        image = new Image(display, "C:/icons/scene.jpg");
        fileName = "scene.jpg";

        shell.setSize(500, 400);
        shell.open();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) display.sleep();
        }
        display.dispose();
    }

    private void createToolItem(ToolBar bar, String text, Listener listener) {
        ToolItem item = new ToolItem(bar, SWT.PUSH);
        item.setText(text);
        item.addListener(SWT.Selection, listener);
    }

    private void openImage() {
        FileDialog dialog = new FileDialog(shell, SWT.OPEN);
        String file = dialog.open();
        if (file != null) {
            if (image != null) image.dispose();
            try { image = new Image(display, file); fileName = file; }
            catch (RuntimeException ex) { image = null; System.err.println("Failed to load: " + file); }
            canvas.redraw();
        }
    }

    void print() {
        PrintDialog dialog = new PrintDialog(shell);
        PrinterData data = dialog.open();
        if (data == null) return;
        Printer printer = new Printer(data);
        print(printer, null);
    }

    // 核心列印邏輯:支援外部傳入 Printer 與 Margin (預覽用)
    void print(final Printer printer, PrintMargin printMargin) {
        if (image == null) return;
        final Point printerDPI = printer.getDPI();
        final Point displayDPI = display.getDPI();
        final PrintMargin margin = (printMargin == null ? PrintMargin.getPrintMargin(printer, 1.0) : printMargin);

        new Thread(() -> {
            if (!printer.startJob(fileName)) { printer.dispose(); return; }
            GC gc = new GC(printer);
            if (!printer.startPage()) { gc.dispose(); return; }

            int imgW = image.getBounds().width;
            int imgH = image.getBounds().height;
            double scaleX = printerDPI.x * 1.0 / displayDPI.x;
            double scaleY = printerDPI.y * 1.0 / displayDPI.y;

            // 計算縮放比例以適應邊距
            double factor = Math.min(1, (margin.right - margin.left) * 1.0 / (scaleX * imgW));
            factor = Math.min(factor, (margin.bottom - margin.top) * 1.0 / (scaleY * imgH));

            gc.drawImage(image, 0, 0, imgW, imgH,
                margin.left, margin.top,
                (int)(scaleX * factor * imgW), (int)(scaleY * factor * imgH));
            
            gc.dispose();
            printer.endPage();
            printer.endJob();
            printer.dispose();
            System.out.println("Printing job done!");
        }).start();
    }

    public static void main(String[] args) { new ImageViewer(); }
}

核心類別二:ImagePrintPreviewDialog (列印預覽)

功能亮點

  • 模態對話框 (SWT.APPLICATION_MODAL),可調整大小。
  • 印表機選擇下拉選單 (Combo) 切換邊距 (0.5~3.0 英吋)。
  • Canvas 繪製縮略圖:白色背景、虛線邊框表示可列印區域、圖片按比例縮放繪製。
  • 「列印」按鈕回呼 ImageViewer.print(printer, margin) 實現無縫列印。
package swt_jface.demo11;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.printing.PrintDialog;
import org.eclipse.swt.printing.Printer;
import org.eclipse.swt.printing.PrinterData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Dialog;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;

public class ImagePrintPreviewDialog extends Dialog {
    ImageViewer viewer;
    Shell shell;
    Canvas canvas;
    Printer printer;
    PrintMargin margin;
    Combo combo;

    public ImagePrintPreviewDialog(ImageViewer viewer) {
        super(viewer.shell);
        this.viewer = viewer;
    }

    public void open() {
        shell = new Shell(viewer.shell, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL | SWT.RESIZE);
        shell.setText("Print preview");
        shell.setLayout(new GridLayout(4, false));

        // 選擇印表機按鈕
        Button btnSelect = new Button(shell, SWT.PUSH);
        btnSelect.setText("Select a printer");
        btnSelect.addListener(SWT.Selection, e -> {
            PrintDialog dialog = new PrintDialog(shell);
            PrinterData data = dialog.open();
            if (data == null) return;
            setPrinter(new Printer(data), Double.parseDouble(combo.getItem(combo.getSelectionIndex())));
        });

        new Label(shell, SWT.NULL).setText("Margin in inches: ");
        combo = new Combo(shell, SWT.READ_ONLY);
        for (double d = 0.5; d <= 3.0; d += 0.5) combo.add(String.valueOf(d));
        combo.select(1); // 預設 1.0
        combo.addListener(SWT.Selection, e -> 
            setPrinter(printer, Double.parseDouble(combo.getItem(combo.getSelectionIndex())))
        );

        Button btnPrint = new Button(shell, SWT.PUSH);
        btnPrint.setText("Print");
        btnPrint.addListener(SWT.Selection, e -> {
            if (printer == null) viewer.print();
            else viewer.print(printer, margin);
            shell.dispose();
        });

        // 預覽畫布
        canvas = new Canvas(shell, SWT.BORDER);
        GridData gd = new GridData(GridData.FILL_BOTH);
        gd.horizontalSpan = 4;
        canvas.setLayoutData(gd);
        canvas.addPaintListener(new PaintListener() {
            @Override
            public void paintControl(PaintEvent e) {
                if (printer == null || printer.isDisposed()) return;
                int border = 20;
                Rectangle prnBounds = printer.getBounds();
                Point cvSize = canvas.getSize();

                // 計算視圖縮放比
                double viewScale = Math.min(
                    (cvSize.x - 2*border) * 1.0 / prnBounds.width,
                    (cvSize.y - 2*border) * 1.0 / prnBounds.height
                );
                int offX = (cvSize.x - (int)(viewScale * prnBounds.width)) / 2;
                int offY = (cvSize.y - (int)(viewScale * prnBounds.height)) / 2;

                // 繪製白紙背景
                e.gc.setBackground(shell.getDisplay().getSystemColor(SWT.COLOR_WHITE));
                e.gc.fillRectangle(offX, offY, (int)(viewScale*prnBounds.width), (int)(viewScale*prnBounds.height));

                // 繪製邊距虛線框
                e.gc.setLineStyle(SWT.LINE_DASH);
                e.gc.setForeground(shell.getDisplay().getSystemColor(SWT.COLOR_BLACK));
                int mOffX = offX + (int)(viewScale * margin.left);
                int mOffY = offY + (int)(viewScale * margin.top);
                e.gc.drawRectangle(mOffX, mOffY,
                    (int)(viewScale * (margin.right - margin.left)),
                    (int)(viewScale * (margin.bottom - margin.top)));

                // 繪製圖片 (若有)
                if (viewer.image != null) {
                    int imgW = viewer.image.getBounds().width;
                    int imgH = viewer.image.getBounds().height;
                    Point dispDPI = shell.getDisplay().getDPI();
                    Point prnDPI = printer.getDPI();
                    double dpiScaleX = prnDPI.x * 1.0 / dispDPI.x;
                    double dpiScaleY = prnDPI.y * 1.0 / dispDPI.y;
                    double factor = Math.min(1, (margin.right-margin.left)*1.0/(dpiScaleX*imgW));
                    factor = Math.min(factor, (margin.bottom-margin.top)*1.0/(dpiScaleY*imgH));

                    e.gc.drawImage(viewer.image, 0, 0, imgW, imgH,
                        mOffX, mOffY,
                        (int)(dpiScaleX * factor * imgW * viewScale),
                        (int)(dpiScaleY * factor * imgH * viewScale));
                }
            }
        });

        shell.setSize(400, 400);
        shell.open();
        setPrinter(null, 1.0); // 初始化預設印表機
        while (!shell.isDisposed()) {
            if (!shell.getDisplay().readAndDispatch()) shell.getDisplay().sleep();
        }
    }

    void setPrinter(Printer prn, double marginSize) {
        if (prn == null) prn = new Printer(Printer.getDefaultPrinterData());
        this.printer = prn;
        this.margin = PrintMargin.getPrintMargin(prn, marginSize);
        canvas.redraw();
    }
}

核心類別三:PrintMargin (邊距計算工具)

封裝印表機幾何計算:getClientArea() (可列印區域)、computeTrim() (硬體邊界)、getDPI()。將英寸轉換為像素。

package swt_jface.demo11;

import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.printing.Printer;

public class PrintMargin {
    public int left, right, top, bottom;

    private PrintMargin(int l, int r, int t, int b) { left=l; right=r; top=t; bottom=b; }

    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 leftMargin   = (int)(mL * dpi.x) - trim.x;
        int rightMargin  = client.width + trim.width - (int)(mR * dpi.x) - trim.x;
        int topMargin    = (int)(mT * dpi.y) - trim.y;
        int bottomMargin = client.height + trim.height - (int)(mB * dpi.y) - trim.y;

        return new PrintMargin(leftMargin, rightMargin, topMargin, bottomMargin);
    }

    @Override public String toString() {
        return "Margin { " + left + ", " + right + "; " + top + ", " + bottom + " }";
    }
}

關鍵技術總結:DPI 縮放與座標轉換

列印核心難點在於螢幕座標 (Display DPI, 通常 96) 與印表機座標 (Printer DPI, 如 600/1200) 的轉換。

  • 影像像素尺寸:image.getBounds()。
  • DPI 縮放因子:printerDPI / displayDPI。影像在螢幕 100px,列印時實際需繪製 100 * (600/96) ≈ 625px 才能保持實體尺寸一致。
  • 邊距限制:計算可列印區域寬高 (margin.right - margin.left),求縮放比 factor = min(1, available / (dpiScale * imgSize)) 確保不超出邊界。
  • GC.drawImage 10 參數版:drawImage(src, srcX, srcY, srcW, srcH, dstX, dstY, dstW, dstH) 精確控制來源裁剪與目標縮放。

資源管理提醒

  • Image、Printer、GC、Font、Color 皆需 dispose()。
  • 列印執行緒中建立的 GC、Printer 必須在該執行緒釋放。
  • 預覽對話框關閉時 (shell.dispose()) 需確保 printer.dispose() (若為對話框建立)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved