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;
}
}
I just did this same thing. Here's what I did:
Also, I used the windows message enum from PInvoke.net: http://www.pinvoke.net/default.aspx/Enums.WindowsMessages
Finally, be sure to remove the message filter when you close the form, or when you no longer need to process the messages: