Swingのスレッド処理にて、作成したアプリと同じ動作を行うC#アプリを作成してみました。
処理の書き方には、ほとんど違いがありませんね。作りやすいです。
てことで、以下サンプルです。
このアプリでは、以下の名称でコントロールが定義されています。
- ダウンロード先のラベル:_downloadUrlLabel
- 開始ボタン:_startButton
- キャンセルボタン:_cancelButton
- プログレスバー:_progressBar
- ステータス表示ラベル:_statusLabel
- BackgroundWorker:_worker
- Timer: _timer
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Net; using System.IO; namespace Gsf.Samples.WinForms { /// <summary> /// BackgroundWorkerコンポーネントの動作確認用クラスです。 /// 本アプリケーションは、開始ボタンが押下されるとJava EE 5のチュートリアルPDFの /// ダウンロードを行います。 /// </summary> public partial class BackgroundWorkerSampleForm : Form { /// <summary> /// コンストラクタ。 /// </summary> public BackgroundWorkerSampleForm() { InitializeComponent(); } /// <summary> /// フォームが最初にロードされた際にコールバックされるメソッドです。 /// </summary> /// <param name="sender">送信元コンポーネント</param> /// <param name="e">イベントオブジェクト</param> private void BackgroundWorkerSampleForm_Load(object sender, EventArgs e) { ChangeFormButtonState(true, false); } /// <summary> /// ボタンの状態を変更します。 /// それぞれ、trueに設定すると有効化。falseだと無効化します。 /// </summary> /// <param name="startButtonState">開始ボタンの状態</param> /// <param name="cancelButtonState">キャンセルボタンの状態</param> private void ChangeFormButtonState(bool startButtonState, bool cancelButtonState) { _startButton.Enabled = startButtonState; _cancelButton.Enabled = cancelButtonState; } /// <summary> /// 開始ボタンが押下された際にコールバックされるメソッドです。 /// </summary> /// <param name="sender">送信元オブジェクト</param> /// <param name="e">イベントオブジェクト</param> private void _startButton_Click(object sender, EventArgs e) { ChangeFormButtonState(false, true); _worker.RunWorkerAsync(_downloadUrlLabel.Text); } /// <summary> /// キャンセルボタンが押下された際にコールバックされるメソッドです。 /// </summary> /// <param name="sender">送信元オブジェクト</param> /// <param name="e">イベントオブジェクト</param> private void _cancelButton_Click(object sender, EventArgs e) { _worker.CancelAsync(); ChangeFormButtonState(true, false); } /// <summary> /// BackgroundWorkerコンポーネントにて、非同期で処理が行われる際にコールバックされるメソッドです。 /// 該当ファイルのダウンロードを行います。 /// </summary> /// <param name="sender">送信元オブジェクト</param> /// <param name="e">イベントオブジェクト</param> private void _worker_DoWork(object sender, DoWorkEventArgs e) { BackgroundWorker worker = sender as BackgroundWorker; if (worker.CancellationPending) { e.Cancel = true; } string url = e.Argument as string; worker.ReportProgress(10, "コネクションを確立しています・・・・"); HttpWebRequest request = HttpWebRequest.Create(url) as HttpWebRequest; worker.ReportProgress(20, "コネクションを確立しました。"); worker.ReportProgress(25, "ダウンロード中です・・・・・・"); using (Stream stream = request.GetResponse().GetResponseStream()) { BinaryReader br = new BinaryReader(stream); // // TimerをONにする // (UIスレッド以外からのコンポーネント更新になるのでInvokeを使用。) // (匿名delegateはDelegate型ではないので、MethodInvokerにキャスト.) // Invoke((MethodInvoker)delegate() { _timer.Enabled = true; }); byte[] buffer = new byte[1024]; while (br.Read(buffer, 0, buffer.Length) > 0) { if (worker.CancellationPending) { // // キャンセルのイベントを発行するには、以下のようにCancelプロパティをtrueにする // e.Cancel = true; return; } buffer = new byte[1024]; } } worker.ReportProgress(100, "ダウンロードが完了しました。"); } /// <summary> /// BackgroundWorkerコンポーネントにて、ReportProgressがコールされた際にコールバックされるメソッドです。 /// ProgressBarへの進捗状況更新および、ステータスバーへのメッセージ表示を行います。 /// </summary> /// <param name="sender">送信元オブジェクト</param> /// <param name="e">イベントオブジェクト</param> private void _worker_ProgressChanged(object sender, ProgressChangedEventArgs e) { _progressBar.Value = e.ProgressPercentage; if(e.UserState != null){ string message = e.UserState as string; _statusLabel.Text = message; } } /// <summary> /// BackgroundWorkerコンポーネントにて、非同期処理完了時にコールバックされるメソッドです。 /// 処理終了メッセージの表示および、連携して使用していたコンポーネントの後処理を行います。 /// </summary> /// <param name="sender">送信元オブジェクト</param> /// <param name="e">イベントオブジェクト</param> private void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if(e.Cancelled){ _statusLabel.Text = "処理がキャンセルされました。"; _progressBar.Value = 0; } else if (e.Error != null) { _statusLabel.Text = "処理中にエラーが発生しました。"; MessageBox.Show(e.Error.Message); _progressBar.Value = 0; } else { _statusLabel.Text = "ダウンロードが完了しました。"; } _timer.Enabled = false; ChangeFormButtonState(true, false); } /// <summary> /// TimerコンポーネントのTickイベントが発生した際にコールバックされるメソッドです。 /// ProgressBarへの周期的な値のアップデートを行います。 /// </summary> /// <param name="sender">送信元オブジェクト</param> /// <param name="e">イベントオブジェクト</param> private void _timer_Tick(object sender, EventArgs e) { if (_progressBar.Value == _progressBar.Maximum) { _progressBar.Value = 0; } _progressBar.Value++; } } }
ちなみに、上記のコードで別スレッドからInvokeを行っている部分(TimerをEnableにする部分)を
Invokeを使用せずに直接、_timer.Enabled = trueと書くとうまく動きません。
DoWorkを実行しているのは、UIスレッドではないからです。そのため、DoWork内部でコンポーネントの
更新をする場合は、Invokeのコールが必要となります。その他のイベント(ProgressChanged, RunWorkerCompleted)は
UIスレッドから実行されます。なので、この部分では直接コンポーネントを処理しても大丈夫です。
リソースとしては、以下のものを参考にしています。
キャンセル処理をする際に、自動で割り込みが発生するわけでなく自分でCancelプロパティを設定しなければ
いけないのかどうかがよくわかりません・・・・。
詳しい方いらっしゃいましたら、教えてください。m(_ _)m