none
Outlook-style Wheel Mouse Behavior RRS feed

  • Question

  • In applications like Outlook and IE the wheel mouse applies the the control that the mouse cursor is hovering over, but in standard windows application it applies to the control that has focus - no matter which control the mouse cursor is over.  Outlook and IE are so common, that my users expect my application to behave the same.  But this does not appear to be supported by .NET as the documentation explicitly says that the event goes to the control that has focus.

     

    I cannot find any way to get the wheel mouse events sent to the window the cursor is over instead of the one that has focus.

     

    Does anyone know how to change this behavior for an application?

    Friday, July 18, 2008 12:53 AM

Answers

  • Agreed, Windows' handling of WM_MOUSEWHEEL isn't very desirable.  That it works the way it does in IE and Outlook is probably more a side-effect of those programs not using controls.  Giving your own UI the same behavior requires redirecting the message from the window with the focus to the window under the mouse cursor.  This seemed to work well:

    using System;
    using System.ComponentModel;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Runtime.InteropServices;

    namespace WindowsApplication1 {
      public partial class Form1 : Form, IMessageFilter {
        public Form1() {
          InitializeComponent();
          Application.AddMessageFilter(this);
        }

        public bool PreFilterMessage(ref Message m) {
          if (m.Msg == 0x20a) {
            // WM_MOUSEWHEEL, find the control at screen position m.LParam
            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) {
              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);
      }
    }

    Note that this code is active for all the forms in your application, not just the main form.
    Saturday, July 19, 2008 4:39 PM
    Moderator

All replies

  • Agreed, Windows' handling of WM_MOUSEWHEEL isn't very desirable.  That it works the way it does in IE and Outlook is probably more a side-effect of those programs not using controls.  Giving your own UI the same behavior requires redirecting the message from the window with the focus to the window under the mouse cursor.  This seemed to work well:

    using System;
    using System.ComponentModel;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Runtime.InteropServices;

    namespace WindowsApplication1 {
      public partial class Form1 : Form, IMessageFilter {
        public Form1() {
          InitializeComponent();
          Application.AddMessageFilter(this);
        }

        public bool PreFilterMessage(ref Message m) {
          if (m.Msg == 0x20a) {
            // WM_MOUSEWHEEL, find the control at screen position m.LParam
            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) {
              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);
      }
    }

    Note that this code is active for all the forms in your application, not just the main form.
    Saturday, July 19, 2008 4:39 PM
    Moderator
  • This code is wonderful, and very much saved my day a few months ago. However, 3 months later I discovered a critical bug with it. For multiple-monitor setups, it does not work on the second (or third, or fourth) monitor(s).

    This is the problematic line:

    Point pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16);


    Those coordinates are not correct on multiple-monitor setups. Once I discovered this, I was able to fix it easily by simply using System.Windows.Forms.Cursor.Position in place of LParam like this:

    public bool PreFilterMessage(ref Message m)
    {
        if (m.Msg == (int)WM.MOUSEWHEEL)
        {
            // WM_MOUSEWHEEL, find the control at screen position m.LParam
            var hWnd = NativeMethods.WindowFromPoint(Cursor.Position);
    
            if (hWnd != IntPtr.Zero && hWnd != m.HWnd && Control.FromHandle(hWnd) != null)
            {
                NativeMethods.SendMessage(hWnd, WM.MOUSEWHEEL, m.WParam, m.LParam);
                return true;
            }
        }
    
        return false;
    }

    That should do the trick. Happy coding. :)


    Tuesday, January 6, 2015 1:31 AM
  • Granted, I'm a bit new to C#, but I can't get this to work at all.

    I'm not sure how to declare WM.Mousewheel, but I'm sure 0x20a will work fine.

    The real problem is when I execute

    var hWnd = WindowFromPoint(Cursor.Position);

    I get a number which is not equal to the control's handle.

    I don't know how to get NativeMethods.WindowFromPoint, so I'm using the declarations in the first example.

    When I put a debug message in my code displaying hWnd, and the .Handle values of each control I try wheeling over, hWnd is never equal to either of them.

    Any ideas why I can't get it to work?  Probably something stupidly simple...


    Ron Mittelman

    Friday, February 13, 2015 12:45 AM