How do I redirect mouse wheel messages from one window to another?

时间:2016-04-15 15:13:28

标签: c# winforms imessagefilter

WM_MOUSEWHEEL messages are sent to the control with the focus. My application has a complex control hierarchy, with controls containing other controls, some of which are invisible or overlapping. I would like the mouse wheel to scroll a specific ScrollableControl.

This question has an answer with a IMessageFilter implementation that catches WM_MOUSEWHEEL messages. This works well and I see the messages being caught. I tried manipulating ScrollableControl's VerticalScroll property to scroll its contents, by changing the value of VerticalScroll.Value. Unfortunately, there are some undesirable side effects like the mouse thumb in the scrollbar becoming unsynchronized with the ScrollableControl's contents. Perhaps this is because this work is being done inside the message pump instead of in an event handler.

This post describes a technique where WM_MOUSEWHEEL messages are reposted to another window. I would like to implement a IMessageFilter that catches WM_MOUSEWHEEL messages, and forwards them to a designated recipient.

I create the following IMessageFilter that tries to do this. I can see the forwarded message being caught by my filter, and I return false from the filter to tell the control to handle the message. The target control does not receive an OnMouseWheel event.

Can this filter be modified to to allow my targetControl to be scrolled using redirected messages?

public static class MouseWheelMessageRedirector
{
    public static void Add(Control rootControl, ScrollableControl targetControl)
    {
        var filter = new MouseWheelMessageFilter(rootControl, targetControl);
        Application.AddMessageFilter(filter);

        rootControl.Disposed += (sender, args) => Application.RemoveMessageFilter(filter);

        targetControl.MouseWheel += (sender, args) =>
        {
            // ... this code never executes
            System.Diagnostics.Trace.WriteLine("WHEEL ON TARGET");
        };
    }

    private class MouseWheelMessageFilter : IMessageFilter
    {
        const int WM_MOUSEWHEEL = 0x020A;

        [DllImport("user32.dll", SetLastError = true)]
        static extern bool PostMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);

        public MouseWheelMessageFilter(Control rootControl, ScrollableControl targetControl)
        {
            _rootControl = rootControl;
            _targetControl = targetControl;
            _targetWindowHandle = _targetControl.Handle;
        }

        public bool PreFilterMessage(ref Message m)
        {
            if (m.Msg != WM_MOUSEWHEEL)
                return false;

            if (m.HWnd == _targetWindowHandle)
                return false;

            // ... get the control that the mouse is over
            // ... determine if this is a control that we want to handle the message for
            // ... (omitted)

            PostMessage(_targetWindowHandle, m.Msg, m.WParam, m.LParam);
            return true;
        }

        private Control _rootControl;
        private ScrollableControl _targetControl;
        private IntPtr _targetWindowHandle;
    }
}

1 个答案:

答案 0 :(得分:0)

我刚做了同样的事情。这是我做的:

    public bool PreFilterMessage(ref Message m)
    {
        if ((WM)m.Msg == WM.MOUSEWHEEL)
        {
            // if mouse is over a certain component, prevent scrolling
            if (comboBoxVendors.Bounds.Contains(PointToClient(Cursor.Position)))
            {
                // return true which says the message is already processed
                return true;
            }


            // which direction did they scroll?
            int delta = 0;
            if ((long)m.WParam >= (long)Int32.MaxValue)
            {
                var wParam = new IntPtr((long)m.WParam << 32 >> 32);
                delta = wParam.ToInt32() >> 16;
            }
            else
            {
                delta = m.WParam.ToInt32() >> 16;
            }

            delta = delta*-1;
            var direction = delta > 0 ? 1 : 0;

            // post message to the control I want scrolled (I am converting the vertical scroll to a horizontal, bu you could just re-send the same message to the control you wanted
            PostMessage(splitContainerWorkArea.Panel2.Handle, Convert.ToInt32(WM.HSCROLL), (IntPtr) direction, IntPtr.Zero);

            // return true to say that I handled the message
            return true;
        }                           

        // message was something other than scroll, so ignore it
        return false;
    }

另外,我使用了来自PInvoke.net的windows消息枚举: http://www.pinvoke.net/default.aspx/Enums.WindowsMessages

最后,请确保在关闭表单时删除邮件过滤器,或者当您不再需要处理邮件时删除邮件过滤器:

Application.RemoveMessageFilter(thisForm)