プログラミングネタ探しをして、Webを徘徊していると、C#(Formベース)のコントロール一覧のページがあり、そこに
BackgroundWorkクラス
が出ていた。
なんだ、これ???
ということで調べてみると、ボタンやラベル等のよくあるUIのWindows Controlではなく、時間を要する処理を行う為の(別)スレッドユーティリティのようです。これはまだ試したことがないので、関連サイトを調べ、似たようなサンプルを参考に、(実に久しぶりに)"InitializeCOmponent"を使わない、MSCompAss用の「全コード」サンプルを作ってみました。
細かい解説は↓のサンプルのコードのコメントを参照されたいのですが、大まかなポイントは、
(1)参照DLLとして"C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.ComponentModel.EventBasedAsync\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.ComponentModel.EventBasedAsync.dll"を加え、コードには"using System.ComponentModel;"を追加します。
(2)System.ComponentModel.BackgroundWorkerのインスタンスを作成します。この際に、
DoWork
ProgressChanged
RunWorkerCompleted
という3つのイベントにメソッドを追加しておきます。
(3)RunWorkerAsyncメソッドで別スレッドの処理(DoWorkイベントで追加したメソッド)を開始します。進捗はProgressChangedイベントのProgressChangedEventArgs eで報告され、完了時に終了の仕方がのRunWorkerCompletedイベントのRunWorkerCompletedEventArgs eで確認できます。
以下のサンプルでは、お約束の0.5秒Sleepを10回繰り返すだけですが、実際の処理では完了までの時間、処理量を100として、0から定量化する必要があるようです。ネットワークからのダウンロード等、環境で処理時間が変わり、進捗が変更されるものもありますが、そこは(ユーザーサイドで)ロジックを組む必要がある、ということだと思います。
ご参考まで。
【BackgroundWorkSample.cs】
//-------------------------
// BackgroundWorker Sample
//-------------------------
using System;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel; //BackgroundWorkerを使う為
using System.Reflection; //Assemblyを使う為
namespace BWSample
{
///////////////////////////
//エントリーポイントクラス
///////////////////////////
class MainApp
{
[STAThread]
public static void Main()
{
Application.Run(new MainForm());
}
}
public partial class MainForm : Form
{
//メンバーコントロール
BackgroundWorker backgroundWorker; //但しMainFormの子になることはない
Label resultLabel;
ProgressBar progressBar;
Button startAsyncBtn;
Button cancelAsyncBtn;
public MainForm()
{
InitControls(); //MainFormとコントロールの初期化
backgroundWorker.WorkerReportsProgress = true; //進行状況の更新を報告可否
backgroundWorker.WorkerSupportsCancellation = true; //キャンセルのサポート可否
}
//BWFormとコントロールの初期化
private void InitControls()
{
//フォームの初期化
Assembly myOwn = Assembly.GetEntryAssembly();
this.Icon = Icon.ExtractAssociatedIcon(myOwn.Location); //プログラムアイコンをフォームにつける
this.Text = "BackgropundWorker Sample";
this.ClientSize = new Size(480, 80);
this.MinimumSize = new Size(480, 80);
this.BackColor = SystemColors.Window;
//ボタンの初期化
startAsyncBtn = new Button();
startAsyncBtn.Location = new Point(ClientSize.Width - startAsyncBtn.Width - 10, 10);
startAsyncBtn.Text = "開始";
startAsyncBtn.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
startAsyncBtn.Click += startAsyncBtn_Click;
this.Controls.Add(startAsyncBtn);
cancelAsyncBtn = new Button();
cancelAsyncBtn.Location = new Point(ClientSize.Width - cancelAsyncBtn.Width - 10, startAsyncBtn.Height + 20);
cancelAsyncBtn.Text = "中止";
cancelAsyncBtn.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
cancelAsyncBtn.Click += cancelAsyncBtn_Click;
this.Controls.Add(cancelAsyncBtn);
//ラベルの初期化
resultLabel = new Label();
resultLabel.Location = new Point(10, 10);
resultLabel.Anchor = (AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Left);
resultLabel.BorderStyle = BorderStyle.Fixed3D;
resultLabel.Width = this.ClientSize.Width - startAsyncBtn.Width - 30;
resultLabel.Text = "ここに進捗を表示する";
this.Controls.Add(resultLabel);
//プログレスバーの初期化
progressBar = new ProgressBar();
progressBar.Location = new Point(10, startAsyncBtn.Height + 20);
progressBar.Anchor = (AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Left);
progressBar.Width = this.ClientSize.Width - startAsyncBtn.Width - 30;
progressBar.Minimum = 0;
progressBar.Maximum = 100;
this.Controls.Add(progressBar);
//BackgroundWorkerの初期化
backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += backgroundWorker_DoWork; //別スレッド処理
backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged); //進捗変化があった場合の処理
backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted); //処理が完了した場合の処理
/* UIコントロールの様にControls.Addによって子供にする必要はない */
}
//startAsyncBtnを押した場合の処理
private void startAsyncBtn_Click(object sender, EventArgs e)
{
if(backgroundWorker.IsBusy != true)
{
//非同期処理開始
backgroundWorker.RunWorkerAsync();
}
}
//cancelAsyncBtnを押した場合の処理
private void cancelAsyncBtn_Click(object sender, EventArgs e)
{
if(backgroundWorker.WorkerSupportsCancellation == true)
{
//非同期処理キャンセル
backgroundWorker.CancelAsync();
}
}
//BackgroundWorkerによるバックグラウンド処理
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker; //"as"はobjectのキャスト演算子
for(int i = 1; i <= 10; i++)
{
if(worker.CancellationPending == true) //キャンセルされた場合
{
e.Cancel = true;
break;
}
else
{
//時間のかかる処理をここで行う
System.Threading.Thread.Sleep(500);
worker.ReportProgress(i * 10); //MinimumとMaximumに合致するように設定する
}
}
}
//バックグラウンド進捗処理
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
resultLabel.Text = (e.ProgressPercentage.ToString() + "%が処理されました。");
//プログレスバーの値を変更する
if(e.ProgressPercentage < this.progressBar.Minimum)
{
this.progressBar.Value = this.progressBar.Minimum;
}
else if(this.progressBar.Maximum < e.ProgressPercentage)
{
this.progressBar.Value = this.progressBar.Maximum;
}
else
{
this.progressBar.Value = e.ProgressPercentage;
}
}
//バックグラウンド終了処理
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if(e.Cancelled == true)
{
resultLabel.Text = "中止されました。";
}
else if(e.Error != null)
{
resultLabel.Text = "エラー:" + e.Error.Message;
}
else
{
resultLabel.Text = "完了しました。";
}
}
}
}