修改Camera.cs和Camera.Designer.cs,移除不必要的菜单配置保存和ImageBox双击事件处理

This commit is contained in:
zqm
2025-11-12 14:12:19 +08:00
parent ad71cfb6bd
commit 7b1956a047
6 changed files with 749 additions and 291 deletions

View File

@@ -58,7 +58,6 @@ namespace JoyD.Windows.CS.Toprie
this.imageBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
this.imageBox.TabIndex = 0;
this.imageBox.TabStop = false;
this.imageBox.DoubleClick += new System.EventHandler(this.ImageBox_DoubleClick);
//
// contextMenuStrip1
//

View File

@@ -1,9 +1,9 @@
using System;
using System.IO;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
@@ -266,6 +266,9 @@ namespace JoyD.Windows.CS.Toprie
// 修改流程第1点和第5点暂停或恢复时设置暂停状态调用更新Info在暂停状态下会显示暂停信息
UpdateInfo();
// 保存菜单状态
SaveMenuConfig();
}
catch (Exception ex)
{
@@ -290,6 +293,9 @@ namespace JoyD.Windows.CS.Toprie
// 初始化图像缓冲区
InitializeImageBuffer();
// 加载保存的菜单配置
LoadMenuConfig();
// 初始化Ping定时器
_pingTimer = new System.Threading.Timer(PingTimer_Tick, null, Timeout.Infinite, Timeout.Infinite);
@@ -1578,6 +1584,11 @@ namespace JoyD.Windows.CS.Toprie
Console.WriteLine("切换到红外模式失败: " + ex.Message);
ShowError("切换到红外模式失败");
}
finally
{
// 菜单状态需要保存和恢复
SaveMenuConfig();
}
}
private void VisibleModeToolStripMenuItem_Click(object sender, EventArgs e)
@@ -1591,6 +1602,11 @@ namespace JoyD.Windows.CS.Toprie
Console.WriteLine("切换到自然模式失败: " + ex.Message);
ShowError("切换到自然模式失败");
}
finally
{
// 菜单状态需要保存和恢复
SaveMenuConfig();
}
}
#endregion
@@ -1856,6 +1872,9 @@ namespace JoyD.Windows.CS.Toprie
{
UpdateRealTimeInfoOnUI();
}
// 菜单状态变更时自动静默保存配置
SaveMenuConfig();
}
/// <summary>
@@ -1879,6 +1898,9 @@ namespace JoyD.Windows.CS.Toprie
{
UpdateRealTimeInfoOnUI();
}
// 菜单状态变更时自动静默保存配置
SaveMenuConfig();
}
/// <summary>
@@ -1894,6 +1916,9 @@ namespace JoyD.Windows.CS.Toprie
{
UpdateRealTimeInfoOnUI();
}
// 菜单状态变更时自动静默保存配置
SaveMenuConfig();
}
/// <summary>
@@ -1909,6 +1934,9 @@ namespace JoyD.Windows.CS.Toprie
{
UpdateRealTimeInfoOnUI();
}
// 菜单状态变更时自动静默保存配置
SaveMenuConfig();
}
/// <summary>
@@ -1924,6 +1952,9 @@ namespace JoyD.Windows.CS.Toprie
{
UpdateRealTimeInfoOnUI();
}
// 菜单状态变更时自动静默保存配置
SaveMenuConfig();
}
/// <summary>
@@ -1945,6 +1976,466 @@ namespace JoyD.Windows.CS.Toprie
// 日志禁用时的清理(如果需要)
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}");
}
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];
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 (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 (thermalModeToolStripMenuItem != null)
ApplyMenuState(menuStates, "thermalMode", thermalModeToolStripMenuItem);
if (visibleModeToolStripMenuItem != null)
ApplyMenuState(menuStates, "visibleMode", visibleModeToolStripMenuItem);
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>
@@ -2217,6 +2708,44 @@ namespace JoyD.Windows.CS.Toprie
}
}
/// <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双击事件处理方法
/// 双击后弹出检测配置窗口

View File

@@ -1,220 +0,0 @@
# JoyD
## 图像处理相关
### InfoImage, ImageBuffer, 图像框的bitmap, LastImage, DisplayImage
1. 初始化时都创建成512x384的透明bitmap
2. 中途不进行Dispose和设置为null只在上面进行绘制
3. 仅当控件被Dispose时才进行Dispose和设置为null
## 操作流程
### 修改流程
1. 暂停或恢复时设置暂停状态调用更新Info
2. 断开或连接时设置连接状态调用更新Info
3. Ping通状态变化时修改Ping状态调用更新Info
4. 图像更新时保存LastImage调用更新UI
5. 2-4 只在非暂停状态下调用更新暂停状态下不更新Info和UI
6. 数据显示菜单勾选变化时,只在非暂停状态下调用更新实时信息
### 更新Info
1. 以透明色清空Info
2. 如果暂停显示暂停信息否则如果Ping不通或断开显示重连信息否则就绪
3. 如果未就绪调用更新UI
### 更新UI
1. 先将LastImage绘制到全局缓冲
2. 再将InfoImage绘制到缓冲
3. 在就绪条件下调用更新实时信息并将DisplayImage绘制到缓冲
4. 最后一次性绘制到图像框的bitmap
5. 同步更新检测配置窗口的实时图像属性
### 更新实时信息
1. 以透明色清空DisplayImage
2. 如果没有温度数据或温度数据的时间3秒之前返回
3. 温度显示菜单下如果未勾选区域温度和全局温度,则不显示任何温度信息
4. 如果勾选了全局温度,则显示全局温度(居中显示),否则显示区域温度(居中显示)
5. 如果勾选了区域温度,则显示区域框,否则不显示区域框
6. 如果勾选了平均温度,显示平均温度(居中显示)
7. 如果勾选了最低温度,显示最低温度(居中显示)
8. 如果勾选了最高温度,显示最高温度(居中显示)
9. 如果是全局温度时,最低温度和最高温度、平均温度,显示三行(左对齐),整体水平和垂直相对于图像框居中显示
10. 如果是区域温度时,最低温度和最高温度、平均温度,显示三行(左对齐),整体水平和垂直相对于区域框居中显示
## TCP温度数据接收
1. 系统初始化时,创建后台线程
2. 然后线程循环执行
3. 判断是否存在tcp连接实例
4. 如果不存在连接实例则判断是否暂停。如果暂停则Sleep 1秒后继续执行。否则创建一个新的连接实例。
5. 如果存在连接实例,则判断是否连接。如果未连接,则重新连接
6. 同步接收和处理数据
7. 收到数据后如果暂停接收后丢弃Sleep 1秒。否则同步将收到的数组复制放到待处理列表线程安全列表
8. 通知处理线程,有新数据到达
9. 跳转到6
10. Dispose时关闭后台线程
### 温度数据接收时的补充说明
总之就是不主动断开连接:
1. 同步机制简单,不用考虑阻塞,因为在后台线程里
2. 不用心跳机制协议约定是每秒1帧数据
3. 异常情况处理出现异常就跳转到第3步
4. 不用考虑波动问题,这是在一个循环中,它会下次重试
5. 在异常出现且需要重新创建对象前,先释放旧的资源
6. 数据完整性验证放在了数据处理环节
7. 连接失败会跳转到第3步。下一循环会自动重试
### 温度数据处理逻辑
1. 收到的数组放到待处理列表中
2. 开始循环处理列表
3. 如果列表为空,退出循环
4. 从列表中读取第一个数组,查看是否有头信息
5. 如果没有头信息将数组从列表中移除跳转到3
6. 如果有头信息,查看头信息中数据长度信息
7. 检查数组中是否有足够的数据长度
8. 如果没有足够的数据长度,看列表中是否有下一个数组
9. 如果没有则退出循环否则将下一个数组合并到当前数组中尾部将下一个数组从列表中移除然后跳转到7
10. 如果有足够的数据长度,则检查数据长度范围内是否又出现了头信息
11. 如果有则将数组中第二个头信息之前的数据移除然后跳转到6
12. 如果没有则将数据长度范围内的数据从数组取出进行解析处理剩余部分保留到数组中跳转到4
13. 处理完毕后跳转到3
14. 循环完成,进入等待状态
### 温度数据解析处理逻辑
根据热像仪SDK文档原始温度数据的解析处理需遵循以下逻辑
#### 原始温度数据格式说明
- 通过TCP端口8081获取温度数据
- 数据包含24字节头部和温度数据两部分
- 头部结构体(header)包含:
- mark[5]: 固定标识"+TEMP"
- payload_length: 温度数据长度
- timestamp: 时间戳前4字节为秒级时间后2字节为毫秒级时间
- 温度数据长度计算××2字节例如240×180分辨率时为240×180×2字节
- 数据按字节低位在前读取如读取为80 51 01 00时实际值为00 01 51 80的16进制值
#### 原始温度数据解析方法
1. **单像素温度计算**
- 每个像素点温度由两个字节组成L为低8位H为高8位
- 公式摄氏温度×10 = H×256 + L
- 实际温度:摄氏温度 = (H×256 + L)/10
2. **温度数据布局**
- 数据按行优先顺序排列
- 例如分辨率为4×3时数据顺序为L1 H1 L2 H2 L3 H3 L4 H4 L5 H5 ...
#### 温补值处理
1. **温补值获取**
- 通过SDK函数`sdk_get_comp_temp`获取温补值
- 函数原型:`sdk_get_comp_temp(const char* ip, int* comp_temp)`
- 返回值为0表示获取成功
2. **温补值应用**
- 温补值也是摄氏度乘以10的结果
- 最终温度计算公式:
```
最终温度 = 原始温度 + 温补值
```
- 示例原始温度为24324.3°C温补值为50.5°C则最终温度为24824.8°C
#### 温度数据解析处理流程
1. 接收TCP数据并解析头部信息
2. 验证mark标识是否为"+TEMP"
3. 根据payload_length确定温度数据长度
4. 按像素点顺序解析每个像素的温度值
5. 获取温补值并应用到每个像素的温度上
6. 计算得到最终的温度数据
#### 注意事项
1. 时间戳转换时需注意低字节在前、高字节在后的顺序
2. 温补值是设备补偿值,必须应用到原始温度上才能得到与网页计算结果一致的温度
3. 分辨率不同时,温度数据长度会相应变化,解析时需根据实际分辨率计算
4. 原始温度数据更新频率约为每秒一条
### 温度显示菜单
1. 在右键菜单中增加温度显示菜单
2. 温度显示菜单下显示全局温度(可勾选)
3. 温度显示菜单下显示区域温度(可勾选)
4. 区域温度和全局温度只能二选一显示
5. 温度显示菜单下显示最高温度、平均温度和最低温度(可勾选)
### 检测配置
#### 区域绘制逻辑
1. 创建独立的叠加层图像 专门维护一个与显示图像同尺寸的Image对象作为矩形框的叠加层
2. 分离绘制逻辑 将临时绘制和最终绘制分离临时矩形仍通过Paint事件显示完成的矩形绘制到叠加层
3. 图像合并机制 在Paint事件中先绘制叠加层再绘制临时矩形。
4. 保存的矩形框位置和大小信息应该是相对于图像的,而不是相对于控件的。
5. 当btnDrawRegion按下后处于绘制状态 btnSelectColor才显示出来
6. 当绘制状态时,右击鼠标,退出绘制状态,且清除所有临时绘制。
7. 当绘制状态时,双击鼠标,随机生成一个颜色,用于绘制矩形框。
8. 当就绪状态时,鼠标移到区域内,该区域内填充半透明色,当有多个重叠时,填充索引号最大的区域
9. 当鼠标在半透明区域内单击时,该区域填充半透明色,且显示八个句柄,表示选中该区域。
10. 选中区域时工具栏显示按钮btnDeleteRegion和btnSelectColor;隐藏btnDrawRegion。
11. 当选中区域时btnSelectColor用于改变选中区域的颜色。
12. 当选中区域时btnDeleteRegion用于删除该区域删除后btnSelectColor隐藏btnDrawRegion显示。
13. 当选中区域时,鼠标移动到八个句柄上,显示对应的光标,用于调整区域大小。
14. 当选中区域时,鼠标可以移动区域,用于调整区域位置。
15. 当选中区域时,右击鼠标,退出选中状态,转为就绪状态
16. 当就绪状态时工具栏显示绘制温差图按钮btnDrawTempDiff
17. 当绘制温差图按钮按下时进入绘制温差图状态显示添加和删除温差图例按钮btnAddTempDiff、btnDeleteTempDiff显示温差图例列表dataGridViewTempDiff
18. 当绘制温差图状态时,右击鼠标进入就绪状态
19. 初始状态/就绪状态显示dataGridViewTempDiff但只允许查看
### btnNewTempRegion新建测温区
1. 移除所有已有的测温区列表
2. 用透明色清空叠加层图像
### btnLoadTempRegion加载测温区
1. 弹出用户打开文件对话框用户选择要加载的csv文件
2. 移除所有已有的测温区列表,用透明色清空叠加层图像
3. 从csv文件中读取所有测温区位置大小和颜色信息添加到测温区列表中
4. 用读取的颜色填充叠加层图像对应的区域
### btnSaveTempRegion保存测温区
1. 弹出用户保存文件对话框,用户选择保存路径和文件名
2. 保存测温区列表中的所有测温区位置大小和颜色信息为csv文件
### btnNewTempDiff新建温差图
1. 用透明色清空温差层图像
2. 移除所有已有的温差图例列表
### btnLoadTempDiff加载温差图
1. 弹出用户打开文件对话框用户选择要加载的csv文件
2. 用透明色清空温差层图像
3. 移除所有已有的温差图例列表
4. 从csv文件中读取所有温差图例信息添加到温差图例列表中
5. 从csv文件中读取所有像素的温度值绘制温差层图像对应的像素
### btnSaveTempDiff保存温差图
1. 弹出用户保存文件对话框,用户选择保存路径和文件名
2. 保存温差图例列表中的所有温差图例信息到csv文件
3. 每个温差图例信息占一行,格式为:
- 温度如20°C
- 颜色(如#FF0000表示红色
4. 保存温差图每个像素的温度值到csv文件
- 每个像素温度值占一行,格式为:
- X坐标如100
- Y坐标如300
- 温度值(°C)如25.5
#### 配置状态说明
1. 初始状态/就绪状态:
- 显示btnDrawRegion和btnDrawTempDiff按钮、dataGridViewTempDiff
- 显示btnNewTempRegion按钮、btnLoadTempRegion按钮、btnSaveTempRegion按钮、btnNewTempDiff按钮、btnLoadTempDiff按钮、btnSaveTempDiff按钮
- 隐藏btnSelectColor、btnDeleteRegion按钮、btnAddTempDiff、btnDeleteTempDiff、btnBrushSize1、btnBrushSize3、btnBrushSize5、btnBrushSize10、btnBrushSize15、btnBrushSize25、txtRegionNumber
2. 选中区域状态:
- 显示btnDeleteRegion和btnSelectColor按钮、txtRegionNumber
- 隐藏btnDrawRegion和btnDrawTempDiff按钮、dataGridViewTempDiff、btnAddTempDiff、btnDeleteTempDiff、btnBrushSize1、btnBrushSize3、btnBrushSize5、btnBrushSize10、btnBrushSize15、btnBrushSize25
- 隐藏btnNewTempRegion按钮、btnLoadTempRegion按钮、btnSaveTempRegion按钮、btnNewTempDiff按钮、btnLoadTempDiff按钮、btnSaveTempDiff按钮
3. 绘制状态:
- 显示btnSelectColor按钮
- 显示btnDrawRegion按钮、txtRegionNumber
- 隐藏btnDeleteRegion按钮
- 隐藏btnDrawTempDiff按钮、dataGridViewTempDiff、btnAddTempDiff、btnDeleteTempDiff、btnBrushSize1、btnBrushSize3、btnBrushSize5、btnBrushSize10、btnBrushSize15、btnBrushSize25
- 隐藏btnNewTempRegion按钮、btnLoadTempRegion按钮、btnSaveTempRegion按钮、btnNewTempDiff按钮、btnLoadTempDiff按钮、btnSaveTempDiff按钮
4. 绘制温差图状态:
- 显示btnDrawTempDiff按钮显示dataGridViewTempDiff、btnAddTempDiff、btnDeleteTempDiff
- 当选定温差图例后显示btnBrushSize1、btnBrushSize3、btnBrushSize5、btnBrushSize10、btnBrushSize15、btnBrushSize25
- 隐藏btnSelectColor、btnDeleteRegion、btnDrawRegion按钮
- 隐藏btnNewTempRegion按钮、btnLoadTempRegion按钮、btnSaveTempRegion按钮、btnNewTempDiff按钮、btnLoadTempDiff按钮、btnSaveTempDiff按钮

View File

@@ -146,7 +146,6 @@ namespace JoyD.Windows.CS
// toolStrip
//
this.toolStrip.AllowDrop = true;
this.toolStrip.CanOverflow = true;
this.toolStrip.Dock = System.Windows.Forms.DockStyle.None;
this.toolStrip.ImageScalingSize = new System.Drawing.Size(20, 20);
this.toolStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
@@ -390,6 +389,7 @@ namespace JoyD.Windows.CS
this.ClientSize = new System.Drawing.Size(793, 450);
this.Controls.Add(this.splitContainer);
this.Name = "Setting";
this.ShowInTaskbar = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
this.Text = "检测配置";
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Setting_FormClosing);

View File

@@ -120,20 +120,4 @@
<metadata name="toolStrip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>516, 17</value>
</metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="toolStripButton1.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAIFSURBVDhPpZLtS1NhGMbPPxJmmlYSgqHiKzGU1EDxg4iK
YKyG2WBogqMYJQOtCEVRFBGdTBCJfRnkS4VaaWNT5sqx1BUxRXxDHYxAJLvkusEeBaPAB+5z4Jzn+t3X
/aLhnEfjo8m+dCoa+7/C3O2Hqe0zDC+8KG+cRZHZhdzaaWTVTCLDMIY0vfM04Nfh77/G/sEhwpEDbO3t
I7TxE8urEVy99fT/AL5gWDLrTB/hnF4XsW0khCu5ln8DmJliT2AXrcNBsU1gj/MH4nMeKwBrPktM28xM
cX79DFKrHHD5d9D26hvicx4pABt2lpg10zYzU0zr7+e3xXGcrkEB2O2TNec9nJFwB3alZn5jZorfeDZh
6Q3g8s06BeCoKF4MRURoH1+BY2oNCbeb0TIclIYxOhzf8frTOuo7FxCbbVIAzpni0iceEc8vhzEwGkJD
lx83ymxifejdKjRNk/8PWnyIyTQqAJek0jqHwfEVscu31baIu8+90sTE4nY025dQ2/5FIPpnXlzKuK8A
HBUzHot52djqQ6HZhfR7IwK4mKpHtvEDMqvfCiQ6zaAAXM8x94aIWTNrLLG4kVUzgaTSPlzLtyJOZxbb
1wtfyg4Q+AfA3aZlButjSfxGcUJBk4g5tuP3haQKRKXcUQDOmbvNTpPOJeFFjordZmbWTNvMTHFUcpUC
nOccAdABIDXXE1nzAAAAAElFTkSuQmCC
</value>
</data>
</root>

View File

@@ -1,54 +1,220 @@
# 托普瑞热像仪应用开发任务列表
# JoyD
## 1. SDK集成准备
## 图像处理相关
- [x] 1.1 复制托普瑞SDK文件到项目目录
- [x] 1.2 在Visual Studio项目中添加SDK引用
- [x] 1.3 阅读SDK文档了解API接口
### InfoImage, ImageBuffer, 图像框的bitmap, LastImage, DisplayImage
1. 初始化时都创建成512x384的透明bitmap
2. 中途不进行Dispose和设置为null只在上面进行绘制
3. 仅当控件被Dispose时才进行Dispose和设置为null
## 2. 设备连接功能
## 操作流程
- [x] 2.1 实现设备连接模块
- [x] 2.2 完善连接状态管理
### 修改流程
1. 暂停或恢复时设置暂停状态调用更新Info
2. 断开或连接时设置连接状态调用更新Info
3. Ping通状态变化时修改Ping状态调用更新Info
4. 图像更新时保存LastImage调用更新UI
5. 2-4 只在非暂停状态下调用更新暂停状态下不更新Info和UI
6. 数据显示菜单勾选变化时,只在非暂停状态下调用更新实时信息
## 3. 温度数据处理
### 更新Info
1. 以透明色清空Info
2. 如果暂停显示暂停信息否则如果Ping不通或断开显示重连信息否则就绪
3. 如果未就绪调用更新UI
### 3.1 设备连接建立与管理
- [ ] 3.1.1 实现设备连接建立流程
- 使用SDK的`sdk_initialize()`初始化套接字库
- 利用`sdk_search_device()`搜索设备IP地址
- 验证设备IP地址是否为默认地址(192.168.100.2)或自定义地址
- 建立与热像仪的TCP连接使用8081端口获取原始温度数据
- [ ] 3.1.2 连接状态维护机制
- 利用现有的`_heartbeatTimer`实现SDK心跳检测(`sdk_heartbeat`)
- 扩展`UpdateConnectionStatus()`方法,支持温度数据连接状态管理
- 实现基于`_connectionCheckTimer`的定时连接状态检测
- 利用现有的自动重连机制,当温度数据连接断开时自动重连
- [ ] 3.1.3 连接条件判断
- 调用`IsNetworkAvailable()`验证网络连接状态
- 检查设备型号兼容性支持IRAY-A8系列设备
- 验证设备版本信息确保SDK兼容性
- 在获取温度数据前检查设备是否处于正常工作状态
### 更新UI
1. 先将LastImage绘制到全局缓冲
2. 再将InfoImage绘制到缓冲
3. 在就绪条件下调用更新实时信息并将DisplayImage绘制到缓冲
4. 最后一次性绘制到图像框的bitmap
5. 同步更新检测配置窗口的实时图像属性
### 3.2 原始温度数据获取
- [ ] 3.2.1 温度数据获取方法实现
-`DeviceManager.cs`中添加`GetRawTemperatureData()`方法
- 创建TCP客户端连接到设备的8081端口
- 实现数据接收线程,使用类似现有的`_imageReceiveThread`模式
- 实现24字节头部结构体解析提取mark("+TEMP")、payload_length和timestamp
- [ ] 3.2.2 温度数据解析与处理
- 根据设备分辨率计算温度数据总长度(宽*高*2字节)
- 实现温度数据解码算法:摄氏温度 = (H*256 + L)/10其中L是低8位H是高8位
- 调用`sdk_get_comp_temp()`获取设备温补值并进行叠加修正
- 创建温度数据模型类,存储解析后的温度矩阵
- [ ] 3.2.3 数据获取条件控制与异常处理
- 添加连接状态验证,确保`_connectionStatus``Connected`
- 实现数据接收超时处理,基于`_lastDataReceivedTime``DataReceivedTimeout`
- 添加数据校验机制,确保接收到的温度数据格式正确
- 实现断线自动重连,利用现有的`_reconnectTimer`机制
- 添加异常捕获和日志记录,使用现有的`Log()`方法
- [ ] 3.2.4 温度数据接口设计
- 添加温度数据接收事件`TemperatureDataReceived`
- 实现线程安全的数据访问方法
- 添加温度数据缓存机制,保留最近几帧数据
- 提供获取特定区域温度数据的辅助方法
### 更新实时信息
1. 以透明色清空DisplayImage
2. 如果没有温度数据或温度数据的时间3秒之前返回
3. 温度显示菜单下如果未勾选区域温度和全局温度,则不显示任何温度信息
4. 如果勾选了全局温度,则显示全局温度(居中显示),否则显示区域温度(居中显示)
5. 如果勾选了区域温度,则显示区域框,否则不显示区域框
6. 如果勾选了平均温度,显示平均温度(居中显示)
7. 如果勾选了最低温度,显示最低温度(居中显示)
8. 如果勾选了最高温度,显示最高温度(居中显示)
9. 如果是全局温度时,最低温度和最高温度、平均温度,显示三行(左对齐),整体水平和垂直相对于图像框居中显示
10. 如果是区域温度时,最低温度和最高温度、平均温度,显示三行(左对齐),整体水平和垂直相对于区域框居中显示
## TCP温度数据接收
1. 系统初始化时,创建后台线程
2. 然后线程循环执行
3. 判断是否存在tcp连接实例
4. 如果不存在连接实例则判断是否暂停。如果暂停则Sleep 1秒后继续执行。否则创建一个新的连接实例。
5. 如果存在连接实例,则判断是否连接。如果未连接,则重新连接
6. 同步接收和处理数据
7. 收到数据后如果暂停接收后丢弃Sleep 1秒。否则同步将收到的数组复制放到待处理列表线程安全列表
8. 通知处理线程,有新数据到达
9. 跳转到6
10. Dispose时关闭后台线程
### 温度数据接收时的补充说明
总之就是不主动断开连接:
1. 同步机制简单,不用考虑阻塞,因为在后台线程里
2. 不用心跳机制协议约定是每秒1帧数据
3. 异常情况处理出现异常就跳转到第3步
4. 不用考虑波动问题,这是在一个循环中,它会下次重试
5. 在异常出现且需要重新创建对象前,先释放旧的资源
6. 数据完整性验证放在了数据处理环节
7. 连接失败会跳转到第3步。下一循环会自动重试
### 温度数据处理逻辑
1. 收到的数组放到待处理列表中
2. 开始循环处理列表
3. 如果列表为空,退出循环
4. 从列表中读取第一个数组,查看是否有头信息
5. 如果没有头信息将数组从列表中移除跳转到3
6. 如果有头信息,查看头信息中数据长度信息
7. 检查数组中是否有足够的数据长度
8. 如果没有足够的数据长度,看列表中是否有下一个数组
9. 如果没有则退出循环否则将下一个数组合并到当前数组中尾部将下一个数组从列表中移除然后跳转到7
10. 如果有足够的数据长度,则检查数据长度范围内是否又出现了头信息
11. 如果有则将数组中第二个头信息之前的数据移除然后跳转到6
12. 如果没有则将数据长度范围内的数据从数组取出进行解析处理剩余部分保留到数组中跳转到4
13. 处理完毕后跳转到3
14. 循环完成,进入等待状态
### 温度数据解析处理逻辑
根据热像仪SDK文档原始温度数据的解析处理需遵循以下逻辑
#### 原始温度数据格式说明
- 通过TCP端口8081获取温度数据
- 数据包含24字节头部和温度数据两部分
- 头部结构体(header)包含:
- mark[5]: 固定标识"+TEMP"
- payload_length: 温度数据长度
- timestamp: 时间戳前4字节为秒级时间后2字节为毫秒级时间
- 温度数据长度计算××2字节例如240×180分辨率时为240×180×2字节
- 数据按字节低位在前读取如读取为80 51 01 00时实际值为00 01 51 80的16进制值
#### 原始温度数据解析方法
1. **单像素温度计算**
- 每个像素点温度由两个字节组成L为低8位H为高8位
- 公式摄氏温度×10 = H×256 + L
- 实际温度:摄氏温度 = (H×256 + L)/10
2. **温度数据布局**
- 数据按行优先顺序排列
- 例如分辨率为4×3时数据顺序为L1 H1 L2 H2 L3 H3 L4 H4 L5 H5 ...
#### 温补值处理
1. **温补值获取**
- 通过SDK函数`sdk_get_comp_temp`获取温补值
- 函数原型:`sdk_get_comp_temp(const char* ip, int* comp_temp)`
- 返回值为0表示获取成功
2. **温补值应用**
- 温补值也是摄氏度乘以10的结果
- 最终温度计算公式:
```
最终温度 = 原始温度 + 温补值
```
- 示例原始温度为24324.3°C温补值为50.5°C则最终温度为24824.8°C
#### 温度数据解析处理流程
1. 接收TCP数据并解析头部信息
2. 验证mark标识是否为"+TEMP"
3. 根据payload_length确定温度数据长度
4. 按像素点顺序解析每个像素的温度值
5. 获取温补值并应用到每个像素的温度上
6. 计算得到最终的温度数据
#### 注意事项
1. 时间戳转换时需注意低字节在前、高字节在后的顺序
2. 温补值是设备补偿值,必须应用到原始温度上才能得到与网页计算结果一致的温度
3. 分辨率不同时,温度数据长度会相应变化,解析时需根据实际分辨率计算
4. 原始温度数据更新频率约为每秒一条
### 温度显示菜单
1. 在右键菜单中增加温度显示菜单
2. 温度显示菜单下显示全局温度(可勾选)
3. 温度显示菜单下显示区域温度(可勾选)
4. 区域温度和全局温度只能二选一显示
5. 温度显示菜单下显示最高温度、平均温度和最低温度(可勾选)
### 检测配置
#### 区域绘制逻辑
1. 创建独立的叠加层图像 专门维护一个与显示图像同尺寸的Image对象作为矩形框的叠加层
2. 分离绘制逻辑 将临时绘制和最终绘制分离临时矩形仍通过Paint事件显示完成的矩形绘制到叠加层
3. 图像合并机制 在Paint事件中先绘制叠加层再绘制临时矩形。
4. 保存的矩形框位置和大小信息应该是相对于图像的,而不是相对于控件的。
5. 当btnDrawRegion按下后处于绘制状态 btnSelectColor才显示出来
6. 当绘制状态时,右击鼠标,退出绘制状态,且清除所有临时绘制。
7. 当绘制状态时,双击鼠标,随机生成一个颜色,用于绘制矩形框。
8. 当就绪状态时,鼠标移到区域内,该区域内填充半透明色,当有多个重叠时,填充索引号最大的区域
9. 当鼠标在半透明区域内单击时,该区域填充半透明色,且显示八个句柄,表示选中该区域。
10. 选中区域时工具栏显示按钮btnDeleteRegion和btnSelectColor;隐藏btnDrawRegion。
11. 当选中区域时btnSelectColor用于改变选中区域的颜色。
12. 当选中区域时btnDeleteRegion用于删除该区域删除后btnSelectColor隐藏btnDrawRegion显示。
13. 当选中区域时,鼠标移动到八个句柄上,显示对应的光标,用于调整区域大小。
14. 当选中区域时,鼠标可以移动区域,用于调整区域位置。
15. 当选中区域时,右击鼠标,退出选中状态,转为就绪状态
16. 当就绪状态时工具栏显示绘制温差图按钮btnDrawTempDiff
17. 当绘制温差图按钮按下时进入绘制温差图状态显示添加和删除温差图例按钮btnAddTempDiff、btnDeleteTempDiff显示温差图例列表dataGridViewTempDiff
18. 当绘制温差图状态时,右击鼠标进入就绪状态
19. 初始状态/就绪状态显示dataGridViewTempDiff但只允许查看
### btnNewTempRegion新建测温区
1. 移除所有已有的测温区列表
2. 用透明色清空叠加层图像
### btnLoadTempRegion加载测温区
1. 弹出用户打开文件对话框用户选择要加载的csv文件
2. 移除所有已有的测温区列表,用透明色清空叠加层图像
3. 从csv文件中读取所有测温区位置大小和颜色信息添加到测温区列表中
4. 用读取的颜色填充叠加层图像对应的区域
### btnSaveTempRegion保存测温区
1. 弹出用户保存文件对话框,用户选择保存路径和文件名
2. 保存测温区列表中的所有测温区位置大小和颜色信息为csv文件
### btnNewTempDiff新建温差图
1. 用透明色清空温差层图像
2. 移除所有已有的温差图例列表
### btnLoadTempDiff加载温差图
1. 弹出用户打开文件对话框用户选择要加载的csv文件
2. 用透明色清空温差层图像
3. 移除所有已有的温差图例列表
4. 从csv文件中读取所有温差图例信息添加到温差图例列表中
5. 从csv文件中读取所有像素的温度值绘制温差层图像对应的像素
### btnSaveTempDiff保存温差图
1. 弹出用户保存文件对话框,用户选择保存路径和文件名
2. 保存温差图例列表中的所有温差图例信息到csv文件
3. 每个温差图例信息占一行,格式为:
- 温度如20°C
- 颜色(如#FF0000表示红色
4. 保存温差图每个像素的温度值到csv文件
- 每个像素温度值占一行,格式为:
- X坐标如100
- Y坐标如300
- 温度值(°C)如25.5
#### 配置状态说明
1. 初始状态/就绪状态:
- 显示btnDrawRegion和btnDrawTempDiff按钮、dataGridViewTempDiff
- 显示btnNewTempRegion按钮、btnLoadTempRegion按钮、btnSaveTempRegion按钮、btnNewTempDiff按钮、btnLoadTempDiff按钮、btnSaveTempDiff按钮
- 隐藏btnSelectColor、btnDeleteRegion按钮、btnAddTempDiff、btnDeleteTempDiff、btnBrushSize1、btnBrushSize3、btnBrushSize5、btnBrushSize10、btnBrushSize15、btnBrushSize25、txtRegionNumber
2. 选中区域状态:
- 显示btnDeleteRegion和btnSelectColor按钮、txtRegionNumber
- 隐藏btnDrawRegion和btnDrawTempDiff按钮、dataGridViewTempDiff、btnAddTempDiff、btnDeleteTempDiff、btnBrushSize1、btnBrushSize3、btnBrushSize5、btnBrushSize10、btnBrushSize15、btnBrushSize25
- 隐藏btnNewTempRegion按钮、btnLoadTempRegion按钮、btnSaveTempRegion按钮、btnNewTempDiff按钮、btnLoadTempDiff按钮、btnSaveTempDiff按钮
3. 绘制状态:
- 显示btnSelectColor按钮
- 显示btnDrawRegion按钮、txtRegionNumber
- 隐藏btnDeleteRegion按钮
- 隐藏btnDrawTempDiff按钮、dataGridViewTempDiff、btnAddTempDiff、btnDeleteTempDiff、btnBrushSize1、btnBrushSize3、btnBrushSize5、btnBrushSize10、btnBrushSize15、btnBrushSize25
- 隐藏btnNewTempRegion按钮、btnLoadTempRegion按钮、btnSaveTempRegion按钮、btnNewTempDiff按钮、btnLoadTempDiff按钮、btnSaveTempDiff按钮
4. 绘制温差图状态:
- 显示btnDrawTempDiff按钮显示dataGridViewTempDiff、btnAddTempDiff、btnDeleteTempDiff
- 当选定温差图例后显示btnBrushSize1、btnBrushSize3、btnBrushSize5、btnBrushSize10、btnBrushSize15、btnBrushSize25
- 隐藏btnSelectColor、btnDeleteRegion、btnDrawRegion按钮
- 隐藏btnNewTempRegion按钮、btnLoadTempRegion按钮、btnSaveTempRegion按钮、btnNewTempDiff按钮、btnLoadTempDiff按钮、btnSaveTempDiff按钮