Родительский элемент управления Мышь Вход / выход из событий с помощью дочерних элементов управления - PullRequest
19 голосов
/ 21 июля 2009

У меня есть приложение WinForms на C # .NET 2.0. Мое приложение имеет элемент управления, который является контейнером для двух дочерних элементов управления: метки и некоторого элемента управления редактирования. Вы можете думать об этом так, где внешний блок является родительским элементом управления:

+---------------------------------+ 
| [Label Control]  [Edit Control] |
+---------------------------------+

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

Я пытался обработать MouseEnter и MouseLeave на родительском и обоих дочерних элементах управления, но это означает, что действие начинается и заканчивается несколько раз, когда мышь перемещается по элементу управления. Другими словами, я получаю это:

Parent.OnMouseEnter      (start doing something)
Parent.OnMouseLeave      (stop)
Child.OnMouseEnter       (start doing something)
Child.OnMouseLeave       (stop)
Parent.OnMouseEnter      (start doing something)
Parent.OnMouseLeave      (stop)

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

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

Есть ли способ сделать это внутри .NET Framework? Или мне нужно использовать крючок мыши для Windows?

Ответы [ 5 ]

8 голосов
/ 12 августа 2014

Мне кажется, я нашел гораздо лучшее решение, чем самое популярное в настоящее время решение.

Проблема с другими предлагаемыми решениями заключается в том, что они либо достаточно сложны (напрямую обрабатывают сообщения более низкого уровня).

Или они терпят неудачу в угловых случаях: полагаясь на положение мыши на MouseLeave, вы можете пропустить выход мыши, если мышь выходит из дочернего элемента управления прямо внутрь контейнера.

Хотя это решение не совсем элегантно, оно простое и работает:

Добавить прозрачный элемент управления, занимающий все пространство контейнера, для которого вы хотите получать события MouseEnter и MouseLeave.

В ответе Амеда я нашел хороший прозрачный элемент управления: Создание прозрачного элемента управления

Который я затем урезал до этого:

public class TranspCtrl : Control
{
    public TranspCtrl()
    {
        SetStyle(ControlStyles.SupportsTransparentBackColor, true);
        SetStyle(ControlStyles.Opaque, true);
        this.BackColor = Color.Transparent;
    }

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;
            cp.ExStyle = cp.ExStyle | 0x20;
            return cp;
        }
    }
}

Пример использования:

public class ChangeBackgroundOnMouseEnterAndLeave
{
    public Panel Container;
    public Label FirstLabel;
    public Label SecondLabel;

    public ChangeBackgroundOnMouseEnterAndLeave()
    {
        Container = new Panel();
        Container.Size = new Size(200, 60);

        FirstLabel = new Label();
        FirstLabel.Text = "First Label";
        FirstLabel.Top = 5;

        SecondLabel = new Label();
        SecondLabel.Text = "Second Lable";
        SecondLabel.Top = 30;

        FirstLabel.Parent = Container;
        SecondLabel.Parent = Container;

        Container.BackColor = Color.Teal;

        var transparentControl = new TranspCtrl();
        transparentControl.Size = Container.Size;

        transparentControl.MouseEnter += MouseEntered;
        transparentControl.MouseLeave += MouseLeft;

        transparentControl.Parent = Container;
        transparentControl.BringToFront();
    }

    void MouseLeft(object sender, EventArgs e)
    {
        Container.BackColor = Color.Teal;
    }

    void MouseEntered(object sender, EventArgs e)
    {
        Container.BackColor = Color.Pink;
    }
}

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

        var test = new ChangeBackgroundOnMouseEnterAndLeave();
        test.Container.Top = 20;
        test.Container.Left = 20;
        test.Container.Parent = this;
    }
}

Наслаждайтесь событиями MouseLeave и MouseEnter!

8 голосов
/ 28 июля 2009

После дополнительных исследований я обнаружил метод Application.AddMessageFilter . Используя это, я создал .NET-версию хука мыши:

class MouseMessageFilter : IMessageFilter, IDisposable
{
    public MouseMessageFilter()
    {
    }

    public void Dispose()
    {
        StopFiltering();
    }

    #region IMessageFilter Members

    public bool PreFilterMessage(ref Message m)
    {
         // Call the appropriate event
         return false;
    }

    #endregion

    #region Events

    public class CancelMouseEventArgs : MouseEventArgs
    {...}

    public delegate void CancelMouseEventHandler(object source, CancelMouseEventArgs e);
    public event CancelMouseEventHandler MouseMove;
    public event CancelMouseEventHandler MouseDown;
    public event CancelMouseEventHandler MouseUp;

    public void StartFiltering()
    {
        StopFiltering();
        Application.AddMessageFilter(this);
    }

    public void StopFiltering()
    {
        Application.RemoveMessageFilter(this);
    }
}

Затем я могу обработать событие MouseMove в моем элементе управления контейнером, проверить, находится ли мышь внутри моего родительского элемента управления, и начать работу. (Мне также пришлось отслеживать последний элемент родительского контроля, чтобы я мог остановить ранее запущенного родителя.)

---- Редактировать ----

В моем классе формы я создаю и подключаю фильтр:

public class MyForm : Form
{
   MouseMessageFilter msgFilter;

   public MyForm()
   {...
       msgFilter = new MouseMessageFilter();
       msgFilter.MouseDown += new MouseMessageFilter.CancelMouseEventHandler(msgFilter_MouseDown);
       msgFilter.MouseMove += new MouseMessageFilter.CancelMouseEventHandler(msgFilter_MouseMove);
    }

    private void msgFilter_MouseMove(object source, MouseMessageFilter.CancelMouseEventArgs e)
    {
        if (CheckSomething(e.Control)
            e.Cancel = true;
    }   
}
3 голосов
/ 21 июля 2009

Вы можете узнать, находится ли мышь в пределах вашего элемента управления следующим образом (при условии, что этот код находится в вашем элементе управления контейнера; если нет, замените this ссылкой на элемент управления контейнера):

private void MyControl_MouseLeave(object sender, EventArgs e)
{
    if (this.ClientRectangle.Contains(this.PointToClient(Cursor.Position)))
    {
        // the mouse is inside the control bounds
    }
    else
    {
        // the mouse is outside the control bounds
    }
}
2 голосов
/ 21 июля 2009

Я не думаю, что вам нужно подключить насос сообщений, чтобы решить эту проблему. Некоторые пометки в вашем интерфейсе должны помочь. Я думаю, что вы создаете переменную-член, что-то вроде Control _someParent, в вашем управляющем классе, которая будет брать ссылку на родительский элемент управления при вызове одного из ваших обработчиков OnMouseEnter. Затем в OnMouseLeave проверьте значение флага _someParent и, если он совпадает с текущим отправителем, фактически не прекращайте обработку, просто вернитесь. Только когда родитель отличается, вы останавливаете и сбрасываете _someParent на ноль.

1 голос
/ 07 марта 2018

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

Для инициализации вы передаете интересующий элемент управления контейнера в конструктор ContainerMessageFilter. Класс собирает дескрипторы окна контейнера и всех дочерних элементов управления в нем.

Затем во время работы класс фильтрует сообщение WM_MOUSEMOVE, проверяя сообщения HWnd, чтобы определить, по какому элементу управления движется мышь. Таким образом, он определяет, когда мышь переместилась в пределах или вне набора элементов управления в контейнере, который она наблюдает.

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

public class ContainerMessageFilter : IMessageFilter {
    private const int WM_MOUSEMOVE = 0x0200;

    public event EventHandler MouseEnter;
    public event EventHandler MouseLeave;

    private bool insideContainer;
    private readonly IEnumerable<IntPtr> handles;

    public ContainerMessageFilter( Control container ) {
        handles = CollectContainerHandles( container );
    }

    private static IEnumerable<IntPtr> CollectContainerHandles( Control container ) {
        var handles = new List<IntPtr> { container.Handle };

        RecurseControls( container.Controls, handles );

        return handles;
    }

    private static void RecurseControls( IEnumerable controls, List<IntPtr> handles ) {
        foreach ( Control control in controls ) {
            handles.Add( control.Handle );

            RecurseControls( control.Controls, handles );
        }
    }

    public bool PreFilterMessage( ref Message m ) {
        if ( m.Msg == WM_MOUSEMOVE ) {
            if ( handles.Contains( m.HWnd ) ) {
                // Mouse is inside container
                if ( !insideContainer ) {
                    // was out, now in
                    insideContainer = true;
                    OnMouseEnter( EventArgs.Empty );
                }
            }
            else {
                // Mouse is outside container
                if ( insideContainer ) {
                    // was in, now out
                    insideContainer = false;
                    OnMouseLeave( EventArgs.Empty );
                }
            }
        }

        return false;
    }

    protected virtual void OnMouseEnter( EventArgs e ) {
        var handler = MouseEnter;
        handler?.Invoke( this, e );
    }

    protected virtual void OnMouseLeave( EventArgs e ) {
        var handler = MouseLeave;
        handler?.Invoke( this, e );
    }
}

В следующем примере использования мы хотим отслеживать вход и выход мыши для Panel и дочерних элементов управления, которые он содержит:

public partial class Form1 : Form {
    private readonly ContainerMessageFilter containerMessageFilter;

    public Form1() {
        InitializeComponent();

        containerMessageFilter = new ContainerMessageFilter( panel1 );
        containerMessageFilter.MouseEnter += ContainerMessageFilter_MouseEnter;
        containerMessageFilter.MouseLeave += ContainerMessageFilter_MouseLeave;
        Application.AddMessageFilter( containerMessageFilter );
    }

    private static void ContainerMessageFilter_MouseLeave( object sender, EventArgs e ) {
        Console.WriteLine( "Leave" );
    }

    private static void ContainerMessageFilter_MouseEnter( object sender, EventArgs e ) {
        Console.WriteLine( "Enter" );
    }

    private void Form1_FormClosed( object sender, FormClosedEventArgs e ) {
        Application.RemoveMessageFilter( containerMessageFilter );
    }
}
...