Parent Control Mouse Enter/Leave Events With Child Controls
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;
}
}
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!
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
}
}
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.