Asynchronous digital clock when sending data to serial port
I have a WPF application and digital clock with the Kinetis development framework. WPF sends start time and 5 alarms. But when Kinetis sends a notification that the alarm has been activated, the WPF application read it and showed it as a message box, but the digital clock that is in the application stops when the message box is activated. How to make the clock work?
CS
public partial class MainWindow : Window
{
SerialPort sp;
public MainWindow()
{
InitializeComponent();
}
void timer_Tick(object sender, EventArgs e)
{
lblTime.Content = Convert.ToDateTime(lblTime.Content).AddSeconds(1).ToLongTimeString();
int btr = sp.BytesToRead;
if (btr != 0)
{
string alarma = char.ConvertFromUtf32(sp.ReadChar());
MessageBoxResult result = MessageBox.Show("La alarma " + alarma + " se activo", "Alarma", MessageBoxButton.OK, MessageBoxImage.Exclamation);
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
int intHoras = Convert.ToInt32(Horas.Text == "" ? "-1" : Horas.Text);
int intMinutos = Convert.ToInt32(Minutos.Text == "" ? "-1" : Minutos.Text);
int intHoras1 = Convert.ToInt32(Horas1.Text == "" ? "-1" : Horas1.Text);
int intMinutos1 = Convert.ToInt32(Minutos1.Text == "" ? "-1" : Minutos1.Text);
int intHoras2 = Convert.ToInt32(Horas2.Text == "" ? "-1" : Horas2.Text);
int intMinutos2 = Convert.ToInt32(Minutos2.Text == "" ? "-1" : Minutos2.Text);
int intHoras3 = Convert.ToInt32(Horas3.Text == "" ? "-1" : Horas3.Text);
int intMinutos3 = Convert.ToInt32(Minutos3.Text == "" ? "-1" : Minutos3.Text);
int intHoras4 = Convert.ToInt32(Horas4.Text == "" ? "-1" : Horas4.Text);
int intMinutos4 = Convert.ToInt32(Minutos4.Text == "" ? "-1" : Minutos4.Text);
int intHoras5 = Convert.ToInt32(Horas5.Text == "" ? "-1" : Horas5.Text);
int intMinutos5 = Convert.ToInt32(Minutos5.Text == "" ? "-1" : Minutos5.Text);
if ((intHoras <= 24) && (intMinutos <= 60) && (intHoras >= 0) && (intMinutos >= 0) &&
(intHoras1 <= 24) && (intMinutos1 <= 60) && (intHoras1 >= 0) && (intMinutos1 >= 0) &&
(intHoras2 <= 24) && (intMinutos2 <= 60) && (intHoras2 >= 0) && (intMinutos2 >= 0) &&
(intHoras3 <= 24) && (intMinutos3 <= 60) && (intHoras3 >= 0) && (intMinutos3 >= 0) &&
(intHoras4 <= 24) && (intMinutos4 <= 60) && (intHoras4 >= 0) && (intMinutos4 >= 0) &&
(intHoras5 <= 24) && (intMinutos5 <= 60) && (intHoras5 >= 0) && (intMinutos5 >= 0))
{
sp = new SerialPort("COM3");
sp.BaudRate = 19200;
sp.Open();
WriteTime(sp, intHoras, intMinutos);
WriteTime(sp, intHoras1, intMinutos1);
WriteTime(sp, intHoras2, intMinutos2);
WriteTime(sp, intHoras3, intMinutos3);
WriteTime(sp, intHoras4, intMinutos4);
WriteTime(sp, intHoras5, intMinutos5);
DateTime date = new DateTime();
TimeSpan ts = new TimeSpan(0, intHoras, intMinutos + 2);
date = date + ts;
lblTime.Content = date.ToLongTimeString();
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += timer_Tick;
timer.Start();
}
else
{
MessageBoxResult result = MessageBox.Show("Ingresaste datos incorrectos", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void WriteTime(SerialPort sp, int intHoras, int intMinutos)
{
List<byte[]> horas = getVectorNumber(intHoras);
List<byte[]> minutos = getVectorNumber(intMinutos);
foreach (byte[] item in horas)
{
sp.Write(item, 0, 1);
}
foreach (byte[] item in minutos)
{
sp.Write(item, 0, 1);
}
}
private List<byte[]> getVectorNumber(int number)
{
List<byte[]> result = new List<byte[]>();
result.Add(Encoding.ASCII.GetBytes(Convert.ToString(number / 10)));
result.Add(Encoding.ASCII.GetBytes(Convert.ToString(number % 10)));
return result;
}
#region NumericValidation
private void textBoxValue_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
e.Handled = !TextBoxTextAllowed(e.Text);
}
private void textBoxValue_Pasting(object sender, DataObjectPastingEventArgs e)
{
if (e.DataObject.GetDataPresent(typeof(String)))
{
String Text1 = (String)e.DataObject.GetData(typeof(String));
if (!TextBoxTextAllowed(Text1)) e.CancelCommand();
}
else e.CancelCommand();
}
#endregion
private Boolean TextBoxTextAllowed(String Text2)
{
return Array.TrueForAll<Char>(Text2.ToCharArray(),
delegate(Char c) { return Char.IsDigit(c) || Char.IsControl(c); });
}
}
XAML
![<Window x:Class="DigitalPITClock.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="579.254" Width="877.835">
<Grid Background="Gray" Margin="0,0,-4.8,-55">
<Label Name="lblTime" FontSize="48" HorizontalAlignment="Left" Margin="297,22,0,0" VerticalAlignment="Top" Height="83" Width="269" RenderTransformOrigin="0.495,0.468"/>
<Label Content="Ingrese las horas y los minutos" HorizontalAlignment="Left" Margin="324,105,0,0" VerticalAlignment="Top" FontSize="15px"/>
<Label Content="Horas" HorizontalAlignment="Left" Margin="270,157,0,0" VerticalAlignment="Top" Height="44" Width="59" FontSize="15px"/>
<TextBox Name="Horas" HorizontalAlignment="Left" Margin="363,157,0,0" TextWrapping="Wrap" VerticalAlignment="Top" RenderTransformOrigin="-0.906,0.034" Height="29" Width="37" PreviewTextInput="textBoxValue_PreviewTextInput" DataObject.Pasting="textBoxValue_Pasting" MaxLength="2" FontSize="15px"/>
<Label Content="Minutos" HorizontalAlignment="Left" Margin="445,157,0,0" VerticalAlignment="Top" Height="29" Width="71" FontSize="15px"/>
<TextBox Name="Minutos" HorizontalAlignment="Left" Margin="544,157,0,0" TextWrapping="Wrap" VerticalAlignment="Top" RenderTransformOrigin="-0.906,0.034" Height="29" Width="38" PreviewTextInput="textBoxValue_PreviewTextInput" DataObject.Pasting="textBoxValue_Pasting" MaxLength="2" FontSize="15px"/>
<Label Content="Alarma 1" HorizontalAlignment="Left" Margin="171,207,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.487,0.445"/>
<Label Content="Horas" HorizontalAlignment="Left" Margin="25,253,0,0" VerticalAlignment="Top" Height="44" Width="59" FontSize="15px"/>
<TextBox x:Name="Horas1" HorizontalAlignment="Left" Margin="118,253,0,0" TextWrapping="Wrap" VerticalAlignment="Top" RenderTransformOrigin="-0.906,0.034" Height="29" Width="37" PreviewTextInput="textBoxValue_PreviewTextInput" DataObject.Pasting="textBoxValue_Pasting" MaxLength="2" FontSize="15px"/>
<Label Content="Minutos" HorizontalAlignment="Left" Margin="200,253,0,0" VerticalAlignment="Top" Height="29" Width="71" FontSize="15px"/>
<TextBox x:Name="Minutos1" HorizontalAlignment="Left" Margin="299,253,0,0" TextWrapping="Wrap" VerticalAlignment="Top" RenderTransformOrigin="-0.906,0.034" Height="29" Width="38" PreviewTextInput="textBoxValue_PreviewTextInput" DataObject.Pasting="textBoxValue_Pasting" MaxLength="2" FontSize="15px"/>
<Label Content="Alarma 2" HorizontalAlignment="Left" Margin="170,324,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.487,0.445"/>
<Label Content="Horas" HorizontalAlignment="Left" Margin="24,363,0,0" VerticalAlignment="Top" Height="44" Width="59" FontSize="15px"/>
<TextBox x:Name="Horas2" HorizontalAlignment="Left" Margin="117,363,0,0" TextWrapping="Wrap" VerticalAlignment="Top" RenderTransformOrigin="-0.906,0.034" Height="29" Width="37" PreviewTextInput="textBoxValue_PreviewTextInput" DataObject.Pasting="textBoxValue_Pasting" MaxLength="2" FontSize="15px"/>
<Label Content="Minutos" HorizontalAlignment="Left" Margin="199,363,0,0" VerticalAlignment="Top" Height="29" Width="71" FontSize="15px"/>
<TextBox x:Name="Minutos2" HorizontalAlignment="Left" Margin="298,363,0,0" TextWrapping="Wrap" VerticalAlignment="Top" RenderTransformOrigin="-0.906,0.034" Height="29" Width="38" PreviewTextInput="textBoxValue_PreviewTextInput" DataObject.Pasting="textBoxValue_Pasting" MaxLength="2" FontSize="15px"/>
<Label Content="Alarma 3" HorizontalAlignment="Left" Margin="385,421,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.487,0.445"/>
<Label Content="Horas" HorizontalAlignment="Left" Margin="249,473,0,0" VerticalAlignment="Top" Height="44" Width="59" FontSize="15px"/>
<TextBox x:Name="Horas3" HorizontalAlignment="Left" Margin="342,473,0,0" TextWrapping="Wrap" VerticalAlignment="Top" RenderTransformOrigin="-0.906,0.034" Height="29" Width="37" PreviewTextInput="textBoxValue_PreviewTextInput" DataObject.Pasting="textBoxValue_Pasting" MaxLength="2" FontSize="15px"/>
<Label Content="Minutos" HorizontalAlignment="Left" Margin="424,473,0,0" VerticalAlignment="Top" Height="29" Width="71" FontSize="15px"/>
<TextBox x:Name="Minutos3" HorizontalAlignment="Left" Margin="523,473,0,0" TextWrapping="Wrap" VerticalAlignment="Top" RenderTransformOrigin="-0.906,0.034" Height="29" Width="38" PreviewTextInput="textBoxValue_PreviewTextInput" DataObject.Pasting="textBoxValue_Pasting" MaxLength="2" FontSize="15px"/>
<Label Content="Alarma 4" HorizontalAlignment="Left" Margin="614,207,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.487,0.445"/>
<Label Content="Horas" HorizontalAlignment="Left" Margin="476,258,0,0" VerticalAlignment="Top" Height="44" Width="59" FontSize="15px"/>
<TextBox x:Name="Horas4" HorizontalAlignment="Left" Margin="569,258,0,0" TextWrapping="Wrap" VerticalAlignment="Top" RenderTransformOrigin="-0.906,0.034" Height="29" Width="37" PreviewTextInput="textBoxValue_PreviewTextInput" DataObject.Pasting="textBoxValue_Pasting" MaxLength="2" FontSize="15px"/>
<Label Content="Minutos" HorizontalAlignment="Left" Margin="651,258,0,0" VerticalAlignment="Top" Height="29" Width="71" FontSize="15px"/>
<TextBox x:Name="Minutos4" HorizontalAlignment="Left" Margin="750,258,0,0" TextWrapping="Wrap" VerticalAlignment="Top" RenderTransformOrigin="-0.906,0.034" Height="29" Width="38" PreviewTextInput="textBoxValue_PreviewTextInput" DataObject.Pasting="textBoxValue_Pasting" MaxLength="2" FontSize="15px"/>
<Label Content="Alarma 5" HorizontalAlignment="Left" Margin="614,324,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.487,0.445"/>
<Label Content="Horas" HorizontalAlignment="Left" Margin="477,363,0,0" VerticalAlignment="Top" Height="44" Width="59" FontSize="15px"/>
<TextBox x:Name="Horas5" HorizontalAlignment="Left" Margin="570,363,0,0" TextWrapping="Wrap" VerticalAlignment="Top" RenderTransformOrigin="-0.906,0.034" Height="29" Width="37" PreviewTextInput="textBoxValue_PreviewTextInput" DataObject.Pasting="textBoxValue_Pasting" MaxLength="2" FontSize="15px"/>
<Label Content="Minutos" HorizontalAlignment="Left" Margin="652,363,0,0" VerticalAlignment="Top" Height="29" Width="71" FontSize="15px"/>
<TextBox x:Name="Minutos5" HorizontalAlignment="Left" Margin="751,363,0,0" TextWrapping="Wrap" VerticalAlignment="Top" RenderTransformOrigin="-0.906,0.034" Height="29" Width="38" PreviewTextInput="textBoxValue_PreviewTextInput" DataObject.Pasting="textBoxValue_Pasting" MaxLength="2" FontSize="15px"/>
<Button Content="Aplicar Cambios" HorizontalAlignment="Left" Margin="351,552,0,0" VerticalAlignment="Top" Width="113" Height="28" Click="Button_Click"/>
</Grid>
</Window>
source to share
The main problem is that you don't return from the timer handler Tick
until the user dismiss this message box. The timer does not fire another event Tick
until you process the current one.
Instead of showing the message box in the event handler itself, you should show it asynchronously using the method Dispatcher.BeginInvoke()
:
void timer_Tick(object sender, EventArgs e)
{
lblTime.Content = Convert.ToDateTime(lblTime.Content).AddSeconds(1).ToLongTimeString();
int btr = sp.BytesToRead;
if (btr != 0)
{
string alarma = char.ConvertFromUtf32(sp.ReadChar());
Dispatcher.BeginInvoke((Action)(() =>
MessageBox.Show("La alarma " + alarma + " se activo", "Alarma",
MessageBoxButton.OK, MessageBoxImage.Exclamation)));
}
}
This will terminate the method call on the delegate, which in turn will be run later on the same thread (the Dispatcher thread). This allows an event handler to be returned Tick
so that the timer can continue to run.
However, your code is not entirely correct. IMHO, the most obvious, most important issue to fix is that the timers (all of them) are not very imprecise. They can (as you have seen) be delayed for various reasons and are not very precisely scheduled for execution. Over time, your watch is likely to drift from the actual time you want to display.
Instead, you should use eg. an instance Stopwatch
, starting it when you want the clock to start, adding its value Elapsed
to your initial value (i.e. new TimeSpan(0, intHoras, intMinutos + 2)
, which you could calculate once and store in a class field for that purpose) and displaying that amount with the appropriate formatted string format (for example "hh:mm:ss tt"
).
Edit:
This is indeed secondary to the original question, but since you asked & hellip;
The best way to track the time for your watch would be:
1. Add two new fields to your class:
private TimeSpan baseTime;
private Stopwatch elapsed;
2. Initialize these fields when starting the timer:
baseTime = new TimeSpan(0, intHoras, intMinutos + 2);
elapsed = Stopwatch.StartNew();
3. Then use these values to display the time in the timer event handler Tick
:
lblTime.Content = (baseTime + elapsed.Elapsed).ToString(@"hh\:mm\:ss");
Naturally, you will remove other code related to the display of time: local variables DateTime
and TimeSpan
in the initialization code, and the existing operator that sets lblTime.Content
in the event handler method Tick
.
Finally, in case it pacifies Hans' opposition before Stopwatch
, you can implement the above using only instead DateTime
. That is, instead of the above, do the following:
1. Add two new fields to your class:
private TimeSpan baseTime;
private DateTime startTime;
2. Initialize these fields when starting the timer:
baseTime = new TimeSpan(0, intHoras, intMinutos + 2);
startTime = DateTime.UtcNow;
3. Then use these values to display the time in the timer event handler Tick
:
lblTime.Content = (baseTime + (DateTime.UtcNow - startTime)).ToString(@"hh\:mm\:ss");
Note that it UtcNow
relies on the system clock, so if it changes it will affect the displayed elapsed time, causing it to be incorrect. For example, if the user changes the set time or the system automatically updates the clock from the NTP server (the only way that automatically adjusts the clock from the NTP server will improve the displayed time if the system clock has drifted further from the correct time than it was already drifting when the meter in your program was started by & hellip, this is not a very common scenario).
source to share