Windows Develop Bookmark and Share   
 index > Windows Forms General > Mouse Leaving/Entering Bounds of Panel Covered in Other Controls?
 

Mouse Leaving/Entering Bounds of Panel Covered in Other Controls?

I have a panel on a form that is completely covered in other controls (and it isn't determined until runtime which and where those controls are on the panel). There is absolutely 0 space of the panel which is exposed.

I need to find out when the mouse enters and leaves the bounds of this control. The MouseEnter and MouseLeave events don't work because for those to fire, the mouse has to be directly over the panel (and instead it is directly over the panel's controls).

I've printed out the WndProc messages, and I noticed that sometimes WM_NOTIFY or WM_SETCURSOR messages are fired, but it is sporadic. It seems like if I move the mouse fast, those events don't ever get fired.

Does anyone have a tip for how to handle something like this?
BlueMikey  Tuesday, October 31, 2006 5:30 PM
I've been playing with IMessageFilter as of late. It seemed perfect to solve this problem. First, we need to subclass the Panel so we can generate the MouseEnter and MouseLeave events. I also put the message processing code in it:

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

class MyPanel : Panel {
private bool mHasMouse;
public bool HasMouse {
// Helper property to generate the MouseEnter/Leave events
get { return mHasMouse; }
set {
if (mHasMouse == value) return;
mHasMouse = value;
if (value) OnMouseEnter(new EventArgs());
else OnMouseLeave(new EventArgs());
}
}
[DllImport("user32.dll")]
private static extern bool ClientToScreen(IntPtr hWnd, ref Point p);

public static void TrapMouse(Form f, ref Message m) {
if (m.Msg != 0x200 && m.Msg != 0x2A1) return; // Only want WM_MOUSEMOVE and WM_MOUSEHOVER
// Map mouse position to screen coordinates
Point pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16);
ClientToScreen(m.HWnd, ref pos);
// Search panel at mouse position
SearchPanel(f.Controls, pos);
}
private static void SearchPanel(ControlCollection ctls, Point pos) {
// Search <ctls> for a MyPanel instance
foreach (Control ctl in ctls) {
if (ctl is MyPanel) {
MyPanel pnl = (MyPanel)ctl;
Point ppos = pnl.PointToClient(pos);
pnl.HasMouse = ppos.X >= 0 && ppos.Y < pnl.Width && ppos.Y >= 0 && ppos.Y < pnl.Height;
}
// Recurse to find panels on a container
if (ctl.Controls.Count > 0) SearchPanel(ctl.Controls, pos);
}
}
}

The TrapMouse method implements the logic of handling the mouse messages and finding the panel that the mouse might be on. We need to call it from the form that hosts the panel(s). Build your project and drop a MyPanel control on the form. Cover it with other controls as necessary, leave a bit of space so you can see the results. Here's the form code:

using System;
using System.Drawing;
using System.Windows.Forms;

namespace WindowsApplication1 {
public partial class Form1 : Form, IMessageFilter {
public Form1() {
InitializeComponent();
// Install message filter
Application.AddMessageFilter(this);
// Install event handlers
this.FormClosed += Form1_FormClosed;
myPanel1.MouseLeave += myPanel1_MouseLeave;
myPanel1.MouseEnter += myPanel1_MouseEnter;
myPanel1.BackColor = Color.Black;
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e) {
// Unregister message filter
Application.RemoveMessageFilter(this);
}
public bool PreFilterMessage(ref Message m) {
// Trap mouse messages
MyPanel.TrapMouse(this, ref m);
return false;
}
private void myPanel1_MouseLeave(object sender, EventArgs e) {
// Demo, black background when mouse leaves panel
myPanel1.BackColor = Color.Black;
}
private void myPanel1_MouseEnter(object sender, EventArgs e) {
// Demo, white background when mouse enters panel
myPanel1.BackColor = Color.White;
}
}
}


nobugz  Friday, November 03, 2006 8:27 PM
You are seeing controls notifying their container. It depends on the type of control what kind of notifications are sent. A button generates WM_GETTEXT and WM_PRINTCLIENT, a ListBox generates WM_NOTIFY and WM_SETCURSOR when you're on its border. A CheckBox generates nothing at all.

You'll need the panel's controls to help out telling the panel what's going on. This is probably most easily done with a UserControl, not a panel...
nobugz  Wednesday, November 01, 2006 12:34 PM
I was afraid that might be the only solution. The problem with that is that our dynamic controls have tons of controls on them as well. I'd have this tree traversal thing going on adding and removing handlers all over the place.

We ended up solving it right now through mouse hooks instead. I don't know if that is the best practice, but it works at least for the time being. I guess I just wished that MouseLeave worked a bit differently.
BlueMikey  Friday, November 03, 2006 5:39 PM
I've been playing with IMessageFilter as of late. It seemed perfect to solve this problem. First, we need to subclass the Panel so we can generate the MouseEnter and MouseLeave events. I also put the message processing code in it:

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

class MyPanel : Panel {
private bool mHasMouse;
public bool HasMouse {
// Helper property to generate the MouseEnter/Leave events
get { return mHasMouse; }
set {
if (mHasMouse == value) return;
mHasMouse = value;
if (value) OnMouseEnter(new EventArgs());
else OnMouseLeave(new EventArgs());
}
}
[DllImport("user32.dll")]
private static extern bool ClientToScreen(IntPtr hWnd, ref Point p);

public static void TrapMouse(Form f, ref Message m) {
if (m.Msg != 0x200 && m.Msg != 0x2A1) return; // Only want WM_MOUSEMOVE and WM_MOUSEHOVER
// Map mouse position to screen coordinates
Point pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16);
ClientToScreen(m.HWnd, ref pos);
// Search panel at mouse position
SearchPanel(f.Controls, pos);
}
private static void SearchPanel(ControlCollection ctls, Point pos) {
// Search <ctls> for a MyPanel instance
foreach (Control ctl in ctls) {
if (ctl is MyPanel) {
MyPanel pnl = (MyPanel)ctl;
Point ppos = pnl.PointToClient(pos);
pnl.HasMouse = ppos.X >= 0 && ppos.Y < pnl.Width && ppos.Y >= 0 && ppos.Y < pnl.Height;
}
// Recurse to find panels on a container
if (ctl.Controls.Count > 0) SearchPanel(ctl.Controls, pos);
}
}
}

The TrapMouse method implements the logic of handling the mouse messages and finding the panel that the mouse might be on. We need to call it from the form that hosts the panel(s). Build your project and drop a MyPanel control on the form. Cover it with other controls as necessary, leave a bit of space so you can see the results. Here's the form code:

using System;
using System.Drawing;
using System.Windows.Forms;

namespace WindowsApplication1 {
public partial class Form1 : Form, IMessageFilter {
public Form1() {
InitializeComponent();
// Install message filter
Application.AddMessageFilter(this);
// Install event handlers
this.FormClosed += Form1_FormClosed;
myPanel1.MouseLeave += myPanel1_MouseLeave;
myPanel1.MouseEnter += myPanel1_MouseEnter;
myPanel1.BackColor = Color.Black;
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e) {
// Unregister message filter
Application.RemoveMessageFilter(this);
}
public bool PreFilterMessage(ref Message m) {
// Trap mouse messages
MyPanel.TrapMouse(this, ref m);
return false;
}
private void myPanel1_MouseLeave(object sender, EventArgs e) {
// Demo, black background when mouse leaves panel
myPanel1.BackColor = Color.Black;
}
private void myPanel1_MouseEnter(object sender, EventArgs e) {
// Demo, white background when mouse enters panel
myPanel1.BackColor = Color.White;
}
}
}


nobugz  Friday, November 03, 2006 8:27 PM
Ooh, that is a pretty solution. I definitely like that better than throwing these mouse hook handlers all over the place.

And, more than that, what I'm doing is a little more simple. I just need to know if it is within the bounds of the panel, so instead of that recursive method, I could just check the bounds of the panel versus the point from the message.

Very nice, thanks very much. I'll be throwing that in now. :)
BlueMikey  Friday, November 03, 2006 9:50 PM
Glad you liked it, I was quite happy with it too. This is really a testament to the original .NET class library designers. I've been getting quite impressed as of late how smart these guys and gals were when they came up with System.Windows.Forms and the BCL. They managed to make it both easy to use and infinitely extensible. Anders rules. I'm not that thrilled about the .NET 2.0 additions and got my toes curled about .NET 3.0; hope it works out...
nobugz  Friday, November 03, 2006 11:15 PM

Another solution not requiring unmanaged code is stated in post:

http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=805145&SiteID=1

A helperproperty to determine if mouse leave/enter events for the inner controls is ignore is below:

private bool IsMouseInsideControl

{

get

{

Point mousePosistion = new Point(MousePosition.X, MousePosition.Y);

mousePosistion = PointToClient(mousePosistion);

// If we're to hide the control on a MouseLeave event, that event will be fired

// when the mouse is one pixel shy of leaving the control. So unless we reduce

// the client rectangle by at least one pixel, the mouse will always appear to be

// inside the control on this event.

Rectangle reducedClientRect = Rectangle.Inflate(ClientRectangle, -1, -1);

// return if the mouse position is inside the control

return reducedClientRect.Contains(mousePosistion);

}

}

DotNetAcing  Wednesday, November 08, 2006 12:50 AM

Nice hack... is this a short coming in the UI framework?

Maybe they need four events?

MouseEnter, MouseEnterBounds
MouseLeave, MouseLeaveBounds

Cheers,
James.

James Miles  Tuesday, November 14, 2006 7:12 AM

You can use google to search for other answers

Custom Search

More Threads

• Where Can I found the sample code for .jpg - >.SWF?
• Format Text Box For Currenct Value
• In need of help
• pop-out menu
• Sizing Controls On A Form
• never throw WM_NCLBUTTONDOWN
• Need to Run an outside application from the C# Form
• MainMenu Designer - Microsoft.VisualStudio.dll in VS2005
• Save files in text or Xml file and get file
• Hatch method in Framwork 3.5 with visualstudio 2008