views:

2406

answers:

4

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, where the outer box is the parent control:

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

I am trying to do something when the mouse enters or leaves the parent control, but I don't care if the mouse moves into one of its children. I want a single flag to represent "the mouse is somewhere inside the parent or children" and "the mouse has moved outside of the parent control bounds".

I've tried handling MouseEnter and MouseLeave on the parent and both child controls, but this means the action begins and ends multiple times as the mouse moves across the control. In other words, I get this:

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

The intermediate OnMouseLeave events cause some undesired effects as whatever I'm doing gets started and then stopped. I want to avoid that.

I don't want to capture the mouse as the parent gets the mouse over, because the child controls need their mouse events, and I want menu and other shortcut keys to work.

Is there a way to do this inside the .NET framework? Or do I need to use a Windows mouse hook?

A: 

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.

Paul Sasik
If I leave the current parent, I don't know in advance I'm going into a child control or really leaving the parent. And if I did as you suggest, the parent OnMouseLeave would have to say "hey, it's me, so continue processing", so moving outside of the control and not into another control would not stop the processing.
Paul Williams
That's a good point though if your control containment structure doesn't have to be too flexible i.e. n-level containment and/or you take into account the parent type then you could still use this method.For example, you get an enter event, then you take the control it came from and walk up the containment tree until you see a GroupBox or Panel (whatever is containing your buttons) and evaluate on the reference to that value. When it changes.
Paul Sasik
A: 

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
    }
}
Fredrik Mörk
Interesting idea, but it seems not to work in practice. Sometimes the last set of events for a given parent control are Child.MouseEnter and Child.MouseLeave. In my Child.MouseLeave handler, sometimes the Cursor.Position is still in the bounds of the parent. The code thinks the cursor is still inside, so the "mouse is inside the parent" action never gets stopped.
Paul Williams
+1  A: 

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;
    }   
}
Paul Williams
Can you add an example usage of this class? I'm not seeing exactly how you'd hook this up.
Chris Marasti-Georg
I added a sample usage as you requested. Hope it helps!
Paul Williams
A: 

in addition to your question, what if i want this order of events without filtering them:

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