C# でウィンドウメッセージをキャプチャするメッセージフィルターを利用する方法

この記事では、 C# で作る Windows Form アプリケーションで Window メッセージを捕捉する方法を紹介します。

Window メッセージの捕捉というと、ちょっとした裏技とか、ハック的な話題かと思われるかもしれませんが(そうでもないかな?)、そういった類のものではなく、 .NET Framework 1.0 のはじめから Windows Forms に仕組みこまれている非常に基本的な方法です。非公式の方法、アンドキュメンテッドな方法でコードを書いていると、バージョンアップとか、あるいはサービスパックのリリースなどにすら対応できない場合があるわけですが、これはそういうものではないので、安心して使える方法である、というわけです。

さて、メッセージループから直接ウィンドウメッセージを拾い上げたい場面は、通常あまりないかもしれませんが、知っておくと何かと自由度が高まるので便利です。

メッセージフィルタの活用例 - バーコードスキャナ対応

例えば、私がこれまでに利用して便利だったのはバーコードスキャナを利用した Windows プログラムを開発した場合です。

これまで何度かバーコードを利用する案件にかかわっていますが、たいていはフォーカスがどこにあるか分からない状態でスキャナを使うことになります。ピッとバーコードをスキャンしたときに、適切なフィールド(例えばテキストボックス)に文字を入力する必要があります。

バーコードスキャナからの入力は通常キーボードからの入力と同様に扱われることになりますので、もし何か特別な処理をしないと、フォーカスがボタンにあればバーコードからの入力は無視されます。あるいは意図した以外のテキストボックスにフォーカスがあれば、そこへバーコードからの入力がセットされてしまいます。

意図した特定のフィールドにバーコードから入力するために、バーコードをスキャンする前に、画面の入力フィールドをクリック(あるいはタップして)フォーカスを設定するというのはわずらわしいですよね。

したがって、どこにフォーカスがあろうともバーコードからの入力を判断して、適切なフィールドに特定のパターンのコードならこちらに入力する、ということができれば嬉しいわけです。

この問題のひとつの解が、メッセージループで直接ウィンドウメッセージをチェックすることによって、特定のコントロールのイベントハンドラに処理が渡る前に必要な処理を行う、ということです。

IMessageFilter の PreFilterMessage メソッドの利用

ウィンドウメッセージが各ウィンドウ(コントロールやフォーム)にディスパッチされる前に、ウィンドウメッセージをチェックするためには、メッセージフィルタを利用します。

メッセージフィルタは、IMessageFilter (System.Windows.Forms) インターフェイスを実装したクラスです。IMessageFilter インターフェイスは PreFilterMessage メソッドひとつだけを定義しています。

bool PreFilterMessage(
	ref Message m
)

メッセージフィルタを登録しておくと、ウィンドウメッセージがディスパッチされる前に PreFilterMessage が呼び出されます。PreFilterMessage にはウィンドウメッセージ Message が渡されます。

PreFilterMessage では true または false を返します。メッセージを処理したら true、処理しなければ false を返します。

ではさっそく、メッセージフィルタの利用例として次をみてください。

メッセージフィルタの利用

このプログラムでは特定のメッセージが送られたときに、それをデバッグトレースに出力します。

メッセージフィルタの利用

DebugView を利用してデバッグトレースをキャプチャすると、上のようになります。

プログラム上のテキストボックスに "abc" という文字をキーボードから入力したわけですが、 一文字毎に 0100 (WM_KEYDOWN), 0102 (WM_CHAR), 0101 (WM_KEYUP) というシーケンスが見られました。

メッセージフィルタのコードは次の通りです。

using System.Windows.Forms;

namespace MessageFilterTest1
{
	class MyMessageFilter : IMessageFilter
	{
		enum WindowMessage
		{
			WM_KEYDOWN = 0x0100,
			WM_KEYUP = 0x0101,
			WM_CHAR = 0x0102
		}

		public bool PreFilterMessage(ref Message m)
		{
			if (m.Msg == (int)WindowMessage.WM_KEYDOWN
				|| m.Msg == (int)WindowMessage.WM_KEYUP
				|| m.Msg == (int)WindowMessage.WM_CHAR)
			{
				System.Diagnostics.Debug.WriteLine(
					string.Format("{0:X4}", m.Msg)
				);
			}
			return false;
		}
	}
}

ちなみに ウィンドウメッセージの定義については、Windows SDK の WinUser.h などをみると分かりやすいです。

/*
#define WM_KEYDOWN                      0x0100
#define WM_KEYUP                        0x0101
#define WM_CHAR                         0x0102
//*/

# Source Insight を使って WinUser.h を開くと次のように見えます。

Source Insight で WinUser.h を開く

メッセージフィルタの登録

メッセージフィルタをこれを Windows フォーム開始時に次のようにして登録することで、メッセージフィルターが有効になります。

using System;
using System.Windows.Forms;

namespace MessageFilterTest1
{
	public partial class Form1 : Form
	{
		MyMessageFilter messageFilter;

		public Form1()
		{
			InitializeComponent();
		}

		private void Form1_Load(object sender, EventArgs e)
		{
			messageFilter = new MyMessageFilter();
			Application.AddMessageFilter(messageFilter);
		}
	}
}

以上で C# でメッセージフィルタを利用する方法の説明を終わりますが、 そもそもウィンドウメッセージって何なの?とか、疑問が残る方は次の記事などを参考にしてください。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 C# 入門