前回までの記事
- http://d.hatena.ne.jp/gsf_zero1/20061104/p1
- http://d.hatena.ne.jp/gsf_zero1/20061106/p1
- http://d.hatena.ne.jp/gsf_zero1/20061109/p1
- http://d.hatena.ne.jp/gsf_zero1/20061112/p1
SwingWorkerを利用して、処理をキャンセル可能にします。
基本的な手順は前回と同じく、以下のようにします。
- 重い処理(時間のかかる処理)はSwingWorkerを利用してバックグラウンドで行う。
- 進捗状況、ステータスの更新は、SwingWorkerのprocess,doneメソッドなどを用いてEvent Dispatch Threadで処理。
- ユーザがキャンセルを行った場合は、SwingWorker.cancel()をコール。(SwingWorkerはjava.util.concurrentのFutureを実装している)
- その他、バックグラウンド処理開始時、終了時などに行いたい処理はSwingWorkerにPropertyChangeListenerを追加する。
以下サンプルです。
このアプリケーションは、開始ボタンが押下されるとJava EE 5のチュートリアルのPDFを
ダウンロードします。(実際には、データの読み込みのみをおこなってるだけで、ファイルの保存はしてません)
このPDFは大体9MBくらいありますので、それなりに早いネットワークでも時間がかかると思います。
画面は、以下のような感じ。
このアプリケーションは、以下の名前で各コンポーネントを定義しています。
- ダウンロード先URL:downloadUrlLabel
- 開始ボタン:startButton
- キャンセルボタン:cancelButton
- プログレスバー:progressBar
- ステータスバーラベル:statusLabel
まずは、フォーム本体の部分。
以下のようになります。
package gsf.samples.swing.swingworker; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import javax.swing.JOptionPane; import javax.swing.Timer; import org.jdesktop.swingworker.SwingWorker; /** * アプリケーションのメインフォームクラスです.<br/> * このフォームは、開始ボタンが押下されるとJava EE 5チュートリアルPDFの<br/> * ダウンロードを行います。<br/> * * @author gsf_zero1 */ public class MainForm extends javax.swing.JFrame { /** バックグラウンド処理を実行するワーカーオブジェクト */ private SwingWorker<String, String> _worker; /** * コンストラクタ.<br/> * */ public MainForm() { initComponents(); } private void initComponents() { // // 省略.... // } /** * フォームが最初にオープンされた際にコールバックされるリスナーメソッドです.<br/> * * @param evt イベントオブジェクト */ private void formWindowOpened(java.awt.event.WindowEvent evt) { startButton.setEnabled(true); cancelButton.setEnabled(false); } /** * キャンセルボタンが押下された際にコールバックされるリスナーメソッドです。<br/> * * @param evt イベントオブジェクト */ private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) { _worker.cancel(true); startButton.setEnabled(true); cancelButton.setEnabled(false); } /** * 開始ボタンが押下された際にコールバックされるリスナーメソッドです.<br/> * 該当ファイルのダウンロードを行います.<br/> * * @param evt イベントオブジェクト */ private void startButtonActionPerformed(java.awt.event.ActionEvent evt) { URL url = null; try { url = new URL(downloadUrlLabel.getText()); } catch (MalformedURLException ex) { JOptionPane.showMessageDialog(null, ex.getMessage(), "エラーが発生しました.", JOptionPane.ERROR_MESSAGE); return; } startButton.setEnabled(false); cancelButton.setEnabled(true); _worker = new DownLoadWorker(url); _worker.execute(); } protected javax.swing.JButton cancelButton; protected javax.swing.JLabel downloadUrlLabel; protected javax.swing.JLabel jLabel1; protected javax.swing.JLabel jLabel2; protected javax.swing.JPanel jPanel1; protected javax.swing.JProgressBar progressBar; protected javax.swing.JButton startButton; protected javax.swing.JPanel statusBarPanel; protected javax.swing.JLabel statusLabel; // // カスタムSwingWorkerの定義(後述) // // // PropertyChangeListenerの定義(後述) // }
次に、カスタムSwingWorkerクラスの定義と追加するPropertyChangeListenerです。
/** * 指定されたURLデータのダウンロードを行うSwingWorkerクラスです.<br/> * * @author gsf_zero1 * @version 1.0 */ class DownLoadWorker extends SwingWorker<String, String>{ /** URL */ private URL _url; /** データダウンロードに使用するコネクション */ private HttpURLConnection _conn; /** * コンストラクタ.<br/> * * @param downLoadUrl ダウンロード先 */ public DownLoadWorker(URL downLoadUrl){ _url = downLoadUrl; addPropertyChangeListener(new WorkerProgressPropertyChangeListener()); addPropertyChangeListener(new WorkerStateValuePropertyChangeListener()); } /** * ダウンロード処理を行います.<br/> * * @return 処理結果 * @throws 処理中にエラーが発生した場合 */ protected String doInBackground() throws Exception { if(isCancelled()){ return "処理はキャンセルされました。"; } progressBar.setValue(0); publish("コネクションを確立中・・・・"); _conn = (HttpURLConnection) _url.openConnection(); _conn.setRequestMethod("GET"); _conn.connect(); // // ダウンロードファイルサイズを取得. // double totalBytes = _conn.getContentLength(); publish("コネクションを確立しました。ダウンロード中です・・・・・ ファイルサイズ 約:" + (int) (totalBytes / 1024 / 1024) + "MB"); BufferedInputStream in = null; try{ in = new BufferedInputStream(_conn.getInputStream()); double currentReadBytes = 0.0; for(byte[] buf = new byte[1024]; in.read(buf) != -1; buf = new byte[1024]){ if(isCancelled()){ break; } currentReadBytes += buf.length; setProgress( (int) ( (currentReadBytes / totalBytes) * 100)); } }finally{ if(in != null){ in.close(); } } return "ダウンロードが完了しました。"; } /** * doInBackgroundメソッドにてpublishされた際にコールバックされるメソッドです.<br/> * 処理の経過を表示します.<br/> * * @param chunks publish()で指定された引数データ */ protected void process(List<String> chunks) { String message = chunks.get(0); statusLabel.setText(message); } /** * 処理が完了した際にコールされるメソッドです.<br/> * 終了メッセージの設定を行います.<br/> * */ protected void done() { String message = null; try { message = get(); } catch (ExecutionException ex) { message = "処理実行中に、エラーが発生しました。"; } catch (InterruptedException ex) { message = "処理はキャンセルされました。"; } catch (CancellationException ex){ message = "処理はキャンセルされました。"; }finally{ statusLabel.setText(message); if(_conn != null){ _conn.disconnect(); } } } } /** * ワーカオブジェクトにて、progressバウンドプロパティの値が変更された際に<br/> * コールバックされるリスナーです.<br/> * 指定された値をフォームのJProgressBarに設定する役割を担当します.<br/> * * @author gsf_zero1 */ class WorkerProgressPropertyChangeListener implements PropertyChangeListener{ public void propertyChange(PropertyChangeEvent evt) { if("progress".equalsIgnoreCase(evt.getPropertyName())){ progressBar.setValue( (Integer) evt.getNewValue() ); } } } /** * ワーカーオブジェクトにて、stateバウンドプロパティ変更の際にコールバックされる<br/> * リスナーです.<br/> * 指定された値をフォームのステータスバーに表示する役割を担当します。<br/> * * @author gsf_zero1 */ class WorkerStateValuePropertyChangeListener implements PropertyChangeListener{ public void propertyChange(PropertyChangeEvent evt) { if("state".equalsIgnoreCase(evt.getPropertyName())){ SwingWorker worker = (SwingWorker) evt.getSource(); SwingWorker.StateValue state = (SwingWorker.StateValue) evt.getNewValue(); if(SwingWorker.StateValue.STARTED == state){ statusLabel.setText("処理を実行中です・・・しばらくお待ちください。"); }else if(SwingWorker.StateValue.DONE == state){ if(!worker.isCancelled()){ statusLabel.setText("ダウンロードが完了しました。"); } startButton.setEnabled(true); cancelButton.setEnabled(false); } } } }
開始ボタン押下時のイベントリスナーの部分は、事前処理を行い、Workerを作成して
すぐに処理を戻すようにしています。このようにすることで、GUIがブロックされることを
防ぎます。上記の場合のように、時間がかかる処理も含めて
全ての処理を単一のリスナ内で行うとその処理時間分GUIがブロックされるからです。
(リスナーメソッドはEvent Dispatch Threadから呼ばれます)
Workerに追加している2つのPropertyChangeListenerは、それぞれ以下のことを担当します。
- 処理開始時と終了時の前処理と後処理。
- 進捗状況の更新。
SwingWorkerは内部にprogressとstateという2つのバウンドプロパティを持っています。
それを利用して処理を行います。
追記:
ソースとモジュールをアップしようとしたけど、はてなって画像ファイルしかアップできないんですね・・・。
更に追記:
ファイルダウンロードの部分ですが、前はtimerを使用して適当にプログレスバーを進めていた部分を
一応ちゃんとファイルサイズみて進ませるようにしました。
================================
過去の記事については、以下のページからご参照下さい。
- いろいろ備忘録日記まとめ