MouseEnter and MouseLeave events from a Panel and its child controls - c#

I have a Panel that contains child controls.
If I handle the Panel's MouseEnter and MouseLeave events, and its child's MouseEnter and MouseLeave events, the events are raised in this order:
Panel.MouseEnter
Panel.MouseLeave
Child1.MouseEnter
Child1.MouseLeave
Panel.MouseEnter
Panel.MouseLeave
But I need the following order:
Panel.MouseEnter
Child1.MouseEnter
Child1.MouseLeave
Panel.MouseLeave
Is that possible?

If you dont mind creating a usercontrol(derived from the panel or other parent container you wish),
Override your parent's OnMouseLeave method to look like the following..
protected override void OnMouseLeave(EventArgs e)
{
if(this.ClientRectangle.Contains(this.PointToClient(Control.MousePosition)))
return;
else
{
base.OnMouseLeave(e);
}
}
Then, the event raising will be in the required order.

The mouse is "leaving" the panel as it enters the child control which is why it fires the event.
You could add something along the following lines in the panel MouseLeave event handler:
// Check if really leaving the panel
if (Cursor.Position.X < Location.X ||
Cursor.Position.Y < Location.Y ||
Cursor.Position.X > Location.X + Width - 1 ||
Cursor.Position.Y > Location.Y + Height - 1)
{
// Do the panel mouse leave code
}

The solution is to track the number of enters/leaves.
In you overall control add a counter:
private int mouseEnterCount = 0;
In the MouseEnter handler do this:
if (++mouseEnterCount == 1)
{
// do whatever needs to be done when it first enters the control.
}
In the MouseLeave handler do this:
if (--mouseEnterCount == 0)
{
// do whatever needs to be done when it finally leaves the control.
}
and do the above MouseEnter and MouseLeave event handlers for ALL the child controls as well as the containing object.

Matthew's answer will not work always. Especially if the child control is set to the edge of its container and the mouse moves off the controls in that direction. You will never detect the MouseLeave event.
The best approach is to create a user control container then hook all the child controls' MouseEnter and MouseLeave events so that you can properly detect when and where the mouse is at all times. THEN if it enters your container's bounds you can fire a custom MouseEnter event and when it leaves MouseLeave event.

Jimmy T. is right. There will be problems if there is no (or small) space betwean Parent Control (Panel) edge and Child Control.
This is how I solve this problem in UserControl-derived class:
public CSStackPanelItem()
{
InitializeComponent();
MouseEnter += new EventHandler(CSStackPanelItem_MouseEnter);
foreach (Control child in Controls)
{
child.MouseEnter += (s, e) => CSStackPanelItem_MouseEnter(s, e);
child.MouseLeave += (s, e) => OnMouseLeave(e);
}
}
protected override void OnMouseLeave(EventArgs e)
{
if (this.ClientRectangle.Contains(this.PointToClient(Control.MousePosition)))
return; //suppress mouse leave event handling
if (m_bIsHover)
{
m_bIsHover = false;
Invalidate(); //actually my mouse Enter/Leave event
}
base.OnMouseLeave(e);
}
void CSStackPanelItem_MouseEnter(object sender, EventArgs e)
{
m_bIsHover = true;
Invalidate(); //actually my mouse Enter/Leave event
}

This may not be the most elegant solution, but you could set a property in the parent control panel (subclass panel) that is a bool value like "bool selected". Then when the MouseEnter for the panel fires set it to true...then stop the mouseleave logic from firing unless it is set to false.
example
bool selected;
MouseEnter(..,..)
{
if (!selected)
selected = true;
else
selected = false;
if (selected)
/.. Logic Here ../
}
MouseLeave()
{
if (selected)
return;
/.. Logic Here ../
}
In reality I would just have the MouseLeave event of the child set the parameter.
Example:
Parent:
bool doLeave;
MouseLeave(..,..)
{
if (doLeave)
{
/.. Logic ../
doLeave = false;
}
Child:
MouseLeave(..., ...)
{
DerivedPanel parent = this.Parent as DerivedPanel;
if (parent != null)
parent.doLeave = true;
}
Neither are elegant but it will work.

I believe so. A nice tool to have for verifying your WinForms application's events.
Windows.Forms Order of Events
http://missico.spaces.live.com/blog/cns!7178D2C79BA0A7E3!186.entry
Created with EventSpy written by Urs Eichmann. (ftp://missico.net/EventSpy.zip)
Using .NET Framework 3.5 and with Visual Basic’s Application Framework enabled.

This is a tricky one, and will be difficult to code reliably for. One idea is to "capture" the 3 incoming events in a list and execute your desired code once the list is complete (has the 3 desired events in the list). Then when you're done executing whatever code (or perhaps capture the combo of events in reverse), you could empty your list and have it ready for the next time that particular combo-event happens. Not ideal, but just a thought.
Of course, that doesn't overcome the potential resolution issues & possible missed events Hans raised. Perhaps more context is in order.

Check the child component..
if (panel1.GetChildAtPoint(panel1.PointToClient(Cursor.Position)) == null)
{
// mouse leave panel and children
}

My solution was to create a MouseEnter event for the panel and for the parent form of that panel. I don't tie to any MouseLeaving events.
When the cursor enters the panel, MouseEnter fires. I can visit all of the panel child controls and nothing happens (which is what I want). When I leave the panel, the parent form's MouseEnter fires.

I was looking for a solution to this problem so I could make a panel act like a button (ie change color when the user hovers over it). I found the simplest solution was to put another panel over the base panel (and over all it's controls) and make the new panel's BackColr Transparent (highlight the whole BackColr property and type in "Transparent"). Then add the MouseEnter and MouseLeave events to the new panel and make them perform whatever you want (change the background color of the base panel). The user still sees all the controls that are on the base panel but the base panel's mouse leave and mouse enter don't fire as the user moves the mouse over the controls. The user can't interact with the controls on the base panel but for a simple button type control this works OK. Just add a the mouse click event handler to the new panel.

Related

Detect if the Control was Moved inside the picturebox

I have added a custom control to picturebox.I need to check if the control has been moved.
Currently i use pictureboxmousemove event,which obviously has some issues.
I have tried adding the mousemouse event to the control as well.It does not seem to work.How can i sort this issue?
pictureBox5.Controls.Clear();
var c = new FrameControl();
c.Size = obj.Size;
c.Location = obj.Location;
pictureBox5.Controls.Add(c);
UPDATE:
I have tried overriding the mousemove event of the Frame Control but it does not seem to work.
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
//Debugger does not hit breakpoint
}
MouseMove event is called when you hover mouse on the control. If you want to check whether control is moved then please use LocationChanged event.

Allow clicks and click-through's

I have a Grid over a Button (They are both directly on another Grid, but the Grid is "above"). I need to subscribe to certain events in the Grid, but still be able to click on the Button. I set the Background of the Grid to Transparent, to get it to raise events, but then the Button isn't clickable. Is there any way to leave IsHitTestVisible = true but let the click go to the next element as well?
I'd first look at whether you can put the button above the grid. You can just move it in the XAML - the further down an element declaration is in the XAML, the higher the z-order.
If you definitely cannot do this, you could override the UI element MouseUp or MouseDown methods and control the setting of the handled property to allow the subsequent elements in the tree to take the clicks.
If you only wanted to restrict this to a specific control, you could inspect the "OriginalSource" property (since all these event args inherit from RoutedEventArgs) to see what the source of the click was and act accordingly:
protected override void OnMouseDown(MouseButtonEventArgs e)
{
var grid = e.OriginalSource as Grid;
if (grid != null && grid.Tag = "yourgridname") e.Handled = false;
}

How do I tell which control the mouse has been clicked over?

I have been looking around for a while for some code that tells what control the mouse has clicked. I have a Form with over 50 controls and I don't want to click each one to make a mouse clicked on. How would I do this?
You can use the Tag property of each control. So set it to something meaningful and on Click event do something like this:
(sender As Control).Tag
EDIT: Also you may do this:
foreach (Control item in this.Controls) //this IS YOUR CURRENT FORM
{
if ((sender as Control).Equals(item))
{
//Do what you want
}
}
Approach One: Individualized Handling
The mouse click event will actually be received by the control on which the mouse is clicked, so the only thing you need to do is handle the MouseClick event for that control. That makes sense if you want mouse clicks to do something different for each of your controls.
This is just the basic event-handling strategy that you should already be familiar with. The event handler method can be wired up either using the designer or manually via code, and looks like this:
private void myControl_MouseClick(object sender, MouseEventArgs e)
{
// do something when the myControl control is clicked
}
Approach Two: Consolidated Handling
If you want the same behavior on multiple controls, you would wire up a single event handler method to handle the MouseClick event for multiple controls. Then, from inside of the event handler method, you would use the sender parameter to identify the control that was clicked. For example, you could cast sender to a Control object and test the value of the Name property.
If you wanted all controls of a certain type to behave a certain way, and all controls of another type to behave a different way, you could have two event handler methods and wire the controls up accordingly by type.
The easiest way to do this is to wire up the event handler methods through code. The designer would work, but it would be overly tedious to use it for each of many controls. For example, in your form's constructor, you could loop over all of the controls and hook up your event handlers:
public MyForm()
{
this.InitializeComponent();
// Wire up your event handlers.
foreach (Control ctrl in this.Controls)
{
if (ctrl is Label)
{
// Wire all Label controls up to do one thing.
ctrl.MouseClick += new MouseEventHandler(LabelControls_MouseClick);
}
else if (ctrl is Button)
{
// Wire up all Button controls to do another thing.
ctrl.MouseClick += new MouseEventHandler(ButtonControls_MouseClick);
}
else
{
// And wire up the rest of the controls to do a third thing.
ctrl.MouseClick += new MouseEventHandler(OtherControls_MouseClick);
}
}
}
private void LabelControls_MouseClick(object sender, MouseEventArgs e)
{
// do something when a Label control is clicked
}
private void ButtonControls_MouseClick(object sender, MouseEventArgs e)
{
// do something when a Button control is clicked
}
private void OtherControls_MouseClick(object sender, MouseEventArgs e)
{
// do something when a control other than a Label or Button is clicked
}
Approach Three: Global Handling
If you've made all of these controls transparent (that is, transparent to mouse events, not visually transparent) so that mouse click events are handled at a higher level (i.e., by your form), you can use the Control.GetChildAtPoint method to determine the control that was clicked on. You just specify the coordinates of the location at which the mouse was clicked, and the method will return the child control (if any) that is located at that point.
private void myForm_MouseClick(object sender, MouseEventArgs e)
{
Control ctrl = Control.GetChildAtPoint(e.Location);
if (ctrl != null)
{
// do something with the clicked control
}
else
{
// if ctrl is null, then the parent form itself was clicked,
// rather than one of its child controls
}
}
I don't really recommend this approach, though, because it violates good object-oriented design. You have to write code to determine which control is which based on its unique properties, instead of just letting the runtime determine and handle that automatically.

WinForms Event Parent scroll

I have two FlowLayoutPanels on a form: PanelA and PanelB. Each will be populated at run-time with multiple controls, such that the panel will scroll (i.e AutoScroll is true).
Here's the issue: The controls that the panels are populated with each contain a ComboBox. Thus, MouseWheel events are consumed by the combo box instead of by the panel. I want MouseWheel events to be consumed by the panel.
If there's no scrollable control on the child controls, then the MouseWheel event skips the child control (which doesn't handle it) and hits the panel, which does handle it. How can I set my child control's combo box to ignore the MouseWheel event? Can I tell it to re-raise the event?
I tried just applying Focus to the Parent whenever one of the child controls ticks the 'MouseEnter' event; this fixed the scrolling issues, but also left the child controls completely un-editable.
Something else I've found from digging around involves fiddling with the Windows API directly, but I find it hard to believe that something like that is required for this.
I tested the following code and it seems like a solution to your issue. Basically you need to focus the 'FlowLayoutPanel' when you click on it, or your mouse enters it:
private void newCheckListQuestionPanel_Click(object sender, EventArgs e)
{
newCheckListQuestionPanel.Focus(); //allows the mouse wheel to work after the panel is clicked
}
private void newCheckListQuestionPanel_MouseEnter(object sender, EventArgs e)
{
newCheckListQuestionPanel.Focus(); //allows the mouse wheel to work after the panel has had the mouse move over it
}
Source

MouseUp event interrupted?

I'm making a custom control with a panel. I want to be able to drag and drop it so I've implemented that in the MouseDown event of my control. But I want the thing to react when you start drag to give a little feedback to the user. So in the MouseDown even I change the color. Then I want to change it back in the MouseUp event.
My control is not installed into VS2008 but just a class I've written that I instanciate at run time (I don't know in advance how many I need and so on). Now, my control exposes a MouseDown event so as to be able to be dragged. When I subscribe to this event from the parent application to actually perform the drag and drop my control is not repainted on its MouseUp event! In fact, the MouseUp is never invoked. If, on the other hand, I don't subscribe to the event in the parent app it works as intended.
What's going on? Is the parent interrupting the flow so that the MouseUp event never fires in my control? How do I get around this?
I'm not sure if you are using Windows Forms or WPF, but in Windows forms here is what I mean:
public class DerivedPanel : Panel
{
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
Capture = true;
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
Capture = false;
// Change your color or whatever here
}
}
In WPF there are two methods, CaptureMouse() and ReleaseMouseCapture() to do the same thing. When the control captures the mouse, it will received mouse events even if the cursor isn't over the control. This could be causing your problem. See MSDN Article
Do you capture the mouse in the custom control on the mousedown event? Try capturing on the mousedown and releasing the capture on the mouseup.

Categories