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

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

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

FileSystemWatcherコンポーネント

 FileSystemWatcherコンポーネント(以下、FileSystemWatcher)は、フォルダの変更やフォルダ内のファイルの変更を検出するためのコンポーネントだ。例えば、特定のフォルダを監視していて、ファイルが更新されたり新規作成や削除されたりすると、イベントが発生する。このコンポーネントの使い道としては、あるフォルダを監視して、ファイルが格納されたら、その内容をチェックしてデータベースに格納する、というような使い道が考えられる。

 FileSystemWatcherの注意点は、ファイルの新規作成を監視している場合に、ファイルの作成完了を待たずにイベントが発生してしまうことだ。そのため、大きなサイズのファイルを新規作成した場合、ファイル作成の途中でイベントが発生するという問題がある。この問題を回避してファイルの作成完了を検知するには、例えばファイル・ロックが解けるのを待つなどの工夫が必要になる。

 お勧めしたい解決方法は、例えば「test.dat」という名前でファイルを作成したいときには、「test.dat」ファイルを出力した直後に、その出力先と同じフォルダに拡張子だけ異なる「test.eof」をいう名前の、サイズが0バイトのファイルを出力し、「.eof」を拡張子に持つファイルをFileSystemWatcherの監視対象にすることだ。こうすれば、(.eofファイルは常に.datファイルの作成完了後にしか作成されないので)実際に処理したい.datファイルへのデータ出力完了を保証できる。この方法はFTPによるファイル転送でも使われる手法だ。

 以上の注意点を踏まえて、FileSystemWatcherを使用したコードは次のようになる。

 なおWindowsフォーム上には、「DatWatch_Button」「EofWatach_Button」「QuitWatch_Button」という名前の3つのButtonコントロールと、「Status_ListBox」という名前のListBoxコントロール、「BackgroundWorker1」という名前のBackgroundWorker、「FileSystemWatcher1」という名前のFileSystemWatcherを配置している。

Imports System.IO

Public Class FileSystemWatcher_Form

  Private Sub FileSystemWatacher_Form_Load( _
      ByVal sender As Object,  ByVal e As EventArgs)
          Handles MyBase.Load
    Me.FileSystemWatcher1.Path = Application.StartupPath
    Me.FileSystemWatcher1.IncludeSubdirectories = False
    Me.FileSystemWatcher1.NotifyFilter = NotifyFilters.FileName
    Me.FileSystemWatcher1.SynchronizingObject = Me
  End Sub

  Private Sub DatWatch_Button_Click( _
      ByVal sender As Object, ByVal e As EventArgs) _
          Handles DatWatch_Button.Click
    Me.Status_ListBox.Items.Clear()
    Call DisplayStatus("datファイル監視開始")
    Me.FileSystemWatcher1.Filter = "*.dat"
    Me.FileSystemWatcher1.EnableRaisingEvents = True
    ' ファイルをbackgroundで作成する
    Me.BackgroundWorker1.WorkerReportsProgress = True
    Me.BackgroundWorker1.RunWorkerAsync()
  End Sub

  Private Sub EofWatach_Button_Click( _
      ByVal sender As Object, ByVal e As EventArgs) _
          Handles EofWatach_Button.Click
    Me.Status_ListBox.Items.Clear()
    Call DisplayStatus("eofファイル監視開始")
    Me.FileSystemWatcher1.Filter = "*.eof"
    Me.FileSystemWatcher1.EnableRaisingEvents = True
    ' ファイルをbackgroundで作成する
    Me.BackgroundWorker1.WorkerReportsProgress = True
    Me.BackgroundWorker1.RunWorkerAsync()
  End Sub

  Private Sub FileSystemWatcher1_Created( _
      ByVal sender As Object, ByVal e As FileSystemEventArgs) _
          Handles FileSystemWatcher1.Created
    Call DisplayStatus("Created:" & vbTab & e.Name)
  End Sub

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

  Private Sub QuitWatch_Button_Click( _
      ByVal sender As Object, ByVal e As EventArgs) _
          Handles QuitWatch_Button.Click
    Me.FileSystemWatcher1.EnableRaisingEvents = False
    Call DisplayStatus("監視終了")
  End Sub

  Private Sub Me_FormClosing( _
      ByVal sender As Object, _
      ByVal e As FormClosingEventArgs) _
          Handles Me.FormClosing
    Me.FileSystemWatcher1.EnableRaisingEvents = False
  End Sub

  Private Sub BackgroundWorker1_DoWork( _
      ByVal sender As Object, _
      ByVal e As System.ComponentModel.DoWorkEventArgs) _
          Handles BackgroundWorker1.DoWork
    Dim fileName As String = _
      Path.Combine(Me.FileSystemWatcher1.Path, "test")
    If File.Exists(fileName & ".dat") Then
      File.Delete(fileName & ".dat")
    End If
    If File.Exists(fileName & ".eof") Then
      File.Delete(fileName & ".eof")
    End If
    Me.BackgroundWorker1.ReportProgress(100, "datファイル出力開始")
    Using _sw As New StreamWriter(fileName & ".dat")
      ' 10秒かかる処理 - CPUは使わずに10秒処理を停止
      For index As Integer = 1 To 10
        System.Threading.Thread.Sleep(1000)
        _sw.WriteLine(index.ToString)
        Me.BackgroundWorker1.ReportProgress(index * 10, _
          String.Format("datファイル出力中 {0}%", index * 10))
      Next
      _sw.Close()
    End Using
    Me.BackgroundWorker1.ReportProgress(100, "datファイル出力完了")
    Using _sw As New StreamWriter(fileName & ".eof")
      _sw.Close()
    End Using
    Me.BackgroundWorker1.ReportProgress(100, "eofファイル出力完了")
  End Sub

  Private Sub BackgroundWorker1_ProgressChanged( _
      ByVal sender As Object, _
      ByVal e As System.ComponentModel.ProgressChangedEventArgs) _
          Handles BackgroundWorker1.ProgressChanged
    Call DisplayStatus(e.UserState.ToString)
  End Sub

 End Class
using System;
using System.Windows.Forms;
using System.IO;

namespace WindowsFileSystemWatcherCs
{
  public partial class FileSystemWatacher_Form : Form
  {
    public FileSystemWatacher_Form()
    {
      InitializeComponent();
    }

    private void FileSystemWatacher_Form_Load(object sender, EventArgs e)
    {
      this.FileSystemWatcher1.Path = Application.StartupPath;
      this.FileSystemWatcher1.IncludeSubdirectories = false;
      this.FileSystemWatcher1.NotifyFilter = NotifyFilters.FileName;
      this.FileSystemWatcher1.SynchronizingObject = this;
    }

    private void DatWatch_Button_Click(object sender, EventArgs e)
    {
      this.Status_ListBox.Items.Clear();
      DisplayStatus("datファイル監視開始");
      this.FileSystemWatcher1.Filter = "*.dat";
      this.FileSystemWatcher1.EnableRaisingEvents = true;
      // ファイルをbackgroundで作成する
      this.BackgroundWorker1.WorkerReportsProgress = true;
      this.BackgroundWorker1.RunWorkerAsync();
    }

    private void EofWatach_Button_Click(object sender, EventArgs e)
    {
      this.Status_ListBox.Items.Clear();
      DisplayStatus("eofファイル監視開始");
      this.FileSystemWatcher1.Filter = "*.eof";
      this.FileSystemWatcher1.EnableRaisingEvents = true;
      // ファイルをbackgroundで作成する
      this.BackgroundWorker1.WorkerReportsProgress = true;
      this.BackgroundWorker1.RunWorkerAsync();
    }

    private void FileSystemWatcher1_Created(object sender, FileSystemEventArgs e)
    {
      DisplayStatus("Created:" + "\t" + e.Name);
    }

    private void QuitWatch_Button_Click(object sender, EventArgs e)
    {
      this.FileSystemWatcher1.EnableRaisingEvents = false;
      DisplayStatus("監視終了");
    }

    private void FileSystemWatacher_Form_FormClosing(object sender, FormClosingEventArgs e)
    {
      this.FileSystemWatcher1.EnableRaisingEvents = false;
    }

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

    private void BackgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
      String fileName =
        Path.Combine(this.fileSystemWatcher1.Path, "test");
      if (File.Exists(fileName + ".dat"))
      {
        File.Delete(fileName + ".dat");
      }
      if (File.Exists(fileName + ".eof"))
      {
        File.Delete(fileName + ".eof");
      }
      this.BackgroundWorker1.ReportProgress(100,
        "datファイル出力開始");
      using (StreamWriter _sw =
        new StreamWriter(fileName + ".dat"))
      {
        // 10秒かかる処理 - CPUは使わずに10秒処理を停止
        for (int index = 1; index <= 10; index++)
        {
          System.Threading.Thread.Sleep(1000);
          _sw.WriteLine(index.ToString());
          this.BackgroundWorker1.ReportProgress(index * 10,
            String.Format("datファイル出力中 {0}%", index * 10));
        }
        _sw.Close();
      }
      this.BackgroundWorker1.ReportProgress(100,
        "datファイル出力完了");
      using (StreamWriter _sw =
        new StreamWriter(fileName + ".eof"))
      {
        _sw.Close();
      }
      this.BackgroundWorker1.ReportProgress(100,
        "eofファイル出力完了");
    }

    private void BackgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
      DisplayStatus(e.UserState.ToString());
    }
  }
}
リスト5 FileSystemWatcherの使用例(上:VB、下:C#)
WindowsフォームのLoadイベント・プロシージャ(=FileSystemWatacher_Form_Loadメソッド)は、Windowsフォーム・デザイナのフォーム画面をダブルクリックすると、そのひな型コードが自動的に生成される。同様に、ButtonコントロールのClickイベント・プロシージャ(=DatWatch_Button_ClickメソッドとEofWatach_Button_ClickメソッドとQuitWatch_Button_Clickメソッド)は、Buttonコントロールをダブルクリックすると自動生成される。
フォームのFormClosingイベント・プロシージャ(=FileSystemWatacher_Form_FormClosingメソッド)は、Windowsフォーム・デザイナのフォーム画面を選択した状態で、[プロパティ]ウィンドウの[イベント]一覧のイベント項目をダブルクリックすると自動生成される。同様に、FileSystemWatcherのCreatedイベント・プロシージャ(=FileSystemWatcher1_Createdメソッド)は、FileSystemWatcherを選択した状態で[プロパティ]ウィンドウの[イベント]一覧のイベント項目をダブルクリックすると自動生成される。また、BackgroundWorkerのDoWorkイベント・プロシージャ(=BackgroundWorker1_DoWorkメソッド)やProgressChangedイベント・プロシージャ(=BackgroundWorker1_ProgressChangedメソッド)も同様の手順で追加すればよい。

 このサンプルでは、BackgroundWorkerのDoWorkイベント・プロシージャを使うことでファイル出力を別スレッドで行いつつ、メイン・スレッドではFileSystemWatcherでそのファイルの出力先を監視してファイル生成を非同期で待ち合わせている。

図5 FileSystemWatcherの実行例(上:[datファイル監視]ボタンをクリック、下:[eofファイル監視]ボタンをクリック)
このサンプルでは、ファイル生成の監視を行っているフォルダに対してファイルを出力したときのFileSystemWatcherのイベント・タイミングを調べることができる。
[datファイル監視]ボタンをクリックすると、メイン・スレッドで.datファイルの監視がスタートして別スレッドで.datファイルの生成が行われる。ファイルの生成を監視しているときのFileSystemWatcherのイベントは、.datファイルが書き出されたときにCreatedイベントが発生するので、.datファイルの出力が終わる前にそのファイルを使ってしまう危険性がある。
これに対し、[eofファイル監視]ボタンをクリックすると、.datファイルの生成が終わった後に生成する(=このアプリケーション・プロトコルを仕様として定義).eofファイルを監視する。そのため、FileSystemWatcherでCreatedイベントが発生したときには、.datファイルの作成は確実に完了している。このように少しの工夫でファイルの出力完了待ち合わせ処理などが不要になる。

HelpProviderコンポーネント

 HelpProviderコンポーネント(以下、HelpProvider)は、ポップアップ・ヘルプやオンライン・ヘルプを表示するためのコンポーネントだ。

 HelpProviderを配置したWindowsフォームにあるコントロールには、以下の画面のように、HelpProviderを使ってポップアップ・ヘルプを表示するHelpStringプロパティと、オンライン・ヘルプのキーワードを指定するHelpKeywordプロパティが追加される。

図6 コントロールに追加されるHelpProvider関連のプロパティ

ImageListコンポーネント

 ImageListコンポーネントは、第2回で取り上げたので説明は割愛するが、簡単にいえば画像などのイメージを配列(正確にはリスト構造)に入れて使いやすくするためのコンポーネントといえる。

MessageQueueコンポーネント

 MessageQueueコンポーネント(以下、MessageQueue)は、Microsoft Windowsメッセージ・キューを使ってプログラム間通信を行うためのコンポーネントだ。Microsoftメッセージ・キューを使うためには、Microsoft Message Queuing(MSMQ)サービスが必要なので、事前にMicrosoftメッセージ・キュー(MSMQ)サーバをインストールしておかなければならない。

 インストールするには、例えばWindows7ならば、[コントロール パネル]、[プログラム]、[Windowsの機能の有効化または無効化]の順にクリックすると、以下のような[Windows の機能]ダイアログが表示されるので、そこで[Microsoft メッセージ キュー (MSMQ) サーバー]チェックボックスにチェックを入れて[OK]ボタンをクリックすればよい。

図7 Microsoftメッセージ・キュー・サーバの有効化

 1つのプログラムでMicrosoftメッセージ・キューに送信して受信するのでもMessageQueueの動作を知ることができるが、(以下に示す)今回のサンプルは、VB用サンプル・コードとC#用サンプル・コードの間でお互いに送受信できるようなサンプルにしてある。

図8 サンプルでのキューの使い方

 このサンプルのコードは、以下のとおり。なおWindowsフォーム上には、「Send_TextBox」という名前のTextBoxコントロールと、「Send_Button」という名前のButtonコントロール(=[送信]ボタン)と、「Status_ListBox」という名前のListBoxコントロール、「MessageQueue1」「MessageQueue2」という名前の2つのMessageQueueを配置している。

 なお、「C#側送信 → VB側受信」は「VBQueue」という名前のキュー、「VB側送信 → C#側受信」は「CSQueue」という名前のキューというように、送受信の各方向でキュー名を入れ替えているだけで、そのほかのコードはまったく同一の意味になっている。

Public Class MessageQueue_Form

  Private Const SendQueueName As String = ".\Private$\CSQueue"
  Private Const RecvQueueName As String = ".\Private$\VBQueue"
  Private xmlFormatter As IMessageFormatter = _
    New XmlMessageFormatter(New Type() {GetType(String)})

  Private Sub MessageQueue_Form_Shown( _
      ByVal sender As Object, ByVal e As EventArgs) _
          Handles Me.Shown
    Me.Refresh()
    If Not Messaging.MessageQueue.Exists(SendQueueName) Then
      Messaging.MessageQueue.Create(SendQueueName)
    End If
    If Not Messaging.MessageQueue.Exists(RecvQueueName) Then
      Messaging.MessageQueue.Create(RecvQueueName)
    End If
    ' 送信用キュー設定
    Me.MessageQueue1.Path = SendQueueName
    ' 受信用キュー設定
    Me.MessageQueue2.Path = RecvQueueName
    Me.MessageQueue2.BeginReceive()
  End Sub

  Private Sub Send_Button_Click( _
      ByVal sender As Object, ByVal e As EventArgs) _
          Handles Send_Button.Click
    Me.Cursor = Cursors.WaitCursor
    If Me.Send_TextBox.Text.Length > 0 Then
      Call Send(Me.Send_TextBox.Text)
    End If
    Me.Cursor = Cursors.Default
  End Sub

  Private Sub Send(ByVal message As String)
    Me.MessageQueue1.Send( _
      New System.Messaging.Message(message, xmlFormatter))
  End Sub

  Private Sub MessageQueue2_ReceiveCompleted( _
      ByVal sender As Object,
      ByVal e As Messaging.ReceiveCompletedEventArgs) _
          Handles MessageQueue2.ReceiveCompleted
    Dim msg As Messaging.MessageQueue = _
      CType(sender, Messaging.MessageQueue)
    msg.Formatter = xmlFormatter
    Call DisplayStatus( _
      msg.EndReceive(e.AsyncResult).Body.ToString)
    Me.MessageQueue2.BeginReceive()
  End Sub

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

  Private Sub MessageQueue_Form_FormClosing( _
      ByVal sender As Object,  ByVal e As FormClosingEventArgs) _
          Handles Me.FormClosing
    ' サンプルなのでキューを削除する
    Messaging.MessageQueue.Delete(SendQueueName)
    Messaging.MessageQueue.Delete(RecvQueueName)
  End Sub

End Class
using System;
using System.Windows.Forms;
using System.Messaging;

namespace WindowsMessageQueueCs
{
  public partial class MessageQueue_Form : Form
  {
    private const String SendQueueName = ".\\Private$\\VBQueue";
    private const String RecvQueueName = ".\\Private$\\CSQueue";
    private IMessageFormatter xmlFormatter =
      new XmlMessageFormatter(new Type[] { typeof(String) });

    public MessageQueue_Form()
    {
      InitializeComponent();
    }

    private void MessageQueue_Form_Shown(object sender, EventArgs e)
    {
      this.Refresh();
      if (!MessageQueue.Exists(SendQueueName))
      {
        MessageQueue.Create(SendQueueName);
      }
      if (!MessageQueue.Exists(RecvQueueName))
      {
        MessageQueue.Create(RecvQueueName);
      }
      // 送信用キュー設定
      this.MessageQueue1.Path = SendQueueName;
      // 受信用キュー設定
      this.MessageQueue2.Path = RecvQueueName;
      this.MessageQueue2.BeginReceive();
    }

    private void Send_Button_Click(object sender, EventArgs e)
    {
      this.Cursor = Cursors.WaitCursor;
      if (this.Send_TextBox.Text.Length > 0)
      {
        Send(this.Send_TextBox.Text);
      }
      this.Cursor = Cursors.Default;
    }

    private void Send(String message)
    {
      this.MessageQueue1.Send(
        new System.Messaging.Message(message, xmlFormatter));
    }

    private void MessageQueue2_ReceiveCompleted(object sender, ReceiveCompletedEventArgs e)
    {
      MessageQueue msg =
        (MessageQueue)sender;
      msg.Formatter = xmlFormatter;
      DisplayStatus(
        msg.EndReceive(e.AsyncResult).Body.ToString());
      this.MessageQueue2.BeginReceive();
    }

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

    private void MessageQueue_Form_FormClosing(object sender, FormClosingEventArgs e)
    {
      // サンプルなのでキューを削除する
      MessageQueue.Delete(SendQueueName);
      MessageQueue.Delete(RecvQueueName);
    }
  }
}
リスト6 MessageQueueの使用例(上:VB、下:C#)
ButtonコントロールのClickイベント・プロシージャ(=Send_Button_Clickメソッド)は、Windowsフォーム・デザイナのフォーム画面上に配置したButtonコントロールをダブルクリックすると、そのひな型コードが自動的に生成される。
フォームのShownイベント・プロシージャ(=MessageQueue_Form_Shownメソッド)やFormClosingイベント・プロシージャ(=MessageQueue_Form_FormClosingメソッド)は、Windowsフォーム・デザイナのフォーム画面を選択した状態で、[プロパティ]ウィンドウの[イベント]一覧のそれぞれのイベント項目をダブルクリックすると自動生成される。同様に、MessageQueueのReceiveCompletedイベント・プロシージャ(=MessageQueue2_ReceiveCompletedメソッド)は、MessageQueueを選択した状態で[プロパティ]ウィンドウの[イベント]一覧のイベント項目をダブルクリックすると自動生成される。

 今回のサンプルでは、Microsoftメッセージ・キューでテキストを送受信したので、フォーマッタとしてXMLフォーマット(=XmlMessageFormatter)を採用した。もし、画像ファイルなどのバイナリ・データを送受信したいときは、XmlMessageFormatterの代わりに、BinaryMessageFormatterを使えばよい。


 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