Mouse Wheel Event (C#)
Asked Answered
A

7

30

I can't get the Mouse Wheel event in the main form.

As a demo I came up with a simple example:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        this.panel1.MouseWheel += new MouseEventHandler(panel1_MouseWheel);
        this.panel1.MouseMove += new MouseEventHandler(panel1_MouseWheel);

        Form2 f2 = new Form2();
        f2.Show(this);
    }

    private void panel1_MouseWheel(object sender, MouseEventArgs e)
    {
        if(e.Delta != 0)
        Console.Out.WriteLine(e.Delta);
    }
}

public partial class Form2 : Form
{
    public Form2()
    {
        InitializeComponent();

        this.MouseMove += new MouseEventHandler(Form2_MouseMove);
        this.MouseWheel += new MouseEventHandler(Form2_MouseMove);
    }

    private void Form2_MouseMove(object sender, MouseEventArgs e)
    {
        if(e.Delta != 0)
            Console.Out.WriteLine(e.Delta);
    }
}

I get the mouse wheel event in Form2 but not Form1 any ideas?

Cheers,

James

Animism answered 26/1, 2009 at 10:3 Comment(1)
The eternal frustration of the mouse wheel is that Microsoft decided to treat it more like a keyboard event than like a mouse event, so mouse wheel messages go to the control with the keyboard focus, forcing almost every app that uses the mouse wheel to do some kind of workaround.Standardbearer
C
44

I suspect the OP wants to get scroll events when just the mouse is hovering over the panel even though the panel does not have the focus.

A way to accomplish this behaviour is explained here:

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

and here:

http://social.msdn.microsoft.com/forums/en-US/winforms/thread/6bfb9287-986d-4c60-bbcc-23486e239384/

One of the code snippets taken from the linked forum:

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 code will basically intercept all wm_mousewheel events and redirect them to the control the mouse is currently hovering over. The panel doesn't need to have the focus anymore to receive the wheel events.

Cateyed answered 4/4, 2012 at 7:42 Comment(9)
This should be marked the answer. It works in all scenarios, and can easily be tweaked (as I have done for my personal needs.)Tasimeter
should this not also remove the message filter when the form is destroyed?Escuage
This code is broken when the mouse is on a screen with negative coordinates, i.e. if a secondary screen is to the left of your primary screen.Parting
"m.LParam.ToInt32() & 0xffff" should be "(short)(ushort)(uint)(int)(long)m.LParam" and "m.LParam.ToInt32() >> 16" should be "(short)(ushort)(((uint)(int)(long)m.LParam) >> 16)"Parting
Maybe some of those casts aren't necessary :-) But that should prevent any casting exceptions. (int)(long) will downcast from a 64 bit IntPtr to int, (uint) will make it perform unsigned shifts (instead of signed shifts), (ushort) to extract the lower 16 bits, and (short) to convert it to a signed value.Parting
@BryceWagner I came across this answer that points out that now you can simply pass the m.LParam.ToInt32() to the Point constructor and get the expected result--tested even on odd-layout multi-monitor setup! Much cleaner, IMOClockwork
@Clockwork Sorry, maybe I wasn't clear where the exception was happening. It only occurs in 64 bit mode. If you have a 64 bit IntPtr with values outside int.MinValue to int.MaxValue, IntPtr.ToInt32() will throw an overflow exception.Parting
However, that is a better constructor. new Point((int)(long)m.LParam) is definitely much cleaner than the mess I posted above.Parting
Yes, it is rather stupid. From the Control.MouseWheel documentation: "When handling the MouseWheel event it is important to follow the user interface (UI) standards associated with the mouse wheel." Am I right in thinking it's a UI standard that the wheel scrolls the control under the mouse cursor? If so, it's puzzling that Winforms is programmed to violate this standard.Wollongong
A
20

Your problem arises from the fact that form1 has focus, not panel1. ...which ofcourse means that it is form1's events that will be fired, not panel1's events.

I recreated your scenario with the following changes to the constructor in Form1 and verified that it fires the scroll wheel event.

public Form1()
{
        InitializeComponent(); 

        /*  --- Old code that don't work ---
            this.panel1.MouseWheel += new MouseEventHandler(panel1_MouseWheel);
            this.panel1.MouseMove += new MouseEventHandler(panel1_MouseWheel);
        */

        this.MouseWheel += new MouseEventHandler(panel1_MouseWheel);
        this.MouseMove += new MouseEventHandler(panel1_MouseWheel);

        Form2 f2 = new Form2();
        f2.Show(this);
    }
}
Alveolus answered 26/1, 2009 at 10:31 Comment(0)
P
13

Add another event of panel MouseEnter and in its Callback function get the input focus:

void MouseEnterEvent()
{
   this.Panel.Focus();
}
Phemia answered 5/3, 2009 at 7:13 Comment(0)
T
4

Thanks to @nitrogenycs 's answer, I've wrote a simple generic class to easily address the issue:

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

namespace MyNamespace
{
  public class MouseWheelManagedForm : Form, IMessageFilter
  {
    private bool managed;

    public MouseWheelManagedForm () : this (true) {
   }

    public MouseWheelManagedForm (bool start) {
      managed = false;
      if (start)
        ManagedMouseWheelStart();
    }

    protected override void Dispose (bool disposing) {
      if (disposing)
        ManagedMouseWheelStop();
      base.Dispose(disposing);
    }

    /************************************
     * IMessageFilter implementation
     * *********************************/
    private const int WM_MOUSEWHEEL = 0x20a;
    // 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);

    private bool IsChild (Control ctrl) {
      Control loopCtrl = ctrl;

      while (loopCtrl != null && loopCtrl != this)
        loopCtrl = loopCtrl.Parent;

      return (loopCtrl == this);
    }

    public bool PreFilterMessage (ref Message m) {
      if (m.Msg == WM_MOUSEWHEEL) {
        //Ensure the message was sent to a child of the current form
        if (IsChild(Control.FromHandle(m.HWnd))) {
          // Find the control at screen position m.LParam
          Point pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16);

          //Ensure control under the mouse is valid and is not the target control
          //otherwise we'd be trap in a loop.
          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;
    }

    /****************************************
     * MouseWheelManagedForm specific methods
     * **************************************/
    public void ManagedMouseWheelStart () {
      if (!managed) {
        managed = true;
        Application.AddMessageFilter(this);
      }
    }

    public void ManagedMouseWheelStop () {
      if (managed) {
        managed = false;
        Application.RemoveMessageFilter(this);
      }
    }

  }
}

From there, you only need to inherit your Form from this class instead of Form for every form you need the MouseWheel to be "managed":

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Windows.Forms;

namespace MyApp
{
  public partial class MyForm : MyNamespace.MouseWheelManagedForm
  {
    public MyForm ()
    {
      InitializeComponent();
    }

  }
}

Hope this will help someone else (than me).

Thalamencephalon answered 5/6, 2014 at 18:9 Comment(1)
fyi you can use this.Contains instead of IsChild.Kistna
B
0

Maybe this will work for you?

public partial class Form1 : Form {
    public Form1() {
        InitializeComponent();
        Form2 f2 = new Form2();
        f2.MouseWheel += new MouseEventHandler(panel1_MouseWheel);
        f2.MouseMove += new MouseEventHandler(panel1_MouseWheel);
        f2.Show(this);
    }

    private void panel1_MouseWheel(object sender, MouseEventArgs e)
    {
        if(e.Delta != 0) Console.Out.WriteLine(e.Delta);
    }
}
Begga answered 26/1, 2009 at 10:13 Comment(0)
A
0

The Panel can't have the focus itself, only an item placed inside the panel can have the focus. The panel will only receive the MouseWheel event once something is placed inside it and that thing has the focus. Simply hoevering over the panel and moving the mouse wheel will send the event to the form, not to the panel.

This is the difference between your two examples.

Auditor answered 26/1, 2009 at 10:31 Comment(2)
Not true. In an app I'm wriitng, I have a panel that is my graphical display space. While you can't click-focus the panel, you can say (in the code) <code>myPanel.Focus()</code> so that the panel takes the focus away from everything else in order for my display code to work.Col
@Col - I have tested this, and my test shows I am right. If you have nothing on the panel, even calling myPanel.Focus() will not change where the MouseWheel events go. I can only get MouseWheel events when something on the panel has focus.Auditor
A
-2
this.MouseWheel += pictureBox1_MouseWheel; //tanımlama
void pictureBox1_MouseWheel(object sender, MouseEventArgs e)
            {
                if (Convert.ToString(e.Delta) == "120")
                {
                    //yukarı
                }
                else if (Convert.ToString(e.Delta) == "-120")
                {
                    //aşağı
                }
            }
Amarette answered 14/8, 2017 at 20:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.