酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

Windows Mobile(.NET Compact Framework)でWinMsg(Windows メッセージ)を拾う

.NET Compact Frameworkは、デスクトップ版.NET Frameworkのサブセットです。省容量のためイベントやプロパティ類が大幅にカットされています。

よって拾えるイベントがものすごく少ないので、Windows Messageによる処理を行なえるようにしました。

一般的なWM_PAINTだと説明が大変なので、本記事ではButtonクラスを継承したCustomButtonにフォーカスが当たった(WM_SETFORCUS)のを拾う方法を紹介します。

public class CustomButton : Button
{
  public const uint WM_SETFOCUS = 0x0007;

  public GradationButton()
  {
    WndProcHooker.HookWndProc(this,
      new WndProcHooker.WndProcCallback(this.WM_Setfocus_Handler),
      WM_SETFOCUS);
  }

  int WM_Setfocus_Handler(IntPtr hwnd, uint msg,
    uint wParam, int lParam, ref bool handled)
  {
      // ここでフォーカス時の処理を入れる
      return 0;
  }
}

WndProcをフックするクラスを実装する

前述のサンプルコードでのWndProcHookerクラスは、マネージウィンドウプロシージャ(WndProc)を使用して、Windows フォーム コントロールをサブクラス化して、ネイティブコードからコールバックを受け取けられるようにしたものです。

元ネタのコメント付きの実装は、方法 : WndProcHooker クラスを使用するにて公開されています。詳細はリンク先をご覧ください。

public class WndProcHooker
{
    public delegate int WndProcCallback(
        IntPtr hwnd, uint msg, uint wParam, int lParam, ref bool handled);
    private static Dictionary<IntPtr, HookedProcInformation> hwndDict =
        new Dictionary<IntPtr, HookedProcInformation>();
    private static Dictionary<Control, HookedProcInformation> ctlDict =
        new Dictionary<Control, HookedProcInformation>();
    public static void HookWndProc(
        Control ctl, WndProcCallback callback, uint msg)
    {
        HookedProcInformation hpi = null;
        if (ctlDict.ContainsKey(ctl))
            hpi = ctlDict[ctl];
        else if (hwndDict.ContainsKey(ctl.Handle))
            hpi = hwndDict[ctl.Handle];
        if (hpi == null)
        {
            hpi = new HookedProcInformation(ctl,
                new Win32.WndProc(WndProcHooker.WindowProc));
            ctl.HandleCreated += new EventHandler(ctl_HandleCreated);
            ctl.HandleDestroyed += new EventHandler(ctl_HandleDestroyed);
            ctl.Disposed += new EventHandler(ctl_Disposed);
            if (ctl.Handle != IntPtr.Zero)
                hpi.SetHook();
        }
        // Stick hpi into the correct dictionary.
        if (ctl.Handle == IntPtr.Zero)
            ctlDict[ctl] = hpi;
        else
            hwndDict[ctl.Handle] = hpi;
        // Add the message/callback into the message map.
        hpi.messageMap[msg] = callback;
    }
    static void ctl_Disposed(object sender, EventArgs e)
    {
        Control ctl = sender as Control;
        if (ctlDict.ContainsKey(ctl))
            ctlDict.Remove(ctl);
        else
            System.Diagnostics.Debug.Assert(false);
    }
    static void ctl_HandleDestroyed(object sender, EventArgs e)
    {
        // When the handle for a control is destroyed, we want to
        // unhook its wndproc and update our lists
        Control ctl = sender as Control;
        if (hwndDict.ContainsKey(ctl.Handle))
        {
            HookedProcInformation hpi = hwndDict[ctl.Handle];
            UnhookWndProc(ctl, false);
        }
        else
            System.Diagnostics.Debug.Assert(false);
    }
    static void ctl_HandleCreated(object sender, EventArgs e)
    {
        Control ctl = sender as Control;
        if (ctlDict.ContainsKey(ctl))
        {
            HookedProcInformation hpi = ctlDict[ctl];
            hwndDict[ctl.Handle] = hpi;
            ctlDict.Remove(ctl);
            hpi.SetHook();
        }
        else
            System.Diagnostics.Debug.Assert(false);
    }
    private static int WindowProc(
        IntPtr hwnd, uint msg, uint wParam, int lParam)
    {
        if (hwndDict.ContainsKey(hwnd))
        {
            HookedProcInformation hpi = hwndDict[hwnd];
            if (hpi.messageMap.ContainsKey(msg))
            {
                WndProcCallback callback = hpi.messageMap[msg];
                bool handled = false;
                int retval = callback(hwnd, msg, wParam, lParam, ref handled);
                if (handled)
                    return retval;
            }
            return hpi.CallOldWindowProc(hwnd, msg, wParam, lParam);
        }
        System.Diagnostics.Debug.Assert(
            false, "WindowProc called for hwnd we don't know about");
        return Win32.DefWindowProc(hwnd, msg, wParam, lParam);
    }
    public static void UnhookWndProc(Control ctl, uint msg)
    {
        HookedProcInformation hpi = null;
        if (ctlDict.ContainsKey(ctl))
            hpi = ctlDict[ctl];
        else if (hwndDict.ContainsKey(ctl.Handle))
            hpi = hwndDict[ctl.Handle];
        if (hpi == null)
            throw new ArgumentException("No hook exists for this control");
        if (hpi.messageMap.ContainsKey(msg))
            hpi.messageMap.Remove(msg);
        else
            // if we couldn't find the message, throw
            throw new ArgumentException(
                string.Format(
                "No hook exists for message ({0}) on this control",
                msg));
    }
    public static void UnhookWndProc(Control ctl, bool disposing)
    {
        HookedProcInformation hpi = null;
        if (ctlDict.ContainsKey(ctl))
            hpi = ctlDict[ctl];
        else if (hwndDict.ContainsKey(ctl.Handle))
            hpi = hwndDict[ctl.Handle];
        if (hpi == null)
            throw new ArgumentException("No hook exists for this control");
        if (ctlDict.ContainsKey(ctl) && disposing)
            ctlDict.Remove(ctl);
        if (hwndDict.ContainsKey(ctl.Handle))
        {
            hpi.Unhook();
            hwndDict.Remove(ctl.Handle);
            if (!disposing)
                ctlDict[ctl] = hpi;
        }
    }

    class HookedProcInformation
    {
        public Dictionary<uint, WndProcCallback> messageMap;
        private IntPtr oldWndProc;
        private Win32.WndProc newWndProc;
        private Control control;
        public HookedProcInformation(Control ctl, Win32.WndProc wndproc)
        {
            control = ctl;
            newWndProc = wndproc;
            messageMap = new Dictionary<uint, WndProcCallback>();
        }
        public void SetHook()
        {
            IntPtr hwnd = control.Handle;
            if (hwnd == IntPtr.Zero)
                throw new InvalidOperationException(
                    "Handle for control has not been created");
            oldWndProc = Win32.SetWindowLong(hwnd, Win32.GWL_WNDPROC,
                Marshal.GetFunctionPointerForDelegate(newWndProc));
        }
        public void Unhook()
        {
            IntPtr hwnd = control.Handle;
            if (hwnd == IntPtr.Zero)
                throw new InvalidOperationException(
                    "Handle for control has not been created");
            Win32.SetWindowLong(hwnd, Win32.GWL_WNDPROC, oldWndProc);
        }
        public int CallOldWindowProc(
            IntPtr hwnd, uint msg, uint wParam, int lParam)
        {
            return Win32.CallWindowProc(
                oldWndProc, hwnd, msg, wParam, lParam);
        }
    }
}