簡易ログビューワーの作成
はじめに
Visual Studioにて指定したテキストファイルを読み込み、変更があったら続きを読み込む簡易ログビューワーを作成します。
シーク(seek)や、ファイルシステムウォッチャー(FileSystemWatcher)を利用して作ります。
とりあえず、備忘録としてのまとめですので、各内容について解説していません。コメントの説明も殴り書きなのでわかりづらいと思いますので、参考サイトを確認していただければと思います。
実際の作成
C#のWindowsフォームアプリケーションを新規作成します。
ここでは次のようにしています。
フォームが表示されます。
ツールボックスよりTextBoxをダブルクリックしてフォームへ追加します。
貼り付けたTextBoxの矢印部分をクリックしてMultiLineをクリックします。
TextBoxを右クリックしてプロパティを表示し、DockプロパティからFill(中心の広い範囲)を選択します。
フォームのタイトルバーをダブルクリックしてLoadイベントを作成します。
private void Form1_Load(object sender, EventArgs e) { }
usingにSystem.IOを追加し、Form1_Loadの上に、以下のように変数を追加。
FileStream fs; FileSystemWatcher fsw; long pos; private void Form1_Load(object sender, EventArgs e) { }
次のようにオープンファイルダイアログからパスを取得し、ファイルストリームを作成して、ファイル監視のための定義を行い、非同期にて対象のファイルのタイムスタンプが変更された場合に、イベントが実行されるコードを記述します。
private void Form1_Load(object sender, EventArgs e) { //オープンファイルダイアログを表示 OpenFileDialog dlg = new OpenFileDialog(); dlg.ShowDialog(); //TODO:ファイルが選ばれなかった場合などの処理が必要 //ファイルを読込専用、他プロセスからの読書き可能として開き、読込んで、カーソル位置を取得する fs = new FileStream(dlg.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); ReadFile(fs, textBox1); pos = fs.Position; //指定のファイルのみ、更新された際に非同期にイベントを呼出す fsw = new FileSystemWatcher(); fsw.Path = Path.GetDirectoryName(dlg.FileName); fsw.Filter = Path.GetFileName(dlg.FileName); fsw.NotifyFilter = NotifyFilters.LastWrite; fsw.Changed += new FileSystemEventHandler(fsw_Changed); fsw.SynchronizingObject = this; fsw.EnableRaisingEvents = true; }
指定のファイルをバイト読み込みで最後まで読込み、エンコード指定して文字列変換した結果をテキストボックスに追記し、行末までカーソルする、ファイル読込み用のメソッドを追加する。
private static void ReadFile(FileStream fs, TextBox tb) { //ファイルを一時的に読み込むバイト型配列を作成する byte[] bs = new byte[0x1000]; //ファイルをすべて読み込む for (;;) { //ファイルの一部を読み込む int readSize = fs.Read(bs, 0, bs.Length); //ファイルをすべて読み込んだときは終了する if (readSize == 0) break; //部分的に読み込んだデータを使用したコードをここに記述する tb.Text += System.Text.Encoding.GetEncoding(932).GetString(bs); } //カーソルを行末に移動して、スクロールさせる tb.SelectionStart = tb.Text.Length; tb.ScrollToCaret(); }
ファイル監視により変更通知を受け取った際に、追加データをテキストボックスに追記する処理を追加する。
void fsw_Changed(object sender, FileSystemEventArgs e) { //ファイルの先頭から指定した位置までストリーム内の読込み位置を変更し、追加分のデータを読込んで、読込み位置を最後の位置にする fs.Seek(pos, SeekOrigin.Begin); ReadFile(fs, textBox1); pos = fs.Position; }
終了時にファイルストリームやファイル監視のオブジェクトで使用したリソースを解放するようにします。
private void Form1_FormClosed(object sender, FormClosedEventArgs e) { fs.Close(); fsw.EnableRaisingEvents = false; fsw.Dispose(); }
ここまで出来たら、F5を押してビルド&デバッグ実行します。
うまく出来ていれば、テキストファイルを変更して保存したら、変更分が表示されるのがわかるかと思います。
しかしまだかなり不十分な状態です。例えばTODOで記載した部分など、対処が必要です。
以下、全ソースコード。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.IO; namespace SimpleLogViewer { public partial class Form1 : Form { public Form1() { InitializeComponent(); } FileStream fs; FileSystemWatcher fsw; long pos; private void Form1_Load(object sender, EventArgs e) { //オープンファイルダイアログを表示 OpenFileDialog dlg = new OpenFileDialog(); dlg.ShowDialog(); //TODO:ファイルが選ばれなかった場合などの処理が必要 //ファイルを読込専用、他プロセスからの読書き可能として開き、読込んで読込み位置を取得する fs = new FileStream(dlg.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); ReadFile(fs, textBox1); pos = fs.Position; //指定のファイルのみ、更新された際に非同期にイベントを呼出す fsw = new FileSystemWatcher(); fsw.Path = Path.GetDirectoryName(dlg.FileName); fsw.Filter = Path.GetFileName(dlg.FileName); fsw.NotifyFilter = NotifyFilters.LastWrite; fsw.Changed += new FileSystemEventHandler(fsw_Changed); fsw.SynchronizingObject = this; fsw.EnableRaisingEvents = true; } static void ReadFile(FileStream fs, TextBox tb) { //ファイルを一時的に読み込むバイト型配列を作成する byte[] bs = new byte[0x1000]; //ファイルをすべて読み込む for (;;) { //ファイルの一部を読み込む int readSize = fs.Read(bs, 0, bs.Length); //ファイルをすべて読み込んだときは終了する if (readSize == 0) break; //部分的に読み込んだデータを使用したコードをここに記述する tb.Text += System.Text.Encoding.GetEncoding(932).GetString(bs); } //カーソルを行末に移動して、スクロールさせる tb.SelectionStart = tb.Text.Length; tb.ScrollToCaret(); } void fsw_Changed(object sender, FileSystemEventArgs e) { //ファイルの先頭から指定した位置までストリーム内の読込み位置を変更し、追加分のデータを読込んで、読込み位置を最後の位置にする fs.Seek(pos, SeekOrigin.Begin); ReadFile(fs, textBox1); pos = fs.Position; } private void Form1_FormClosed(object sender, FormClosedEventArgs e) { fs.Close(); fsw.EnableRaisingEvents = false; fsw.Dispose(); } } }