当前位置: 首页 > news >正文

C#编写多导联扫描式的波形图Demo

本代码调用ZedGraph绘图框架,自己先安装好ZedGraph环境,然后拖一个zedGraphControl控件就行了,直接黏贴下面代码

基本代码显示

using System;
using System.Windows.Forms;
using ZedGraph;
using System.Timers;namespace ECGPlot
{public partial class Form1 : Form{// 定义导联数量和每个导联的电压偏移量private const int ChannelCount = 8;private const int VoltageOffset = 150; // 每个导联的偏移量// 用于存储每个导联的数据点列表和曲线对象private PointPairList[] _dataLists;private LineItem[] _curves;// 定时器用于模拟ECG信号数据更新private System.Timers.Timer _timer;private int _currentIndex = 0;private int _maxPoints = 500;private double[][] _yValues;private double _timeIncrement = 0.1;private double _currentTime = 0;private Random _random = new Random();public Form1(){InitializeComponent();InitializeGraph(); // 初始化图表StartTimer(); // 启动定时器}private void InitializeGraph(){// 获取图表区域对象GraphPane myPane = zedGraphControl1.GraphPane;// 设置图表标题和轴标题myPane.Title.Text = "ECG Data";myPane.XAxis.Title.Text = "Time";myPane.YAxis.Title.Text = "Voltage";// 初始化数据点列表和曲线数组_dataLists = new PointPairList[ChannelCount];_curves = new LineItem[ChannelCount];_yValues = new double[ChannelCount][];for (int i = 0; i < ChannelCount; i++){// 为每个导联创建数据点列表和曲线对象,并添加到图表中_dataLists[i] = new PointPairList();_curves[i] = myPane.AddCurve($"ECG Channel {i + 1}", _dataLists[i], GetColor(i), SymbolType.None);_yValues[i] = new double[_maxPoints];}// 设置X轴和Y轴的范围myPane.XAxis.Scale.Min = 0;myPane.XAxis.Scale.Max = _maxPoints * _timeIncrement;myPane.YAxis.Scale.Min = -600;myPane.YAxis.Scale.Max = 600 + VoltageOffset * (ChannelCount - 1);// 显示网格线myPane.XAxis.MajorGrid.IsVisible = true;myPane.YAxis.MajorGrid.IsVisible = true;// 应用更改并刷新图表zedGraphControl1.AxisChange();}private System.Drawing.Color GetColor(int index){// 定义一组颜色用于不同导联的曲线System.Drawing.Color[] colors = {System.Drawing.Color.Black,System.Drawing.Color.Red,System.Drawing.Color.Blue,System.Drawing.Color.Green,System.Drawing.Color.Purple,System.Drawing.Color.Orange,System.Drawing.Color.Brown,System.Drawing.Color.Magenta};// 根据索引返回颜色return colors[index % colors.Length];}private void StartTimer(){// 创建并配置定时器_timer = new System.Timers.Timer(100); // 100毫秒的更新频率_timer.Elapsed += OnTimedEvent; // 绑定定时器事件_timer.AutoReset = true; // 自动重置_timer.Enabled = true; // 启用定时器}private void OnTimedEvent(Object source, ElapsedEventArgs e){// 为每个导联生成模拟ECG信号数据并更新曲线for (int i = 0; i < ChannelCount; i++){double voltage = _random.Next(-400, 400) + i * VoltageOffset; // 生成带偏移量的电压数据_yValues[i][_currentIndex] = voltage;if (_dataLists[i].Count < _maxPoints){// 添加新的数据点_dataLists[i].Add(_currentTime, voltage);}else{// 更新现有数据点_dataLists[i][_currentIndex].Y = voltage;}}// 更新时间和当前索引_currentTime += _timeIncrement;_currentIndex = (_currentIndex + 1) % _maxPoints;// 使图表无效以触发重绘zedGraphControl1.Invalidate();}private void Form1_Load(object sender, EventArgs e){// 窗体加载事件处理方法(目前为空)}}
}

注释解释:

  1. 全局变量定义:定义导联数量、每个导联的电压偏移量,以及存储数据和曲线的变量。
  2. 构造函数:调用 InitializeGraphStartTimer 方法初始化图表和启动定时器。
  3. InitializeGraph 方法:初始化图表区域,设置标题和轴标题,创建每个导联的曲线对象,并设置轴的范围和网格。
  4. GetColor 方法:定义一组颜色,根据索引返回颜色用于不同导联的曲线。
  5. StartTimer 方法:创建并配置定时器,设置定时器事件处理方法。
  6. OnTimedEvent 方法:在定时器触发时生成模拟ECG信号数据,为每个导联添加或更新数据点,并刷新图表。
  7. Form1_Load 方法:窗体加载事件处理方法(目前为空)。

在这里插入图片描述

添加了y轴方向的导联标签

using System;
using System.Windows.Forms;
using ZedGraph;
using System.Timers;namespace ECGPlot
{public partial class Form1 : Form{// 定义导联数量和每个导联的电压偏移量private const int ChannelCount = 8;private const int VoltageOffset = 500; // 每个导联的偏移量// 用于存储每个导联的数据点列表和曲线对象private PointPairList[] _dataLists;private LineItem[] _curves;// 定时器用于模拟ECG信号数据更新private System.Timers.Timer _timer;private int _currentIndex = 0;private int _maxPoints = 500;private double[][] _yValues;private double _timeIncrement = 0.1;private double _currentTime = 0;private Random _random = new Random();public Form1(){InitializeComponent();InitializeGraph(); // 初始化图表StartTimer(); // 启动定时器}private void InitializeGraph(){// 获取图表区域对象GraphPane myPane = zedGraphControl1.GraphPane;// 设置图表标题和轴标题myPane.Title.Text = "ECG Data";myPane.XAxis.Title.Text = "Time (s)";myPane.YAxis.Title.Text = "Voltage";// 初始化数据点列表和曲线数组_dataLists = new PointPairList[ChannelCount];_curves = new LineItem[ChannelCount];_yValues = new double[ChannelCount][];for (int i = 0; i < ChannelCount; i++){// 为每个导联创建数据点列表和曲线对象,并添加到图表中_dataLists[i] = new PointPairList();_curves[i] = myPane.AddCurve("", _dataLists[i], GetColor(i), SymbolType.None);_yValues[i] = new double[_maxPoints];}// 移除图例myPane.Legend.IsVisible = false;// 设置X轴和Y轴的范围myPane.XAxis.Scale.Min = 0;myPane.XAxis.Scale.Max = _maxPoints * _timeIncrement;myPane.YAxis.Scale.Min = -800;myPane.YAxis.Scale.Max = 800 + VoltageOffset * (ChannelCount - 1);// 显示网格线myPane.XAxis.MajorGrid.IsVisible = true;myPane.YAxis.MajorGrid.IsVisible = true;// 隐藏Y=0的实线myPane.YAxis.MajorGrid.IsZeroLine = false;// 自定义Y轴刻度标注Scale yScale = myPane.YAxis.Scale;yScale.MajorStep = VoltageOffset;yScale.MinorStep = VoltageOffset;yScale.MajorStepAuto = false;yScale.MinorStepAuto = false;myPane.YAxis.ScaleFormatEvent += new Axis.ScaleFormatHandler(FormatYScale);// 应用更改并刷新图表zedGraphControl1.AxisChange();}private string FormatYScale(GraphPane pane, Axis axis, double val, int index){// 自定义Y轴刻度标注int leadIndex = (int)Math.Round(val / VoltageOffset);if (leadIndex >= 0 && leadIndex < ChannelCount){return $"Lead {leadIndex + 1}";}return "";}private System.Drawing.Color GetColor(int index){// 定义一组颜色用于不同导联的曲线System.Drawing.Color[] colors = {System.Drawing.Color.Black,System.Drawing.Color.Red,System.Drawing.Color.Blue,System.Drawing.Color.Green,System.Drawing.Color.Purple,System.Drawing.Color.Orange,System.Drawing.Color.Brown,System.Drawing.Color.Magenta};// 根据索引返回颜色return colors[index % colors.Length];}private void StartTimer(){// 创建并配置定时器_timer = new System.Timers.Timer(10); // 100毫秒的更新频率_timer.Elapsed += OnTimedEvent; // 绑定定时器事件_timer.AutoReset = true; // 自动重置_timer.Enabled = true; // 启用定时器}private void OnTimedEvent(Object source, ElapsedEventArgs e){// 为每个导联生成模拟ECG信号数据并更新曲线for (int i = 0; i < ChannelCount; i++){double voltage = _random.Next(-200, 200) + i * VoltageOffset; // 生成带偏移量的电压数据_yValues[i][_currentIndex] = voltage;if (_dataLists[i].Count < _maxPoints){// 添加新的数据点_dataLists[i].Add(_currentTime, voltage);}else{// 更新现有数据点_dataLists[i][_currentIndex].Y = voltage;}}// 更新时间和当前索引_currentTime += _timeIncrement;_currentIndex = (_currentIndex + 1) % _maxPoints;// 更新X轴刻度显示zedGraphControl1.GraphPane.XAxis.Scale.TextLabels = GenerateTimeLabels(_currentTime, _timeIncrement, _maxPoints);// 使图表无效以触发重绘zedGraphControl1.Invalidate();}private string[] GenerateTimeLabels(double currentTime, double increment, int maxPoints){string[] labels = new string[maxPoints];double startTime = currentTime - (maxPoints * increment);for (int i = 0; i < maxPoints; i++){labels[i] = (startTime + i * increment).ToString("0.0");}return labels;}private void Form1_Load(object sender, EventArgs e){// 窗体加载事件处理方法(目前为空)}}
}

在这里插入图片描述

添加了时间刻度跟随时间扫描变化

using System;
using System.Windows.Forms;
using ZedGraph;
using System.Timers;namespace ECGPlot
{public partial class Form1 : Form{// 定义导联数量和每个导联的电压偏移量private const int ChannelCount = 8;private const int VoltageOffset = 700; // 每个导联的偏移量// 用于存储每个导联的数据点列表和曲线对象private PointPairList[] _dataLists;private LineItem[] _curves;// 定时器用于模拟ECG信号数据更新private System.Timers.Timer _timer;private int _currentIndex = 0;private int _maxPoints = 800;private double[][] _yValues;private double _timeIncrement = 0.1;private double _currentTime = 0;private Random _random = new Random();private DateTime[] _timeLabels;public Form1(){InitializeComponent();InitializeGraph(); // 初始化图表StartTimer(); // 启动定时器}private void InitializeGraph(){// 获取图表区域对象GraphPane myPane = zedGraphControl1.GraphPane;// 设置图表标题和轴标题myPane.Title.Text = "ECG Data";myPane.XAxis.Title.Text = "Time";myPane.YAxis.Title.Text = "Voltage";// 初始化数据点列表和曲线数组_dataLists = new PointPairList[ChannelCount];_curves = new LineItem[ChannelCount];_yValues = new double[ChannelCount][];_timeLabels = new DateTime[_maxPoints];for (int i = 0; i < ChannelCount; i++){// 为每个导联创建数据点列表和曲线对象,并添加到图表中_dataLists[i] = new PointPairList();_curves[i] = myPane.AddCurve("", _dataLists[i], GetColor(i), SymbolType.None);_yValues[i] = new double[_maxPoints];}// 移除图例myPane.Legend.IsVisible = false;// 设置X轴和Y轴的范围myPane.XAxis.Scale.Min = 0;myPane.XAxis.Scale.Max = _maxPoints;myPane.YAxis.Scale.Min = -800;myPane.YAxis.Scale.Max = 800 + VoltageOffset * (ChannelCount - 1);// 显示网格线myPane.XAxis.MajorGrid.IsVisible = true;myPane.YAxis.MajorGrid.IsVisible = true;// 隐藏Y=0的实线myPane.YAxis.MajorGrid.IsZeroLine = false;// 自定义Y轴刻度标注Scale yScale = myPane.YAxis.Scale;yScale.MajorStep = VoltageOffset;yScale.MinorStep = VoltageOffset;yScale.MajorStepAuto = false;yScale.MinorStepAuto = false;myPane.YAxis.ScaleFormatEvent += new Axis.ScaleFormatHandler(FormatYScale);// 设置X轴为文本类型myPane.XAxis.Type = AxisType.Text;// 设置X轴刻度字体大小myPane.XAxis.Scale.FontSpec.Size = 7; // 可以根据需要调整字体大小myPane.XAxis.Scale.FontSpec.FontColor = System.Drawing.Color.Black;// 初始化时间标签DateTime startTime = DateTime.Now;for (int i = 0; i < _maxPoints; i++){_timeLabels[i] = startTime;}// 应用更改并刷新图表zedGraphControl1.AxisChange();}private string FormatYScale(GraphPane pane, Axis axis, double val, int index){// 自定义Y轴刻度标注int leadIndex = (int)Math.Round(val / VoltageOffset);if (leadIndex >= 0 && leadIndex < ChannelCount){return $"Lead {leadIndex + 1}";}return "";}private System.Drawing.Color GetColor(int index){// 定义一组颜色用于不同导联的曲线System.Drawing.Color[] colors = {//System.Drawing.Color.Black,//System.Drawing.Color.Red,//System.Drawing.Color.Blue,//System.Drawing.Color.Green,//System.Drawing.Color.Purple,//System.Drawing.Color.Orange,//System.Drawing.Color.Brown,//System.Drawing.Color.MagentaSystem.Drawing.Color.Black,System.Drawing.Color.Black,System.Drawing.Color.Black,System.Drawing.Color.Black,System.Drawing.Color.Black,System.Drawing.Color.Black,System.Drawing.Color.Black,System.Drawing.Color.Black};// 根据索引返回颜色return colors[index % colors.Length];}private void StartTimer(){// 创建并配置定时器_timer = new System.Timers.Timer(1); // 100毫秒的更新频率_timer.Elapsed += OnTimedEvent; // 绑定定时器事件_timer.AutoReset = true; // 自动重置_timer.Enabled = true; // 启用定时器}private void OnTimedEvent(Object source, ElapsedEventArgs e){// 记录当前时间DateTime currentTime = DateTime.Now;// 为每个导联生成模拟ECG信号数据并更新曲线for (int i = 0; i < ChannelCount; i++){double voltage = _random.Next(-200, 200) + i * VoltageOffset; // 生成带偏移量的电压数据_yValues[i][_currentIndex] = voltage;if (_dataLists[i].Count < _maxPoints){// 添加新的数据点_dataLists[i].Add(_currentIndex, voltage);}else{// 更新现有数据点_dataLists[i][_currentIndex].Y = voltage;}}// 更新时间标签_timeLabels[_currentIndex] = currentTime;// 更新时间和当前索引_currentTime += _timeIncrement;_currentIndex = (_currentIndex + 1) % _maxPoints;// 更新X轴刻度显示zedGraphControl1.GraphPane.XAxis.Scale.TextLabels = GenerateTimeLabels();// 使图表无效以触发重绘zedGraphControl1.Invalidate();}private string[] GenerateTimeLabels(){string[] labels = new string[_maxPoints];for (int i = 0; i < _maxPoints; i++){labels[i] = _timeLabels[i].ToString("HH:mm:ss.fff");}return labels;}private void Form1_Load(object sender, EventArgs e){// 窗体加载事件处理方法(目前为空)}}
}

在这里插入图片描述

添加了个竖线,扫描分界线

using System;
using System.Windows.Forms;
using ZedGraph;
using System.Timers;namespace ECGPlot
{public partial class Form1 : Form{// 定义导联数量和每个导联的电压偏移量private const int ChannelCount = 8;private const int VoltageOffset = 500; // 每个导联的偏移量// 用于存储每个导联的数据点列表和曲线对象private PointPairList[] _dataLists;private LineItem[] _curves;// 定时器用于模拟ECG信号数据更新private System.Timers.Timer _timer;private int _currentIndex = 0;private int _maxPoints = 500;private double[][] _yValues;private double _timeIncrement = 0.1;private double _currentTime = 0;private Random _random = new Random();private DateTime[] _timeLabels;private LineObj _scanLine;public Form1(){InitializeComponent();InitializeGraph(); // 初始化图表StartTimer(); // 启动定时器}private void InitializeGraph(){// 获取图表区域对象GraphPane myPane = zedGraphControl1.GraphPane;// 设置图表标题和轴标题myPane.Title.Text = "ECG Data";myPane.XAxis.Title.Text = "Time (hh:mm:ss.fff)";myPane.YAxis.Title.Text = "Voltage";// 初始化数据点列表和曲线数组_dataLists = new PointPairList[ChannelCount];_curves = new LineItem[ChannelCount];_yValues = new double[ChannelCount][];_timeLabels = new DateTime[_maxPoints];for (int i = 0; i < ChannelCount; i++){// 为每个导联创建数据点列表和曲线对象,并添加到图表中_dataLists[i] = new PointPairList();_curves[i] = myPane.AddCurve("", _dataLists[i], GetColor(i), SymbolType.None);_yValues[i] = new double[_maxPoints];}// 移除图例myPane.Legend.IsVisible = false;// 设置X轴和Y轴的范围myPane.XAxis.Scale.Min = 0;myPane.XAxis.Scale.Max = _maxPoints;myPane.YAxis.Scale.Min = -800;myPane.YAxis.Scale.Max = 800 + VoltageOffset * (ChannelCount - 1);// 显示网格线myPane.XAxis.MajorGrid.IsVisible = true;myPane.YAxis.MajorGrid.IsVisible = true;// 隐藏Y=0的实线myPane.YAxis.MajorGrid.IsZeroLine = false;// 自定义Y轴刻度标注Scale yScale = myPane.YAxis.Scale;yScale.MajorStep = VoltageOffset;yScale.MinorStep = VoltageOffset;yScale.MajorStepAuto = false;yScale.MinorStepAuto = false;myPane.YAxis.ScaleFormatEvent += new Axis.ScaleFormatHandler(FormatYScale);// 设置X轴为文本类型myPane.XAxis.Type = AxisType.Text;// 设置X轴刻度字体大小myPane.XAxis.Scale.FontSpec.Size = 10; // 可以根据需要调整字体大小// 初始化时间标签DateTime startTime = DateTime.Now;for (int i = 0; i < _maxPoints; i++){_timeLabels[i] = startTime;}// 初始化扫描竖线_scanLine = new LineObj(System.Drawing.Color.Black, 0, -800, 0, 800 + VoltageOffset * (ChannelCount - 1));_scanLine.Line.Style = System.Drawing.Drawing2D.DashStyle.Dash;_scanLine.IsClippedToChartRect = true;myPane.GraphObjList.Add(_scanLine);// 应用更改并刷新图表zedGraphControl1.AxisChange();}private string FormatYScale(GraphPane pane, Axis axis, double val, int index){// 自定义Y轴刻度标注int leadIndex = (int)Math.Round(val / VoltageOffset);if (leadIndex >= 0 && leadIndex < ChannelCount){return $"Lead {leadIndex + 1}";}return "";}private System.Drawing.Color GetColor(int index){// 定义一组颜色用于不同导联的曲线System.Drawing.Color[] colors = {System.Drawing.Color.Gray,System.Drawing.Color.Gray,System.Drawing.Color.Gray,System.Drawing.Color.Gray,System.Drawing.Color.Gray,System.Drawing.Color.Gray,System.Drawing.Color.Gray,System.Drawing.Color.Gray,};// 根据索引返回颜色return colors[index % colors.Length];}private void StartTimer(){// 创建并配置定时器_timer = new System.Timers.Timer(50); // 100毫秒的更新频率_timer.Elapsed += OnTimedEvent; // 绑定定时器事件_timer.AutoReset = true; // 自动重置_timer.Enabled = true; // 启用定时器}private void OnTimedEvent(Object source, ElapsedEventArgs e){// 记录当前时间DateTime currentTime = DateTime.Now;// 为每个导联生成模拟ECG信号数据并更新曲线for (int i = 0; i < ChannelCount; i++){double voltage = _random.Next(-200, 200) + i * VoltageOffset; // 生成带偏移量的电压数据_yValues[i][_currentIndex] = voltage;if (_dataLists[i].Count < _maxPoints){// 添加新的数据点_dataLists[i].Add(_currentIndex, voltage);}else{// 更新现有数据点_dataLists[i][_currentIndex].Y = voltage;}}// 更新时间标签_timeLabels[_currentIndex] = currentTime;// 更新时间和当前索引_currentTime += _timeIncrement;_currentIndex = (_currentIndex + 1) % _maxPoints;// 更新X轴刻度显示zedGraphControl1.GraphPane.XAxis.Scale.TextLabels = GenerateTimeLabels();// 更新扫描竖线位置UpdateScanLine();// 使图表无效以触发重绘zedGraphControl1.Invalidate();}private string[] GenerateTimeLabels(){string[] labels = new string[_maxPoints];for (int i = 0; i < _maxPoints; i++){labels[i] = _timeLabels[i].ToString("HH:mm:ss.fff");}return labels;}private void UpdateScanLine(){// 移除旧的扫描竖线zedGraphControl1.GraphPane.GraphObjList.Remove(_scanLine);// 添加新的扫描竖线_scanLine = new LineObj(System.Drawing.Color.Black, _currentIndex, -800, _currentIndex, 800 + VoltageOffset * (ChannelCount - 1));_scanLine.Line.Style = System.Drawing.Drawing2D.DashStyle.Dash;_scanLine.IsClippedToChartRect = true;zedGraphControl1.GraphPane.GraphObjList.Add(_scanLine);// 应用更改并刷新图表zedGraphControl1.AxisChange();}private void Form1_Load(object sender, EventArgs e){// 窗体加载事件处理方法(目前为空)}}
}

在这里插入图片描述

我修改了扫描线左边和右边是不同颜色

using System;
using System.Windows.Forms;
using ZedGraph;
using System.Timers;namespace ECGPlot
{public partial class Form1 : Form{// 定义导联数量和每个导联的电压偏移量private const int ChannelCount = 8;private const int VoltageOffset = 500; // 每个导联的偏移量// 用于存储每个导联的数据点列表和曲线对象private PointPairList[] _dataListsLeft;private PointPairList[] _dataListsRight;private LineItem[] _curvesLeft;private LineItem[] _curvesRight;// 定时器用于模拟ECG信号数据更新private System.Timers.Timer _timer;private int _currentIndex = 0;private int _maxPoints = 500;private double[][] _yValues;private double _timeIncrement = 0.1;private double _currentTime = 0;private Random _random = new Random();private DateTime[] _timeLabels;private LineObj _scanLine;public Form1(){InitializeComponent();InitializeGraph(); // 初始化图表StartTimer(); // 启动定时器}private void InitializeGraph(){// 获取图表区域对象GraphPane myPane = zedGraphControl1.GraphPane;// 设置图表标题和轴标题myPane.Title.Text = "ECG Data";myPane.XAxis.Title.Text = "Time";myPane.YAxis.Title.Text = "Voltage";// 初始化数据点列表和曲线数组_dataListsLeft = new PointPairList[ChannelCount];_dataListsRight = new PointPairList[ChannelCount];_curvesLeft = new LineItem[ChannelCount];_curvesRight = new LineItem[ChannelCount];_yValues = new double[ChannelCount][];_timeLabels = new DateTime[_maxPoints];for (int i = 0; i < ChannelCount; i++){// 为每个导联创建数据点列表和曲线对象,并添加到图表中_dataListsLeft[i] = new PointPairList();_dataListsRight[i] = new PointPairList();_curvesLeft[i] = myPane.AddCurve("", _dataListsLeft[i], System.Drawing.Color.Black, SymbolType.None);_curvesRight[i] = myPane.AddCurve("", _dataListsRight[i], System.Drawing.ColorTranslator.FromHtml("#CCCCCC"), SymbolType.None);_yValues[i] = new double[_maxPoints];// 初始化右边灰色波形for (int j = 0; j < _maxPoints; j++){_dataListsRight[i].Add(j, double.NaN); // 初始化为NaN,表示没有数据}}// 移除图例myPane.Legend.IsVisible = false;// 设置X轴和Y轴的范围myPane.XAxis.Scale.Min = 0;myPane.XAxis.Scale.Max = _maxPoints;myPane.YAxis.Scale.Min = -800;myPane.YAxis.Scale.Max = 800 + VoltageOffset * (ChannelCount - 1);// 显示网格线myPane.XAxis.MajorGrid.IsVisible = true;myPane.YAxis.MajorGrid.IsVisible = true;// 隐藏Y=0的实线myPane.YAxis.MajorGrid.IsZeroLine = false;// 自定义Y轴刻度标注Scale yScale = myPane.YAxis.Scale;yScale.MajorStep = VoltageOffset;yScale.MinorStep = VoltageOffset;yScale.MajorStepAuto = false;yScale.MinorStepAuto = false;myPane.YAxis.ScaleFormatEvent += new Axis.ScaleFormatHandler(FormatYScale);// 设置X轴为文本类型myPane.XAxis.Type = AxisType.Text;// 设置X轴刻度字体大小myPane.XAxis.Scale.FontSpec.Size = 4; // 可以根据需要调整字体大小// 初始化时间标签DateTime startTime = DateTime.Now;for (int i = 0; i < _maxPoints; i++){_timeLabels[i] = startTime;}// 初始化扫描竖线_scanLine = new LineObj(System.Drawing.Color.Black, 0, -800, 0, 800 + VoltageOffset * (ChannelCount - 1));_scanLine.Line.Style = System.Drawing.Drawing2D.DashStyle.Dash;_scanLine.IsClippedToChartRect = true;myPane.GraphObjList.Add(_scanLine);// 应用更改并刷新图表zedGraphControl1.AxisChange();}private string FormatYScale(GraphPane pane, Axis axis, double val, int index){// 自定义Y轴刻度标注int leadIndex = (int)Math.Round(val / VoltageOffset);if (leadIndex >= 0 && leadIndex < ChannelCount){return $"Lead {leadIndex + 1}";}return "";}private void StartTimer(){// 创建并配置定时器_timer = new System.Timers.Timer(50); // 50毫秒的更新频率_timer.Elapsed += OnTimedEvent; // 绑定定时器事件_timer.AutoReset = true; // 自动重置_timer.Enabled = true; // 启用定时器}private void OnTimedEvent(Object source, ElapsedEventArgs e){// 记录当前时间DateTime currentTime = DateTime.Now;// 为每个导联生成模拟ECG信号数据并更新曲线for (int i = 0; i < ChannelCount; i++){double voltage = _random.Next(-200, 200) + i * VoltageOffset; // 生成带偏移量的电压数据_yValues[i][_currentIndex] = voltage;if (_dataListsLeft[i].Count < _maxPoints){// 添加新的数据点_dataListsLeft[i].Add(_currentIndex, voltage);}else{// 更新现有数据点_dataListsLeft[i][_currentIndex].Y = voltage;}// 更新右边灰色波形数据点_dataListsRight[i][_currentIndex].Y = voltage;}// 更新时间标签_timeLabels[_currentIndex] = currentTime;// 更新时间和当前索引_currentTime += _timeIncrement;_currentIndex = (_currentIndex + 1) % _maxPoints;// 更新曲线数据UpdateCurves();// 更新X轴刻度显示zedGraphControl1.GraphPane.XAxis.Scale.TextLabels = GenerateTimeLabels();// 更新扫描竖线位置UpdateScanLine();// 使图表无效以触发重绘zedGraphControl1.Invalidate();}private void UpdateCurves(){for (int i = 0; i < ChannelCount; i++){// 更新左边(黑色)部分PointPairList leftPoints = _curvesLeft[i].Points as PointPairList;leftPoints.Clear();for (int j = 0; j < _maxPoints; j++){if (j <= _currentIndex){leftPoints.Add(j, _yValues[i][j]);}else{leftPoints.Add(j, double.NaN);}}}}private string[] GenerateTimeLabels(){string[] labels = new string[_maxPoints];for (int i = 0; i < _maxPoints; i++){labels[i] = _timeLabels[i].ToString("HH:mm:ss.fff");}return labels;}private void UpdateScanLine(){// 移除旧的扫描竖线zedGraphControl1.GraphPane.GraphObjList.Remove(_scanLine);// 添加新的扫描竖线_scanLine = new LineObj(System.Drawing.Color.Black, _currentIndex, -800, _currentIndex, 800 + VoltageOffset * (ChannelCount - 1));_scanLine.Line.Style = System.Drawing.Drawing2D.DashStyle.Dash;_scanLine.IsClippedToChartRect = true;zedGraphControl1.GraphPane.GraphObjList.Add(_scanLine);// 应用更改并刷新图表zedGraphControl1.AxisChange();}private void Form1_Load(object sender, EventArgs e){// 窗体加载事件处理方法(目前为空)}}
}

在这里插入图片描述

第一次扫描时间刻度的设置

using System;
using System.Windows.Forms;
using ZedGraph;
using System.Timers;
using System.Drawing;
using System.IO;namespace ECGPlot
{public partial class Form1 : Form{// 定义导联数量和每个导联的电压偏移量private const int ChannelCount = 8;private const int VoltageOffset = 500; // 每个导联的偏移量// 用于存储每个导联的数据点列表和曲线对象private PointPairList[] _dataListsLeft;private PointPairList[] _dataListsRight;private LineItem[] _curvesLeft;private LineItem[] _curvesRight;// 定时器用于模拟ECG信号数据更新private System.Timers.Timer _timer;private int _currentIndex = 0;private int _maxPoints = 1000;private double[][] _yValues;private double _timeIncrement = 0.1;private double _currentTime = 0;private Random _random = new Random();private DateTime[] _timeLabels;private LineObj _scanLine;private bool _firstScan = true;public Form1(){InitializeComponent();InitializeGraph(); // 初始化图表InitializeDataFile(); // 初始化数据文件StartTimer(); // 启动定时器}private void InitializeGraph(){// 获取图表区域对象GraphPane myPane = zedGraphControl1.GraphPane;// 设置图表标题和轴标题myPane.Title.Text = "ECG Data";myPane.XAxis.Title.Text = "Time";myPane.YAxis.Title.Text = "Voltage";// 初始化数据点列表和曲线数组_dataListsLeft = new PointPairList[ChannelCount];_dataListsRight = new PointPairList[ChannelCount];_curvesLeft = new LineItem[ChannelCount];_curvesRight = new LineItem[ChannelCount];_yValues = new double[ChannelCount][];_timeLabels = new DateTime[_maxPoints];for (int i = 0; i < ChannelCount; i++){// 为每个导联创建数据点列表和曲线对象,并添加到图表中_dataListsLeft[i] = new PointPairList();_dataListsRight[i] = new PointPairList();_curvesLeft[i] = myPane.AddCurve("", _dataListsLeft[i], System.Drawing.Color.Black, SymbolType.None);_curvesRight[i] = myPane.AddCurve("", _dataListsRight[i], Color.FromArgb(100, Color.Black), SymbolType.None);_yValues[i] = new double[_maxPoints];// 初始化右边灰色波形for (int j = 0; j < _maxPoints; j++){_dataListsRight[i].Add(j, double.NaN); // 初始化为NaN,表示没有数据}}// 移除图例myPane.Legend.IsVisible = false;// 设置X轴和Y轴的范围myPane.XAxis.Scale.Min = 0;myPane.XAxis.Scale.Max = _maxPoints;myPane.YAxis.Scale.Min = -800;myPane.YAxis.Scale.Max = 800 + VoltageOffset * (ChannelCount - 1);// 网格线myPane.XAxis.MajorGrid.IsVisible = false; // 关闭纵向主要网格线myPane.XAxis.MinorGrid.IsVisible = false; // 关闭纵向次要网格线myPane.YAxis.MajorGrid.IsVisible = true;// 隐藏Y=0的实线myPane.YAxis.MajorGrid.IsZeroLine = false;// 自定义Y轴刻度标注Scale yScale = myPane.YAxis.Scale;yScale.MajorStep = VoltageOffset;yScale.MinorStep = VoltageOffset;yScale.MajorStepAuto = false;yScale.MinorStepAuto = false;myPane.YAxis.ScaleFormatEvent += new Axis.ScaleFormatHandler(FormatYScale);myPane.YAxis.Scale.FontSpec.Size = 8;myPane.YAxis.Scale.FontSpec.Family = "Times New Roman"; // 设置字体为// 设置X轴为文本类型myPane.XAxis.Type = AxisType.Text;// 设置X轴刻度字体大小myPane.XAxis.Scale.FontSpec.Size = 5; // 可以根据需要调整字体大小myPane.XAxis.Scale.FontSpec.Family = "Times New Roman"; // 设置字体为// 初始化时间标签DateTime startTime = DateTime.Now;for (int i = 0; i < _maxPoints; i++){_timeLabels[i] = startTime;}// 初始化扫描竖线_scanLine = new LineObj(System.Drawing.Color.Black, 0, -800, 0, 800 + VoltageOffset * (ChannelCount - 1));_scanLine.Line.Style = System.Drawing.Drawing2D.DashStyle.Dash;_scanLine.IsClippedToChartRect = true;myPane.GraphObjList.Add(_scanLine);// 应用更改并刷新图表zedGraphControl1.AxisChange();}private string FormatYScale(GraphPane pane, Axis axis, double val, int index){// 自定义Y轴刻度标注int leadIndex = (int)Math.Round(val / VoltageOffset);if (leadIndex >= 0 && leadIndex < ChannelCount){return $"Lead {leadIndex + 1}";}return "";}private void StartTimer(){// 创建并配置定时器_timer = new System.Timers.Timer(50); // 50毫秒的更新频率_timer.Elapsed += OnTimedEvent; // 绑定定时器事件_timer.AutoReset = true; // 自动重置_timer.Enabled = true; // 启用定时器}private void OnTimedEvent(Object source, ElapsedEventArgs e){// 记录当前时间DateTime currentTime = DateTime.Now;// 为每个导联生成模拟ECG信号数据并更新曲线for (int i = 0; i < ChannelCount; i++){double voltage = _random.Next(-200, 200) + i * VoltageOffset; // 生成带偏移量的电压数据_yValues[i][_currentIndex] = voltage;if (_dataListsLeft[i].Count < _maxPoints){// 添加新的数据点_dataListsLeft[i].Add(_currentIndex, voltage);}else{// 更新现有数据点_dataListsLeft[i][_currentIndex].Y = voltage;}// 更新右边灰色波形数据点_dataListsRight[i][_currentIndex].Y = voltage;}// 更新时间标签_timeLabels[_currentIndex] = currentTime;// 实时保存数据到文件SaveCurrentDataToFile(_currentIndex, currentTime);// 更新时间和当前索引_currentTime += _timeIncrement;_currentIndex = (_currentIndex + 1) % _maxPoints;// 更新曲线数据UpdateCurves();// 更新X轴刻度显示zedGraphControl1.GraphPane.XAxis.Scale.TextLabels = GeneratePartialTimeLabels();// 更新扫描竖线位置UpdateScanLine();// 使图表无效以触发重绘zedGraphControl1.Invalidate();}private void UpdateCurves(){for (int i = 0; i < ChannelCount; i++){// 更新左边(黑色)部分PointPairList leftPoints = _curvesLeft[i].Points as PointPairList;leftPoints.Clear();for (int j = 0; j < _maxPoints; j++){if (j <= _currentIndex){leftPoints.Add(j, _yValues[i][j]);}else{leftPoints.Add(j, double.NaN);}}}}private string[] GeneratePartialTimeLabels(){string[] labels = new string[_maxPoints];for (int i = 0; i < _maxPoints; i++){if (i <= _currentIndex || !_firstScan){labels[i] = _timeLabels[i].ToString("HH:mm:ss.fff");}else{labels[i] = ""; // 未扫描到的位置使用空字符串}}// 仅在第一次扫描后将_firstScan设为falseif (_currentIndex == _maxPoints - 1){_firstScan = false;}return labels;}private void UpdateScanLine(){// 移除旧的扫描竖线zedGraphControl1.GraphPane.GraphObjList.Remove(_scanLine);// 添加新的扫描竖线_scanLine = new LineObj(System.Drawing.Color.Black, _currentIndex, -800, _currentIndex, 800 + VoltageOffset * (ChannelCount - 1));_scanLine.Line.Style = System.Drawing.Drawing2D.DashStyle.Solid;_scanLine.IsClippedToChartRect = true;zedGraphControl1.GraphPane.GraphObjList.Add(_scanLine);// 应用更改并刷新图表zedGraphControl1.AxisChange();}private void SaveCurrentDataToFile(int index, DateTime time){string filePath = "ECGData.txt"; // 可以修改为需要保存的文件路径try{using (StreamWriter writer = new StreamWriter(filePath, true)) // 以追加模式打开文件{// 写入索引writer.Write($"{index},");// 写入8个导联的数据for (int j = 0; j < ChannelCount; j++){writer.Write($"{_yValues[j][index]},");}// 写入时间writer.WriteLine(time.ToString("HH:mm:ss.fff"));}}catch (Exception ex){MessageBox.Show($"Error saving data: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);}}private void InitializeDataFile(){string filePath = "ECGData.txt"; // 可以修改为需要保存的文件路径try{using (StreamWriter writer = new StreamWriter(filePath, false)) // 以覆盖模式打开文件{// 写入表头writer.WriteLine("Index,Lead1,Lead2,Lead3,Lead4,Lead5,Lead6,Lead7,Lead8,Time");}}catch (Exception ex){MessageBox.Show($"Error initializing data file: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);}}private void btnSaveData_Click(object sender, EventArgs e){using (SaveFileDialog saveFileDialog = new SaveFileDialog()){saveFileDialog.Filter = "Text files (*.txt)|*.txt";saveFileDialog.Title = "Save ECG Data";if (saveFileDialog.ShowDialog() == DialogResult.OK){SaveDataToFile(saveFileDialog.FileName);}}}private void SaveDataToFile(string filePath){try{using (StreamWriter writer = new StreamWriter(filePath)){// 写入表头writer.WriteLine("Index,Lead1,Lead2,Lead3,Lead4,Lead5,Lead6,Lead7,Lead8,Time");for (int i = 0; i < _maxPoints; i++){// 写入索引writer.Write($"{i},");// 写入8个导联的数据for (int j = 0; j < ChannelCount; j++){writer.Write($"{_yValues[j][i]},");}// 写入时间writer.WriteLine(_timeLabels[i].ToString("HH:mm:ss.fff"));}}MessageBox.Show("Data saved successfully.", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);}catch (Exception ex){MessageBox.Show($"Error saving data: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);}}private void Form1_Load(object sender, EventArgs e){// 窗体加载事件处理方法(目前为空)}}
}

在这里插入图片描述

提高响应速度

在Windows窗体应用程序中使用System.Timers.Timer,其回调在不同于UI线程的线程上运行。如果你要更新UI控件(如ZedGraphControl),最好使用System.Windows.Forms.Timer,因为它在UI线程上执行。下面是你如何改进代码以使用System.Windows.Forms.Timer来避免线程间的调度问题并提高响应速度。
此外,确保图表在更新时不会频繁刷新,这样可以减少卡顿。

修改后的代码

using System;
using System.Windows.Forms;
using ZedGraph;
using System.Drawing;
using System.IO;namespace ECGPlot
{public partial class Form1 : Form{// 定义导联数量和每个导联的电压偏移量private const int ChannelCount = 8;private const int VoltageOffset = 500; // 每个导联的偏移量// 用于存储每个导联的数据点列表和曲线对象private PointPairList[] _dataListsLeft;private PointPairList[] _dataListsRight;private LineItem[] _curvesLeft;private LineItem[] _curvesRight;// 定时器用于模拟ECG信号数据更新private System.Windows.Forms.Timer _timer;private int _currentIndex = 0;private int _maxPoints = 1101;private double[][] _yValues;private double _timeIncrement = 0.1;private double _currentTime = 0;private Random _random = new Random();private DateTime[] _timeLabels;private LineObj _scanLine;private bool _firstScan = true;public Form1(){InitializeComponent();InitializeGraph(); // 初始化图表InitializeDataFile(); // 初始化数据文件StartTimer(); // 启动定时器}private void InitializeGraph(){// 获取图表区域对象GraphPane myPane = zedGraphControl1.GraphPane;// 设置图表标题和轴标题myPane.Title.Text = "ECG Data";myPane.XAxis.Title.Text = "Time";myPane.YAxis.Title.Text = "Voltage";// 初始化数据点列表和曲线数组_dataListsLeft = new PointPairList[ChannelCount];_dataListsRight = new PointPairList[ChannelCount];_curvesLeft = new LineItem[ChannelCount];_curvesRight = new LineItem[ChannelCount];_yValues = new double[ChannelCount][];_timeLabels = new DateTime[_maxPoints];for (int i = 0; i < ChannelCount; i++){// 为每个导联创建数据点列表和曲线对象,并添加到图表中_dataListsLeft[i] = new PointPairList();_dataListsRight[i] = new PointPairList();_curvesLeft[i] = myPane.AddCurve("", _dataListsLeft[i], System.Drawing.Color.Black, SymbolType.None);_curvesRight[i] = myPane.AddCurve("", _dataListsRight[i], Color.FromArgb(100, Color.Black), SymbolType.None);_yValues[i] = new double[_maxPoints];// 初始化右边灰色波形for (int j = 0; j < _maxPoints; j++){_dataListsRight[i].Add(j, double.NaN); // 初始化为NaN,表示没有数据}}// 移除图例myPane.Legend.IsVisible = false;// 设置X轴和Y轴的范围myPane.XAxis.Scale.Min = 0;myPane.XAxis.Scale.Max = _maxPoints;myPane.YAxis.Scale.Min = -800;myPane.YAxis.Scale.Max = 800 + VoltageOffset * (ChannelCount - 1);// 设置X轴主刻度步长,使刻度标签不那么密集myPane.XAxis.Scale.MajorStep = 100; // 可以根据需要调整这个值// 网格线myPane.XAxis.MajorGrid.IsVisible = false; // 关闭纵向主要网格线myPane.XAxis.MinorGrid.IsVisible = false; // 关闭纵向次要网格线myPane.YAxis.MajorGrid.IsVisible = true;// 隐藏Y=0的实线myPane.YAxis.MajorGrid.IsZeroLine = false;// 自定义Y轴刻度标注Scale yScale = myPane.YAxis.Scale;yScale.MajorStep = VoltageOffset;yScale.MinorStep = VoltageOffset;yScale.MajorStepAuto = false;yScale.MinorStepAuto = false;myPane.YAxis.ScaleFormatEvent += new Axis.ScaleFormatHandler(FormatYScale);myPane.YAxis.Scale.FontSpec.Size = 8;myPane.YAxis.Scale.FontSpec.Family = "Times New Roman"; // 设置字体为// 设置X轴为文本类型myPane.XAxis.Type = AxisType.Text;// 设置X轴刻度字体大小myPane.XAxis.Scale.FontSpec.Size = 8; // 可以根据需要调整字体大小myPane.XAxis.Scale.FontSpec.Family = "Times New Roman"; // 设置字体为// 初始化时间标签DateTime startTime = DateTime.Now;for (int i = 0; i < _maxPoints; i++){_timeLabels[i] = startTime;}// 初始化扫描竖线_scanLine = new LineObj(System.Drawing.Color.Black, 0, -800, 0, 800 + VoltageOffset * (ChannelCount - 1));_scanLine.Line.Style = System.Drawing.Drawing2D.DashStyle.Dash;_scanLine.IsClippedToChartRect = true;myPane.GraphObjList.Add(_scanLine);// 应用更改并刷新图表zedGraphControl1.AxisChange();}private string FormatYScale(GraphPane pane, Axis axis, double val, int index){// 自定义Y轴刻度标注int leadIndex = (int)Math.Round(val / VoltageOffset);if (leadIndex >= 0 && leadIndex < ChannelCount){return $"Lead {leadIndex + 1}";}return "";}private void StartTimer(){// 创建并配置定时器_timer = new System.Windows.Forms.Timer();_timer.Interval = 50; // 50毫秒的更新频率_timer.Tick += OnTimedEvent; // 绑定定时器事件_timer.Start(); // 启用定时器}private void OnTimedEvent(Object source, EventArgs e){// 记录当前时间DateTime currentTime = DateTime.Now;// 为每个导联生成模拟ECG信号数据并更新曲线for (int i = 0; i < ChannelCount; i++){double voltage = _random.Next(-200, 200) + i * VoltageOffset; // 生成带偏移量的电压数据_yValues[i][_currentIndex] = voltage;if (_dataListsLeft[i].Count < _maxPoints){// 添加新的数据点_dataListsLeft[i].Add(_currentIndex, voltage);}else{// 更新现有数据点_dataListsLeft[i][_currentIndex].Y = voltage;}// 更新右边灰色波形数据点_dataListsRight[i][_currentIndex].Y = voltage;}// 更新时间标签_timeLabels[_currentIndex] = currentTime;// 实时保存数据到文件SaveCurrentDataToFile(_currentIndex, currentTime);// 更新时间和当前索引_currentTime += _timeIncrement;_currentIndex = (_currentIndex + 1) % _maxPoints;// 更新曲线数据UpdateCurves();// 更新X轴刻度显示zedGraphControl1.GraphPane.XAxis.Scale.TextLabels = GeneratePartialTimeLabels();// 更新扫描竖线位置UpdateScanLine();// 使图表无效以触发重绘zedGraphControl1.Invalidate();}private void UpdateCurves(){for (int i = 0; i < ChannelCount; i++){// 更新左边(黑色)部分PointPairList leftPoints = _curvesLeft[i].Points as PointPairList;leftPoints.Clear();for (int j = 0; j < _maxPoints; j++){if (j <= _currentIndex){leftPoints.Add(j, _yValues[i][j]);}else{leftPoints.Add(j, double.NaN);}}}}private string[] GeneratePartialTimeLabels(){string[] labels = new string[_maxPoints];for (int i = 0; i < _maxPoints; i++){if (i <= _currentIndex || !_firstScan){labels[i] = _timeLabels[i].ToString("HH:mm:ss.fff");}else{labels[i] = ""; // 未扫描到的位置使用空字符串}}// 仅在第一次扫描后将_firstScan设为falseif (_currentIndex == _maxPoints - 1){_firstScan = false;}return labels;}private void UpdateScanLine(){// 移除旧的扫描竖线zedGraphControl1.GraphPane.GraphObjList.Remove(_scanLine);// 添加新的扫描竖线_scanLine = new LineObj(System.Drawing.Color.Black, _currentIndex, -800, _currentIndex, 800 + VoltageOffset * (ChannelCount - 1));_scanLine.Line.Style = System.Drawing.Drawing2D.DashStyle.Solid;_scanLine.IsClippedToChartRect = true;zedGraphControl1.GraphPane.GraphObjList.Add(_scanLine);// 应用更改并刷新图表zedGraphControl1.AxisChange();}private void SaveCurrentDataToFile(int index, DateTime time){string filePath = "ECGData.txt"; // 可以修改为需要保存的文件路径try{using (StreamWriter writer = new StreamWriter(filePath, true)) // 以追加模式打开文件{// 写入索引writer.Write($"{index},");// 写入8个导联的数据for (int j = 0; j < ChannelCount; j++){writer.Write($"{_yValues[j][index]},");}// 写入时间writer.WriteLine(time.ToString("HH:mm:ss.fff"));}}catch (Exception ex){MessageBox.Show($"Error saving data: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);}}private void InitializeDataFile(){string filePath = "ECGData.txt"; // 可以修改为需要保存的文件路径try{using (StreamWriter writer = new StreamWriter(filePath, false)) // 以覆盖模式打开文件{// 写入表头writer.WriteLine("Index,Lead1,Lead2,Lead3,Lead4,Lead5,Lead6,Lead7,Lead8,Time");}}catch (Exception ex){MessageBox.Show($"Error initializing data file: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);}}private void btnSaveData_Click(object sender, EventArgs e){using (SaveFileDialog saveFileDialog = new SaveFileDialog()){saveFileDialog.Filter = "Text files (*.txt)|*.txt";saveFileDialog.Title = "Save ECG Data";if (saveFileDialog.ShowDialog() == DialogResult.OK){SaveDataToFile(saveFileDialog.FileName);}}}private void SaveDataToFile(string filePath){try{using (StreamWriter writer = new StreamWriter(filePath)){// 写入表头writer.WriteLine("Index,Lead1,Lead2,Lead3,Lead4,Lead5,Lead6,Lead7,Lead8,Time");for (int i = 0; i < _maxPoints; i++){// 写入索引writer.Write($"{i},");// 写入8个导联的数据for (int j = 0; j < ChannelCount; j++){writer.Write($"{_yValues[j][i]},");}// 写入时间writer.WriteLine(_timeLabels[i].ToString("HH:mm:ss.fff"));}}MessageBox.Show("Data saved successfully.", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);}catch (Exception ex){MessageBox.Show($"Error saving data: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);}}private void Form1_Load(object sender, EventArgs e){// 窗体加载事件处理方法(目前为空)}}
}

解释

  1. 将定时器从System.Timers.Timer更改为System.Windows.Forms.Timer,使其在UI线程上执行更新操作。
  2. System.Windows.Forms.Timer的回调在UI线程上运行,消除了跨线程更新控件的问题。
  3. 调整_timer.Interval来控制刷新频率,这里设置为50毫秒。
  4. OnTimedEvent方法中直接更新UI,消除了跨线程调用的开销。
    能够改善响应速度和卡顿问题。

相关文章:

C#编写多导联扫描式的波形图Demo

本代码调用ZedGraph绘图框架&#xff0c;自己先安装好ZedGraph环境&#xff0c;然后拖一个zedGraphControl控件就行了&#xff0c;直接黏贴下面代码 基本代码显示 using System; using System.Windows.Forms; using ZedGraph; using System.Timers;namespace ECGPlot {public…...

QT网络编程

Qt 给用户提供了网络编程的接口&#xff0c;包括TCP、UDP、HTTP三种协议的API以及各种类&#xff0c;可以了解一下。 而在 QT 中想要使用网络编程&#xff0c;必须在pro文件中添加 network 模块&#xff0c;否则无法包含网络编程所需的头文件。 UDP UDP是传输层的协议&#…...

Django ASGI服务

1. ASGI简介 在Django中, ASGI(Asynchronous Server Gateway Interface)的引入使得Django应用能够支持异步编程. 从Django 3.0开始, Django就增加了对ASGI的支持, 但直到Django 3.1才正式推荐在生产环境中使用ASGI. ASGI是一个用于Python的异步Web服务器的标准接口, 它允许你运…...

Servlet(2)

1、WebServlet 这个注解可以用来修饰一个Servlet类&#xff0c;可以简化配置&#xff0c;替代Web.xml中 的servlet配置 ①value属性 表示指定某个url-pattern ②urlPatterns属性 表示接受多个不同的url-pattern,多个值写在一对{}中&#xff0c;逗号分隔 补充;url-pattern…...

电竞玩家的云端盛宴!四大云电脑平台:ToDesk、顺网云、青椒云、极云普惠云实测大比拼

本文目录 一、云电脑概念及市场需求二、云电竞性能测试2.1 ToDesk云电脑2.2 顺网云2.3 青椒云2.4 极云普惠云电脑 三、四大云电脑平台综合配置对比3.1 CPU处理器3.2 GPU显卡3.3 内存 四、总结 一、云电脑概念及市场需求 在数字化时代的推动下&#xff0c;云计算技术日益成熟&a…...

基础复习(反射、注解、动态代理)

反射 反射&#xff0c;指的是加载类的字节码到内存&#xff0c;并以编程的方法解刨出类中的各个成分&#xff08;成员变量、方法、构造器等&#xff09;。 1.获取类的字节码 &#xff08;3种方式&#xff09; public class Test1Class{public static void main(String[] arg…...

OGG同步目标端中文乱码处理

现象说明&#xff1a; 源端字符集&#xff1a;AMERICAN_AMERICA.ZHS16GBK 目标端字符集&#xff1a;AMERICAN_AMERICA.AL32UTF8 源端同步过来的数据显示中文乱码。 查询数据库表中含有乱码的字段&#xff1a; select * from xx.xxxx a where to_char(a.crtopetime,yyyy-mm-…...

使用WPF调用Python进行图像灰度处理

1. 前言 在本文中&#xff0c;我们将通过WPF应用程序调用Python脚本进行图像灰度处理。我们将使用Python的OpenCV库来处理图像&#xff0c;并将其转换为灰度图像&#xff0c;然后通过WPF界面来启动Python进程并展示结果。 2. 准备工作 在开始之前&#xff0c;请确保系统已经…...

(二)测试工具

16. 如何进行浏览器兼容性测试? 正确回答通过率:38.0%[ 详情 ] 推荐指数: ★★★★★ 试题难度: 高难 1、兼容性测试含义 兼容性测试是指要测试的软件在不同的硬件平台上、不同的应用软件之间、不同的操作系统中、不同的网络环境中是否可以正常的运行、有无异常的测试过程…...

springboot 博客交流平台-计算机毕业设计源码56406

摘要 博客交流平台 作为一种重要的网络平台&#xff0c;为用户提供了展示自我、分享经验和与他人互动的空间。在国内外&#xff0c;研究者们关注博客交流平台 的各个方面&#xff0c;并取得了显著的进展。研究内容主要包括用户体验和界面设计、社交化和互动性、多媒体内容支持、…...

从0开始搭建vue + flask 旅游景点数据分析系统( 八):美化前端可视化图形

这一期来美化我们仅有的三个可视化图形&#xff08;可怜&#xff09;&#xff0c;毕竟&#xff0c;帅是一辈子的事。 1 折线图改面积图&#xff08;渐变色&#xff09; 需求&#xff1a;折线图改为蓝色的面积图&#xff0c;并且有一点的渐变色。 这个非常简单&#xff0c;只…...

【前端面试】七、算法-迭代器和生成器

目录 1.迭代器 2.生成器 1.迭代器 lterator&#xff1a;也被称作游标Cursor&#xff0c;是一种设计模式。迭代器提供了一种遍历内容的方法(比如 JS 迭代器中的next)&#xff0c;而不需要关心内部构造。 // 迭代器的遍历const s new Set([1,2,3,4,5])const it s.values()//…...

vs+qt一些问题

一直遇到的两个问题&#xff0c;今天解决了 1、 因为前后端分离&#xff0c;前端写完了&#xff0c;后端还在一直修改&#xff0c;但是每次都是单独打开的后端的sln&#xff0c;所以会出现这个&#xff0c;把前端的模块删掉就好了。 2、打开vs项目&#xff0c;很多报错&#…...

基于K8S配置Jenkins主从节点实例

基于K8S配置Jenkins主从节点实例 1.配置Jenkins主节点1.确认 Jenkins Pod 名称2.进入 Jenkins Pod&#xff1a;3.生成SSH密钥对4.将公钥复制到目标节点&#xff1a; 2.配置Jenkins的node1节点1.安装java2.配置 Jenkins node1节点的 Java 路径1.添加Java环境变量2.生效Java环境变…...

萱仔环境记录——git的安装流程

最近由于我有一个大模型的offer&#xff0c;由于我只在实验室的电脑上装了git&#xff0c;我准备在自己的笔记本上本地安装一个git&#xff0c;也给我的一个师弟讲解一下git安装和使用的过程&#xff0c;给我的环境安装章节添砖加瓦。 github是基于git的一个仓库托管平台。 g…...

品味食家巷蛋奶酪饼,感受西北美食魅力

在广袤的西北大地&#xff0c;美食的丰富多样令人叹为观止。而食家巷蛋奶酪饼&#xff0c;宛如一颗璀璨的明珠&#xff0c;散发着独特的魅力。 这款蛋奶酪饼&#xff0c;是传统工艺与现代口味的完美融合。而当你继续品尝&#xff0c;鸡蛋的鲜嫩和奶酪的浓郁醇厚便会在口中交融…...

常用的图像增强操作

我们将介绍如何用PIL库实现一些简单的图像增强方法。 [!NOTE] 初始化配置 import numpy as np from PIL import Image, ImageOps, ImageEnhance import warningswarnings.filterwarnings(ignore) IMAGE_SIZE 640[!important] 辅助函数 主要用于控制增强幅度 def int_param…...

探索TinyDB的轻量级魅力:Python中的微型数据库

文章目录 探索TinyDB的轻量级魅力&#xff1a;Python中的微型数据库背景&#xff1a;为何选择TinyDB&#xff1f;什么是TinyDB&#xff1f;如何安装TinyDB&#xff1f;5个简单的库函数使用方法3个场景下的应用实例常见问题与解决方案总结 探索TinyDB的轻量级魅力&#xff1a;Py…...

模型优化学习笔记—Adam算法

首先复习一下&#xff1a; 动量梯度下降&#xff1a; 1、算出dw、db 2、计算指数加权&#xff08;移动&#xff09;平均 vdw k *vdw (1-k)*dw vdb k *vdb (1-k)*db 3、梯度下降 w w - r*vdw b b - r*vdb RMSprop&#xff1a; 1、算出dw和db 2、算指数平均值&am…...

车辆出险报告(h5)-车辆出险记录接口-车辆相关接口

接口简介&#xff1a;通过vin及行驶证查询车辆出险、理赔、事故记录接口。查询成功率99%,返回URL地址的查询报告。 不能对返回的报告进行任何的修改&#xff0c;否则由用户自行承担相应的责任 报告结果只保留30天&#xff0c;如需永久保存&#xff0c;请您查询后自行保存 接口地…...

前端导出带有合并单元格的列表

// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

剑指offer20_链表中环的入口节点

链表中环的入口节点 给定一个链表&#xff0c;若其中包含环&#xff0c;则输出环的入口节点。 若其中不包含环&#xff0c;则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户&#xff0c;但你不希望用 root 权限运行 ns-3&#xff08;这是对的&#xff0c;ns3 工具会拒绝 root&#xff09;&#xff0c;你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案&#xff1a;创建非 roo…...

论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)

笔记整理&#xff1a;刘治强&#xff0c;浙江大学硕士生&#xff0c;研究方向为知识图谱表示学习&#xff0c;大语言模型 论文链接&#xff1a;http://arxiv.org/abs/2407.16127 发表会议&#xff1a;ISWC 2024 1. 动机 传统的知识图谱补全&#xff08;KGC&#xff09;模型通过…...

AI编程--插件对比分析:CodeRider、GitHub Copilot及其他

AI编程插件对比分析&#xff1a;CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展&#xff0c;AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者&#xff0c;分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

Mac下Android Studio扫描根目录卡死问题记录

环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中&#xff0c;提示一个依赖外部头文件的cpp源文件需要同步&#xff0c;点…...

uniapp 字符包含的相关方法

在uniapp中&#xff0c;如果你想检查一个字符串是否包含另一个子字符串&#xff0c;你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的&#xff0c;但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...

第7篇:中间件全链路监控与 SQL 性能分析实践

7.1 章节导读 在构建数据库中间件的过程中&#xff0c;可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中&#xff0c;必须做到&#xff1a; &#x1f50d; 追踪每一条 SQL 的生命周期&#xff08;从入口到数据库执行&#xff09;&#…...

学习一下用鸿蒙​​DevEco Studio HarmonyOS5实现百度地图

在鸿蒙&#xff08;HarmonyOS5&#xff09;中集成百度地图&#xff0c;可以通过以下步骤和技术方案实现。结合鸿蒙的分布式能力和百度地图的API&#xff0c;可以构建跨设备的定位、导航和地图展示功能。 ​​1. 鸿蒙环境准备​​ ​​开发工具​​&#xff1a;下载安装 ​​De…...