DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

BackgroundWorkerクラスを使用して進行状況ダイアログを作成する

進行状況ダイアログを表示する方法は「進行状況ダイアログを表示する」でも説明していますが、ここでは、.NET Framework 2.0から追加されたBackgroundWorkerクラスを使って進行状況ダイアログを作成する方法を紹介します。

ここでは進行状況ダイアログのサンプルコードとその使い方だけを示し、BackgroundWorkerクラスについては説明しません。BackgroundWorkerクラスについて詳しくは、「時間のかかる処理の進行状況を表示する」をご覧ください。

では早速作成を開始します。まずはプロジェクトに新しいフォームを追加します(このフォームが進行状況ダイアログになります)。メニューの「プロジェクト」-「Windowsフォームの追加」で追加できます。ここではフォームの名前を「ProgressDialog」(ファイル名は「ProgressDialog.vb」または「ProgressDialog.cs」)とします。

次に、フォームデザイナを使って、フォームに必要なコントロールを配置します。ここでは、プログレスバー、ボタン、ラベル、そしてBackgroundWorkerを1つずつ配置してください。配置の仕方はお任せします。ただし、それぞれのコントロールの名前(Nameプロパティ)は、以下のように変更してください。

コントロール 名前(Nameプロパティ)
ProgressBar progressBar1
Label messageLabel
Button cancelAsyncButton
BackgroundWorker backgroundWorker1

ProgressDialogのコントロールの配置

フォームのデザインは以上で終了です。残りはコードです。ProgressDialogクラスのコードを、以下のように書きます。

VB.NET
コードを隠すコードを選択
Imports System.ComponentModel
Imports System.Windows.Forms

''' <summary>
''' バックグラウンド処理の進行状況を表示するフォーム
''' </summary>
Partial Public Class ProgressDialog
    Inherits Form
    ''' <summary>
    ''' ProgressDialogクラスのコンストラクタ
    ''' </summary>
    ''' <param name="caption">タイトルバーに表示するテキスト</param>
    ''' <param name="doWorkHandler">バックグラウンドで実行するメソッド</param>
    ''' <param name="argument">doWorkで取得できるパラメータ</param>
    Public Sub New(ByVal caption As String, _
                   ByVal doWork As DoWorkEventHandler, _
                   ByVal argument As Object)
        InitializeComponent()

        '初期設定
        Me.workerArgument = argument
        Me.Text = caption
        Me.FormBorderStyle = FormBorderStyle.FixedDialog
        Me.ShowInTaskbar = False
        Me.StartPosition = FormStartPosition.CenterParent
        Me.ControlBox = False
        Me.CancelButton = Me.cancelAsyncButton
        Me.messageLabel.Text = ""
        Me.progressBar1.Minimum = 0
        Me.progressBar1.Maximum = 100
        Me.progressBar1.Value = 0
        Me.cancelAsyncButton.Text = "キャンセル"
        Me.cancelAsyncButton.Enabled = True
        Me.backgroundWorker1.WorkerReportsProgress = True
        Me.backgroundWorker1.WorkerSupportsCancellation = True

        'イベント
        AddHandler Me.Shown, New EventHandler(AddressOf ProgressDialog_Shown)
        AddHandler Me.cancelAsyncButton.Click, _
            New EventHandler(AddressOf cancelAsyncButton_Click)
        AddHandler Me.backgroundWorker1.DoWork, doWork
        AddHandler Me.backgroundWorker1.ProgressChanged, _
            New ProgressChangedEventHandler( _
                AddressOf backgroundWorker1_ProgressChanged)
        AddHandler Me.backgroundWorker1.RunWorkerCompleted, _
            New RunWorkerCompletedEventHandler( _
                AddressOf backgroundWorker1_RunWorkerCompleted)
    End Sub

    ''' <summary>
    ''' ProgressDialogクラスのコンストラクタ
    ''' </summary>
    Public Sub New(ByVal formTitle As String, _
                   ByVal doWorkHandler As DoWorkEventHandler)
        Me.New(formTitle, doWorkHandler, Nothing)
    End Sub

    Private workerArgument As Object = Nothing

    Private _result As Object = Nothing
    ''' <summary>
    ''' DoWorkイベントハンドラで設定された結果
    ''' </summary>
    Public ReadOnly Property Result() As Object
        Get
            Return Me._result
        End Get
    End Property

    Private _error As Exception = Nothing
    ''' <summary>
    ''' バックグラウンド処理中に発生したエラー
    ''' </summary>
    Public ReadOnly Property [Error]() As Exception
        Get
            Return Me._error
        End Get
    End Property

    ''' <summary>
    ''' 進行状況ダイアログで使用しているBackgroundWorkerクラス
    ''' </summary>
    Public ReadOnly Property BackgroundWorker() As BackgroundWorker
        Get
            Return Me.backgroundWorker1
        End Get
    End Property

    'フォームが表示されたときにバックグラウンド処理を開始
    Private Sub ProgressDialog_Shown(ByVal sender As Object, _
                                     ByVal e As EventArgs)
        Me.backgroundWorker1.RunWorkerAsync(Me.workerArgument)
    End Sub

    'キャンセルボタンが押されたとき
    Private Sub cancelAsyncButton_Click(ByVal sender As Object, _
                                        ByVal e As EventArgs)
        cancelAsyncButton.Enabled = False
        backgroundWorker1.CancelAsync()
    End Sub

    'ReportProgressメソッドが呼び出されたとき
    Private Sub backgroundWorker1_ProgressChanged(ByVal sender As Object, _
                                        ByVal e As ProgressChangedEventArgs)
        'プログレスバーの値を変更する
        If e.ProgressPercentage < Me.progressBar1.Minimum Then
            Me.progressBar1.Value = Me.progressBar1.Minimum
        ElseIf Me.progressBar1.Maximum < e.ProgressPercentage Then
            Me.progressBar1.Value = Me.progressBar1.Maximum
        Else
            Me.progressBar1.Value = e.ProgressPercentage
        End If
        'メッセージのテキストを変更する
        Me.messageLabel.Text = DirectCast(e.UserState, String)
    End Sub

    'バックグラウンド処理が終了したとき
    Private Sub backgroundWorker1_RunWorkerCompleted(ByVal sender As Object, _
                                     ByVal e As RunWorkerCompletedEventArgs)
        If e.Error IsNot Nothing Then
            MessageBox.Show(Me, "エラー", _
                            "エラーが発生しました。" & vbCrLf & vbCrLf & _
                                e.Error.Message, MessageBoxButtons.OK, _
                            MessageBoxIcon.Error)
            Me._error = e.Error
            Me.DialogResult = DialogResult.Abort
        ElseIf e.Cancelled Then
            Me.DialogResult = DialogResult.Cancel
        Else
            Me._result = e.Result
            Me.DialogResult = DialogResult.OK
        End If

        Me.Close()
    End Sub
End Class
C#
コードを隠すコードを選択
using System;
using System.ComponentModel;
using System.Windows.Forms;

/// <summary>
/// バックグラウンド処理の進行状況を表示するフォーム
/// </summary>
public partial class ProgressDialog : Form
{
    /// <summary>
    /// ProgressDialogクラスのコンストラクタ
    /// </summary>
    /// <param name="caption">タイトルバーに表示するテキスト</param>
    /// <param name="doWorkHandler">バックグラウンドで実行するメソッド</param>
    /// <param name="argument">doWorkで取得できるパラメータ</param>
    public ProgressDialog(string caption,
        DoWorkEventHandler doWork,
        object argument)
    {
        InitializeComponent();

        //初期設定
        this.workerArgument = argument;
        this.Text = caption;
        this.FormBorderStyle = FormBorderStyle.FixedDialog;
        this.ShowInTaskbar = false;
        this.StartPosition = FormStartPosition.CenterParent;
        this.ControlBox = false;
        this.CancelButton = this.cancelAsyncButton;
        this.messageLabel.Text = "";
        this.progressBar1.Minimum = 0;
        this.progressBar1.Maximum = 100;
        this.progressBar1.Value = 0;
        this.cancelAsyncButton.Text = "キャンセル";
        this.cancelAsyncButton.Enabled = true;
        this.backgroundWorker1.WorkerReportsProgress = true;
        this.backgroundWorker1.WorkerSupportsCancellation = true;

        //イベント
        this.Shown += new EventHandler(ProgressDialog_Shown);
        this.cancelAsyncButton.Click += new EventHandler(cancelAsyncButton_Click);
        this.backgroundWorker1.DoWork += doWork;
        this.backgroundWorker1.ProgressChanged +=
            new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
        this.backgroundWorker1.RunWorkerCompleted +=
            new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
    }

    /// <summary>
    /// ProgressDialogクラスのコンストラクタ
    /// </summary>
    public ProgressDialog(string formTitle,
        DoWorkEventHandler doWorkHandler)
        : this(formTitle, doWorkHandler, null)
    {
    }

    private object workerArgument = null;

    private object _result = null;
    /// <summary>
    /// DoWorkイベントハンドラで設定された結果
    /// </summary>
    public object Result
    {
        get
        {
            return this._result;
        }
    }

    private Exception _error = null;
    /// <summary>
    /// バックグラウンド処理中に発生したエラー
    /// </summary>
    public Exception Error
    {
        get
        {
            return this._error;
        }
    }

    /// <summary>
    /// 進行状況ダイアログで使用しているBackgroundWorkerクラス
    /// </summary>
    public BackgroundWorker BackgroundWorker
    {
        get
        {
            return this.backgroundWorker1;
        }
    }

    //フォームが表示されたときにバックグラウンド処理を開始
    private void ProgressDialog_Shown(object sender, EventArgs e)
    {
        this.backgroundWorker1.RunWorkerAsync(this.workerArgument);
    }

    //キャンセルボタンが押されたとき
    private void cancelAsyncButton_Click(object sender, EventArgs e)
    {
        cancelAsyncButton.Enabled = false;
        backgroundWorker1.CancelAsync();
    }

    //ReportProgressメソッドが呼び出されたとき
    private void backgroundWorker1_ProgressChanged(
        object sender, ProgressChangedEventArgs e)
    {
        //プログレスバーの値を変更する
        if (e.ProgressPercentage < this.progressBar1.Minimum)
        {
            this.progressBar1.Value = this.progressBar1.Minimum;
        }
        else if (this.progressBar1.Maximum < e.ProgressPercentage)
        {
            this.progressBar1.Value = this.progressBar1.Maximum;
        }
        else
        {
            this.progressBar1.Value = e.ProgressPercentage;
        }
        //メッセージのテキストを変更する
        this.messageLabel.Text = (string)e.UserState;
    }

    //バックグラウンド処理が終了したとき
    private void backgroundWorker1_RunWorkerCompleted(
        object sender, RunWorkerCompletedEventArgs e)
    {
        if (e.Error != null)
        {
            MessageBox.Show(this,
                "エラー",
                "エラーが発生しました。\n\n" + e.Error.Message,
                MessageBoxButtons.OK,
                MessageBoxIcon.Error);
            this._error = e.Error;
            this.DialogResult = DialogResult.Abort;
        }
        else if (e.Cancelled)
        {
            this.DialogResult = DialogResult.Cancel;
        }
        else
        {
            this._result = e.Result;
            this.DialogResult = DialogResult.OK;
        }

        this.Close();
    }
}

これで出来上がりです。

このProgressDialogクラスを実際に使用したコードを以下に示します。なおこのコードは進行状況ダイアログを呼び出すフォームのクラスに書かれており、そのフォームには Button1 というボタンコントロールが配置されているものとします。Button1 をクリックすると進行状況ダイアログが表示され、100ミリ秒ごとにプログレスバーが伸びていき、100%になると自動的に閉じられます。

進行状況ダイアログ

VB.NET
コードを隠すコードを選択
'Button1のClickイベントハンドラ
Private Sub Button1_Click(ByVal sender As Object, _
        ByVal e As System.EventArgs) Handles Button1.Click
    'ProgressDialogオブジェクトを作成する
    Dim pd As New ProgressDialog("進行状況ダイアログのテスト", _
        New DoWorkEventHandler(AddressOf ProgressDialog_DoWork), _
        100)
    '進行状況ダイアログを表示する
    Dim result As DialogResult = pd.ShowDialog(Me)
    '結果を取得する
    If result = DialogResult.Cancel Then
        MessageBox.Show("キャンセルされました")
    ElseIf result = DialogResult.Abort Then
        'エラー情報を取得する
        Dim ex As Exception = pd.Error
        MessageBox.Show("エラー: " + ex.Message)
    ElseIf result = DialogResult.OK Then
        '結果を取得する
        Dim stopTime As Integer = CInt(pd.Result)
        MessageBox.Show("成功しました: " & stopTime.ToString())
    End If

    '後始末
    pd.Dispose()
End Sub

'DoWorkイベントハンドラ
Private Sub ProgressDialog_DoWork(ByVal sender As Object, _
                                  ByVal e As DoWorkEventArgs)
    Dim bw As BackgroundWorker = DirectCast(sender, BackgroundWorker)

    'パラメータを取得する
    Dim stopTime As Integer = CInt(e.Argument)

    '時間のかかる処理を開始する
    For i As Integer = 1 To 100
        'キャンセルされたか調べる
        If bw.CancellationPending Then
            'キャンセルされたとき
            e.Cancel = True
            Return
        End If

        '指定された時間待機する
        System.Threading.Thread.Sleep(stopTime)

        'ProgressChangedイベントハンドラを呼び出し、
        'コントロールの表示を変更する
        bw.ReportProgress(i, i.ToString() & "% 終了しました")
    Next

    '結果を設定する
    e.Result = stopTime * 100
End Sub
C#
コードを隠すコードを選択
//Button1のClickイベントハンドラ
private void Button1_Click(object sender, System.EventArgs e)
{
    //ProgressDialogオブジェクトを作成する
    ProgressDialog pd = new ProgressDialog("進行状況ダイアログのテスト",
        new DoWorkEventHandler(ProgressDialog_DoWork),
        100);
    //進行状況ダイアログを表示する
    DialogResult result = pd.ShowDialog(this);
    //結果を取得する
    if (result == DialogResult.Cancel)
    {
        MessageBox.Show("キャンセルされました");
    }
    else if (result == DialogResult.Abort)
    {
        //エラー情報を取得する
        Exception ex = pd.Error;
        MessageBox.Show("エラー: " + ex.Message);
    }
    else if (result == DialogResult.OK)
    {
        //結果を取得する
        int stopTime = (int)pd.Result;
        MessageBox.Show("成功しました: " + stopTime.ToString());
    }

    //後始末
    pd.Dispose();
}

//DoWorkイベントハンドラ
private void ProgressDialog_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker bw = (BackgroundWorker)sender;

    //パラメータを取得する
    int stopTime = (int)e.Argument;

    //時間のかかる処理を開始する
    for (int i = 1; i <= 100; i++)
    {
        //キャンセルされたか調べる
        if (bw.CancellationPending)
        {
            //キャンセルされたとき
            e.Cancel = true;
            return;
        }

        //指定された時間待機する
        System.Threading.Thread.Sleep(stopTime);

        //ProgressChangedイベントハンドラを呼び出し、
        //コントロールの表示を変更する
        bw.ReportProgress(i, i.ToString() + "% 終了しました");
    }

    //結果を設定する
    e.Result = stopTime * 100;
}

上記のコードを説明します。

まず、ProgressDialogのコンストラクタのパラメータに、ダイアログのタイトルバーに表示する文字列、BackgroundWorkerのDoWorkイベントハンドラ、さらにDoWorkイベントハンドラで取得できるパラメータを指定しています。ただし、3番目のパラメータは省略できます。

ProgressDialogのShowDialogメソッドを呼び出すと、進行状況ダイアログが表示され、同時にDoWorkイベントハンドラが実行されます。

DoWorkイベントハンドラには、バックグラウンドで行う時間のかかる処理を記述します。詳しくは「進行状況ダイアログを表示する」で説明していますが、最も注意しなければいけないのは、DoWorkイベントハンドラ内では絶対にコントロール(フォームを含む)を操作してはいけないということです。そうしないと、例外System.InvalidOperationExceptionが発生します。

ユーザーが進行状況ダイアログのキャンセルボタンを押したかは、CancellationPendingプロパティで分かります。CancellationPendingプロパティの値を随時調べ、Trueになったらキャンセルの処理を行います。この時、DoWorkEventArgs.CancelをTrueにして、DoWorkイベントハンドラを終了させます。

進行状況ダイアログのプログレスバーの値とメッセージの内容を変更するには、ReportProgressメソッドを呼び出します。ReportProgressメソッドの第1パラメータには、プログレスバーの値を0〜100までの整数で指定します。第2パラメータには、メッセージの内容をString型で指定します。

DoWorkイベントハンドラが終了すると、ProgressDialog.ShowDialogメソッドはDialogResultを返します。処理が正常に完了したときは、DialogResult.OKを返します。キャンセルされたときは、DialogResult.Cancelを返します。エラーが発生したときは、DialogResult.Abortを返します。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • このサイトで紹介されているコードの多くは、例外処理が省略されています。例外処理については、こちらをご覧ください。
  • イベントハンドラの意味が分からない、C#のコードをそのまま書いても動かないという方は、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。