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

SWT/JFace ProgressBar 進度條實戰:從基礎樣式到多執行緒動態更新完整指南

     編輯:Java 相關知識

ProgressBar 控件核心屬性與樣式

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()。

範例一:三種樣式並列展示與 SMOOTH 模式文字繪製

建立一個 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();
    }
}

進階主題:最佳實踐與常見坑

1. 執行緒模型鐵律:UI 只能在 UI Thread 操作

SWT 使用單執行緒租賃模型。任何背景執行緒直接呼叫 widget.setText()、progressBar.setSelection() 都會拋出 SWTException: Invalid thread access。解決方案:

  • Display.getDefault().asyncExec(Runnable):非阻塞,適合進度更新。
  • Display.getDefault().syncExec(Runnable):阻塞等待 UI 完成,適合需取得 UI 回傳值時。
  • 檢查 widget.isDisposed() 避免視窗關閉後仍嘗試更新導致異常。

2. PaintListener 資源管理鐵律

在 paintControl 內 new Font()、new Color()、new Image() 後,必須在同一方法結束前呼叫 dispose()。這些資源由作業系統管理,不受 Java GC 控制,洩漏會導致應用程式長時間運行後崩潰 (GDI Handle 耗盡)。

3. INDETERMINATE 模式切換技巧

任務開始前無法預估進度時使用 INDETERMINATE;任務進入可計算階段(如下載檔案已知總大小)時,需重新建立 ProgressBar 或切換樣式(SWT 不支援動態改變 Style),建議:

// 移除舊的 INDETERMINATE
progressBar.dispose();
// 建立新的 SMOOTH
progressBar = new ProgressBar(parent, SWT.SMOOTH);
parent.layout();

4. JFace 整合:ProgressMonitorDialog

在 Eclipse RCP/Plugin 開發中,優先使用 org.eclipse.jface.dialogs.ProgressMonitorDialog 配合 IRunnableWithProgress,內建取消按鈕、子任務分割、對話框模態管理,比手工操作 ProgressBar 更健壯。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved