10.20PLC+相机2.3视觉修改

This commit is contained in:
17860779768
2025-08-25 16:33:58 +08:00
committed by Maikouce China
commit dca4b2afac
52 changed files with 11698 additions and 0 deletions

316
Check.Main/FrmMain.cs Normal file
View File

@@ -0,0 +1,316 @@
using Check.Main.Camera;
using Check.Main.Common;
using Check.Main.UI;
using HslCommunication;
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.Threading.Tasks;
using System.Windows.Forms;
using WeifenLuo.WinFormsUI.Docking;
namespace Check.Main
{
public partial class FrmMain : Form
{
// private FrmCamConfig _formCameraConfig;
public static ModbusTcpService PlcClient;//定义全局PLC对象 --- 10.10添加①
private FrmConfig _frmConfig;
private FrmLog _formLog;
private ThemeBase _theme = new VS2015BlueTheme(); // 外观主题
public DockPanel MainDockPanel => this.dockPanel1;
private FormControlPanel _formControlPanel;
private FormStatistics _formStatistics;
private readonly string _layoutConfigFile = Path.Combine(Application.StartupPath, "layout.xml");
//用于反序列化时创建窗体的委托
private DeserializeDockContent _deserializeDockContent;
public FrmMain()
{
InitializeComponent();
dockPanel1.Theme = _theme;
IsMdiContainer = true;
_deserializeDockContent = new DeserializeDockContent(GetContentFromPersistString);
}
//添加 PLC 初始化方法 --- 10.10添加②
private async Task InitPlcConnection()
{
try
{
PlcClient = new ModbusTcpService("192.168.1.88", 502);
// await PlcClient.ConnectAsync();
ThreadSafeLogger.Log("[PLC] 已成功连接到汇川PLC");
}
catch (Exception ex)
{
ThreadSafeLogger.Log($"[PLC] 连接失败: {ex.Message}");
}
}
private async void FrmMain_Load(object sender, EventArgs e)//添加了一个async原来是private void FrmMain_Load-----10.10修改
{
// 初始化PLC连接-----10.10添加
await InitPlcConnection();
ModbusTcpService easyPlcClient = new ModbusTcpService("192.168.1.88", 502, 1);
//easyPlcClient.ConnectAsync();
_frmConfig = new FrmConfig { Text = "主程序配置" };
_formLog = new FrmLog { Text = "运行日志" };
_formStatistics = new FormStatistics { Text = "生产统计" };
_formControlPanel = new FormControlPanel { Text = "控制面板" };
// 为每个子窗体订阅 FormClosing 事件
_frmConfig.FormClosing += DockContent_FormClosing;
_formLog.FormClosing += DockContent_FormClosing;
_formStatistics.FormClosing += DockContent_FormClosing;
_formControlPanel.FormClosing += DockContent_FormClosing;
ThreadSafeLogger.Initialize();
ThreadSafeLogger.OnLogMessage += (msg) => { _formLog.AddLog(msg); };
// 2. 尝试加载布局文件
if (File.Exists(_layoutConfigFile))
{
try
{
// 使用委托加载布局
dockPanel1.LoadFromXml(_layoutConfigFile, _deserializeDockContent);
ThreadSafeLogger.Log("成功加载用户布局。");
}
catch (Exception ex)
{
ThreadSafeLogger.Log($"加载布局失败: {ex.Message}。将使用默认布局。");
// 如果加载失败,则使用默认布局
ShowDefaultLayout();
}
}
else
{
// 3. 如果布局文件不存在,则显示默认布局
ThreadSafeLogger.Log("未找到布局文件,使用默认布局。");
ShowDefaultLayout();
}
}
/// <summary>
/// 将相机名称添加到状态栏
/// </summary>
public void AddCameraToStatusStrip(string name)
{
if (statusStrip1.InvokeRequired)
{
statusStrip1.Invoke(new Action(() => AddCameraToStatusStrip(name)));
return;
}
var label = new ToolStripStatusLabel(name)
{
Name = "status_" + name,
BorderSides = ToolStripStatusLabelBorderSides.All,
Spring = false,
};
label.Click += StatusLabel_Click;
statusStrip1.Items.Add(label);
}
private void ToolStripMenuItem_Click(object sender, EventArgs e)
{
if (_formControlPanel == null || _formControlPanel.IsDisposed)
{
_formControlPanel = new FormControlPanel { Text = "控制面板" };
_formControlPanel.FormClosing += DockContent_FormClosing;
}
_formControlPanel.Show(this.dockPanel1);
}
/// <summary>
/// 清空状态栏中的相机名称
/// </summary>
public void ClearStatusStrip()
{
if (statusStrip1.InvokeRequired)
{
statusStrip1.Invoke(new Action(ClearStatusStrip));
return;
}
// 从后往前删,避免索引问题
for (int i = statusStrip1.Items.Count - 1; i >= 0; i--)
{
if (statusStrip1.Items[i] is ToolStripStatusLabel && statusStrip1.Items[i].Name.StartsWith("status_"))
{
statusStrip1.Items.RemoveAt(i);
}
}
}
private void StatusLabel_Click(object sender, EventArgs e)
{
if (sender is ToolStripStatusLabel label)
{
string cameraName = label.Text;
ThreadSafeLogger.Log($"用户点击状态栏标签: {cameraName}");
bool foundOriginal = false;
bool foundResult = false;
// 1. 尝试重新显示“原图”窗口
if (CameraManager.OriginalImageDisplays.TryGetValue(cameraName, out var originalDisplayForm))
{
// 使用 Show() 方法来重新激活或显示隐藏的窗口
// 传入 DockPanel 确保它知道在哪里显示
originalDisplayForm.Show(this.dockPanel1);
originalDisplayForm.Activate(); // 调用 Activate() 确保它成为当前焦点窗口
foundOriginal = true;
}
// 2. 尝试重新显示“结果”窗口
if (CameraManager.ResultImageDisplays.TryGetValue(cameraName, out var resultDisplayForm))
{
resultDisplayForm.Show(this.dockPanel1);
resultDisplayForm.Activate();
foundResult = true;
}
// 3. 提供反馈
if (foundOriginal || foundResult)
{
ThreadSafeLogger.Log($"已重新激活相机 '{cameraName}' 的显示窗口。");
}
else
{
ThreadSafeLogger.Log($"[警告] 未能找到相机 '{cameraName}' 对应的活动显示窗口。可能设备已关闭。");
}
}
}
/// <summary>
/// 根据持久化字符串创建或返回对应的窗体实例。
/// 这是DockPanel Suite反序列化布局时需要的回调方法。
/// </summary>
/// <param name="persistString">在XML中代表一个窗体的唯一字符串通常是其类型名。</param>
/// <returns>对应的窗体实例。</returns>
private IDockContent GetContentFromPersistString(string persistString)
{
if (persistString == typeof(FormControlPanel).ToString())
return _formControlPanel;
if (persistString == typeof(FrmConfig).ToString())
return _frmConfig;
if (persistString == typeof(FrmLog).ToString())
return _formLog;
if (persistString == typeof(FormStatistics).ToString())
return _formStatistics;
// 对于图像显示窗口,由于它们是动态创建的,情况会更复杂。
// 在当前设计中,我们不保存图像窗口的布局,它们会在应用相机配置时重新创建。
// 如果需要保存它们需要在CameraManager中管理它们的持久化字符串。
// 目前的设计下返回null是安全的。
return null;
}
/// <summary>
/// 显示程序的默认窗口布局。
/// </summary>
private void ShowDefaultLayout()
{
// 确保所有窗体都未被意外关闭
if (_formControlPanel.IsDisposed) _formControlPanel = new FormControlPanel();
if (_frmConfig.IsDisposed) _frmConfig = new FrmConfig();
if (_formLog.IsDisposed) _formLog = new FrmLog();
if (_formStatistics.IsDisposed) _formStatistics = new FormStatistics();
// 显示默认窗口
_frmConfig.Show(dockPanel1, DockState.DockLeft);
_formLog.Show(dockPanel1, DockState.DockBottom);
_formControlPanel.Show(dockPanel1, DockState.DockRight);
_formStatistics.Show(dockPanel1, DockState.DockTop);
}
/// <summary>
/// 【新增】保存当前窗口布局到XML文件。
/// </summary>
private void SaveLayout()
{
try
{
// 将当前DockPanel的布局保存到指定文件
dockPanel1.SaveAsXml(_layoutConfigFile);
ThreadSafeLogger.Log("布局已成功保存!");
//MessageBox.Show("布局已成功保存!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
ThreadSafeLogger.Log("保存布局失败: " + ex.Message);
//MessageBox.Show("保存布局失败: " + ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void FrmMain_FormClosing(object sender, FormClosingEventArgs e)
{
if (_formStatistics != null && !_formStatistics.IsDisposed)
{
StatisticsExporter.ExportToExcel(_formStatistics.CurrentStatistics, "Shutdown");
}
CameraManager.Shutdown();
ThreadSafeLogger.Shutdown();
}
private void ToolStripMenuSaveLayou_Click(object sender, EventArgs e)
{
SaveLayout();
}
private void ToolStripMenuItem_Click(object sender, EventArgs e)
{
//_frmConfig.Show();
// 如果窗体因意外被销毁,则重新创建它
if (_frmConfig == null || _frmConfig.IsDisposed)
{
_frmConfig = new FrmConfig() { Text = "主程序配置" };
_frmConfig.FormClosing += DockContent_FormClosing;
}
// 调用 Show() 会自动处理隐藏和显示逻辑
_frmConfig.Show(this.dockPanel1);
}
private void ToolStripMenuItem_Click(object sender, EventArgs e)
{
if (_formLog == null || _formLog.IsDisposed)
{
_formLog = new FrmLog { Text = "运行日志" };
_formLog.FormClosing += DockContent_FormClosing;
}
_formLog.Show(this.dockPanel1);
}
/// <summary>
/// 处理所有可停靠子窗体关闭事件的通用方法。
/// 通过取消关闭并隐藏窗口,来实现“假关闭”,以便后续能重新显示。
/// </summary>
private void DockContent_FormClosing(object sender, FormClosingEventArgs e)
{
// 检查关闭原因是否为用户点击了关闭按钮
if (e.CloseReason == CloseReason.UserClosing)
{
// 1. 取消真正的关闭Dispose操作防止窗体被销毁
e.Cancel = true;
// 2. 将窗口隐藏起来
// 我们需要将 sender 转换为 DockContent 类型来访问 Hide() 方法
if (sender is DockContent dockContent)
{
if (dockContent is FormImageDisplay imageDisplay)
{
ThreadSafeLogger.Log($"用户关闭了窗口 '{imageDisplay.Text}',已将其隐藏。");
}
dockContent.Hide();
}
}
}
}
}