Master -
Yucchi
Since - 2012/05/05
SwingWorker を試す
JDK1.6 から標準採用となった SwingWorker を試してみます。
SwingWorker クラスはどのような場合に使用されるか調べてみました。
GUI とやりとりする時間のかかる処理を、専用のスレッドで実行するための抽象クラスです。
Swing を使ってマルチスレッドアプリケーションを記述する場合、下記のような二つの制約があります。
1.イベントディスパッチスレッドでは時間のかかる処理を実行しないようにする。
2.Swing コンポーネントには、イベントディスパッチスレッド上でのみアクセスする。
この二つの制約をクリアしようと思えば時間のかかるスレッドとGUI 関連の全てを実行するスレッドが必要となります。
参考までに Sun のサイトにあるチュートリアルでは、30ミリ秒以上の処理はイベントディスパッチスレッド上で実行しない。SwingWorker を使ってワーカースレッドで実行しましょうとあります。
この最低二つのスレッド間で通信を行うには非常に複雑で困難な実装が必要になるでしょう。
SwingWorker クラスはこのような問題を解決してくれます。
SwingWorker クラスは、バックグランドスレッドで時間のかかる処理を実行する必要があり、その実行中または実行完了後に UI を更新する必要がある場合を想定して設計されているそうです。
この SwingWorker クラスを使ってアプリケーションを組むには最低限 doInBackground() メソッドをオーバーライドすればいいだけです。
doInBackground() メソッドで実行される処理はワーカースレッドとなります。
そして、doInBackground() メソッドが終了すると、イベントディスパッチスレッド上で done() メソッドが呼ばれます。
done() メソッドをオーバーライドして UI を更新、及び get() メソッドで処理結果を取得します。
get() メソッドで返される結果の型は型パラメータで指定しなければいけません。
あと、処理の途中結果を随時取り出すために publish() メソッドと process() メソッドがあります。
アプリケーションの進捗状態を表すプログレスバーのためのメソッドもあります。
setProgress(int progress) ではバウンドプロパティを0~100の値に設定できます。
getProgress() でバウンドプロパティを返します。
ワーカースレッド上で doInBackground() メソッドが呼び出される前に、SwingWorker はすべての PropertyChangeListeners に、state プロパティの値が StateValue.STARTED に変更されたことを通知します。
doInBackground() メソッドが完了すると、done() メソッドが実行されます。
そして SwingWorker はすべてのPropertyChangeListeners に、state プロパティの値が StateValue.DONE に変更されたことを通知します。
SwingWorker は一回だけ実行されるように設計されてます。
これらのことを考慮して簡単なアプリケーションを組んでみましょう。
ラベルにあるサイトの画像をダウンロードして縮小表示させる簡単なアプリケーションを作ってみます。
LOAD ボタンを押して画像のダウンロード、画像縮小処理を開始します。
それに伴い、LOAD ボタンを使用不可に更新し、CANCEL ボタンを有効にします。
それらの処理が完了したらラベル上に画像を貼り付け、LOAD ボタンを有効にし、CANCEL ボタンを無効にします。
これらの処理の進捗状況をプログレスバーで表示します。
はじめは不確定モードで、そして確定モードへとします。
アプリケーションの中断を CANCEL ボタンで行えるようにします。
これらを SwingWorker クラスを使って作ってみます。
開発環境は
OS Solaris NEVADA
JDK6u3
NetBeans IDE 6.0
です。
[ ファイル ] → [ 新規プロジェクト ] で新規プロジェクトを作ります。
[ Java ] → [ Java アプリケーション ] を選択し [ 次へ > ] ボタンを押します。
下記のように設定します。
SwingWorkerTest プロジェクトを右クリックし、[ 新規 ] → [ JFrame フォーム... ] を選択します。
そして下記のように設定します。
自動生成された JFrame に GUI ビルダーを使い、JPanel を二つ、JLabel、JProgressBar、JButton を二つ作ります
JLabel を右クリックし、プロパティを選択します。
そしてプロパティの設定ウィンドウが表示されたら icon の右端の [...] ボタンを押します。
カスタムエディタが開きます。
そしてJLabel にセットする画像を指定します。
外部イメージラジオボタンを押し、右端の [...] ボタンを押します。
ファイルチューザが出ますので JLabel に貼り付けるイメージファイルを選択します。
[ プロジェクトにインポート... ] ボタンを押してプロジェクトにインポートします。
ウィザードに従ってファイルを選択します。
ターゲットフォルダを選択します。
無ければ新規作成します。
Images というフォルダを新規作成しました。
これまでの作業でデザインはこのようになります。
コンポーネントの配置状態もインスペクタ画面で確認できます。
コンポーネントの変数名も変更されているのが確認できますね。
次にプログレスバーにリスナーを設定します。
JProgressBar を右クリックしてプロパティを選択し、プロパティ設定画面を表示させます。
生成後のコードの右端の [ ... ] ボタンを押します。
生成後のコードのカスタムエディタがひらきます。
下記のように生成後のコードを記入します。
次に、ProgressListener クラスを作ります。
これは入れ子のクラスにしました。
PropertyChangeListener を実装します。
下記コードのように propertyChange メソッドを実装します。
これによりプログレスバーの進捗表示が確定モードとなり可能になります。
SwingWorker クラスは抽象クラスですから拡張クラスを作らなければいけません。
下記のように作ります。
(普通この程度の小さなアプリケーションでは入れ子のクラスにします)
ちゃんと作られました。
とりあえず作っただけです。
ImageLoader クラスに戻り、LOAD ボタンのイベントを記述します。
GUI ビルダーで LOAD ボタンをダブルクリックすると親切にコードエディタに飛びます。
そして //TODO add your handling code here: とこれまた親切な表示がされます。
そこで下記コードを記入します。
JLabel の icon を null にし、LOAD ボタンの表示を NOW LOADING... に変更し使用不可にします。
そして、CANCEL ボタンを有効にします。
さらに、downImage() メソッドを呼び出します。
エラーメッセージと対応策が表示されます。
対応策どおりに downImage() を自動作成させます。
自動作成された downImage() メソッドを下記のように記述します。
SwingWorker クラスのサブクラスの worker オブジェクトを生成。
プログレスバーの初期値を0に設定。
worker オブジェクトに addPropertyChangeListener() メソッドで progressListener を渡す。
プログレスバーを不確定モードに設定する。
execute() メソッドでワーカースレッドの実行スケジュールを立てる。
次に CANCEL ボタンのイベントを記述します。
worker オブジェクトが null でなく、 ワーカースレッドが完了してない場合、
cancel(true) メソッドでワーカースレッドの実行を取り消します。
そしてプログレスバーを初期値に戻します。
LOAD ボタンの表示を [LOAD] に戻し有効にします。
CANCEL ボタンを無効にします。
ImageWorker クラスに下記のようなフィールドを追加します。
コンストラクタを作ります。
コードエディタを右クリックし、[ コードを挿入... ] を選択します。
生成画面が出ますのでコンストラクタを選択します。
コンストラクタの生成ウィンドウがでますので必要に応じて初期化するフィールドにチェックをいれます。
引数なしのコンストラクタを作ります。
ちゃんと作られました。
便利ですね(^^)
次に下図のようにフィールドを三つ選んで作りました。
これもちゃんと作られました。
さて、長い時間のかかる処理を doInBackground() メソッドに記述します。(オーバーライドします)
ここに素直に長い時間のかかる処理を記述すればいいのでしょうが、makeImage() メソッドにさせることにしました。
特に理由はありません。
doInBacground() という名前のメソッドではどういった処理をしているのか想像しにくいからです。
下図のようにエラーメッセージと対応策が表示されます。
対応策どおりに makeImage() メソッドを自動生成させます。
また、「この文に制御が移ることはありません。」というエラーメッセージがでました。
コメントアウトにして対応します。
長い時間のかかる処理のワーカースレッドを下記のように記述します。
JLabel のテキストを null にします。
JLabel のフォアグランドとバックグランドそしてフォントサイズおよびカラーを設定します。
そして、JLabel のテキスト表示を「ダウンロードしています」に更新します。
次に URL を取得し、画像をダウンロードします。
ImageReader reader に IIOReadProgressListener() を addIIOReadProgressListener() で登録します。
imageComplete(ImageReader source) メソッドで setProgress(100); とプログレスバーのバウンドプロパティを設定します。
imageProgress(ImageReader source, float percentageDone) メソッドで setProgress((int)percentageDone); とし、
進捗状況を取得します。
readAborted(ImageReader source) メソッドで中断された時の処理を setProgress(0); とします。
取得した画像を必要に応じて縮小します。
done() メソッドをオーバーライドします。
キャンセルされた場合の処理をはじめに書いてあります。
標準出力に CANCEL と表示させます。
そしてここがちょっと問題のある場所なんですが・・・
ImageReader reader が null でなくなるまで待ってます。
このような処理は良くないのですが私の実力不足でこのようなコーディングになりました(T-T)
これをしないと不確定モード時にキャンセルするとプログレスバーが初期値に戻らない場合があります。
そして、readAborted(ImageReader source) メソッドを呼び出します。
JLabel に「キャンセルしました」と表示させます。
次は無事に画像の取得と縮小が完了した場合です。
get() メソッドで画像を取得します。
この get() メソッドの型は、public class ImageWorker extends SwingWorker<Image , Void> {
とあるように第一型パラメータです。
あとは画像を JLabel に貼り付けて表示させるだけです。
画像の取得に失敗した場合はエラーダイアログを表示させます。
以上でプログラムは無事できあがりました。
コンパイルし、実行します。
[ LOAD ] ボタンを押します。
JLabel に「ダウンロードしています」と表示され、[ LOAD ] ボタンが [ NOW LOADING... ] に変更され無効になります。
[ CANCEL ] ボタンが無効から有効になります。
そしてプログレスバーが不確定モードで起動します。
プログレスバーが確定モードになり、進捗状況が表示されます。
無事に処理が終了すれば JLabel に画像が表示されます。
そして [ LOAD ] ボタンが再び有効になります。
[ CANCEL ] ボタンが無効になります。
もし、アプリケーションが途中で [ CANCEL ] ボタンで中断されると JLabel に「キャンセルしました」と表示されます。
この場合も、[ LOAD ] ボタンが有効に戻り、[ CANCEL ] ボタンが無効になります。
実際に動作を確認できるように Java Web Start を配備しました。
JDK1.6 以降のバージョンなら動くと思います。
最初に書いたようにSwingWorker クラスは、JDK1.6 から標準採用されたからです。
最後にどうしてこのような便利なクラスが今まで標準で提供されなかったのか不思議です。
SwingWorker クラスに実装されている、
Runnable , Future<T> , RunnableFuture<T> インタフェースが必要だったからでしょうか。
まぁ、そこらへんの事情は置いといて(^^;
SwingWorker クラスは素晴らしいですね(^^)