一、运行效果图
1、开关串口
2、波特率选择
3、串口数据处理(目前对应的是报警器设备,抓取的数据有:红外传感器和CO传感器、以及设备NTC温度值)
4、所有串口接收字串
5、四个Series显示,代表各自的电压值;
6、可以选择显示或者隐藏对应的Series
Chart绘图存在的问题:当显示的数据量很大,达到上万数据时,整个winform窗口变得卡顿。时间允许,会尝试用其他方法,需要源码的见最后链接。
二、程序讲解
1、整个winform程序包含的方法并不多,如下:因并未学习Java或C++等语言,所以整个代码里有很多C语言的影子,风格显得很突兀。
2、Chart初始化
#region Chart初始化
public void InitChart()
{
chart1.BackColor = System.Drawing.Color.White;//设置图表的背景颜色
//chart1.Series["Series1"].IsValueShownAsLabel = true;//设置是否在Chart中显示坐标点值
//chart1.Series["Series1"].XValueMember = "name";//设置X轴的数据源
//chart1.Series["Series1"].YValueMembers = "mobile";//设置Y轴的数据源
Series SeriesDark = chart1.Series[0];
// 画样条曲线(Spline)
SeriesDark.ChartType = SeriesChartType.Spline;
// 线宽2个像素
SeriesDark.BorderWidth = 2;
// 线的颜色:红色
SeriesDark.Color = System.Drawing.Color.Black;
// 图示上的文字是否可见
SeriesDark.IsVisibleInLegend = true;
// 线条名称
SeriesDark.Name = "Dark";
SeriesDark.Enabled = true;
Series SeriesLight = chart1.Series[1];
SeriesLight.ChartType = SeriesChartType.Spline;
SeriesLight.BorderWidth = 2;
SeriesLight.Color = System.Drawing.Color.Red;
SeriesLight.IsVisibleInLegend = true;
SeriesLight.Name = "Light";
SeriesLight.Enabled = true;
Series SeriesL_D = chart1.Series[2];
SeriesL_D.ChartType = SeriesChartType.Spline;
SeriesL_D.BorderWidth = 2;
SeriesL_D.Color = System.Drawing.Color.Blue;
SeriesL_D.IsVisibleInLegend = true;
SeriesL_D.Name = "L_D";
SeriesL_D.Enabled = true;
Series SeriesCo = chart1.Series[3];
SeriesCo.ChartType = SeriesChartType.Spline;
SeriesCo.BorderWidth = 2;
SeriesCo.Color = System.Drawing.Color.Green;
SeriesCo.IsVisibleInLegend = true;
SeriesCo.Name = "Co";
SeriesCo.Enabled = true;
// 设置显示范围
ChartArea chartArea = chart1.ChartAreas[0];
chartArea.AxisX.Minimum = 0;
chartArea.AxisX.Maximum = 100000;
chartArea.AxisX.Interval = 1;
chartArea.AxisY.Minimum = 0;
chartArea.AxisY.Maximum = 5;
chartArea.AxisY.Interval = 0.5;
//显示网格,虚线,且均设为灰色
chartArea.AxisX.Enabled = AxisEnabled.True; //可见
chartArea.AxisY.Enabled = AxisEnabled.True;
chartArea.AxisX.MajorGrid.LineDashStyle = ChartDashStyle.Dash; //虚线
chartArea.AxisY.MajorGrid.LineDashStyle = ChartDashStyle.Dash;
chartArea.AxisX.MajorGrid.LineColor = System.Drawing.Color.Silver; //主网格线为灰色,Major,Minor主次
chartArea.AxisY.MajorGrid.LineColor = System.Drawing.Color.Silver;
//刻度显示下属性
//chartArea.AxisY.LabelStyle.Format = "{MMM}";
//chartArea.AxisY.LabelStyle.Interval = 1;
//chartArea.AxisY.LabelStyle.IntervalType = DateTimeIntervalType.Months;
//chartArea.AxisY.MajorTickMark.LineColor = System.Drawing.Color.Red;
//chartArea.AxisY.MajorTickMark.Enabled = true;
//chartArea.AxisY.MajorTickMark.Interval = 1;
//chartArea.AxisY.MajorTickMark.IntervalType = DateTimeIntervalType.Days;
string[] AxisYLabelName = { "0.5v", "1.0v", "1.5v", "2.0v", "2.5v", "3.0v", "3.5v", "4.0v", "4.5v", "5.0v"};
for (int i = 0; i < 10; i++)
{
CustomLabel label = new CustomLabel();
label.Text = AxisYLabelName[i];
label.ToPosition = i+1;
chartArea.AxisY.CustomLabels.Add(label);
}
//X,Y轴颜色设定
//chartArea.AxisX.MajorTickMark.LineColor = System.Drawing.Color.Red; //X轴颜色
//chartArea.AxisY.MajorTickMark.LineColor = System.Drawing.Color.Red; //X轴颜色
//设置图表显示样式, 即X/Y显示比例
this.chart1.ChartAreas[0].AxisX.ScaleView.Zoom(1,20);
this.chart1.ChartAreas[0].CursorX.IsUserEnabled = true;
this.chart1.ChartAreas[0].CursorX.IsUserSelectionEnabled = true;
this.chart1.ChartAreas[0].AxisX.ScaleView.Zoomable = true;
//设置滚动条
this.chart1.ChartAreas[0].AxisX.ScrollBar.IsPositionedInside = true; //将滚动内嵌到坐标轴中
this.chart1.ChartAreas[0].AxisX.ScrollBar.Size = 20; //设置滚动条的大小
this.chart1.ChartAreas[0].AxisX.ScrollBar.ButtonStyle = ScrollBarButtonStyles.All; //设置滚动条的按钮的风格,下面代码是将所有滚动条上的按钮都显示出来
this.chart1.ChartAreas[0].AxisX.ScrollBar.Enabled = true;
this.chart1.ChartAreas[0].AxisX.ScaleView.Scroll(ScrollType.LargeDecrement);
//chartArea.AxisX.LabelStyle.IsEndLabelVisible = true; //show the last label
}
#endregion
3、串口打开操作
#region 串口打开操作button1
private void button1_Click(object sender, EventArgs e)
{
try
{
if (serialPort1.IsOpen) //open状态
{
while (UartBusyFlag)
{
System.Windows.Forms.Application.DoEvents();
}
serialPort1.Close();
mainForm.button1.Text = "打开串口";
mainForm.pictureBox1_On_Off.Image = mainForm.pictureBox1_On_Off.InitialImage;
//button1.ForeColor = Color.Gray;
//DataProcessThread.Abort();
//DataMatchThread.Abort();
}
else
{
serialPort1.Encoding = System.Text.Encoding.GetEncoding("GB2312"); //此行非常重要 可解决接收中文乱码问题
serialPort1.PortName = comboBox1.Text; //得到当前COM选择口
serialPort1.BaudRate = Convert.ToInt32(comboBox2.Text); //得到当前设置的波特率
serialPort1.Open();
mainForm.button1.Text = "关闭串口";
//button1.ForeColor = Color.Blue;
//mainForm.richTextBox1.Text = ""; //清空接收区
mainForm.pictureBox1_On_Off.Image = mainForm.pictureBox1_On_Off.ErrorImage;
if (FirstClikFlag == true)
{
FirstClikFlag = false;
DataMatchThread.Start();
}
}
}
catch (Exception ex)
{
SerialPort SerialPort1 = new System.IO.Ports.SerialPort(); //与下面语句作用一致
string[] ComNum = SerialPort.GetPortNames();
comboBox1.Text = ComNum[0]; //默认第一个数值值作为串口端口显示,COM1
//SerialPort SerialPort1 = new SerialPort();
MessageBox.Show(ex.Message);
}
}
#endregion
4、串口接收事件处理
#region 串口接收事件处理
private void MySerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
UartBusyFlag = true;
Thread.Sleep(50);
#if SUPPORT_READ_BYTE
int DataLength = serialPort1.BytesToRead; //接收到buffer的字节数
byte[] DataBuf = new byte[DataLength];
ReceiveTotalCnt += DataLength;
serialPort1.Read(DataBuf, 0, DataLength);
sb.Clear(); //清空
String LastValue = Encoding.ASCII.GetString(DataBuf); //将整个数组解码为ASCII数组
sb.Append(LastValue); //遍历数组进行字符串转化及拼接
#else
//int DataLength = serialPort1.BytesToRead; //接收到buffer的字节数
//char[] DataBuf = new char[DataLength];
//ReceiveTotalCnt += DataLength;
//serialPort1.Read(DataBuf, 0, DataLength);
//string LastValue = new string(DataBuf);
string LastValue = serialPort1.ReadLine();
//string LastValue = serialPort1.ReadExisting();
#endif
PausedEvent.Reset();
try
{
//因为要访问UI资源,所以需要使用invoke方式同步ui
//在创建this对象的线程上调用匿名委托.
//匿名委托的代码就是你看见的那个:
//delegate {
//xxx b
//}
//这是匿名委托的一种写法,算是C#的语法
//Invoke((EventHandler)(delegate { this.richTextBox1.AppendText(sb.ToString()); })); //invoke函数括号中的参数类型必须是委托
//this.richTextBox1.Focus(); //获取焦点
//this.richTextBox1.Select(this.richTextBox1.TextLength, 0); //光标定位到文本最后
//this.richTextBox1.ScrollToCaret(); //滚动到光标处
SetRichTextBox(sb.ToString());
}
catch (Exception ex)
{
//响铃并显示异常给用户
System.Media.SystemSounds.Beep.Play();
MessageBox.Show(ex.Message);
}
UartDataBuf += LastValue;
//for (int i = 0; i < 5; i++ )
//{
// ValueObject[i].DataSourceValue += LastValue;
// if (ValueObject[i].DataSourceValue.Length > 1024)
// {
// ValueObject[i].DataSourceValue = ValueObject[i].DataSourceValue.Remove(0, 1000);
// }
//}
PausedEvent.Set();
UartBusyFlag = false;
//Console.WriteLine("{0}", TempValue);
}
private delegate void SetTextCallback(string text);
private void SetRichTextBox(string text)
{
// InvokeRequired需要比较调用线程ID和创建线程ID
// 如果它们不相同则返回true
if (mainForm.richTextBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetRichTextBox);
mainForm.Invoke(d, new object[] { text });
}
else
{
mainForm.richTextBox1.AppendText(text);
mainForm.richTextBox1.Focus(); //获取焦点
mainForm.richTextBox1.Select(this.richTextBox1.TextLength, 0); //光标定位到文本最后
mainForm.richTextBox1.ScrollToCaret(); //滚动到光标处
}
}
#endregion
5、保存串口信息(以当前时间为文件名,保存为txt文档,路径固定为当前exe执行文件路径)
#region 保存串口信息button 2
private void button2_Click(object sender, EventArgs e)
{
string PathStrValue = System.Windows.Forms.Application.StartupPath;
Console.WriteLine("Path:{0}", PathStrValue);
System.DateTime currentTime = new System.DateTime();
currentTime = System.DateTime.Now;
string DataStrValue = currentTime.Year.ToString() + "-" + currentTime.Month.ToString() + "-" + currentTime.Day.ToString() + "-";
DataStrValue += currentTime.Hour.ToString() + "-" + currentTime.Minute.ToString() + "-" + currentTime.Second.ToString();
Console.WriteLine("Time:{0}", DataStrValue);
PathStrValue = PathStrValue+"\\"+DataStrValue+".txt";
Console.WriteLine("Last:{0}", PathStrValue);
StreamWriter SW = File.CreateText(PathStrValue);
SW.Write(richTextBox1.Text);
SW.Flush();
SW.Close();
}
#endregion
6、清除串口信息(效果如下)
#region 清除串口信息button 3
private void button3_Click(object sender, EventArgs e)
{
string ZeroStrChar = "0";
string NullStrChar = "null";
richTextBox1.Text = ""; //清空接收文本框
for(int i = 0; i<5; i++)
{
for (int j = 0; j < 5; j++)
{
ValueObject[i].DataValue[j] = defaultValue[j];
}
}
for (int i = 0; i < 5; i++)
{
mainForm.MyCoTextBoxList[i].Text = ZeroStrChar;
}
for (int i = 0; i < 5; i++)
{
mainForm.MyDarkTextBoxList[i].Text = ZeroStrChar;
}
for (int i = 0; i < 5; i++)
{
mainForm.MyLightTextBoxList[i].Text = ZeroStrChar;
}
for (int i = 0; i < 5; i++)
{
mainForm.MyL_DTextBoxList[i].Text = ZeroStrChar;
}
for (int i = 0; i < 5; i++)
{
mainForm.MyTempTextBoxList[i].Text = ZeroStrChar;
}
mainForm.textBox_CO_PPM.Text = ZeroStrChar;
mainForm.textBox_MCU_BAT.Text = ZeroStrChar;
mainForm.textBox_CO_BAT.Text = ZeroStrChar;
mainForm.textBox_Event.Text = NullStrChar;
for (int i = 0; i < 4; i++)
{
mainForm.chart1.Series[i].Points.Clear();
}
CoSum = 0;
DarkSum = 0;
LightSum = 0;
L_DSum = 0;
SeriesSum = 0;
}
#endregion
7、实时动态曲线绘制方法
#region 实时动态曲线绘制方法
private static uint CoSum = 0;
private static uint DarkSum = 0;
private static uint LightSum = 0;
private static uint L_DSum = 0;
private static uint SeriesSum = 0;
private delegate void DynamicDrawLine(int SeriesIndex, int DrawValue);
private static void DynamicDrawLineFunc(int SeriesIndex, int DrawValue)
{
ChartArea chartArea = mainForm.chart1.ChartAreas[0];
double DisplayData = DrawValue / 1000.0;
if (mainForm.chart1.InvokeRequired)
{
DynamicDrawLine d = new DynamicDrawLine(DynamicDrawLineFunc);
mainForm.Invoke(d, new object[] { SeriesIndex, DrawValue });
}
else
{
chartArea.AxisX.ScaleView.Size = 50D;
switch (SeriesIndex)
{
case 0:
CoSum++;
Series SeriesCo = mainForm.chart1.Series[3];
SeriesCo.Points.AddXY(CoSum, DisplayData);
break;
case 1:
DarkSum++;
Series SeriesDark = mainForm.chart1.Series[0];
SeriesDark.Points.AddXY(DarkSum, DisplayData);
break;
case 2:
LightSum++;
Series SeriesLight = mainForm.chart1.Series[1];
SeriesLight.Points.AddXY(LightSum, DisplayData);
break;
case 3:
L_DSum++;
Series SeriesL_D = mainForm.chart1.Series[2];
SeriesL_D.Points.AddXY(L_DSum, DisplayData);
break;
default:
break;
}
SeriesSum++;
chartArea.AxisX.ScaleView.Position = 0;
if ((CoSum % 50 > 0) || (DarkSum % 50 > 0) || (LightSum % 50 > 0) || (L_DSum % 50 > 0))
{
double max = chartArea.AxisX.Maximum;
max = (SeriesSum / 10 + 1) * 10;
chartArea.AxisX.Interval = max / 10;
chartArea.AxisX.ScaleView.Size = SeriesSum * 1.1;
}
}
}
#endregion
8、串口端口号实时获取
#region 串口端口号实时获取
private delegate void SerialPortGet();
public void SerialPortGetFunc()
{
mainForm.serialPort1.PortName = mainForm.comboBox1.Text; //得到当前COM选择口
mainForm.comboBox1.Items.Clear();
foreach (string portName in SerialPort.GetPortNames())
{
mainForm.comboBox1.Items.Add(portName);
}
}
private void comboBox1_MouseClick(object sender, MouseEventArgs e)
{
if (mainForm.serialPort1.IsOpen)
{
}
else
{
if (mainForm.comboBox1.InvokeRequired)
{
SerialPortGet d = new SerialPortGet(SerialPortGetFunc);
mainForm.Invoke(d, new object[] { });
}
else
{
SerialPortGetFunc();
}
}
}
#endregion
9、选择Series RadioButton (只选择Light, Co可见)
#region 选择Series RadioButton
private void Dark_Series_Button_Click(object sender, EventArgs e)
{
Series SeriesDark = chart1.Series[0];
if (SeriesDarkSelectFlag == true)
{
Dark_Series_Button.Checked = false;
SeriesDarkSelectFlag = false;
SeriesDark.IsVisibleInLegend = false; //Series 名称是否可见
SeriesDark.Enabled = false;
}
else
{
Dark_Series_Button.Checked = true;
SeriesDarkSelectFlag = true;
SeriesDark.IsVisibleInLegend = true;
SeriesDark.Enabled = true;
}
}
private void Light_Series_Button_Click(object sender, EventArgs e)
{
Series SeriesLight = chart1.Series[1];
if (SeriesLightSelectFlag == true)
{
Light_Series_Button.Checked = false;
SeriesLightSelectFlag = false;
SeriesLight.IsVisibleInLegend = false; //Series 名称是否可见
SeriesLight.Enabled = false;
}
else
{
Light_Series_Button.Checked = true;
SeriesLightSelectFlag = true;
SeriesLight.IsVisibleInLegend = true;
SeriesLight.Enabled = true;
}
}
private void L_D_Series_Button_Click(object sender, EventArgs e)
{
Series SeriesL_D = chart1.Series[2];
if (SeriesL_DSelectFlag == true)
{
L_D_Series_Button.Checked = false;
SeriesL_DSelectFlag = false;
SeriesL_D.IsVisibleInLegend = false; //Series 名称是否可见
SeriesL_D.Enabled = false;
}
else
{
L_D_Series_Button.Checked = true;
SeriesL_DSelectFlag = true;
SeriesL_D.IsVisibleInLegend = true;
SeriesL_D.Enabled = true;
}
}
private void Co_Series_Button_Click(object sender, EventArgs e)
{
Series SeriesCo = chart1.Series[3];
if (SeriesCoSelectFlag == true)
{
Co_Series_Button.Checked = false;
SeriesCoSelectFlag = false;
SeriesCo.IsVisibleInLegend = false; //Series 名称是否可见
SeriesCo.Enabled = false;
}
else
{
Co_Series_Button.Checked = true;
SeriesCoSelectFlag = true;
SeriesCo.IsVisibleInLegend = true;
SeriesCo.Enabled = true;
}
}
#endregion
10、窗体的关闭事件处理函数、chart鼠标滚动事 、显示帮助信息 button 4
#region 窗体的关闭事件处理函数,在该事件中将之前创建的线程全部终止
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (serialPort1.IsOpen) //open状态
{
while (UartBusyFlag)
{
System.Windows.Forms.Application.DoEvents();
}
serialPort1.Close();
}
DataMatchThread.Abort();
if (MyThreadList.Count > 0)
{
//编列自定义队列,将所有线程终止
foreach (Thread tWorkingThread in MyThreadList)
{
tWorkingThread.Abort();
}
}
}
#endregion
#region chart鼠标滚动事
private void chart1_MouseEnter(object sender, MouseEventArgs e)
{
ChartArea chartArea = mainForm.chart1.ChartAreas[0];
if (e.Delta > 0)//鼠标向上
{
if (chartArea.AxisX.ScaleView.Size < 5000)//判断显示的最大数值
chartArea.AxisX.ScaleView.Size += 5;//+=5---滚动一次显示5个
}
else//鼠标向下滚动
{
if (chartArea.AxisX.ScaleView.Size > 50)
chartArea.AxisX.ScaleView.Size -= 5;// - = 5---滚动一次减小显示5个
}
}
private void chart1_MouseEnter(object sender, EventArgs e)
{
MouseWheel += new MouseEventHandler(chart1_MouseEnter); //调用滚轮事件
}
#endregion
#region 显示帮助信息 button 4
private void button4_Click(object sender, EventArgs e)
{
//DialogResult MsgBoxResult;//设置对话框的返回值
//MsgBoxResult = MessageBox.Show("请选择你要按下的按钮",//对话框的显示内容
//"提示",//对话框的标题
//MessageBoxButtons.YesNo,//定义对话框的按钮,这里定义了YSE和NO两个按钮
//MessageBoxIcon.Exclamation,//定义对话框内的图表式样,这里是一个黄色三角型内加一个感叹号
//MessageBoxDefaultButton.Button2);//定义对话框的按钮式样
//if (MsgBoxResult == DialogResult.Yes)//如果对话框的返回值是YES(按"Y"按钮)
//{
// this.label1.ForeColor = System.Drawing.Color.Red;//字体颜色设定
// label1.Text = " 你选择了按下”Yes“的按钮!";
//}
//if (MsgBoxResult == DialogResult.No)//如果对话框的返回值是NO(按"N"按钮)
//{
// this.label1.ForeColor = System.Drawing.Color.Blue;//字体颜色设定
// label1.Text = " 你选择了按下”No“的按钮!";
//}
DialogResult MsgBoxResult;//设置对话框的返回值
MsgBoxResult = MessageBox.Show("采样电压,精度最大mV\nCO: [Co=xmV] 或 [Co=x.xV]\nDark: [Dark=xmV] 或 [Dark=x.xV]\nLight: [Light=xmV] 或 [Light=x.xV]\nL_D: [L_D=xmV] 或 [L_D=x.xV]\nT: [T=xx^C] \n电池和事件\nppm: [CO:xxxppm]\nMCU_Bat: [MCU_Bat:xmV] 或 [MCU_Bat:x.xV]\nCO_Bat: [CO_Bat:xmV] 或 [CO_Bat:x.xV]\nEvent: [Event:xxxx\\n]",//对话框的显示内容
"帮助(输出格式)",//对话框的标题
MessageBoxButtons.OK,//定义对话框的按钮,这里定义了YSE和NO两个按钮
MessageBoxIcon.Information,//定义对话框内的图表式样,这里是一个黄色三角型内加一个感叹号
MessageBoxDefaultButton.Button1);//定义对话框的按钮式样
}
#endregion