連載
» 2004年07月09日 05時00分 公開

.NET TIPS:時間がかかる処理の進行状況をダイアログで表示するには?

[一色政彦,デジタルアドバンテージ]
.NET TIPS
Insider.NET


「.NET TIPS」のインデックス

連載目次

 Windowsアプリケーションで時間のかかる処理を行う場合、その処理実行中に何もユーザーに応答がないと、それが本当に実行中なのか、もしくはアプリケーションがハングアップ(フリーズ)してしまったのか、区別が付かないことが多い。

 これが5秒程度の短い処理ならば、「TIPS:待機状態のマウス・カーソルを表示するには?」で紹介した「待機カーソル」を表示して、処理実行中であることをユーザーに明示すれば問題ないだろう。しかしそれ以上の長い処理では、いつまで処理が続くのか、いつまで待てばよいのかを明示しないと、「待機カーソル」のままハングアップしてしまったのではと不安になるかもしれない。

 このような状況を回避する1つの方法は、進行状況(処理の何%が完了したのか)を表すメーター(ProgressBarコントロール)を使った「進行状況ダイアログ」を表示することだ。そこで本稿では、この進行状況ダイアログの実装方法について紹介する。

進行状況ダイアログ(WaitDialogクラス)の内容

 次の画面は、本稿で準備した進行状況ダイアログである。

進行状況ダイアログ 進行状況ダイアログ
メッセージ・テキストとメーター(ProgressBarコントロール)によって、時間がかかる処理の進行状況を明示するダイアログ。処理が開始されると自動的に表示され、処理が完了すると自動的に閉じられる。
  (1)メイン・メッセージ。処理の概要を表示する。例えば、ファイルの転送を行っているなら、「ファイルを転送しています……」のように表示する。
  (2)サブ・メッセージ。詳細な処理内容を表示する。例えば、ファイル転送なら、転送中の個々のファイル名(「readme.htm」「contents.htm」など)を表示する。
  (3)進行状況メッセージ。処理の進行状況として、「何件中の何件分が終わったのか」「全体の何%が終わったのか」などを表示する。
  (4)進行状況メーター。(3)の情報をメーターで視覚的に分かりやすく表現する。この用途にはProgressBarコントロールが利用できる。
  (5)[キャンセル]ボタン。処理を途中でキャンセル(中断)する。
  (6)「閉じる」ボタン。(5)の[キャンセル]ボタンと同じように、処理を途中でキャンセル(中断)する。

 本稿では、この進行状況ダイアログを、WindowsフォームであるFormクラスを継承した「WaitDialogクラス」として実装した。WaitDialogクラスのソース・コードは以下からダウンロードできる。

 WaitDialogクラスでは、継承元のFormクラス(System.Windows.Forms名前空間)のメンバに加えて、さらに次の表にあるメンバを追加している。

【パブリック・プロパティ】
IsAborting 処理がキャンセル(中止)されているかどうか(上掲画面の(5)または(6)が押されたかどうか)を示す値を取得する。キャンセルされた場合はtrue。それ以外はfalse
[VB.NET]Public ReadOnly Property IsAborting() As Boolean
[C#]public bool IsAborting {get;}
MainMsg メイン・メッセージ(上掲画面の(1))のテキストを設定する。
[VB.NET]Public WriteOnly Property MainMsg () As String
[C#]public string MainMsg {set;}
SubMsg サブ・メッセージ(上掲画面の(2))のテキストを設定する
[VB.NET]Public WriteOnly Property SubMsg () As String
[C#]public string SubMsg {set;}
ProgressMsg 進行状況メッセージ(上掲画面の(3))のテキストを設定する
[VB.NET]Public WriteOnly Property ProgressMsg () As String
[C#]public string ProgressMsg {set;}
ProgressValue 進行状況メーターの現在位置(上掲画面の(4)の現在位置)を設定する。既定値は「0」
[VB.NET]Public WriteOnly Property ProgressValue () As Integer
[C#]public int ProgressValue {set;}
ProgressMax 進行状況メーターの範囲の最大値(上掲画面の(4)の右端の値)を設定する。既定値は「100」
[VB.NET]Public WriteOnly Property ProgressMax () As Integer
[C#]public int ProgressMax {set;}
ProgressMin 進行状況メーターの範囲の最小値(上掲画面の(4)の左端の値)を設定する。既定値は「0」
[VB.NET]Public WriteOnly Property ProgressMin () As Integer
[C#]public int ProgressMin {set;}
ProgressStep PerformStepメソッドを呼び出したときに、進行状況メーターの現在位置を進める量を設定する。既定値は「10」
[VB.NET]Public WriteOnly Property ProgressStep () As Integer
[C#]public int ProgressStep {set;}
【パブリック・メソッド】
PerformStep 進行状況メーターの現在位置をProgressStepプロパティの量だけ進める
[VB.NET]Public Sub PerformStep()
[C#]public void PerformStep();
WaitDialogクラスで独自に追加したメンバ

 このWaitDialogクラスでは、進行状況ダイアログを表示するのにShowメソッドを使い、表示したダイアログはCloseメソッドで閉じるという仕様になっている。逆に、FormクラスのShowDialogメソッドによるダイアログの表示を禁止しているので注意してほしい。

 このような仕様になっているのは、進行状況ダイアログをモードレス・モードで使用するためである。なお、モードレス・ダイアログについては、「TIPS:モーダル・ダイアログやモードレス・ダイアログを表示するには?」で詳説しているので、詳しくはそちらを参照してほしい。

進行状況ダイアログ(WaitDialogクラス)の使い方

 それでは、この進行状況ダイアログ(WaitDialogクラス)を使用するサンプル・コードを以下に示す。

private void button1_Click(object sender, System.EventArgs e)
{
  // 進行状況ダイアログの初期化処理
  WaitDialog waitDlg = new WaitDialog();
  waitDlg.Owner = this// ダイアログのオーナーを設定
  waitDlg.MainMsg = "進行状況を表示しています……"// 処理の概要
  waitDlg.ProgressMax = 200;  // 全体の処理件数
  waitDlg.ProgressMin = 0;  // 処理件数の最小値(0件から開始)
  waitDlg.ProgressStep = 1;  // 何件ごとにメーターを進めるか
  waitDlg.ProgressValue = 0;  // 最初の件数

  // オーナーのフォームを無効にする
  this.Enabled = false;

  // 進行状況ダイアログを表示する
  waitDlg.Show();

  // 時間のかかる処理
  int iCount = 0;  // 何件目の処理かを示すカウンタ
  for (int i = 0; i < 200; i++)
  {
    // 処理中止かどうかをチェック
    if (waitDlg.IsAborting == true)
    {
      break;
    }

    // 進行状況ダイアログの詳細な処理内容を設定
    // (0件目、51件目、101件目、151件目で表示更新)
    if ((i == 0) ||
      ((i != 1) && (i % 50 == 1)))
    {
      waitDlg.SubMsg = "50件ごと表示を更新しています……:" +
     ((int)(i / 50) + 1).ToString();
    }

    // 進行状況ダイアログのメーターを設定
    waitDlg.ProgressMsg =
      ((int)(iCount * 100 / 200)).ToString() + "% " +
      "(" + iCount.ToString() + " / 200 件)";

    // メッセージ処理を促して表示を更新する
    Application.DoEvents();

    // 何らかの処理
    DoSomeWork();

    // 処理カウントを1ステップ進める
    iCount++;
    waitDlg.PerformStep();
  }

  // 最終メッセージを表示して、閉じるのを少し遅らせる
  if (waitDlg.DialogResult == DialogResult.Abort)
  {
    waitDlg.SubMsg = "処理を中断しました。";
  }
  else
  {
    waitDlg.SubMsg = "処理を完了しました。";
    waitDlg.ProgressMsg ="100% (200 / 200 件)";
  }
  Application.DoEvents();
  Thread.Sleep(100);

  // いったんオーナーをアクティブにする
  this.Activate();

  // 進行状況ダイアログを閉じる
  waitDlg.Close();

  // オーナーのフォームを有効に戻す
  this.Enabled = true;
}

進行状況ダイアログ(WaitDialogクラス)を使用するサンプル・コード(C#)
サンプル・プログラムのダウンロード(C#)

Private Sub button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles button1.Click
  ' 進行状況ダイアログの初期化処理
  Dim waitDlg As WaitDialog = New WaitDialog
  waitDlg.Owner = Me ' ダイアログのオーナーを設定
  waitDlg.MainMsg = "進行状況を表示しています……" ' 処理の概要
  waitDlg.ProgressMax = 200 ' 全体の処理件数
  waitDlg.ProgressMin = 0 ' 処理件数の最小値(0件から開始)
  waitDlg.ProgressStep = 1 ' 何件ごとにメーターを進めるか
  waitDlg.ProgressValue = 0 ' 最初の件数

  ' オーナーのフォームを無効にする
  Me.Enabled = False

  ' 進行状況ダイアログを表示する
  waitDlg.Show()

  ' 時間のかかる処理
  Dim iCount As Integer = 0   ' 何件目の処理かを示すカウンタ
  Dim i As Integer
  For i = 0 To 200 - 1 Step i + 1
    ' 処理中止かどうかをチェック
    If waitDlg.IsAborting = True Then
      Exit For
    End If

    ' 進行状況ダイアログの詳細な処理内容を設定
    ' (0件目、51件目、101件目、151件目で表示更新)
    If (i = 0) Or ((i <> 1) And (i Mod 50 = 1)) Then
      waitDlg.SubMsg = "50件ごと表示を更新しています……:" + _
        (CType((i / 50) + 1, Integer)).ToString()
    End If

    ' 進行状況ダイアログのメーターを設定
    waitDlg.ProgressMsg = _
       (CType((iCount * 100 / 200), Integer)).ToString() + "% " _
       + "(" + iCount.ToString() + " / 200 件)"

    ' メッセージ処理を促して表示を更新する
    Application.DoEvents()

    ' 何らかの処理
    DoSomeWork()

    ' 処理カウントを1ステップ進める
    iCount = iCount + 1
    waitDlg.PerformStep()
  Next

  ' 最終メッセージを表示して、閉じるのを少し遅らせる
  If waitDlg.DialogResult = DialogResult.Abort Then
    waitDlg.SubMsg = "処理を中断しました。"
  Else
    waitDlg.SubMsg = "処理を完了しました。"
    waitDlg.ProgressMsg = "100% (200 / 200 件)"
  End If
  Application.DoEvents()
  Thread.Sleep(100)

  ' いったんオーナーをアクティブにする
  Me.Activate()

  ' 進行状況ダイアログを閉じる
  waitDlg.Close()

  ' オーナーのフォームを有効に戻す
  Me.Enabled = True
End Sub

進行状況ダイアログ(WaitDialogクラス)を使用するサンプル・コード(VB.NET)
サンプル・プログラムのダウンロード(VB.NET)

 上記のコードの概要を簡単に説明しておく(コードの詳細な解説は割愛させていただく)。

 まず、WaitDialogクラスのインスタンスを生成して、ダイアログのオーナー(所有者)となる自分自身(メインのフォーム)をOwnerプロパティに設定している。さらに、進行状況ダイアログを初期化するため、前述したMainMsgプロパティ、ProgressMaxプロパティ、ProgressMinプロパティ、ProgressStepプロパティ、ProgressValueプロパティを設定している。

 次に、フォームのEnabledプロパティをfalseに設定してフォーム全体を無効化したうえで、WaitDialogオブジェクトのShowメソッドを呼び出し、進行状況ダイアログを表示している。

 その後、(ダイアログが表示されたままの状態で)「時間のかかる処理」を実行している。処理の実行状況をダイアログ上に表示するために、前述したSubMsgプロパティ、ProgressMsgプロパティにメッセージ・テキストを設定することで進行状況メッセージを更新し、PerformStepメソッドを呼び出すことで進行状況メーターを進めている。

 なお、「時間のかかる処理」の中にあるApplication.DoEventsメソッドの呼び出しは、ダイアログの表示内容を描画更新するためのものである。詳しくは、「TIPS:時間がかかる処理での「応答なし」を回避するには?」を参照していただきたい。また、DoSomeWorkメソッドは、「時間のかかる処理」を行うメソッドで、このサンプル・プログラムでは単にスリープしているだけである。

 「時間のかかる処理」が終了すると、進行状況に関する最終的なメッセージや進行状況メーター(「100%で完了」、もしくは「50%で中断」など)を表示更新している。その際、Threadクラス(System.Threading名前空間)のSleepメソッドにより、進行状況ダイアログのクローズを少し(100ミリ秒間)遅らせている。

 最後に、フォームのActivateメソッドを呼び出してアクティブ化したうえで、WaitDialogオブジェクトのCloseメソッドを呼び出して進行状況ダイアログを閉じ、Enabledプロパティをtrueに再設定することにより、無効化しておいたフォームを再び有効に戻している。

カテゴリ:Windowsフォーム 処理対象:ダイアログ・ボックス
使用ライブラリ:Formクラス(System.Windows.Forms名前空間)
使用ライブラリ:Threadクラス(System.Threading名前空間)
関連TIPS:待機状態のマウス・カーソルを表示するには?
関連TIPS:時間がかかる処理での「応答なし」を回避するには?


「.NET TIPS」のインデックス

.NET TIPS

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。