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(); } } /// /// 将相机名称添加到状态栏 /// 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); } /// /// 清空状态栏中的相机名称 /// 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}' 对应的活动显示窗口。可能设备已关闭。"); } } } /// /// 根据持久化字符串创建或返回对应的窗体实例。 /// 这是DockPanel Suite反序列化布局时需要的回调方法。 /// /// 在XML中代表一个窗体的唯一字符串(通常是其类型名)。 /// 对应的窗体实例。 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; } /// /// 显示程序的默认窗口布局。 /// 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); } /// /// 【新增】保存当前窗口布局到XML文件。 /// 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); } /// /// 处理所有可停靠子窗体关闭事件的通用方法。 /// 通过取消关闭并隐藏窗口,来实现“假关闭”,以便后续能重新显示。 /// 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(); } } } } }