C#でMouseWheelのフック

前回Ctrl+マウスホイールで拡大縮小するImageコントロールを用意しました。

そのホイール、WPFならルーティングイベントでどうにかなりそうですが、WindowsFormsでは対象コントロールがフォーカスを持っていないとホイールイベントは取得できません。そこで”フック”が登場します、フックとはWindowsから各アプリケーションに送られてくるWINDOWメッセージをのぞき見たり、時には上書きしたり無かったことにしたりするための仕組みです。

C#でのフックの仕方はココにやり方が載っています。、やってみるとわかりますがWH_MOUSEではマウス押下は取得可能ですが、マウスホイールはフックできません!またページ下部に記載ありますが、.NET Frameworkでは他のスレッドのメッセージもフックできる “グローバルフック” はサポートされておらず、自スレッドのメッセージだけフックできる “ローカルフック” がサポートされているとのこと。そもそも”フック”ってこんなにいろんな種類があるんですね。

じゃあpureC#のWindowsFormsで、他のコントロールにフォーカスがある場合マウスホイールは取得できないのか?いいえ、できます。

1.フックを使う方法

めげずにフックです。先ほどのMSDNのサンプルを変更して作ります。

csharpmousehook

namespace MouseWheelHook
{
    using System;
    using System.Windows.Forms;
    using System.Runtime.InteropServices;

    public partial class MouseHookForm : Form
    {
        #region PInvoke
        public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);

        static int hHook = 0;
        static int hLLHook = 0;

        public const int HC_ACTION = 0;
        public const int WH_MOUSE = 7;
        public const int WH_MOUSE_LL = 14;
        public const int WM_MOUSE_WHEEL = 0x20A;

        HookProc MouseHookProcedure;
        HookProc MouseHookLLProcedure;

        [StructLayout(LayoutKind.Sequential)]
        public class POINT
        {
            public int x;
            public int y;
        }

        [StructLayout(LayoutKind.Sequential)]
        public class MouseHookStruct
        {
            public POINT pt;
            public int hwnd;
            public int wHitTestCode;
            public int dwExtraInfo;
        }

        [StructLayout(LayoutKind.Sequential)]
        public class MouseLLHookStruct
        {
            public POINT pt;
            public int mouseData;
            public int flags;
            public int time;
            public int dwExtraInfo;
        }

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern IntPtr GetModuleHandle(string lpModuleName);

        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);

        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern bool UnhookWindowsHookEx(int idHook);

        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);
        #endregion

        public MouseHookForm()
        {
            InitializeComponent();
        }

        private void buttonMouseHook_Click(object sender, EventArgs e)
        {
            if (hHook == 0)
            {
                // Create an instance of HookProc.
                MouseHookProcedure = new HookProc(MouseHookForm.MouseHookProc);

                hHook = SetWindowsHookEx(WH_MOUSE,
                            MouseHookProcedure,
                            (IntPtr)0,
                            AppDomain.GetCurrentThreadId());
                //If the SetWindowsHookEx function fails.
                if (hHook == 0)
                {
                    MessageBox.Show("SetWindowsHookEx Failed");
                    return;
                }
                buttonMouseHook.Text = "UnHook Windows Hook";
            }
            else
            {
                bool ret = UnhookWindowsHookEx(hHook);
                //If the UnhookWindowsHookEx function fails.
                if (ret == false)
                {
                    MessageBox.Show("UnhookWindowsHookEx Failed");
                    return;
                }
                hHook = 0;
                buttonMouseHook.Text = "Set Windows Hook";
                this.Text = "Mouse Hook";
            }
        }
        private void buttonMouseLowLevelHook_Click(object sender, EventArgs e)
        {
            if (hLLHook == 0)
            {
                MouseHookLLProcedure = new HookProc(MouseHookForm.MouseLLHookProc);
                hLLHook = SetWindowsHookEx(WH_MOUSE_LL,
                            MouseHookLLProcedure,
                            GetModuleHandle(null),
                            0);
                if (hLLHook == 0){
                    MessageBox.Show("SetWindowsHookEx Failed");
                    return;
                }
                buttonMouseLowLevelHook.Text = "UnHook Windows Hook";
            }
            else
            {
                bool ret = UnhookWindowsHookEx(hLLHook);
                if (ret == false){
                    MessageBox.Show("UnhookWindowsHookEx Failed");
                    return;
                }
                hLLHook = 0;
                buttonMouseLowLevelHook.Text = "Set Windows Hook";
                this.Text = "Mouse Hook";
            }
        }
        private static int MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (0 <= nCode)
            {
                MouseHookStruct MyMouseHookStruct = (MouseHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseHookStruct));
                MouseHookForm tempForm = Form.ActiveForm as MouseHookForm;
                if (tempForm != null)
                {
                    String strCaption = "x = " + MyMouseHookStruct.pt.x.ToString("d") +
                                      "  y = " + MyMouseHookStruct.pt.y.ToString("d");
                    tempForm.labelStatus.Text = strCaption;
                }
            }
            return CallNextHookEx(hHook, nCode, wParam, lParam);
        }
        private static int MouseLLHookProc(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode == HC_ACTION)
            {
                MouseLLHookStruct MyMouseHookStruct = (MouseLLHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseLLHookStruct));
                MouseHookForm tempForm = Form.ActiveForm as MouseHookForm;
                if (tempForm != null && (WM_MOUSE_WHEEL == wParam.ToInt32()))
                {
                    String strCaption = "Wheeling x = " + MyMouseHookStruct.pt.x.ToString("d") +
                                      "  y = " + MyMouseHookStruct.pt.y.ToString("d");
                    tempForm.labelStatus.Text = strCaption;
                }
            }
            return CallNextHookEx(hHook, nCode, wParam, lParam);
        }
    }
}

べた書きでごめんなさい。張り付けて簡単に使って見れる構成にしたかったのです。。MSDNからパクッてきた左のボタンで有効になるのがローカルフックです。ボタンを押してフック有効にしてマウス移動させるとフォームの外に移動すると座標更新されないのがわかります。

WH_MOUSEによるフック

対して追加した右のボタンで有効になるのが今回のホイールをフックできるグローバルフックです。グローバルフック全体は.NET Frameworkでサポートされていないけどこのマウスホイール他いくつかはサポートされるよう(ただ動くだけかも)ですね。こちらはウィンドウをはみ出てもホイールしたタイミングで座標更新している様子が確認できます。

WH_MOUSE_LL

 2.親に頼む

別に何の面白みもなく、ごく当たり前なのですが、親コントロールに頼む方法があります。

subcontrol

namespace MouseWheelHook
{
    using System;
    using System.Windows.Forms;

    public partial class NeedWheelEventControl : UserControl
    {
        public NeedWheelEventControl()
        {
            InitializeComponent();
        }

        private void buttonAttachParent_Click(object sender, EventArgs e)
        {
            // 本来は適宜、静的or動的に親をさかのぼって探してください
            MouseHookForm parent = this.Parent as MouseHookForm;
            if (null == parent){
                return;
            }
            MouseEventHandler handler = (s, ea) => {
                labelStatus.Text = "Wheeling[2] x = " + ea.X.ToString("d") +
                                             "  y = " + ea.Y.ToString("d");

            };
            parent.MouseWheel += handler;
        }
    }
}

これを先ほどの親Formに張り付けて、コントロール外でマウスをホイールさせても当然イベントは取れます。
way2

親を探す方法をもうちょっと頑張って汎用的にすればライブラリ化もできそうですね。

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中