標準 JDK 未內建 SWT (Standard Widget Toolkit),需額外引入平台相關 JAR。本文提供一個完整的 SWT 圖片檢視器範例,涵蓋 Image 載入、Canvas 繪圖、滑鼠滾輪縮放、拖曳平移、雙緩衝防閃爍、資源釋放等實務技巧。
<dependency>
<groupId>org.eclipse.platform</groupId>
<artifactId>org.eclipse.swt.win32.win32.x86_64</artifactId>
<version>4.28</version>
</dependency>
其他平台請換對應 artifactId:linux.gtk.x86_64、macosx.cocoa.x86_64 等。
package com.example.swt;
import org.eclipse.swt.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;
public class SwtImageViewer {
private Shell shell;
private Canvas canvas;
private Image originalImage;
private Image displayImage; // 縮放後用於繪圖
private float scale = 1.0f;
private int offsetX = 0, offsetY = 0;
private int dragStartX, dragStartY;
private boolean dragging = false;
public static void main(String[] args) {
new SwtImageViewer().run();
}
public void run() {
Display display = new Display();
shell = new Shell(display);
shell.setText("SWT 圖片檢視器");
shell.setLayout(new FillLayout());
shell.setSize(1024, 768);
// 選擇檔案對話框
FileDialog fd = new FileDialog(shell, SWT.OPEN);
fd.setFilterNames(new String[] {"圖片檔案", "所有檔案"});
fd.setFilterExtensions(new String[] {"*.jpg;*.jpeg;*.png;*.bmp;*.gif", "*.*"});
String path = fd.open();
if (path == null) {
display.dispose();
return;
}
// 載入原始圖片
originalImage = new Image(display, path);
if (originalImage.getBounds().isEmpty()) {
System.err.println("無法載入圖片: " + path);
display.dispose();
return;
}
canvas = new Canvas(shell, SWT.DOUBLE_BUFFERED | SWT.H_SCROLL | SWT.V_SCROLL);
canvas.addPaintListener(this::onPaint);
canvas.addMouseWheelListener(this::onMouseWheel);
canvas.addMouseListener(new MouseAdapter() {
@Override public void mouseDown(MouseEvent e) {
if (e.button == 1) { // 左鍵拖曳
dragging = true;
dragStartX = e.x - offsetX;
dragStartY = e.y - offsetY;
}
}
@Override public void mouseUp(MouseEvent e) {
dragging = false;
}
});
canvas.addMouseMoveListener(e -> {
if (dragging) {
offsetX = e.x - dragStartX;
offsetY = e.y - dragStartY;
canvas.redraw();
}
});
// 雙擊重置
canvas.addMouseListener(new MouseAdapter() {
@Override public void mouseDoubleClick(MouseEvent e) {
resetView();
}
});
// 選單列:檔案/檢視
createMenuBar();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) display.sleep();
}
// 資源釋放
if (originalImage != null) originalImage.dispose();
if (displayImage != null) displayImage.dispose();
display.dispose();
}
private void createMenuBar() {
Menu bar = new Menu(shell, SWT.BAR);
shell.setMenuBar(bar);
MenuItem fileItem = new MenuItem(bar, SWT.CASCADE);
fileItem.setText("檔案(&F)");
Menu fileMenu = new Menu(shell, SWT.DROP_DOWN);
fileItem.setMenu(fileMenu);
MenuItem openItem = new MenuItem(fileMenu, SWT.PUSH);
openItem.setText("開啟(&O)\tCtrl+O");
openItem.addListener(SWT.Selection, e -> openNewImage());
MenuItem exitItem = new MenuItem(fileMenu, SWT.PUSH);
exitItem.setText("結束(&X)");
exitItem.addListener(SWT.Selection, e -> shell.close());
MenuItem viewItem = new MenuItem(bar, SWT.CASCADE);
viewItem.setText("檢視(&V)");
Menu viewMenu = new Menu(shell, SWT.DROP_DOWN);
viewItem.setMenu(viewMenu);
MenuItem zoomIn = new MenuItem(viewMenu, SWT.PUSH);
zoomIn.setText("放大\tCtrl++");
zoomIn.addListener(SWT.Selection, e -> zoom(1.2f));
MenuItem zoomOut = new MenuItem(viewMenu, SWT.PUSH);
zoomOut.setText("縮小\tCtrl+-");
zoomOut.addListener(SWT.Selection, e -> zoom(0.8f));
MenuItem resetItem = new MenuItem(viewMenu, SWT.PUSH);
resetItem.setText("實際大小\tCtrl+0");
resetItem.addListener(SWT.Selection, e -> resetView());
}
private void openNewImage() {
FileDialog fd = new FileDialog(shell, SWT.OPEN);
fd.setFilterExtensions(new String[] {"*.jpg;*.jpeg;*.png;*.bmp;*.gif"});
String path = fd.open();
if (path != null) {
if (originalImage != null) originalImage.dispose();
if (displayImage != null) displayImage.dispose();
originalImage = new Image(shell.getDisplay(), path);
resetView();
}
}
private void onPaint(PaintEvent e) {
GC gc = e.gc;
gc.setAntialias(SWT.ON);
gc.setInterpolation(SWT.HIGH);
if (displayImage == null || displayImage.isDisposed()) {
// 首次或縮放後重建
Rectangle src = originalImage.getBounds();
int w = (int) (src.width * scale);
int h = (int) (src.height * scale);
displayImage = new Image(shell.getDisplay(), w, h);
GC imgGc = new GC(displayImage);
imgGc.setAntialias(SWT.ON);
imgGc.setInterpolation(SWT.HIGH);
imgGc.drawImage(originalImage, 0, 0, src.width, src.height, 0, 0, w, h);
imgGc.dispose();
}
// 計算置中或偏移位置
Rectangle client = canvas.getClientArea();
int x = (client.width - displayImage.getBounds().width) / 2 + offsetX;
int y = (client.height - displayImage.getBounds().height) / 2 + offsetY;
gc.drawImage(displayImage, x, y);
}
private void onMouseWheel(MouseEvent e) {
if ((e.stateMask & SWT.CTRL) != 0) {
zoom(e.count > 0 ? 1.1f : 0.9f); // 向上放大
} else {
// 垂直捲動由 SWT.H_SCROLL/V_SCROLL 自動處理
}
}
private void zoom(float factor) {
scale *= factor;
scale = Math.max(0.1f, Math.min(10f, scale)); // 限制 10%-1000%
if (displayImage != null) {
displayImage.dispose();
displayImage = null;
}
canvas.redraw();
}
private void resetView() {
scale = 1.0f;
offsetX = offsetY = 0;
if (displayImage != null) {
displayImage.dispose();
displayImage = null;
}
canvas.redraw();
}
}
| 技術 | 說明 |
|---|---|
SWT.DOUBLE_BUFFERED | Canvas 內建雙緩衝,消除重繪閃爍,無需手動實作。 |
Image.dispose() | SWT 圖片資源由作業系統管理,必須手動釋放,否則會洩漏 GDI/GTK 控制代碼。 |
GC.setInterpolation(SWT.HIGH) | 啟用高品質雙三次/蘭喬斯插值,縮放畫質優於預設最近鄰。 |
drawImage(src, sx, sy, sw, sh, dx, dy, dw, dh) | 單次呼叫完成裁剪+縮放+繪製,效能最佳。 |
滑鼠滾輪 e.count | 正值=向上/向前,負值=向下/向後;配合 CTRL 鍵區分縮放與捲動。 |
new Image(display, path) 會完整解碼到記憶體。超大圖 (如 100MP+) 建議先用 ImageIO 或 TwelveMonkeys 讀取縮略圖,或分塊載入。-Dsun.java2d.uiScale.enabled=true 或 SWT 4.24+ Display.setAutoScaling(true)。maven-shade-plugin + SWT 原生庫解壓縮)。