I have a winform with some controls on it.
While the cursor is enters the form (form boundaries, this includes form's content) I want to trigger specific task.
When cursor leavs the form, i want to trigger another task.
Obviously seeting these events on form won't work: (because, for instance MouseLeave will bw triggered when i move from form itself to other control)
this.MouseEnter += (sender, e) =>
{
//do stuff
};
this.MouseLeave += (sender, e) =>
{
//do stuff
};
I laso tried IMessageFilter interface as appears in similar questions, but none gives me the needed result!
The problem is to detect when mouse leave the form completely.
public bool PreFilterMessage(ref Message m)
{
switch (m.Msg)
{
case WM_MOUSEMOVE: //or other messages
bool z = myForm.Bounds.Contains(Cursor.Position); //This didn't help
}
eturn false;
}
There is also problem with detection within the form content (when i move between controls).
What am I missing?
update: i don't want to use timers!
You could get the current x,y coordinates of the pointer and see if they are within the form. If they aren't, then perform the off-form task.
I pulled a trick like this once to keep tabs on all forms' ancestors:
Forms have a ControlAdded event. You could hook yourself into that (probably before InitializeForm. Inside that event handler can hook yourself into the ControlAdded and MouseXXX events of all children and grandchildren. You could send all the events of the same type to the same handler.
With that methodology, I suppose that the easiest solution will then be to watch the MouseLeave and MouseEnter events of all ancestors and then check the mouse cursor position to see if it's in the form's screen rectangle (your event handlers will be mashed full of events firing off as your mouse moves around the form). Anyway, then you'll have it solved with zero timers!
Related
I've been trying to work with the mouse event handlers. Using MouseDown only seems to work with the right mouse button. And PreviewMouseLeftButton doesn't activate the MouseMove event until I release the left button.
So this code:
private void NewButton_MouseDown(object sender, MouseButtonEventArgs e)
{
var test = e.LeftButton;
var test2 = e.RightButton;
if (!bButtonIsDown)
{
bButtonIsDown = true;
Button mover = (Button)sender;
pntEnterPoint = e.GetPosition(this.cnvsLinkScreen);
cnvsLinkScreen.MouseMove += CnvsLinkScreen_MouseMove;
cnvsLinkScreen.MouseUp += CnvsLinkScreen_MouseUp;
spMover = (StackPanel)((Button)sender).Parent;
pntPreviousPoint.X = Canvas.GetLeft(spMover);
pntPreviousPoint.Y = Canvas.GetTop(spMover);
}
}
private void NewButton_MouseMove(object sender, MouseEventArgs e)
{
Button mover = (Button)sender;
StackPanel spMover = (StackPanel)((Button)sender).Parent;
base.OnMouseMove(e);
if (e.LeftButton == MouseButtonState.Pressed)
{
// Inititate the drag-and-drop operation.
FindChild<StackPanel>(cnvsLinkScreen, "CSVImport");
pntEnterPoint = e.GetPosition(this.cnvsLinkScreen);
Canvas.SetLeft(spMover, pntEnterPoint.X + 1);
Canvas.SetTop(spMover, pntEnterPoint.Y + 1);
}
}
Works but with right button only. When enabling the event handler with:
newButton.MouseDown += NewButton_MouseDown;
That event handler will not get touched with the left mouse button, which I think is odd.
I've done a good amount of reading out there and people refer to thePreviewMouseLeftButtonDown event handler but when I use that in place (same code) the cnvsLinkScreen.MouseMove += CnvsLinkScreen_MouseMove doesn't fire until I let go of the left mouse button (button up) then the StackPanel drags until I click and drop it.
I set breakpoints and see that the event handler is getting set, but it's just not working until after the left button is released. Clearly I'm missing something with the event handlers on this.
Why isn't the MouseDown event handler not even firing with a left mouse button down; only a right mouse button down?
This has to do with Routed Events. Unlike with normal events, a routed event can be marked as handled, after which no further handlers for that event will be called.
MouseDown and MouseMove are bubbling routed events, meaning they start at the deepest elements (the ones inside the button) and then "bubble" their way up, through their parents, toward the Window. Any parent can register a handler for these events and it will be called when the event reaches that parent's level. However if any child marks the event as handled, the event will never reach the parent and the parent's handler will never be called.
This is what's happening here. Button has a handler for MouseDown internally. When Button decides it has been clicked, it raises its own Click event and marks MouseDown as handled. Button also seems to handle MouseMove, but only while a click is in progress. This is why your event handlers don't get called during a left-click, because left-click triggers a "click" which ends up handling those events before they make it out of the Button.
PreviewMouseDown and PreviewMouseMove, on the other hand, are both tunneling routed events, meaning they start from the broadest parent (the Window) and work their way inwards toward the exact point of the cursor. Tunneling events happen before bubbling events; the mouse input first tunnels in toward the cursor, then bubbles back up.
If you attach event handlers to PreviewMouseDown and PreviewMouseMove instead of MouseDown and MouseMove, you will see all the mouse input you expect.
Funny side note: you can actually play the same trick on Button that Button is playing on you. If you mark PreviewMouseDown as handled before it gets inside the Button, then it will never raise its Click event.
I have a little problem with winforms and mousewheel events.
I have a custom user control representing a slider. Now, I have a couple groups of sliders in which each group is wrapped inside a panel. All the groups are then wrapped in another panel (which has AutoScroll set to true) and this is wrapped in a form. The slider logic is implemented such that the mousewheel can be used to change its value. For this, the slider user control gets focus when the mouse is over the slider. However, when I scroll, also the AutoScroll parent panel scrolls with it.
I've already lost a lot of time on this issue. Anybody knows what is happening here and how I can solve it? I thought the event was bubbling to the parent panel but I don't find a Handled property on the event when handling it in the Slider control (as is possible with WPF).
many thanks
We implemented the Slider as a complete custom user control (inheriting the UserControl class) with own look-and-feel.
You might have noticed that a UserControl doesn't show the MouseWheel event in the Properties window. Hint of trouble there. The WM_MOUSEWHEEL message bubbles. If the control that has the focus doesn't handle it then Windows passes it on to its Parent. Repeatedly, until it finds a parent window that wants to handle it. The Panel in your case.
You'll need to invoke a bit of black magic in your slider control. The actual event args object that get passed to the MouseWheel event is not of the MouseEventArgs type as the event signature suggests, it is HandledMouseEventArgs. Which lets you stop the bubbling. Like this:
protected override void OnMouseWheel(MouseEventArgs e) {
base.OnMouseWheel(e);
// do the slider scrolling
//..
((HandledMouseEventArgs)e).Handled = true;
}
If you are creating event dynamically like
object.event += new EventHandler<EventArgs>(eventfunction);
try un-registering the event after the eventfunction is called like this
object.event -= new EventHandler<EventArgs>(eventfunction);
I have few controls (Label, Custom Textbox, Datagridview) docked on a form. When I tried to hook the MouseMove event to the individual controls, the event fires perfectly fine but when I tried to hook the event on the form itself, the mousemove event do not respond at all. What could be the possible cause of this?
Edit:
Here is the event hook from resources.cs
this.MouseMove += new System.Windows.Forms.MouseEventHandler(this.LogicSimulationViewerForm_MouseMove);
and here is the function handled on catching the event
private void LogicSimulationViewerForm_MouseMove(object sender, MouseEventArgs e)
{
//DOESN'T WORK!!!
}
Winforms events don't bubble up like in WPF or in Html. Therefore if the controls are docked on the form, the form doesn't expose any surface of it's own and it doesn't catch any mouse events. However 'under water', all windows messages (a mouse move is a windows message), do pass the form, so it is possible to catch that message.
edit
Tigran has linked to a good example for the use of IMessageFilter that makes creating another example a bit superfluous :)
The cause of this is that in difference of WPF in WindowsForms the event is "blocked" by the control that handled it (in WPF the event will be pushed to the parent up to the Visual Tree, or in opposite direction: from parent to child).
So to catch that event you need to process it on application level and do not subscribe to single control event.
For more detailed example how to handle it can look here:
How do I capture the mouse mouse move event in my winform application
I'm creating a custom dropdown box, and I want to register when the mouse is clicked outside the dropdown box, in order to hide it. Is it possible to detect a click outside a control? or should I make some mechanism on the containing form and check for mouseclick when any dropdownbox is open?
So I finally understand that you only want it to close when the user clicks outside of it. In that case, the Leave event should work just fine... For some reason, I got the impression you wanted it to close whenever they moved the mouse outside of your custom dropdown. The Leave event is raised whenever your control loses the focus, and if the user clicks on something else, it will certainly lose focus as the thing they clicked on gains the focus.
The documentation also says that this event cascades up and down the control chain as necessary:
The Enter and Leave events are hierarchical and will cascade up and down the parent chain until the appropriate control is reached. For example, assume you have a Form with two GroupBox controls, and each GroupBox control has one TextBox control. When the caret is moved from one TextBox to the other, the Leave event is raised for the TextBox and GroupBox, and the Enter event is raised for the other GroupBox and TextBox.
Overriding your UserControl's OnLeave method is the best way to handle this:
protected override void OnLeave(EventArgs e)
{
// Call the base class
base.OnLeave(e);
// When this control loses the focus, close it
this.Hide();
}
And then for testing purposes, I created a form that shows the drop-down UserControl on command:
public partial class Form1 : Form
{
private UserControl1 customDropDown;
public Form1()
{
InitializeComponent();
// Create the user control
customDropDown = new UserControl1();
// Add it to the form's Controls collection
Controls.Add(customDropDown);
customDropDown.Hide();
}
private void button1_Click(object sender, EventArgs e)
{
// Display the user control
customDropDown.Show();
customDropDown.BringToFront(); // display in front of other controls
customDropDown.Select(); // make sure it gets the focus
}
}
Everything works perfectly with the above code, except for one thing: if the user clicks on a blank area of the form, the UserControl doesn't close. Hmm, why not? Well, because the form itself doesn't want the focus. Only controls can get the focus, and we didn't click on a control. And because nothing else stole the focus, the Leave event never got raised, meaning that the UserControl didn't know it was supposed to close itself.
If you need the UserControl to close itself when the user clicks on a blank area in the form, you need some special case handling for that. Since you say that you're only concerned about clicks, you can just handle the Click event for the form, and set the focus to a different control:
protected override void OnClick(EventArgs e)
{
// Call the base class
base.OnClick(e);
// See if our custom drop-down is visible
if (customDropDown.Visible)
{
// Set the focus to a different control on the form,
// which will force the drop-down to close
this.SelectNextControl(customDropDown, true, true, true, true);
}
}
Yes, this last part feels like a hack. The better solution, as others have mentioned, is to use the SetCapture function to instruct Windows to capture the mouse over your UserControl's window. The control's Capture property provides an even simpler way to do the same thing.
Technically, you'll need to p/invoke SetCapture() in order to receive click events that happen outside of your control.
But in your case, handling the Leave event, as #Martin suggests, should be sufficient.
EDIT: While looking for an usage example for SetCapture(), I came across the Control.Capture property, of which I was not aware. Using that property means you won't have to p/invoke anything, which is always a good thing in my book.
So, you'll have to set Capture to true when showing the dropdown, then determine if the mouse pointer lies inside the control in your click event handler and, if it doesn't, set Capture to false and close the dropdown.
UPDATE:
You can also use the Control.Focused property to determine if the control has got or lost focus when using a keyboard or mouse instead of using the Capture with the same example provided in the MSDN Capture page.
Handle the Form's MouseDown event, or override the Form's OnMouseDown
method:
enter code here
And then:
protected override void OnMouseDown(MouseEventArgs e)
{
if (!theListBox.Bounds.Contains(e.Location))
{
theListBox.Visible = false;
}
}
The Contains method old System.Drawing.Rectangle can be used to indicate if
a point is contained inside a rectangle. The Bounds property of a Control is
the outer Rectangle defined by the edges of the Control. The Location
property of the MouseEventArgs is the Point relative to the Control which
received the MouseDown event. The Bounds property of a Control in a Form is
relative to the Form.
You are probably looking for the leave event:
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.leave.aspx
Leave occurs when the input focus leaves the control.
I just wanted to share this. It is probably not a good way of doing it that way, but looks like it works for drop down panel that closes on fake "MouseLeave", I tried to hide it on Panel MouseLeave but it does not work because moving from panel to button leaves the panel because the button is not the panel itself. Probably there is better way of doing this but I am sharing this because I used about 7 hours figuring out how to get it to work. Thanks to #FTheGodfather
But it works only if the mouse moves on the form. If there is a panel this will not work.
private void click_to_show_Panel_button_MouseDown(object sender, MouseEventArgs e)
{
item_panel1.Visible = true; //Menu Panel
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (!item_panel1.Bounds.Contains(e.Location))
{
item_panel1.Visible = false; // Menu panel
}
}
I've done this myself, and this is how I did it.
When the drop down is opened, register a click event on the control's parent form:
this.Form.Click += new EventHandler(CloseDropDown);
But this only takes you half the way. You probably want your drop down to close also when the current window gets deactivated. The most reliable way of detecting this has for me been through a timer that checks which window is currently active:
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
and
var timer = new Timer();
timer.Interval = 100;
timer.Tick += (sender, args) =>
{
IntPtr f = GetForegroundWindow();
if (this.Form == null || f != this.Form.Handle)
{
CloseDropDown();
}
};
You should of course only let the timer run when the drop down is visible. Also, there's probably a few other events on the parent form you'd want to register when the drop down is opened:
this.Form.LocationChanged += new EventHandler(CloseDropDown);
this.Form.SizeChanged += new EventHandler(CloseDropDown);
Just don't forget to unregister all these events in the CloseDropDown method :)
EDIT:
I forgot, you should also register the Leave event on you control to see if another control gets activated/clicked:
this.Leave += new EventHandler(CloseDropDown);
I think I've got it now, this should cover all bases. Let me know if I'm missing something.
If you have Form, you can simply use Deactivate event just like this :
protected override void OnDeactivate(EventArgs e)
{
this.Dispose();
}
I'm making a calendar From in C# using WinForms.
I've put it together using a two-dimensional array of Panels, and inside them I have a List<> of custom controls which represent appointments.
The user needs to be able to drag appointment controls from one Panel to another (from day to day).
The custom control has a MouseDown and MouseUp event, which passes a message up from the control to the Parent.Parent (custom control -> day panel -> calendar form) and calls public methods StartDragging() and StopDragging() respectively.
Inside these methods, I make a clone of the custom control and add it to the Form, and store it in a global variable in the form which is called DraggedControl.
The Form has an event handler for MouseMove which goes like this:
void Calendar_MouseMove(object sender, MouseEventArgs e)
{
if (DraggedControl == null)
return;
DraggedControl.Location = PointToClient(MousePosition);
Refresh();
}
There are two problems, however:
First of all, the custom control is under everything else. I can see it being added and removed on MouseDown and MouseUp, but it is being added at 0,0 under the panels and day Labels.
Secondly, it does not appear to be moving with the MouseMove at all. I have a feeling this might be because I am moving the mouse with the button pressed, and this would represent a drag action rather than a basic MouseMove.
If I remove the MouseUp code, the control does drag with the mouse, however as soon as the mouse enters the panels (which the control is, sadly, underneath), the drag action stops.
What would you suggest I do?
I suspect there is probably a better way to do what I am trying to do.
custom control is under everything
else
bring it on top:
DraggedControl.BringToFront();
it does not appear to be moving with
the MouseMove at all
Control, which handled MouseDown event, captures mouse input and receives all following MouseMove events until it releases mouse input on MouseUp event, that's why Calendar_MouseMove() is not called. Handle MouseMove event for the same control, which generated MouseDown event.