IMessageFilter

今回は最近知った機能を紹介します。
なにはともあれ次のコードを見てください。

class MessageFilter : IMessageFilter
{
	private const int WM_KEYDOWN = 0x100;

	public bool PreFilterMessage(ref Message m)
	{
		if (m.Msg == WM_KEYDOWN)
		{
			switch ((int)m.WParam)
			{
				case (int)Keys.A:
					MessageBox.Show("Filter A Down");
					break;
			}
		}

		return false;
	}
}

次に上記のコードを使用するフォームのコードです。

public partial class Form1 : Form
{
	private IMessageFilter _messageFiletr = new MessageFilter();

	public Form1()
	{
		InitializeComponent();
	}

	private void Form1_Load(object sender, EventArgs e)
	{
		Application.AddMessageFilter(_messageFiletr);
	}

	private void Form1_FormClosed(object sender, FormClosedEventArgs e)
	{
		Application.RemoveMessageFilter(_messageFiletr);
	}

	private void Form1_KeyDown(object sender, KeyEventArgs e)
	{
		if (e.KeyCode == Keys.A)
		{
			MessageBox.Show("Form A Down");
		}

	}

}

フォームのコードには適切なデザイナのコードがあることを仮定しています。


まず最初のコードのブロックですが、
IMessageFilterというインタフェースを実装したクラスMessageFilterを定義しています。
タイトルにあるように今回紹介したいものはこのIMessageFilterインタフェースです。


IMessageFilterインタフェースに宣言されているメソッドは一つだけです。

bool PreFilterMessage(ref Message m);

メソッド名と、MessageFilterクラスでの実装を見てどのような役割をしているのか大まか予想できると思いますが詳細は後述します。
ようはWin32APIでのプログラミングのようにメッセージループ本体を書いてやります。
FormやControlのWndProcをオーバライドしたのと同じような記述方法でもあります。


次にこのIMessageFilterインタフェースの使い方です。
Formのコード中のForm1_Loadイベント内でApplication.AddMessageFilter(messageFilter)というような使い方をしています。
AddMessageFilterというのはApplicationクラスのpublicでstaticなメソッドです。

public static void AddMessageFilter(IMessageFilter value)

これはアプリケーションに実装したメッセージフィルタを登録してやります。
Form1_FormClosedイベント内で使用していますが、登録を外す場合はApplication.RemoveMessageFilter(messageFilter)を使います。

public static void RemoveMessageFilter(IMessageFilter value)


これで準備完了です。
アプリケーションを実行してキーボードのAを押してやると、
Filter A Downと書かれたメッセージボックス → Form A Downと書かれたメッセージボックス
の順でメッセージボックスが出てくると思います。


IMessageFilterの実装が登録されると、メッセージがアプリケーションのメッセージループに渡される前にPreFilterMessageメソッドに渡されます。
そこの中でメッセージを処理したあとアプリケーションにメッセージが渡されるので実行したときのような順番でメッセージボックスが表示されたというわけです。


IMessageFilterという名前なのですから、メッセージをフィルタリングできそうなものです。
もちろんその機能も持っています。

public bool PreFilterMessage(ref Message m)
{
	if (m.Msg == WM_KEYDOWN)
	{
		switch ((int)m.WParam)
		{
			case (int)Keys.A:
				MessageBox.Show("Filter A Down");
				break;
		}

		return true;
	}

	return false;
}

上記のコードのようにm.MsgがWM_KEYDOWNであれば無条件でtrueを返すようにします。
これを実行すれば、Filter A Downは出ますがForm A Downは出ないと思います。
つまりPreFilterMessageメソッドがtrueを返した場合アプリケーションにそのメッセージは渡されないということです。


このような機能のIMessageFilterがどんな場合に便利なんだろうということで、その一例を示したいと思います。


アプリケーションを作成しているとそのアプリケーション全体で使用したいキーボードショートカットを実装したいということがあると思います。
ですがこれは結構面倒です。
なぜかというと、そのキーを押したというイベントをアプリケーションがどのような状態であってもキャプチャできなければいけません。
たとえばメインフォームにKeyDownイベントを追加してその中でそのキーボードショートカットをキャプチャするようにした場合、KeyDownイベントが呼ばれるのはメインフォームがフォーカスを持っている場合だけなので、ほかのコントロールがフォーカスを持っているときはキーボードショートカットをキャプチャできません。


かといってフォーカスを持つ可能性のあるコントロールすべてにキーボードショートカットをキャプチャするコードを書くのは馬鹿げた話です。


そういう場合に使えるのがIMessageFilterです。
PreFilterMessageメソッドはどのコントロールがフォーカスを持っていても、それらにメッセージが渡る前に呼ばれます。
これはアプリケーション全体で使用されるキーボードショートカットをキャプチャするにはもってこいです。


キーボードショートカットいうのはだいたいCtrlキーと何かという組み合わせなことが多いですが、そのような場合のコードの例を示します。

public bool PreFilterMessage(ref Message m)
{
	if (m.Msg == WM_KEYDOWN)
	{
		switch ((int)m.WParam | (int)Control.ModifierKeys)
		{
			case (int)Keys.A:
				MessageBox.Show("Filter A Down");
				break;
			case (int)Keys.A | (int)Keys.Control:
				MessageBox.Show("Filter A  & Control Down");
				break;
			case (int)Keys.A | (int)Keys.Control | (int)Keys.Shift:
				MessageBox.Show("Filter A & Control & Shift Down");
				break;
		}
	}

	return false;
}

上記のコードでは

  • Aだけを押した場合
  • Ctrl+Aの場合
  • Ctrl+Shift+Aを押した場合
というように分岐させています。


こうすることによってどのコントロールがフォーカスを持っていたとしてもキーボードショットカットをキャプチャすることができます。


と、いうことでIMessageFilterインタフェースの紹介は終了です。
やたらと長くなってしまった。
長くなるのは癖なんだろうね。直せそうにないや。