第九章: GUIアプリケーション

1時半就寝。7時半起床。昨日は体調が悪かったのでお休みしました。

Swingのイベントスレッド拘束

Swingのコンポーネントとモデルには、EDTからしかアクセスしてはいけない。
ただし、Swingのメソッドの一部はどのスレッドからも呼び出すことが可能。

  • SwingUtilities.isEventDispatchThread
  • SwingUtilities.invokeLater: EDTにRunnableをスケジュールする。
  • SwingUtilities.invokeAndWait: EDTにRunnableをスケジュールし、それが完了するまで現在のスレッドをブロックする。EDT以外のスレッドからのみ呼び出し可能。
  • repaint/revalidate: Swingコンポーネントの再描画をEDTにスケジュールする。validateなどを直接呼び出してはいけない。
  • イベントリスナの追加/削除: ただし、イベントの発火自体はEDTで行われる。

GUIイベント

例えばMVCパターンで設計したときは、シンプルな実装にすると

  1. マウスクリックの受信
  2. コントローラによる処理
  3. モデルの更新
  4. ビューの更新

といった一連のイベントは全てEDTで行われる。しかし、長時間のアクションを行う場合は、別スレッドで実行しないと応答性が劣化する。
このような場合には自前でExecutorを用いても良いが、SwingWorkerに各種の便利メソッドが用意されている。
自前で、ただ別スレッドで実行するような場合には以下のような実装になる。場面によってはFutureを使ってキャンセルの実装などが必要。

public class GUIExecutor extends AbstractExecutorService {

	private static final GUIExecutor INSTANCE = new GUIExecutor();

	public static GUIExecutor getInstance() {
		return INSTANCE;
	}
	
	public void execute(Runnable r) {
		if (SwingUtilities.isEventDispatchThread()) {
			r.run();
		} else {
			SwingUtilities.invokeLater(r);
		}
	}
	
	...

	private GUIExecutor() {
	}

}

final ExecutorService backgroundExecutor = Executors.newCachedThreadPool();

...

button.addActionListener(new ActionListener() {
	
	public void actionPerformed(ActionEvent e) {
		backgroundExecutor.execute(new Runnable() {
			public void run() {
				try {
					doBigComputation();
				} finally {
					GUIExecutor.getInstance().execute(new Runnable() {
						public void run() {
							button.setEnabled(true);
							button.setText("done");
						};
					});
				}
			}
		});
		
	}
	
});