Files
CheckDevice/Check.Main/FrmMain.cs
17860779768 2e46747ba9 视觉修改
2025-08-25 16:33:58 +08:00

298 lines
12 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 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;
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);
}
private void FrmMain_Load(object sender, EventArgs e)
{
EasyPlcClient easyPlcClient = new EasyPlcClient("127.0.0.1", 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();
}
}
}
}
}