Files
JoyD/Windows/CS/Framework4.0/Toprie/Toprie/Camera.cs

1912 lines
77 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Threading.Tasks;
namespace JoyD.Windows.CS.Toprie
{
public partial class Camera : UserControl
{
// 设备管理器实例
private DeviceManager _deviceManager;
// 是否正在接收图像
private bool _isReceivingImage = false;
// 全局图像缓冲区bitmap用于在其上绘制图像和mask信息
private Bitmap _imageBuffer = null;
private const int BUFFER_WIDTH = 512;
private const int BUFFER_HEIGHT = 384;
// 最后接收的图像
private Image _lastImage = null;
// 信息图像,用于显示额外信息
private Image _infoImage = null;
// 最近一次获取到的温度数据
private TemperatureData _lastTemperatureData = null;
// 用于保护_lastTemperatureData的线程锁
private readonly object _lastTemperatureDataLock = new object();
// 用于保护_lastImage的线程锁
private readonly object _lastImageLock = new object();
// 用于保护_infoImage的线程锁
private readonly object _infoImageLock = new object();
// 是否显示信息图像
private bool _isDisplayingInfo = false;
// 项目路径,用于数据文件的存取
private string _projectPath = "";
/// <summary>
/// 获取或设置项目路径,控件所需的数据文件将在此目录中进行存取
/// </summary>
[Category("配置")]
[Description("设置项目路径,控件所需的数据文件将在此目录中进行存取")]
[DefaultValue("")]
public string ProjectPath
{
get { return _projectPath; }
set
{
// 只有当值发生变化时才进行同步
if (_projectPath != value)
{
_projectPath = value;
// 如果DeviceManager已经初始化则同步更新其ProjectPath属性
if (_deviceManager != null)
{
_deviceManager.ProjectPath = _projectPath;
}
}
}
}
/// <summary>
/// 获取最近一次接收到的温度数据
/// </summary>
public TemperatureData LastTemperatureData
{
get
{
lock (_lastTemperatureDataLock)
{
return _lastTemperatureData;
}
}
}
/// <summary>
/// 设备管理器温度数据接收事件处理
/// </summary>
private void DeviceManager_TemperatureReceived(object sender, DeviceManager.TemperatureReceivedEventArgs e)
{
if (DesignMode) return;
try
{
// 确保事件参数有效
if (e == null || e.TemperatureData == null)
{
Console.WriteLine("接收到无效的温度数据事件参数");
return;
}
// 更新最近一次温度数据
lock (_lastTemperatureDataLock)
{
_lastTemperatureData = e.TemperatureData;
}
Console.WriteLine("温度数据已更新");
// 按照README中要求的修改流程第4点和第6点温度数据更新时只在非暂停状态下调用更新Info
if (!_isPaused)
{
UpdateInfo();
}
}
catch (Exception ex)
{
Console.WriteLine($"处理接收到的温度数据时出错: {ex.Message}");
}
}
// 显示错误的定时器
/// <summary>
/// 更新设计模式状态到DeviceManager
/// </summary>
private void UpdateDesignModeStatus()
{
DeviceManager.IsDesignMode = DesignMode;
Console.WriteLine($"相机控件设计模式状态已更新: {DesignMode}");
}
/// <summary>
/// 更新InfoImage显示
/// 1. 如果暂停,显示暂停信息
/// 2. 否则如果Ping不通或断开显示重连信息
/// 3. 否则清空InfoImage
/// 4. 最后调用更新UI
/// </summary>
private void UpdateInfo()
{
// 更新Ping状态到Info文本
Console.WriteLine($"Ping状态更新: {(IsDevicePingable ? "Ping通" : "Ping通")}");
if (DesignMode) return;
try
{
lock (_infoImageLock)
{
// 检查连接状态
bool isDisconnected = _deviceManager != null && _deviceManager.ConnectionStatus == ConnectionStatus.Disconnected;
bool isReconnecting = _deviceManager != null && _deviceManager.ConnectionStatus == ConnectionStatus.Reconnecting;
bool isPaused = _isPaused; // 使用_isPaused标志判断暂停状态
bool isPingFailed = !IsDevicePingable;
// 根据用户要求的优先级显示信息先检查暂停状态然后再检查Ping状态和连接状态
using (Graphics g = Graphics.FromImage(_infoImage))
{
// 设置半透明背景
g.Clear(Color.FromArgb(128, Color.Transparent));
if (isPaused)
{
// 暂停状态 - 最高优先级
// 绘制暂停文本
string text = "暂停";
Color textColor = Color.Red;
using (Font font = new Font("Arial", 48, FontStyle.Bold))
using (SolidBrush textBrush = new SolidBrush(textColor))
{
StringFormat format = new StringFormat() { Alignment = StringAlignment.Center };
// 将主文本居中显示
g.DrawString(text, font, textBrush,
new RectangleF(0, BUFFER_HEIGHT / 3, BUFFER_WIDTH, BUFFER_HEIGHT / 3),
format);
}
}
else if (isPingFailed || isDisconnected || isReconnecting)
{
// 非暂停状态下检查Ping状态和连接状态
// 确定显示的文本和颜色
string text = "";
Color textColor = Color.White;
if (isReconnecting)
{
text = "正在重连...";
textColor = Color.Yellow;
}
else if (isDisconnected)
{
text = "连接断开";
textColor = Color.Red;
}
else if (isPingFailed)
{
text = "网络断开";
textColor = Color.Red;
}
// 绘制文本
using (Font font = new Font("Arial", 48, FontStyle.Bold))
using (SolidBrush textBrush = new SolidBrush(textColor))
{
StringFormat format = new StringFormat() { Alignment = StringAlignment.Center };
// 将主文本居中显示
g.DrawString(text, font, textBrush,
new RectangleF(0, BUFFER_HEIGHT / 3, BUFFER_WIDTH, BUFFER_HEIGHT / 3),
format);
}
}
else g.Clear(Color.Transparent);
}
// 否则清空InfoImage已在开头处理
// 设置显示标志
_isDisplayingInfo = (isPaused || isDisconnected || isReconnecting);
}
// 调用更新UI
UpdateImageOnUI();
}
catch (Exception ex)
{
Console.WriteLine($"更新Info显示时出错: {ex.Message}");
}
}
/// <summary>
/// 暂停/恢复检测菜单项点击事件处理
/// 1、暂停或恢复时设置暂停状态调用更新Info
/// </summary>
private void PauseDetectionToolStripMenuItem_Click(object sender, EventArgs e)
{
if (DesignMode) return;
try
{
// 切换暂停状态
_isPaused = !_isPaused;
if (_isPaused)
{
// 设置暂停状态
pauseDetectionToolStripMenuItem.Text = "恢复检测";
// 暂停时停止图像接收并更新DeviceManager的暂停检测状态
if (_deviceManager != null)
{
_deviceManager.IsDetectionPaused = true;
if (_isReceivingImage)
{
_deviceManager.StopImageReceiving();
_deviceManager.StopTemperatureDataReceiving();
_isReceivingImage = false;
}
}
Console.WriteLine("检测已暂停");
}
else
{
// 设置恢复状态
pauseDetectionToolStripMenuItem.Text = "暂停检测";
// 恢复时更新DeviceManager的暂停检测状态并重新开始图像接收
if (_deviceManager != null)
{
_deviceManager.IsDetectionPaused = false;
if (_deviceManager.ConnectionStatus == ConnectionStatus.Connected)
{
_deviceManager.StopImageReceiving();
_deviceManager.StopTemperatureDataReceiving();
_deviceManager.StartImageReceiving();
_deviceManager.StartTemperatureDataReceiving();
_isReceivingImage = true;
// 恢复检测后,启动连接检查以确保连接正常
_deviceManager.StartConnectionCheck();
}
// 如果当前是断开状态但启用了自动重连,尝试启动重连
else if (_deviceManager.AutoReconnectEnabled)
{
_deviceManager.StartAutoReconnect();
}
}
Console.WriteLine("检测已恢复");
}
// 按照用户要求暂停或恢复时设置暂停状态调用更新Info
UpdateInfo();
}
catch (Exception ex)
{
Console.WriteLine($"处理暂停/恢复检测时出错: {ex.Message}");
}
}
public Camera()
{
InitializeComponent();
// 为右键菜单添加Opening事件用于在菜单显示前更新色彩模式的选中状态
this.contextMenuStrip1.Opening += ContextMenuStrip1_Opening;
// 将设计模式状态传递给DeviceManager
UpdateDesignModeStatus();
// 初始化图像缓冲区
InitializeImageBuffer();
// 初始化Ping定时器
_pingTimer = new System.Threading.Timer(PingTimer_Tick, null, Timeout.Infinite, Timeout.Infinite);
// 只有在非设计模式下才初始化设备管理器和错误定时器
if (!DesignMode)
{
// 清空现有日志
try
{
string logFile = Path.Combine(Application.StartupPath, "log.txt");
// 确保日志文件目录存在
string logDir = Path.GetDirectoryName(logFile);
if (!Directory.Exists(logDir))
{
Directory.CreateDirectory(logDir);
}
if (File.Exists(logFile))
{
File.WriteAllText(logFile, string.Empty);
}
}
catch { }
InitializeDeviceManager();
}
}
/// <summary>
/// Camera控件加载事件
/// 在控件加载时自动启动相机显示热图
/// </summary>
private void Camera_Load(object sender, EventArgs e)
{
// 只有在非设计模式下才启动相机
if (!DesignMode)
{
try
{
StartCamera();
}
catch (Exception ex)
{
ShowError($"自动启动相机失败: {ex.Message}");
}
}
}
// 注意UserControl不支持FormClosing事件资源清理已在Dispose方法中处理
/// <summary>
/// 初始化设备管理器
/// </summary>
private void InitializeDeviceManager()
{
// 只有在非设计模式下才初始化设备管理器
if (!DesignMode)
{
_deviceManager = new DeviceManager
{
AutoReconnectEnabled = true,
ReconnectInterval = 2000, // 2秒
ProjectPath = !string.IsNullOrEmpty(ProjectPath) ? ProjectPath : Application.StartupPath
};
// 设置静态属性
DeviceManager.MaxReconnectAttempts = 5;
// 注册图像接收事件
_deviceManager.ImageReceived += DeviceManager_ImageReceived;
// 注册连接状态变更事件
_deviceManager.ConnectionStatusChanged += DeviceManager_ConnectionStatusChanged;
// 注册连接异常事件
_deviceManager.ConnectionException += DeviceManager_ConnectionException;
// 注册温度数据接收事件
_deviceManager.TemperatureReceived += DeviceManager_TemperatureReceived;
}
}
/// <summary>
/// 初始化图像缓冲区和相关图像资源
/// </summary>
private void InitializeImageBuffer()
{
try
{
// 创建512*384大小的透明bitmap作为图像缓冲区
_imageBuffer = new Bitmap(BUFFER_WIDTH, BUFFER_HEIGHT);
Console.WriteLine($"图像缓冲区已初始化: {BUFFER_WIDTH}x{BUFFER_HEIGHT}");
// 初始化缓冲区为黑色背景
using (Graphics g = Graphics.FromImage(_imageBuffer))
{
g.Clear(Color.Black);
}
// 初始化InfoImage为透明bitmap
lock (_infoImageLock)
{
_infoImage = new Bitmap(BUFFER_WIDTH, BUFFER_HEIGHT);
using (Graphics g = Graphics.FromImage(_infoImage))
{
g.Clear(Color.Transparent);
}
Console.WriteLine("InfoImage已初始化为透明bitmap");
}
// 初始化图像框的bitmap为透明
if (imageBox != null && !imageBox.IsDisposed)
{
imageBox.Image = new Bitmap(BUFFER_WIDTH, BUFFER_HEIGHT);
using (Graphics g = Graphics.FromImage(imageBox.Image))
{
g.Clear(Color.Transparent);
}
Console.WriteLine("图像框bitmap已初始化为透明");
}
}
catch (Exception ex)
{
Console.WriteLine($"初始化图像资源失败: {ex.Message}");
// 发生异常时释放已创建的资源
if (_imageBuffer != null)
{
_imageBuffer.Dispose();
_imageBuffer = null;
}
lock (_infoImageLock)
{
if (_infoImage != null)
{
_infoImage.Dispose();
_infoImage = null;
}
}
}
}
/// <summary>
/// 启动相机
/// </summary>
public void StartCamera()
{
// 启动设备Ping
StartDevicePing();
if (DesignMode) return;
try
{
// 只有在没有接收图像时才启动
if (!_isReceivingImage)
{
// 清理错误显示
ShowError(string.Empty);
// 使用异步方式连接设备和设置模式避免UI线程阻塞
ThreadPool.QueueUserWorkItem(delegate
{
try
{
// 设置为热图模式
bool modeSet = false;
// 启用自动重连
_deviceManager.AutoReconnectEnabled = true;
// 尝试连接设备
if (_deviceManager.ConnectDevice())
{
Console.WriteLine("设备连接成功");
// 如果热图模式未成功设置,在连接成功后再次尝试
if (!modeSet)
{
try
{
_deviceManager.SetImageMode(ImageMode.Infrared);
Console.WriteLine("连接后已设置热图模式");
}
catch (Exception ex)
{
Console.WriteLine($"连接后设置热图模式失败: {ex.Message}");
}
}
// 在连接成功后开始接收图像
this.Invoke(new Action(() =>
{
// 再次检查是否已在UI线程设置为接收状态
if (!_isReceivingImage)
{
StartReceiveImage();
}
}));
}
else
{
// 连接失败时显示错误
string errorMsg = "设备连接失败,请检查设备状态或网络连接";
this.Invoke(new Action(() => ShowError(errorMsg)));
Console.WriteLine(errorMsg);
}
}
catch (Exception ex)
{
// 捕获所有异常,防止后台线程崩溃
string errorMsg = $"启动相机异常: {ex.Message}";
this.Invoke(new Action(() => ShowError(errorMsg)));
Console.WriteLine(errorMsg);
Console.WriteLine(ex.StackTrace);
}
});
Console.WriteLine("相机启动流程已开始");
}
else
{
Console.WriteLine("相机已在运行状态");
}
}
catch (Exception ex)
{
ShowError($"启动相机失败: {ex.Message}");
Console.WriteLine($"启动相机主流程错误: {ex.Message}");
Console.WriteLine(ex.StackTrace);
}
}
/// <summary>
/// 开始接收图像使用HTTP方式
/// </summary>
private void StartReceiveImage()
{
if (DesignMode) return;
try
{
if (!_isReceivingImage && _deviceManager.ConnectionStatus == ConnectionStatus.Connected)
{
Console.WriteLine("Camera开始使用HTTP方式接收图像");
// 直接调用HTTP方式的图像接收
_deviceManager.StartImageReceiving();
// 在调用StartTemperatureDataReceiving前先调用StopTemperatureDataReceiving保持与ResumeDetection方法一致的模式
// 并添加时间间隔检查,避免短时间内频繁调用
lock (_temperatureCallLock)
{
TimeSpan elapsed = DateTime.Now - _lastTemperatureStartCall;
if (elapsed.TotalMilliseconds < 500) // 至少间隔500毫秒
{
Console.WriteLine("避免短时间内重复启动温度数据接收,跳过本次调用");
}
else
{
_deviceManager.StopTemperatureDataReceiving();
_deviceManager.StartTemperatureDataReceiving();
_lastTemperatureStartCall = DateTime.Now;
}
}
_isReceivingImage = true;
}
}
catch (Exception ex)
{
ShowError($"开始接收图像失败: {ex.Message}");
}
}
/// <summary>
/// 停止接收图像
/// </summary>
public void StopCamera()
{
// 停止设备Ping
StopDevicePing();
if (DesignMode) return;
try
{
if (_isReceivingImage)
{
_deviceManager.StopReceiveImage();
_isReceivingImage = false;
}
}
catch (Exception ex)
{
Console.WriteLine($"停止相机失败: {ex.Message}");
}
}
private bool _isPaused = false; // 暂停状态标志
// 记录上次启动温度数据接收的时间,用于防止短时间内重复调用
private DateTime _lastTemperatureStartCall = DateTime.MinValue;
private readonly object _temperatureCallLock = new object();
// Ping相关字段
private System.Threading.Timer _pingTimer;
private bool _isDevicePingable = false;
private const int _pingInterval = 500; // 0.5秒Ping一次
/// <summary>
/// 获取设备是否可Ping通
/// </summary>
public bool IsDevicePingable
{
get { return _isDevicePingable; }
private set
{
if (_isDevicePingable != value)
{
_isDevicePingable = value;
Console.WriteLine($"设备Ping状态变更: {(_isDevicePingable ? "Ping通" : "Ping通")}");
// 状态变化时调用更新Info
UpdateInfo();
}
}
}
/// <summary>
/// 设备管理器图像接收事件处理
/// </summary>
private void DeviceManager_ImageReceived(object sender, ImageReceivedEventArgs e)
{
if (DesignMode) return;
try
{
if (e.ImageData != null && e.ImageData.Length > 0)
{
// 创建内存流并从流中创建图像
using (MemoryStream ms = new MemoryStream(e.ImageData))
{
// 检查流是否可读且有效
if (ms.CanRead && ms.Length > 0)
{
// 从流中创建图像
using (Image newImage = System.Drawing.Image.FromStream(ms))
{
// 立即验证新创建的图像是否有效
try
{
// 访问Width和Height属性来验证图像是否有效
int width = newImage.Width;
int height = newImage.Height;
if (width <= 0 || height <= 0)
{
Console.WriteLine("创建的图像尺寸无效");
return;
}
if (_lastImage == null) _lastImage = new Bitmap(newImage);
else
{
using(Graphics g= Graphics.FromImage(_lastImage))
{
g.DrawImage(newImage,Point.Empty);
}
}
}
catch (Exception)
{
Console.WriteLine("创建的图像无效");
return;
}
// 按照README中要求的修改流程第5点和第6点图像更新时保存LastImage只在非暂停状态下调用更新到UI
this.BeginInvoke(new Action(() =>
{
try
{
if (!_isPaused)
{
UpdateImageOnUI();
}
}
catch (Exception ex)
{
Console.WriteLine($"更新UI图像失败: {ex.Message}");
}
}));
}
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"处理接收到的图像时出错: {ex.Message}");
}
}
/// <summary>
/// 在UI线程上更新图像 - 新实现,按照用户要求:
/// 1. 先将LastImage绘制到全局缓冲
/// 2. 再将InfoImage绘制到缓冲
/// 3. 最后一次性绘制到图像框的bitmap
/// </summary>
private void UpdateImageOnUI()
{
if (DesignMode) return;
// 线程安全检查 - 确保在UI线程上执行
if (this.InvokeRequired)
{
try
{
this.BeginInvoke(new Action(UpdateImageOnUI));
}
catch (ObjectDisposedException)
{
Console.WriteLine("控件已释放跳过UI更新");
}
return;
}
// 一次性控件有效性检查,避免重复检查
if (this.IsDisposed || imageBox == null || imageBox.IsDisposed)
{
Console.WriteLine("控件已释放,无法更新图像");
return;
}
Image lastImage = null;
Image infoImage = null;
Image oldImage = null;
Bitmap displayImage = null;
try
{
// 检查图像缓冲区是否有效
if (_imageBuffer == null)
{
Console.WriteLine("图像缓冲区未初始化,尝试重新初始化");
InitializeImageBuffer();
if (_imageBuffer == null)
{
Console.WriteLine("重新初始化图像缓冲区失败");
return;
}
}
// 保存旧图像引用,以便在设置新图像后释放
oldImage = imageBox.Image;
// 获取当前的LastImage引用
lock (_lastImageLock)
{
if (_lastImage != null)
{
lastImage = (Image)_lastImage.Clone();
}
}
// 获取当前的InfoImage引用
lock (_infoImageLock)
{
if (_infoImage != null)
{
infoImage = (Image)_infoImage.Clone();
}
}
// 合并锁定,减少锁的数量,提高性能
lock (_imageBuffer)
{
using (Graphics g = Graphics.FromImage(_imageBuffer))
{
// 清除缓冲区背景为黑色
g.Clear(Color.Black);
// 步骤1先将LastImage绘制到全局缓冲
if (lastImage != null)
{
// 保持原始图像比例,居中显示
float scale = Math.Min((float)BUFFER_WIDTH / lastImage.Width, (float)BUFFER_HEIGHT / lastImage.Height);
int scaledWidth = (int)(lastImage.Width * scale);
int scaledHeight = (int)(lastImage.Height * scale);
int x = (BUFFER_WIDTH - scaledWidth) / 2;
int y = (BUFFER_HEIGHT - scaledHeight) / 2;
g.DrawImage(lastImage, x, y, scaledWidth, scaledHeight);
}
// 步骤2再将InfoImage绘制到缓冲
if (infoImage != null)
{
g.DrawImage(infoImage, 0, 0);
}
else if (_isDisplayingInfo)
{
// 如果没有InfoImage但需要显示信息则绘制默认信息
using (Font font = new Font("微软雅黑", 12, FontStyle.Bold))
using (Brush textBrush = new SolidBrush(Color.Red))
{
g.DrawString("测试信息", font, textBrush, 10, 10);
}
}
}
// 在同一个锁内创建缓冲区的副本,避免重复锁定
displayImage = (Bitmap)_imageBuffer.Clone();
}
// 将全局缓冲一次性绘制到图像框的bitmap
imageBox.Image = displayImage;
if (lastImage != null)
{
Console.WriteLine($"图像更新成功: {lastImage.Width}x{lastImage.Height}");
}
}
catch (ArgumentException ex) when (ex.Message.Contains("参数无效"))
{
// 特别处理"参数无效"异常
Console.WriteLine($"图像参数无效异常: {ex.Message}");
// 尝试设置旧图像回来,不需要再次检查控件状态(已在方法开始处检查)
if (oldImage != null)
{
try
{
imageBox.Image = oldImage;
}
catch
{
// 如果设置旧图像也失败,释放它
DisposeImage(oldImage);
}
}
}
catch (Exception ex)
{
Console.WriteLine($"更新图像UI异常: {ex.Message}");
}
finally
{
// 确保在任何情况下都释放资源
DisposeImage(lastImage);
DisposeImage(infoImage);
// 只有当旧图像不再被使用时才释放
if (oldImage != null && oldImage != imageBox.Image)
{
DisposeImage(oldImage);
}
}
}
/// <summary>
/// 安全释放图像资源的辅助方法
/// </summary>
private void DisposeImage(Image image)
{
if (image != null)
{
try { image.Dispose(); } catch { }
}
}
/// <summary>
/// 设备管理器连接状态变更事件处理
/// </summary>
private void DeviceManager_ConnectionStatusChanged(object sender, ConnectionStatusChangedEventArgs e)
{
if (DesignMode) return;
// 参数验证
if (e == null)
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 警告: 接收到空的连接状态变更事件参数");
return;
}
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] 接收连接状态变更事件: {e.Status}");
// 捕获所有可能的异常,确保事件处理器不会崩溃
try
{
// 首先检查控件状态
if (this.IsDisposed || this.Disposing)
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 控件已释放或正在释放跳过UI更新");
return;
}
// 检查事件参数中的状态信息
string statusMessage = $"连接状态变更为: {e.Status}";
if (!string.IsNullOrEmpty(e.DeviceInfo))
{
statusMessage += $" (设备信息: {e.DeviceInfo})";
}
// 线程安全处理 - 确保在UI线程上更新
if (this.InvokeRequired)
{
try
{
// 使用BeginInvoke代替Invoke避免可能的死锁问题
this.BeginInvoke(new Action<ConnectionStatusChangedEventArgs>(args =>
{
// 再次检查控件状态,防止在异步调用期间控件被释放
if (!this.IsDisposed && !this.Disposing)
{
HandleConnectionStatusChanged(args);
}
else
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 异步调用期间控件已释放,跳过处理");
}
}), e);
}
catch (ObjectDisposedException ode)
{
// 捕获控件已释放异常,避免程序崩溃
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 控件已释放无法进行UI线程调用: {ode.Message}");
}
catch (InvalidOperationException ioe)
{
// 捕获无效操作异常,通常发生在控件状态异常时
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] UI线程调用无效: {ioe.Message}");
}
}
else
{
// 直接在UI线程上处理
HandleConnectionStatusChanged(e);
}
}
catch (Exception ex)
{
// 捕获所有其他异常,记录并继续
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 处理连接状态变更事件异常: {ex.Message}\n{ex.StackTrace}");
}
}
/// <summary>
/// 处理连接状态变更
/// 2、断开或连接时设置连接状态调用更新Info
/// </summary>
private void HandleConnectionStatusChanged(ConnectionStatusChangedEventArgs e)
{
if (DesignMode) return;
try
{
// 确保在UI线程上更新UI状态
if (this.InvokeRequired)
{
this.Invoke(new Action<ConnectionStatusChangedEventArgs>(HandleConnectionStatusChanged), e);
return;
}
// 更新UI状态
UpdateUIState(e.Status == ConnectionStatus.Connected);
// 检查_deviceManager是否为空
if (_deviceManager == null)
{
Console.WriteLine("设备管理器未初始化");
return;
}
switch (e.Status)
{
case ConnectionStatus.Connected:
Console.WriteLine("设备已连接");
// 仅在首次连接时设置为热图模式,重连时保留之前的模式
if (!_isReceivingImage) // 首次连接时_isReceivingImage为false
{
try
{
_deviceManager.SetImageMode(ImageMode.Infrared);
Console.WriteLine("首次连接,设置热图模式");
}
catch (Exception ex)
{
Console.WriteLine($"设置热图模式失败: {ex.Message}");
}
}
else
{
Console.WriteLine("重连成功,保留当前图像模式");
}
// 注意色彩模式同步现在在DeviceManager内部的连接成功处理中自动完成
// 无需在此处重复调用
// 开始接收图像包含在try-catch中
if (!_isReceivingImage)
{
try
{
StartReceiveImage();
}
catch (Exception ex)
{
Console.WriteLine($"开始接收图像失败: {ex.Message}");
}
}
// 按照README中要求的修改流程第2点和第6点连接状态变化时只在非暂停状态下调用更新Info
if (!_isPaused)
{
UpdateInfo();
}
break;
case ConnectionStatus.Disconnected:
Console.WriteLine("设备已断开连接");
// 停止接收图像(添加空检查和异常处理)
if (_isReceivingImage)
{
try
{
_deviceManager.StopReceiveImage();
_isReceivingImage = false;
}
catch (Exception ex)
{
Console.WriteLine($"停止接收图像失败: {ex.Message}");
_isReceivingImage = false; // 确保状态更新
}
}
// 当TCP连接断开时将最近一次温度数据实例设为null
lock (_lastTemperatureDataLock)
{
_lastTemperatureData = null;
Console.WriteLine("温度数据实例已清空");
}
// 按照README中要求的修改流程第2点和第6点连接状态变化时只在非暂停状态下调用更新Info
if (!_isPaused)
{
UpdateInfo();
}
if (!string.IsNullOrEmpty(e.DeviceInfo))
{
ShowError(e.DeviceInfo);
}
else
{
ShowError("设备连接已断开");
}
break;
case ConnectionStatus.Connecting:
Console.WriteLine($"正在连接设备...{(!string.IsNullOrEmpty(e.DeviceInfo) ? " " + e.DeviceInfo : "")}");
ShowError(string.Empty); // 清除之前的错误信息
break;
case ConnectionStatus.Reconnecting:
Console.WriteLine($"正在重新连接设备...{(!string.IsNullOrEmpty(e.DeviceInfo) ? " " + e.DeviceInfo : "")}");
ShowError(string.Empty); // 清除之前的错误信息
// 按照README中要求的修改流程第2点和第6点连接状态变化时只在非暂停状态下调用更新Info
if (!_isPaused)
{
UpdateInfo();
}
break;
}
}
catch (Exception ex)
{
Console.WriteLine($"处理连接状态变更时发生错误: {ex.Message}");
// 避免在异常处理中再次引起异常
try
{
ShowError($"连接状态处理错误: {ex.Message}");
}
catch
{}
}
}
/// <summary>
/// 更新UI状态
/// </summary>
/// <param name="isConnected">是否已连接</param>
private void UpdateUIState(bool isConnected)
{
if (DesignMode) return;
try
{
// 根据连接状态更新图像框状态
if (imageBox != null && !imageBox.IsDisposed)
{
if (isConnected)
{
imageBox.BorderStyle = BorderStyle.FixedSingle;
}
else
{
imageBox.BorderStyle = BorderStyle.Fixed3D;
}
}
// 更新图像框的边框颜色,提供视觉反馈
if (imageBox != null)
{
imageBox.BorderStyle = isConnected ? BorderStyle.FixedSingle : BorderStyle.Fixed3D;
// 可选:设置不同的边框颜色以提供更好的视觉反馈
if (isConnected)
{
imageBox.BackColor = Color.LightGreen;
}
else
{
imageBox.BackColor = Color.LightGray;
}
}
Console.WriteLine($"UI状态已更新为: {(isConnected ? "" : "")}");
}
catch (Exception ex)
{
Console.WriteLine($"更新UI状态时出错: {ex.Message}");
}
}
/// <summary>
/// 设备管理器连接异常事件处理
/// </summary>
private void DeviceManager_ConnectionException(object sender, ConnectionExceptionEventArgs e)
{
if (DesignMode) return;
// 参数验证
if (e == null)
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 警告: 接收到空的连接异常事件参数");
return;
}
// 记录详细的异常信息但避免在UI线程上执行耗时操作
string exceptionMessage = e.Exception != null ? e.Exception.Message : "无详细异常信息";
string stackTrace = e.Exception != null ? e.Exception.StackTrace : "无堆栈信息";
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] 接收连接异常事件: {e.Message}\n{exceptionMessage}\n{stackTrace}");
// 捕获所有可能的异常,确保异常处理不会导致程序崩溃
try
{
// 首先检查控件状态
if (this.IsDisposed || this.Disposing)
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 控件已释放或正在释放,跳过异常显示");
return;
}
// 创建用于UI显示的错误消息
string uiErrorMessage = $"连接异常: {e.Message}";
if (string.IsNullOrEmpty(e.Message) && e.Exception != null)
{
uiErrorMessage = $"连接异常: {exceptionMessage}";
}
// 线程安全处理 - 确保在UI线程上更新
if (this.InvokeRequired)
{
try
{
// 创建局部变量保存错误消息,避免闭包问题
string errorMsg = uiErrorMessage;
// 使用BeginInvoke代替Invoke避免可能的死锁问题
this.BeginInvoke(new Action(() =>
{
// 再次检查控件状态,防止在异步调用期间控件被释放
if (!this.IsDisposed && !this.Disposing)
{
try
{
ShowError(errorMsg);
}
catch (Exception showEx)
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 显示错误消息异常: {showEx.Message}");
}
}
else
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 异步调用期间控件已释放,跳过错误显示");
}
}));
}
catch (ObjectDisposedException ode)
{
// 捕获控件已释放异常,避免程序崩溃
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 控件已释放无法进行UI线程调用: {ode.Message}");
}
catch (InvalidOperationException ioe)
{
// 捕获无效操作异常,通常发生在控件状态异常时
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] UI线程调用无效: {ioe.Message}");
}
}
else
{
// 直接在UI线程上处理
try
{
ShowError(uiErrorMessage);
}
catch (Exception showEx)
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 显示错误消息异常: {showEx.Message}");
}
}
}
catch (Exception ex)
{
// 捕获所有其他异常,记录并继续
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 处理连接异常事件异常: {ex.Message}\n{ex.StackTrace}");
}
}
/// <summary>
/// 显示错误信息
/// </summary>
private void ShowError(string message)
{
if (DesignMode) return;
Console.WriteLine(message);
// 错误消息仅写入日志即可不需要在UI上显示
}
/// <summary>
/// 右键菜单显示前的事件处理方法
/// 用于更新色彩模式菜单项的选中状态
/// </summary>
/// <summary>
/// 右键菜单打开事件处理
/// </summary>
private void ContextMenuStrip1_Opening(object sender, System.ComponentModel.CancelEventArgs e)
{
// 暂停菜单项的文本已经在点击事件中更新,这里无需再次更新
if (DesignMode) return;
try
{
// 检查是否处于暂停状态
bool isPaused = pauseDetectionToolStripMenuItem.Text == "恢复检测";
// 检查设备是否已连接
bool isConnected = _deviceManager != null && _deviceManager.ConnectionStatus == ConnectionStatus.Connected;
// 在暂停状态或未连接状态下,隐藏图像模式根菜单和色彩模式菜单
// 注意:根菜单隐藏后,其所有子菜单会自动隐藏,不需要单独设置
var currentImageMode = _deviceManager.CurrentImageMode;
bool isInfraredMode = currentImageMode == ImageMode.Infrared;
colorModeToolStripMenuItem.Visible = isInfraredMode;
saveTemperatureToolStripMenuItem.Visible = isInfraredMode;
if (isPaused || !isConnected)
{
// 隐藏图像模式根菜单
if (imageModeToolStripMenuItem != null)
imageModeToolStripMenuItem.Visible = false;
// 隐藏色彩模式菜单
colorModeToolStripMenuItem.Visible = false;
saveTemperatureToolStripMenuItem.Visible = false;
// 当只有一个菜单项可见时,隐藏分隔符
toolStripSeparator1.Visible = false;
}
else
{
// 在非暂停状态且已连接状态下,显示图像模式根菜单
// 注意:根菜单显示后,其所有子菜单会自动显示,不需要单独设置
if (imageModeToolStripMenuItem != null)
imageModeToolStripMenuItem.Visible = true;
// 在非暂停状态且已连接状态下,显示分隔符
toolStripSeparator1.Visible = true;
// 根据当前图像模式控制色彩模式菜单的可见性
// 只有在红外模式下才显示色彩模式菜单和保存温度菜单
// 清除视频模式菜单项的选中状态
thermalModeToolStripMenuItem.Checked = false;
visibleModeToolStripMenuItem.Checked = false;
fusionMode1ToolStripMenuItem.Checked = false;
fusionMode2ToolStripMenuItem.Checked = false;
fusionMode3ToolStripMenuItem.Checked = false;
fusionMode4ToolStripMenuItem.Checked = false;
fusionMode5ToolStripMenuItem.Checked = false;
// 清除色彩模式菜单项的选中状态
whiteHotToolStripMenuItem.Checked = false;
blackHotToolStripMenuItem.Checked = false;
ironRedToolStripMenuItem.Checked = false;
lavaToolStripMenuItem.Checked = false;
rainbowToolStripMenuItem.Checked = false;
ironGrayToolStripMenuItem.Checked = false;
redHotToolStripMenuItem.Checked = false;
rainbow2ToolStripMenuItem.Checked = false;
// 尝试获取当前色彩模式并更新对应菜单项的选中状态
if (_deviceManager != null && _deviceManager.ConnectionStatus == ConnectionStatus.Connected)
{
try
{
// 获取当前色彩模式
PaletteType currentPalette = _deviceManager.CurrentPaletteType;
// 根据当前色彩模式设置对应菜单项的选中状态
switch (currentPalette)
{
case PaletteType.WhiteHot:
whiteHotToolStripMenuItem.Checked = true;
break;
case PaletteType.BlackHot:
blackHotToolStripMenuItem.Checked = true;
break;
case PaletteType.IronRed:
ironRedToolStripMenuItem.Checked = true;
break;
case PaletteType.Lava:
lavaToolStripMenuItem.Checked = true;
break;
case PaletteType.Rainbow:
rainbowToolStripMenuItem.Checked = true;
break;
case PaletteType.IronGray:
ironGrayToolStripMenuItem.Checked = true;
break;
case PaletteType.RedHot:
redHotToolStripMenuItem.Checked = true;
break;
case PaletteType.Rainbow2:
rainbow2ToolStripMenuItem.Checked = true;
break;
}
}
catch (Exception ex)
{
Console.WriteLine($"获取当前色彩模式失败: {ex.Message}");
}
// 更新视频模式菜单项的选中状态
try
{
// 更改为使用ImageMode枚举
thermalModeToolStripMenuItem.Checked = currentImageMode == ImageMode.Infrared;
visibleModeToolStripMenuItem.Checked = currentImageMode == ImageMode.Natural;
}
catch (Exception ex)
{
Console.WriteLine($"获取当前图像模式失败: {ex.Message}");
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"更新右键菜单选中状态失败: {ex.Message}");
}
}
#region
/// <summary>
/// 白热色彩模式
/// </summary>
private void WhiteHotToolStripMenuItem_Click(object sender, EventArgs e)
{
try
{
if (_deviceManager != null)
{
Console.WriteLine("切换到白热色彩模式");
_deviceManager.SetPaletteType(PaletteType.WhiteHot);
}
}
catch (Exception ex)
{
Console.WriteLine($"切换到白热色彩模式失败: {ex.Message}");
}
}
/// <summary>
/// 黑热色彩模式
/// </summary>
private void BlackHotToolStripMenuItem_Click(object sender, EventArgs e)
{
try
{
if (_deviceManager != null)
{
Console.WriteLine("切换到黑热色彩模式");
_deviceManager.SetPaletteType(PaletteType.BlackHot);
}
}
catch (Exception ex)
{
Console.WriteLine($"切换到黑热色彩模式失败: {ex.Message}");
}
}
/// <summary>
/// 铁红色彩模式
/// </summary>
private void IronRedToolStripMenuItem_Click(object sender, EventArgs e)
{
try
{
if (_deviceManager != null)
{
Console.WriteLine("切换到铁红色彩模式");
_deviceManager.SetPaletteType(PaletteType.IronRed);
}
}
catch (Exception ex)
{
Console.WriteLine($"切换到铁红色彩模式失败: {ex.Message}");
}
}
/// <summary>
/// 熔岩色彩模式
/// </summary>
private void LavaToolStripMenuItem_Click(object sender, EventArgs e)
{
try
{
if (_deviceManager != null)
{
Console.WriteLine("切换到熔岩色彩模式");
_deviceManager.SetPaletteType(PaletteType.Lava);
}
}
catch (Exception ex)
{
Console.WriteLine($"切换到熔岩色彩模式失败: {ex.Message}");
}
}
/// <summary>
/// 彩虹色彩模式
/// </summary>
private void RainbowToolStripMenuItem_Click(object sender, EventArgs e)
{
try
{
if (_deviceManager != null)
{
Console.WriteLine("切换到彩虹色彩模式");
_deviceManager.SetPaletteType(PaletteType.Rainbow);
}
}
catch (Exception ex)
{
Console.WriteLine($"切换到彩虹色彩模式失败: {ex.Message}");
}
}
/// <summary>
/// 铁灰色彩模式
/// </summary>
private void IronGrayToolStripMenuItem_Click(object sender, EventArgs e)
{
try
{
if (_deviceManager != null)
{
Console.WriteLine("切换到铁灰色彩模式");
_deviceManager.SetPaletteType(PaletteType.IronGray);
}
}
catch (Exception ex)
{
Console.WriteLine($"切换到铁灰色彩模式失败: {ex.Message}");
}
}
/// <summary>
/// 红热色彩模式
/// </summary>
private void RedHotToolStripMenuItem_Click(object sender, EventArgs e)
{
try
{
if (_deviceManager != null)
{
Console.WriteLine("切换到红热色彩模式");
_deviceManager.SetPaletteType(PaletteType.RedHot);
}
}
catch (Exception ex)
{
Console.WriteLine($"切换到红热色彩模式失败: {ex.Message}");
}
}
/// <summary>
/// 彩虹2色彩模式
/// </summary>
private void Rainbow2ToolStripMenuItem_Click(object sender, EventArgs e)
{
try
{
if (_deviceManager != null)
{
Console.WriteLine("切换到彩虹2色彩模式");
_deviceManager.SetPaletteType(PaletteType.Rainbow2);
}
}
catch (Exception ex)
{
Console.WriteLine($"切换到彩虹2色彩模式失败: {ex.Message}");
}
}
#endregion
#region
/// <summary>
/// 红外模式
/// </summary>
private void ThermalModeToolStripMenuItem_Click(object sender, EventArgs e)
{
try
{
_deviceManager.SetImageMode(ImageMode.Infrared);
}
catch (Exception ex)
{
Console.WriteLine("切换到红外模式失败: " + ex.Message);
ShowError("切换到红外模式失败");
}
}
private void VisibleModeToolStripMenuItem_Click(object sender, EventArgs e)
{
try
{
_deviceManager.SetImageMode(ImageMode.Natural);
}
catch (Exception ex)
{
Console.WriteLine("切换到自然模式失败: " + ex.Message);
ShowError("切换到自然模式失败");
}
}
#endregion
#region Ping相关方法
/// <summary>
/// Ping定时器的回调方法
/// </summary>
/// <param name="state">状态对象</param>
private void PingTimer_Tick(object state)
{
if (_deviceManager != null && !string.IsNullOrEmpty(_deviceManager.IPAddress))
{
Task.Factory.StartNew(() =>
{
bool pingResult = PingDevice(_deviceManager.IPAddress);
try
{
// 在线程安全的方式下更新状态
if (this.InvokeRequired)
{
this.Invoke(new Action<bool>(UpdatePingState), pingResult);
}
else
{
UpdatePingState(pingResult);
}
}
catch (ObjectDisposedException)
{
// 控件可能已被释放,忽略此更新
}
});
}
}
/// <summary>
/// 执行Ping操作
/// </summary>
/// <param name="ipAddress">要Ping的IP地址</param>
/// <returns>是否Ping通</returns>
private bool PingDevice(string ipAddress)
{
try
{
using (var ping = new System.Net.NetworkInformation.Ping())
{
var reply = ping.Send(ipAddress, 2000); // 2秒超时
return reply != null && reply.Status == System.Net.NetworkInformation.IPStatus.Success;
}
}
catch (Exception ex)
{
Console.WriteLine($"Ping设备失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 更新Ping状态
/// </summary>
/// <param name="isPingable">是否可Ping通</param>
private void UpdatePingState(bool isPingable)
{
// 按照README中要求的修改流程第3点和第6点Ping通状态变化时修改Ping状态只在非暂停状态下调用更新Info
IsDevicePingable = isPingable;
if (!_isPaused)
{
UpdateInfo();
}
}
/// <summary>
/// 开始设备Ping
/// </summary>
private void StartDevicePing()
{
try
{
if (_pingTimer != null)
{
_pingTimer.Change(0, _pingInterval); // 立即开始,然后按间隔执行
Console.WriteLine("设备Ping已启动");
}
}
catch (Exception ex)
{
Console.WriteLine($"启动设备Ping失败: {ex.Message}");
}
}
/// <summary>
/// 停止设备Ping
/// </summary>
private void StopDevicePing()
{
try
{
if (_pingTimer != null)
{
_pingTimer.Change(Timeout.Infinite, Timeout.Infinite);
Console.WriteLine("设备Ping已停止");
}
}
catch (Exception ex)
{
Console.WriteLine($"停止设备Ping失败: {ex.Message}");
}
}
#endregion
/// <summary>
/// 清理资源
/// </summary>
protected override void Dispose(bool disposing)
{
if (disposing)
{
// 检查是否处于设计模式
if (!DesignMode)
{
// 停止相机并释放相关资源
try
{
StopCamera();
}
catch (Exception ex)
{
Console.WriteLine("关闭相机时出错: " + ex.Message);
}
}
// 取消注册事件并释放设备管理器
if (_deviceManager != null)
{
// 移除所有事件监听
_deviceManager.ImageReceived -= DeviceManager_ImageReceived;
_deviceManager.ConnectionStatusChanged -= DeviceManager_ConnectionStatusChanged;
_deviceManager.ConnectionException -= DeviceManager_ConnectionException;
_deviceManager.TemperatureReceived -= DeviceManager_TemperatureReceived;
// 释放温度数据资源
lock (_lastTemperatureDataLock)
{
_lastTemperatureData = null;
}
// 释放设备管理器资源
_deviceManager.Dispose();
_deviceManager = null;
}
// 无论是否在设计模式下,都需要释放图像资源
// 释放Ping定时器
if (_pingTimer != null)
{
_pingTimer.Dispose();
_pingTimer = null;
}
if (imageBox != null && !imageBox.IsDisposed && imageBox.Image != null)
{
imageBox.Image.Dispose();
imageBox.Image = null;
}
// 释放图像缓冲区资源
if (_imageBuffer != null)
{
try
{
_imageBuffer.Dispose();
_imageBuffer = null;
Console.WriteLine("图像缓冲区资源已释放");
}
catch (Exception ex)
{
Console.WriteLine($"清理ImageBuffer资源异常: {ex.Message}");
}
}
// 释放LastImage资源
lock (_lastImageLock)
{
if (_lastImage != null)
{
try
{
_lastImage.Dispose();
_lastImage = null;
Console.WriteLine("LastImage资源已释放");
}
catch (Exception ex)
{
Console.WriteLine($"清理LastImage资源异常: {ex.Message}");
}
}
}
// 释放InfoImage资源
lock (_infoImageLock)
{
if (_infoImage != null)
{
try
{
_infoImage.Dispose();
_infoImage = null;
Console.WriteLine("InfoImage资源已释放");
}
catch (Exception ex)
{
Console.WriteLine($"清理InfoImage资源异常: {ex.Message}");
}
}
}
// 释放组件资源
if (components != null)
{
components.Dispose();
}
}
base.Dispose(disposing);
}
/// <summary>
/// 保存温度菜单项点击事件处理程序
/// </summary>
/// <param name="sender">事件发送者</param>
/// <param name="e">事件参数</param>
private void SaveTemperatureToolStripMenuItem_Click(object sender, EventArgs e)
{
try
{
// 检查设备管理器是否初始化且设备是否已连接
if (_deviceManager == null || _deviceManager.ConnectionStatus != ConnectionStatus.Connected)
{
MessageBox.Show("设备未连接,请先连接设备后再保存温度数据。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
// 使用SaveFileDialog让用户选择保存路径
SaveFileDialog saveFileDialog = new SaveFileDialog
{
Title = "保存温度数据",
Filter = "CSV文件 (*.csv)|*.csv|文本文件 (*.txt)|*.txt|所有文件 (*.*)|*.*",
DefaultExt = "csv",
FileName = $"温度数据_{DateTime.Now:yyyyMMdd_HHmmss}"
};
// 设置保存对话框的初始目录为ProjectPath属性的值
String SavePath = String.IsNullOrWhiteSpace(ProjectPath) ? Application.StartupPath : ProjectPath;
if (!string.IsNullOrEmpty(SavePath))
{
// 确保目录存在
if (Directory.Exists(SavePath))
{
saveFileDialog.InitialDirectory = SavePath;
}
else
{
// 尝试创建目录
try
{
Directory.CreateDirectory(SavePath);
saveFileDialog.InitialDirectory = SavePath;
}
catch (Exception ex)
{
Console.WriteLine($"创建保存路径目录失败: {ex.Message}");
// 如果创建目录失败,则不设置初始目录,使用默认行为
}
}
}
// 如果用户选择了文件路径
if (saveFileDialog.ShowDialog() == DialogResult.OK)
{
// 使用已缓存的温度数据
TemperatureData temperatureData;
lock (_lastTemperatureDataLock)
{
temperatureData = _lastTemperatureData;
}
if (temperatureData == null)
{
MessageBox.Show("没有可用的温度数据,请确保设备已连接且正在接收数据。", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// 保存温度数据到CSV文件
using (StreamWriter writer = new StreamWriter(saveFileDialog.FileName))
{
// 写入文件头部信息(以#开头的注释行不会被CSV解析器当作数据
writer.WriteLine("# 温度数据文件 - CSV格式逗号分隔值");
writer.WriteLine($"# 生成时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
writer.WriteLine($"# 分辨率: {temperatureData.Width} x {temperatureData.Height}");
writer.WriteLine($"# 最高温度: {temperatureData.MaxTemperature:F2} °C");
writer.WriteLine($"# 最低温度: {temperatureData.MinTemperature:F2} °C");
writer.WriteLine($"# 平均温度: {temperatureData.AverageTemperature:F2} °C");
writer.WriteLine();
// 写入温度数据矩阵CSV格式
for (int i = 0; i < temperatureData.Height; i++)
{
StringBuilder lineBuilder = new StringBuilder();
for (int j = 0; j < temperatureData.Width; j++)
{
// 获取温度值
string tempValue = $"{temperatureData.TemperatureMatrix[i, j]:F2}";
// CSV标准格式如果值包含逗号、引号或换行符需要用引号包围并转义内部引号
if (tempValue.Contains(',') || tempValue.Contains('"') || tempValue.Contains('\n'))
{
tempValue = $"\"{tempValue.Replace("\"", "\"\"")}\"";
}
lineBuilder.Append(tempValue);
if (j < temperatureData.Width - 1)
{
lineBuilder.Append(",");
}
}
writer.WriteLine(lineBuilder.ToString());
}
}
// 显示保存成功消息
MessageBox.Show($"温度数据已成功保存到:\n{saveFileDialog.FileName}", "保存成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
catch (Exception ex)
{
// 显示错误消息
MessageBox.Show($"保存温度数据时发生错误:\n{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
Console.WriteLine($"保存温度数据异常: {ex.Message}\n{ex.StackTrace}");
}
}
}
}