using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.IO; 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 class TemperatureZone { public int Index { get; set; } public int X { get; set; } public int Y { get; set; } public int Width { get; set; } public int Height { get; set; } public Color Color { get; set; } } // 温差配置类 private class TemperatureDiffConfig { public List> TemperatureLegend { get; set; } public Dictionary PixelTemperatureData { get; set; } public TemperatureDiffConfig() { TemperatureLegend = new List>(); PixelTemperatureData = new Dictionary(); } } // 设备管理器实例 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 Image _displayImage = null; private readonly object _displayImageLock = new object(); // 最近一次获取到的温度数据 // 温度显示状态标志 private bool _showGlobalTemperature = false; // 是否显示全局温度 private bool _showAreaTemperature = false; // 是否显示区域温度 private bool _showMaxTemperature = false; // 是否显示最高温度(默认为false) private bool _showAverageTemperature = false; // 是否显示平均温度 private bool _showMinTemperature = false; // 是否显示最低温度 // 日志保存状态标志 // 不再需要_saveLogEnabled字段,直接使用DeviceManager.LogToFile静态属性控制日志记录 // 用于保护_lastImage的线程锁 private readonly object _lastImageLock = new object(); // 用于保护_infoImage的线程锁 private readonly object _infoImageLock = new object(); // 是否显示信息图像 private bool _isDisplayingInfo = false; // 是否暂停检测 private bool _isPaused = false; // 项目路径,用于数据文件的存取 private string _projectPath = ""; // 配置是否已经加载的标志位 private bool _isConfigLoaded = false; // 加载的测温区配置 private readonly List _loadedTemperatureZones = new List(); // 加载的温差配置 private TemperatureDiffConfig _loadedTemperatureDiffConfig = new TemperatureDiffConfig(); /// /// 获取或设置项目路径,控件所需的数据文件将在此目录中进行存取 /// [Category("配置")] [Description("设置项目路径,控件所需的数据文件将在此目录中进行存取")] [DefaultValue("")] public string ProjectPath { get { return _projectPath; } set { // 只有当值发生变化时才进行同步 if (_projectPath != value) { _projectPath = value; // 如果DeviceManager已经初始化,则同步更新其ProjectPath属性 if (_deviceManager != null) { _deviceManager.ProjectPath = _projectPath; } // 加载配置文件 LoadAllConfigs(); // 设置配置已加载标志 _isConfigLoaded = true; } } } /// /// 加载测温区配置文件 /// private void LoadZoneConfig() { try { if (string.IsNullOrWhiteSpace(_projectPath)) _projectPath = ""; // 配置文件存储在Config子目录中 string configDir = Path.Combine(_projectPath, "Config"); // 确保Config目录存在 Directory.CreateDirectory(configDir); string configPath = Path.Combine(configDir, "测温区信息.csv"); if (!File.Exists(configPath)) return; // 清空已加载的测温区 _loadedTemperatureZones.Clear(); // 读取CSV文件 using (StreamReader reader = new StreamReader(configPath, Encoding.UTF8)) { // 跳过标题行 reader.ReadLine(); // 读取数据行 string line; while ((line = reader.ReadLine()) != null) { if (string.IsNullOrWhiteSpace(line)) continue; string[] parts = line.Split(','); if (parts.Length < 6) continue; try { // 解析颜色(支持HTML格式和十六进制格式) TemperatureZone zone = new TemperatureZone { Index = int.Parse(parts[0]), X = int.Parse(parts[1]), Y = int.Parse(parts[2]), Width = int.Parse(parts[3]), Height = int.Parse(parts[4]), Color = ColorTranslator.FromHtml(parts[5]) }; _loadedTemperatureZones.Add(zone); } catch (Exception ex) { Console.WriteLine($"解析测温区配置行失败: {line}, 错误: {ex.Message}"); } } } Console.WriteLine($"成功加载 {_loadedTemperatureZones.Count} 个测温区配置"); } catch (Exception ex) { Console.WriteLine($"加载测温区配置文件失败: {ex.Message}"); } } /// /// 加载温差配置文件 /// private void LoadTemperatureDiffConfig() { try { if (string.IsNullOrWhiteSpace(_projectPath)) _projectPath = ""; // 配置文件存储在Config子目录中 string configDir = Path.Combine(_projectPath, "Config"); // 确保Config目录存在 Directory.CreateDirectory(configDir); string configPath = Path.Combine(configDir, "温差数据.csv"); if (!File.Exists(configPath)) return; // 创建新的温差配置实例 TemperatureDiffConfig config = new TemperatureDiffConfig(); // 读取CSV文件 using (StreamReader reader = new StreamReader(configPath, Encoding.UTF8)) { string line; bool readingLegend = false; bool readingPixelData = false; while ((line = reader.ReadLine()) != null) { if (string.IsNullOrWhiteSpace(line)) continue; // 检查部分标题 if (line == "温差图例信息") { readingLegend = true; readingPixelData = false; reader.ReadLine(); // 跳过温度图例的标题行 continue; } else if (line == "像素温度数据") { readingLegend = false; readingPixelData = true; reader.ReadLine(); // 跳过像素温度数据的标题行 continue; } if (readingLegend) { // 解析温差图例数据 string[] parts = line.Split(','); if (parts.Length >= 2) { try { double temperature = double.Parse(parts[0]); Color color = ColorTranslator.FromHtml(parts[1]); config.TemperatureLegend.Add(new Tuple(temperature, color)); } catch (Exception ex) { Console.WriteLine($"解析温差图例数据失败: {line}, 错误: {ex.Message}"); } } } else if (readingPixelData) { // 解析像素温度数据 string[] parts = line.Split(','); if (parts.Length >= 3) { try { int x = int.Parse(parts[0]); int y = int.Parse(parts[1]); double temperature = double.Parse(parts[2]); config.PixelTemperatureData[new Point(x, y)] = temperature; } catch (Exception ex) { Console.WriteLine($"解析像素温度数据失败: {line}, 错误: {ex.Message}"); } } } } } // 更新加载的温差配置 _loadedTemperatureDiffConfig = config; Console.WriteLine($"成功加载温差配置,包含 {_loadedTemperatureDiffConfig.TemperatureLegend.Count} 个图例项和 {_loadedTemperatureDiffConfig.PixelTemperatureData.Count} 个像素温度数据"); } catch (Exception ex) { Console.WriteLine($"加载温差配置文件失败: {ex.Message}"); } } /// /// 加载所有配置文件 /// private void LoadAllConfigs() { LoadZoneConfig(); LoadTemperatureDiffConfig(); } /// /// 更新设计模式状态到DeviceManager /// private void UpdateDesignModeStatus() { DeviceManager.IsDesignMode = DesignMode; Console.WriteLine($"相机控件设计模式状态已更新: {DesignMode}"); } /// /// 更新InfoImage显示 - 按照用户要求的详细步骤: /// 1. 以透明色清空Info /// 2. 如果暂停,显示暂停信息,否则如果Ping不通或断开,显示重连信息,否则满足就绪条件 /// 3. 在就绪条件下,如果有温度数据,显示最高温度 /// 4. 最后调用更新UI /// 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 isConnected = _deviceManager != null && _deviceManager.ConnectionStatus == ConnectionStatus.Connected; bool isPaused = _isPaused; // 使用_isPaused标志判断暂停状态 bool isPingFailed = !IsDevicePingable; bool isReady = !isPaused && !isPingFailed && isConnected; // 就绪条件:非暂停、可Ping通、已连接 using (Graphics g = Graphics.FromImage(_infoImage)) { // 步骤1:以透明色清空Info g.Clear(Color.Transparent); // 步骤2:检查暂停状态、Ping状态和连接状态 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); } } } // 设置显示标志 _isDisplayingInfo = isPaused || isDisconnected || isReconnecting || _showGlobalTemperature || _showAreaTemperature; // 步骤3:无论就绪状态如何,都调用更新UI以显示状态信息 UpdateImageOnUI(); } } catch (Exception ex) { Console.WriteLine($"更新Info显示时出错: {ex.Message}"); } } /// /// 暂停/恢复检测菜单项点击事件处理 /// 1、暂停或恢复时,设置暂停状态,调用更新Info /// private void PauseDetectionToolStripMenuItem_Click(object sender, EventArgs e) { if (DesignMode) return; try { // 记录操作前的暂停状态 Console.WriteLine($"[PauseDetection] 操作前状态: _isPaused = {_isPaused}"); // 切换暂停状态 _isPaused = !_isPaused; // 记录操作后的暂停状态 Console.WriteLine($"[PauseDetection] 操作后状态: _isPaused = {_isPaused}"); if (_isPaused) { // 设置暂停状态 pauseDetectionToolStripMenuItem.Text = "恢复检测"; // 暂停时停止图像接收并更新DeviceManager的暂停检测状态 if (_deviceManager != null) { _deviceManager.IsDetectionPaused = true; if (_isReceivingImage) { _deviceManager.StopImageReceiving(); _deviceManager.PauseTemperatureDataReceiving(); _isReceivingImage = false; } } Console.WriteLine($"[PauseDetection] 检测已暂停 - DeviceManager状态更新完成,当前时间: {DateTime.Now:HH:mm:ss.fff}"); } else { // 设置恢复状态 pauseDetectionToolStripMenuItem.Text = "暂停检测"; // 恢复时更新DeviceManager的暂停检测状态并重新开始图像接收 if (_deviceManager != null) { _deviceManager.IsDetectionPaused = false; if (_deviceManager.ConnectionStatus == ConnectionStatus.Connected) { _deviceManager.StopImageReceiving(); _deviceManager.StartImageReceiving(); _deviceManager.ResumeTemperatureDataReceiving(); _isReceivingImage = true; // 恢复检测后,启动连接检查以确保连接正常 _deviceManager.StartConnectionCheck(); } // 如果当前是断开状态但启用了自动重连,尝试启动重连 else if (_deviceManager.AutoReconnectEnabled) { _deviceManager.StartAutoReconnect(); } } Console.WriteLine($"[PauseDetection] 检测已恢复 - DeviceManager状态更新完成,连接状态: {_deviceManager?.ConnectionStatus}, 当前时间: {DateTime.Now:HH:mm:ss.fff}"); } // 修改流程第1点和第5点:暂停或恢复时,设置暂停状态,调用更新Info(在暂停状态下会显示暂停信息) UpdateInfo(); // 保存菜单状态 SaveMenuConfig(); } catch (Exception ex) { Console.WriteLine($"处理暂停/恢复检测时出错: {ex.Message}"); } } public Camera() { InitializeComponent(); // 为右键菜单添加Opening事件,用于在菜单显示前更新色彩模式的选中状态 this.contextMenuStrip1.Opening += ContextMenuStrip1_Opening; // 初始化日志保存状态,确保与菜单项状态同步 // 直接使用DeviceManager.LogToFile静态属性控制日志记录,无需额外的_saveLogEnabled字段 saveLogToolStripMenuItem.Checked = DeviceManager.LogToFile; // 将设计模式状态传递给DeviceManager UpdateDesignModeStatus(); // 初始化图像缓冲区 InitializeImageBuffer(); // 加载保存的菜单配置 LoadMenuConfig(); // 初始化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(); } } /// /// Camera控件加载事件 /// 在控件加载时自动启动相机显示热图 /// private void Camera_Load(object sender, EventArgs e) { // 只有在非设计模式下才启动相机 if (!DesignMode) { try { StartCamera(); } catch (Exception ex) { ShowError($"自动启动相机失败: {ex.Message}"); } } } // 注意:UserControl不支持FormClosing事件,资源清理已在Dispose方法中处理 /// /// 初始化设备管理器 /// private void InitializeDeviceManager() { // 只有在非设计模式下才初始化设备管理器 if (!DesignMode) { _deviceManager = new DeviceManager { AutoReconnectEnabled = true, ReconnectInterval = 2000, // 2秒 ProjectPath = !string.IsNullOrEmpty(ProjectPath) ? ProjectPath : Application.StartupPath }; // 设置静态属性 DeviceManager.MaxReconnectAttempts = 5; // 初始化时加载配置文件,只执行一次 if (!_isConfigLoaded) { LoadAllConfigs(); _isConfigLoaded = true; } // 注册图像接收事件 _deviceManager.ImageReceived += DeviceManager_ImageReceived; // 注册连接状态变更事件 _deviceManager.ConnectionStatusChanged += DeviceManager_ConnectionStatusChanged; // 注册连接异常事件 _deviceManager.ConnectionException += DeviceManager_ConnectionException; // 不再需要温度数据实时通知,移除事件订阅 } } /// /// 初始化图像缓冲区和相关图像资源 /// 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"); } // 初始化DisplayImage为透明bitmap lock (_displayImageLock) { _displayImage = new Bitmap(BUFFER_WIDTH, BUFFER_HEIGHT); using (Graphics g = Graphics.FromImage(_displayImage)) { g.Clear(Color.Transparent); } Console.WriteLine("DisplayImage已初始化为透明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; } } } } /// /// 启动相机 /// 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); } } /// /// 开始接收图像(使用HTTP方式) /// private void StartReceiveImage() { if (DesignMode) return; try { if (!_isReceivingImage && _deviceManager.ConnectionStatus == ConnectionStatus.Connected) { Console.WriteLine("Camera开始使用HTTP方式接收图像"); // 记录日志 WriteLog("开始使用HTTP方式接收图像"); // 直接调用HTTP方式的图像接收 _deviceManager.StartImageReceiving(); // 不再需要温度数据实时通知机制,移除相关代码 _isReceivingImage = true; WriteLog("图像接收已启动"); } } catch (Exception ex) { ShowError($"开始接收图像失败: {ex.Message}"); WriteLog($"开始接收图像失败: {ex.Message}"); } } /// /// 停止接收图像 /// public void StopCamera() { // 停止设备Ping StopDevicePing(); if (DesignMode) return; try { if (_isReceivingImage) { _deviceManager.StopReceiveImage(); _isReceivingImage = false; } } catch (Exception ex) { Console.WriteLine($"停止相机失败: {ex.Message}"); } } // 温度数据相关变量已移除,不再需要温度数据实时通知机制 // Ping相关字段 private System.Threading.Timer _pingTimer; private bool _isDevicePingable = false; private const int _pingInterval = 500; // 0.5秒Ping一次 /// /// 获取设备是否可Ping通 /// public bool IsDevicePingable { get { return _isDevicePingable; } private set { if (_isDevicePingable != value) { _isDevicePingable = value; Console.WriteLine($"设备Ping状态变更: {(_isDevicePingable ? "可Ping通" : "不可Ping通")}"); // 按照README中要求的修改流程第3点和第6点:Ping通状态变化时,只在非暂停状态下调用更新Info if (!_isPaused) { UpdateInfo(); } } } } /// /// 设备管理器图像接收事件处理 /// 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中要求:图像更新时,保存LastImage,调用更新UI,不调用更新Info this.BeginInvoke(new Action(() => { try { if (!_isPaused) { UpdateImageOnUI(); // 只调用更新UI,不调用更新Info } } catch (Exception ex) { Console.WriteLine($"更新UI图像失败: {ex.Message}"); } })); } } } } } catch (Exception ex) { Console.WriteLine($"处理接收到的图像时出错: {ex.Message}"); } } /// /// 在UI线程上更新图像 - 新实现,按照用户要求: /// 1. 先将LastImage绘制到全局缓冲 /// 2. 再将InfoImage绘制到缓冲 /// 3. 最后一次性绘制到图像框的bitmap /// 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; } // 调用温度显示更新方法 // 在就绪条件下,调用更新实时信息 bool isReady = !_isPaused && IsDevicePingable && _deviceManager != null && _deviceManager.ConnectionStatus == ConnectionStatus.Connected; if (isReady) { UpdateRealTimeInfoOnUI(); } Image lastImage = null; Image infoImage = null; Image oldImage = null; Bitmap tempImage = 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); } } // 步骤2.1:绘制DisplayImage(实时温度信息等) - 仅在就绪条件下执行 if (isReady) { lock (_displayImageLock) { if (_displayImage != null) { g.DrawImage(_displayImage, 0, 0); } } } // 注意:温度信息不再直接在这里绘制 // 而是在UpdateRealTimeInfoOnUI方法中绘制到_displayImage,然后在这里绘制到_imageBuffer } // 在同一个锁内创建缓冲区的副本,避免重复锁定 tempImage = (Bitmap)_imageBuffer.Clone(); } // 将全局缓冲一次性绘制到图像框的bitmap imageBox.Image = tempImage; // 步骤5:同步更新检测配置窗口的实时图像属性 // 创建LastImage的副本并通过UpdateRealTimeImage方法传递给Setting窗口 lock (_lastImageLock) { if (_lastImage != null) { // 直接创建LastImage的副本以避免线程安全问题 Image lastImageCopy = (Image)_lastImage.Clone(); // 调用Setting窗口的方法更新实时温度图像 Setting.Form.UpdateRealTimeImage(lastImageCopy); } } //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); } } } /// /// 安全释放图像资源的辅助方法 /// private void DisposeImage(Image image) { if (image != null) { try { image.Dispose(); } catch { } } } /// /// 设备管理器连接状态变更事件处理 /// 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(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}"); } } /// /// 处理连接状态变更 /// 2、断开或连接时,设置连接状态,调用更新Info /// private void HandleConnectionStatusChanged(ConnectionStatusChangedEventArgs e) { if (DesignMode) return; try { // 确保在UI线程上更新UI if (this.InvokeRequired) { this.Invoke(new Action(HandleConnectionStatusChanged), e); return; } // 更新UI状态 UpdateUIState(e.Status == ConnectionStatus.Connected); // 检查_deviceManager是否为空 if (_deviceManager == null) { Console.WriteLine("设备管理器未初始化"); WriteLog("设备管理器未初始化"); return; } // 记录连接状态变化 WriteLog($"连接状态变化: {e.Status} {(!string.IsNullOrEmpty(e.DeviceInfo) ? "- " + e.DeviceInfo : "")}"); switch (e.Status) { case ConnectionStatus.Connected: Console.WriteLine("设备已连接"); WriteLog("设备已连接成功"); // 仅在首次连接时设置为热图模式,重连时保留之前的模式 if (!_isReceivingImage) // 首次连接时_isReceivingImage为false { try { _deviceManager.SetImageMode(ImageMode.Infrared); Console.WriteLine("首次连接,设置热图模式"); WriteLog("首次连接,设置热图模式"); } catch (Exception ex) { Console.WriteLine($"设置热图模式失败: {ex.Message}"); WriteLog($"设置热图模式失败: {ex.Message}"); } } else { Console.WriteLine("重连成功,保留当前图像模式"); WriteLog("重连成功,保留当前图像模式"); } // 注意:色彩模式同步现在在DeviceManager内部的连接成功处理中自动完成 // 无需在此处重复调用 // 开始接收图像(包含在try-catch中) if (!_isReceivingImage) { try { StartReceiveImage(); } catch (Exception ex) { Console.WriteLine($"开始接收图像失败: {ex.Message}"); WriteLog($"开始接收图像失败: {ex.Message}"); } } // 按照README中要求的修改流程第2点和第5点:断开或连接时,设置连接状态,只在非暂停状态下调用更新Info if (!_isPaused) { UpdateInfo(); } break; case ConnectionStatus.Disconnected: Console.WriteLine("设备已断开连接"); WriteLog("设备连接已断开"); // 停止接收图像(添加空检查和异常处理) if (_isReceivingImage) { try { _deviceManager.StopReceiveImage(); _isReceivingImage = false; } catch (Exception ex) { Console.WriteLine($"停止接收图像失败: {ex.Message}"); WriteLog($"停止接收图像失败: {ex.Message}"); _isReceivingImage = false; // 确保状态更新 } } // 按照README中要求的修改流程第2点和第5点:断开或连接时,设置连接状态,只在非暂停状态下调用更新Info if (!_isPaused) { UpdateInfo(); } if (!string.IsNullOrEmpty(e.DeviceInfo)) { ShowError(e.DeviceInfo); WriteLog($"断开原因: {e.DeviceInfo}"); } else { ShowError("设备连接已断开"); WriteLog("设备连接已断开"); } break; case ConnectionStatus.Connecting: Console.WriteLine($"正在连接设备...{(!string.IsNullOrEmpty(e.DeviceInfo) ? " " + e.DeviceInfo : "")}"); WriteLog($"正在连接设备...{(!string.IsNullOrEmpty(e.DeviceInfo) ? " " + e.DeviceInfo : "")}"); ShowError(string.Empty); // 清除之前的错误信息 break; case ConnectionStatus.Reconnecting: Console.WriteLine($"正在重新连接设备...{(!string.IsNullOrEmpty(e.DeviceInfo) ? " " + e.DeviceInfo : "")}"); WriteLog($"正在重新连接设备...{(!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 { } } } /// /// 更新UI状态 /// /// 是否已连接 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}"); } } /// /// 设备管理器连接异常事件处理 /// 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}"); } } /// /// 显示错误信息 /// private void ShowError(string message) { if (DesignMode) return; Console.WriteLine(message); // 错误消息仅写入日志即可,不需要在UI上显示 } /// /// 右键菜单显示前的事件处理方法 /// 用于更新色彩模式菜单项的选中状态 /// /// /// 右键菜单打开事件处理 /// private void ContextMenuStrip1_Opening(object sender, System.ComponentModel.CancelEventArgs e) { // 确保全局温度和区域温度的互斥性 // 如果两个都被选中,默认保持全局温度选中状态,取消区域温度选中状态 if (globalTemperatureToolStripMenuItem.Checked && areaTemperatureToolStripMenuItem.Checked) { areaTemperatureToolStripMenuItem.Checked = false; } // 暂停菜单项的文本已经在点击事件中更新,这里无需再次更新 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; // 在暂停状态下隐藏温度显示根菜单 temperatureDisplayToolStripMenuItem.Visible = false; // 当只有一个菜单项可见时,隐藏分隔符 toolStripSeparator1.Visible = false; } else { // 在非暂停状态且已连接状态下,显示图像模式根菜单 // 注意:根菜单显示后,其所有子菜单会自动显示,不需要单独设置 // 只在红外模式下显示温度显示根菜单 temperatureDisplayToolStripMenuItem.Visible = isInfraredMode; 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 色彩模式切换方法 /// /// 白热色彩模式 /// 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}"); } } /// /// 黑热色彩模式 /// 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}"); } } /// /// 铁红色彩模式 /// 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}"); } } /// /// 熔岩色彩模式 /// 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}"); } } /// /// 彩虹色彩模式 /// 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}"); } } /// /// 铁灰色彩模式 /// 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}"); } } /// /// 红热色彩模式 /// 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}"); } } /// /// 彩虹2色彩模式 /// 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 视频模式切换方法 /// /// 红外模式 /// private void ThermalModeToolStripMenuItem_Click(object sender, EventArgs e) { try { _deviceManager.SetImageMode(ImageMode.Infrared); } catch (Exception ex) { Console.WriteLine("切换到红外模式失败: " + ex.Message); ShowError("切换到红外模式失败"); } finally { // 菜单状态需要保存和恢复 SaveMenuConfig(); } } private void VisibleModeToolStripMenuItem_Click(object sender, EventArgs e) { try { _deviceManager.SetImageMode(ImageMode.Natural); } catch (Exception ex) { Console.WriteLine("切换到自然模式失败: " + ex.Message); ShowError("切换到自然模式失败"); } finally { // 菜单状态需要保存和恢复 SaveMenuConfig(); } } #endregion #region 设备Ping相关方法 /// /// Ping定时器的回调方法 /// /// 状态对象 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(UpdatePingState), pingResult); } else { UpdatePingState(pingResult); } } catch (ObjectDisposedException) { // 控件可能已被释放,忽略此更新 } }); } } /// /// 执行Ping操作 /// /// 要Ping的IP地址 /// 是否Ping通 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; } } /// /// 更新Ping状态 /// /// 是否可Ping通 private void UpdatePingState(bool isPingable) { // 按照README中要求的修改流程第3点:Ping通状态变化时,修改Ping状态 // 注意:UpdateInfo的调用已在IsDevicePingable的setter中实现(只在非暂停状态下) IsDevicePingable = isPingable; } /// /// 开始设备Ping /// private void StartDevicePing() { try { if (_pingTimer != null) { _pingTimer.Change(0, _pingInterval); // 立即开始,然后按间隔执行 Console.WriteLine("设备Ping已启动"); } } catch (Exception ex) { Console.WriteLine($"启动设备Ping失败: {ex.Message}"); } } /// /// 停止设备Ping /// 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 /// /// 清理资源 /// protected override void Dispose(bool disposing) { if (disposing) { // 检查是否处于设计模式 if (!DesignMode) { // 停止相机并释放相关资源 try { StopCamera(); } catch (Exception ex) { Console.WriteLine("关闭相机时出错: " + ex.Message); } } Setting.Form.Close(); // 取消注册事件并释放设备管理器 if (_deviceManager != null) { // 移除所有事件监听 _deviceManager.ImageReceived -= DeviceManager_ImageReceived; _deviceManager.ConnectionStatusChanged -= DeviceManager_ConnectionStatusChanged; _deviceManager.ConnectionException -= DeviceManager_ConnectionException; // 已移除对TemperatureReceived事件的订阅,不再需要取消订阅 // 释放设备管理器资源 _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}"); } } } // 释放DisplayImage资源 lock (_displayImageLock) { if (_displayImage != null) { try { _displayImage.Dispose(); _displayImage = null; Console.WriteLine("DisplayImage资源已释放"); } catch (Exception ex) { Console.WriteLine($"清理DisplayImage资源异常: {ex.Message}"); } } } // 释放组件资源 components?.Dispose(); } base.Dispose(disposing); } /// /// 保存温度菜单项点击事件处理程序 /// /// 事件发送者 /// 事件参数 /// /// 全局温度菜单项点击事件处理 /// 当全局温度被勾选时,自动取消区域温度的勾选 /// private void GlobalTemperatureToolStripMenuItem_Click(object sender, EventArgs e) { // 更新全局温度显示状态标志 _showGlobalTemperature = globalTemperatureToolStripMenuItem.Checked; // 当全局温度被勾选时,自动取消区域温度的勾选 if (_showGlobalTemperature) { _showAreaTemperature = false; areaTemperatureToolStripMenuItem.Checked = false; } // 修改流程第6点:数据显示菜单勾选变化时,只在非暂停状态下调用更新实时信息 if (!_isPaused) { UpdateRealTimeInfoOnUI(); } // 菜单状态变更时自动静默保存配置 SaveMenuConfig(); } /// /// 区域温度菜单项点击事件处理 /// 当区域温度被勾选时,自动取消全局温度的勾选 /// private void AreaTemperatureToolStripMenuItem_Click(object sender, EventArgs e) { // 更新区域温度显示状态标志 _showAreaTemperature = areaTemperatureToolStripMenuItem.Checked; // 当区域温度被勾选时,自动取消全局温度的勾选 if (_showAreaTemperature) { _showGlobalTemperature = false; globalTemperatureToolStripMenuItem.Checked = false; } // 修改流程第6点:数据显示菜单勾选变化时,只在非暂停状态下调用更新实时信息 if (!_isPaused) { UpdateRealTimeInfoOnUI(); } // 菜单状态变更时自动静默保存配置 SaveMenuConfig(); } /// /// 最高温度菜单项点击事件处理 /// private void MaxTemperatureToolStripMenuItem_Click(object sender, EventArgs e) { // 更新最高温度显示状态标志 _showMaxTemperature = maxTemperatureToolStripMenuItem.Checked; // 修改流程第6点:数据显示菜单勾选变化时,只在非暂停状态下调用更新实时信息 if (!_isPaused) { UpdateRealTimeInfoOnUI(); } // 菜单状态变更时自动静默保存配置 SaveMenuConfig(); } /// /// 平均温度菜单项点击事件处理 /// private void AvgTemperatureToolStripMenuItem_Click(object sender, EventArgs e) { // 更新平均温度显示状态标志 _showAverageTemperature = avgTemperatureToolStripMenuItem.Checked; // 修改流程第6点:数据显示菜单勾选变化时,只在非暂停状态下调用更新实时信息 if (!_isPaused) { UpdateRealTimeInfoOnUI(); } // 菜单状态变更时自动静默保存配置 SaveMenuConfig(); } /// /// 最低温度菜单项点击事件处理 /// private void MinTemperatureToolStripMenuItem_Click(object sender, EventArgs e) { // 更新最低温度显示状态标志 _showMinTemperature = minTemperatureToolStripMenuItem.Checked; // 修改流程第6点:数据显示菜单勾选变化时,只在非暂停状态下调用更新实时信息 if (!_isPaused) { UpdateRealTimeInfoOnUI(); } // 菜单状态变更时自动静默保存配置 SaveMenuConfig(); } /// /// 保存日志菜单项点击事件处理 /// private void SaveLogToolStripMenuItem_Click(object sender, EventArgs e) { // 更新DeviceManager.LogToFile静态属性,这是控制所有日志记录的单一来源 DeviceManager.LogToFile = saveLogToolStripMenuItem.Checked; // 可以在这里添加日志记录的初始化或清理逻辑 if (DeviceManager.LogToFile) { // 日志启用时的初始化 WriteLog("日志保存功能已启用"); } else { // 日志禁用时的清理(如果需要) WriteLog("日志保存功能已禁用"); } // 菜单状态变更时自动静默保存配置 SaveMenuConfig(); } /// /// 保存菜单配置到CSV文件 /// private void SaveMenuConfig() { try { WriteLog("开始保存菜单配置"); string configFilePath; // 如果项目路径存在,使用项目路径下的Config目录 if (!string.IsNullOrEmpty(_projectPath) && Directory.Exists(_projectPath)) { try { // 创建Config目录(如果不存在) string configDir = Path.Combine(_projectPath, "Config"); Directory.CreateDirectory(configDir); // 配置文件路径 configFilePath = Path.Combine(configDir, "menu_config.csv"); WriteLog($"使用项目路径保存配置: {configFilePath}"); } catch (UnauthorizedAccessException accessEx) { string errorMsg = $"项目路径访问权限不足: {accessEx.Message}"; WriteLog($"{errorMsg} - {accessEx.StackTrace}"); Console.WriteLine(errorMsg); // 回退到应用程序目录 configFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config", "menu_config.csv"); Directory.CreateDirectory(Path.GetDirectoryName(configFilePath)); WriteLog($"回退到应用程序目录保存: {configFilePath}"); } } else { // 如果项目路径不存在,使用应用程序目录下的Config目录 string configDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config"); Directory.CreateDirectory(configDir); configFilePath = Path.Combine(configDir, "menu_config.csv"); WriteLog($"使用应用程序目录保存配置: {configFilePath}"); } // 创建CSV内容 StringBuilder csvContent = new StringBuilder(); // 添加标题行 csvContent.AppendLine("MenuName,Status,Checked"); try { // 保存暂停检测菜单状态 if (pauseDetectionToolStripMenuItem != null) { csvContent.AppendLine($"pauseDetection,{pauseDetectionToolStripMenuItem.Text},{_isPaused}"); } else { WriteLog("警告: pauseDetectionToolStripMenuItem未初始化"); } // 保存日志保存菜单状态 if (saveLogToolStripMenuItem != null) { csvContent.AppendLine($"saveLog,{saveLogToolStripMenuItem.Text},{saveLogToolStripMenuItem.Checked}"); } else { WriteLog("警告: saveLogToolStripMenuItem未初始化"); } // 保存温度显示菜单状态 if (globalTemperatureToolStripMenuItem != null) csvContent.AppendLine($"globalTemperature,{globalTemperatureToolStripMenuItem.Text},{globalTemperatureToolStripMenuItem.Checked}"); if (areaTemperatureToolStripMenuItem != null) csvContent.AppendLine($"areaTemperature,{areaTemperatureToolStripMenuItem.Text},{areaTemperatureToolStripMenuItem.Checked}"); if (maxTemperatureToolStripMenuItem != null) csvContent.AppendLine($"maxTemperature,{maxTemperatureToolStripMenuItem.Text},{maxTemperatureToolStripMenuItem.Checked}"); if (avgTemperatureToolStripMenuItem != null) csvContent.AppendLine($"avgTemperature,{avgTemperatureToolStripMenuItem.Text},{avgTemperatureToolStripMenuItem.Checked}"); if (minTemperatureToolStripMenuItem != null) csvContent.AppendLine($"minTemperature,{minTemperatureToolStripMenuItem.Text},{minTemperatureToolStripMenuItem.Checked}"); // 保存色彩模式菜单状态 if (whiteHotToolStripMenuItem != null) csvContent.AppendLine($"whiteHot,{whiteHotToolStripMenuItem.Text},{whiteHotToolStripMenuItem.Checked}"); if (blackHotToolStripMenuItem != null) csvContent.AppendLine($"blackHot,{blackHotToolStripMenuItem.Text},{blackHotToolStripMenuItem.Checked}"); if (ironRedToolStripMenuItem != null) csvContent.AppendLine($"ironRed,{ironRedToolStripMenuItem.Text},{ironRedToolStripMenuItem.Checked}"); if (lavaToolStripMenuItem != null) csvContent.AppendLine($"lava,{lavaToolStripMenuItem.Text},{lavaToolStripMenuItem.Checked}"); if (rainbowToolStripMenuItem != null) csvContent.AppendLine($"rainbow,{rainbowToolStripMenuItem.Text},{rainbowToolStripMenuItem.Checked}"); if (ironGrayToolStripMenuItem != null) csvContent.AppendLine($"ironGray,{ironGrayToolStripMenuItem.Text},{ironGrayToolStripMenuItem.Checked}"); if (redHotToolStripMenuItem != null) csvContent.AppendLine($"redHot,{redHotToolStripMenuItem.Text},{redHotToolStripMenuItem.Checked}"); if (rainbow2ToolStripMenuItem != null) csvContent.AppendLine($"rainbow2,{rainbow2ToolStripMenuItem.Text},{rainbow2ToolStripMenuItem.Checked}"); // 保存图像模式菜单状态 if (thermalModeToolStripMenuItem != null) csvContent.AppendLine($"thermalMode,{thermalModeToolStripMenuItem.Text},{thermalModeToolStripMenuItem.Checked}"); if (visibleModeToolStripMenuItem != null) csvContent.AppendLine($"visibleMode,{visibleModeToolStripMenuItem.Text},{visibleModeToolStripMenuItem.Checked}"); } catch (Exception ex) { // 捕获配置数据收集过程中的异常 string errorMsg = $"收集菜单状态数据失败: {ex.Message}"; WriteLog($"{errorMsg} - {ex.StackTrace}"); Console.WriteLine(errorMsg); throw; } // 写入文件 try { File.WriteAllText(configFilePath, csvContent.ToString(), Encoding.UTF8); string successMsg = $"菜单配置已成功保存到CSV文件: {configFilePath}"; Console.WriteLine(successMsg); WriteLog(successMsg); } catch (IOException ioEx) { // 捕获IO异常并记录详细信息 string errorMsg = $"配置文件IO异常: {ioEx.Message}"; WriteLog($"{errorMsg} - {ioEx.StackTrace}"); Console.WriteLine(errorMsg); } catch (UnauthorizedAccessException accessEx) { // 捕获权限异常并记录详细信息 string errorMsg = $"配置文件访问权限异常: {accessEx.Message}"; WriteLog($"{errorMsg} - {accessEx.StackTrace}"); Console.WriteLine(errorMsg); } } catch (Exception ex) { // 记录保存失败的综合日志 string errorMsg = $"保存菜单配置过程中发生未预期的错误: {ex.Message}"; WriteLog($"{errorMsg} - {ex.StackTrace}"); Console.WriteLine(errorMsg); } finally { WriteLog("菜单配置保存操作完成"); } } /// /// 从CSV文件加载菜单配置 /// private void LoadMenuConfig() { WriteLog("开始加载菜单配置"); try { string configFilePath = null; // 检查项目路径下的配置文件 if (!string.IsNullOrEmpty(_projectPath) && Directory.Exists(_projectPath)) { try { configFilePath = Path.Combine(_projectPath, "Config", "menu_config.csv"); WriteLog($"检查项目路径配置文件: {configFilePath}"); // 如果项目路径下没有配置文件,检查应用程序目录 if (!File.Exists(configFilePath)) { configFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config", "menu_config.csv"); WriteLog($"项目路径配置文件不存在,检查应用程序目录: {configFilePath}"); } } catch (UnauthorizedAccessException accessEx) { string errorMsg = $"项目路径访问权限不足: {accessEx.Message}"; WriteLog($"{errorMsg} - {accessEx.StackTrace}"); Console.WriteLine(errorMsg); // 回退到应用程序目录 configFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config", "menu_config.csv"); WriteLog($"回退到应用程序目录: {configFilePath}"); } } else { // 直接检查应用程序目录下的配置文件 configFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config", "menu_config.csv"); WriteLog($"检查应用程序目录配置文件: {configFilePath}"); } // 如果配置文件不存在,直接返回 if (!File.Exists(configFilePath)) { string infoMsg = $"菜单配置文件不存在,使用默认配置: {configFilePath}"; Console.WriteLine(infoMsg); WriteLog(infoMsg); return; } // 读取CSV文件内容 string[] lines = null; try { lines = File.ReadAllLines(configFilePath, Encoding.UTF8); WriteLog($"成功读取配置文件,共{lines.Length}行"); } catch (IOException ioEx) { string errorMsg = $"配置文件读取失败: {ioEx.Message}"; WriteLog($"{errorMsg} - {ioEx.StackTrace}"); Console.WriteLine(errorMsg); return; } catch (UnauthorizedAccessException accessEx) { string errorMsg = $"配置文件访问权限不足: {accessEx.Message}"; WriteLog($"{errorMsg} - {accessEx.StackTrace}"); Console.WriteLine(errorMsg); return; } // 创建菜单状态字典 Dictionary> menuStates = new Dictionary>(); // 解析CSV内容 try { for (int i = 1; i < lines.Length; i++) // 跳过标题行 { string line = lines[i]; if (string.IsNullOrWhiteSpace(line)) { WriteLog($"警告: 配置文件第{i + 1}行为空,已跳过"); continue; } try { // 简单的CSV解析,这里假设没有包含逗号的字段 string[] parts = line.Split(','); if (parts.Length >= 3) { string menuName = parts[0]; string status = parts[1]; string isChecked = parts[2]; menuStates[menuName] = new Dictionary { { "status", status }, { "checked", isChecked } }; } else { WriteLog($"警告: 配置文件第{i + 1}行格式不正确,字段数量不足: {line}"); } } catch (Exception ex) { WriteLog($"解析配置文件第{i + 1}行时出错: {ex.Message} - {ex.StackTrace}"); // 继续处理下一行,不中断整个解析过程 } } WriteLog($"成功解析配置文件,共加载{menuStates.Count}个菜单状态"); } catch (Exception ex) { string errorMsg = $"配置文件解析过程中发生错误: {ex.Message}"; WriteLog($"{errorMsg} - {ex.StackTrace}"); Console.WriteLine(errorMsg); return; } // 应用菜单状态 try { // 暂停检测菜单 if (menuStates.ContainsKey("pauseDetection") && pauseDetectionToolStripMenuItem != null) { try { _isPaused = bool.Parse(menuStates["pauseDetection"]["checked"]); pauseDetectionToolStripMenuItem.Text = menuStates["pauseDetection"]["status"]; WriteLog($"应用暂停检测菜单状态: {_isPaused}"); } catch (FormatException ex) { WriteLog($"暂停检测菜单状态格式错误: {ex.Message}"); } } // 日志保存菜单 if (menuStates.ContainsKey("saveLog") && saveLogToolStripMenuItem != null) { try { saveLogToolStripMenuItem.Checked = bool.Parse(menuStates["saveLog"]["checked"]); saveLogToolStripMenuItem.Text = menuStates["saveLog"]["status"]; // 如果有DeviceManager实例,同步状态 if (_deviceManager != null) { DeviceManager.LogToFile = saveLogToolStripMenuItem.Checked; WriteLog($"同步日志保存状态到DeviceManager: {saveLogToolStripMenuItem.Checked}"); } WriteLog($"应用日志保存菜单状态: {saveLogToolStripMenuItem.Checked}"); } catch (FormatException ex) { WriteLog($"日志保存菜单状态格式错误: {ex.Message}"); } } // 温度显示菜单 - 添加空值检查 if (globalTemperatureToolStripMenuItem != null) ApplyMenuState(menuStates, "globalTemperature", globalTemperatureToolStripMenuItem); if (areaTemperatureToolStripMenuItem != null) ApplyMenuState(menuStates, "areaTemperature", areaTemperatureToolStripMenuItem); if (maxTemperatureToolStripMenuItem != null) ApplyMenuState(menuStates, "maxTemperature", maxTemperatureToolStripMenuItem); if (avgTemperatureToolStripMenuItem != null) ApplyMenuState(menuStates, "avgTemperature", avgTemperatureToolStripMenuItem); if (minTemperatureToolStripMenuItem != null) ApplyMenuState(menuStates, "minTemperature", minTemperatureToolStripMenuItem); // 同步菜单状态到对应的私有字段,确保与显示逻辑一致 if (globalTemperatureToolStripMenuItem != null) _showGlobalTemperature = globalTemperatureToolStripMenuItem.Checked; if (areaTemperatureToolStripMenuItem != null) _showAreaTemperature = areaTemperatureToolStripMenuItem.Checked; if (maxTemperatureToolStripMenuItem != null) _showMaxTemperature = maxTemperatureToolStripMenuItem.Checked; if (avgTemperatureToolStripMenuItem != null) _showAverageTemperature = avgTemperatureToolStripMenuItem.Checked; if (minTemperatureToolStripMenuItem != null) _showMinTemperature = minTemperatureToolStripMenuItem.Checked; // 处理温度显示菜单项的互斥逻辑 if (_showGlobalTemperature && _showAreaTemperature) { // 如果两者都被选中,默认保持全局温度选中,取消区域温度 _showAreaTemperature = false; if (areaTemperatureToolStripMenuItem != null) areaTemperatureToolStripMenuItem.Checked = false; } // 更新实时温度信息显示,只在非暂停状态下调用 if (!_isPaused) { UpdateRealTimeInfoOnUI(); } // 色彩模式菜单 - 添加空值检查 if (whiteHotToolStripMenuItem != null) ApplyMenuState(menuStates, "whiteHot", whiteHotToolStripMenuItem); if (blackHotToolStripMenuItem != null) ApplyMenuState(menuStates, "blackHot", blackHotToolStripMenuItem); if (ironRedToolStripMenuItem != null) ApplyMenuState(menuStates, "ironRed", ironRedToolStripMenuItem); if (lavaToolStripMenuItem != null) ApplyMenuState(menuStates, "lava", lavaToolStripMenuItem); if (rainbowToolStripMenuItem != null) ApplyMenuState(menuStates, "rainbow", rainbowToolStripMenuItem); if (ironGrayToolStripMenuItem != null) ApplyMenuState(menuStates, "ironGray", ironGrayToolStripMenuItem); if (redHotToolStripMenuItem != null) ApplyMenuState(menuStates, "redHot", redHotToolStripMenuItem); if (rainbow2ToolStripMenuItem != null) ApplyMenuState(menuStates, "rainbow2", rainbow2ToolStripMenuItem); // 应用选中的色彩模式到设备 if (_deviceManager != null) { try { if (whiteHotToolStripMenuItem != null && whiteHotToolStripMenuItem.Checked) { _deviceManager.SetPaletteType(PaletteType.WhiteHot); Console.WriteLine("应用白热色彩模式到设备"); } else if (blackHotToolStripMenuItem != null && blackHotToolStripMenuItem.Checked) { _deviceManager.SetPaletteType(PaletteType.BlackHot); Console.WriteLine("应用黑热色彩模式到设备"); } else if (ironRedToolStripMenuItem != null && ironRedToolStripMenuItem.Checked) { _deviceManager.SetPaletteType(PaletteType.IronRed); Console.WriteLine("应用铁红色彩模式到设备"); } else if (lavaToolStripMenuItem != null && lavaToolStripMenuItem.Checked) { _deviceManager.SetPaletteType(PaletteType.Lava); Console.WriteLine("应用熔岩色彩模式到设备"); } else if (rainbowToolStripMenuItem != null && rainbowToolStripMenuItem.Checked) { _deviceManager.SetPaletteType(PaletteType.Rainbow); Console.WriteLine("应用彩虹色彩模式到设备"); } else if (ironGrayToolStripMenuItem != null && ironGrayToolStripMenuItem.Checked) { _deviceManager.SetPaletteType(PaletteType.IronGray); Console.WriteLine("应用铁灰色彩模式到设备"); } else if (redHotToolStripMenuItem != null && redHotToolStripMenuItem.Checked) { _deviceManager.SetPaletteType(PaletteType.RedHot); Console.WriteLine("应用红热色彩模式到设备"); } else if (rainbow2ToolStripMenuItem != null && rainbow2ToolStripMenuItem.Checked) { _deviceManager.SetPaletteType(PaletteType.Rainbow2); Console.WriteLine("应用彩虹2色彩模式到设备"); } } catch (Exception ex) { Console.WriteLine($"应用色彩模式到设备失败: {ex.Message}"); } } // 图像模式菜单 - 添加空值检查 if (thermalModeToolStripMenuItem != null) ApplyMenuState(menuStates, "thermalMode", thermalModeToolStripMenuItem); if (visibleModeToolStripMenuItem != null) ApplyMenuState(menuStates, "visibleMode", visibleModeToolStripMenuItem); // 应用选中的图像模式到设备 if (_deviceManager != null) { try { if (thermalModeToolStripMenuItem != null && thermalModeToolStripMenuItem.Checked) { _deviceManager.SetImageMode(ImageMode.Infrared); Console.WriteLine("应用红外图像模式到设备"); } else if (visibleModeToolStripMenuItem != null && visibleModeToolStripMenuItem.Checked) { _deviceManager.SetImageMode(ImageMode.Natural); Console.WriteLine("应用自然图像模式到设备"); } } catch (Exception ex) { Console.WriteLine($"应用图像模式到设备失败: {ex.Message}"); } } string successMsg = "菜单配置已从CSV文件成功加载并应用"; Console.WriteLine(successMsg); WriteLog(successMsg); } catch (Exception ex) { string errorMsg = $"应用菜单状态过程中发生错误: {ex.Message}"; WriteLog($"{errorMsg} - {ex.StackTrace}"); Console.WriteLine(errorMsg); } } catch (Exception ex) { // 捕获未预期的异常 string errorMsg = $"加载菜单配置过程中发生未预期的错误: {ex.Message}"; WriteLog($"{errorMsg} - {ex.StackTrace}"); Console.WriteLine(errorMsg); } finally { WriteLog("菜单配置加载操作完成"); } } /// /// 应用菜单状态的辅助方法 /// /// 菜单状态字典 /// 菜单名称 /// 菜单项控件 private void ApplyMenuState(Dictionary> menuStates, string menuName, ToolStripMenuItem menuItem) { try { // 参数验证 if (menuStates == null) { WriteLog($"警告: 应用菜单状态时,menuStates参数为null,菜单名称: {menuName}"); return; } if (string.IsNullOrEmpty(menuName)) { WriteLog("警告: 应用菜单状态时,menuName参数为空"); return; } if (menuItem == null) { WriteLog($"警告: 应用菜单状态时,菜单项为null,菜单名称: {menuName}"); return; } // 检查菜单状态字典中是否包含该菜单 if (menuStates.ContainsKey(menuName)) { WriteLog($"应用菜单状态: {menuName}"); try { // 尝试解析和应用Checked状态 string checkedValue = menuStates[menuName].ContainsKey("checked") ? menuStates[menuName]["checked"] : "false"; bool isChecked = bool.Parse(checkedValue); menuItem.Checked = isChecked; WriteLog($"菜单 {menuName} 选中状态已设置为: {isChecked}"); } catch (FormatException ex) { string errorMsg = $"菜单 {menuName} 选中状态格式错误: {ex.Message}"; WriteLog(errorMsg); // 保留默认值,不抛出异常以允许其他菜单状态继续应用 } try { // 尝试设置菜单项文本 if (menuStates[menuName].ContainsKey("status")) { menuItem.Text = menuStates[menuName]["status"]; WriteLog($"菜单 {menuName} 文本已设置为: {menuStates[menuName]["status"]}"); } } catch (Exception ex) { string errorMsg = $"设置菜单 {menuName} 文本时出错: {ex.Message}"; WriteLog(errorMsg); // 保留默认值,不抛出异常以允许其他菜单状态继续应用 } } else { WriteLog($"菜单配置中未找到菜单项: {menuName}"); } } catch (Exception ex) { // 捕获所有其他未预期的异常 string errorMsg = $"应用菜单 {menuName} 状态时发生未预期的错误: {ex.Message}"; WriteLog($"{errorMsg} - {ex.StackTrace}"); // 不重新抛出异常,避免影响其他菜单状态的应用 } } /// /// 写入日志信息到文件 /// 只有在DeviceManager.LogToFile为true时才会保存 /// /// 日志消息 private void WriteLog(string logMessage) { // 只有当日志保存功能启用时才执行写入 // 直接使用DeviceManager.LogToFile静态属性,保持与系统其他部分一致 if (DeviceManager.LogToFile) { try { string logFilePath; // 如果项目路径存在,使用项目路径下的Logs目录 if (!string.IsNullOrEmpty(_projectPath) && Directory.Exists(_projectPath)) { // 创建Logs目录(如果不存在) string logDir = Path.Combine(_projectPath, "Logs"); Directory.CreateDirectory(logDir); // 生成日志文件名(按日期) string logFileName = $"Log_{DateTime.Now:yyyyMMdd}.txt"; logFilePath = Path.Combine(logDir, logFileName); } else { // 如果项目路径不存在,使用应用程序目录下的Log.txt logFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Log.txt"); // 确保目录存在 Directory.CreateDirectory(Path.GetDirectoryName(logFilePath)); } // 写入日志(带时间戳) string logEntry = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] {logMessage}"; File.AppendAllText(logFilePath, logEntry + Environment.NewLine); } catch (Exception ex) { // 记录日志写入失败的异常(但不抛出,以免影响主程序) Console.WriteLine($"写入日志失败: {ex.Message}"); } } } /// /// 更新实时信息显示 /// 实现温度数据及其他实时信息的显示功能 /// 根据README.md要求实现10项功能 /// /// /// 在指定区域内居中显示文本 /// /// 用于绘制的Graphics对象 /// 要显示的文本数组 /// 文本颜色,默认为白色 /// 显示文本的区域,默认为null,此时使用整个图像框 private void DrawTextInAreaCentered(Graphics g, string[] texts, Color? color = null, RectangleF? area = null) { if (g == null || texts == null || texts.Length == 0) return; // 设置默认颜色为白色 Color displayColor = color ?? Color.White; // 创建固定的字体和格式对象 using (Font font = new Font("微软雅黑", 12, FontStyle.Bold)) using (Brush brush = new SolidBrush(displayColor)) { StringFormat format = new StringFormat { Alignment = StringAlignment.Near, // 左对齐 LineAlignment = StringAlignment.Center // 垂直居中 }; // 如果没有指定区域,使用整个图像框作为默认区域 RectangleF displayArea = area ?? new RectangleF(0, 0, _displayImage.Width, _displayImage.Height); // 计算文本区域大小 SizeF[] textSizes = new SizeF[texts.Length]; float maxTextWidth = 0; float totalTextHeight = 0; const float lineSpacing = 5; // 行间距 for (int i = 0; i < texts.Length; i++) { textSizes[i] = g.MeasureString(texts[i], font); maxTextWidth = Math.Max(maxTextWidth, textSizes[i].Width); totalTextHeight += textSizes[i].Height; } // 添加文本间距 totalTextHeight += (texts.Length - 1) * lineSpacing; // 计算文本在区域内的居中位置 float textAreaX = displayArea.X + (displayArea.Width - maxTextWidth) / 2; float textAreaY = displayArea.Y + (displayArea.Height - totalTextHeight) / 2; // 绘制温度文本 float currentY = textAreaY; for (int i = 0; i < texts.Length; i++) { g.DrawString(texts[i], font, brush, textAreaX, currentY, format); currentY += textSizes[i].Height + lineSpacing; // 加上行间距 } } } private void UpdateRealTimeInfoOnUI() { lock (_displayImageLock) { // 1. 以透明色清空DisplayImage // 遵循README要求:中途不进行Dispose和设置为null,只在上面进行绘制 if (_displayImage == null) return; using (Graphics g = Graphics.FromImage(_displayImage)) { // 清除DisplayImage为透明色 g.Clear(Color.Transparent); // 2. 如果没有温度数据或温度数据的时间3秒之前,返回 TemperatureData temperatureData = _deviceManager.LastTemperature; if (temperatureData == null || temperatureData.Timestamp == null) return; TimeSpan timeDiff = DateTime.Now - temperatureData.Timestamp; if (timeDiff.TotalSeconds > 3) return; // 3. 温度显示菜单下如果未勾选区域温度和全局温度,则不显示任何温度信息 if (!_showGlobalTemperature && !_showAreaTemperature) return; // 4. 如果勾选了全局温度且未勾选区域温度,则显示全局温度(居中显示),否则显示区域温度(居中显示) bool isGlobalTemperatureMode = _showGlobalTemperature && !_showAreaTemperature; // 5. 如果勾选了区域温度,则显示区域框和编号,否则不显示区域框 if (_showAreaTemperature) { try { // 使用固定的字体和格式对象绘制区域编号 using (Font font = new Font("微软雅黑", 10, FontStyle.Bold)) { // 遍历已加载的测温区列表,绘制每个区域的框线和编号 foreach (TemperatureZone zone in _loadedTemperatureZones) { // 创建画笔,使用区域的颜色作为框线颜色 using (Pen pen = new Pen(zone.Color, 2)) { // 绘制区域框线 g.DrawRectangle(pen, zone.X, zone.Y, zone.Width, zone.Height); } // 创建画刷,使用区域的颜色作为文字颜色 using (Brush brush = new SolidBrush(zone.Color)) { // 绘制区域编号,编号显示在区域左上角 PointF numberPosition = new PointF(zone.X + 5, zone.Y + 5); g.DrawString(zone.Index.ToString(), font, brush, numberPosition); } } } } catch (Exception ex) { Console.WriteLine($"绘制区域框时发生异常: {ex.Message}"); } } // 6. 只有在全局温度模式下才显示温度数据 if (isGlobalTemperatureMode) { // 准备温度文本 List temperatureTexts = new List(); if (_showAverageTemperature) { temperatureTexts.Add($"平均: {temperatureData.AverageTemperature:F2} °C"); } if (_showMinTemperature) { temperatureTexts.Add($"最低: {temperatureData.MinTemperature:F2} °C"); } if (_showMaxTemperature) { temperatureTexts.Add($"最高: {temperatureData.MaxTemperature:F2} °C"); } // 记录温度数据日志 if (temperatureTexts.Count > 0) { WriteLog($"全局温度数据 - 平均: {temperatureData.AverageTemperature:F2} °C, 最低: {temperatureData.MinTemperature:F2} °C, 最高: {temperatureData.MaxTemperature:F2} °C"); } // 如果没有要显示的温度文本,直接返回 if (temperatureTexts.Count == 0) return; // 将List转换为string[],以便传递给DrawTextInAreaCentered方法 string[] textsArray = temperatureTexts.ToArray(); // 调用DrawTextInAreaCentered方法绘制温度文本 DrawTextInAreaCentered(g, textsArray); } } // 设置显示状态标志 _isDisplayingInfo = true; } // 标记信息正在显示 _isDisplayingInfo = true; } 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) { // 获取已有的温度数据(从DeviceManager缓存中获取) TemperatureData temperatureData = _deviceManager.LastTemperature; if (temperatureData == null) { MessageBox.Show("获取温度数据失败,请确保设备已连接且正在接收数据。", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } // 使用DeviceManager的新重载方法,直接传入已获取的温度数据 bool saveResult = _deviceManager.SaveTemperatureDataToCsv(saveFileDialog.FileName, temperatureData); if (saveResult) { // 显示保存成功消息 MessageBox.Show($"温度数据已成功保存到:\n{saveFileDialog.FileName}", "保存成功", MessageBoxButtons.OK, MessageBoxIcon.Information); } else { // 显示保存失败消息 MessageBox.Show("保存温度数据失败,请检查文件路径是否有效且有写入权限。", "保存失败", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } catch (Exception ex) { // 显示错误消息 MessageBox.Show($"保存温度数据时发生错误:\n{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); Console.WriteLine($"保存温度数据异常: {ex.Message}\n{ex.StackTrace}"); } } /// /// 验证菜单功能是否正常工作 - 用于测试 /// 可以在调试时手动调用此方法来测试菜单配置的保存和加载 /// public void ValidateMenuFunctionality() { try { WriteLog("开始验证菜单功能"); // 1. 保存当前菜单配置 SaveMenuConfig(); WriteLog("菜单配置已保存"); // 2. 模拟菜单状态更改并再次保存 if (globalTemperatureToolStripMenuItem != null) { globalTemperatureToolStripMenuItem.Checked = !globalTemperatureToolStripMenuItem.Checked; WriteLog($"临时更改全局温度菜单状态为: {globalTemperatureToolStripMenuItem.Checked}"); } // 3. 重新加载配置 LoadMenuConfig(); WriteLog("菜单配置已重新加载"); // 4. 输出验证信息 WriteLog("菜单功能验证完成"); Console.WriteLine("菜单功能验证完成: 保存和加载配置正常工作"); } catch (Exception ex) { WriteLog($"验证菜单功能时出错: {ex.Message}"); Console.WriteLine($"验证菜单功能失败: {ex.Message}"); } } /// /// /// imageBox双击事件处理方法 /// 双击后弹出检测配置窗口 /// private void ImageBox_DoubleClick(object sender, EventArgs e) { try { // 显示配置窗口,使用完整命名空间引用Setting类 JoyD.Windows.CS.Setting.Form.ShowDialog(); } catch (Exception ex) { Console.WriteLine($"打开配置窗口时出错: {ex.Message}"); MessageBox.Show($"打开配置窗口时出错: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } }