[Java] SWTでProgressbar | Archive Redo Blog

Archive Redo Blog

DBエンジニアのあれこれ備忘録

インストーラーなどでよく見かけるプログレスバー。

処理時間が長い場合、待っている間のイライラを多少なりとも緩和してくれます。


このプログレスバーをEclipseのSWTで実装してみました。

ProgressBar

100ミリ秒ごとに1%ずつプログレスバーが進むよう単純なものです。


実行用のクラスと、

package progressbar;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class TestProgressBar {

  // ディスプレイ
  private Display display;
  // シェル
  private Shell sShell = null;
  // プログレスバーテスト画面
  private ProgressBarComposite progressBarComposite= null;

  // 処理実行用スレッド(内部クラス)
  class TestProgressBarThread implements Runnable {

    // 処理実行
    public void run() {
      // 100msごとに1%ずつ処理を進行させ、
      // 途中経過を画面に通知しつつ、
      // 100%に達したら処理を終了させる。
      for (int i = 1; i <= 100; i++){
        try {
          Thread.sleep(100);
        } catch (InterruptedException e){
          break;
        }
        notifyProgress(i);
      }
      notifyFinish();
    }

    // 画面との同期を行う
    private boolean asyncExec(Runnable r) {
      if (display != null && !display.isDisposed()) {
        display.asyncExec(r);
        return true;
      } else {
        return false;
      }
    }

    // 途中経過を画面に通知する。
    protected void notifyProgress(final int percent) {
      asyncExec(new Runnable() {
        public void run() {
          if (progressBarComposite == null || progressBarComposite.isDisposed()) {
            return;
          }
          progressBarComposite.setProgress(percent);
          sShell.redraw();
        }
      });
    }

    // 処理終了を画面に通知する。
    protected void notifyFinish() {
      asyncExec(new Runnable() {
        public void run() {
          if (progressBarComposite == null || progressBarComposite.isDisposed()) {
            return;
          }
          progressBarComposite.finalize();
          sShell.redraw();
        }
      });
    }

  }

  // ProgressBarのテスト
  public static void main(String[] args) {
    TestProgressBar thisClass = new TestProgressBar();
    thisClass.display = Display.getDefault();
    thisClass.createSShell();
    thisClass.sShell.pack();
    thisClass.sShell.open();
    while (!thisClass.sShell.isDisposed()) {
      if (!thisClass.display.readAndDispatch())
        thisClass.display.sleep();
    }
    thisClass.display.dispose();
  }

  // シェルの生成
  private void createSShell() {

    sShell = new Shell(SWT.MODELESS | SWT.SHELL_TRIM);
    sShell.setLayout(new GridLayout());
    sShell.setSize(new org.eclipse.swt.graphics.Point(500, 300));
    sShell.setText("Test ProgressBar");

    progressBarComposite = new ProgressBarComposite(sShell, SWT.NONE);
    progressBarComposite.addOkListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent arg0) {
        progressBarComposite.dispose();
        sShell.dispose();
      }
    });

    sShell.layout();
    new Thread(new TestProgressBarThread()).start();

  }

}

画面のクラスです。

package progressbar;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.ProgressBar;

public class ProgressBarComposite extends Composite {

  // プログレスバー
  private ProgressBar progressBar = null;
  // パーセント表示
  private Label labelPercent = null;
  // OKボタン
  private Button buttonOk = null;
  // 画面
  public ProgressBarComposite(Composite parent, int style) {
    super(parent, style);
    initialize();
  }

  // 画面初期化
  private void initialize() {

    // グリッドレイアウト
    this.setBounds(new org.eclipse.swt.graphics.Rectangle(0, 0, 500, 100));
    GridLayout gridLayout = new GridLayout();
    gridLayout.numColumns = 2;
    setLayout(gridLayout);

    // プログレスバー
    GridData gridDataProgressBar = new GridData();
    gridDataProgressBar.widthHint = 400;
    gridDataProgressBar.heightHint = 20;
    gridDataProgressBar.horizontalSpan = 1;
    gridDataProgressBar.horizontalIndent = 10;
    progressBar = new ProgressBar(this, SWT.SMOOTH);
    progressBar.setMaximum(100);
    progressBar.setSelection(0);
    progressBar.setLayoutData(gridDataProgressBar);

    // パーセント
    GridData gridDataPercent = new GridData();
    gridDataPercent.widthHint = 30;
    gridDataPercent.horizontalSpan = 1;
    labelPercent = new Label(this, SWT.RIGHT);
    labelPercent.setText("0");
    labelPercent.setLayoutData(gridDataPercent);

    // OKボタン
    GridData gridDataOk = new GridData();
    gridDataOk.widthHint = 80;
    gridDataOk.horizontalSpan = 2;
    gridDataOk.horizontalIndent = 380;
    buttonOk = new Button(this, SWT.NONE);
    buttonOk.setText("OK");
    buttonOk.setEnabled(false);
    buttonOk.setLayoutData(gridDataOk);

  }

  // リスナー(OKボタン)
  public void addOkListener(SelectionListener listener) {
    buttonOk.addSelectionListener(listener);
  }

  // 処理経過の設定
  public void setProgress(int percent) {
    progressBar.setSelection(percent);
    labelPercent.setText(String.valueOf(percent) + "%");
    buttonOk.setEnabled(false);
  }

  // 処理終了時の設定
  public void finalize() {
    progressBar.setSelection(progressBar.getMaximum());
    buttonOk.setEnabled(true);
  }

}

プログレスバーを更新する部分をシーケンス図にするとこんな感じです。

ProgressBar

処理を実行するクラス(ここでは内部クラスTestProgressBarThread)をスレッドとして実行し、処理中に notifyProgress() や notifyFinish() を呼び出して、経過を画面に通知します。


処理を実行するスレッドからは直接画面を更新することができないため、notifyProgress()、notifyFinish() の中では、画面(ProgressBarComposite)の setProgress() や finalize() を実行するスレッドを生成し、Displayクラスの asyncExec() をラップした asyncExec() に引数として渡すというようなことをしています。



しかし、たったこれだけのことですが、ものすごく複雑なプログラムになってしまいますね。

慣れないと、何をやっているのかさっぱりわかりません^^;