Multiple MouseHover events in a Control - c#

I'm trying to implement a custom control in C# and I need to get events when the mouse is hovered. I know there is the MouseHover event but it only fires once. To get it to fire again I need to take the mouse of the control and enter it again.
Is there any way I can accomplish this?

Let's define "stops moving" as "remains within an x pixel radius for n ms".
Subscribe to the MouseMove event and use a timer (set to n ms) to set your timeout. Each time the mouse moves, check against the tolerance. If it's outside your tolerance, reset the timer and record a new origin.
Pseudocode:
Point lastPoint;
const float tolerance = 5.0;
//you might want to replace this with event subscribe/unsubscribe instead
bool listening = false;
void OnMouseOver()
{
lastpoint = Mouse.Location;
timer.Start();
listening = true; //listen to MouseMove events
}
void OnMouseLeave()
{
timer.Stop();
listening = false; //stop listening
}
void OnMouseMove()
{
if(listening)
{
if(Math.abs(Mouse.Location - lastPoint) > tolerance)
{
//mouse moved beyond tolerance - reset timer
timer.Reset();
lastPoint = Mouse.Location;
}
}
}
void timer_Tick(object sender, EventArgs e)
{
//mouse "stopped moving"
}

I realize that this is an old topic but I wanted to share a way I found to do it: on the mouse move event call ResetMouseEventArgs() on the control that catches the event.

Why not subscribe to the MouseMove events in the MouseHover event, then unsubscribe in the MouseLeave event
Edit:
One way to determine if the mouse is stopped would be to start a timer every time you get a mouse move event, when the timer has elapsed then you could consider the mouse stopped.
The reason for removing the MouseMove events on MouseLeave would allow you to only receive events while the mouse is over your control.

This was the cleanest implementation I could come up with. It works quite well:
[DllImport("user32.dll")]
static extern IntPtr SendMessage
(
IntPtr controlHandle,
uint message,
IntPtr param1,
IntPtr param2
);
const uint WM_MOUSELEAVE = 0x02A3;
Point lastMousePoint = Point.Empty;
void richTextBox_MouseMove(object sender, MouseEventArgs e)
{
if (Math.Abs(e.Location.X - lastMousePoint.X) > 1 || Math.Abs(e.Location.Y - lastMousePoint.Y) > 1)
{
lastMousePoint = e.Location;
SendMessage((sender as RichTextBox).Handle, WM_MOUSELEAVE, IntPtr.Zero, IntPtr.Zero);
}
}
void richTextBox_MouseHover(object sender, EventArgs e)
{
foo();
}

Here is a slightly simpler version that doesn't rely on a Timer. Just record the time of the last mouse move then to compare current time whenever you want to check if the mouse is hovering.
private DateTime mouseMoveTime = DateTime.Now;
private Point mouseMoveLoc = new Point();
private bool IsHovering()
{
return (mouseMoveTime.AddMilliseconds(SystemInformation.MouseHoverTime)).CompareTo(DateTime.Now) < 0;
}
private void myControl_MouseMove(object sender, MouseEventArgs e)
{
// update mouse position and time of last move
if (Math.Abs(e.X - mouseMoveLoc.X) > SystemInformation.MouseHoverSize.Width ||
Math.Abs(e.Y - mouseMoveLoc.Y) > SystemInformation.MouseHoverSize.Height)
{
mouseMoveLoc = new Point(e.X, e.Y);
mouseMoveTime = DateTime.Now;
}
}
Or a really short version that doesn't have a distance tolerance.
private DateTime mouseMoveTime = DateTime.Now;
private bool IsHovering()
{
return (mouseMoveTime.AddMilliseconds(SystemInformation.MouseHoverTime)).CompareTo(DateTime.Now) < 0;
}
private void myControl_MouseMove(object sender, MouseEventArgs e)
{
mouseMoveTime = DateTime.Now;
}
Just use if (IsHovering())... when you need to check if the mouse is moving.
One thing I have noticed though is that MouseMove is not fired when you are dragging. You can however copy the mouseMoveTime/Loc update code to your drag event to get round this.

Related

How can I delay an animation without using System.Threading.Thread.Sleep()

I want to move my text up, but if I use System.Threading.Thread.Sleep() my app gets stuck. I think that the using a Timer is a good way to solve it but pls show me how. I was trying to use Animate() also, but I didn't solve it by this way.
for (int i = 0; i < 30; i+=2)
{
Brush snizovaniViditelnosti = new SolidBrush(Color.FromArgb(0+i*8, 0+i*8,0+i*8));
g.DrawString("+1", fontPridaniMaterialu, snizovaniViditelnosti, MousePosition.X, MousePosition.Y - i);
System.Threading.Thread.Sleep(30);
//ImageAnimator.Animate()
Timer d = new Timer();
d.Interval = 55;
Refresh();
}
It's suppose to work that I click on some button and then appears text - "+1" and it will be moving up with reducing opacity. Finally it will disappear.
Grab Timer and Button from toolbox.
Then select timer and go to events section in properties window. Double click Tick event. Apply your logic for moving text.
For button you need to use click event.
Sample code:
private void timer1_Tick (object sender, EventArgs e)
{
button1.Location = new Point (button1.Location.X + 1, button1.Location.Y);
}
private void button1_Click (object sender, EventArgs e)
{
timer1.Start ();
}
You will have to create a Timer out of your for loop and replace the loop by a Tick event. At the moment you are re-creating the Timer in every loop iteration. Put it as a component to your control, like this:
// Timer Interval is set to 0,5 second
private Timer _timer = new Timer { Interval = 500 };
And adding also the following fields to your control for
private int _index = 0;
private int _maxIndex = 30;
After this adding a delegate to the Tick event, which will moving up your text a ttle bit on every tick.
this._timer.Tick += delegate
{
if (this._index < this._maxIndex)
{
var alphaValue = 255 - this._index * 8;
Brush snizovaniViditelnosti = new SolidBrush(Color.FromArgb(alphaValue, 255, 255, 255));
g.DrawString("+1", fontPridaniMaterialu, snizovaniViditelnosti, MousePosition.X, MousePosition.Y - this._index);
Refresh();
this._index++;
}
else
{
this._timer.Stop();
}
};
If you only want to reduce opacity, reduce the alpha value and leave the color - as shown in the example above.
And wire this to your Button click event
private void Button_Click(object sender, EventArgs e)
{
this._timer.Start();
}
Hint: This is a quick solution for only one item. If you want to do this for more than one item you may add a class to your code containing the text, the Timer and the current and maxIndex.
I guess you are using winforms.
To avoid flickering while re-drawing your UI. You should activate double buffering.
See more information about Handling and Raising Events
As #apocalypse suggested in his answer. It will be better to setup a fix start location for your text to move up.

Distinguishing between Click and DoubleClick events in C#

I currently have a NotifyIcon as part of a Windows Form application. I would like to have the form show/hide on a double click of the icon and show a balloon tip when single clicked. I have the two functionalities working separately, but I can't find a way to have the app distinguish between a single click and double click. Right now, it treats a double click as two clicks.
Is there a way to block the single click event if there is a second click detected?
Unfortunately the suggested handling of MouseClick event doesn't work for NotifyIcon class - in my tests e.MouseClicks is always 0, which also can be seen from the reference source.
The relatively simple way I see is to delay the processing of the Click event by using a form level flag, async handler and Task.Delay :
bool clicked;
private async void OnNotifyIconClick(object sender, EventArgs e)
{
if (clicked) return;
clicked = true;
await Task.Delay(SystemInformation.DoubleClickTime);
if (!clicked) return;
clicked = false;
// Process Click...
}
private void OnNotifyIconDoubleClick(object sender, EventArgs e)
{
clicked = false;
// Process Double Click...
}
The only drawback is that in my environment the processing of the Click is delayed by half second (DoubleClickTime is 500 ms).
There are 2 different kinds of events.
Click/DoubleClick
MouseClick / MouseDoubleClick
The first 2 only pass in EventArgs whereas the second pass in a MouseEventArgs which will likely allow you additional information to determine whether or not the event is a double click.
so you could do something like;
obj.MouseClick+= MouseClick;
obj.MouseDoubleClick += MouseClick;
// some stuff
private void MouseClick(object sender, MouseEventArgs e)
{
if(e.Clicks == 2) { // handle double click }
}
It is enough to register a Click event and then handle single and double clicks from it.
int clickCount;
async void NotifyIcon_Click( object sender, EventArgs e ) {
if( clickCount > 0 ) {
clickCount = 2;
return;
}
clickCount = 1;
await Task.Delay( SystemInformation.DoubleClickTime );
if( clickCount == 1 ) {
// Process single click ...
} else if( clickCount == 2 ) {
// Process double click ...
}
clickCount = 0;
}

Did "MouseUp" event change value of NumericUpDown?

I need to determine if the value of a NumericUpDown control was changed by a mouseUp event.
I need to call an expensive function when the value of a numericupdown has changed. I can't just use "ValueChanged", I need to use MouseUp and KeyUp events.
Basically, I need to know:
Did the value of the numericUpDown change when the user let go of the
mouse? If any area which is not highlighted in red is clicked, the
answer is no. I need to IGNORE the mouse up event, when ANYWHERE but the red area is clicked.
How can I determine this by code? I find events a little confusing.
This will fire when the user releases the mouse button. You might want to investigate which mousebutton was released.
EDIT
decimal numvalue = 0;
private void numericUpDown1_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left && numvalue != numericUpDown1.Value)
{
//expensive routines
MessageBox.Show(numericUpDown1.Value.ToString());
}
numvalue = numericUpDown1.Value;
}
EDIT 2
This will determine if the left mousebutton is still down, if it is exit before performing expensive routine, doesn't help with keyboard button down.
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
if ((Control.MouseButtons & MouseButtons.Left) == MouseButtons.Left)
{
return;
}
//expensive routines
}
Edit 3
How to detect the currently pressed key?
Will help solve the Any key down, Though I think the only ones that matter are the arrow keys
Problem - I need to IGNORE the mouse up event, when ANYWHERE but the red area is clicked.
Derive a custom numeric control as shown below. Get the TextArea of the Numeric Control and ignore the KeyUp.
class UpDownLabel : NumericUpDown
{
private Label mLabel;
private TextBox mBox;
public UpDownLabel()
{
mBox = this.Controls[1] as TextBox;
mBox.Enabled = false;
mLabel = new Label();
mLabel.Location = mBox.Location;
mLabel.Size = mBox.Size;
this.Controls.Add(mLabel);
mLabel.BringToFront();
mLabel.MouseUp += new MouseEventHandler(mLabel_MouseUp);
}
// ignore the KeyUp event in the textarea
void mLabel_MouseUp(object sender, MouseEventArgs e)
{
return;
}
protected override void UpdateEditText()
{
base.UpdateEditText();
if (mLabel != null) mLabel.Text = mBox.Text;
}
}
In the MainForm, update your designer with this control i.e. UpDownLabel:-
private void numericUpDown1_MouseUp(object sender, MouseEventArgs e)
{
MessageBox.Show("From Up/Down");
}
Referred from - https://stackoverflow.com/a/4059473/763026 & handled the MouseUp event.
Now, use this control instead of the standard one and hook on the
KeyUp event. You will always get the KeyUp event from the Up/Down button only i.e. RED AREA when you click the
spinner [Up/Down button, which is again a different control derived
from UpDownBase].
I think you should use Leave event that when the focus of NumericUpDown control gone, it would called.
int x = 0;
private void numericUpDown1_Leave(object sender, EventArgs e)
{
x++;
label1.Text = x.ToString();
}

Track Bar Only fire event on final value not ever time value changes

I am working on a pretty basic C# visual studio forms application but am having some issue getting the track bar to act as I want it to so hoping someone in the community might have a solution for this.
What I have is a pretty basic application with the main part being a track bar with a value of 0 to 100. The user sets the value of the track to represent "the amount of work to perform" at which point the program reaches out to some devices and tells them to do "x" amount of work (x being the value of the trackbar). So what I do is use the track bars scroll event to catch when the track bars value has changed and inside the handler call out to the devices and tells them how much work to do.
My issue is that my event handler is called for each value between where the track bar currently resides and where ever it ends. So if it is slid from 10 to 30, my event handler is called 20 times which means I am reaching out to my devices and telling them to run at values I don't even want them to run at. Is there someway only to event when scroll has stopped happening so you can check the final value?
Just check a variable, if the user clicked the track bar. If so, delay the output.
bool clicked = false;
trackBar1.Scroll += (s,
e) =>
{
if (clicked)
return;
Console.WriteLine(trackBar1.Value);
};
trackBar1.MouseDown += (s,
e) =>
{
clicked = true;
};
trackBar1.MouseUp += (s,
e) =>
{
if (!clicked)
return;
clicked = false;
Console.WriteLine(trackBar1.Value);
};
For the problem #roken mentioned, you can set LargeChange and SmallChange to 0.
Try the MouseCaptureChanged event - that is the best for this task
A user could also move the track bar multiple times in a short period of time, or click on the track multiple times to increment the thumb over instead of dragging the thumb. All being additional cases where the value that registers at the end of a "thumb move" is not really the final value your user desires.
Sounds like you need a button to confirm the change, which would then capture the current value of the trackbar and send it off to your devices.
Try this with the trackbar_valuechanged event handler:
trackbar_valuechanged(s,e) {
if(trackbar.value == 10){
//Do whatever you want
} else{
//Do nothing or something else
}
}
I found a fairly reliable way to do this is to use a timer hooked up in the trackbar.Scroll event:
private Timer _scrollingTimer = null;
private void trackbar_Scroll(object sender, EventArgs e)
{
if (_scrollingTimer == null)
{
// Will tick every 500ms (change as required)
_scrollingTimer = new Timer()
{
Enabled = false,
Interval = 500,
Tag = (sender as TrackBar).Value
};
_scrollingTimer.Tick += (s, ea) =>
{
// check to see if the value has changed since we last ticked
if (trackBar.Value == (int)_scrollingTimer.Tag)
{
// scrolling has stopped so we are good to go ahead and do stuff
_scrollingTimer.Stop();
// Do Stuff Here . . .
_scrollingTimer.Dispose();
_scrollingTimer = null;
}
else
{
// record the last value seen
_scrollingTimer.Tag = trackBar.Value;
}
};
_scrollingTimer.Start();
}
}
I had this problem just now as I'm implementing a built in video player and would like the user to be able to change the position of the video but I didn't want to overload the video playback API by sending it SetPosition calls for every tick the user passed on the way to his/her final destination.
This is my solution:
First, the arrow keys are a problem. You can try your best to handle the arrow keys via a timer or some other mechanism but I found it more pain than it is worth. So set the property SmallChange and LargeChange to 0 as #Matthias mentioned.
For mouse input, the user is going to have to click down, move it, and let go so handle the MouseDown, MouseUp, and the Scroll events of the trackbar like so:
private bool trackbarMouseDown = false;
private bool trackbarScrolling = false;
private void trackbarCurrentPosition_Scroll(object sender, EventArgs e)
{
trackbarScrolling = true;
}
private void trackbarCurrentPosition_MouseUp(object sender, MouseEventArgs e)
{
if (trackbarMouseDown == true && trackbarScrolling == true)
Playback.SetPosition(trackbarCurrentPosition.Value);
trackbarMouseDown = false;
trackbarScrolling = false;
}
private void trackbarCurrentPosition_MouseDown(object sender, MouseEventArgs e)
{
trackbarMouseDown = true;
}
I had a similar problem, only with a range TrackBar Control. Same idea applies to this also, only it's easier for this case.
I handled the MouseUp Event on the TrackBar to launch the procedures I needed, only after you would let go of the mouse button. This works if you drag the bar to your desired position or just click it.
private void rangeTrackBarControl1_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
{
YourProcedureHere();
}
i solved the problem for my application with two events:
catch the Trackbar-ValueChange-Event
whithin the value-change event disable the valuechange event and enable the MouseUp-Event
public MainWindow()
{
//Event for new Trackbar-Value
trackbar.ValueChanged += new System.EventHandler(trackbar_ValueChanged);
}
private void trackbar_ValueChanged(object sender, EventArgs e)
{
//enable Trackbar Mouse-ButtonUp-Event
trackbar.MouseUp += ch1_slider_MouseUp;
//disable Trackbar-ValueChange-Event
trackbar.ValueChanged -= ch1_slider_ValueChanged;
}
private void trackbar_MouseUp(object sender, EventArgs e)
{
//enable Trackbar-ValueChange-Event again
trackbar.ValueChanged += new System.EventHandler(trackbar_ValueChanged);
//disable Mouse-ButtonUp-Event
trackbar.MouseUp -= trackbar_MouseUp;
//This is the final trackbar-value
textBox.AppendText(trackbar.Value);
}
ATTENTION: this works if the trackbar is moved by mose. It is also possible to move the trackbar by keyboard. Then futher code must be implemented to handle this event.

Mouse events not fired

I'm making a C# WinForms application. The MouseMove and MouseClick events of the form aren't getting fired for some reason. (I'm probably going to feel like an idiot when I find out why.)
It is a transparent form (TransparencyKey is set to the background colour) with a semi-transparent animated gif in a Picture Box. I am making a screensaver.
Any suggestions?
EDIT:
MainScreensaver.cs
Random randGen = new Random();
public MainScreensaver(Rectangle bounds)
{
InitializeComponent();
this.Bounds = Bounds;
}
private void timer1_Tick(object sender, EventArgs e)
{
Rectangle screen = Screen.PrimaryScreen.Bounds;
Point position = new Point(randGen.Next(0,screen.Width-this.Width)+screen.Left,randGen.Next(0,screen.Height-this.Height)+screen.Top);
this.Location = position;
}
private void MainScreensaver_Load(object sender, EventArgs e)
{
Cursor.Hide();
TopMost = true;
}
private Point mouseLocation;
private void MainScreensaver_MouseMove(object sender, MouseEventArgs e)
{
if (!mouseLocation.IsEmpty)
{
// Terminate if mouse is moved a significant distance
if (Math.Abs(mouseLocation.X - e.X) > 5 ||
Math.Abs(mouseLocation.Y - e.Y) > 5)
Application.Exit();
}
// Update current mouse location
mouseLocation = e.Location;
}
private void MainScreensaver_KeyPress(object sender, KeyPressEventArgs e)
{
Application.Exit();
}
private void MainScreensaver_Deactive(object sender, EventArgs e)
{
Application.Exit();
}
private void MainScreensaver_MouseClick(object sender, MouseEventArgs e)
{
Application.Exit();
}
Excerpt from MainScreensaver.Designer.cs InitialiseComponent()
this.MouseClick += new System.Windows.Forms.MouseEventHandler(this.MainScreensaver_MouseClick);
this.MouseMove += new System.Windows.Forms.MouseEventHandler(this.MainScreensaver_MouseMove);
This isn't an answer to you question, but I'm leaving this answer in case anyone else stumbles upon this question while trying to debug this same issue (which is how I got here)
In my case, I had a class that was derived from Form.
I was also using TransparencyKey.
Some things I noticed
The events will not fire on the transparent parts of the form.
The events will not fire if the mouse cursor is over another control on the form.
The events will not fire if you override WndProc and set the result of a WM_NCHITTEST message. Windows doesn't even send out the corresponding mouse messages that would cause the .NET events.
My Solution
In my constructor, I had forgotten to call InitializeComponent().
Which was where the event handlers were being bound to my controls.
Events were not firing because the handlers were not being bound.
Are you sure that your form has focus? If your form does not have focus, the mouse events will not be fired.

Categories