Since the introduction of the wheel mouse, being able to operate scroll bars without the need for precision or the use of the mouse buttons has proven to be a godsend. User interfaces became friendlier and smoother as a result.

Unfortunately, the default handling of the MouseWheel event in Windows Forms is at-odds with the average user’s expectation about how and when this event should fire. In the vast majority of applications which support the mouse wheel, users expect to be able to affect a scrollable control or panel by simply moving the mouse over it and rotating the wheel. In WinForms, however, the MouseWheel event will only fire if the control has focus. In practice, this adds an unnecessary extra click to the process of using the mouse wheel and hinders the convenience offered by the wheel in the first place.

Thankfully, it is very simple to alter this behaviour and bring it in line with the user’s expectations. Better still, it is an application-wide solution and requires no changes to any control, panel or form.

The mechanism employed here is known as a message filter. Whenever the application receives a window message (the basic primitive used by all Win32 applications to handle interactivity, painting, events, etc), it is first passed to the message filter’s PreFilterMessage() method. Returning true from this method prevents the message from being processed in the normal way.

Our solution intercepts the WM_MOUSEWHEEL message, which is sent to the application when it is active and the user rotates the mouse wheel. Using the native method WindowFromPoint(), we can determine which control is under the mouse pointer at the time. We can then call another native method, SendMessage(), to send a copy of the original message to that control, allowing it to be processed as if the control was in focus at the time.

internal class MouseWheelMessageFilter : IMessageFilter {

    const int WM_MOUSEWHEEL = 0x20a;

    public bool PreFilterMessage(ref Message m) {
        if (m.Msg == WM_MOUSEWHEEL) {
            // LParam contains the location of the mouse pointer
            Point pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16);
            IntPtr hWnd = WindowFromPoint(pos);
            if (hWnd != IntPtr.Zero && hWnd != m.HWnd && Control.FromHandle(hWnd) != null) {
                // redirect the message to the correct control
                SendMessage(hWnd, m.Msg, m.WParam, m.LParam);
                return true;
            }
        }
        return false;
    }

    // P/Invoke declarations
    [DllImport("user32.dll")]
    private static extern IntPtr WindowFromPoint(Point pt);
    [DllImport("user32.dll")]
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
}

All we need do to use the message filter is to add the following line somewhere in the initialisation of the program (e.g. in the Main() method):

Application.AddMessageFilter(new MouseWheelMessageFilter());

This technique works for almost every type of control. A notable exception is the WebBrowser control, which uses its own logic to handle the rotation of the mouse wheel.

Use this code and enjoy the improvement in the usability of your application!

2 thoughts on “Making the MouseWheel event conform to user’s expectations

  1. +1
    vote

    Thanks for the code. You can use PostMessage instead of SendMessage, so that your application won’t be waiting for other application to finish processing the message.

    Reply
    • +2
      vote

      Thanks for the tip! I have not noticed any issues with responsiveness, but it doesn’t hurt to follow best practices 🙂

      Reply

Leave a reply

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> 

required