SWT ProgressBar 是視覺化任務進度的標準控件。建構子 new ProgressBar(parent, style) 的 style 參數決定外觀行為:
| Style 常數 | 外觀描述 | 適用場景 |
|---|---|---|
SWT.NULL (0) | 平台原生分段樣式 (Windows 塊狀) | 一般標準進度顯示 |
SWT.SMOOTH | 平滑連續填充,支援自訂繪製文字 | 需顯示百分比、自訂顏色/字型 |
SWT.INDETERMINATE | 動畫循環滑動,無具體數值 | 未知耗時任務 (連線、初始化) |
SWT.VERTICAL | 垂直方向進度條 | 側邊欄、特殊佈局需求 |
常用屬性方法:setMinimum(int)、setMaximum(int)、setSelection(int)、getSelection()。
建立一個 Shell 並排放置三種進度條,重點實作 PaintListener 於 SMOOTH 進度條中央繪製白色粗體「60%」文字。
package swt_jface.demo8;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Shell;
public class ProgressBarExamples {
Display display = new Display();
Shell shell = new Shell(display);
public ProgressBarExamples() {
ProgressBar pbDefault = new ProgressBar(shell, SWT.NULL);
final ProgressBar pbSmooth = new ProgressBar(shell, SWT.SMOOTH);
ProgressBar pbIndeterminate = new ProgressBar(shell, SWT.INDETERMINATE);
// 為 SMOOTH 進度條添加自訂繪製
pbSmooth.addPaintListener(new PaintListener() {
@Override
public void paintControl(PaintEvent e) {
Point size = pbSmooth.getSize();
Font font = new Font(shell.getDisplay(), "Courier", 10, SWT.BOLD);
e.gc.setFont(font);
e.gc.setForeground(shell.getDisplay().getSystemColor(SWT.COLOR_WHITE));
FontMetrics fm = e.gc.getFontMetrics();
String text = "60%";
int textWidth = fm.getAverageCharWidth() * text.length();
int textHeight = fm.getHeight();
// 計算置中座標
int x = (size.x - textWidth) / 2;
int y = (size.y - textHeight) / 2;
e.gc.drawString(text, x, y, true); // true 代表透明背景
font.dispose(); // 關鍵:釋放字體資源防洩漏
}
});
pbDefault.setSelection(60);
pbSmooth.setSelection(60);
// INDETERMINATE 不需設定 selection
// 佈局 (此處為簡化使用 setBounds,實務建議用 Layout)
pbDefault.setBounds(100, 10, 200, 20);
pbSmooth.setBounds(100, 40, 200, 20);
pbIndeterminate.setBounds(100, 70, 200, 20);
new Label(shell, SWT.NULL).setText("(default)").setBounds(10, 10, 80, 20);
new Label(shell, SWT.NULL).setText("SWT.SMOOTH").setBounds(10, 40, 80, 20);
shell.pack();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) display.sleep();
}
display.dispose();
}
public static void main(String[] args) {
new ProgressBarExamples();
}
}
模擬 10 秒計數任務,背景執行緒每秒透過 Display.asyncExec() 更新進度條與按鈕文字。並展示如何在 PaintListener 中動態計算百分比字串並繪製。
package swt_jface.demo8;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Shell;
public class CountNumbers {
Display display = new Display();
Shell shell = new Shell(display);
Button button;
ProgressBar progressBar;
public CountNumbers() {
shell.setLayout(new GridLayout(1, true));
button = new Button(shell, SWT.PUSH);
button.setText("Start to count");
button.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
progressBar = new ProgressBar(shell, SWT.SMOOTH);
progressBar.setMinimum(0);
progressBar.setMaximum(10);
progressBar.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
// 背景工作執行緒
final Thread worker = new Thread("Counter") {
@Override
public void run() {
for (int i = 0; i <= 10; i++) {
final int current = i;
try { Thread.sleep(1000); } catch (InterruptedException ex) { return; }
// 切換到 UI 執行緒更新
display.asyncExec(() -> {
if (button.isDisposed() || progressBar.isDisposed()) return;
button.setText("Counting: " + current);
progressBar.setSelection(current);
// 進度條數值改變會自動觸發重繪,PaintListener 會被呼叫
});
}
// 任務結束恢復按鈕
display.asyncExec(() -> {
if (!button.isDisposed()) {
button.setText("Completed");
button.setEnabled(true);
}
});
}
};
button.addListener(SWT.Selection, e -> {
button.setEnabled(false);
worker.start();
});
// 動態繪製百分比文字
progressBar.addPaintListener(new PaintListener() {
@Override
public void paintControl(PaintEvent e) {
int max = progressBar.getMaximum();
int min = progressBar.getMinimum();
int sel = progressBar.getSelection();
if (max == min) return; // 避免除零
String percent = String.format("%.0f%%", (sel * 100.0) / (max - min));
Point size = progressBar.getSize();
Font font = new Font(display, "Courier", 10, SWT.BOLD);
e.gc.setFont(font);
e.gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
FontMetrics fm = e.gc.getFontMetrics();
int w = fm.getAverageCharWidth() * percent.length();
int h = fm.getHeight();
e.gc.drawString(percent, (size.x - w) / 2, (size.y - h) / 2, true);
font.dispose();
}
});
shell.setSize(300, 100);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) display.sleep();
}
display.dispose();
}
public static void main(String[] args) {
new CountNumbers();
}
}
SWT 使用單執行緒租賃模型。任何背景執行緒直接呼叫 widget.setText()、progressBar.setSelection() 都會拋出 SWTException: Invalid thread access。解決方案:
Display.getDefault().asyncExec(Runnable):非阻塞,適合進度更新。Display.getDefault().syncExec(Runnable):阻塞等待 UI 完成,適合需取得 UI 回傳值時。widget.isDisposed() 避免視窗關閉後仍嘗試更新導致異常。在 paintControl 內 new Font()、new Color()、new Image() 後,必須在同一方法結束前呼叫 dispose()。這些資源由作業系統管理,不受 Java GC 控制,洩漏會導致應用程式長時間運行後崩潰 (GDI Handle 耗盡)。
任務開始前無法預估進度時使用 INDETERMINATE;任務進入可計算階段(如下載檔案已知總大小)時,需重新建立 ProgressBar 或切換樣式(SWT 不支援動態改變 Style),建議:
// 移除舊的 INDETERMINATE
progressBar.dispose();
// 建立新的 SMOOTH
progressBar = new ProgressBar(parent, SWT.SMOOTH);
parent.layout();
在 Eclipse RCP/Plugin 開發中,優先使用 org.eclipse.jface.dialogs.ProgressMonitorDialog 配合 IRunnableWithProgress,內建取消按鈕、子任務分割、對話框模態管理,比手工操作 ProgressBar 更健壯。