增加热图显示

This commit is contained in:
zqm
2025-10-24 13:30:12 +08:00
parent 2e5bc392f0
commit 21a04f36ec
5 changed files with 497 additions and 25 deletions

View File

@@ -7,19 +7,6 @@
/// </summary> /// </summary>
private System.ComponentModel.IContainer components = null; private System.ComponentModel.IContainer components = null;
/// <summary>
/// 清理所有正在使用的资源。
/// </summary>
/// <param name="disposing">如果应释放托管资源,为 true否则为 false。</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region #region
/// <summary> /// <summary>
@@ -28,10 +15,34 @@
/// </summary> /// </summary>
private void InitializeComponent() private void InitializeComponent()
{ {
components = new System.ComponentModel.Container(); this.imageBox = new System.Windows.Forms.PictureBox();
((System.ComponentModel.ISupportInitialize)(this.imageBox)).BeginInit();
this.SuspendLayout();
//
// imageBox
//
this.imageBox.Dock = System.Windows.Forms.DockStyle.Fill;
this.imageBox.Location = new System.Drawing.Point(0, 0);
this.imageBox.Name = "imageBox";
this.imageBox.Size = new System.Drawing.Size(150, 150);
this.imageBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
this.imageBox.TabIndex = 0;
this.imageBox.TabStop = false;
//
// Camera
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.imageBox);
this.Name = "Camera";
this.Load += new System.EventHandler(this.Camera_Load);
((System.ComponentModel.ISupportInitialize)(this.imageBox)).EndInit();
this.ResumeLayout(false);
} }
#endregion #endregion
private System.Windows.Forms.PictureBox imageBox;
} }
} }

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Drawing; using System.Drawing;
@@ -11,9 +11,354 @@ namespace JoyD.Windows.CS.Toprie
{ {
public partial class Camera : UserControl public partial class Camera : UserControl
{ {
// 设备管理器实例
private DeviceManager _deviceManager;
// 是否正在接收图像
private bool _isReceivingImage = false;
// 显示错误的定时器
private System.Windows.Forms.Timer _errorDisplayTimer;
public Camera() public Camera()
{ {
InitializeComponent(); InitializeComponent();
// 只有在非设计模式下才初始化设备管理器和错误定时器
if (!DesignMode)
{
InitializeDeviceManager();
InitializeErrorTimer();
}
}
/// <summary>
/// Camera控件加载事件
/// 在控件加载时自动启动相机显示热图
/// </summary>
private void Camera_Load(object sender, EventArgs e)
{
// 只有在非设计模式下才启动相机
if (!DesignMode)
{
try
{
StartCamera();
}
catch (Exception ex)
{
ShowError($"自动启动相机失败: {ex.Message}");
}
}
}
// 注意UserControl不支持FormClosing事件资源清理已在Dispose方法中处理
/// <summary>
/// 初始化设备管理器
/// </summary>
private void InitializeDeviceManager()
{
_deviceManager = new DeviceManager();
// 注册图像接收事件
_deviceManager.ImageReceived += DeviceManager_ImageReceived;
// 注册连接状态变更事件
_deviceManager.ConnectionStatusChanged += DeviceManager_ConnectionStatusChanged;
// 注册连接异常事件
_deviceManager.ConnectionException += DeviceManager_ConnectionException;
}
/// <summary>
/// 初始化错误显示定时器
/// </summary>
private void InitializeErrorTimer()
{
_errorDisplayTimer = new System.Windows.Forms.Timer { Interval = 3000 }; // 显示3秒后清除
_errorDisplayTimer.Tick += ErrorDisplayTimer_Tick;
}
/// <summary>
/// 启动并连接设备
/// </summary>
public void StartCamera()
{
try
{
// 设置为热图模式
_deviceManager.CurrentImageMode = ImageMode.Thermal;
// 启用自动重连
_deviceManager.AutoReconnectEnabled = true;
// 连接设备
bool connected = _deviceManager.ConnectDevice();
if (connected)
{
// 开始接收图像
StartReceiveImage();
}
else
{
ShowError("无法连接到设备,请检查设备是否在线");
}
}
catch (Exception ex)
{
ShowError($"启动相机失败: {ex.Message}");
}
}
/// <summary>
/// 开始接收图像
/// </summary>
private void StartReceiveImage()
{
try
{
if (!_isReceivingImage && _deviceManager.ConnectionStatus == ConnectionStatus.Connected)
{
_deviceManager.StartReceiveImage();
_isReceivingImage = true;
}
}
catch (Exception ex)
{
ShowError($"开始接收图像失败: {ex.Message}");
}
}
/// <summary>
/// 停止接收图像
/// </summary>
public void StopCamera()
{
try
{
if (_isReceivingImage)
{
_deviceManager.StopReceiveImage();
_isReceivingImage = false;
}
}
catch (Exception ex)
{
Console.WriteLine($"停止相机失败: {ex.Message}");
}
}
/// <summary>
/// 设备管理器图像接收事件处理
/// </summary>
private void DeviceManager_ImageReceived(object sender, ImageReceivedEventArgs e)
{
if (this.InvokeRequired)
{
// 在UI线程上更新图像
this.Invoke(new Action(() => UpdateImage(e.ImageData)));
}
else
{
UpdateImage(e.ImageData);
}
}
/// <summary>
/// 更新图像显示
/// </summary>
private void UpdateImage(Image image)
{
try
{
// 释放之前的图像资源
if (imageBox.Image != null)
{
imageBox.Image.Dispose();
}
// 设置新图像
imageBox.Image = (Image)image.Clone();
}
catch (Exception ex)
{
Console.WriteLine($"更新图像失败: {ex.Message}");
}
}
/// <summary>
/// 设备管理器连接状态变更事件处理
/// </summary>
private void DeviceManager_ConnectionStatusChanged(object sender, ConnectionStatusChangedEventArgs e)
{
if (this.InvokeRequired)
{
this.Invoke(new Action(() => HandleConnectionStatusChanged(e)));
}
else
{
HandleConnectionStatusChanged(e);
}
}
/// <summary>
/// 处理连接状态变更
/// </summary>
private void HandleConnectionStatusChanged(ConnectionStatusChangedEventArgs e)
{
switch (e.NewStatus)
{
case ConnectionStatus.Connected:
Console.WriteLine("设备已连接");
if (!_isReceivingImage)
{
StartReceiveImage();
}
break;
case ConnectionStatus.Disconnected:
Console.WriteLine("设备已断开连接");
_isReceivingImage = false;
if (!string.IsNullOrEmpty(e.Message))
{
ShowError(e.Message);
}
break;
case ConnectionStatus.Connecting:
case ConnectionStatus.Reconnecting:
Console.WriteLine("正在连接设备...");
break;
}
}
/// <summary>
/// 设备管理器连接异常事件处理
/// </summary>
private void DeviceManager_ConnectionException(object sender, ConnectionExceptionEventArgs e)
{
if (this.InvokeRequired)
{
this.Invoke(new Action(() => ShowError($"连接异常: {e.ContextMessage}")));
}
else
{
ShowError($"连接异常: {e.ContextMessage}");
}
}
/// <summary>
/// 显示错误信息
/// </summary>
private void ShowError(string message)
{
Console.WriteLine(message);
// 可以根据需要添加UI上的错误显示
// 这里简化处理,只在控制台输出
// 如果需要在图像区域显示错误文字,可以使用以下代码
using (Bitmap errorBitmap = new Bitmap(imageBox.Width, imageBox.Height))
using (Graphics g = Graphics.FromImage(errorBitmap))
{
g.Clear(Color.Black);
using (Font font = new Font("Arial", 10))
using (Brush brush = new SolidBrush(Color.Red))
{
SizeF textSize = g.MeasureString(message, font);
PointF textLocation = new PointF(
(errorBitmap.Width - textSize.Width) / 2,
(errorBitmap.Height - textSize.Height) / 2);
g.DrawString(message, font, brush, textLocation);
}
UpdateImage(errorBitmap);
}
// 启动定时器3秒后清除错误显示
_errorDisplayTimer.Stop();
_errorDisplayTimer.Start();
}
/// <summary>
/// 错误显示定时器事件
/// </summary>
private void ErrorDisplayTimer_Tick(object sender, EventArgs e)
{
_errorDisplayTimer.Stop();
// 清除错误显示,恢复到等待图像状态
using (Bitmap waitingBitmap = new Bitmap(imageBox.Width, imageBox.Height))
using (Graphics g = Graphics.FromImage(waitingBitmap))
{
g.Clear(Color.Black);
using (Font font = new Font("Arial", 10))
using (Brush brush = new SolidBrush(Color.White))
{
string waitingText = "正在等待热图数据...";
SizeF textSize = g.MeasureString(waitingText, font);
PointF textLocation = new PointF(
(waitingBitmap.Width - textSize.Width) / 2,
(waitingBitmap.Height - textSize.Height) / 2);
g.DrawString(waitingText, font, brush, textLocation);
}
UpdateImage(waitingBitmap);
}
}
/// <summary>
/// 清理资源
/// </summary>
protected override void Dispose(bool disposing)
{
if (disposing)
{
// 检查是否处于设计模式
if (!DesignMode)
{
// 停止相机并释放相关资源
try
{
StopCamera();
}
catch (Exception ex)
{
Console.WriteLine("关闭相机时出错: " + ex.Message);
}
}
// 取消注册事件并释放设备管理器
if (_deviceManager != null)
{
_deviceManager.ImageReceived -= DeviceManager_ImageReceived;
_deviceManager.ConnectionStatusChanged -= DeviceManager_ConnectionStatusChanged;
_deviceManager.ConnectionException -= DeviceManager_ConnectionException;
_deviceManager.Dispose();
_deviceManager = null;
}
// 无论是否在设计模式下,都需要释放定时器
if (_errorDisplayTimer != null)
{
_errorDisplayTimer.Stop();
_errorDisplayTimer.Dispose();
_errorDisplayTimer = null;
}
// 无论是否在设计模式下,都需要释放图像资源
if (imageBox.Image != null)
{
imageBox.Image.Dispose();
imageBox.Image = null;
}
// 释放组件资源
if (components != null)
{
components.Dispose();
}
}
base.Dispose(disposing);
} }
} }
} }

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -106,9 +106,6 @@ namespace JoyD.Windows.CS.Toprie
// 设备ID列表 // 设备ID列表
private List<int> _deviceIds; private List<int> _deviceIds;
// 当前连接的设备ID
private int _currentDeviceId = -1;
// 是否已初始化 // 是否已初始化
private bool _isInitialized; private bool _isInitialized;
@@ -874,8 +871,6 @@ namespace JoyD.Windows.CS.Toprie
// 启动心跳检测 // 启动心跳检测
StartHeartbeat(); StartHeartbeat();
// 设置连接状态
_currentDeviceId = 1; // 简化处理,实际应该根据设备返回的数据设置
UpdateConnectionStatus(ConnectionStatus.Connected, "设备连接成功"); UpdateConnectionStatus(ConnectionStatus.Connected, "设备连接成功");
// 重置重连尝试计数 // 重置重连尝试计数
@@ -980,10 +975,7 @@ namespace JoyD.Windows.CS.Toprie
StopAllImageReceivers(); StopAllImageReceivers();
// 设置断开状态 // 设置断开状态
UpdateConnectionStatus(ConnectionStatus.Disconnected, "设备已断开连接"); UpdateConnectionStatus(ConnectionStatus.Disconnected, "设备已断开连接");
// 重置设备ID
_currentDeviceId = -1;
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -1163,7 +1155,6 @@ namespace JoyD.Windows.CS.Toprie
// 释放非托管资源 // 释放非托管资源
_isInitialized = false; _isInitialized = false;
_currentDeviceId = -1;
_disposed = true; _disposed = true;
} }

View File

@@ -69,5 +69,10 @@
<Compile Include="DeviceManager.cs" /> <Compile Include="DeviceManager.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Camera.resx">
<DependentUpon>Camera.cs</DependentUpon>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project> </Project>