Подтвердить что ты не робот

Мышь родительского контроля Ввод/выключение событий с помощью детского контроля

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

+---------------------------------+ 
| [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? Или мне нужно использовать крючок для мыши Windows?

4b9b3361

Ответ 1

После дополнительных исследований я обнаружил метод 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 в элементе управления контейнера, проверить, находится ли мышь внутри моего родительского элемента управления и начать работу. (Я также должен был отслеживать последний moused над родительским контролем, чтобы я мог остановить ранее начатый родитель.)

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

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

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

Ответ 2

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

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

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

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

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

Я нашел хороший прозрачный контроль в ответе Amed здесь: Создание прозрачного контроля

Что я тогда разделил до этого:

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!

Ответ 3

Вы можете узнать, находится ли мышь в пределах вашего элемента управления (при условии, что этот код находится в вашем контейнере, а если нет, замените 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
    }
}

Ответ 4

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