アプリケーションを作成している時にファイルが更新されたら、処理をさせたいことありませんか?
そういう場合は「FileSystemWatcherクラス」が使うと簡単にファイルの監視を行うことができます。
今回は FileSystemWatcher の使い方と注意点について紹介します。
オススメの参考書
C#の使い方が丁寧に解説しており、「基礎からしっかりと学びたい」という初心者の方にオススメの一冊です。サンプルコードも記載してあり、各章の最後に復習問題があるので理解度を確認しながら読み進めることができます。新しい C# のバージョンにも対応している書籍です。
FileSystemWatcherクラス
C# には、ファイルやディレクトリの作成・変更・削除を監視するための FileSystemWatcher クラスが用意されています。
これを利用することで、特定のディレクトリにファイルが作成・変更・削除・名前の変更など状態変更が発生したタイミングでイベントを発火することができます。
プロパティの設定
FileSystemWatcher クラスを利用する際は、以下のプロパティを設定します。
Path | 監視するディレクトリのパスを指定します。 |
---|---|
Filter | 監視するファイルの拡張子を指定します。”*.*”は全てのファイルを監視します。”*.txt”はテキストファイルを監視します。 |
NotifyFilter | 監視する変更の種類を設定します。複数監視対象がある場合はORで組み合わせます。 |
IncludeSubdirectories | 指定したディレクトリのサブディレクトリを監視するかどうかを示すフラグを設定します。 |
EnableRaisingEvents | 監視を有効にするか無効にするかを設定します。”true”にすると監視が開始されます。 |
プロパティを使うことで監視対象とするファイルの範囲を指定することができます。
Filter
プロパティはテキストファイル以外にも”*.png”や”*.csv”などの拡張子も使えます。EnableRaisingEvents
プロパティはイベントを発生させたくない時は”false”にすることで監視を無効にすることができます。
NotifyFilter
プロパティはNotifyFilters
の列挙体で定義されているものから必要なものを OR で組み合わせて設定します。列挙体一覧は以下になります。
- Attributes:ファイルまたはフォルダの属性
- CreationTime:ファイルまたはフォルダが作成された時刻
- DirectoryName:ディレクトリの名前
- FileName:ファイルの名前
- LastAccess:ファイルまたはフォルダへの最終アクセス日時
- LastWrite:ファイルまたはフォルダへの最終書き込み日時
- Security:ファイルまたはフォルダのセキュリティ設定
- Size:ファイルまたはフォルダのサイズ
イベントの設定
NotifyFilters
プロパティで設定した監視対象が変更された時にイベントが発火されるように、イベントの設置を行います。設定できるイベントの種類は以下になります。
Created | ファイルまたはディレクトリが作成された時にイベントが発生します。 |
---|---|
Deleted | ファイルまたはディレクトリが削除された時にイベントが発生します。 |
Renamed | ファイルまたはディレクトリの名前が変更された時にイベントが発生します。 |
Changed | サイズや属性、最終更新日時、最終アクセス日時などが変更された時にイベントが発生します。 |
Error | インスタンスが変更の監視を続けられない場合、または内部バッファー オーバーフローの場合に発生します。 |
サンプル
デスクトップ上にあるテキストファイルが更新(上書き保存)されたら、そのファイルを強制終了させて”sample write”という文字列を末尾に書くサンプルを作成します。
サンプルはこちら。
public partial class MainWindow : Window
{
//FileSystemWatcherのオブジェクト名を設定
System.IO.FileSystemWatcher watcher;
public MainWindow()
{
InitializeComponent();
Loaded += (s, e) =>
{
//監視するディレクトリの指定
var directory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
//監視するディレクトリとファイルの種類を指定して初期化
watcher = new System.IO.FileSystemWatcher(directory, "*.txt");
//監視するフィールドの設定
watcher.NotifyFilter =
(NotifyFilters.Attributes
| NotifyFilters.LastAccess
| NotifyFilters.LastWrite
| NotifyFilters.FileName
| NotifyFilters.DirectoryName);
//サブディレクトリは監視しない
watcher.IncludeSubdirectories = false;
//監視を開始する
watcher.EnableRaisingEvents = true;
Debug.WriteLine("Start!");
//イベント設定
watcher.Changed += new System.IO.FileSystemEventHandler(watcher_Changed);
watcher.Created += new System.IO.FileSystemEventHandler(watcher_Changed);
watcher.Deleted += new System.IO.FileSystemEventHandler(watcher_Changed);
watcher.Renamed += new System.IO.RenamedEventHandler(watcher_Renamed);
};
}
private void watcher_Created(object sender, FileSystemEventArgs e)
{
Debug.WriteLine("Created");
}
private DateTime lastWriteTimeSave = DateTime.Now;
private void watcher_Changed(object sender, FileSystemEventArgs e)
{
Debug.WriteLine("Changed");
//初回のみイベントを実行
var file = new FileInfo(e.FullPath);
if (file.LastWriteTime.Subtract(lastWriteTimeSave) < new TimeSpan(0, 0, 0, 1)) return;
//メモ帳のプロセスをプロセスを強制的に終了させる
Process[] ps = Process.GetProcessesByName("notepad");
foreach (Process p in ps) p.Kill();
//イベントを無効にする
watcher.Changed -= watcher_Changed;
//イベント発生元のファイルパスを取得しファイルを開く
using (StreamWriter writer = new StreamWriter(e.FullPath, true, Encoding.GetEncoding("Shift_JIS")))
{
//文字を書き込む
writer.WriteLine("sample write");
};
//更新日時を更新
lastWriteTimeSave = file.LastWriteTime;
//イベントを無効にする
watcher.Changed += watcher_Changed;
}
private void watcher_Deleted(object sender, FileSystemEventArgs e)
{
Debug.WriteLine("Deleted");
}
private void watcher_Renamed(object sender, RenamedEventArgs e)
{
Debug.WriteLine("Renamed");
}
}
インスタンスの初期化
記述量をちょっと減らすためにFileSystemWatcher
のインスタンスを生成する際、監視するディレクトリとファイルの種類を指定しています。
こうすることでプロパティでPath
とFilter
の設定が完了します。ここでは、テキストファイルのみ監視するので、第2引数に”*.txt”を指定します。
FileSystemWatcherの初期設定
Load
イベントで監視するNotifyFilter
の設定、イベントの設定などを行います。この設定は用途にあわせて設定をし直す必要があります。
Changedイベント
このChanged
イベントですが、複数回に分けて発生する場合があるので、使用する時は注意が必要です。外部のアプリケーションを使っている時に発生する場合が多く、ファイルを作成または変更する際に何度かに分けてアクセスして処理を行う場合があるからです。こうなるとこちらが意図していない不具合が発生してしまいます。
この問題の解決策として、連続でChanged
イベントが発生した場合に初回のイベントのみ処理をするようにフィルターをかけます。ファイルの最終更新日時から1秒間未満に続けてイベントが発生した場合はイベントの処理をしないようにしています。
private DateTime lastWriteTimeSave = DateTime.Now;
private void watcher_Changed(object sender, FileSystemEventArgs e)
{
Debug.WriteLine("Changed");
//初回のみイベントを実行
var file = new FileInfo(e.FullPath);
if (file.LastWriteTime.Subtract(lastWriteTimeSave) < new TimeSpan(0, 0, 0, 1)) return;
}
上記のフィルターを通過した場合は、開いているメモ帳を全て強制的にプロセスを閉じます。メモ帳が開いたままだとテキストファイルへの書き込みが失敗するからです。
そのあとファイルへ”sample write”を書き込みします。
これで完成です。ファイルを更新(上書き保存)する度に指定した文字が末尾に記載される様になりました。これを応用すれば、どこかでうまいこと使えそうな使えなさそうそんな感じがします。
まとめ
今回は、FileSystemWatcher クラスの使い方と注意点について説明をしました。特に Changed イベントが複数回発生する場合の対処方法について記載されている記事が少ないように感じたので、参考になればと思います。
以上、最後まで読んで頂きありがとうございました。