Parent Control Mouse Enter/Leave Events With Child Controls

前端 未结 5 1034
天涯浪人
天涯浪人 2020-11-27 18:49

I have a C# .NET 2.0 WinForms app. My app has a control that is a container for two child controls: a label, and some kind of edit control. You can think of it like this,

相关标签:
5条回答
  • 2020-11-27 19:01

    You can find out whether the mouse is within the bounds of your control like this (assuming this code resides in your container control; if not, replace this with a reference to the container control):

    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
        }
    }
    
    0 讨论(0)
  • 2020-11-27 19:01

    I had the exact same need. Paul Williams' answer provided me with the core idea, but I had difficulty understanding the code. I found another take here, and together, the two examples helped me develop my own version.

    To initialize, you pass the container control of interest into the ContainerMessageFilter constructor. The class collects the window handles of the container and all child controls within it.

    Then, during operation, the class filters the WM_MOUSEMOVE message, checking the messages's HWnd to determine what control the mouse is moving within. In this way, it determines when the mouse has moved within or outside the set of controls within the container that it is watching.

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

    In the following usage example, we want to monitor mouse entry and exit for a Panel and the child controls that it contains:

    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 );
        }
    }
    
    0 讨论(0)
  • 2020-11-27 19:06

    I feel I found a much better solution than the currently top accepted solution.

    The problem with other proposed solutions is that they are either fairly complex (directly handling lower level messages).

    Or they fail corner cases: relying on the mouse position on MouseLeave can cause you to miss the mouse exiting if the mouse goes straight from inside a child control to outside the container.

    While this solution isn't entirely elegant, it is straightforward and works:

    Add a transparent control that takes up the entire space of the container that you want to receive MouseEnter and MouseLeave events for.

    I found a good transparent control in Amed's answer here: Making a control transparent

    Which I then stripped down to this:

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

    Example usage:

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

    Enjoy proper MouseLeave and MouseEnter events!

    0 讨论(0)
  • 2020-11-27 19:16

    After more research, I discovered the Application.AddMessageFilter method. Using this, I created a .NET version of a mouse hook:

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

    Then, I can handle the MouseMove event in my container control, check to see if the mouse is inside my parent control, and start the work. (I also had to track the last moused over parent control so I could stop the previously started parent.)

    ---- Edit ----

    In my form class, I create and hookup the filter:

    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;
        }   
    }
    
    0 讨论(0)
  • 2020-11-27 19:16

    i don't think you need to hook the message pump to solve this. Some flagging in your UI should do the trick. i'm thinking that you create a member variable, something like Control _someParent, in your controlling class which will take the reference of the parent control when one of your OnMouseEnter handlers is called. Then, in OnMouseLeave, check the value of the _someParent "flag" and if it's the same as the current sender's then do not actually stop your processing, just return. Only when the parent is different do you stop and reset _someParent to null.

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