I'm new to C# WPF
and I have a problem while running my program. My program freezes if my serial port (xbee on it) can't receive the required data in a specific time (Dispatcher).
So, I am trying to solve it by making a second window that will be actively waiting for a data to lessen the load of the main UI window. The problem I am experiencing is that I can't run the second window synchronously with my main window.
Any tips?
MAIN UI
public partial class Window5 : Window
{
SerialPort Senport = new SerialPort("COM4", 9600, Parity.None, 8, StopBits.One);
int flagger=0;
int fflagger=0;
DispatcherTimer timer1 = new DispatcherTimer(); //Feed Timer
DispatcherTimer timer2 = new DispatcherTimer();//Disabler Timer during Feeding
DispatcherTimer timer3 = new DispatcherTimer();//Disabler Timer during Drinking
DispatcherTimer timer4 = new DispatcherTimer();//Disabler Timer during Cleaning
DispatcherTimer timer5 = new DispatcherTimer();// Serial Port Data Receiver Timer
DispatcherTimer timer6 = new DispatcherTimer();//
DispatcherTimer timer7 = new DispatcherTimer();
public Window5()
{
InitializeComponent();
new Window2();
timer1.Interval = TimeSpan.FromSeconds(300);
timer2.Interval = TimeSpan.FromSeconds(15);
timer3.Interval = TimeSpan.FromSeconds(15);
timer4.Interval = TimeSpan.FromSeconds(60);
}
void timer_Tick(object sender, EventArgs e)
{
FEED.IsEnabled=true;
timer1.Stop();
}
void FEED_Click(object sender, RoutedEventArgs e)
{
fflagger=1;
flagger=1;
Sender();
timer1.Start();
timer2.Start();
Disabler();
MessageBox.Show("Feeds Dispensing is starting","Drinking Water Process",MessageBoxButton.OK,MessageBoxImage.Information);
timer1.Tick +=timer_Tick;
timer2.Tick +=Enabler;
// Xbee Code Will be Here
}
void FEED2_Click(object sender, RoutedEventArgs e)
{
flagger=2;
Sender();
timer3.Start();
Disabler();
MessageBox.Show("Water Dispensing is starting", "Drinking Water Process",MessageBoxButton.OK,MessageBoxImage.Information);
timer3.Tick +=Enabler;
// Xbee Code Will be Here
}
void Clean_Click(object sender, RoutedEventArgs e)
{
//Window4 w4 = new Window4();
flagger=3;
Sender();
timer4.Start();
Disabler();
MessageBox.Show("Cleaning Process is starting", "Cleaning Process",MessageBoxButton.OK,MessageBoxImage.Information);
//w4.Show();
timer4.Tick +=Enabler;
}
void CCTV_Click(object sender, RoutedEventArgs e)
{
Process.Start(@"C:\Program Files\ CMS 2.0\CMS");
}
public void Enabler(object sender, EventArgs e)
{
if(flagger==1)
{
FEED2.IsEnabled=true;
Clean.IsEnabled=true;
timer2.Stop();
}
else if(flagger==2 && fflagger==0)
{
FEED.IsEnabled=true;
FEED2.IsEnabled=true;
Clean.IsEnabled=true;
timer3.Stop();
}
else if(flagger==2 && fflagger==1)
{
FEED2.IsEnabled=true;
Clean.IsEnabled=true;
timer3.Stop();
}
else if(flagger==3 && fflagger==0)
{
FEED.IsEnabled=true;
FEED2.IsEnabled=true;
Clean.IsEnabled=true;
timer4.Stop();
}
else
{
FEED2.IsEnabled=true;
Clean.IsEnabled=true;
timer4.Stop();
}
}
// Function in disabling Buttons
public void Disabler()
{
if(flagger == 1)
{
FEED.IsEnabled=false;
FEED2.IsEnabled=false;
Clean.IsEnabled=false;
}
else if(flagger == 2)
{
Clean.IsEnabled=false;
FEED.IsEnabled=false;
FEED2.IsEnabled=false;
}
else
{
Clean.IsEnabled=false;
FEED.IsEnabled=false;
FEED2.IsEnabled=false;
}
}
//Function for Serial Port Sender
public void Sender()
{
if(flagger == 1)
{
try
{
if (!(Senport.IsOpen == true)) Senport.Open();
Senport.Write("AB");
}
catch {}
}
else if(flagger == 2)
{
try
{
if (!(Senport.IsOpen == true)) Senport.Open();
Senport.Write("BC");
}
catch {}
}
else if(flagger == 3)
{
try
{
if (!(Senport.IsOpen == true)) Senport.Open();
Senport.Write("CD");
}
catch {}
}
Senport.Close();
}
}
Window 2
public partial class Window2 : Window
{
SerialPort Senport = new SerialPort("COM4", 9600, Parity.None, 8,StopBits.One);
DispatcherTimer timer1 = new DispatcherTimer(); //Feed Timer
DispatcherTimer timer2 = new DispatcherTimer();//Disabler Timer during Feeding
DispatcherTimer timer3 = new DispatcherTimer();//Disabler Timer during Drinking
DispatcherTimer timer4 = new DispatcherTimer();//Disabler Timer during Cleaning
DispatcherTimer timer5 = new DispatcherTimer();// Serial Port Data Receiver Timer
DispatcherTimer timer6 = new DispatcherTimer();//
DispatcherTimer timer7 = new DispatcherTimer();
string rdata;
string rdata1;
public Window2()
{
InitializeComponent();
Window5 WORK1 = new Window5();
while(true)
{
if (!(Senport.IsOpen == true)) Senport.Open();
rdata= Senport.ReadLine();
rdata1.ToString();
rdata1 = rdata;
Senport.Close();
if(rdata1 == "FEED")
{
MessageBox.Show("Feeds already being dispense!", "Feeding Process",MessageBoxButton.OK,MessageBoxImage.Information);
}
if(rdata1 == "DRINK")
{
MessageBox.Show("Drinkable water is dispense!", "Drinking Water Process",MessageBoxButton.OK,MessageBoxImage.Information);
}
if(rdata1 == "CLEAN")
{
MessageBox.Show("Cleaning the cage is done!", "Cleaning Process",MessageBoxButton.OK,MessageBoxImage.Information);
}
}
}
}
Event based programming is largely superior to timer-based and blocking code. There are many levels of event based programming, from synchronous to asynchronous to full-fledged TPL. In your case, I would recommend starting by thoroughly reading the SerialPort documentation, and in particular the DataReceived event.
Adding your second UI is a large amount of unnecessary complexity.
Here's a basic example of how you can use SerialPorts with a state machine:
public class SerialPortTests
{
//Your states are pretty obscure to me, I'm making those up.
private enum States
{
State1,
State2,
State3
}
private States _state = States.State1;
private SerialPort _port = new SerialPort(/*Enter your port's config here*/);
public SerialPortTests()
{
_port.DataReceived += dataReceived; //This is the important line
_port.Open();
}
private void dataReceived(object sender, SerialDataReceivedEventArgs e)
{
var sendingPort = (SerialPort)sender;
var data = sendingPort.ReadExisting(); //Careful, you may have more than 1 line in data.
var dataLines = data.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
foreach (var line in dataLines)
{
verifyState(line);
processLine(line);
}
}
private void verifyState(string line)
{
//Your states are pretty obscure, I'm just making things up here.
if (line == "FEED" && _state != States.State1)
{
//Handle the error if you can, or just throw to learn more about the problem in the stack trace.
throw new ApplicationException("received FEED while in state " + _state);
}
}
private void processLine(string line)
{
if (line == "FEED")
{
//Don't use MessageBox unless you really have to. Change a label's text or something.
Console.WriteLine("Feeds already being dispense!");
_state = States.State2;
}
}
}
Essentially, the SerialPort class is capable of raising an event when it knows there is more data to read. You can subscribe to that event. In C#, that's called "handling" and it's done on this line:
_port.DataReceived += dataReceived; //This is the important line
This line means "everytime the port raises the DataReceived event, execute the private void dataReceived(...) function that I declared further down".
Your problem was that you used SerialPort.ReadLine() in a while(true) loop. That is almost always a bad idea. SerialPort.ReadLine() is a blocking call, as the documentation tells you. Your code will stop until it has read the NewLine characters from the COM port. If those characters don't come, your program will freeze forever. By using the "DataReceived" event, you are guaranteed that there is data to read, so even though I still call ReadExisting (which is also a blocking call) I already know that there will be data and that the line will execute and return very quickly. For most cases, it will be so quick that your UI will not freeze long enough for anyone to notice.
This is the lowest level event based programming. If you still see freezes, you have to use multi-threading, and that is very complex. Use it only if it's strictly necessary.
The rest of the code doesn't have anything to do with SerialPorts. It's there because you use timers to simulate a state machine, and that's also a bad idea. DataReceived will be fired when any data is received, regardless of the data, so it's important to keep track of your state to compare it with the incoming data. If you are expecting "FEED" (ie. your program is in State1, or StateFeed) and you receive "DRINK" then something went wrong and you have to do something about it. The least you can do when things go wrong is to throw an exception. Once you understand what went wrong, you can start adding code that handles exceptions gracefully.