Files
JoyD/Windows/CS/Framework4.0/Toprie/Toprie/preview.cs
2026-01-21 15:24:21 +08:00

386 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

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

using System;
using System.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);
}
}
}
}
}