S-JIS[2009-11-03/2009-11-08] 変更履歴

JProgressBar(Swing)

JProgressBarは、進捗状況を表すバー。 プログレスバーとか進捗バーとか呼ばれる。
実行状況をリアルタイムでユーザーに知らせるもの。


プログレスバーの主なメソッド

メソッド 使用例 説明
コンストラクター JProgressBar bar = new JProgressBar(0, 100); プログレスバーコンポーネントを生成する。
進捗の最小値と最大値を指定する。
引数を1つも指定しない場合、最小値0/最大値100になる。
方向(orient)を指定するコンストラクターもあるが、縦(VERTICAL)を指定することはまず無いと思う^^;
setValue(値) bar.setValue(50); プログレスバーの現在値を設定する。
getValue() int n = bar.getValue(); プログレスバーの現在値を取得する。
getMinimum() int min = bar.getMinimum(); プログレスバーの最小値を取得する。
getMaximum() int max = bar.getMaximum(); プログレスバーの最大値を取得する。
getPercentComplete() double complete = bar.getPercentComplete(); 進捗度合いを「割合(0.0〜1.0)」で返す。
getString() String complete = bar.getString(); 進捗度合いを文字列で返す。
デフォルトでは「整数%(0%100%)」の値。
setString()で文字列をセットしていた場合はその文字列が返る。
setStringPainted(b) bar.setStringPainted(true); trueを指定すると、プログレスバーと共に進捗度合いを表示するようになる。
getString()で取得した文字列が表示される)
デフォルトはfalse。

基本的な使用例

他のコンポーネントと同じく、JProgressBarのインスタンスを作り、コンテナ(JFrameJDialogやJPanel)に登録しておく。
そしてsetValue()でもって進捗具合をセットしてやる。

ただし、“進捗具合を表す”という目的上、値をセットするのは別スレッドからになるだろう。
基本的にSwingのオブジェクトはMTセーフではないので、別スレッドから値をセットしていいのかどうか不安だが…とりあえず試した感じでは大丈夫そう。
複数スレッドから同一プログレスバーインスタンスのsetValue()を呼び出すのは確実に駄目だが、1スレッドからなら大丈夫なのだろう、きっと。

プログレスバーのあるダイアログの例:

import javax.swing.JDialog;
import javax.swing.JProgressBar;
@SuppressWarnings("serial")
public class SampleProgressDialog extends JDialog {

	protected JProgressBar progressBar;

	public SampleProgressDialog(Frame owner, boolean modal) {
		super(owner, modal);
	}

	public void init(int min, int max) {
		setTitle("プログレスバーの実験");

		setSize(256, 256);
		setLocationRelativeTo(null);

		setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);

		// プログレスバー生成
		this.progressBar = new JProgressBar(min, max);

		Container c = getContentPane();
		c.add(progressBar, BorderLayout.PAGE_START);
	}

	public JProgressBar getProgressBar() {
		return progressBar;
	}
}

サンプルダイアログを使用する例:

		final int min = 0;
		final int max = 50000;

		final SampleProgressDialog dialog = new SampleProgressDialog(null, false); //モードレスダイアログ
		dialog.init(min, max);
		dialog.setVisible(true); //ダイアログの表示

		// スレッド生成・実行
		Thread thread = new Thread() {
			@Override
			public void run() {
				JProgressBar progressBar = dialog.getProgressBar();
				for (int i = min; i < max; i++) {

					//何かの処理

					progressBar.setValue(i + 1); //プログレスバーに現在値をセット
				}
			}
		};
		thread.start();
//		thread.join();

モードレスダイアログ(呼び出し元とは独立に動く)の場合、setVisible(true)の呼び出しからすぐ戻ってくるので、その後でプログレスバーを動かすスレッドを実行すればよい。
thread.join()を呼んでしまうとスレッド終了待ちに入ってしまって呼び出し元のフレーム等が動かせないので、ダイアログがモードレスである意味があまり無いが(苦笑))

モーダルダイアログ(呼び出し元が使えなくなる)の場合、setVisible(true)が終わるのはダイアログが終了した後なので、スレッドの実行開始(thread.start())はsetVisible(true)より前に行わないといけない。

		final SampleProgressDialog dialog = new SampleProgressDialog(null, true); //モーダルダイアログ
		dialog.init(min, max);

		// スレッド生成・実行
		Thread thread = new Thread() { 〜 };
		thread.start();

		dialog.setVisible(true); //ダイアログの表示

		thread.join();

キャンセル可能なプログレスダイアログ

親切な作りにするなら、ダイアログにキャンセルボタンでも設けて、キャンセルボタンが押された場合はスレッドを中断するのが良いだろう。
ただしThread#stop()は非推奨なので、終了条件はスレッド内で判断すべきである。

		// スレッド生成
		Thread thread = new Thread() {
			@Override
			public void run() {
				JProgressBar progressBar = dialog.getProgressBar();
				for (int i = min; i < max; i++) {
					if (終了判定) {
						break;
					}

					//何かの処理

					progressBar.setValue(i + 1); //プログレスバーに現在値をセット
				}
			}
		};

モードレスダイアログの場合、setVisible(true)の後でスレッドを開始できるので、JDialog#isActive()・isVisible()・isValid()等がfalseになったら終了する、という条件判定が出来る。

					// 終了判定
					if (!dialog.isActive()) {
						break;
					}

モーダルダイアログの場合、setVisible(true)の前にスレッドを開始する必要があるので、スレッド開始時点ではまだダイアログが表示されていない。
したがってJDialog#isActive()・isVisisble()・isValid()は最初はfalseなので、これを終了条件に使うとすぐに終了 してしまう!
自分でフラグを用意するのが確実なようだ。

		final CancelableProgressDialog dialog = new CancelableProgressDialog(null, true); //モーダルダイアログ
〜
					// 終了判定
					if (dialog.isEnd()) {
						break;
					}
@SuppressWarnings("serial")
public class CancelableProgressDialog extends SampleProgressDialog {

	protected boolean end = false;

	public CancelableProgressDialog(Frame owner, boolean modal) {
		super(owner, modal);
	}

	@Override
	public void init(int min, int max) {
		super.init(min, max);

		// キャンセルボタン
		JButton button = new JButton("CANCEL");
		button.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				dispose();
			}
		});

		// 最下行にキャンセルボタンを追加
		Container c = getContentPane();
		JPanel panel = new JPanel(new FlowLayout());
		panel.add(button);
		c.add(panel, BorderLayout.PAGE_END);
	}

	@Override
	public void dispose() {
		this.end = true;
		super.dispose();
	}

	public boolean isEnd() {
		return end;
	}
}

ProgressMonitor

上記では自前でダイアログを作ってみたが、実はプログレスバーを表示するダイアログは標準で用意されている。[2009-11-08]
それがProgressMonitor
(→自作の汎用進捗ダイアログ:ProgressDialog

import javax.swing.ProgressMonitor;
		Frame owner = null;

		int min = 0;
		int max = 500;

		ProgressMonitor pm = new ProgressMonitor(owner, "メッセージ", "ノート", min, max);

		for (int i = min; i < max; i++) {
			// 終了判定
			if (pm.isCanceled()) {
				pm.close();
				break;
			}

			pm.setNote("現在:" + i);

			//何かの処理

			pm.setProgress(i + 1); //プログレスバーに現在値をセット
		}

デフォルトでは、最初の0.5秒間はダイアログは表示されない。(setMillisToDecideToPopup()で変更できる。デフォルト:500ミリ秒)
0.5秒以上経過すると、「プログレスバーの現在の進捗率」と「それまでにかかった時間」から全部で何秒かかりそうか計算し、それが2秒以上ならダイアログが表示される。(setMillisToPopup()で変更できる。デフォルト:2000ミリ秒)
これらの判定はsetProgress()内で行われるので、ダイアログが表示されていなくてもsetProgress()を随時呼び出す必要がある。
(ダイアログの実体はJOptionPaneだが、それを生成して表示するのがProgressMonitor#setProgress()内。
 一度ダイアログが生成されると、これらの判定はもう行わない)

いわばダイアログの方が別スレッドで動く為、処理本体をスレッドにする必要は無いようだ。
(ボタンを押したときにこれらの処理を行ったとすると、処理が終わるまでボタン押下状態は終わらないけど)

ProgressMonitorのコンストラクターの「メッセージ」はObjectクラス、「ノート」はString。
メッセージの方はString以外にJLabel等のコンポーネントが使える。
ノートの方は、setNote()で後から文言を変更することが出来る。進捗具合に関する値を表示するのに使うのだろう。
どちらも、nullを指定すれば何も表示されない。
(ノートを表示するかどうかは、0.5秒経過後のダイアログ表示時点でnull以外が設定されているかどうかによる)

処理が全て終わる(setProgress()で最大値が指定される)と、自動的にダイアログは消える。
キャンセルボタンが押された場合はそういった動作は特に行われないので、自分でisCanceled()で判定し、close()でダイアログを閉じると良いようだ。

ダイアログのタイトルの文言にはUIManager.getString("ProgressMonitor.progressText")で取得した値が使われているので、UIManager.put()でセットしておけば変えられる。
しかし見て分かる通りstaticメソッドなので、全ProgressMonitor共通の設定となる。
まぁ、並行でProgressMonitorダイアログを起動しないのであれば、個々に設定しても問題ないかもしれないが…。


ProgressMonitorInputStream

ProgressMonitorInputStreamは、読み込み状況の進捗ダイアログを表示するInputStream[2009-11-08]

import java.io.File;
import java.io.FileInputStream;
import java.io.InterruptedIOException;

import javax.swing.ProgressMonitorInputStream;
	Frame owner = null;

	File f = new File("〜");
	FileInputStream fis = new FileInputStream(f);
	ProgressMonitorInputStream pis = new ProgressMonitorInputStream(owner, f.getAbsolutePath(), fis);

	//ダイアログの表示に関わる初期設定
//	ProgressMonitor monitor = pis.getProgressMonitor();
//	monitor.setMillisToDecideToPopup(100);
//	monitor.setMillisToPopup(1000);

	byte[] buf = new byte[256];
	try {
		for (;;) {
			int len = pis.read(buf);
			if (len <= 0) {
				break;
			}

			//何らかの処理
		}
	} catch (InterruptedIOException e) {
		// ProgressMonitorでキャンセルボタンが押されると、read()でInterruptedIOExceptionが発生する
		if ("progress".equals(e.getMessage())) {
			System.out.println("キャンセルされたっぺ");
		} else {
			e.printStackTrace();
		}
	} finally {
		pis.close();
	}

進捗ダイアログにはProgressMonitorが使われる。
したがって、デフォルトでは「0.5秒以上経過した時点で、全体が2秒以上かかる」と判断されないとダイアログは表示されない。

ProgressMonitorInputStreamのコンストラクターで、ProgressMonitorの「メッセージ」と、入力元となるInputStreamを指定する。
ProgressMonitorの初期化の為に進捗の最大値が必要となるが、それは“読み込む全体のバイト数”が使われる。これは入力元InputStreamavailable()メソッドで取得される。
したがって、FileInputStreamのように最大長が最初に分かるものは良いが、SocketInputStreamのように最大長が分からないものでは使用できない。

また、進捗状況が更新される(ProgressMonitor#setProgress()が呼ばれる)のは、ProgressMonitorInputStreamのread()系のメソッドが呼ばれたとき。
したがって、ProgressMonitorInputStreamをBufferedInputStreamBufferedReaderでラップすると、read()の呼ばれる頻度が減る為、進捗具合が細かく把握されなくなる。
(ダイアログがsetMillisToDecideToPopup()で指定した時間を過ぎても表示されなかったり、 表示の更新頻度が減ったり進捗度合いが一気に進んだりするだろう)

ProgressMonitorInputStream#close()を呼ぶと、ProgressMonitorクローズされる。
したがって、(インスタンス自体は残っているが、)情報はクリアされる。例えばisCanceled()は常にfalseを返す。


Swing目次へ戻る / Java目次へ戻る
メールの送信先:ひしだま