How to stop remaining actions on timer stop? - c#

I have a button with mouse click event handlers.
When the button is pressed (mouse down), a timer is started to simulate other clicks with a delay. The click runs a bitmap transformation, e.g. rotation of 0.5 each time.
When the button is released (mouse up), the timer is stopped.
Code in Button class :
private void TreatMouseDown(object sender, MouseEventArgs e) {
// Following clicks
_repeteTimer.Tick += (senderTick, eTick) => {
_repeteTimer.Stop();
PerformClick();
_repeteTimer.Interval = _nextDelay;
_repeteTimer.Start();
};
// First click
PerformClick();
_repeteTimer.Interval = _firstDelay;
_repeteTimer.Start();
}
private void TreatMouseUp(object sender, MouseEventArgs e) {
_repeteTimer.Stop();
}
Code in window :
btnRotation1.Click += (sender, e) => PHOTO_Treat(1);
private void PHOTO_Treat(int delta) {
if (!_inProgress) {
_photoRotation += delta * 0.5F;
PHOTO_Show();
}
}
private void PHOTO_Show(){
if (_inProgress) return;
_inProgress= true;
// Make image treatments ...
_inProgress = false;
}
The problem is that several transformations occur again after the button is released, if the transformation take long time.
How can I suppress remaining ?

Sounds like you have some serious threading issues.
You could check whether the button is still down by checking _repeteTimer.Enabled.
Also, add a lock. Even if just to be safe.
private readonly object SyncRoot = new object();
private void PHOTO_Show()
{
if (!_inProgress)
{
// race for a lock
lock (SyncRoot)
{
// check button state
if (!_repeteTimer.Enabled)
{
// button was released
return;
}
// else
try
{
_inProgress = true;
MakeImageTreatments();
}
finally
{
_inProgress = false;
}
}
}
}

Related

How can I re-propagate an event after I initially set the handled property to true in an async handler?

I am trying to create a touch and hold event handler with a variable delay in a WPF application by calling a bool task which runs a timer. If the timer elapses, the task returns true. If another event such as touch leave or touch up occurs, the task immediately returns false. Below is my event handler code:
private static async void Element_PreviewTouchDown(object sender, TouchEventArgs e)
{
// Set handled to true to avoid clicks
e.Handled = true;
var isTouchHold = await TouchHold((FrameworkElement)sender, variableTimespan);
if (isTouchHold)
TouchHoldCmd?.Execute(someParam);
else
{
// Here is where I would like to re initiate bubbling up of the event.
// This doesn't work:
e.Handled = false;
}
}
The reason I want it to propagate the event is because, for example, if the user wants to pan the scrollviewer that the element is part of and the panning gesture is started by touching my element, my touchhold works as intended in that the touch and hold command won't get triggered but neither will the scrollviewer start panning.
I tried raising the event manually but this also doesn't seem to work:
bool firedBySelf;
private static async void Element_PreviewTouchDown(object sender, TouchEventArgs e)
{
if(firedBySelf)
{
firedBySelf = false;
return;
}
...
else
{
firedBySelf = true;
e.Handled = false;
((FrameworkElement)sender).RaiseEvent(e);
}
}
How can I achieve my goal?
Edit: Here is the class containing the task:
public static class TouchHoldHelper
{
private static DispatcherTimer _timer;
private static TaskCompletionSource<bool> _task;
private static FrameworkElement _element;
private static void MouseUpCancel(object sender, MouseButtonEventArgs e) => CancelHold();
private static void MouseLeaveCancel(object sender, System.Windows.Input.MouseEventArgs e) => CancelHold();
private static void TouchCancel(object sender, TouchEventArgs e) => CancelHold();
private static void AddCancellingHandlers()
{
if (_element == null) return;
_element.PreviewMouseUp += MouseUpCancel;
_element.MouseUp += MouseUpCancel;
_element.MouseLeave += MouseLeaveCancel;
_element.PreviewTouchUp += TouchCancel;
_element.TouchUp += TouchCancel;
_element.TouchLeave += TouchCancel;
}
private static void RemoveCancellingHandlers()
{
if (_element == null) return;
_element.PreviewMouseUp -= MouseUpCancel;
_element.MouseUp -= MouseUpCancel;
_element.MouseLeave -= MouseLeaveCancel;
_element.PreviewTouchUp -= TouchCancel;
_element.TouchUp -= TouchCancel;
_element.TouchLeave -= TouchCancel;
}
private static void CancelHold()
{
if (_timer != null)
{
_timer.Stop();
_timer.Tick -= _timer_Tick;
_timer = null;
}
if (_task?.Task.Status != TaskStatus.RanToCompletion)
_task?.TrySetResult(false);
RemoveCancellingHandlers();
}
private static void _timer_Tick(object sender, EventArgs e)
{
var timer = sender as DispatcherTimer;
timer.Stop();
timer.Tick -= _timer_Tick;
timer = null;
_task.TrySetResult(true);
RemoveCancellingHandlers();
}
public static Task<bool> TouchHold(this FrameworkElement element, TimeSpan duration)
{
_element = element;
_timer = new DispatcherTimer();
_timer.Interval = duration;
_timer.Tick += _timer_Tick;
_task = new TaskCompletionSource<bool>();
AddCancellingHandlers();
_timer.Start();
return _task.Task;
}
}
Edit: to better explain my intended behavior, consider how icons on a smartphone's screen work. If I tap the icon, it starts the app the icon represents. If I touch and move on an icon, it pans the screen. If I touch and hold the icon, it allows me to move the icon so I can place it somewhere else without panning the screen. If I touch and hold the icon but I don't hold it long enough to trigger the moving of the icon, it acts as if I tapped it, starting the app. I am trying to replicate these last 2 behaviors.
I am not saying my current implementation is the right approach but it's what I was able to come up with. If there is any alternative approach, I would be glad to explore it.
Your workflow of setting e.Handled to true and then wanting to set it back to false again strikes me as odd.
From When to Mark Events as Handled
Another way to consider the "handled" issue is that you should generally mark a routed event handled if your code responded to the routed event in a significant and relatively complete way.
Seems like either you'd be using the wrong event or it's as if the folks at Microsoft had gotten it wrong ;)
// Set handled to true to avoid clicks
Nope, they even thought of that, ref Remarks.
You can set Stylus.IsPressAndHoldEnabled="False" to disable the 'click behavior'. Allowing you to fall back to the default WPF pattern of handling the event or letting it tunnel (in this case) forward.
private static async void Element_PreviewTouchDown(object sender, TouchEventArgs e)
{
var isTouchHold = await TouchHold((FrameworkElement)sender, variableTimespan);
if (isTouchHold)
{
TouchHoldCmd?.Execute(someParam);
e.Handled = true;
}
}
However, as you so aptly point out in the comments:
The issue is that the event handler (Element_PreviewTouchDown) is finished executing before the task is. By the time the task is finished, it doesn't make any difference if I change the e.Handled value.
Given the ship has already sailed and you don't want to interfere with the normal functioning of UI elements, we can remove the line that marks the event as handled all together.
private static async void Element_PreviewTouchDown(object sender, TouchEventArgs e)
{
var isTouchHold = await TouchHold((FrameworkElement)sender, variableTimespan);
if (isTouchHold)
{
TouchHoldCmd?.Execute(someParam);
}
}
I don't have a touch-enabled device, so I experimented with the MouseDown/MouseUp events. I attempted to implement a ClickAndHold event, without interfering with the DoubleClick event. What worked for me was to clone the event args of both MouseDown and MouseUp events, and raise them again using the RaiseEvent method. I also had to check explicitly the e.ClickCount property on MouseDown, and allow the event to propagate unhandled in case of e.ClickCount > 1. I have no idea if this approach will work for implementing an interference-free TouchAndHold event. In any case, here is my code:
public static IDisposable OnClickAndHold(Control control, int delay,
MouseButtonEventHandler handler)
{
bool handleMouseDown = true;
bool handleOtherEvents = false;
RoutedEventArgs eventArgsToRepeat = null;
CancellationTokenSource cts = null;
control.MouseDown += Control_MouseDown;
control.MouseUp += Control_MouseUp;
return new Disposer(() =>
{
control.MouseDown -= Control_MouseDown;
control.MouseUp -= Control_MouseUp;
});
async void Control_MouseDown(object sender, MouseButtonEventArgs e)
{
if (!handleMouseDown || e.ClickCount > 1) return;
e.Handled = true;
var clonedArgs = CloneArgs(e);
try
{
cts = new CancellationTokenSource();
handleOtherEvents = true;
await Task.Delay(delay, cts.Token);
handleOtherEvents = false;
}
catch (TaskCanceledException)
{
handleOtherEvents = false;
try
{
handleMouseDown = false;
control.RaiseEvent(clonedArgs);
}
finally
{
handleMouseDown = true;
}
control.RaiseEvent(eventArgsToRepeat);
return;
}
handler(sender, clonedArgs);
}
void Control_MouseUp(object sender, MouseButtonEventArgs e)
{
if (!handleOtherEvents) return;
e.Handled = true;
eventArgsToRepeat = CloneArgs(e);
cts?.Cancel();
}
MouseButtonEventArgs CloneArgs(MouseButtonEventArgs e)
{
return new MouseButtonEventArgs(e.MouseDevice, e.Timestamp,
e.ChangedButton)
{
RoutedEvent = e.RoutedEvent,
Source = control,
};
}
}
private struct Disposer : IDisposable
{
private readonly Action _action;
public Disposer(Action action) => _action = action;
public void Dispose() => _action();
}
Usage example:
OnClickAndHold(Label1, 1000, (s, e) =>
{
MessageBox.Show("ClickAndHold");
});

Scrolling cause flickering when controlling MouseWheel events

I'm trying to understand what is executing before the MouseWheel event.
What I've done:
I have a form which has AutoScroll property set to true. There is a control (ZEDGRAPH) at the top and the bottom of this form.
To overcome the issue of scrolling and zooming at the same time I captured the mousewheel += new MouseEvenHandler(mymethod) for the form.Then using a bool variable I keep track of when the control (ZEDGRAPH) has focus and when it does not.
When it has focus I make verticalscroll.value = (int)mydesiredposition;
This works in accomplishing what I wanted which is to ignore the mousewheel event in the form and focus on the control.
What I am struggling with is the fact that when I scroll the form flickers every time and scrolls down before coming to the set scrollbar value.
So what I am wondering is what is getting triggered before this mouseeventhandler that causes it to flicker and is there a relatively simple workaround this?
My code snapshot:
public Form(Form1 f)
{
InitializeComponent();
this.MouseWheel += new MouseEventHandler(mousewheel);
}//end of constructor
//
//
bool mousehoverZedGraph1 = false;
bool mousehoverZedGraph2 = false;
//
//
private void zedGraphControl1_MouseHover(object sender, EventArgs e)
{
mousehoverZedGraph1 = true;
return;
}
private void mousewheel(object sender, MouseEventArgs e)
{
if (mousehoverZedGraph1 == true)
{
VerticalScroll.Enabled = false;
VerticalScroll.Value = 0;
return;
}
else if (mousehoverZedGraph2 == true)
{
VerticalScroll.Value = 429;
VerticalScroll.Enabled = false;
}
else
{
//VerticalScroll.Value += e.Delta;
}
}
private void Form_MouseEnter(object sender, EventArgs e)
{
mousehoverZedGraph1 = mousehoverZedGraph2 = false;
VerticalScroll.Enabled = true;
}
A small video highlighting the flicker:

Only repeat a timer a limited number of times

I am trying to write an interface for a motorised stage. What I am trying to do is to create a scan feature such that the motor will move a certain distance, stop and wait a specified time and then move the same distance again. It will repeat the process until it has reached the total length specified by the user. To do this I am trying to use a Timer class features as I still want the GUI to be active during the scan.
I've got some idea of how to code it but get stuck. It would go something like:
private void btnGo_Click(object sender, EventArgs e) //On click
{
int i = 0;
int stop = 15; //number of times I want the motor to stop
System.Timers.Timer bTimer; //initialise timer
bTimer = new System.Timers.Timer(waittime); //time I want the motor to wait
bTimer.Elapsed += PerformMove;
bTimer.Enabled = true;
if(i==stop){bTimer.stop()}
}
private void PerformMove(Object source, ElapsedEventArgs e) //event to move motor
{
//movemotor
i++;
}
Not being particularly familiar with C# or timers is undoubtedly the cause of my confusion. What's the best way to approach this problem? Any example code would be great.
If somebody could clarify what the lines
bTimer.Elapsed += PerformMove;
bTimer.Enabled = true;
actually do too that would also be of great use!
EDIT (sorry, didn't think this was a key part): The value of stop is defined upon the user click of the button from a text box within the GUI. i.e.
int stop = Convert.ToDouble(tbIntervalStops.Text); //grab integer from user input upon button click
This would be the correct solution without memory leak
private int i = 0;
private int stop = 15; //number of times I want the motor to stop
private Timer bTimer; //initialise timer -> Thats wrong: nothing is INITIALIZED here its just defined
private void btnGo_Click(object sender, EventArgs e) //On click
{
i = 0;
stop = Convert.ToInt32(tbIntervalStops.Text); //using int because double is a floating point number like 12.34 and for counting only full numbers will be needed
bTimer = new System.Timers.Timer(waittime); //time I want the motor to wait + Here the Timer is INITIALIZED
bTimer.Elapsed += PerformMove; //Adds the Eventhandler, What should be called when the time is over
bTimer.Enabled = true; //This starts the timer. It enables running like pressing the start button on a stopwatch
}
private void PerformMove(Object source, ElapsedEventArgs e) //event to move motor
{
//movemotor
i++;
if (i == stop) //if stop would be a double here we will have the danger to get not a true because of rounding problems
{
bTimer.Stop();
//now enable the Garbage Collector to remove the Timer instance
bTimer.Elapsed -= PerformMove; //This removes the Eventhandler
bTimer.Dispose(); //This frees all resources held by the Timer instance.
bTimer = null;
}
}
Alternatively, you could also derive from the System.Timers.Timer object and create a wrapper which has properties specific to the task. In which case, you would simply need to instantiate a MoveTimer and subscribe to it's OnPerformMoveEvent.
Update: Added OnMovesCompletedEvent
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TimerExample
{
public class MoveTimer : System.Timers.Timer
{
public event EventHandler OnPerformMoveEvent = delegate { };
public event EventHandler OnMovesCompletedEvent = delegate { };
public MoveTimer()
{
Initialize(new TimeSpan(), 0);
}
public MoveTimer(TimeSpan wait, int moves)
{
this.Initialize(wait, moves);
}
private int _i;
private int _totalmoves;
public int Moves
{
get { return this._totalmoves; }
set { this._totalmoves = value; }
}
private TimeSpan _wait;
public TimeSpan Wait
{
get { return this._wait; }
set { this._wait = value; }
}
private System.Timers.Timer _timer;
private void Initialize(TimeSpan wait, int moves)
{
this._totalmoves = moves;
this._wait = wait;
this._timer = new System.Timers.Timer(wait.Milliseconds);
}
private void BindComponents()
{
this._timer.Elapsed += _timer_Elapsed;
}
private void UnBindComponents()
{
this._timer.Elapsed -= _timer_Elapsed;
}
public void StartTimer()
{
this._timer.Enabled = true;
}
public void StopTimer()
{
this._timer.Enabled = false;
}
void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
this._i++;
if (this.OnPerformMoveEvent != null)
this.OnPerformMoveEvent(this, EventArgs.Empty);
if (this._i == this._totalmoves)
{
this._timer.Stop();
this.UnBindComponents();
this.Dispose();
if (this.OnMovesCompletedEvent != null)
this.OnMovesCompletedEvent(this, EventArgs.Empty);
}
}
}
}
In regards to the user input where the number of moves or stops is provided as a string. I would handle this outside of the MoveTimer object. Validation should always be performed.
First determine that the value can be parsed into an integer. If not, throw an exception to let the user know that the input was entered incorrectly.
To use the above, something like the following would be all it requires:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TimerExample
{
class Program
{
static void Main(string[] args)
{
//Create move timer that will trigger 15 times, once every 30 seconds.
MoveTimer moveTimer = new MoveTimer(new TimeSpan(0, 0, 30), 15);
//Substribe to the move timer events
moveTimer.OnPerformMoveEvent += moveTimer_OnPerformMoveEvent;
moveTimer.OnMovesCompletedEvent += moveTimer_OnMovesCompletedEvent;
//Start the timer
moveTimer.StartTimer();
//What happens in between the moves performed?
}
static void moveTimer_OnMovesCompletedEvent(object sender, EventArgs e)
{
//All the moves have been performed, what would you like to happen? Eg. Beep or tell the user.
}
static void moveTimer_OnPerformMoveEvent(object sender, EventArgs e)
{
//Timer has lapsed, what needs to be done when a move is requested?
}
}
}

Mouse Wheel Scroll - How can I capture the time interval between start and stop of scrolling?

Is there any way to capture the time interval between mouse wheel scroll start and stop? Actually I want to capture the interval between the scrolling start and stop when I very quickly scroll the mouse wheel.
I have already looked at MouseWheel event but it don't fulfill my requirement. In senes that it always gives a value of Delta 120 or -120 but i want to call a function depending on the speed of the mouse scroll for example when i scroll the mouse normally i want to perform function 1 and when i scrolled the mouse very quickly i want to perform the function 2. In other words is there any way to distinguish between the mouse scroll high and normal speed.
Any advice will be appreciated.
can't you capture the mouse wheel events and see how long between them. Basically start a timer when you get a mouse wheel event and then in the next event see what the timer is at (and so how long has elapsed between the events) to determine the speed the wheel is being turned at? If the elapsedtime is smaller than a certain threshold, perform function2 and if it is faster than a certain threshold perform function 1.
You will probably have to set it to perform function 1 if the timer goes off in case they only do a single scroll.
In fact you might be able to do it this way:
start a timer (with an interval that indicates slow mouse wheeling) in the mouse wheel event, then if the timer goes off perform function 1. If the mouse wheel event happens again before the timer has gone off then reset the timer and increment a counter (to keep track of the number in wheel events since you did stuff) then start a second (longer) timer. if the counter is greater then a certain threshold perform function 2. When the second timer elapses, reset the counter. Something along those lines should give you the ability to fire function 1 when slow wheel turning and function 2 when the wheel is rapidly turned through a few 'clicks'.
this code should give a (very dirty) indication of the sort of thing I was thinking of. After playing a little I'm not really sure it's a good solution though....
private void mouseWheelHandler (object sender, MouseEventArgs e)
{
slowTimer.Enabled = false;
slowTimer.Stop ();
slowTimer.Interval = 200;
slowTimer.Start();
slowTimer.Enabled = true;
m_counter++;
Trace.WriteLine(string.Format("counter={0}", m_counter));
if (fastTimer.Enabled==false)
{
fastTimer.Enabled = true;
fastTimer.Interval = 150;
fastTimer.Start ();
}
if (m_counter>5)
{
Trace.WriteLine("called method 2");
m_counter = 0;
fastTimer.Stop ();
slowTimer.Enabled = false;
slowCheckTimer.Stop ();
slowCheckTimer.Interval = 250;
slowCheckTimer.Start();
slowCheckTimer.Enabled = true;
}
}
private void slowTimer_Tick(object sender, EventArgs e)
{
Trace.WriteLine("slow timer ticked");
if (slowCheckTimer.Enabled==false)
{
Trace.WriteLine ("called method 1");
}
slowTimer.Enabled = false;
}
private void fastTimer_Tick(object sender, EventArgs e)
{
fastTimer.Enabled = false;
Trace.WriteLine("fast timer ticked");
m_counter = 0;
fastTimer.Stop ();
}
private void slowCheckTimer_Tick(object sender, EventArgs e)
{
Trace.WriteLine("slow check timer ticked");
slowCheckTimer.Stop ();
slowCheckTimer.Enabled = false;
}
Take a look at the Control.MouseWheel event.
As suggested by the Sam Holder i am posting here a modified verion of his advice to help other programmers facing the same problem.
public partial class Form1 : Form
{
int m_counter = 0;
public Form1()
{
InitializeComponent();
// Attach Mouse Wheel Event
this.MouseWheel += new MouseEventHandler(Form1_MouseWheel);
}
void Form1_MouseWheel(object sender, MouseEventArgs e)
{
// Refresh Slow Timer
slowTimer.Enabled = false;
slowTimer.Stop();
slowTimer.Interval = 150;
slowTimer.Start();
slowTimer.Enabled = true;
// Incremenet counter
m_counter++;
// Start Fast Timer
if (fastTimer.Enabled == false)
{
fastTimer.Enabled = true;
fastTimer.Interval = 50;
fastTimer.Start();
}
// If this returns true call
// the fast scroll implementation
if (m_counter > 4)
{
Console.WriteLine("Quick Method Called");
m_counter = 0;
fastTimer.Stop();
slowTimer.Enabled = false;
slowCheckTimer.Stop();
slowCheckTimer.Interval = 200;
slowCheckTimer.Start();
slowCheckTimer.Enabled = true;
}
}
private void slowTimer_Tick(object sender, EventArgs e)
{
if (slowCheckTimer.Enabled == false)
{
Console.WriteLine("Slow Method Called");
}
slowTimer.Enabled = false;
}
private void fastTimer_Tick(object sender, EventArgs e)
{
fastTimer.Enabled = false;
m_counter = 0;
fastTimer.Stop();
}
private void slowCheckTimer_Tick(object sender, EventArgs e)
{
slowCheckTimer.Stop();
slowCheckTimer.Enabled = false;
}
}

How can I catch both single-click and double-click events on WPF FrameworkElement?

I can catch a single-click on a TextBlock like this:
private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("you single-clicked");
}
I can catch a double-click on a TextBlock like this:
private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
if (e.ClickCount == 2)
{
MessageBox.Show("you double-clicked");
}
}
}
But how do I catch them both on a single TextBlock and differentiate between the two?
You need to fire the event after the click sequence is over... when is that? I suggest using a timer. The MouseDown event would reset it and increase the click count. When timer interval elapses it makes the call to evaluate the click count.
private System.Timers.Timer ClickTimer;
private int ClickCounter;
public MyView()
{
ClickTimer = new Timer(300);
ClickTimer.Elapsed += new ElapsedEventHandler(EvaluateClicks);
InitializeComponent();
}
private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e)
{
ClickTimer.Stop();
ClickCounter++;
ClickTimer.Start();
}
private void EvaluateClicks(object source, ElapsedEventArgs e)
{
ClickTimer.Stop();
// Evaluate ClickCounter here
ClickCounter = 0;
}
Cheers!
If you need to detect the difference, I suggest you use a control such as Label that does the work for you:
label.MouseDown += delegate(object sender, MouseEventArgs e)
{
if (e.ClickCount == 1)
{
// single click
}
};
label.MouseDoubleClick += delegate
{
// double click
};
EDIT: My advice was following from documentation on MSDN:
The Control class defines the
PreviewMouseDoubleClick and
MouseDoubleClick events, but not
corresponding single-click events. To
see if the user has clicked the
control once, handle the MouseDown
event (or one of its counterparts) and
check whether the ClickCount property
value is 1.
However, doing so will give you a single click notification even if the user single clicks.
You must use a timer to differentiate between the two. Add a timer to your form in the GUI (easiest that way - it will automatically handle disposing etc...). In my example, the timer is called clickTimer.
private bool mSingleClick;
private void TextBlock_MouseUp(object sender, MouseButtonEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
if (e.ClickCount < 2)
{
mSingleClick = true;
clickTimer.Interval = System.Windows.Forms.SystemInformation.DoubleClickTime;
clickTimer.Start();
}
else if (e.ClickCount == 2)
{
clickTimer.Stop();
mSingleClick = false;
MessageBox.Show("you double-clicked");
}
}
}
private void clickTimer_Tick(object sender, EventArgs e)
{
if (mSingleClick)
{
clickTimer.Stop();
mSingleClick = false;
MessageBox.Show("you single-clicked");
}
}
I did it this Way and it works perfectly
If e.Clicks = 2 Then
doubleClickTimer.Stop()
ElseIf e.Clicks = 1 Then
doubleClickTimer.Enabled = True
doubleClickTimer.Interval = 1000
doubleClickTimer.Start()
End If
Private Sub doubleClickTimer_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles doubleClickTimer.Tick
OpenWebPage("abc")
doubleClickTimer.Stop()
End Sub
You are simply can use MouseDown event and count click number, like this:
if (e.ChangedButton == MouseButton.Left && e.ClickCount == 2)
{
// your code here
}
My suggestion, implemented in a UserControl by simply using a Task:
private int _clickCount = 0;
protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
{
_clickCount = e.ClickCount;
}
protected override async void OnPreviewMouseUp(MouseButtonEventArgs e)
{
if (_clickCount > 1)
{
//apparently a second mouse down event has fired => this must be the second mouse up event
//no need to start another task
//the first mouse up event will be handled after the task below
return;
}
await Task.Delay(500);
if (_clickCount == 1)
{
//single click
}
else
{
//double (or more) click
}
}
The drawback of all these solutions is, of course, that there will be a delay before actually responding to the user's action.
You could do it on MouseUp instead of MouseDown. That way you can ask the ClickCount property for the total number of clicks, and decide what to do from that point.
It's my working solution :)
#region message label click --------------------------------------------------------------------------
private Timer messageLabelClickTimer = null;
private void messageLabel_MouseUp(object sender, MouseButtonEventArgs e)
{
Debug.Print(e.ChangedButton.ToString() + " / Left:" + e.LeftButton.ToString() + " Right:" + e.RightButton.ToString() + " click: " + e.ClickCount.ToString());
// in MouseUp (e.ClickCount == 2) don't work!! Always 1 comes.
// in MouseDown is set e.ClickCount succesfully (but I don't know should I fire one clicked event or wait second click)
if (e.ChangedButton == MouseButton.Left)
{
if (messageLabelClickTimer == null)
{
messageLabelClickTimer = new Timer();
messageLabelClickTimer.Interval = 300;
messageLabelClickTimer.Elapsed += new ElapsedEventHandler(messageLabelClickTimer_Tick);
}
if (! messageLabelClickTimer.Enabled)
{ // Equal: (e.ClickCount == 1)
messageLabelClickTimer.Start();
}
else
{ // Equal: (e.ClickCount == 2)
messageLabelClickTimer.Stop();
var player = new SoundPlayer(ExtraResource.bip_3short); // Double clicked signal
player.Play();
}
}
}
private void messageLabelClickTimer_Tick(object sender, EventArgs e)
{ // single-clicked
messageLabelClickTimer.Stop();
var player = new SoundPlayer(ExtraResource.bip_1short); // Single clicked signal
player.Play();
}
#endregion
My issue was with single/double-clicking rows in a DataGrid in WPF. For some reason the ButtonDown events weren't firing, only the OnMouseLeftButtonUp event was. Anyway, I wanted to handle the single-click differently from the double-click. It looks me a little time (I'm sure the solution isn't perfect, but it appears to work) to distill the problem down until I got it down to the below. I created a Task which calls an Action and that Action's target can be updated by a second click. Hope this helps someone!
private Action _clickAction;
private int _clickCount;
private void Grid_OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("Button Click Occurred");
_clickCount++;
if (_clickCount == 1)
{
_clickAction = SingleClick;
}
if (_clickCount > 1)
{
_clickAction = DoubleClick;
}
if (_clickCount == 1)
{
Task.Delay(200)
.ContinueWith(t => _clickAction(), TaskScheduler.FromCurrentSynchronizationContext())
.ContinueWith(t => { _clickCount = 0; });
}
}
private void DoubleGridClick()
{
Debug.WriteLine("Double Click");
}
private void SingleGridClick()
{
Debug.WriteLine("Single Click");
}

Categories

Resources