298 lines
12 KiB
C#
298 lines
12 KiB
C#
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();
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
}
|
||
}
|