UserControl: How to add MouseWheel Listener?

后端 未结 4 1532
既然无缘
既然无缘 2021-01-21 01:34

I\'m creating an UserControl that should react if the mouse is over the control and MouseWheel gets rotated.

Currently i\'m doing this as shown here:

            


        
相关标签:
4条回答
  • 2021-01-21 02:20

    having the same problem, I finally implemented a mix of both solution by @Paul_Westcott and @nr1. This is a local solution like solution of @Paul_Westcott (only applies to the winforms control that subscribes to). It is multiple monitor safe and MDI safe (overlapping by other windows within the application)

    public static class MouseWheelHandlerForWinformsControl
    {
        private class MouseWheelMessageFilter : IMessageFilter
        {
            [DllImport("user32.dll")]
            private static extern IntPtr WindowFromPoint(Point pt);
    
            private readonly Control mCtrl;
            private readonly Action<MouseEventArgs> mOnMouseWheel;
    
            public MouseWheelMessageFilter(Control ctrl, Action<MouseEventArgs> onMouseWheel)
            {
                mCtrl = ctrl;
                mOnMouseWheel = onMouseWheel;
            }
    
            public bool PreFilterMessage(ref Message m)
            {
                // handle only mouse wheel messages
                if (m.Msg != 0x20a)
                    return false;
    
                Point mouseAbsolutePosition = new Point(m.LParam.ToInt32());
                Point mouseRelativePosition = mCtrl.PointToClient(mouseAbsolutePosition);
    
                IntPtr hControlUnderMouse = WindowFromPoint(mouseAbsolutePosition);
                Control controlUnderMouse = Control.FromHandle(hControlUnderMouse);
    
                if (controlUnderMouse != mCtrl)
                    return false;
    
                MouseButtons buttons = GetMouseButtons(m.WParam.ToInt32());
                int delta = m.WParam.ToInt32() >> 16;
    
                var e = new MouseEventArgs(buttons, 0, mouseRelativePosition.X, mouseRelativePosition.Y, delta);
    
                mOnMouseWheel(e);
    
                return true;
            }
    
            private static MouseButtons GetMouseButtons(int wParam)
            {
                MouseButtons buttons = MouseButtons.None;
    
                if(HasFlag(wParam, 0x0001)) buttons |= MouseButtons.Left;
                if(HasFlag(wParam, 0x0010)) buttons |= MouseButtons.Middle;
                if(HasFlag(wParam, 0x0002)) buttons |= MouseButtons.Right;
                if(HasFlag(wParam, 0x0020)) buttons |= MouseButtons.XButton1;
                if(HasFlag(wParam, 0x0040)) buttons |= MouseButtons.XButton2;
    
                return buttons;
            }
    
            private static bool HasFlag(int input, int flag)
            {
                return (input & flag) == flag;
            }
        }
    
        public static void MemorySafeAdd(Control ctrl, Action<MouseEventArgs> onMouseWheel)
        {
            if (ctrl == null || onMouseWheel == null)
                throw new ArgumentNullException();
    
            var filter = new MouseWheelMessageFilter(ctrl, onMouseWheel);
            Application.AddMessageFilter(filter);
            ctrl.Disposed += (s, e) => Application.RemoveMessageFilter(filter);
        }
    }
    

    Once you have added this helper class into your solution, you subscribe a control myControl to the mouse-wheel, in one line, as following:

    public void Init() {
        MouseWheelHandlerForWinformsControl.MemorySafeAdd(myControl, OnMouseWheelEvent);
    }
    
    void OnMouseWheelEvent(MouseEventArgs args) {
        // do what you need here
    }
    
    0 讨论(0)
  • 2021-01-21 02:20

    Just a slight variation on the solution that has already been published, in order to not have to use P/Invoke.

    public static class MouseWheelHandler
    {
        public static void Add(Control ctrl, Action<MouseEventArgs> onMouseWheel)
        {
            if (ctrl == null || onMouseWheel == null)
                throw new ArgumentNullException();
    
            var filter = new MouseWheelMessageFilter(ctrl, onMouseWheel);
            Application.AddMessageFilter(filter);
            ctrl.Disposed += (s, e) => Application.RemoveMessageFilter(filter);
        }
    
        class MouseWheelMessageFilter
            : IMessageFilter
        {
            private readonly Control _ctrl;
            private readonly Action<MouseEventArgs> _onMouseWheel;
    
            public MouseWheelMessageFilter(Control ctrl, Action<MouseEventArgs> onMouseWheel)
            {
                _ctrl = ctrl;
                _onMouseWheel = onMouseWheel;
            }
    
            public bool PreFilterMessage(ref Message m)
            {
                var parent = _ctrl.Parent;
                if (parent != null && m.Msg == 0x20a) // WM_MOUSEWHEEL, find the control at screen position m.LParam
                {
                    var pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16);
    
                    var clientPos = _ctrl.PointToClient(pos);
    
                    if (_ctrl.ClientRectangle.Contains(clientPos)
                     && ReferenceEquals(_ctrl, parent.GetChildAtPoint(parent.PointToClient(pos))))
                    {
                        var wParam = m.WParam.ToInt32();
                        Func<int, MouseButtons, MouseButtons> getButton =
                            (flag, button) => ((wParam & flag) == flag) ? button : MouseButtons.None;
    
                        var buttons = getButton(wParam & 0x0001, MouseButtons.Left)
                                    | getButton(wParam & 0x0010, MouseButtons.Middle)
                                    | getButton(wParam & 0x0002, MouseButtons.Right)
                                    | getButton(wParam & 0x0020, MouseButtons.XButton1)
                                    | getButton(wParam & 0x0040, MouseButtons.XButton2)
                                    ; // Not matching for these /*MK_SHIFT=0x0004;MK_CONTROL=0x0008*/
    
                        var delta = wParam >> 16;
                        var e = new MouseEventArgs(buttons, 0, clientPos.X, clientPos.Y, delta);
                        _onMouseWheel(e);
    
                        return true;
                    }
                }
                return false;
            }
        }
    }
    

    And then this can used from individual controls like

    class MyControl
        : Control
    {
        public MyControl()
        {
            ...
            MouseWheelHandler.Add(this, MyOnMouseWheel);
        }
    
        void MyOnMouseWheel(MouseEventArgs e)
        {
            ...
        }
    }
    
    0 讨论(0)
  • 2021-01-21 02:23

    Paul Westcott's answer works great when using a FlowLayoutPanel. My implementation of MyOnMouseWheel event is:

    void MyOnMouseWheel(MouseEventArgs e)
    {
        int ChangeIncrement = (this.panel1.VerticalScroll.SmallChange * 4); //Change the 4 to any positive number to scroll more or less one each scroll event.
        if (e.Delta < 0)
        {
            int NewValue = this.panel1.VerticalScroll.Value + ChangeIncrement;
            if (NewValue > this.panel1.VerticalScroll.Maximum)
            {
                this.panel1.VerticalScroll.Value = this.panel1.VerticalScroll.Maximum;
            }
            else
            {
                this.panel1.VerticalScroll.Value = NewValue;
            }
        }
        else if (e.Delta > 0)
        {
            int NewValue = this.panel1.VerticalScroll.Value - ChangeIncrement;
            if (NewValue < this.panel1.VerticalScroll.Minimum)
            {
                this.panel1.VerticalScroll.Value = this.panel1.VerticalScroll.Minimum;
            }
            else
            {
                this.panel1.VerticalScroll.Value = NewValue;
            }
        }
        this.panel1.PerformLayout();
    }
    
    0 讨论(0)
  • 2021-01-21 02:37

    Copy pasted from http://social.msdn.microsoft.com/forums/en-US/winforms/thread/eb922ed2-1036-41ca-bd15-49daed7b637c/

    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);
      }
    }
    

    this works great, thx Hans Passant

    0 讨论(0)
提交回复
热议问题