using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.IO.Ports;
using System.IO;
using System.Globalization;
using System.Windows.Forms.DataVisualization.Charting;
using OfficeOpenXml;
using OfficeOpenXml.Style;
using OfficeOpenXml.Drawing.Chart;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Linq;
namespace PhysicsExperimentDisplay
{
[DataContract]
public class ExperimentRecord
{
[DataMember] public string TimeStamp { get; set; }
[DataMember] public double T1 { get; set; }
[DataMember] public double T2 { get; set; }
[DataMember] public double PlateWidth { get; set; } // 挡光片宽度 (m 或 mm,根据使用习惯)
[DataMember] public double GateSpacing { get; set; } // 光电门间距
[DataMember] public double V1 { get; set; }
[DataMember] public double V2 { get; set; }
[DataMember] public double Acc { get; set; }
}
public partial class MainForm : Form
{
// 控件声明
private TableLayoutPanel mainLayout;
private GroupBox gbSerial;
private ComboBox cmbPorts;
private ComboBox cmbBaudRate;
private Button btnConnect;
private Label lblPort;
private Label lblBaud;
private GroupBox gbData;
private DataGridView dgvResults;
private GroupBox gbChart;
private Button btnExport;
private Button btnClear;
private SerialPort serialPort;
private FlowLayoutPanel buttonPanel;
private TableLayoutPanel serialLayout;
private Chart chartPlot;
// 新增控件:输入挡光片宽度和光电门间距、保存/加载历史
private Label lblPlateWidth;
private TextBox txtPlateWidth;
private Label lblGateSpacing;
private TextBox txtGateSpacing;
private Button btnSaveExperiment;
private Button btnLoadExperiment;
// 数据存储
private List<double> accelerationData = new List<double>();
private int dataPointCount = 0;
private DateTime experimentStartTime = DateTime.Now;
// 当前实验完整原始记录
private List<ExperimentRecord> currentRecords = new List<ExperimentRecord>();
// 保存目录
private readonly string experimentsFolder;
public object JsonConvert { get; private set; }
public object Newtonsoft { get; private set; }
public MainForm()
{
// 初始化保存目录路径
experimentsFolder = Path.Combine(Application.StartupPath, "Experiments");
Directory.CreateDirectory(experimentsFolder);
// 初始化组件
InitializeComponent();
// 初始化串口和图表
LoadAvailablePorts();
InitializePlot();
// 绑定按钮事件(确保事件处理器已实现)
btnConnect.Click += btnConnect_Click;
btnExport.Click += btnExport_Click;
btnClear.Click += btnClear_Click;
btnSaveExperiment.Click += btnSaveExperiment_Click;
btnLoadExperiment.Click += btnLoadExperiment_Click;
}
private void btnLoadExperiment_Click(object sender, EventArgs e)
{
string selectedFile = null;
OpenFileDialog ofd = null;
try
{
// 在 try 内创建对话框,确保构造期抛出的异常也能被捕获
ofd = new System.Windows.Forms.OpenFileDialog();
ofd.InitialDirectory = experimentsFolder;
ofd.Filter = "CSV 文件 (*.csv)|*.csv|JSON 文件 (*.json)|*.json|所有文件 (*.*)|*.*";
ofd.Title = "选择要加载的实验文件";
// 优先在标准 UI 环境中弹出系统对话框(传入父窗口)
if (ofd.ShowDialog(this) == DialogResult.OK)
{
selectedFile = ofd.FileName;
}
else
{
return;
}
}
catch (NotImplementedException nie)
{
// 记录诊断信息到日志文件,便于后续调查运行环境
try
{
string logPath = Path.Combine(experimentsFolder, "dialog_error.log");
var sbLog = new StringBuilder();
sbLog.AppendLine("=== OpenFileDialog NotImplementedException ===");
sbLog.AppendLine(DateTime.Now.ToString("o"));
sbLog.AppendLine(nie.ToString());
try
{
sbLog.AppendLine($"OpenFileDialog runtime type: {ofd?.GetType().FullName ?? "null"}");
sbLog.AppendLine($"OpenFileDialog assembly: {ofd?.GetType().Assembly.FullName ?? "null"}");
}
catch { /* 忽略获取类型信息失败 */ }
try
{
sbLog.AppendLine($"Framework: {System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription}");
sbLog.AppendLine($"OS: {System.Runtime.InteropServices.RuntimeInformation.OSDescription}");
sbLog.AppendLine($"Is64BitProcess: {Environment.Is64BitProcess}");
}
catch { /* 运行时信息获取失败则忽略 */ }
File.AppendAllText(logPath, sbLog.ToString(), Encoding.UTF8);
}
catch
{
// 忽略日志写入错误,避免二次异常
}
// 回退到内置选择器
selectedFile = PickFileFallback();
if (string.IsNullOrEmpty(selectedFile)) return;
}
catch (Exception ex)
{
MessageBox.Show($"打开文件对话框失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
finally
{
// 确保 Dispose,即使构造失败也安全
ofd?.Dispose();
}
try
{
dgvResults.Rows.Clear();
accelerationData.Clear();
dataPointCount = 0;
string ext = Path.GetExtension(selectedFile).ToLowerInvariant();
if (ext == ".csv")
{
var lines = File.ReadAllLines(selectedFile);
// 假定第一行为表头:Time,T1,T2,V1,V2,Acc
foreach (var line in lines.Skip(1))
{
if (string.IsNullOrWhiteSpace(line)) continue;
var cols = line.Split(',');
int row = dgvResults.Rows.Add();
dgvResults.Rows[row].Cells[0].Value = cols.ElementAtOrDefault(0) ?? "";
dgvResults.Rows[row].Cells[1].Value = cols.ElementAtOrDefault(1) ?? "";
dgvResults.Rows[row].Cells[2].Value = cols.ElementAtOrDefault(2) ?? "";
dgvResults.Rows[row].Cells[3].Value = cols.ElementAtOrDefault(3) ?? "";
dgvResults.Rows[row].Cells[4].Value = cols.ElementAtOrDefault(4) ?? "";
dgvResults.Rows[row].Cells[5].Value = cols.ElementAtOrDefault(5) ?? "";
if (double.TryParse(cols.ElementAtOrDefault(5), NumberStyles.Any, CultureInfo.InvariantCulture, out double acc))
accelerationData.Add(acc);
}
}
else if (ext == ".json")
{
var json = File.ReadAllText(selectedFile);
var records = Newtonsoft.Json.JsonConvert.DeserializeObject<List<ExperimentRecord>>(json);
if (records != null)
{
foreach (var r in records)
{
int row = dgvResults.Rows.Add();
dgvResults.Rows[row].Cells[0].Value = r.TimeStamp;
dgvResults.Rows[row].Cells[1].Value = r.T1.ToString("0.000");
dgvResults.Rows[row].Cells[2].Value = r.T2.ToString("0.000");
dgvResults.Rows[row].Cells[3].Value = r.V1.ToString("0.000");
dgvResults.Rows[row].Cells[4].Value = r.V2.ToString("0.000");
dgvResults.Rows[row].Cells[5].Value = r.Acc.ToString("0.000");
accelerationData.Add(r.Acc);
}
}
}
dataPointCount = accelerationData.Count;
// 用批量数据重建图表(避免每点重绘)
chartPlot.Series["加速度"].Points.Clear();
for (int i = 0; i < accelerationData.Count; i++)
chartPlot.Series["加速度"].Points.AddXY(i + 1, accelerationData[i]);
chartPlot.Titles[0].Text = $"加速度变化曲线 (共 {dataPointCount} 个数据点)";
// 调整轴范围等(可复用 UpdatePlot 的逻辑)
}
catch (Exception ex)
{
MessageBox.Show($"加载失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
// 简单回退文件选择器:在无法使用系统 OpenFileDialog 时使用(仅列出 experimentsFolder 下文件)
private string PickFileFallback()
{
try
{
string[] files = Directory.GetFiles(experimentsFolder)
.OrderByDescending(f => File.GetCreationTimeUtc(f))
.ToArray();
if (files.Length == 0)
{
MessageBox.Show("未找到历史实验文件。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return null;
}
using (Form pickForm = new Form())
{
pickForm.Text = "选择要加载的实验文件(回退)";
pickForm.StartPosition = FormStartPosition.CenterParent;
pickForm.ClientSize = new Size(600, 400);
pickForm.FormBorderStyle = FormBorderStyle.FixedDialog;
pickForm.MinimizeBox = false;
pickForm.MaximizeBox = false;
pickForm.ShowInTaskbar = false;
var lb = new ListBox { Dock = DockStyle.Fill };
foreach (var f in files)
lb.Items.Add(Path.GetFileName(f));
var btnPanel = new FlowLayoutPanel { Dock = DockStyle.Bottom, Height = 40, FlowDirection = FlowDirection.RightToLeft, Padding = new Padding(6) };
var btnOk = new Button { Text = "确定", DialogResult = DialogResult.OK, AutoSize = true, Margin = new Padding(6) };
var btnCancel = new Button { Text = "取消", DialogResult = DialogResult.Cancel, AutoSize = true, Margin = new Padding(6) };
btnPanel.Controls.Add(btnOk);
btnPanel.Controls.Add(btnCancel);
pickForm.Controls.Add(lb);
pickForm.Controls.Add(btnPanel);
// 双击快速选择
lb.DoubleClick += (s, e) =>
{
if (lb.SelectedIndex >= 0)
pickForm.DialogResult = DialogResult.OK;
};
if (pickForm.ShowDialog() == DialogResult.OK && lb.SelectedIndex >= 0)
{
return files[lb.SelectedIndex];
}
}
}
catch (Exception ex)
{
MessageBox.Show($"文件选择回退失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
return null;
}
private void btnSaveExperiment_Click(object sender, EventArgs e)
{
if (dgvResults.Rows.Count == 0)
{
MessageBox.Show("没有数据可保存。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
try
{
// 解析附加参数(挡光片宽度、光电门间距)
double plateWidth = 0;
double gateSpacing = 0;
double.TryParse(txtPlateWidth?.Text, NumberStyles.Any, CultureInfo.InvariantCulture, out plateWidth);
double.TryParse(txtGateSpacing?.Text, NumberStyles.Any, CultureInfo.InvariantCulture, out gateSpacing);
// 从 DataGridView 构建记录列表
var records = new List<ExperimentRecord>();
foreach (DataGridViewRow row in dgvResults.Rows)
{
if (row.IsNewRow) continue;
string timeStamp = row.Cells[0].Value?.ToString() ?? "";
double t1 = 0, t2 = 0, v1 = 0, v2 = 0, acc = 0;
double.TryParse(row.Cells[1].Value?.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out t1);
double.TryParse(row.Cells[2].Value?.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out t2);
double.TryParse(row.Cells[3].Value?.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out v1);
double.TryParse(row.Cells[4].Value?.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out v2);
double.TryParse(row.Cells[5].Value?.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out acc);
records.Add(new ExperimentRecord
{
TimeStamp = timeStamp,
T1 = t1,
T2 = t2,
V1 = v1,
V2 = v2,
Acc = acc,
PlateWidth = plateWidth,
GateSpacing = gateSpacing
});
}
// 将当前记录缓存在内存
currentRecords = records;
// 生成文件名(时间戳)
string baseName = $"Experiment_{DateTime.Now:yyyyMMdd_HHmmss}";
string csvPath = Path.Combine(experimentsFolder, baseName + ".csv");
string jsonPath = Path.Combine(experimentsFolder, baseName + ".json");
// 写 CSV(带表头)
var sb = new StringBuilder();
sb.AppendLine("TimeStamp,T1,T2,V1,V2,Acc,PlateWidth,GateSpacing");
foreach (var r in records)
{
string line = string.Join(",",
EscapeCsv(r.TimeStamp),
r.T1.ToString("0.###", CultureInfo.InvariantCulture),
r.T2.ToString("0.###", CultureInfo.InvariantCulture),
r.V1.ToString("0.###", CultureInfo.InvariantCulture),
r.V2.ToString("0.###", CultureInfo.InvariantCulture),
r.Acc.ToString("0.###", CultureInfo.InvariantCulture),
r.PlateWidth.ToString("0.###", CultureInfo.InvariantCulture),
r.GateSpacing.ToString("0.###", CultureInfo.InvariantCulture));
sb.AppendLine(line);
}
File.WriteAllText(csvPath, sb.ToString(), Encoding.UTF8);
// 写 JSON(格式化)
string json = JsonConvert.SerializeObject(records, Formatting.Indented);
File.WriteAllText(jsonPath, json, Encoding.UTF8);
MessageBox.Show($"保存成功:\n{csvPath}\n{jsonPath}", "保存完成", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
MessageBox.Show($"保存失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
// 本地函数:CSV 字段转义(简单处理逗号与引号)
string EscapeCsv(string s)
{
if (string.IsNullOrEmpty(s)) return "";
if (s.Contains(",") || s.Contains("\"") || s.Contains("\n"))
return $"\"{s.Replace("\"", "\"\"")}\"";
return s;
}
}
// 初始化UI
private void InitializeComponent()
{
// 主窗体设置
this.SuspendLayout();
this.AutoScaleDimensions = new SizeF(8F, 15F);
this.AutoScaleMode = AutoScaleMode.Font;
this.ClientSize = new Size(1200, 800);
this.Text = "气垫导轨实验数据监控";
// 创建主布局面板
this.mainLayout = new TableLayoutPanel
{
ColumnCount = 1,
Dock = DockStyle.Fill,
Padding = new Padding(10),
RowCount = 4
};
mainLayout.RowStyles.Add(new RowStyle(SizeType.Absolute, 100F));
mainLayout.RowStyles.Add(new RowStyle(SizeType.Absolute, 70F)); // 增加高度以容纳输入控件
mainLayout.RowStyles.Add(new RowStyle(SizeType.Percent, 40F));
mainLayout.RowStyles.Add(new RowStyle(SizeType.Percent, 60F));
// 串口设置组
this.gbSerial = new GroupBox
{
Text = "串口设置",
Dock = DockStyle.Fill
};
// 串口布局
this.serialLayout = new TableLayoutPanel
{
ColumnCount = 4,
Dock = DockStyle.Fill,
RowCount = 2
};
serialLayout.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 100F));
serialLayout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 30F));
serialLayout.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 100F));
serialLayout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 30F));
serialLayout.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
serialLayout.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
// 串口控件
this.lblPort = new Label
{
Text = "串口号:",
TextAlign = ContentAlignment.MiddleRight,
Dock = DockStyle.Fill
};
this.cmbPorts = new ComboBox
{
DropDownStyle = ComboBoxStyle.DropDownList,
Dock = DockStyle.Fill
};
this.lblBaud = new Label
{
Text = "波特率:",
TextAlign = ContentAlignment.MiddleRight,
Dock = DockStyle.Fill
};
this.cmbBaudRate = new ComboBox
{
DropDownStyle = ComboBoxStyle.DropDownList,
Dock = DockStyle.Fill
};
this.btnConnect = new Button
{
Text = "连接串口",
BackColor = Color.LightGreen,
Font = new Font("Microsoft YaHei UI", 10F, FontStyle.Bold),
Dock = DockStyle.Fill,
Margin = new Padding(5)
};
// 添加控件到串口布局
serialLayout.Controls.Add(lblPort, 0, 0);
serialLayout.Controls.Add(cmbPorts, 1, 0);
serialLayout.Controls.Add(lblBaud, 2, 0);
serialLayout.Controls.Add(cmbBaudRate, 3, 0);
serialLayout.SetColumnSpan(btnConnect, 4);
serialLayout.Controls.Add(btnConnect, 0, 1);
gbSerial.Controls.Add(serialLayout);
mainLayout.Controls.Add(gbSerial, 0, 0);
// 按钮面板(包含输入挡光片宽度、光电门间距、保存历史等)
this.buttonPanel = new FlowLayoutPanel
{
Dock = DockStyle.Fill,
Padding = new Padding(10, 6, 10, 6),
AutoSize = false,
WrapContents = false
};
// 挡光片宽度控件
this.lblPlateWidth = new Label
{
Text = "挡光片宽度:",
TextAlign = ContentAlignment.MiddleCenter,
AutoSize = true,
Margin = new Padding(6, 10, 0, 0)
};
this.txtPlateWidth = new TextBox
{
Width = 100,
Text = "0.010", // 默认 10 mm (示例);用户可自行修改
Margin = new Padding(6)
};
// 光电门间距控件
this.lblGateSpacing = new Label
{
Text = "光电门间距:",
TextAlign = ContentAlignment.MiddleCenter,
AutoSize = true,
Margin = new Padding(6, 10, 0, 0)
};
this.txtGateSpacing = new TextBox
{
Width = 100,
Text = "0.100", // 默认 100 mm (示例)
Margin = new Padding(6)
};
// 保存实验按钮
this.btnSaveExperiment = new Button
{
Text = "保存本次实验",
BackColor = Color.LightGoldenrodYellow,
Size = new Size(140, 36),
Margin = new Padding(6)
};
// 加载历史按钮
this.btnLoadExperiment = new Button
{
Text = "加载历史实验",
BackColor = Color.LightSteelBlue,
Size = new Size(140, 36),
Margin = new Padding(6)
};
// 导出与清除按钮
this.btnExport = new Button
{
Text = "导出数据(Excel)",
BackColor = Color.LightBlue,
Size = new Size(180, 40),
Margin = new Padding(6)
};
this.btnClear = new Button
{
Text = "清除数据",
BackColor = Color.LightSalmon,
Size = new Size(180, 40),
Margin = new Padding(6)
};
// 把控件加入面板
buttonPanel.Controls.Add(lblPlateWidth);
buttonPanel.Controls.Add(txtPlateWidth);
buttonPanel.Controls.Add(lblGateSpacing);
buttonPanel.Controls.Add(txtGateSpacing);
buttonPanel.Controls.Add(btnSaveExperiment);
buttonPanel.Controls.Add(btnLoadExperiment);
buttonPanel.Controls.Add(btnExport);
buttonPanel.Controls.Add(btnClear);
mainLayout.Controls.Add(buttonPanel, 0, 1);
// 数据表格
this.gbData = new GroupBox
{
Text = "实验数据",
Dock = DockStyle.Fill
};
this.dgvResults = new DataGridView
{
AllowUserToAddRows = false,
AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill,
BackgroundColor = SystemColors.Window,
BorderStyle = BorderStyle.None,
ColumnHeadersHeight = 34,
Dock = DockStyle.Fill,
ReadOnly = true,
RowHeadersVisible = false,
ScrollBars = ScrollBars.Vertical
};
// 添加列
dgvResults.Columns.Add("Time", "时间");
dgvResults.Columns.Add("T1", "T1(s)");
dgvResults.Columns.Add("T2", "T2(s)");
dgvResults.Columns.Add("V1", "V1(m/s)");
dgvResults.Columns.Add("V2", "V2(m/s)");
dgvResults.Columns.Add("Acc", "加速度(m/s²)");
gbData.Controls.Add(dgvResults);
mainLayout.Controls.Add(gbData, 0, 2);
// 图表
this.gbChart = new GroupBox
{
Text = "加速度变化曲线",
Dock = DockStyle.Fill
};
this.chartPlot = new Chart
{
Dock = DockStyle.Fill
};
gbChart.Controls.Add(chartPlot);
mainLayout.Controls.Add(gbChart, 0, 3);
// 添加主布局到窗体
this.Controls.Add(mainLayout);
this.ResumeLayout(false);
}
// 初始化图表
private void InitializePlot()
{
chartPlot.ChartAreas.Clear();
chartPlot.Series.Clear();
chartPlot.Titles.Clear();
// 创建图表区域
ChartArea chartArea = new ChartArea("MainArea")
{
AxisX = { Title = "实验序号" },
AxisY = { Title = "加速度 (m/s²)" }
};
chartArea.AxisX.MajorGrid.LineColor = Color.LightGray;
chartArea.AxisY.MajorGrid.LineColor = Color.LightGray;
chartArea.AxisY.MajorGrid.LineDashStyle = ChartDashStyle.Dot;
chartPlot.ChartAreas.Add(chartArea);
// 创建数据系列
Series series = new Series("加速度")
{
ChartType = SeriesChartType.Line,
Color = Color.Blue,
BorderWidth = 2,
MarkerStyle = MarkerStyle.Circle,
MarkerSize = 8,
MarkerColor = Color.DarkBlue
};
chartPlot.Series.Add(series);
// 添加标题
chartPlot.Titles.Add("加速度变化曲线");
chartPlot.Titles[0].Font = new Font("Microsoft YaHei UI", 12, FontStyle.Bold);
chartPlot.Titles[0].ForeColor = Color.Black;
// 设置初始轴范围
chartArea.AxisX.Minimum = 0;
chartArea.AxisX.Maximum = 10;
chartArea.AxisY.Minimum = 0;
chartArea.AxisY.Maximum = 10;
// 添加初始点
series.Points.AddXY(0, 0);
}
// 加载可用串口
private void LoadAvailablePorts()
{
cmbPorts.Items.Clear();
string[] ports = SerialPort.GetPortNames();
cmbPorts.Items.AddRange(ports);
if (ports.Length > 0)
cmbPorts.SelectedIndex = 0;
// 设置波特率选项
cmbBaudRate.Items.AddRange(new object[] { 9600, 19200, 38400, 57600, 115200 });
cmbBaudRate.SelectedIndex = 4; // 默认115200
}
// 连接/断开串口
private void btnConnect_Click(object sender, EventArgs e)
{
if (serialPort == null || !serialPort.IsOpen)
{
try
{
if (cmbPorts.SelectedItem == null)
{
MessageBox.Show("请选择有效的串口", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
serialPort = new SerialPort(cmbPorts.Text, (int)cmbBaudRate.SelectedItem);
serialPort.DataReceived += SerialPort_DataReceived;
serialPort.Open();
btnConnect.Text = "断开连接";
btnConnect.BackColor = Color.LightCoral;
// 重置实验数据
experimentStartTime = DateTime.Now;
dgvResults.Rows.Clear();
accelerationData.Clear();
dataPointCount = 0;
InitializePlot();
}
catch (Exception ex)
{
MessageBox.Show($"串口连接错误: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
else
{
try
{
serialPort.Close();
serialPort.Dispose();
serialPort = null;
btnConnect.Text = "连接串口";
btnConnect.BackColor = Color.LightGreen;
}
catch (Exception ex)
{
MessageBox.Show($"断开连接错误: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
// 串口数据接收处理
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
string data = serialPort.ReadLine();
this.Invoke(new Action(() => ProcessData(data)));
}
catch (Exception ex)
{
this.Invoke(new Action(() =>
MessageBox.Show($"数据接收错误: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error)));
}
}
// 数据处理和显示
private void ProcessData(string rawData)
{
try
{
string[] pairs = rawData.Split(',');
double t1 = 0, t2 = 0, v1 = 0, v2 = 0, acc = 0;
foreach (string pair in pairs)
{
string[] keyValue = pair.Split(':');
if (keyValue.Length != 2) continue;
string key = keyValue[0].Trim();
string value = keyValue[1].Trim();
switch (key)
{
case "T1": t1 = ParseDouble(value); break;
case "T2": t2 = ParseDouble(value); break;
case "V1": v1 = ParseDouble(value); break;
case "V2": v2 = ParseDouble(value); break;
case "A": acc = ParseDouble(value); break;
}
}
// 计算相对时间
TimeSpan elapsed = DateTime.Now - experimentStartTime;
string timeString = $"{elapsed.Minutes:00}:{elapsed.Seconds:00}.{elapsed.Milliseconds:000}";
// 添加新行到DataGridView
int rowIndex = dgvResults.Rows.Add();
dgvResults.Rows[rowIndex].Cells[0].Value = timeString;
dgvResults.Rows[rowIndex].Cells[1].Value = t1.ToString("0.000");
dgvResults.Rows[rowIndex].Cells[2].Value = t2.ToString("0.000");
dgvResults.Rows[rowIndex].Cells[3].Value = v1.ToString("0.000");
dgvResults.Rows[rowIndex].Cells[4].Value = v2.ToString("0.000");
dgvResults.Rows[rowIndex].Cells[5].Value = acc.ToString("0.000");
// 设置加速度单元格颜色
if (acc > 0)
dgvResults.Rows[rowIndex].Cells[5].Style.BackColor = Color.FromArgb(230, 255, 230);
else if (acc < 0)
dgvResults.Rows[rowIndex].Cells[5].Style.BackColor = Color.FromArgb(255, 230, 230);
// 自动滚动到最后一行
if (dgvResults.Rows.Count > 0)
dgvResults.FirstDisplayedScrollingRowIndex = dgvResults.Rows.Count - 1;
// 更新图表
UpdatePlot(acc);
}
catch (Exception ex)
{
MessageBox.Show($"数据处理错误: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
// 解析双精度值
private double ParseDouble(string value)
{
return double.Parse(value.Trim(), CultureInfo.InvariantCulture);
}
// 更新图表数据
private void UpdatePlot(double acc)
{
accelerationData.Add(acc);
dataPointCount++;
// 限制显示点数
if (accelerationData.Count > 100)
accelerationData.RemoveAt(0);
// 更新图表
chartPlot.Series["加速度"].Points.Clear();
for (int i = 0; i < accelerationData.Count; i++)
{
chartPlot.Series["加速度"].Points.AddXY(i + 1, accelerationData[i]);
}
// 更新标题
chartPlot.Titles[0].Text = $"加速度变化曲线 (共 {dataPointCount} 个数据点)";
// 自动调整Y轴范围
if (accelerationData.Count > 0)
{
double min = accelerationData[0];
double max = accelerationData[0];
foreach (double val in accelerationData)
{
if (val < min) min = val;
if (val > max) max = val;
}
double range = max - min;
if (range < 0.1) range = 0.1;
chartPlot.ChartAreas["MainArea"].AxisY.Minimum = min - range * 0.1;
chartPlot.ChartAreas["MainArea"].AxisY.Maximum = max + range * 0.1;
}
// 更新X轴范围
chartPlot.ChartAreas["MainArea"].AxisX.Minimum = 1;
chartPlot.ChartAreas["MainArea"].AxisX.Maximum = accelerationData.Count > 0 ? accelerationData.Count + 1 : 10;
chartPlot.Invalidate();
}
// 导出数据到Excel
private void btnExport_Click(object sender, EventArgs e)
{
if (dgvResults.Rows.Count == 0)
{
MessageBox.Show("没有数据可导出!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
using (var sfd = new System.Windows.Forms.SaveFileDialog())
{
sfd.Filter = "Excel 文件|*.xlsx";
sfd.Title = "保存实验数据";
sfd.FileName = $"气垫导轨实验_{DateTime.Now:yyyyMMdd_HHmmss}.xlsx";
if (sfd.ShowDialog() == DialogResult.OK)
{
try
{
ExperimentExcelExporter.ExportToExcel(
dgvResults,
accelerationData,
sfd.FileName,
chartPlot);
MessageBox.Show("数据导出成功!", "导出完成", MessageBoxButtons.OK, MessageBoxIcon.Information);
// 尝试打开Excel文件
try
{
Process.Start(new ProcessStartInfo
{
FileName = sfd.FileName,
UseShellExecute = true
});
}
catch { /* 忽略打开错误 */ }
}
catch (Exception ex)
{
MessageBox.Show($"导出失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}
// 清除数据
private void btnClear_Click(object sender, EventArgs e)
{
if (MessageBox.Show("确定要清除所有数据吗?", "确认清除",
MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
dgvResults.Rows.Clear();
accelerationData.Clear();
dataPointCount = 0;
experimentStartTime = DateTime.Now;
InitializePlot();
}
}
}
// Excel导出工具类
public static class ExperimentExcelExporter
{
public static void ExportToExcel(
DataGridView dgv,
List<double> accelerationData,
string filePath,
Chart chart = null)
{
using (ExcelPackage excelPackage = new ExcelPackage())
{
// 创建实验信息表
CreateInfoSheet(excelPackage, dgv, accelerationData);
// 创建数据工作表
CreateDataSheet(excelPackage, dgv);
// 创建图表工作表
if (accelerationData.Count > 0)
{
CreateChartSheet(excelPackage, accelerationData);
}
// 添加图表图像
if (chart != null)
{
CreateChartImageSheet(excelPackage, chart);
}
// 保存文件
FileInfo excelFile = new FileInfo(filePath);
excelPackage.SaveAs(excelFile);
}
}
private static void CreateInfoSheet(ExcelPackage excelPackage, DataGridView dgv, List<double> accelerationData)
{
ExcelWorksheet infoSheet = excelPackage.Workbook.Worksheets.Add("实验信息");
// 标题行
infoSheet.Cells[1, 1].Value = "气垫导轨实验报告";
infoSheet.Cells[1, 1, 1, 6].Merge = true;
infoSheet.Cells[1, 1].Style.Font.Bold = true;
infoSheet.Cells[1, 1].Style.Font.Size = 16;
infoSheet.Cells[1, 1].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
// 实验信息
infoSheet.Cells[3, 1].Value = "实验时间";
infoSheet.Cells[3, 2].Value = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
infoSheet.Cells[4, 1].Value = "数据点数";
infoSheet.Cells[4, 2].Value = dgv.Rows.Count;
infoSheet.Cells[5, 1].Value = "平均加速度";
double avgAcc = CalculateAverage(accelerationData);
infoSheet.Cells[5, 2].Value = $"{avgAcc:0.000} m/s²";
infoSheet.Cells[6, 1].Value = "最大加速度";
double maxAcc = CalculateMax(accelerationData);
infoSheet.Cells[6, 2].Value = $"{maxAcc:0.000} m/s²";
// 设置列宽
infoSheet.Column(1).Width = 15;
infoSheet.Column(2).Width = 25;
}
private static void CreateDataSheet(ExcelPackage excelPackage, DataGridView dgv)
{
ExcelWorksheet dataSheet = excelPackage.Workbook.Worksheets.Add("实验数据");
// 添加标题行
for (int i = 0; i < dgv.Columns.Count; i++)
{
dataSheet.Cells[1, i + 1].Value = dgv.Columns[i].HeaderText;
dataSheet.Cells[1, i + 1].Style.Fill.PatternType = ExcelFillStyle.Solid;
dataSheet.Cells[1, i + 1].Style.Fill.BackgroundColor.SetColor(Color.LightBlue);
dataSheet.Cells[1, i + 1].Style.Font.Bold = true;
dataSheet.Cells[1, i + 1].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
}
// 添加数据行
for (int row = 0; row < dgv.Rows.Count; row++)
{
for (int col = 0; col < dgv.Columns.Count; col++)
{
var cellValue = dgv.Rows[row].Cells[col].Value;
ExcelRange cell = dataSheet.Cells[row + 2, col + 1];
if (col == 0) // 时间列
{
cell.Value = cellValue?.ToString();
}
else if (double.TryParse(cellValue?.ToString(), out double numValue))
{
cell.Value = numValue;
cell.Style.Numberformat.Format = "0.000";
// 加速度列特殊样式(假设最后一列是加速度)
if (dgv.Columns[col].Name == "Acc" || dgv.Columns[col].HeaderText.Contains("加速度"))
{
cell.Style.Font.Color.SetColor(
numValue > 0 ? Color.DarkGreen : Color.DarkRed);
}
}
else
{
cell.Value = cellValue?.ToString();
}
}
}
// 自动调整列宽
dataSheet.Cells[dataSheet.Dimension.Address].AutoFitColumns();
// 添加表格边框
using (ExcelRange range = dataSheet.Cells[1, 1, dgv.Rows.Count + 1, dgv.Columns.Count])
{
range.Style.Border.Top.Style = ExcelBorderStyle.Thin;
range.Style.Border.Bottom.Style = ExcelBorderStyle.Thin;
range.Style.Border.Left.Style = ExcelBorderStyle.Thin;
range.Style.Border.Right.Style = ExcelBorderStyle.Thin;
}
}
private static void CreateChartSheet(ExcelPackage excelPackage, List<double> accelerationData)
{
ExcelWorksheet chartSheet = excelPackage.Workbook.Worksheets.Add("加速度曲线");
// 添加标题行
chartSheet.Cells[1, 1].Value = "实验序号";
chartSheet.Cells[1, 2].Value = "加速度 (m/s²)";
// 添加数据
for (int i = 0; i < accelerationData.Count; i++)
{
chartSheet.Cells[i + 2, 1].Value = i + 1;
chartSheet.Cells[i + 2, 2].Value = accelerationData[i];
}
// 创建折线图
var chart = chartSheet.Drawings.AddChart("加速度曲线", eChartType.Line) as ExcelLineChart;
chart.Title.Text = "加速度变化曲线";
chart.SetPosition(1, 0, 3, 0);
chart.SetSize(800, 400);
// 添加数据系列
var series = chart.Series.Add(
chartSheet.Cells[2, 2, accelerationData.Count + 1, 2],
chartSheet.Cells[2, 1, accelerationData.Count + 1, 1]);
series.Header = "加速度";
// 设置图表样式
chart.YAxis.Title.Text = "加速度 (m/s²)";
chart.XAxis.Title.Text = "实验序号";
// 添加趋势线
var trendline = series.TrendLines.Add(eTrendLine.Linear);
trendline.DisplayEquation = true;
}
// 修复3: 正确处理Bitmap到Excel图片的转换
private static void CreateChartImageSheet(ExcelPackage excelPackage, Chart winFormsChart)
{
ExcelWorksheet imageSheet = excelPackage.Workbook.Worksheets.Add("图表图像");
imageSheet.Cells[1, 1].Value = "加速度变化曲线图";
imageSheet.Cells[1, 1].Style.Font.Bold = true;
imageSheet.Cells[1, 1].Style.Font.Size = 14;
// 使用内存流保存图像
using (Bitmap chartImage = new Bitmap(winFormsChart.Width, winFormsChart.Height))
using (MemoryStream ms = new MemoryStream())
{
winFormsChart.DrawToBitmap(chartImage, new Rectangle(0, 0, winFormsChart.Width, winFormsChart.Height));
chartImage.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
ms.Position = 0;
// 添加图像到Excel
var picture = imageSheet.Drawings.AddPicture("加速度图表", ms);
picture.SetPosition(10, 5);
picture.SetSize(800, 400);
}
}
private static double CalculateAverage(List<double> data)
{
if (data.Count == 0) return 0;
double sum = 0;
foreach (double val in data)
sum += val;
return sum / data.Count;
}
private static double CalculateMax(List<double> data)
{
if (data.Count == 0) return 0;
double max = data[0];
foreach (double val in data)
if (Math.Abs(val) > Math.Abs(max))
max = val;
return max;
}
}
// 程序入口
static class Program
{
[STAThread]
static void Main()
{
// 初始化 EPPlus 许可(兼容 EPPlus 8+ 与旧版本)
InitEpplusLicense();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
// 使用反射以同时兼容 EPPlus 8+ 的 ExcelPackage.License 和 旧版的 ExcelPackage.LicenseContext
private static void InitEpplusLicense()
{
try
{
Type excelPackageType = typeof(ExcelPackage);
// 优先尝试 EPPlus 8+:ExcelPackage.License (type OfficeOpenXml.License)
PropertyInfo licenseProp = excelPackageType.GetProperty("License", BindingFlags.Static | BindingFlags.Public);
if (licenseProp != null && licenseProp.CanWrite)
{
Type licenseType = licenseProp.PropertyType;
object licenseInstance = Activator.CreateInstance(licenseType);
PropertyInfo contextProp = licenseType.GetProperty("Context", BindingFlags.Instance | BindingFlags.Public);
if (contextProp != null)
{
Type contextEnumType = contextProp.PropertyType;
object nonCommercial = Enum.Parse(contextEnumType, "NonCommercial");
contextProp.SetValue(licenseInstance, nonCommercial);
}
licenseProp.SetValue(null, licenseInstance);
return;
}
// 回退:旧的静态属性 ExcelPackage.LicenseContext
PropertyInfo oldContextProp = excelPackageType.GetProperty("LicenseContext", BindingFlags.Static | BindingFlags.Public);
if (oldContextProp != null && oldContextProp.CanWrite)
{
Type ctxEnumType = oldContextProp.PropertyType;
object nonCommercialOld = Enum.Parse(ctxEnumType, "NonCommercial");
oldContextProp.SetValue(null, nonCommercialOld);
return;
}
Debug.WriteLine("未找到可写的 EPPlus 许可 API(License 或 LicenseContext)。");
}
catch (Exception ex)
{
Debug.WriteLine("EPPlus 许可初始化失败: " + ex.Message);
}
}
}
}
请你把我修复代码
最新发布