本範例實作一個功能完整的圖片檢視器 ImageViewer,包含三大核心類別:
FileDialog)、列印預覽、直接列印。Canvas + PaintListener 繪製圖片,支援紅繪。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(); }
}
SWT.APPLICATION_MODAL),可調整大小。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();
}
}
封裝印表機幾何計算: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 + " }";
}
}
列印核心難點在於螢幕座標 (Display DPI, 通常 96) 與印表機座標 (Printer DPI, 如 600/1200) 的轉換。
image.getBounds()。printerDPI / displayDPI。影像在螢幕 100px,列印時實際需繪製 100 * (600/96) ≈ 625px 才能保持實體尺寸一致。(margin.right - margin.left),求縮放比 factor = min(1, available / (dpiScale * imgSize)) 確保不超出邊界。drawImage(src, srcX, srcY, srcW, srcH, dstX, dstY, dstW, dstH) 精確控制來源裁剪與目標縮放。Image、Printer、GC、Font、Color 皆需 dispose()。GC、Printer 必須在該執行緒釋放。shell.dispose()) 需確保 printer.dispose() (若為對話框建立)。