酢ろぐ!

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

Windows Mobile(.NET Compact Framework)でフォーカスが当たったメッセージ(WM_SETFORCUS) を拾う

.NET Compact Frameworkは、デスクトップ.NET Frameworkのサブセットです。拾えるイベントがものすごく少ないですので、Windows Messageによる処理を行なえるようにしました。

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

|cs| 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; } } ||<

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

元ネタのコメント付きの実装は、Download Visual Studio 2005 Retired documentation from Official Microsoft Download Centerにて公開されています。長いのでコメントを省きます。

|cs| 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); } } } ||<

結局はWin32APIを利用する形になってしまうので、こちらの本が参考になります。

Windows Mobileプログラミング徹底理解

Windows Mobileプログラミング徹底理解