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
Related
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.
Trying to get clear about flaw in this code:
Scenario 1:
This scenario uses data binding and causes the very well known cross-thread exception in the NotifyPropertyChanged() method in the PriceSimulator class.
Scenario 2:
This scenario solves the problem by subscribing to the PropertyChanged event of PriceSimulator, eliminates the cross-thread issue but has to avoid data binding altogether.
Assuming Scenario 1 was the intended scenario and assuming one has no knowledge of the inner workings of PriceSimulator and just wanted to bind to the Price property, what is the core issue here?
Form1.cs:
public partial class Form1 : Form
{
PriceSimulator simul;
Action labelAction;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
labelAction = new Action(SetLabel);
simul = new PriceSimulator(5, 1000);
//Scenario 1:
//Use data binding and get Cross-Thread exception
//label1.DataBindings.Add("Text", simul, "Price");
//Scenario 2:
//This works fine
//Subscribe to PropertyChanged event
simul.PropertyChanged += task_PropertyChanged;
simul.Start();
}
//Scenario 2:
void task_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (label1.InvokeRequired)
Invoke(labelAction);
else SetLabel();
}
private void SetLabel()
{
label1.Text = simul.Price.ToString("C2");
}
}
PriceSimulator.cs:
public class PriceSimulator : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int max, delay, priceValue;
private Timer timer;
public PriceSimulator(int max, int delay)
{
this.max = max;
this.delay = delay;
}
public void Start()
{
timer = new Timer(CallbackProc, null, delay, delay);
}
private void CallbackProc(object obj)
{
if (++Price >= max)
timer.Dispose();
}
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
try
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
catch (Exception ex)
{
timer.Dispose();
System.Windows.Forms.MessageBox.Show(ex.Message);
}
}
public int Price
{
get
{
return priceValue;
}
set
{
if (priceValue != value)
{
priceValue = value;
NotifyPropertyChanged();
}
}
}
}
You have to have the current context in your PriceSimulator class:
private readonly SynchronizationContext _context = SynchronizationContext.Current;
Now that you have the context, you can use it to update the UI:
_context.Post(delegate
{
if (++Price >= max)
timer.Dispose();
}, null);
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();
}
}
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();
}
I have a silverlight application that simply plots points on a graph. The points that it plots come from a sql query.
The silverlight program will have to run the query and pull the relevant data on its own.
How do i achieve this kind of functionality??
thanks!
You don't say how many times it needs to plot the data per sec/min? Is it just to plot once. If so then when your app first loads write an asynchronous call and have the call query sql and return the results in the callback..
If the program needs to return data at set intervals then you'll need a dispatcher timer or something similar..
Ok something like this..
public class MyClass : INotifyPropertyChanged
{
public MyClass()
{
DispatcherTimer timer = new DispatcherTimer();
timer.Tick += OnTimerTick;
timer.Interval = TimeSpan.FromSeconds(300);
}
private void OnTimerTick(object sender, EventArgs e)
{
var result = await UpdateGraphPoints();
MyGraphPoints = this.PopulateTheGraph(result);
}
private async Task<List<MyGraphPoint>> UpdateGraphPoints()
{
var oper = await YourDatabaseQueryMethod();
return oper;
}
private ObservableCollection<MyGraphPoint> PopulateTheGraph(object result)
{
}
private ObservableCollection<MyGraphPoint> myGraphPoints;
public ObservableCollection<MyGraphPoint> MyGraphPoints
{
get { return this.myGraphPoints; }
set
{
myGraphPoints = value;
OnPropertyChanged("MyGraphPoints");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class MyGraphPoint : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int xValue;
public int XValue
{
get { return xValue; }
set
{
this.xValue = value;
this.OnPropertyChanged("XValue");
}
}
private int yValue;
public int YValue
{
get { return yValue; }
set
{
this.yValue = value;
this.OnPropertyChanged("YValue");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And then in your xaml - bind the MyGraphPoints observable collection to your graph control.