C# 两个 Windows:第一个用于 main UI,第二个将作为后台进程 (Serial.Read)(活动)
C# Two Windows: First one for main UI, second one will be the background process (Serial.Read) (Active)
我是 C# WPF
的新手,我在 运行 安装我的程序时遇到了问题。如果我的串行端口(上面的 xbee)无法在特定时间(Dispatcher)接收到所需的数据,我的程序就会冻结。
所以,我试图通过创建第二个 window 来解决它,它将主动等待数据以减轻主 UI window 的负载。我遇到的问题是我无法 运行 第二个 window 与我的主 window 同步。
有什么建议吗?
主要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 事件。
添加第二个 UI 会产生大量不必要的复杂性。
这是一个基本示例,说明如何将 SerialPorts 与状态机一起使用:
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;
}
}
}
本质上,SerialPort class 能够在知道有更多数据要读取时引发事件。您可以订阅该事件。在 C# 中,这称为 "handling" 并在这一行完成:
_port.DataReceived += dataReceived; //This is the important line
这一行表示"everytime the port raises the DataReceived event, execute the private void dataReceived(...) function that I declared further down".
您的问题是您在 while(true) 循环中使用了 SerialPort.ReadLine()。这几乎总是一个坏主意。 SerialPort.ReadLine() 是一个阻塞调用,正如文档告诉您的那样。您的代码将停止,直到它从 COM 端口读取换行符。如果这些字符不出现,您的程序将永远冻结。通过使用 "DataReceived" 事件,您可以保证有 是 数据要读取,所以即使我仍然调用 ReadExisting (这也是一个阻塞调用)我已经知道那里将是数据并且该行将执行并且 return 非常快。在大多数情况下,它会如此之快,以至于您的 UI 不会冻结足够长的时间让任何人注意到。
这是最低级别的基于事件的编程。如果您仍然看到冻结,则必须使用 multi-threading,这非常复杂。仅在绝对必要时才使用它。
其余代码与 SerialPorts 无关。它在那里是因为您使用定时器来模拟状态机,这也是一个坏主意。 DataReceived 将在接收到任何数据时触发,无论数据是什么,因此跟踪您的状态以将其与传入数据进行比较非常重要。如果您期望 "FEED"(即您的程序在 State1 或 StateFeed 中)并且您收到 "DRINK",则说明出现了问题,您必须对此采取措施。当出现问题时,您至少可以做的就是抛出异常。一旦您了解出了什么问题,您就可以开始添加优雅地处理异常的代码。
我是 C# WPF
的新手,我在 运行 安装我的程序时遇到了问题。如果我的串行端口(上面的 xbee)无法在特定时间(Dispatcher)接收到所需的数据,我的程序就会冻结。
所以,我试图通过创建第二个 window 来解决它,它将主动等待数据以减轻主 UI window 的负载。我遇到的问题是我无法 运行 第二个 window 与我的主 window 同步。
有什么建议吗?
主要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 事件。
添加第二个 UI 会产生大量不必要的复杂性。
这是一个基本示例,说明如何将 SerialPorts 与状态机一起使用:
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;
}
}
}
本质上,SerialPort class 能够在知道有更多数据要读取时引发事件。您可以订阅该事件。在 C# 中,这称为 "handling" 并在这一行完成:
_port.DataReceived += dataReceived; //This is the important line
这一行表示"everytime the port raises the DataReceived event, execute the private void dataReceived(...) function that I declared further down".
您的问题是您在 while(true) 循环中使用了 SerialPort.ReadLine()。这几乎总是一个坏主意。 SerialPort.ReadLine() 是一个阻塞调用,正如文档告诉您的那样。您的代码将停止,直到它从 COM 端口读取换行符。如果这些字符不出现,您的程序将永远冻结。通过使用 "DataReceived" 事件,您可以保证有 是 数据要读取,所以即使我仍然调用 ReadExisting (这也是一个阻塞调用)我已经知道那里将是数据并且该行将执行并且 return 非常快。在大多数情况下,它会如此之快,以至于您的 UI 不会冻结足够长的时间让任何人注意到。
这是最低级别的基于事件的编程。如果您仍然看到冻结,则必须使用 multi-threading,这非常复杂。仅在绝对必要时才使用它。
其余代码与 SerialPorts 无关。它在那里是因为您使用定时器来模拟状态机,这也是一个坏主意。 DataReceived 将在接收到任何数据时触发,无论数据是什么,因此跟踪您的状态以将其与传入数据进行比较非常重要。如果您期望 "FEED"(即您的程序在 State1 或 StateFeed 中)并且您收到 "DRINK",则说明出现了问题,您必须对此采取措施。当出现问题时,您至少可以做的就是抛出异常。一旦您了解出了什么问题,您就可以开始添加优雅地处理异常的代码。