org.eclipse.swt.widgets.ProgressBar 提供三種視覺樣式,建構子第二個參數指定:
展示預設、平滑、不定三種進度條並排。重點示範 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 pb1 = new ProgressBar(shell, SWT.NULL);
final ProgressBar pb2 = new ProgressBar(shell, SWT.SMOOTH);
ProgressBar pb3 = new ProgressBar(shell, SWT.INDETERMINATE);
// 關鍵:為 SMOOTH 進度條添加繪製監聽器
pb2.addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
Point point = pb2.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 fontMetrics = e.gc.getFontMetrics();
int stringWidth = fontMetrics.getAverageCharWidth() * 4; // "60%" 約 4 字元寬
int stringHeight = fontMetrics.getHeight();
// 置中繪製
e.gc.drawString("60%", (point.x - stringWidth) / 2, (point.y - stringHeight) / 2, true);
font.dispose(); // 立即釋放字體資源
}
});
pb1.setSelection(60);
pb2.setSelection(60);
// pb3 不定模式無需 setSelection
pb1.setBounds(100, 10, 200, 20);
pb2.setBounds(100, 40, 200, 20);
pb3.setBounds(100, 70, 200, 20);
Label label = new Label(shell, SWT.NULL);
label.setText("(default)");
Label label2 = new Label(shell, SWT.NULL);
label2.setText("SWT.SMOOTH");
label.setAlignment(SWT.RIGHT);
label2.setAlignment(SWT.RIGHT);
label.setBounds(10, 10, 80, 20);
label2.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();
}
}
實務最常見場景:背景執行緒執行耗時任務,透過 Display.asyncExec() 安全更新 UI 進度條。並示範在 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() {
GridLayout gridLayout = new GridLayout(1, true);
shell.setLayout(gridLayout);
button = new Button(shell, SWT.BORDER);
button.setText("Start to count");
progressBar = new ProgressBar(shell, SWT.SMOOTH);
progressBar.setMinimum(0);
progressBar.setMaximum(10);
// 背景計數執行緒
final Thread countThread = new Thread() {
public void run() {
for (int i = 0; i <= 10; i++) {
final int num = i;
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
// 必須在 UI 執行緒更新 Widget
shell.getDisplay().asyncExec(new Runnable() {
public void run() {
if (button.isDisposed() || progressBar.isDisposed()) return;
button.setText("Counting: " + num);
progressBar.setSelection(num);
// progressBar.redraw(); // 觸發重繪,PaintListener 會自動被呼叫
}
});
}
}
};
button.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event event) {
button.setEnabled(false); // 防止重複點擊
countThread.start();
}
});
// 動態繪製百分比
progressBar.addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
String string = (progressBar.getSelection() * 1.0 / (progressBar.getMaximum() - progressBar.getMinimum()) * 100) + "%";
Point point = progressBar.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 fontMetrics = e.gc.getFontMetrics();
int stringWidth = fontMetrics.getAverageCharWidth() * string.length();
int stringHeight = fontMetrics.getHeight();
e.gc.drawString(string, (point.x - stringWidth) / 2, (point.y - stringHeight) / 2, true);
font.dispose();
}
});
button.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
progressBar.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
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 實作單執行緒 UI 模型(Apartment Threading)。任何非 UI 執行緒存取 Widget 皆會拋出 SWTException。必須使用 display.asyncExec(Runnable)(非阻塞)或 syncExec(Runnable)(阻塞等待)將 UI 操作排程至 UI 執行緒。
paintControl 中建立的 Font、Color、Image 必須在方法結束前 dispose(),否則會導致 GDI/Handle 洩漏。setSelection()、redraw()、視窗遮蔽還原皆會觸發 Paint。適用於啟動服務、連線伺服器等無法預估進度的場景。無需呼叫 setSelection(),啟動後自動播放動畫。任務完成後可切換為 SWT.SMOOTH 並設定 100% 顯示完成狀態。
若在 Eclipse RCP/Plugin 開發中,建議使用 org.eclipse.jface.dialogs.ProgressMonitorDialog 或 IRunnableWithProgress,它們封裝了對話框、取消按鈕、子任務分割等功能,比手動操作 ProgressBar 更標準。