EventQueue#invokeLaterなどを使えということなのですが、実際の処理をいちいちinvokeLaterすると処理の記述が分断して大変みにくくなります。記述の美しさよりも処理の安全さ、とはいえ、これはあんまり。
ということで、Java SE 6からはSwingWorkerというクラスが用意されていて、便利に使えます。
使い方を図解すると、こう
関係ないけど、今回のサンプルはムダにNimbusです。
SwingWorkerは、Genericsパラメータをふたつ指定します。一つ目が処理結果の型で、二つ目が途中経過の型です。
new SwingWorker<Integer, int[]>()
SwingWorkerで実装するべきなのは、doInBackgroundメソッドです。ここでは、別スレッドで動かしたい処理を記述します。戻り値の型は、Genericsで一つ目に指定した型になります。
@Override Integer doInBackground() throws Exception {
processメソッドは、途中経過を表示するメソッドです。引数にはGenericsで二つ目に指定した型のListが渡されます。
@Override void process(List<int[]> chunks) {
publishメソッドで途中経過を渡すと、適当なタイミングでprocessメソッドが呼び出されます。
publish(new int[]{i, sum});
processメソッドが呼び出される頻度よりもpublishメソッドの方が多いとき、複数の出力がたまっている可能性もあります。そこで、出力はまとめて行うとなんとなく得した気分になります。
StringBuilder sb = new StringBuilder(); for(int[] values : chunks){ sb.append(String.format( "%dを足して%d%n", values[0], values[1])); } taOutput.append(sb.toString());
出力は文字列で行うことが多いですが、最終的な出力文字列の生成はprocessメソッドで行ったほうがいいと思います。
処理が終わったら、doneメソッドが呼び出されます。
@Override void done()
ここで、getメソッドを使うと処理結果が受け取れます。
int result = get();
このgetをdoneメソッド以外で呼び出した場合、doInBackgroundの処理が終わるまでブロックします。
処理の進捗はsetProgressメソッドで指定します。
setProgress(i * 10);
進捗が変わったときは、PropertyChangeEventが発生するので、PropertyChangeListenerを登録しておく必要があります。このときのイベントは"progress"です。
sw.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if("progress".equals(evt.getPropertyName())){ progressBar.setValue((Integer)evt.getNewValue()); } } });
executeメソッドを呼び出すと、処理が開始します。
sw.execute();
ということで、ソース
import java.awt.BorderLayout; import java.awt.event.*; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.List; import java.util.concurrent.ExecutionException; import javax.swing.*; import javax.swing.SwingWorker; public class SwingWorkerSample { public static void main(String[] args){ //なんとなくNimbusを使う try { UIManager.setLookAndFeel( "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel"); } catch (Exception ex) { } //ウィンドウ JFrame f = new JFrame("SwingWorkerサンプル"); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //テキストエリア final JTextArea taOutput = new JTextArea(15, 30); JScrollPane sp = new JScrollPane(taOutput); f.add(sp); //プログレスバー final JProgressBar progressBar = new JProgressBar(); f.add(BorderLayout.SOUTH, progressBar); //ボタン JButton b = new JButton("開始"); f.add(BorderLayout.NORTH, b); //実行結果Integer, 処理経過データint[]のSwingWorker final SwingWorker<Integer, int[]> sw = new SwingWorker<Integer, int[]>(){ /** バックグラウンド処理 */ @Override protected Integer doInBackground() throws Exception { int sum = 0; for(int i = 1; i <= 10; ++i){ sum += i; publish(new int[]{i, sum});//結果出力 setProgress(i * 10);//進捗 Thread.sleep(1000); } return sum; } /** 途中経過の表示 */ @Override protected void process(List<int[]> chunks) { StringBuilder sb = new StringBuilder(); for(int[] values : chunks){ sb.append(String.format( "%dを足して%d%n", values[0], values[1])); } taOutput.append(sb.toString()); } /** 処理終了 */ @Override protected void done() { try { int result = get(); taOutput.append("終了。" + result + "でした\n"); } catch (InterruptedException ex) { } catch (ExecutionException ex) { } } }; //プログレスバーの処理 sw.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if("progress".equals(evt.getPropertyName())){ progressBar.setValue((Integer)evt.getNewValue()); } } }); //ボタンが押されたときの処理 b.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { sw.execute();//SwingWorkderの実行 } }); //ウィンドウ表示 f.pack(); f.setVisible(true); } }