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

SWT/JFace ProgressBar 進度條控件全解析:樣式、自訂繪製與執行緒更新實作

     編輯:Java 相關知識

ProgressBar 三大樣式

org.eclipse.swt.widgets.ProgressBar 提供三種視覺樣式,建構子第二個參數指定:

  • SWT.NULL (預設):平台原生標準進度條(Windows 為分段塊狀)。
  • SWT.SMOOTH:平滑連續填充樣式,適合自訂繪製文字(如百分比)。
  • SWT.INDETERMINATE:不定進度模式(動畫循環),用於無法預估耗時的任務。

範例一:三種樣式對比與 SMOOTH 自訂繪製 (ProgressBarExamples)

展示預設、平滑、不定三種進度條並排。重點示範 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();
    }
}

範例二:背景執行緒模擬任務與進度更新 (CountNumbers)

實務最常見場景:背景執行緒執行耗時任務,透過 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();
    }
}

核心最佳實踐總結

1. 執行緒安全:asyncExec 是關鍵

SWT 實作單執行緒 UI 模型(Apartment Threading)。任何非 UI 執行緒存取 Widget 皆會拋出 SWTException。必須使用 display.asyncExec(Runnable)(非阻塞)或 syncExec(Runnable)(阻塞等待)將 UI 操作排程至 UI 執行緒。

2. 自訂繪製 (PaintListener) 注意事項

  • 資源管理:在 paintControl 中建立的 Font、Color、Image 必須在方法結束前 dispose(),否則會導致 GDI/Handle 洩漏。
  • 效能:避免在 Paint 事件中進行複雜計算或物件建立(如 new StringBuffer),預先計算好字串或快取 FontMetrics。
  • 觸發時機:setSelection()、redraw()、視窗遮蔽還原皆會觸發 Paint。

3. 不定進度模式 (INDETERMINATE)

適用於啟動服務、連線伺服器等無法預估進度的場景。無需呼叫 setSelection(),啟動後自動播放動畫。任務完成後可切換為 SWT.SMOOTH 並設定 100% 顯示完成狀態。

4. 進度條與 JFace ProgressMonitor

若在 Eclipse RCP/Plugin 開發中,建議使用 org.eclipse.jface.dialogs.ProgressMonitorDialog 或 IRunnableWithProgress,它們封裝了對話框、取消按鈕、子任務分割等功能,比手動操作 ProgressBar 更標準。

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