読者です 読者をやめる 読者になる 読者になる

RのSkyrim Mod開発日記

SkyrimのMod開発を中心に、備忘録などを載せていきます。

簡易ログビューワーの作成

はじめに

Visual Studioにて指定したテキストファイルを読み込み、変更があったら続きを読み込む簡易ログビューワーを作成します。
シーク(seek)や、ファイルシステムウォッチャー(FileSystemWatcher)を利用して作ります。
とりあえず、備忘録としてのまとめですので、各内容について解説していません。コメントの説明も殴り書きなのでわかりづらいと思いますので、参考サイトを確認していただければと思います。

実際の作成

C#Windowsフォームアプリケーションを新規作成します。
ここでは次のようにしています。
f:id:rrryutaro:20161219192341p:plain

フォームが表示されます。
f:id:rrryutaro:20161219192517p:plain

ツールボックスよりTextBoxをダブルクリックしてフォームへ追加します。
f:id:rrryutaro:20161219192624p:plain

貼り付けたTextBoxの矢印部分をクリックしてMultiLineをクリックします。
f:id:rrryutaro:20161219193039p:plain

TextBoxを右クリックしてプロパティを表示し、DockプロパティからFill(中心の広い範囲)を選択します。
f:id:rrryutaro:20161219193328p:plain

フォームのタイトルバーをダブルクリックして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();
        }
    }
}

Qiitaで作り直した記事

qiita.com