連載:Windowsフォーム開発入門【Visual Studio 2010対応】

Windowsフォーム・コントロールの基礎(その3)

初音 玲
2010/10/19
Page1 Page2 Page3 Page4

 Windowsフォーム・コントロールを個別に説明するのも3回目を迎え、いよいよ今回でコントロールの個別説明は最終回だ。

 最後に取り上げるコントロールは、以下の画面に示す、Visual Studio 2010の[ツールボックス]の[コンポーネント]タブに含まれるコントロール群だ。

図1 今回、取り上げるコントロール

 コンポーネントとは、Windowsフォーム画面のデザインにかかわるコントロールではないが、.NETのさまざまな機能を簡単に使えるようにする(Windowsフォーム画面上には表示されない)コントロール(以下、コンポーネント)のことだ。

 [コンポーネント]タブには、下記のようなコンポーネントが含まれている。

(1)コンポーネント
BackgroundWorker、DirectoryEntry、DirectorySearcher、ErrorProvider、EventLog、FileSystemWatcher、HelpProvider、ImageList、MessageQueue、PerformanceCounter、Process、SerialPort、ServiceController、Timer

 コンポーネントは、使い方にコツが必要だったり前提条件があったりするものが多い。そこで、比較的使用頻度が高そうなBackgroundWorker、DirectoryEntry、EventLog、FileSystemWatcher、MessageQueue、Process、Timerの7つのコンポーネントについては、具体的なコードを記載して使い方を説明したい。

コンポーネント

BackgroundWorkerコンポーネント

 Windowsフォーム・アプリケーションで時間のかかる処理を実行していると、Windowsフォームへの操作が行えなくなり、Windowsタスク・マネージャでも「応答なし」と表示されてしまうことがある。そこで、強制的に終了せさようとすると、以下の画面のような「応答なし」の警告ダイアログが表示される。このようになってしまうのは、時間のかかる処理をしている間、Windowsフォームの再描画などUIに関係する処理もすべて「待ち」になってしまい、これがOSから見ると「応答していない」と判断されるからだ。

図2 「応答なし」の警告ダイアログ

 このような「応答なし」になってしまう現象を回避するには、時間のかかる処理を別スレッドとして(メイン・スレッドから分離して)実行し、メイン・スレッドではUIの処理が行えるようにしなければならない。別スレッドで実行させるのに、Windows.Threading名前空間の機能を直接使ってもよいが、BackgroundWorkerコンポーネント(以下、BackgroundWorker)を使うとWindows.Threading名前空間を直接使うよりも手軽に別スレッド化が行える。

 BackgroundWorkerを[ツールボックス]からドラッグ&ドロップすると、Windowsフォーム・デザイナのデザイン・サーフェイスの下にあるコンポーネント・トレイに[BackgroundWorker]アイコンが配置される。

 これで、BackgroundWorkerをコード中で利用できる。下記のコードはBackgroundWorkerの利用例だ。なおWindowsフォーム上には、「Background_Button」という名前のButtonコントロール(=[別スレッド実行]ボタン)と、「Status_ListBox」という名前のListBoxコントロール、「BackgroundWorker1」という名前のBackgroundWorkerを配置しているものとする。

Imports System.ComponentModel

Public Class BackgroundWoker_Form

  Private Sub Background_Button_Click( _
      ByVal sender As Object, ByVal e As EventArgs) _
          Handles Background_Button.Click
    For Each ctl As Control In Me.Controls
      ctl.Enabled = False
    Next
    Me.Status_ListBox.Items.Clear()
    Call DisplayStatus("Button.Click:Start")
    Me.BackgroundWorker1.WorkerReportsProgress = True
    Me.BackgroundWorker1.RunWorkerAsync()
    Call DisplayStatus("Button.Click:End")
  End Sub

  Private Sub BackgroundWorker1_DoWork( _
      ByVal sender As Object, _
      ByVal e As System.ComponentModel.DoWorkEventArgs) _
          Handles BackgroundWorker1.DoWork
    Dim ds As DataSet = GetRecords(True)
  End Sub

  Private Function GetRecords(ByVal isAsync As Boolean) As DataSet
    Dim ds As New DataSet

    For index As Integer = 1 To 10
      System.Threading.Thread.Sleep(1000)
      If isAsync Then
        Me.BackgroundWorker1.ReportProgress(index * 10)
      End If
    Next
    Return ds
  End Function

  Private Sub DisplayStatus(ByVal message As String)
    Me.Status_ListBox.Items.Add( _
      DateTime.Now.ToString("yyyyMMddHHmmss") & _
      vbTab & message)
  End Sub

  Private Sub BackgroundWorker1_ProgressChanged( _
      ByVal sender As Object,
      ByVal e As ProgressChangedEventArgs) _
          Handles BackgroundWorker1.ProgressChanged
    Call DisplayStatus(String.Format( _
      "BackgroundWorker1.ProgressChanged]{0}%", _
      e.ProgressPercentage))
  End Sub

  Private Sub BackgroundWorker1_RunWorkerCompleted( _
      ByVal sender As Object, _
      ByVal e As RunWorkerCompletedEventArgs) _
          Handles BackgroundWorker1.RunWorkerCompleted
    Call DisplayStatus("BackgroundWorker1.RunWorkerCompleted")
    For Each ctl As Control In Me.Controls
      ctl.Enabled = True
    Next
  End Sub

End Class
using System;
using System.ComponentModel;
using System.Data;
using System.Windows.Forms;

namespace WindowsBackgroundWorkerCs
{
  public partial class BackgroundWorker_Form : Form
  {
    public BackgroundWorker_Form()
    {
      InitializeComponent();
    }

    private void Background_Button_Click(object sender, EventArgs e)
    {
      foreach (Control ctl in this.Controls)
      {
        ctl.Enabled = false;
      }
      this.Status_ListBox.Items.Clear();
      DisplayStatus("Button.Click:Start");
      this.BackgroundWorker1.WorkerReportsProgress = true;
      this.BackgroundWorker1.RunWorkerAsync();
      DisplayStatus("Button.Click:End");
    }

    private void BackgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
      DataSet ds = GetRecords(true);
    }

    private DataSet GetRecords(Boolean isAsync)
    {
      DataSet ds = new DataSet();

      for (int index = 1; index <= 10; index++)
      {
        System.Threading.Thread.Sleep(1000);
        if (isAsync == true)
        {
          this.BackgroundWorker1.ReportProgress(index * 10);
        }
      }
      return ds;
    }

    private void DisplayStatus(String message)
    {
      this.Status_ListBox.Items.Add(
        DateTime.Now.ToString("yyyyMMddHHmmss") +
        "\t" + message);
    }

    private void BackgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
      DisplayStatus(String.Format(
        "BackgroundWorker1.ProgressChanged]{0}%",
        e.ProgressPercentage));
    }

    private void BackgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
      DisplayStatus("BackgroundWorker1.RunWorkerCompleted");
      foreach (Control ctl in this.Controls)
      {
        ctl.Enabled = true;
      }
    }
  }
}
リスト1 BackgroundWorkerの使用例(上:VB、下:C#)
ButtonコントロールのClickイベント・プロシージャ(=Background_Button_Clickメソッド)は、Windowsフォーム・デザイナのフォーム画面上に配置したButtonコントロールをダブルクリックすると、そのひな型コードが自動的に生成される。同様に、BackgroundWorkerのDoWorkイベント・プロシージャ(=BackgroundWorker1_DoWorkメソッド)も、BackgroundWorker1をダブルクリックすれば自動生成される。
BackgroundWorkerのProgressChangedイベント・プロシージャ(=BackgroundWorker1_ProgressChangedメソッド)とRunWorkerCompletedイベント・プロシージャ(=BackgroundWorker1_RunWorkerCompletedメソッド)は、Windowsフォーム・デザイナのフォーム画面上に配置したBackgroundWorkerを選択した状態で、[プロパティ]ウィンドウの[イベント]一覧のそれぞれのイベント項目をダブルクリックすると自動生成される。

 それではプログラムの動きを順番に見ていこう。

(1)Background_Button_Clickイベント・プロシージャ
[別スレッド実行]ボタンをクリックすると、Background_Button_Clickイベント・プロシージャが呼び出される。プロシージャの中では、「for each」構文を使って画面上のコントロールのEnabledプロパティを「false」に設定して、時間のかかる処理を別スレッドで実行中に新たな操作を受け付けないようにしている。もし、時間のかかる処理を実行中でもほかの操作を行えるようにしたいときは、この「for each」の部分は不要だ(ただし、[別スレッドで実行]ボタンだけは「Enabled = false」にしておいた方がいいだろう)。
次に「BackgroundWorker1.WorkerReportsProgress = true」として処理の途中経過を受け取れるようにしてから、BackgroundWorker1.RunWorkerAsyncメソッドによりBackgroundWorker経由で別スレッドを生成する。生成元のスレッドのBackground_Button_Clickイベント・プロシージャの残りのコードは、そのまま処理を続けることになる。

(2)BackgroundWorker1_DoWorkイベント・プロシージャ
別スレッドが生成されると、DoWorkイベント・プロシージャが別スレッドとして実行される。そこで、このプロシージャの中で時間のかかる処理を実行する。

(3)BackgroundWorker1_ProgressChangedイベント・プロシージャ
時間のかかる処理の中でBackgroundWorker1.ReportProgressメソッドを(処理進行度をパーセンテージでパラメータに指定して)実行すると、ProgressChangedイベントが(別スレッド側ではなく)スレッドを生成した側(つまりWindowsフォーム側)で発生する。BackgroundWorkerを使わない場合、別スレッドからWindowsフォームやコントロールを直接操作するとエラーが発生するため、デリゲート(delegate)などを使って回避しなければならないが(参考:「.NET TIPS:Windowsフォームで別スレッドからコントロールを操作するには?」)、BackgroundWorkerを使っていれば、スレッド間通信などを意識しなくて済む。

(4)BackgroundWorker1_RunWorkerCompletedイベント・プロシージャ
BackgroundWorker1_DoWorkイベント・プロシージャ内のすべての処理実行が終わり、別スレッドでの処理が完了すると、RunWorkerCompletedイベントが発生する。

 BackgroundWorkerを使う利点は、ProgressChangedイベントによりスレッドをまたがってデータをやり取りできる点だろう。このサンプルでは処理進行度を渡しているが、文字データを渡すこともできる。

図3 BackgroundWorkerの実行例


 INDEX
  [連載]Windowsフォーム開発入門【Visual Studio 2010対応】
  Windowsフォーム・コントロールの基礎(その3)
  1.コンポーネント(1):BackgroundWorker
    2.コンポーネント(2):DirectoryEntry/DirectroySearcher/ErrorProvider/EventLog
    3.コンポーネント(3):FileSystemWatcher/HelpProvider/ImageList/MessageQueue
    4.コンポーネント(4):PerformanceCounter/Process/SerialPort/ServiceController/Timer

インデックス・ページヘ  「Windowsフォーム開発入門」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)
- PR -

注目のテーマ

業務アプリInsider 記事ランキング

本日 月間
ソリューションFLASH