Событие колеса мыши (C#)

Я не могу получить событие колеса мыши в основной форме.

В качестве демонстрации я придумал простой пример:

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

Я получаю событие колеса мыши в Form2, но не в Form1, есть идеи?

Ваше здоровье,

Джеймс


person Community    schedule 26.01.2009    source источник
comment
Вечное разочарование колесика мыши заключается в том, что Microsoft решила относиться к нему больше как к событию клавиатуры, чем как к событию мыши, поэтому сообщения колесика мыши передаются в элемент управления с фокусом клавиатуры, заставляя почти каждое приложение, использующее колесико мыши, выполнять некоторые действия. своего рода обходной путь.   -  person Qwertie    schedule 12.06.2017


Ответы (6)


Я подозреваю, что OP хочет получать события прокрутки, когда мышь зависает над панелью, даже если панель не имеет фокуса.

Способ достижения такого поведения объясняется здесь:

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

и тут:

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

Один из фрагментов кода, взятых из связанного форума:

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

Этот код в основном перехватывает все события wm_mousewheel и перенаправляет их на элемент управления, над которым в данный момент находится мышь. Панель больше не должна иметь фокус, чтобы получать события колеса.

person nitrogenycs    schedule 04.04.2012
comment
Это должно быть отмечено ответом. Он работает во всех сценариях и может быть легко изменен (как я сделал для своих личных нужд). - person Martin Bliss; 07.08.2012
comment
не должно ли это также удалить фильтр сообщений при уничтожении формы? - person Sam; 10.05.2014
comment
Этот код не работает, когда мышь находится на экране с отрицательными координатами, т. е. если дополнительный экран находится слева от вашего основного экрана. - person Bryce Wagner; 29.05.2015
comment
m.LParam.ToInt32() и 0xffff должны быть (short)(ushort)(uint)(int)(long)m.LParam и m.LParam.ToInt32() ›› 16 должно быть (short)(ushort)(( (uint)(int)(long)m.LParam) ›› 16) - person Bryce Wagner; 29.05.2015
comment
Возможно, некоторые из этих приведений не нужны :-) Но это должно предотвратить любые исключения приведения типов. (int)(long) преобразует 64-битный IntPtr в int, (uint) заставит его выполнять сдвиги без знака (вместо сдвигов со знаком), (ushort) для извлечения младших 16 бит и (short) для преобразования его в значение со знаком. - person Bryce Wagner; 29.05.2015
comment
@BryceWagner Я наткнулся на этот ответ, который указывает, что теперь вы можете просто передать m.LParam.ToInt32() в Укажите конструктор и получите ожидаемый результат — проверено даже на нестандартной конфигурации с несколькими мониторами! Гораздо чище, имхо - person glancep; 28.10.2015
comment
@glancep Извините, может быть, я не понял, где происходит исключение. Это происходит только в 64-битном режиме. Если у вас есть 64-битный IntPtr со значениями от int.MinValue до int.MaxValue, IntPtr.ToInt32() вызовет исключение переполнения. - person Bryce Wagner; 28.10.2015
comment
Тем не менее, это лучший конструктор. new Point((int)(long)m.LParam) определенно намного чище, чем беспорядок, который я написал выше. - person Bryce Wagner; 28.10.2015
comment
Да, это довольно глупо. Из документации Control.MouseWheel: При обработке события MouseWheel важно следовать стандартам пользовательского интерфейса (UI), связанным с колесиком мыши. Правильно ли я думаю, что это стандарт пользовательского интерфейса, когда колесо прокручивает элемент управления под курсором мыши? Если это так, вызывает недоумение тот факт, что Winforms запрограммирован на нарушение этого стандарта. - person Stewart; 28.11.2017

Ваша проблема возникает из-за того, что form1 имеет фокус, а не панель1. ... что, конечно же, означает, что будут запущены события form1, а не события panel1.

Я воссоздал ваш сценарий со следующими изменениями в конструкторе в Form1 и убедился, что он запускает событие колеса прокрутки.

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);
    }
}
person Presidenten    schedule 26.01.2009

Добавьте еще одно событие панели MouseEnter и в его функции обратного вызова получите фокус ввода:

void MouseEnterEvent()
{
   this.Panel.Focus();
}
person Community    schedule 05.03.2009

Благодаря ответу @nitrogenycs я написал простой универсальный класс, чтобы легко решить проблему:

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

  }
}

Оттуда вам нужно только наследовать форму от этого класса вместо формы для каждой формы, для которой вам нужно управлять MouseWheel:

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

  }
}

Надеюсь, это поможет кому-то другому (кроме меня).

person STremblay    schedule 05.06.2014
comment
к вашему сведению, вы можете использовать this.Contains вместо IsChild. - person colin lamarre; 21.01.2015

Сама панель не может иметь фокус, только элемент, помещенный внутри панели, может иметь фокус. Панель получит событие MouseWheel только после того, как что-то будет помещено внутрь нее и эта вещь получит фокус. Простое наведение курсора на панель и перемещение колесика мыши отправит событие в форму, а не на панель.

В этом разница между двумя вашими примерами.

person Sam Meldrum    schedule 26.01.2009
comment
Не правда. В приложении, которое я пишу, у меня есть панель, которая является моим графическим пространством для отображения. Хотя вы не можете сфокусировать панель щелчком мыши, вы можете сказать (в коде) ‹code›myPanel.Focus()‹/code›, чтобы панель отвлекла внимание от всего остального, чтобы мой код отображения работал. . - person Jerry; 26.01.2009
comment
@ Джерри - я проверил это, и мой тест показывает, что я прав. Если у вас ничего нет на панели, даже вызов myPanel.Focus() не изменит направление событий MouseWheel. Я могу получать события MouseWheel только тогда, когда что-то на панели имеет фокус. - person Sam Meldrum; 27.01.2009

Может быть, это сработает для вас?

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);
    }
}
person Community    schedule 26.01.2009