連載:Windowsフォーム開発入門【Visual Studio 2010対応】
Windowsフォーム・コントロールの基礎(その3)
初音 玲
2010/10/19 |
|
|
●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を使えばよい。