[WPF] ウインドウメッセージハンドラをフックする


やりたいこと

Windowsのメッセージを受け取って、その中に含まれているデータを取り出したいということがあったので、まずメッセージを受け取れるようにしたい。

イメージとしては、別の記事に書いた「C++のダイアログベースのひな型」の中に出てきたメッセージループ(ダイアログプロシージャ)を、C#からでも使えるようにする感じ。

ちょっと調べると、Windowsのメッセージのハンドラ(メッセージループ)をフックする、というやり方で実現できるようなので一度やってみる。

やったこと

ざっくり手順は下記。

  • フック時に行う処理を書いたメソッドを作成する
  • 使うメッセージの番号を定数定義する
  • 作成したメソッドをHwndSourceを使い、フック時に実行するメソッドとして登録する

サンプルプログラム

ここでは、例としてWM_PAINTを取った。
WM_PAINT0x000Fなので、その値を最初に定義している。

using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Interop;

namespace WpfApp61
{
    public partial class MainWindow : Window
    {
        // メッセージの定数を定義
        private const int WM_PAINT = 0x000F;

        public MainWindow()
        {
            InitializeComponent();

            // メッセージループをフックするメソッドを登録
            //var hWnd = new WindowInteropHelper(Application.Current.MainWindow).EnsureHandle();
            var hWnd = new WindowInteropHelper(this).EnsureHandle();
            HwndSource source = HwndSource.FromHwnd(hWnd);
            source.AddHook(new HwndSourceHook(WndProc));
        }

        // メッセージループを記述したメソッド
        private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            if (msg == WM_PAINT)
            {
                Debug.WriteLine("WM_PAINT");
                // wparamやlparamでなにかを判定or取得する場合は下記のようにする
                if (wParam.ToInt32() == 0) { // なにかする(※WM_PAINTはwParamもlParamもなにも入ってない) }
            }
            return IntPtr.Zero;
        }
    }
}

これを実行すると、WM_PAINTが来たとき=画面に描画が必要になったとき(=ウインドウを画面の端っこにもっていって画面外に出た部分がまた画面内に戻ってきたとき等)に、WM_PAINTが来ているのを見れる。

参考

ダイアログベースのひな型
https://qiita.com/tera1707/items/003961a385e6bbf8a160

HwndSource クラス
https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.interop.hwndsource?view=net-5.0

WPFアプリケーションでウィンドウプロシージャをフックする
https://qiita.com/tricogimmick/items/86141bc33c0e06e9d2e9