386 lines
15 KiB
C#
386 lines
15 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.ComponentModel;
|
||
using System.Data;
|
||
using System.Drawing;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using System.Windows.Forms;
|
||
using JoyD.Windows.CS.Toprie;
|
||
|
||
namespace JoyD.Windows.CS
|
||
{
|
||
/// <summary>
|
||
/// 预览窗口类,用于显示图像或数据预览
|
||
/// </summary>
|
||
public partial class preview : Form
|
||
{
|
||
// 原始温度数据矩阵尺寸
|
||
private const int DATA_WIDTH = 256;
|
||
private const int DATA_HEIGHT = 192;
|
||
// 热力图显示尺寸
|
||
private const int DISPLAY_WIDTH = 512;
|
||
private const int DISPLAY_HEIGHT = 384;
|
||
|
||
// 鼠标当前指向的数据点坐标
|
||
private Point _currentDataPoint = new Point(-1, -1);
|
||
// 用于保护_currentDataPoint访问的锁对象
|
||
private readonly object _dataPointLock = new object();
|
||
// 最新的温度数据
|
||
private TemperatureData _temperatureData = null;
|
||
// 标题栏更新防抖相关字段
|
||
private Timer _titleUpdateTimer = null;
|
||
private float _pendingTemperature = float.NaN;
|
||
private float _currentDisplayedTemperature = float.NaN;
|
||
private const int TITLE_UPDATE_DELAY = 50; // 防抖延迟时间(毫秒)
|
||
|
||
/// <summary>
|
||
/// 初始化预览窗口的构造函数
|
||
/// </summary>
|
||
public preview()
|
||
{
|
||
InitializeComponent();
|
||
previewImageBox.MouseMove += PreviewImageBox_MouseMove;
|
||
previewImageBox.MouseLeave += PreviewImageBox_MouseLeave;
|
||
previewImageBox.Paint += PreviewImageBox_Paint;
|
||
|
||
// 初始化标题栏更新防抖定时器
|
||
_titleUpdateTimer = new Timer();
|
||
_titleUpdateTimer.Interval = TITLE_UPDATE_DELAY;
|
||
_titleUpdateTimer.Tick += TitleUpdateTimer_Tick;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 动态更新预览图像(线程安全)
|
||
/// </summary>
|
||
/// <param name="image">要显示的图像</param>
|
||
public void UpdateImage(Image image)
|
||
{
|
||
if (this.InvokeRequired)
|
||
{
|
||
this.Invoke(new Action<Image>(UpdateImage), image);
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 释放旧图像资源
|
||
if (previewImageBox.Image != null)
|
||
{
|
||
previewImageBox.Image.Dispose();
|
||
previewImageBox.Image = null;
|
||
}
|
||
|
||
// 克隆新图像
|
||
if (image != null)
|
||
{
|
||
previewImageBox.Image = (Image)image.Clone();
|
||
}
|
||
|
||
// 图像更新后,确保重绘以保持覆盖层正确
|
||
previewImageBox.Invalidate();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"更新预览图像时出错: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新温度数据(线程安全)
|
||
/// </summary>
|
||
/// <param name="temperatureData">温度数据</param>
|
||
public void UpdateTemperatureData(TemperatureData temperatureData)
|
||
{
|
||
if (this.InvokeRequired)
|
||
{
|
||
this.Invoke(new Action<TemperatureData>(UpdateTemperatureData), temperatureData);
|
||
return;
|
||
}
|
||
|
||
// 更新温度数据
|
||
_temperatureData = temperatureData;
|
||
|
||
// 温度数据更新后,自动重新计算当前鼠标指向点的温度
|
||
lock (_dataPointLock)
|
||
{
|
||
if (_currentDataPoint.X >= 0 && _currentDataPoint.Y >= 0)
|
||
{
|
||
// 重新获取温度值并使用防抖机制更新标题栏
|
||
float temperature = GetTemperatureAtDataPoint(_currentDataPoint);
|
||
UpdateTitleBarWithDebounce(temperature);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 窗口关闭时释放资源
|
||
/// </summary>
|
||
protected override void OnFormClosed(FormClosedEventArgs e)
|
||
{
|
||
base.OnFormClosed(e);
|
||
|
||
// 移除事件绑定
|
||
previewImageBox.MouseMove -= PreviewImageBox_MouseMove;
|
||
previewImageBox.MouseLeave -= PreviewImageBox_MouseLeave;
|
||
previewImageBox.Paint -= PreviewImageBox_Paint;
|
||
|
||
// 释放定时器资源
|
||
if (_titleUpdateTimer != null)
|
||
{
|
||
_titleUpdateTimer.Stop();
|
||
_titleUpdateTimer.Dispose();
|
||
_titleUpdateTimer = null;
|
||
}
|
||
|
||
// 释放图像资源
|
||
if (previewImageBox.Image != null)
|
||
{
|
||
previewImageBox.Image.Dispose();
|
||
previewImageBox.Image = null;
|
||
}
|
||
}
|
||
|
||
// 标题栏更新防抖处理
|
||
private void UpdateTitleBarWithDebounce(float temperature)
|
||
{
|
||
// 只有当温度值实际变化时才更新
|
||
// 检查NaN情况:如果两者都是NaN,认为没有变化
|
||
if (float.IsNaN(temperature) && float.IsNaN(_currentDisplayedTemperature))
|
||
{
|
||
return; // 都是NaN,不需要更新
|
||
}
|
||
// 检查数值变化:如果都是有效数字且差异小于阈值,认为没有变化
|
||
if (!float.IsNaN(temperature) && !float.IsNaN(_currentDisplayedTemperature) &&
|
||
Math.Abs(temperature - _currentDisplayedTemperature) < 0.1f)
|
||
{
|
||
return; // 温度值没有显著变化,不需要更新
|
||
}
|
||
|
||
// 保存待更新的温度值
|
||
_pendingTemperature = temperature;
|
||
|
||
// 停止并重新启动定时器
|
||
_titleUpdateTimer.Stop();
|
||
_titleUpdateTimer.Start();
|
||
}
|
||
|
||
// 定时器Tick事件,实际更新标题栏
|
||
private void TitleUpdateTimer_Tick(object sender, EventArgs e)
|
||
{
|
||
// 停止定时器
|
||
_titleUpdateTimer.Stop();
|
||
|
||
// 只有当温度值实际变化时才更新标题栏
|
||
// 检查NaN情况:如果两者都是NaN,认为没有变化
|
||
if (float.IsNaN(_pendingTemperature) && float.IsNaN(_currentDisplayedTemperature))
|
||
{
|
||
return; // 都是NaN,不需要更新
|
||
}
|
||
// 检查数值变化:如果都是有效数字且差异小于阈值,认为没有变化
|
||
if (!float.IsNaN(_pendingTemperature) && !float.IsNaN(_currentDisplayedTemperature) &&
|
||
Math.Abs(_pendingTemperature - _currentDisplayedTemperature) < 0.1f)
|
||
{
|
||
return; // 温度值没有显著变化,不需要更新
|
||
}
|
||
|
||
// 更新标题栏
|
||
if (!float.IsNaN(_pendingTemperature))
|
||
{
|
||
this.Text = $"预览 - 温度: {_pendingTemperature:F1}°C";
|
||
}
|
||
else
|
||
{
|
||
this.Text = "预览";
|
||
}
|
||
|
||
// 更新当前显示的温度值
|
||
_currentDisplayedTemperature = _pendingTemperature;
|
||
}
|
||
|
||
// 鼠标移动事件处理
|
||
private void PreviewImageBox_MouseMove(object sender, MouseEventArgs e)
|
||
{
|
||
if (previewImageBox.Image == null) return;
|
||
|
||
// 计算鼠标在图像上的坐标(考虑图像缩放)
|
||
Point imagePoint = GetImagePoint(e.Location);
|
||
|
||
// 映射到原始数据点坐标
|
||
Point dataPoint = MapToDataPoint(imagePoint);
|
||
|
||
// 添加鼠标移动日志
|
||
Console.WriteLine($"鼠标移动 - 控件坐标: ({e.Location.X},{e.Location.Y}), 图像坐标: ({imagePoint.X},{imagePoint.Y}), 数据点: ({dataPoint.X},{dataPoint.Y})");
|
||
|
||
// 使用锁保护_currentDataPoint的访问
|
||
lock (_dataPointLock)
|
||
{
|
||
// 如果数据点发生变化,重绘控件
|
||
if (_currentDataPoint != dataPoint)
|
||
{
|
||
_currentDataPoint = dataPoint;
|
||
previewImageBox.Invalidate(); // 触发Paint事件重绘
|
||
|
||
// 获取温度值并使用防抖机制更新标题栏
|
||
float temperature = GetTemperatureAtDataPoint(dataPoint);
|
||
UpdateTitleBarWithDebounce(temperature);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 鼠标离开事件处理
|
||
private void PreviewImageBox_MouseLeave(object sender, EventArgs e)
|
||
{
|
||
// 使用锁保护_currentDataPoint的访问
|
||
lock (_dataPointLock)
|
||
{
|
||
_currentDataPoint = new Point(-1, -1);
|
||
previewImageBox.Invalidate(); // 触发Paint事件重绘
|
||
|
||
// 使用防抖机制清除标题栏温度显示
|
||
UpdateTitleBarWithDebounce(float.NaN);
|
||
}
|
||
}
|
||
|
||
// 将控件坐标转换为图像坐标
|
||
private Point GetImagePoint(Point controlPoint)
|
||
{
|
||
if (previewImageBox.Image == null)
|
||
return controlPoint;
|
||
|
||
// 计算图像在控件中的缩放比例
|
||
// SizeMode = StretchImage 将图像拉伸填满整个PictureBox
|
||
// 控件左上角对应图像左上角,控件右下角对应图像右下角
|
||
float scaleX = (float)previewImageBox.Image.Width / previewImageBox.ClientSize.Width;
|
||
float scaleY = (float)previewImageBox.Image.Height / previewImageBox.ClientSize.Height;
|
||
|
||
// 计算图像坐标
|
||
return new Point(
|
||
(int)(controlPoint.X * scaleX),
|
||
(int)(controlPoint.Y * scaleY)
|
||
);
|
||
}
|
||
|
||
// 将图像坐标映射到原始数据点坐标
|
||
private Point MapToDataPoint(Point imagePoint)
|
||
{
|
||
// 确保坐标在有效范围内
|
||
int x = Math.Max(0, Math.Min(imagePoint.X, DISPLAY_WIDTH - 1));
|
||
int y = Math.Max(0, Math.Min(imagePoint.Y, DISPLAY_HEIGHT - 1));
|
||
|
||
// 计算原始数据点坐标(整数除法,向下取整)
|
||
int dataX = x / 2;
|
||
int dataY = y / 2;
|
||
|
||
// 确保数据点坐标在有效范围内
|
||
dataX = Math.Max(0, Math.Min(dataX, DATA_WIDTH - 1));
|
||
dataY = Math.Max(0, Math.Min(dataY, DATA_HEIGHT - 1));
|
||
|
||
return new Point(dataX, dataY);
|
||
}
|
||
|
||
// 获取特定数据点的温度值
|
||
private float GetTemperatureAtDataPoint(Point dataPoint)
|
||
{
|
||
if (_temperatureData == null || _temperatureData.TemperatureMatrix == null)
|
||
return float.NaN;
|
||
|
||
// 原始数据点(x,y)对应TemperatureMatrix[y][x](TemperatureMatrix是[行][列]结构,已包含温补后的温度数据)
|
||
int x = dataPoint.X;
|
||
int y = dataPoint.Y;
|
||
|
||
// 确保坐标在有效范围内
|
||
// TemperatureMatrix是[行][列]结构,所以:
|
||
// - GetLength(0)返回行数
|
||
// - GetLength(1)返回列数
|
||
// - y对应行索引,x对应列索引
|
||
if (x >= 0 && x < _temperatureData.TemperatureMatrix.GetLength(1) && // 列索引有效
|
||
y >= 0 && y < _temperatureData.TemperatureMatrix.GetLength(0)) // 行索引有效
|
||
{
|
||
float temperature = _temperatureData.TemperatureMatrix[y, x]; // [行][列]访问
|
||
|
||
// 添加鼠标位置温度日志
|
||
Console.WriteLine($"鼠标位置温度 - 数据点: ({x},{y}), 温度: {temperature:F2} °C, 使用矩阵: TemperatureMatrix");
|
||
|
||
// 同时记录RealTemperatureMatrix中的对应温度(如果可用)
|
||
if (_temperatureData.RealTemperatureMatrix != null)
|
||
{
|
||
int realX = x * 2;
|
||
int realY = y * 2;
|
||
if (realX >= 0 && realX < _temperatureData.RealTemperatureMatrix.GetLength(1) &&
|
||
realY >= 0 && realY < _temperatureData.RealTemperatureMatrix.GetLength(0))
|
||
{
|
||
float realTemp = _temperatureData.RealTemperatureMatrix[realY, realX];
|
||
Console.WriteLine($"对应RealTemperatureMatrix温度 - 坐标: ({realX},{realY}), 温度: {realTemp:F2} °C");
|
||
if (Math.Abs(temperature - realTemp) > 0.1f)
|
||
{
|
||
Console.WriteLine($"温度差异: {Math.Abs(temperature - realTemp):F2} °C");
|
||
}
|
||
}
|
||
}
|
||
|
||
return temperature;
|
||
}
|
||
|
||
return float.NaN;
|
||
}
|
||
|
||
// 将原始数据点坐标映射到热力图覆盖区域
|
||
private Rectangle GetDataPointCoverageArea(Point dataPoint)
|
||
{
|
||
// 确保数据点在有效范围内
|
||
int x = Math.Max(0, Math.Min(dataPoint.X, DATA_WIDTH - 1));
|
||
int y = Math.Max(0, Math.Min(dataPoint.Y, DATA_HEIGHT - 1));
|
||
|
||
// 计算覆盖区域(2×2像素)
|
||
return new Rectangle(x * 2, y * 2, 2, 2);
|
||
}
|
||
|
||
// Paint事件处理函数
|
||
private void PreviewImageBox_Paint(object sender, PaintEventArgs e)
|
||
{
|
||
// 边界检查:如果没有图像,不需要绘制覆盖层
|
||
if (previewImageBox.Image == null)
|
||
return;
|
||
|
||
// 使用锁保护_currentDataPoint的访问并进行有效性检查
|
||
Point currentDataPoint;
|
||
lock (_dataPointLock)
|
||
{
|
||
currentDataPoint = _currentDataPoint;
|
||
// 如果没有有效的数据点,不需要绘制覆盖层
|
||
if (currentDataPoint.X < 0 || currentDataPoint.Y < 0)
|
||
return;
|
||
}
|
||
|
||
// 计算数据点在图像坐标系中的覆盖区域
|
||
Rectangle imageArea = GetDataPointCoverageArea(currentDataPoint);
|
||
|
||
// 转换为控件坐标系
|
||
float scaleX = (float)previewImageBox.ClientSize.Width / previewImageBox.Image.Width;
|
||
float scaleY = (float)previewImageBox.ClientSize.Height / previewImageBox.Image.Height;
|
||
|
||
Rectangle controlArea = new Rectangle(
|
||
(int)(imageArea.X * scaleX),
|
||
(int)(imageArea.Y * scaleY),
|
||
(int)(imageArea.Width * scaleX),
|
||
(int)(imageArea.Height * scaleY)
|
||
);
|
||
|
||
// 使用using语句确保GDI+资源正确释放
|
||
using (Brush semiTransparentRed = new SolidBrush(Color.FromArgb(128, Color.Red)))
|
||
{
|
||
// 绘制覆盖层
|
||
e.Graphics.FillRectangle(semiTransparentRed, controlArea);
|
||
|
||
// 使用using语句确保Pen资源正确释放
|
||
using (Pen borderPen = new Pen(Color.Red, 1))
|
||
{
|
||
// 可选:绘制矩形边框以增强视觉效果
|
||
e.Graphics.DrawRectangle(borderPen, controlArea);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|