C# Calling a function repeatedly to increase value of label automatically
This is a WPF program. It has a label on it (minuteTimerLabel) that displays a two digit number, starting from 00, 01, 02 and so on up to 99. I want two features from this label. First, when the left mouse button goes up on the label (simulating a click), the number increases by 1. I have been able to implement this. But I have problem with the second feature.
private void MinuteTimerLabel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
while(true)
{
if (timerMinute == 99)
{
return;
}
timerMinute += 1;
minuteTimerLabel.Content = String.Format("{0:00}", timerMinute);
Thread.Sleep(250);
}
}
When the left mouse button is held down on the label for a few seconds, the number should keep increasing one by one automatically.
timerMinute is a global int variable.
With my current code, the entire program locks up and nothing works. When I remove the while(true), number increases only once when pressing mouse down. If I release the mouse and press again, it works but again only once.
To avoid locking up the UI you need to use some form of async coding. I'd suggest using Microsoft's Reactive Framework.
NuGet "System.Reactive.Windows.Threading" to get the bits and add using System.Reactive.Linq; to the top of your code.
Then you can do this:
private IDisposable _subscription = null;
private void MinuteTimerLabel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_subscription =
Observable
.Interval(TimeSpan.FromMilliseconds(250.0))
.Select(x => String.Format("{0:00}", x))
.ObserveOnDispatcher()
.Subscribe(x => MinuteTimerLabel.Content = x);
}
private void MinuteTimerLabel_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_subscription.Dispose();
}
It's super simple and super clean.
private IDisposable _subscription = null;
private int _counter = 0;
private void MinuteTimerLabel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_subscription =
Observable
.Interval(TimeSpan.FromMilliseconds(250.0))
.Select(x => String.Format("{0:00}", x))
.ObserveOnDispatcher()
.Subscribe(x => MinuteTimerLabel.Content = _counter++ % 100);
}
private void MinuteTimerLabel_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_subscription.Dispose();
}
Try the below code:
The code will run in the another thread un-blocking the Main Thread.
private void MinuteTimerLabel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
new Thread(Timer).Start();
}
private void Timer() {
while(true)
{
if (timerMinute == 99)
{
return;
}
timerMinute += 1;
minuteTimerLabel.Content = String.Format("{0:00}", timerMinute);
Thread.Sleep(250);
}
}
Good fit for a CancellationToken and Async Await Pattern
private CancellationTokenSource _cs;
private int _timerMinute =0;
private async void Label1_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_cs?.Cancel();
_cs?.Dispose();
_cs = new CancellationTokenSource();
try
{
while (!_cs.Token.IsCancellationRequested)
{
await Task.Delay(200, _cs.Token);
Label1.Content = $"{++_timerMinute:00}";
}
}
catch (OperationCanceledException) {}
}
private void Label1_OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_cs?.Cancel();
_cs?.Dispose();
}
Also if you wanted to be tricky you could add weight to it the more the mouse is down, by altering the delay
You need to bind couple of event in this case, PreviewMouseDown and PreviewMouseUp. Then calculate time different between it and increment the value for label on ticks/secs.
Like this
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
PreviewMouseDown += Window3_PreviewMouseDown;
PreviewMouseUp += Window3_PreviewMouseUp;
}
DateTime mouseDown;
private void Window3_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
mouseDown = DateTime.Now;
}
private void Window3_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
var seconds = DateTime.Now.Subtract( mouseDown ).Seconds;
//Condition code goes here..
minuteTimerLabel.Content = seconds;
}
}
Related
How to use timers to trigger click button event every 3 seconds?
I'm trying to rotate 2 pictures in pictureboxes by triggering the rotate button automaticly using timer but it seems doesnt works. I never used timer before so this is my first time. Anyone know whats wrong with my code or any other code suggestion for it? Thanks
Code I'm using
private void timer1_Tick(object sender, EventArgs e)
{
rotateRightButton_Click(null, null);
pictureBox1.Refresh();
pictureBox2.Refresh();
}
private void timerStartButton_Click(object sender, EventArgs e)
{
timer1.Start();
}
private void timerStopButton_Click(object sender, EventArgs e)
{
timer1.Stop();
}
It's even possible (and more simple) with tasks
public partial class Form1 : Form
{
// variable to keep track if the timer is running.
private bool _timerRunning;
public Form1()
{
InitializeComponent();
}
private async Task StartTimer()
{
// set it to true
_timerRunning = true;
while (_timerRunning)
{
// call the rotateRightButton_Click (what you want)
rotateRightButton_Click(this, EventArgs.Empty);
pictureBox1.Refresh();
pictureBox2.Refresh();
// wait for 3 seconds (but don't block the GUI thread)
await Task.Delay(3000);
}
}
private void rotateRightButton_Click(Form1 form1, EventArgs empty)
{
// do your thing
}
private async void buttonStart_Click(object sender, EventArgs e)
{
// if it's already started, don't start it again.
if (_timerRunning)
return;
// start it.
await StartTimer();
}
private void buttonStop_Click(object sender, EventArgs e)
{
// stop it.
_timerRunning = false;
}
}
timer1.Interval = 3000; // set interval to 3 seconds and then call Time Elapsed event
timer1.Elapsed += Time_Elapsed;
//Event
private void Time_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
// will be triggered in every 3 seconds
rotateRightButton_Click(null, null);
pictureBox1.Refresh();
pictureBox2.Refresh();
}
Hope this helps!
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");
});
I want to hide the cursor after a certain time (if the mouse is not moving), and I want to show the cursor when I move the mouse in a picturebox. I just can't get it to work... This is what I have tried:
// this Never seem to hide the cursor
private void picBox_MouseMove(object sender, MouseEventArgs e)
{
Cursor.Show();
tim.Stop();
tim.Start();
}
private void tim_Tick(object sender, EventArgs e)
{
Cursor.Hide();
tim.Stop();
}
-
// works but in this case I want cursor.ico to be a resource
private void picBox_MouseMove(object sender, MouseEventArgs e)
{
Cursor.Current = Cursors.Default;
tim.Stop();
tim.Start();
}
private void tim_Tick(object sender, EventArgs e)
{
Cursor.Current = new Cursor("cursor.ico");
tim.Stop();
}
-
// Properties.Resources.cursor gives an error even though I added it to my resources
// cannot convert from 'System.Drawing.Icon' to 'System.IntPtr'
private void picBox_MouseMove(object sender, MouseEventArgs e)
{
Cursor.Current = Cursors.Default;
tim.Stop();
tim.Start();
}
private void tim_Tick(object sender, EventArgs e)
{
Cursor.Current = new Cursor(Properties.Resources.cursor);
tim.Stop();
}
You need to have a timer and handle its Tick event. In the Tick event, check if the last movement of the mouse was before the certain time, then hide the cursor using Cursor.Hide(). Also handle MouseMove of the PictureBox and show the cursor using Cursor.Show() method.
Note: Don't forget to enable the timer and set Interval of timer to a short value, for example 300 and change duration value in the following code, for a shorter/longer inactive time:
DateTime? lastMovement;
bool hidden = false;
int duration = 2;
void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
lastMovement = DateTime.Now;
if (hidden)
{
Cursor.Show();
hidden = false;
}
}
private void timer1_Tick(object sender, EventArgs e)
{
if (!lastMovement.HasValue)
return;
TimeSpan elaped = DateTime.Now - lastMovement.Value;
if (elaped >= TimeSpan.FromSeconds(duration) && !hidden)
{
Cursor.Hide();
hidden = true;
}
}
In my program i'm starting for loop using button, I want to break this for loop using another button.
For example:
private void button1_Click(object sender, EventArgs e)
{
for( int i = 0; i < var; i++)
{
//doing something
}
}
And using second button break loop,
private void button2_Click(object sender, EventArgs e)
{
//breaking loop;
}
Need help :)
Set a flag in button2_Click() method and check it in the button1_Click()'s loop.
In order to process Windows events and allow button2_Click() handle to run while iterating, add Application.DoEvents() in your loop:
bool breakLoop = false;
private void button1_Click(object sender, EventArgs e)
{
breakLoop = false;
for( int i = 0; i < var && !breakLoop; i++)
{
//doing something
Application.DoEvents();
}
}
private void button2_Click(object sender, EventArgs e)
{
breakLoop = true;
}
You cannot do that, because the loop in button1_Click event handler will be holding the UI thread. Your user interface will not respond to any event, showing hourglass icon, until the loop is over. This means that button2_Click cannot be entered until button1_Click has completed.
You need to replace the long-running loop from the event handler with something that runs outside the UI thread. For example, you can use Tasks, which can be cancelled using CancellationToken (related Q&A).
Arguably it would be better to use threads and cancellation tokens in some form, rather than the Application.DoEvents(). Something like this:
private CancellationTokenSource loopCanceller = new CancellationTokenSource();
private void button1_Click(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
{
try
{
for (int i = 0; i < 100; i++)
{
this.loopCanceller.Token.ThrowIfCancellationRequested(); // exit, if cancelled
// simulating half a second of work
Thread.Sleep(500);
// UI update, Invoke needed because we are in another thread
Invoke((Action)(() => this.Text = "Iteration " + i));
}
}
catch (OperationCanceledException ex)
{
loopCanceller = new CancellationTokenSource(); // resetting the canceller
Invoke((Action)(() => this.Text = "Thread cancelled"));
}
}, loopCanceller.Token);
}
private void button2_Click(object sender, EventArgs e)
{
loopCanceller.Cancel();
}
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");
}