How to stop a timer counter at zero using C#? - c#

I have used the following code:
DispatcherTimer sec = new DispatcherTimer();
sec.Interval = new TimeSpan(0, 0, 0, 1);
sec.Tick += delegate
{
lblsec.Text = b--.ToString() + " Seconds.";
};
sec.Start();
c--;
this code will display a counter start from 5 and will decreases down and it will go negative.
my question is how to stop it when it reaches zero?

First, your timer interval is way too short. You're never going to get single millisecond timer intervals from Windows, and for UI purposes the user is never going to perceive timer updates that quickly. For something like this, 100ms or longer is much more appropriate.
Second, you cannot count on the timer being very precise. If you, for example, specify an interval of 100ms, you might get called back ten times in a second, but often you won't. It will depend on the resolution of the Windows thread scheduler as well as what other activities the UI thread is doing.
With that in mind, and with the assumption that what you are trying to do here is set a five-second timer and display the countdown to the user, something like this should work:
TimeSpan total = TimeSpan.FromSeconds(5);
DispatcherTimer timer = new DispatcherTimer();
Stopwatch sw = new Stopwatch();
timer.Interval = TimeSpan.FromMilliseconds(100);
timer.Tick += (sender, e) =>
{
double secondsLeft = (total - sw.Elapsed).TotalSeconds;
if (secondsLeft <= 0)
{
timer.Stop();
secondsLeft = 0;
}
lblsec.Text = secondsLeft.ToString("0.0") + " Seconds";
};
sw.Start();
timer.Start();
Addendum:
Here is a complete WPF program illustrating how the above code might be used:
C#:
class TimerModel : INotifyPropertyChanged
{
private TimeSpan _timeLeft;
private readonly ICommand _startCommand;
public TimeSpan TimeLeft
{
get { return _timeLeft; }
set
{
if (value != _timeLeft)
{
_timeLeft = value;
OnPropertyChanged();
}
}
}
public ICommand Start { get { return _startCommand; } }
public TimerModel()
{
_startCommand = new StartCommand(this);
}
private class StartCommand : ICommand
{
private bool _running;
private readonly TimerModel _timerModel;
public bool CanExecute(object parameter)
{
return !_running;
}
public event EventHandler CanExecuteChanged;
public StartCommand(TimerModel timerModel)
{
_timerModel = timerModel;
}
public void Execute(object parameter)
{
TimeSpan total = TimeSpan.FromSeconds(5);
DispatcherTimer timer = new DispatcherTimer();
Stopwatch sw = new Stopwatch();
timer.Interval = TimeSpan.FromMilliseconds(100);
timer.Tick += (sender, e) =>
{
TimeSpan timeLeft = total - sw.Elapsed;
if (timeLeft <= TimeSpan.Zero)
{
timer.Stop();
timeLeft = TimeSpan.Zero;
_running = false;
OnCanExecuteChanged();
}
_timerModel.TimeLeft = timeLeft;
};
sw.Start();
timer.Start();
_running = true;
OnCanExecuteChanged();
}
private void OnCanExecuteChanged()
{
EventHandler handler = CanExecuteChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
XAML:
<Window x:Class="TestSO27333077CountdownTimer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:TestSO27333077CountdownTimer"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<l:TimerModel/>
</Window.DataContext>
<StackPanel>
<Button Content="Start" Command="{Binding Start}" HorizontalAlignment="Left"/>
<TextBlock Text="{Binding TimeLeft.TotalSeconds, StringFormat={}{0:0.0} Seconds}"/>
</StackPanel>
</Window>
Note that the time-keeping can also be done using the DateTime.UtcNow property instead of a Stopwatch. For example, you could change the StartCommand.Execute() method so that it looks like this instead:
public void Execute(object parameter)
{
DateTime finishTime = DateTime.UtcNow + TimeSpan.FromSeconds(5);
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(100);
timer.Tick += (sender, e) =>
{
TimeSpan timeLeft = finishTime - DateTime.UtcNow;
if (timeLeft <= TimeSpan.Zero)
{
timer.Stop();
timeLeft = TimeSpan.Zero;
_running = false;
OnCanExecuteChanged();
}
_timerModel.TimeLeft = timeLeft;
};
timer.Start();
_running = true;
OnCanExecuteChanged();
}

Related

Xamarin Timer crashes

So I've done this in code:
public partial class Timer : ContentView , INotifyPropertyChanged
{
private int seconds = 30;
private System.Timers.Timer timer;
public Timer()
{
InitializeComponent();
timer = new System.Timers.Timer();
timer.Interval = 1000;
timer.AutoReset = true;
timer.Elapsed += new System.Timers.ElapsedEventHandler(OneSecondPassed);
timer.Enabled = true;
}
private void OneSecondPassed(object source, System.Timers.ElapsedEventArgs e)
{
seconds--;
Time = seconds.ToString();
}
public event PropertyChangedEventHandler PropertyChanged;
public string Time
{
get => seconds.ToString();
set
{
Time = value;
if (PropertyChanged != null)
{
PropertyChanged(this , new PropertyChangedEventArgs("Time"));
}
}
}
}
and then bound my label's text to it in XAML:
<Label BindingContext ="{x:Reference this}"
Text="{Binding Time}"/>
When I start the app, it crashes...I don't really understand how PropertyChanged works, just that INotifyPropertyChanged implements it.Also, when I declare PropertyChanged, it tells me that BindableObject.PropertyChanged already exists, use new in order to hide it.If you could explain how the interface and its event works, I'd be really thankful.
your setter is creating an infinite loop.
set
{
// this will call the setter again, infinitely
Time = value;
...
}
you already have a private variable for seconds, you should use it here
public int Time
{
get => seconds;
set
{
seconds = value;
if (PropertyChanged != null)
{
PropertyChanged(this , new PropertyChangedEventArgs("Time"));
}
}
}
private void OneSecondPassed(object source, System.Timers.ElapsedEventArgs e)
{
Time--;
}
when I declare PropertyChanged, it tells me that BindableObject.PropertyChanged already exists
I see that Timer inherits from ContentView.
ContentView is a BindableObject, so it already implements everything used in binding, such as PropertyChanged. Delete your declaration of PropertyChanged.
OPTIONAL: You could also remove , INotifyPropertyChanged - ContentView does that for you. However it is harmless to leave it there.

Xamarin Timer not updating

I am making a fitness app so I need a timer.I have done this in code-behind:
public partial class Timer : ContentView
{
private int seconds = 30;
private System.Timers.Timer timer;
public Timer()
{
InitializeComponent();
timer = new System.Timers.Timer();
timer.Interval = 1000;
timer.AutoReset = true;
timer.Elapsed += new System.Timers.ElapsedEventHandler(OneSecondPassed);
timer.Enabled = true;
}
private void OneSecondPassed(object source, System.Timers.ElapsedEventArgs e)
{
seconds--;
}
public string Time
{
get => seconds.ToString();
}
}
And then for the UI, I made a label and bound its text property to my Time property:
<Label BindingContext ="{x:Reference this}"
Text="{Binding Time}"/>
//"this" is a reference to my class
When I start the app, the timer remains 30. I know that "seconds" is for sure decreasing, so there must be a problem with the binding.I know I could've just updated the text property of the label inside OneSecondPassed , but I'd like to learn more about data binding.Help?
As Jason said, implementing the INotifyPropertyChanged interface is a good choice.
For your better understanding, I wrote a runnable project that meets your requirements for your reference.
Here is the xaml code:
<StackLayout>
<Label Text="{Binding DateTime}"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="Center"/>
</StackLayout>
Here is the cs code:
public partial class MainPage : ContentPage, INotifyPropertyChanged
{
int dateTime;
public event PropertyChangedEventHandler PropertyChanged;
public MainPage()
{
InitializeComponent();
this.DateTime = 30;
Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
if (DateTime > 0)
{
DateTime--;
}
return true;
});
BindingContext = this;
}
public int DateTime
{
set
{
if (dateTime != value)
{
dateTime = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("DateTime"));
}
}
}
get
{
return dateTime;
}
}
}

List of Stopwatches - restarting begins all timers due to binding

Hope you can help ... I have a list of activities which use a stopwatch to track each one of their activity durations. When I tap an activity in my list, it starts the stopwatch for the activity tapped and when I tap the activity row again it stops the stopwatch and resets it.
However when I then tap another activity, although it is really only updating the duration for the activity tapped in the backend, the frontend UI updates all of the activities timers with the same activity time tapped because they are binded to the same activityduration element. I don't know how to only change the activity duration for the activity tapped in the front end. Could anyone advise on how to do this without re-structuring my model/collection? Or will I have to make a subnest for the activity name in my collection?
On tap event in code behind:-
public async void OnActivityTap(object sender, EventArgs e)
{
var item = (ViewCell)sender;
UserActivities.Activities myactivitiesModel = item.BindingContext as UserActivities.Activities;
if (myactivitiesModel == null)
{return;}
// OnPropertyChanged("ActivityDuration");
// mystopwatch.Reset();
//ViewModel.getUserActivities();
foreach (var x in ViewModel.UserActivitiesList) {
if(x.ActivityName == myactivitiesModel.ActivityName) {
int seconds = 1;
//if the activity is not enabled and the activity is tapped
if (myactivitiesModel.ActivityEnabled == false)
{
//enable the activity and start the stopwatch
myactivitiesModel.ActivityEnabled = true;
//Get the current duration and add it onto the stopwatch start time
ts = x.ActivityDuration;
//Reset the stopwatch back to zero
mystopwatch = new Stopwatch();
//Start the stopwatch
mystopwatch.Reset();
mystopwatch.Start();
while (myactivitiesModel.ActivityEnabled == true)
{
Device.StartTimer(TimeSpan.FromSeconds(seconds), () =>
{
myactivitiesModel.ActivityDuration = (mystopwatch.Elapsed + ts);
return true;
});
return;
}
}
else if (myactivitiesModel.ActivityEnabled == true)
{
//disable the activity and stop the stopwatch
x.ActivityEnabled = false;
//Stop the clock
mystopwatch.Stop();
//Store activity time stopped
await MongoService.UpdateUserActivityTime(userIdentity, myactivitiesModel.ActivityName, x.ActivityDuration);
//Store the time stopped in the UserActivitiesList binded to the UI list
x.ActivityDuration = myactivitiesModel.ActivityDuration;
OnPropertyChanged("ActivityDuration");
return;
}
} //end of if activityName Tapped
} //end of foreach
} //end of OnTap Activity
Model:
public class UserActivities : INotifyPropertyChanged
{
[BsonId, BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
string _foreignUserID;
[BsonElement("foreignUserID")]
public string foreignUserID
{
get => _foreignUserID; set
{
if (_foreignUserID == value)
return;
_foreignUserID = value;
HandlePropertyChanged();
}
}
[BsonElement("activities")]
public ObservableCollection<Activities> UserTimedActivities { get; set; }
public class Activities : INotifyPropertyChanged
{
string _activityName;
[BsonElement("activityName")]
public string ActivityName
{
get => _activityName; set
{
if (_activityName == value)
return;
_activityName = value;
HandlePropertyChanged();
}
}
TimeSpan _activityDuration;
[BsonElement("activityDuration")]
public TimeSpan ActivityDuration
{
get => _activityDuration; set
{
if (_activityDuration == value)
return;
_activityDuration = value;
HandlePropertyChanged();
}
}
TimeSpan _activityGoalDuration;
[BsonElement("activityGoalDuration")]
public TimeSpan ActivityGoalDuration
{
get => _activityGoalDuration; set
{
if (_activityGoalDuration == value)
return;
_activityGoalDuration = value;
HandlePropertyChanged();
}
}
Boolean _activityEnabled;
[BsonElement("activityEnabled")]
public Boolean ActivityEnabled
{
get => _activityEnabled; set
{
if (_activityEnabled == value)
return;
_activityEnabled = value;
HandlePropertyChanged();
}
}
public Activities(string activityname, TimeSpan activityduration, TimeSpan activitygoalduration, Boolean activityenabled ) {
ActivityName = activityname;
ActivityDuration = activityduration;
ActivityGoalDuration = activitygoalduration;
ActivityEnabled = activityenabled;
}
void HandlePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public UserActivities(ObservableCollection<Activities> usertimedactivities)
{
UserTimedActivities = usertimedactivities;
}
public event PropertyChangedEventHandler PropertyChanged;
void HandlePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
ViewModel method to compile list-
public async void getUserActivities()
{
var userActivities = await MongoService.GetUserActivityData(userIdentity);
try
{
if (IsBusy)
return;
IsBusy = true;
UserActivitiesList.Clear();
foreach (var x in userActivities)
{
foreach(var y in x.UserTimedActivities) {
//foreach (var y in x.userActivities)
UserActivitiesList.Add(y);
}
}
}
catch (Exception ex)
{
IsBusy = false;
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
2. Alternative Timer method suggested by Jason (doesn't get elapsed time or update UI):
public async void OnActivityTap(object sender, EventArgs e)
{
var item = (ViewCell)sender;
UserActivities.Activities myactivitiesModel = item.BindingContext as UserActivities.Activities;
if (myactivitiesModel == null)
{ return; }
foreach (var x in ViewModel.UserActivitiesList)
{
if (x.ActivityName == myactivitiesModel.ActivityName)
{
if (x.ActivityEnabled == false)
{
x.ActivityEnabled = true;
timer.Enabled = true;
timer.Start();
timer.Elapsed += OnTimedEvent;
x.ActivityDuration = x.ActivityDuration.Add(new TimeSpan(0, 0, 0, 0, (int)interval)); //new
return;
}
else
{
x.ActivityEnabled = false;
timer.Enabled = false;
timer.Stop();
x.ActivityDuration = x.ActivityDuration.Add(new TimeSpan(0, 0, 0, 0, (int)interval)); //new
return;
}
**3. Alternative approach with stopwatch - does work but doesn't update UI until tapped **
foreach (var x in ViewModel.UserActivitiesList)
{
if (x.ActivityName == myactivitiesModel.ActivityName)
{
if (x.ActivityEnabled == false)
{
x.ActivityEnabled = true;
//timer.Enabled = true;
mystopwatch.Restart();
mystopwatch.Start();
x.ActivityDuration = mystopwatch.Elapsed + x.ActivityDuration;
NotifyPropertyChanged("ActivityDuration");
//timer.Elapsed += OnTimedEvent;
//x.ActivityDuration = x.ActivityDuration.Add(new TimeSpan(0, 0, 0, 0, (int)interval)); //new
return;
}
else
{
x.ActivityEnabled = false;
//timer.Enabled = false;
mystopwatch.Stop();
x.ActivityDuration = mystopwatch.Elapsed + x.ActivityDuration;
NotifyPropertyChanged("ActivityDuration");
//x.ActivityDuration = x.ActivityDuration.Add(new TimeSpan(0, 0, 0, 0, (int)interval)); //new
return;
}
I think you're making this way too complicated. I would create a single timer and do something like this
using System.Timers;
...
// 100ms == .1s
double interval = 100;
// CREATE ONE TIMER FOR ALL ACTIVITIES
Timer timer = new Timer(interval);
timer.Elapsed += UpdateTimers;
timer.Start();
...
private void UpdateTimers(object sender, EventArgs a)
{
// this is psudocode, update for your model
foreach(var a in Activities)
{
// if activity is selected
if (a.Active) {
// update the elapsed time
a.Elapsed = a.Elapsed.Add(new Timespan(0,0,0,0,interval));
}
}
}
Final Solution as implemented by OP
public page()
{
timer.Start();
timer.Elapsed += UpdateTimers;
}
private void UpdateTimers(object sender, EventArgs a)
{
foreach (var x in ViewModel.UserActivitiesList)
{
if (x.ActivityEnabled)
{
x.ActivityDuration = x.ActivityDuration.Add(new
TimeSpan(0, 0, 0, 0, (int)interval));
}
}
}

c# Bind countdown timer to textbox in windows store app

I'm fairly new to C# and to programming in general.
In a previous question "C# Reset a countdown timer-DispatcherTimer-" I got help to reset my timer. Then I tried to make my code more elegant and tried to create a separate class for the timer and update the countdown text block through databinding instead of hardcoding the text property in this line in timer_Tick():
Countdown.Text = (int)(duration - sw.Elapsed).TotalSeconds + " second(s)
My problem is that the binding fails. I still struggle with MVVM. Here is my code:
CountDownTimer.cs
class CountDownTimer : DispatcherTimer
{
public System.Diagnostics.Stopwatch sw { get; set; }
static readonly TimeSpan duration = TimeSpan.FromSeconds(60);
private int _seconds;
public int Seconds
{
get { return _seconds; }
set { _seconds = value; NotifyPropertyChanged("Seconds"); }
}
private string _timeElapsed;
public string TimeElapsed
{
get { return _timeElapsed; }
set { _timeElapsed = value; NotifyPropertyChanged("TimeElapsed"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public void timer_Tick(object sender, object e)
{
if (sw.Elapsed <= duration)
{
Seconds = (int)(duration - sw.Elapsed).TotalSeconds;
TimeElapsed = String.Format("{0} second(s)", Seconds);
}
else
{
TimeElapsed = "Times Up";
this.Stop();
}
}
}
EquationView.xaml
<StackPanel x:Name="timePanel" Orientation="Horizontal" Visibility="Collapsed">
<TextBlock Text="Time Left: " Height="auto"
Margin="20,10,5,10" FontSize="26"/>
<TextBlock x:Name="countdown" Text="{Binding TimeElapsed}"
Margin="20,10,20,10" Width="200"
Height="auto" FontSize="26"/>
</StackPanel>
EquationView.xaml.cs
public sealed partial class EquationView : Page
{
//code
private void startButton_Click(object sender, RoutedEventArgs e)
{
//more code
// If level == difficult enable timer
if (Level == PlayerModel.LevelEnum.Difficult)
{
// timer commands
timer.sw = System.Diagnostics.Stopwatch.StartNew();
timer.Interval = new TimeSpan(0, 0, 0, 1);
timer.Tick += timer.timer_Tick;
timer.Start();
countdown.DataContext = timer;
//more code
} //end of method
// much more code
} //end of class EquationView
I inserted the line countdown.Text = timer.TimeElapsed; to try to figure out what was off and it gave me a System.NullReferenceException. Then I changed it to timer.Seconds.ToString() the first time it showed 0 but after that it returns 56 or 57.
p.s. I retyped the property changed method from my BindableBase class because I don't want to deal with multiple inheritance right now.
I changed the countdown timer seeing this question: How do I display changing time within a TextBlock?. I'd seen before I started building the timer but it helped me more now.
CountDownTimer.cs
public class CountDownTimer : BindableBase
{
System.Diagnostics.Stopwatch sw;
static readonly TimeSpan duration = TimeSpan.FromSeconds(60);
private DispatcherTimer timer;
public CountDownTimer()
{
timer = new DispatcherTimer();
sw = System.Diagnostics.Stopwatch.StartNew();
timer.Interval = new TimeSpan(0, 0, 0, 1);
timer.Tick += timer_Tick;
}
private int? _seconds;
public int? Seconds
{
get { return _seconds; }
set { _seconds = value; NotifyPropertyChanged("Seconds"); }
}
private string _timeElapsed;
public string TimeElapsed
{
get { return _timeElapsed; }
set { _timeElapsed = value; NotifyPropertyChanged("TimeElapsed"); }
}
public void timer_Tick(object sender, object e)
{
if (sw.Elapsed < duration)
{
Seconds = (int)(duration - sw.Elapsed).TotalSeconds;
TimeElapsed = String.Format("{0} second(s)", Seconds);
}
else
{
TimeElapsed = "Times Up";
timer.Stop();
}
}
public void StartCountDown()
{
sw.Start();
timer.Start();
}
public void StopCountDown()
{
timer.Stop();
sw.Stop();
}
}

Databinding issue with stopwatched elapsed

I am trying to make a class that when it starts it starts a stopwatch and all the time the elapsed time is written to a local variable Elapsed which I have a Listview that databinds to. But when I use this code the Listview just displays 00:00:00.00000001 and never changes.
The class' code is:
namespace project23
{
public class ActiveEmployee
{
public int EmpID { get; set; }
public string EmpName { get; set; }
private DateTime date;
private BackgroundWorker worker;
public Stopwatch sw;
public ActiveEmployee(int empID, string empName)
{
date = DateTime.Now;
worker = new BackgroundWorker();
worker.DoWork += BackgroundWork;
worker.WorkerReportsProgress = true;
worker.RunWorkerAsync();
}
private TimeSpan elapsed;
public TimeSpan Elapsed
{
get { return elapsed; }
set
{
elapsed = value;
NotifyPropertyChanged("Elapsed");
}
}
private void BackgroundWork(object sender, DoWorkEventArgs args)
{
sw = new Stopwatch();
sw.Start();
if(true)
{
Elapsed = sw.Elapsed;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
}
Now it works using Timers instead
using System;
using System.ComponentModel;
using System.Timers;
namespace Eksamen_Januar_2011
{
public class ActiveEmployee : INotifyPropertyChanged
{
public int EmpID { get; set; }
public string EmpName { get; set; }
private DateTime startDate;
private BackgroundWorker worker;
private Timer timer;
public ActiveEmployee(int empID, string empName)
{
startDate = DateTime.Now;
worker = new BackgroundWorker();
worker.DoWork += BackgroundWork;
timer = new Timer(1000);
timer.Elapsed += TimerElapsed;
worker.RunWorkerAsync();
}
private TimeSpan elapsed;
public TimeSpan Elapsed
{
get { return elapsed; }
set
{
elapsed = value;
NotifyPropertyChanged("Elapsed");
}
}
private void BackgroundWork(object sender, DoWorkEventArgs args)
{
timer.Start();
}
private void TimerElapsed(object sender, ElapsedEventArgs e)
{
Elapsed = DateTime.Now - startDate;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
}
You need to implement the INotifyPropertyChanged interface. Modify your class declaration to:
public class Employee : System.ComponentModel.INotifyPropertyChanged
See http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.aspx.
Data-binding and observing change relies on change-notification events; either *Changed (for property *) or INotifyPropertyChanged typically (although custom implementations are possible).
Stopwatch does not provide such; you may be better off using a Timer instead, to update the UI every so often. Just store the DateTime when you start, and whenever the timer fires calculate the offset from then to now.
What's the point of having the if(true) statement? Did you mean to write while(true)?
You're only updating the elapsed time once in the thread, then it exists.. which is what you're seeing on the form

Categories

Resources