3498 lines
147 KiB
C#
3498 lines
147 KiB
C#
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
|
||
{
|
||
/// <summary>
|
||
/// 相机控件类,用于显示和处理相机图像
|
||
/// </summary>
|
||
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<Tuple<double, Color>> TemperatureLegend { get; set; }
|
||
public Dictionary<Point, double> PixelTemperatureData { get; set; }
|
||
|
||
public TemperatureDiffConfig()
|
||
{
|
||
TemperatureLegend = new List<Tuple<double, Color>>();
|
||
PixelTemperatureData = new Dictionary<Point, double>();
|
||
}
|
||
}
|
||
|
||
// 设备管理器实例
|
||
private DeviceManager _deviceManager;
|
||
|
||
// 是否正在接收图像
|
||
private bool _isReceivingImage = false;
|
||
|
||
// 全局图像缓冲区bitmap,用于在其上绘制图像和mask信息
|
||
private Bitmap _imageBuffer = null;
|
||
private const int BUFFER_WIDTH = 512;
|
||
|
||
// 右键菜单菜单项
|
||
private System.Windows.Forms.ToolStripMenuItem modifyConfigToolStripMenuItem;
|
||
private const int BUFFER_HEIGHT = 384;
|
||
|
||
// 预览窗口引用
|
||
private JoyD.Windows.CS.preview _previewForm;
|
||
|
||
// 最后接收的图像
|
||
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;
|
||
/// <summary>
|
||
/// 首次ping通设备并更新信息
|
||
/// </summary>
|
||
private volatile bool _isFirst = true;
|
||
|
||
// 项目路径,用于数据文件的存取
|
||
private string _projectPath = "";
|
||
|
||
// 配置是否已经加载的标志位
|
||
private bool _isConfigLoaded = false;
|
||
|
||
// 自动配置标志位,自动加载或保存区域、温度和温差信息
|
||
private bool _autoConfig = true;
|
||
|
||
// 加载的测温区配置
|
||
private readonly List<TemperatureZone> _loadedTemperatureZones = new List<TemperatureZone>();
|
||
|
||
// 加载的温差配置
|
||
private TemperatureDiffConfig _loadedTemperatureDiffConfig = new TemperatureDiffConfig();
|
||
|
||
// 检测区配置
|
||
private DetectionZone _detectionZone = new DetectionZone();
|
||
|
||
/// <summary>
|
||
/// 获取或设置项目路径,控件所需的数据文件将在此目录中进行存取
|
||
/// </summary>
|
||
[Category("配置")]
|
||
[Description("设置项目路径,控件所需的数据文件将在此目录中进行存取")]
|
||
[DefaultValue("")]
|
||
[DisplayName("项目路径")]
|
||
[Editor(typeof(System.Windows.Forms.Design.FolderNameEditor), typeof(System.Drawing.Design.UITypeEditor))]
|
||
public string ProjectPath
|
||
{
|
||
get { return _projectPath; }
|
||
set
|
||
{
|
||
// 只有当值发生变化时才进行同步
|
||
if (_projectPath != value)
|
||
{
|
||
_projectPath = value;
|
||
// 如果DeviceManager已经初始化,则同步更新其ProjectPath属性
|
||
if (_deviceManager != null)
|
||
{
|
||
_deviceManager.ProjectPath = _projectPath;
|
||
}
|
||
|
||
// ProjectPath改变时,无论是否为自动配置,都要加载菜单
|
||
LoadMenuConfig();
|
||
|
||
// 只有当自动配置开启时,才加载配置文件
|
||
if (_autoConfig && !_isConfigLoaded)
|
||
{
|
||
LoadZoneConfig();
|
||
LoadTemperatureDiffConfig();
|
||
// 设置配置已加载标志
|
||
_isConfigLoaded = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取或设置是否自动配置,自动加载或保存区域、温度和温差信息
|
||
/// </summary>
|
||
[Category("配置")]
|
||
[DisplayName("自动配置")]
|
||
[Description("自动加载或保存区域、温度和温差信息")]
|
||
[DefaultValue(true)]
|
||
public bool AutoConfig
|
||
{
|
||
get { return _autoConfig; }
|
||
set
|
||
{
|
||
_autoConfig = value;
|
||
// 更新Setting窗口的自动配置状态
|
||
JoyD.Windows.CS.Setting.Form.AutoConfig = _autoConfig;
|
||
// 如果自动配置开启且配置尚未加载,则加载配置
|
||
if (_autoConfig && !_isConfigLoaded)
|
||
{
|
||
LoadAllConfigs();
|
||
_isConfigLoaded = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 温度类型枚举
|
||
/// </summary>
|
||
public enum TemperatureType
|
||
{
|
||
/// <summary>
|
||
/// 最高温度
|
||
/// </summary>
|
||
Max = 0,
|
||
/// <summary>
|
||
/// 最低温度
|
||
/// </summary>
|
||
Min = 1,
|
||
/// <summary>
|
||
/// 平均温度
|
||
/// </summary>
|
||
Average = 2
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取或设置检测区配置
|
||
/// </summary>
|
||
[Category("配置")]
|
||
[DisplayName("检测区配置")]
|
||
[Description("设置检测区的位置和大小")]
|
||
public DetectionZone CurrentDetectionZone
|
||
{
|
||
get { return _detectionZone; }
|
||
set
|
||
{
|
||
_detectionZone = value;
|
||
// 同时更新DeviceManager的检测区配置
|
||
if (_deviceManager != null)
|
||
{
|
||
_deviceManager.DetectionZone = value;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 关闭相机并完全释放所有资源
|
||
/// </summary>
|
||
[Category("控制")]
|
||
[DisplayName("关闭相机")]
|
||
[Description("关闭相机并完全释放所有资源")]
|
||
public void Close()
|
||
{
|
||
try
|
||
{
|
||
// 停止相机并释放相关资源
|
||
StopCamera();
|
||
|
||
// 关闭设置窗口
|
||
Setting.Form.Close();
|
||
|
||
// 取消注册事件并释放设备管理器
|
||
if (_deviceManager != null)
|
||
{
|
||
// 移除所有事件监听
|
||
_deviceManager.ImageReceived -= DeviceManager_ImageReceived;
|
||
_deviceManager.ConnectionStatusChanged -= DeviceManager_ConnectionStatusChanged;
|
||
_deviceManager.ConnectionException -= DeviceManager_ConnectionException;
|
||
|
||
// 释放设备管理器资源
|
||
_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)
|
||
{
|
||
_imageBuffer.Dispose();
|
||
_imageBuffer = null;
|
||
}
|
||
|
||
// 释放LastImage资源
|
||
lock (_lastImageLock)
|
||
{
|
||
if (_lastImage != null)
|
||
{
|
||
_lastImage.Dispose();
|
||
_lastImage = null;
|
||
}
|
||
}
|
||
|
||
// 释放InfoImage资源
|
||
lock (_infoImageLock)
|
||
{
|
||
if (_infoImage != null)
|
||
{
|
||
_infoImage.Dispose();
|
||
_infoImage = null;
|
||
}
|
||
}
|
||
|
||
// 释放DisplayImage资源
|
||
lock (_displayImageLock)
|
||
{
|
||
if (_displayImage != null)
|
||
{
|
||
_displayImage.Dispose();
|
||
_displayImage = null;
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"关闭相机时出错: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取指定测温区的温度值
|
||
/// </summary>
|
||
/// <param name="tempType">温度类型:0表示最高温,1表示最低温,2表示平均温</param>
|
||
/// <param name="zoneIndex">测温区编号,0表示全局温度</param>
|
||
/// <returns>温度值</returns>
|
||
public float GetAreaTemp(int tempType, int zoneIndex)
|
||
{
|
||
// 检查DeviceManager是否为空或未连接
|
||
if (_deviceManager == null || _deviceManager.ConnectionStatus != ConnectionStatus.Connected)
|
||
{
|
||
return float.NaN;
|
||
}
|
||
|
||
// 获取当前温度数据
|
||
TemperatureData temperatureData = _deviceManager.LastTemperature;
|
||
if (temperatureData == null)
|
||
{
|
||
return float.NaN;
|
||
}
|
||
|
||
// 全局温度
|
||
if (zoneIndex == 0)
|
||
{
|
||
switch (tempType)
|
||
{
|
||
case 0: // 最高温
|
||
return temperatureData.MaxTemperature;
|
||
case 1: // 最低温
|
||
return temperatureData.MinTemperature;
|
||
case 2: // 平均温
|
||
return temperatureData.AverageTemperature;
|
||
default:
|
||
return float.NaN;
|
||
}
|
||
}
|
||
// 特定测温区温度
|
||
else
|
||
{
|
||
if (temperatureData.ZoneTemperatures.TryGetValue(zoneIndex, out var zoneTempData))
|
||
{
|
||
switch (tempType)
|
||
{
|
||
case 0: // 最高温
|
||
return zoneTempData.MaxTemperature;
|
||
case 1: // 最低温
|
||
return zoneTempData.MinTemperature;
|
||
case 2: // 平均温
|
||
return zoneTempData.AverageTemperature;
|
||
default:
|
||
return float.NaN;
|
||
}
|
||
}
|
||
return float.NaN;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 加载测温区配置文件
|
||
/// </summary>
|
||
private void LoadZoneConfig()
|
||
{
|
||
try
|
||
{
|
||
if (string.IsNullOrWhiteSpace(_projectPath)) _projectPath = "";
|
||
|
||
// 配置文件存储在Config子目录中
|
||
string configDir = Path.Combine(_projectPath, "Config");
|
||
|
||
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}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 加载温差配置文件
|
||
/// </summary>
|
||
private void LoadTemperatureDiffConfig()
|
||
{
|
||
try
|
||
{
|
||
if (string.IsNullOrWhiteSpace(_projectPath)) _projectPath = "";
|
||
|
||
// 配置文件存储在Config子目录中
|
||
string configDir = Path.Combine(_projectPath, "Config");
|
||
|
||
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<double, Color>(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}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 加载所有配置文件
|
||
/// </summary>
|
||
public void LoadAllConfigs()
|
||
{
|
||
LoadMenuConfig();
|
||
LoadZoneConfig();
|
||
LoadTemperatureDiffConfig();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新设计模式状态到DeviceManager
|
||
/// </summary>
|
||
private void UpdateDesignModeStatus()
|
||
{
|
||
DeviceManager.IsDesignMode = DesignMode;
|
||
Console.WriteLine($"相机控件设计模式状态已更新: {DesignMode}");
|
||
}
|
||
/// <summary>
|
||
/// 更新InfoImage显示 - 按照用户要求的详细步骤:
|
||
/// 1. 以透明色清空Info
|
||
/// 2. 如果暂停,显示暂停信息,否则如果Ping不通或断开,显示重连信息,否则满足就绪条件
|
||
/// 3. 在就绪条件下,如果有温度数据,显示最高温度
|
||
/// 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 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);
|
||
}
|
||
Console.WriteLine($"当前状态:{text}");
|
||
}
|
||
}
|
||
|
||
// 设置显示标志
|
||
_isDisplayingInfo = isPaused || isDisconnected || isReconnecting || _showGlobalTemperature || _showAreaTemperature;
|
||
|
||
// 步骤3:无论就绪状态如何,都调用更新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
|
||
{
|
||
// 记录操作前的暂停状态
|
||
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}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 相机控件构造函数
|
||
/// </summary>
|
||
public Camera()
|
||
{
|
||
InitializeComponent();
|
||
|
||
// 为右键菜单添加Opening事件,用于在菜单显示前更新色彩模式的选中状态
|
||
this.contextMenuStrip1.Opening += ContextMenuStrip1_Opening;
|
||
|
||
// 初始化日志保存状态,确保与菜单项状态同步
|
||
// 直接使用DeviceManager.LogToFile静态属性控制日志记录,无需额外的_saveLogEnabled字段
|
||
saveLogToolStripMenuItem.Checked = DeviceManager.LogToFile;
|
||
|
||
// 将设计模式状态传递给DeviceManager
|
||
UpdateDesignModeStatus();
|
||
|
||
// 初始化Ping定时器
|
||
_pingTimer = new System.Threading.Timer(PingTimer_Tick, null, Timeout.Infinite, Timeout.Infinite);
|
||
|
||
InitializeImageBuffer();
|
||
// 异步初始化图像缓冲区和加载菜单配置,避免阻塞UI线程
|
||
ThreadPool.QueueUserWorkItem(delegate
|
||
{
|
||
try
|
||
{
|
||
LoadMenuConfig();
|
||
Console.WriteLine("图像缓冲区和菜单配置已异步初始化完成");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"异步初始化失败: {ex.Message}");
|
||
}
|
||
});
|
||
|
||
// 只有在非设计模式下才初始化设备管理器和错误定时器
|
||
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
|
||
{
|
||
Console.WriteLine("正在加载并启动相机");
|
||
// 启动设备Ping
|
||
StartDevicePing();
|
||
// 延迟启动相机,避免界面卡顿
|
||
ThreadPool.QueueUserWorkItem(delegate
|
||
{
|
||
while (!IsDevicePingable)
|
||
Thread.Sleep(3000); // 延迟3秒后启动
|
||
_isFirst = false;
|
||
|
||
// 确保窗口句柄已经创建,避免Invoke时抛出异常
|
||
while (!this.IsHandleCreated)
|
||
Thread.Sleep(100); // 等待窗口句柄创建
|
||
|
||
// 使用Invoke或BeginInvoke调用UI线程操作
|
||
if (this.IsHandleCreated)
|
||
{
|
||
this.BeginInvoke(new Action(() =>
|
||
{
|
||
try
|
||
{
|
||
StartCamera();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ShowError($"自动启动相机失败: {ex.Message}");
|
||
}
|
||
}));
|
||
}
|
||
});
|
||
}
|
||
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;
|
||
|
||
// 异步加载配置文件,避免阻塞主线程
|
||
if (!_isConfigLoaded)
|
||
{
|
||
ThreadPool.QueueUserWorkItem(delegate
|
||
{
|
||
try
|
||
{
|
||
LoadAllConfigs();
|
||
_isConfigLoaded = true;
|
||
Console.WriteLine("配置文件已异步加载完成");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"异步加载配置文件失败: {ex.Message}");
|
||
}
|
||
});
|
||
}
|
||
|
||
// 注册图像接收事件
|
||
_deviceManager.ImageReceived += DeviceManager_ImageReceived;
|
||
|
||
// 注册连接状态变更事件
|
||
_deviceManager.ConnectionStatusChanged += DeviceManager_ConnectionStatusChanged;
|
||
|
||
// 注册连接异常事件
|
||
_deviceManager.ConnectionException += DeviceManager_ConnectionException;
|
||
|
||
// 不再需要温度数据实时通知,移除事件订阅
|
||
}
|
||
}
|
||
|
||
/// <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");
|
||
}
|
||
|
||
// 初始化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;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 启动相机
|
||
/// </summary>
|
||
public void StartCamera()
|
||
{
|
||
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方式接收图像");
|
||
// 记录日志
|
||
WriteLog("开始使用HTTP方式接收图像");
|
||
// 直接调用HTTP方式的图像接收
|
||
_deviceManager.StartImageReceiving();
|
||
|
||
// 不再需要温度数据实时通知机制,移除相关代码
|
||
_isReceivingImage = true;
|
||
WriteLog("图像接收已启动");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ShowError($"开始接收图像失败: {ex.Message}");
|
||
WriteLog($"开始接收图像失败: {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}");
|
||
}
|
||
}
|
||
|
||
// 温度数据相关变量已移除,不再需要温度数据实时通知机制
|
||
|
||
// 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通")}");
|
||
// 按照README中要求的修改流程第3点和第6点:Ping通状态变化时,只在非暂停状态下调用更新Info
|
||
if (!_isPaused)
|
||
{
|
||
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中要求:图像更新时,保存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}");
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/// <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;
|
||
}
|
||
|
||
// 调用温度显示更新方法
|
||
// 在就绪条件下,调用更新实时信息
|
||
bool isReady = _deviceManager != null && (_isFirst || (!_isPaused && IsDevicePingable && _deviceManager.ConnectionStatus == ConnectionStatus.Connected));
|
||
if (isReady)
|
||
{
|
||
UpdateRealTimeInfoOnUI();
|
||
}
|
||
|
||
Image lastImage = null;
|
||
Image infoImage = null;
|
||
Image oldImage = null;
|
||
Bitmap tempImage = null;
|
||
|
||
try
|
||
{
|
||
// 检查图像缓冲区是否有效
|
||
if (_imageBuffer == null)
|
||
{
|
||
InitializeImageBuffer();
|
||
if (_imageBuffer == null)
|
||
{
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 保存旧图像引用,以便在设置新图像后释放
|
||
oldImage = imageBox.Image;
|
||
|
||
if (!_isFirst)
|
||
{
|
||
// 获取当前的LastImage引用
|
||
lock (_lastImageLock)
|
||
{
|
||
if (_lastImage == null)
|
||
{
|
||
return;
|
||
}
|
||
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;
|
||
// 通知预览窗口更新图像
|
||
if (_previewForm != null && !_previewForm.IsDisposed)
|
||
{
|
||
_previewForm.UpdateImage(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);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <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;
|
||
}
|
||
|
||
// 捕获所有可能的异常,确保事件处理器不会崩溃
|
||
try
|
||
{
|
||
// 首先检查控件状态
|
||
if (this.IsDisposed || this.Disposing)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 线程安全处理 - 确保在UI线程上更新
|
||
if (this.InvokeRequired)
|
||
{
|
||
try
|
||
{
|
||
// 使用BeginInvoke代替Invoke,避免可能的死锁问题
|
||
this.BeginInvoke(new Action<ConnectionStatusChangedEventArgs>(args =>
|
||
{
|
||
// 再次检查控件状态,防止在异步调用期间控件被释放
|
||
if (!this.IsDisposed && !this.Disposing)
|
||
{
|
||
HandleConnectionStatusChanged(args);
|
||
}
|
||
}), 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.BeginInvoke(new Action<ConnectionStatusChangedEventArgs>(HandleConnectionStatusChanged), e);
|
||
return;
|
||
}
|
||
|
||
// 更新UI状态
|
||
UpdateUIState(e.Status == ConnectionStatus.Connected);
|
||
|
||
// 检查_deviceManager是否为空
|
||
if (_deviceManager == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
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 (_isFirst || !_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 (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 色彩模式切换方法
|
||
|
||
/// <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("切换到红外模式失败");
|
||
}
|
||
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相关方法
|
||
|
||
/// <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.BeginInvoke(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, 1000); // 1秒超时,与DeviceManager保持一致
|
||
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点:Ping通状态变化时,修改Ping状态
|
||
// 注意:UpdateInfo的调用已在IsDevicePingable的setter中实现(只在非暂停状态下)
|
||
IsDevicePingable = isPingable;
|
||
}
|
||
|
||
/// <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);
|
||
}
|
||
}
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存温度菜单项点击事件处理程序
|
||
/// </summary>
|
||
/// <param name="sender">事件发送者</param>
|
||
/// <param name="e">事件参数</param>
|
||
/// <summary>
|
||
/// 全局温度菜单项点击事件处理
|
||
/// 当全局温度被勾选时,自动取消区域温度的勾选
|
||
/// </summary>
|
||
private void GlobalTemperatureToolStripMenuItem_Click(object sender, EventArgs e)
|
||
{
|
||
// 更新全局温度显示状态标志
|
||
_showGlobalTemperature = globalTemperatureToolStripMenuItem.Checked;
|
||
|
||
// 当全局温度被勾选时,自动取消区域温度的勾选
|
||
if (_showGlobalTemperature)
|
||
{
|
||
_showAreaTemperature = false;
|
||
areaTemperatureToolStripMenuItem.Checked = false;
|
||
}
|
||
|
||
// 修改流程第6点:数据显示菜单勾选变化时,只在非暂停状态下调用更新实时信息
|
||
if (!_isPaused)
|
||
{
|
||
UpdateRealTimeInfoOnUI();
|
||
}
|
||
|
||
// 菜单状态变更时自动静默保存配置
|
||
SaveMenuConfig();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 区域温度菜单项点击事件处理
|
||
/// 当区域温度被勾选时,自动取消全局温度的勾选
|
||
/// </summary>
|
||
private void AreaTemperatureToolStripMenuItem_Click(object sender, EventArgs e)
|
||
{
|
||
// 更新区域温度显示状态标志
|
||
_showAreaTemperature = areaTemperatureToolStripMenuItem.Checked;
|
||
|
||
// 当区域温度被勾选时,自动取消全局温度的勾选
|
||
if (_showAreaTemperature)
|
||
{
|
||
_showGlobalTemperature = false;
|
||
globalTemperatureToolStripMenuItem.Checked = false;
|
||
}
|
||
|
||
// 修改流程第6点:数据显示菜单勾选变化时,只在非暂停状态下调用更新实时信息
|
||
if (!_isPaused)
|
||
{
|
||
UpdateRealTimeInfoOnUI();
|
||
}
|
||
|
||
// 菜单状态变更时自动静默保存配置
|
||
SaveMenuConfig();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 最高温度菜单项点击事件处理
|
||
/// </summary>
|
||
private void MaxTemperatureToolStripMenuItem_Click(object sender, EventArgs e)
|
||
{
|
||
// 更新最高温度显示状态标志
|
||
_showMaxTemperature = maxTemperatureToolStripMenuItem.Checked;
|
||
|
||
// 修改流程第6点:数据显示菜单勾选变化时,只在非暂停状态下调用更新实时信息
|
||
if (!_isPaused)
|
||
{
|
||
UpdateRealTimeInfoOnUI();
|
||
}
|
||
|
||
// 菜单状态变更时自动静默保存配置
|
||
SaveMenuConfig();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 平均温度菜单项点击事件处理
|
||
/// </summary>
|
||
private void AvgTemperatureToolStripMenuItem_Click(object sender, EventArgs e)
|
||
{
|
||
// 更新平均温度显示状态标志
|
||
_showAverageTemperature = avgTemperatureToolStripMenuItem.Checked;
|
||
|
||
// 修改流程第6点:数据显示菜单勾选变化时,只在非暂停状态下调用更新实时信息
|
||
if (!_isPaused)
|
||
{
|
||
UpdateRealTimeInfoOnUI();
|
||
}
|
||
|
||
// 菜单状态变更时自动静默保存配置
|
||
SaveMenuConfig();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 最低温度菜单项点击事件处理
|
||
/// </summary>
|
||
private void MinTemperatureToolStripMenuItem_Click(object sender, EventArgs e)
|
||
{
|
||
// 更新最低温度显示状态标志
|
||
_showMinTemperature = minTemperatureToolStripMenuItem.Checked;
|
||
|
||
// 修改流程第6点:数据显示菜单勾选变化时,只在非暂停状态下调用更新实时信息
|
||
if (!_isPaused)
|
||
{
|
||
UpdateRealTimeInfoOnUI();
|
||
}
|
||
|
||
// 菜单状态变更时自动静默保存配置
|
||
SaveMenuConfig();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存日志菜单项点击事件处理
|
||
/// </summary>
|
||
private void SaveLogToolStripMenuItem_Click(object sender, EventArgs e)
|
||
{
|
||
// 更新DeviceManager.LogToFile静态属性,这是控制所有日志记录的单一来源
|
||
DeviceManager.LogToFile = saveLogToolStripMenuItem.Checked;
|
||
|
||
// 可以在这里添加日志记录的初始化或清理逻辑
|
||
if (DeviceManager.LogToFile)
|
||
{
|
||
// 日志启用时的初始化
|
||
WriteLog("日志保存功能已启用");
|
||
}
|
||
else
|
||
{
|
||
// 日志禁用时的清理(如果需要)
|
||
WriteLog("日志保存功能已禁用");
|
||
}
|
||
|
||
// 菜单状态变更时自动静默保存配置
|
||
SaveMenuConfig();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存菜单配置到CSV文件
|
||
/// </summary>
|
||
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}");
|
||
|
||
// 保存检测区配置
|
||
csvContent.AppendLine(); // 添加空行
|
||
csvContent.AppendLine($"detectionZone,{_detectionZone.X},{_detectionZone.Y},{_detectionZone.Width},{_detectionZone.Height},{ColorTranslator.ToHtml(_detectionZone.Color)}");
|
||
}
|
||
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("菜单配置保存操作完成");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从CSV文件加载菜单配置
|
||
/// </summary>
|
||
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<string, Dictionary<string, string>> menuStates = new Dictionary<string, Dictionary<string, string>>();
|
||
|
||
// 解析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];
|
||
|
||
// 检测区配置处理
|
||
if (menuName == "detectionZone")
|
||
{
|
||
if (parts.Length >= 6)
|
||
{
|
||
int x = int.Parse(parts[1]);
|
||
int y = int.Parse(parts[2]);
|
||
int width = int.Parse(parts[3]);
|
||
int height = int.Parse(parts[4]);
|
||
// 解析检测区颜色,支持颜色名称和十六进制颜色代码
|
||
Color color = CommonUtils.ParseColor(parts[5]);
|
||
|
||
_detectionZone = new DetectionZone
|
||
{
|
||
X = x,
|
||
Y = y,
|
||
Width = width,
|
||
Height = height,
|
||
Color = color
|
||
};
|
||
WriteLog($"成功加载检测区配置: X={x}, Y={y}, Width={width}, Height={height}, Color={color.Name}");
|
||
}
|
||
continue;
|
||
}
|
||
|
||
string status = parts[1];
|
||
string isChecked = parts[2];
|
||
|
||
menuStates[menuName] = new Dictionary<string, string>
|
||
{
|
||
{ "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("菜单配置加载操作完成");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 应用菜单状态的辅助方法
|
||
/// </summary>
|
||
/// <param name="menuStates">菜单状态字典</param>
|
||
/// <param name="menuName">菜单名称</param>
|
||
/// <param name="menuItem">菜单项控件</param>
|
||
private void ApplyMenuState(Dictionary<string, Dictionary<string, string>> 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}");
|
||
// 不重新抛出异常,避免影响其他菜单状态的应用
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 写入日志信息到文件
|
||
/// 只有在DeviceManager.LogToFile为true时才会保存
|
||
/// </summary>
|
||
/// <param name="logMessage">日志消息</param>
|
||
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}");
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新实时信息显示
|
||
/// 实现温度数据及其他实时信息的显示功能
|
||
/// 根据README.md要求实现10项功能
|
||
/// </summary>
|
||
/// <summary>
|
||
/// 在指定区域内居中显示文本
|
||
/// </summary>
|
||
/// <param name="g">用于绘制的Graphics对象</param>
|
||
/// <param name="texts">要显示的文本数组</param>
|
||
/// <param name="color">文本颜色,默认为白色</param>
|
||
/// <param name="area">显示文本的区域,默认为null,此时使用整个图像框</param>
|
||
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.Near // 顶部对齐,由代码控制垂直居中
|
||
};
|
||
|
||
// 如果没有指定区域,使用整个图像框作为默认区域
|
||
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);
|
||
|
||
if (_deviceManager.CurrentImageMode == ImageMode.Infrared)
|
||
{
|
||
// 1. 先绘制区域框线,不受温度数据影响,只受显示设置影响
|
||
if (_showAreaTemperature)
|
||
{
|
||
try
|
||
{
|
||
// 使用固定的字体和格式对象绘制区域编号
|
||
using (Font font = new Font("微软雅黑", 10, FontStyle.Bold))
|
||
{
|
||
// 遍历已加载的测温区列表,绘制每个区域的框线和编号
|
||
foreach (TemperatureZone zone in _loadedTemperatureZones)
|
||
{
|
||
// 将相对坐标转换为绝对坐标
|
||
int absoluteX = _detectionZone.X + zone.X;
|
||
int absoluteY = _detectionZone.Y + zone.Y;
|
||
|
||
// 创建画笔,使用区域的颜色作为框线颜色
|
||
using (Pen pen = new Pen(zone.Color, 2))
|
||
{
|
||
// 绘制区域框线
|
||
g.DrawRectangle(pen, absoluteX, absoluteY, zone.Width, zone.Height);
|
||
}
|
||
|
||
// 创建画刷,使用区域的颜色作为文字颜色
|
||
using (Brush brush = new SolidBrush(zone.Color))
|
||
{
|
||
// 绘制区域编号,编号显示在区域左上角
|
||
PointF numberPosition = new PointF(absoluteX + 5, absoluteY + 5);
|
||
g.DrawString(zone.Index.ToString(), font, brush, numberPosition);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"绘制区域框时发生异常: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// 2. 温度数据绘制逻辑,仍受温度数据有效性影响
|
||
TemperatureData temperatureData = _deviceManager.LastTemperature;
|
||
if (temperatureData != null && temperatureData.Timestamp != null)
|
||
{
|
||
TimeSpan timeDiff = DateTime.Now - temperatureData.Timestamp;
|
||
if (timeDiff.TotalSeconds <= 3)
|
||
{
|
||
// 3. 温度显示菜单下如果未勾选区域温度和全局温度,则不显示任何温度信息
|
||
if (!_showGlobalTemperature && !_showAreaTemperature)
|
||
return;
|
||
|
||
// 4. 如果勾选了全局温度且未勾选区域温度,则显示全局温度(居中显示),否则显示区域温度(居中显示)
|
||
bool isGlobalTemperatureMode = _showGlobalTemperature && !_showAreaTemperature;
|
||
|
||
// 5. 根据温度模式显示温度数据
|
||
if (isGlobalTemperatureMode)
|
||
{
|
||
// 准备温度文本
|
||
List<string> temperatureTexts = new List<string>();
|
||
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>转换为string[],以便传递给DrawTextInAreaCentered方法
|
||
string[] textsArray = temperatureTexts.ToArray();
|
||
|
||
// 调用DrawTextInAreaCentered方法绘制温度文本
|
||
DrawTextInAreaCentered(g, textsArray);
|
||
}
|
||
else if (_showAreaTemperature)
|
||
{
|
||
// 区域温度模式:为每个区域计算并显示温度数据
|
||
foreach (TemperatureZone zone in _loadedTemperatureZones)
|
||
{
|
||
// 准备温度文本
|
||
List<string> areaTemperatureTexts = new List<string>();
|
||
|
||
// 直接从ZoneTemperatures字典获取已计算好的区域温度数据
|
||
if (temperatureData.ZoneTemperatures.TryGetValue(zone.Index, out var zoneTempData))
|
||
{
|
||
if (_showMaxTemperature)
|
||
{
|
||
areaTemperatureTexts.Add($"最高: {zoneTempData.MaxTemperature:F2} °C");
|
||
}
|
||
if (_showMinTemperature)
|
||
{
|
||
areaTemperatureTexts.Add($"最低: {zoneTempData.MinTemperature:F2} °C");
|
||
}
|
||
if (_showAverageTemperature)
|
||
{
|
||
areaTemperatureTexts.Add($"平均: {zoneTempData.AverageTemperature:F2} °C");
|
||
}
|
||
}
|
||
|
||
// 将相对坐标转换为绝对坐标
|
||
int absoluteX = _detectionZone.X + zone.X;
|
||
int absoluteY = _detectionZone.Y + zone.Y;
|
||
|
||
// 如果有温度文本需要绘制
|
||
if (areaTemperatureTexts.Count > 0)
|
||
{
|
||
// 创建表示测温区的矩形区域
|
||
RectangleF zoneArea = new RectangleF(absoluteX, absoluteY, zone.Width, zone.Height);
|
||
|
||
// 将List<string>转换为string[],以便传递给DrawTextInAreaCentered方法
|
||
string[] textsArray = areaTemperatureTexts.ToArray();
|
||
|
||
// 调用DrawTextInAreaCentered方法绘制温度文本,文本会在区域内垂直居中
|
||
DrawTextInAreaCentered(g, textsArray, zone.Color, zoneArea);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 设置显示状态标志
|
||
_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}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证菜单功能是否正常工作 - 用于测试
|
||
/// 可以在调试时手动调用此方法来测试菜单配置的保存和加载
|
||
/// </summary>
|
||
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}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// imageBox双击事件处理方法
|
||
/// 双击后弹出预览窗口
|
||
/// </summary>
|
||
private void ImageBox_DoubleClick(object sender, EventArgs e)
|
||
{
|
||
try
|
||
{
|
||
// 关闭已存在的预览窗口
|
||
if (_previewForm != null && !_previewForm.IsDisposed)
|
||
{
|
||
_previewForm.Close();
|
||
}
|
||
|
||
// 创建新的预览窗口实例
|
||
_previewForm = new JoyD.Windows.CS.preview();
|
||
// 设置初始图像
|
||
_previewForm.UpdateImage(_lastImage);
|
||
// 模态显示预览窗口
|
||
_previewForm.ShowDialog();
|
||
// 预览窗口关闭后清空引用
|
||
_previewForm = null;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"打开预览窗口时出错: {ex.Message}");
|
||
MessageBox.Show($"打开预览窗口时出错: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// "修改配置"菜单项点击事件处理方法
|
||
/// 点击后弹出检测配置窗口
|
||
/// </summary>
|
||
private void ModifyConfigToolStripMenuItem_Click(object sender, EventArgs e)
|
||
{
|
||
try
|
||
{
|
||
// 显示配置窗口,使用完整命名空间引用Setting类
|
||
JoyD.Windows.CS.Setting.Form.AutoConfig = this.AutoConfig;
|
||
JoyD.Windows.CS.Setting.Form.ProjectPath = this.ProjectPath;
|
||
JoyD.Windows.CS.Setting.Form.CurrentDetectionZone = this.CurrentDetectionZone;
|
||
JoyD.Windows.CS.Setting.Form.ShowDialog();
|
||
|
||
// 获取修改后的检测区配置
|
||
this.CurrentDetectionZone = JoyD.Windows.CS.Setting.Form.CurrentDetectionZone;
|
||
// 保存修改后的检测区配置到文件
|
||
SaveMenuConfig();
|
||
|
||
// 重新加载测温区和温差数据,应用新的配置
|
||
LoadZoneConfig();
|
||
LoadTemperatureDiffConfig();
|
||
|
||
// 触发DeviceManager重新加载配置,确保DeviceManager使用最新的测温区配置
|
||
if (_deviceManager != null)
|
||
{
|
||
_deviceManager.ProjectPath = this.ProjectPath;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"打开配置窗口时出错: {ex.Message}");
|
||
MessageBox.Show($"打开配置窗口时出错: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
}
|
||
}
|
||
}
|
||
} |