Files
HkVisionPro/HkVisionPro.App/MainForm.cs
2026-03-24 11:10:15 +08:00

2123 lines
72 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 System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Configuration;
using ReaLTaiizor.Enum.Poison;
using ReaLTaiizor.Forms;
using ReaLTaiizor.Manager;
using VM.PlatformSDKCS;
using VM.Core;
using IMVSBcrModuCs;
using PointSetMODU_STDCs;
namespace HkVisionPro.App
{
public partial class MainForm : PoisonForm
{
/// <summary>
/// VisionMaster 方案管理对象(负责加载、执行、保存方案)。
/// </summary>
private readonly SolutionManager _solutionManager;
/// <summary>
/// 当前已加载的方案文件完整路径。
/// </summary>
private string _currentSolutionPath = string.Empty;
/// <summary>
/// 当前待完成加载的方案路径(用于异步加载结束时提交状态)。
/// </summary>
private string _pendingLoadSolutionPath = string.Empty;
/// <summary>
/// 当前是否已成功加载方案。
/// </summary>
private bool _isSolutionLoaded;
/// <summary>
/// 当前是否正在进行加载/执行/保存过程(用于按钮防重入)。
/// </summary>
private bool _isBusy;
/// <summary>
/// 当前是否处于方案加载过程SDK 可能异步加载,需显式防重入)。
/// </summary>
private bool _isSolutionLoading;
/// <summary>
/// 当前是否处于流程连续执行状态。
/// </summary>
private bool _isContinuousRunning;
/// <summary>
/// 当前连续执行的流程名称。
/// </summary>
private string _continuousProcedureName = string.Empty;
/// <summary>
/// 渲染互斥标记0=空闲1=渲染中),用于避免连续执行阶段并发读取结果。
/// </summary>
private int _renderingGuard;
/// <summary>
/// 当前选择的流程对象(用于流程执行与结果读取)。
/// </summary>
private VmProcedure _currentProcedure;
/// <summary>
/// 当前激活的条码模块工具对象(用于结果回调订阅与解绑)。
/// </summary>
private IMVSBcrModuTool _activeBcrTool;
/// <summary>
/// VM.Core 方案事件是否已注册。
/// </summary>
private bool _isVmSolutionEventRegistered;
/// <summary>
/// 关闭阶段资源是否已释放(用于防止 FormClosing 与 OnFormClosed 重复释放)。
/// </summary>
private bool _isClosingResourcesReleased;
/// <summary>
/// 流程字符串输出名称(支持配置,默认 out
/// </summary>
private readonly string _procedureOutputCodeName;
/// <summary>
/// 流程整数输出名称(支持配置,默认 out0
/// </summary>
private readonly string _procedureOutputNumberName;
/// <summary>
/// 渲染图像输出名称(支持配置,默认 Image
/// </summary>
private readonly string _renderImageOutputName;
/// <summary>
/// 渲染 ROI 输出名称(支持配置,默认 ROI
/// </summary>
private readonly string _renderRoiOutputName;
/// <summary>
/// 渲染文本输出名称(支持配置,默认 code
/// </summary>
private readonly string _renderCodeOutputName;
/// <summary>
/// VisionMaster: 方案正在加载中的错误码IMVS_EC_SOLUTION_LOADING
/// </summary>
private const int VmErrorCodeSolutionLoading = -536870899;
/// <summary>
/// VisionMaster: 模块已处于连续执行中的错误码IMVS_EC_MODULE_CONTINUE_EXECUTE
/// </summary>
private const int VmErrorCodeModuleContinueExecute = -536870127;
/// <summary>
/// ReaLTaiizor Poison 样式管理器(用于统一暗色主题风格元数据)。
/// </summary>
private readonly PoisonStyleManager _poisonStyleManager;
/// <summary>
/// 主背景颜色。
/// </summary>
private static readonly Color DarkThemeMainBackColor = Color.FromArgb(28, 28, 30);
/// <summary>
/// 分组容器背景颜色。
/// </summary>
private static readonly Color DarkThemePanelBackColor = Color.FromArgb(36, 36, 40);
/// <summary>
/// 输入类控件背景颜色。
/// </summary>
private static readonly Color DarkThemeInputBackColor = Color.FromArgb(45, 45, 48);
/// <summary>
/// 前景文字颜色。
/// </summary>
private static readonly Color DarkThemeForeColor = Color.FromArgb(230, 230, 230);
/// <summary>
/// 主题强调色(按钮边框、选中态)。
/// </summary>
private static readonly Color DarkThemeAccentColor = Color.FromArgb(0, 174, 219);
/// <summary>
/// 分组框边框颜色。
/// </summary>
private static readonly Color DarkThemeGroupBorderColor = Color.FromArgb(78, 78, 82);
/// <summary>
/// 分组框标题底色。
/// </summary>
private static readonly Color DarkThemeGroupTitleBackColor = Color.FromArgb(48, 48, 52);
/// <summary>
/// 列表选中项背景色。
/// </summary>
private static readonly Color DarkThemeSelectedBackColor = Color.FromArgb(0, 120, 170);
public MainForm()
{
InitializeComponent();
_poisonStyleManager = components == null ? new PoisonStyleManager() : new PoisonStyleManager(components);
_solutionManager = new SolutionManager();
_procedureOutputCodeName = GetAppSettingOrDefault("ProcedureOutputCodeName", "out");
_procedureOutputNumberName = GetAppSettingOrDefault("ProcedureOutputNumberName", "out0");
_renderImageOutputName = GetAppSettingOrDefault("RenderImageOutputName", "Image");
_renderRoiOutputName = GetAppSettingOrDefault("RenderRoiOutputName", "ROI");
_renderCodeOutputName = GetAppSettingOrDefault("RenderCodeOutputName", "code");
RegisterSolutionCallbacks();
RegisterVmSolutionCallbacks();
// 统一联动:流程切换时自动刷新模块列表。
cmbProcdure.SelectedIndexChanged += cmbProcdure_SelectedIndexChanged;
// 视图改为 pictureBox 展示时,统一设置渲染行为。
pictureBox1.SizeMode = PictureBoxSizeMode.Zoom;
pictureBox1.BackColor = Color.Black;
InitializeDarkTheme();
UpdateUiState(false);
txtSolutionAddress.Text = string.Empty;
}
/// <summary>
/// 初始化 ReaLTaiizor 暗色主题,并将原生 WinForms 控件统一成暗色风格。
/// </summary>
private void InitializeDarkTheme()
{
_poisonStyleManager.Owner = this;
_poisonStyleManager.Theme = ThemeStyle.Dark;
_poisonStyleManager.Style = ColorStyle.Teal;
StyleManager = _poisonStyleManager;
Theme = ThemeStyle.Dark;
Style = ColorStyle.Teal;
ApplyDarkThemeToNativeControls(this);
}
/// <summary>
/// 递归应用暗色主题到原生 WinForms 控件。
/// </summary>
/// <param name="rootControl">递归起始控件。</param>
private void ApplyDarkThemeToNativeControls(Control rootControl)
{
if (rootControl == null)
{
return;
}
if (ReferenceEquals(rootControl, this))
{
BackColor = DarkThemeMainBackColor;
ForeColor = DarkThemeForeColor;
}
foreach (Control control in rootControl.Controls)
{
if (ReferenceEquals(control, vmProcedureConfigControl1) || ReferenceEquals(control, vmParamsConfigControl1))
{
continue;
}
if (control is GroupBox groupBox)
{
ApplyGroupBoxTheme(groupBox);
}
else if (control is Button button)
{
ApplyButtonTheme(button);
}
else if (control is TextBox textBox)
{
ApplyTextBoxTheme(textBox);
}
else if (control is ComboBox comboBox)
{
ApplyComboBoxTheme(comboBox);
}
else if (control is ListBox listBox)
{
ApplyListBoxTheme(listBox);
}
else if (control is Label label)
{
ApplyLabelTheme(label);
}
else if (control is PictureBox pictureBox)
{
ApplyPictureBoxTheme(pictureBox);
}
else
{
control.BackColor = DarkThemeMainBackColor;
control.ForeColor = DarkThemeForeColor;
}
if (control.HasChildren)
{
ApplyDarkThemeToNativeControls(control);
}
}
}
/// <summary>
/// 设置分组框控件暗色样式。
/// </summary>
/// <param name="groupBox">目标分组框。</param>
private void ApplyGroupBoxTheme(GroupBox groupBox)
{
groupBox.BackColor = DarkThemePanelBackColor;
groupBox.ForeColor = DarkThemeForeColor;
groupBox.Paint -= ThemedGroupBox_Paint;
groupBox.Paint += ThemedGroupBox_Paint;
}
/// <summary>
/// 自定义绘制分组框标题与边框,统一暗色视觉层级。
/// </summary>
/// <param name="sender">事件发起对象。</param>
/// <param name="e">绘制事件参数。</param>
private void ThemedGroupBox_Paint(object sender, PaintEventArgs e)
{
var groupBox = sender as GroupBox;
if (groupBox == null)
{
return;
}
e.Graphics.Clear(groupBox.BackColor);
var groupText = groupBox.Text ?? string.Empty;
var textSize = TextRenderer.MeasureText(groupText, groupBox.Font);
var textRect = new Rectangle(12, 0, textSize.Width + 8, textSize.Height);
var borderRect = new Rectangle(
1,
Math.Max(1, textRect.Height / 2),
Math.Max(1, groupBox.Width - 2),
Math.Max(1, groupBox.Height - (textRect.Height / 2) - 2));
using (var borderPen = new Pen(DarkThemeGroupBorderColor))
{
e.Graphics.DrawRectangle(borderPen, borderRect);
}
using (var titleBackBrush = new SolidBrush(DarkThemeGroupTitleBackColor))
{
e.Graphics.FillRectangle(titleBackBrush, textRect);
}
TextRenderer.DrawText(
e.Graphics,
groupText,
groupBox.Font,
textRect,
DarkThemeForeColor,
TextFormatFlags.Left | TextFormatFlags.VerticalCenter | TextFormatFlags.EndEllipsis);
}
/// <summary>
/// 设置按钮控件暗色样式。
/// </summary>
/// <param name="button">目标按钮。</param>
private void ApplyButtonTheme(Button button)
{
button.FlatStyle = FlatStyle.Flat;
button.FlatAppearance.BorderSize = 1;
button.FlatAppearance.BorderColor = DarkThemeAccentColor;
button.FlatAppearance.MouseOverBackColor = Color.FromArgb(52, 52, 56);
button.FlatAppearance.MouseDownBackColor = Color.FromArgb(62, 62, 66);
button.BackColor = DarkThemePanelBackColor;
button.ForeColor = DarkThemeForeColor;
button.UseVisualStyleBackColor = false;
button.Paint -= ThemedButton_Paint;
button.Paint += ThemedButton_Paint;
}
/// <summary>
/// 自定义绘制按钮文本,确保暗色主题下文字始终可读。
/// </summary>
/// <param name="sender">事件发起对象。</param>
/// <param name="e">绘制事件参数。</param>
private void ThemedButton_Paint(object sender, PaintEventArgs e)
{
var button = sender as Button;
if (button == null)
{
return;
}
var textColor = button.Enabled ? DarkThemeForeColor : Color.FromArgb(170, 170, 170);
var textBounds = button.ClientRectangle;
TextRenderer.DrawText(
e.Graphics,
button.Text,
button.Font,
textBounds,
textColor,
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.EndEllipsis);
}
/// <summary>
/// 设置文本框控件暗色样式。
/// </summary>
/// <param name="textBox">目标文本框。</param>
private void ApplyTextBoxTheme(TextBox textBox)
{
textBox.BorderStyle = BorderStyle.FixedSingle;
textBox.BackColor = DarkThemeInputBackColor;
textBox.ForeColor = DarkThemeForeColor;
}
/// <summary>
/// 设置下拉框控件暗色样式,并接管绘制以统一列表区域颜色。
/// </summary>
/// <param name="comboBox">目标下拉框。</param>
private void ApplyComboBoxTheme(ComboBox comboBox)
{
comboBox.FlatStyle = FlatStyle.Flat;
comboBox.BackColor = DarkThemeInputBackColor;
comboBox.ForeColor = DarkThemeForeColor;
comboBox.DrawMode = DrawMode.OwnerDrawFixed;
comboBox.DrawItem -= ThemedComboBox_DrawItem;
comboBox.DrawItem += ThemedComboBox_DrawItem;
}
/// <summary>
/// 设置列表框控件暗色样式,并接管绘制以统一选中高亮。
/// </summary>
/// <param name="listBox">目标列表框。</param>
private void ApplyListBoxTheme(ListBox listBox)
{
listBox.BorderStyle = BorderStyle.FixedSingle;
listBox.BackColor = DarkThemeInputBackColor;
listBox.ForeColor = DarkThemeForeColor;
listBox.Font = new Font("微软雅黑", 9F, FontStyle.Regular, GraphicsUnit.Point, 134);
listBox.ItemHeight = Math.Max(18, TextRenderer.MeasureText("日志", listBox.Font).Height + 2);
listBox.DrawMode = DrawMode.OwnerDrawFixed;
listBox.DrawItem -= ThemedListBox_DrawItem;
listBox.DrawItem += ThemedListBox_DrawItem;
}
/// <summary>
/// 设置标签控件暗色样式。
/// </summary>
/// <param name="label">目标标签。</param>
private void ApplyLabelTheme(Label label)
{
label.BackColor = Color.Transparent;
label.ForeColor = DarkThemeForeColor;
}
/// <summary>
/// 设置图片显示区域暗色样式。
/// </summary>
/// <param name="pictureBox">目标图片框。</param>
private void ApplyPictureBoxTheme(PictureBox pictureBox)
{
pictureBox.BackColor = Color.Black;
pictureBox.BorderStyle = BorderStyle.FixedSingle;
}
/// <summary>
/// 自定义绘制暗色下拉框项。
/// </summary>
/// <param name="sender">事件发起对象。</param>
/// <param name="e">绘制事件参数。</param>
private void ThemedComboBox_DrawItem(object sender, DrawItemEventArgs e)
{
e.DrawBackground();
if (e.Index < 0)
{
return;
}
var comboBox = sender as ComboBox;
var itemText = comboBox?.Items[e.Index]?.ToString() ?? string.Empty;
var isSelected = (e.State & DrawItemState.Selected) == DrawItemState.Selected;
var backColor = isSelected ? DarkThemeSelectedBackColor : DarkThemeInputBackColor;
using (var backBrush = new SolidBrush(backColor))
using (var textBrush = new SolidBrush(DarkThemeForeColor))
{
e.Graphics.FillRectangle(backBrush, e.Bounds);
e.Graphics.DrawString(itemText, e.Font, textBrush, e.Bounds);
}
e.DrawFocusRectangle();
}
/// <summary>
/// 自定义绘制暗色列表框项。
/// </summary>
/// <param name="sender">事件发起对象。</param>
/// <param name="e">绘制事件参数。</param>
private void ThemedListBox_DrawItem(object sender, DrawItemEventArgs e)
{
if (e.Index < 0)
{
return;
}
var listBox = sender as ListBox;
var itemText = listBox?.Items[e.Index]?.ToString() ?? string.Empty;
var isSelected = (e.State & DrawItemState.Selected) == DrawItemState.Selected;
var backColor = isSelected ? DarkThemeSelectedBackColor : DarkThemeInputBackColor;
var textColor = listBox != null && listBox.Enabled ? DarkThemeForeColor : Color.FromArgb(170, 170, 170);
var textRect = new Rectangle(e.Bounds.X + 6, e.Bounds.Y + 1, Math.Max(1, e.Bounds.Width - 8), Math.Max(1, e.Bounds.Height - 2));
using (var backBrush = new SolidBrush(backColor))
using (var textBrush = new SolidBrush(textColor))
{
e.Graphics.FillRectangle(backBrush, e.Bounds);
e.Graphics.DrawString(itemText, e.Font, textBrush, textRect);
}
e.DrawFocusRectangle();
}
/// <summary>
/// 注册 VisionMaster 方案生命周期回调,用于关键节点日志输出。
/// </summary>
private void RegisterSolutionCallbacks()
{
_solutionManager.OnSolutionLoadBeginCallBack += OnSolutionLoadBegin;
_solutionManager.OnSolutionLoadProgressCallBack += OnSolutionLoadProgress;
_solutionManager.OnSolutionLoadEndCallBack += OnSolutionLoadEnd;
_solutionManager.OnSolutionSaveBeginCallBack += OnSolutionSaveBegin;
_solutionManager.OnSolutionSaveProgressCallBack += OnSolutionSaveProgress;
_solutionManager.OnSolutionSaveEndCallBack += OnSolutionSaveEnd;
}
/// <summary>
/// 注销 VisionMaster 回调,避免窗体关闭后的委托残留。
/// </summary>
private void UnregisterSolutionCallbacks()
{
_solutionManager.OnSolutionLoadBeginCallBack -= OnSolutionLoadBegin;
_solutionManager.OnSolutionLoadProgressCallBack -= OnSolutionLoadProgress;
_solutionManager.OnSolutionLoadEndCallBack -= OnSolutionLoadEnd;
_solutionManager.OnSolutionSaveBeginCallBack -= OnSolutionSaveBegin;
_solutionManager.OnSolutionSaveProgressCallBack -= OnSolutionSaveProgress;
_solutionManager.OnSolutionSaveEndCallBack -= OnSolutionSaveEnd;
}
/// <summary>
/// 注册 VM.Core 执行状态回调。
/// </summary>
private void RegisterVmSolutionCallbacks()
{
if (_isVmSolutionEventRegistered)
{
return;
}
VmSolution.OnWorkStatusEvent += VmSolution_OnWorkStatusEvent;
_isVmSolutionEventRegistered = true;
}
/// <summary>
/// 注销 VM.Core 执行状态回调。
/// </summary>
private void UnregisterVmSolutionCallbacks()
{
if (!_isVmSolutionEventRegistered)
{
return;
}
VmSolution.OnWorkStatusEvent -= VmSolution_OnWorkStatusEvent;
_isVmSolutionEventRegistered = false;
}
/// <summary>
/// 解绑当前流程结束回调,防止重复订阅和对象残留。
/// </summary>
private void UnbindProcedureWorkEndCallback()
{
if (_currentProcedure == null)
{
return;
}
try
{
_currentProcedure.OnWorkEndStatusCallBack -= Process_OnWorkEndStatusCallBack;
}
catch (Exception ex)
{
WriteLog($"解绑流程结束回调异常:{ex.Message}");
}
}
/// <summary>
/// 更新连续执行状态及按钮文案。
/// </summary>
/// <param name="isRunning">是否连续执行中。</param>
/// <param name="procedureName">连续执行流程名称。</param>
private void SetContinuousRunState(bool isRunning, string procedureName)
{
_isContinuousRunning = isRunning;
_continuousProcedureName = isRunning ? (procedureName ?? string.Empty) : string.Empty;
btnProExecAlway.Text = isRunning ? "停止连续" : "连续执行";
}
/// <summary>
/// 停止当前连续执行流程(若有),并重置连续执行状态。
/// </summary>
/// <param name="reason">停止原因(用于日志)。</param>
private void StopContinuousRunIfNeeded(string reason)
{
if (!_isContinuousRunning)
{
return;
}
try
{
if (_currentProcedure != null)
{
_currentProcedure.ContinuousRunEnable = false;
}
if (!string.IsNullOrWhiteSpace(_continuousProcedureName))
{
WriteLog($"连续执行流程已停止:{_continuousProcedureName}{reason}");
}
else
{
WriteLog($"连续执行流程已停止({reason})。");
}
}
catch (Exception ex)
{
WriteLog($"停止连续执行异常:{ex.Message}");
}
finally
{
SetContinuousRunState(false, string.Empty);
}
}
/// <summary>
/// 释放 VisionMaster 相关资源(幂等,允许重复调用)。
/// </summary>
private void ReleaseSdkResources()
{
if (_isClosingResourcesReleased)
{
return;
}
_isClosingResourcesReleased = true;
try
{
UnregisterSolutionCallbacks();
UnregisterVmSolutionCallbacks();
if (_activeBcrTool != null)
{
_activeBcrTool.ModuleResultCallBackArrived -= BcrModuleResultCallBackArrived;
_activeBcrTool = null;
}
StopContinuousRunIfNeeded("窗体关闭释放资源");
UnbindProcedureWorkEndCallback();
_currentProcedure = null;
vmParamsConfigControl1.ModuleSource = null;
ClearPictureBoxImage();
if (_isSolutionLoaded)
{
_solutionManager.CloseSolution();
_isSolutionLoaded = false;
}
_currentSolutionPath = string.Empty;
}
catch (Exception ex)
{
WriteLog($"关闭窗体时释放资源异常:{ex.Message}");
}
}
/// <summary>
/// 方案加载开始回调。
/// </summary>
private void OnSolutionLoadBegin(ImvsSdkDefine.IMVS_SOLUTION_LOAD_BEGEIN_INFO beginInfo)
{
_isSolutionLoading = true;
WriteLog("方案加载开始。");
if (!IsHandleCreated || IsDisposed)
{
return;
}
BeginInvoke(new Action(() => UpdateUiState(_isBusy)));
}
/// <summary>
/// 方案加载进度回调。
/// </summary>
private void OnSolutionLoadProgress(ImvsSdkDefine.IMVS_SOLUTION_LOAD_PROCESS_INFO progressInfo)
{
WriteLog($"方案加载进度:{progressInfo.nProcess}%");
}
/// <summary>
/// 方案加载结束回调。
/// </summary>
private void OnSolutionLoadEnd(ImvsSdkDefine.IMVS_SOLUTION_LOAD_END_INFO endInfo)
{
_isSolutionLoading = false;
WriteLog($"方案加载结束,状态码:{endInfo.nStatus}");
if (!IsHandleCreated || IsDisposed)
{
return;
}
BeginInvoke(new Action(() =>
{
try
{
if (endInfo.nStatus == 0)
{
_currentSolutionPath = _pendingLoadSolutionPath;
_isSolutionLoaded = true;
BindFirstModuleToRenderControl();
UpdateProcedureList();
WriteLog($"方案加载成功:{_currentSolutionPath}");
MessageBox.Show(this, "方案加载成功。", "加载方案", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
_isSolutionLoaded = false;
ResetProcedureAndModuleState();
MessageBox.Show(this, $"方案加载失败,状态码:{endInfo.nStatus}", "加载方案", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
catch (Exception ex)
{
_isSolutionLoaded = false;
ResetProcedureAndModuleState();
HandleException("加载方案回调处理", ex);
}
finally
{
_pendingLoadSolutionPath = string.Empty;
UpdateUiState(false);
}
}));
}
/// <summary>
/// 方案保存开始回调。
/// </summary>
private void OnSolutionSaveBegin(ImvsSdkDefine.IMVS_SOLUTION_SAVE_BEGEIN_INFO beginInfo)
{
WriteLog("方案保存开始。");
}
/// <summary>
/// 方案保存进度回调。
/// </summary>
private void OnSolutionSaveProgress(ImvsSdkDefine.IMVS_SOLUTION_SAVE_PROCESS_INFO progressInfo)
{
WriteLog($"方案保存进度:{progressInfo.nProcess}%");
}
/// <summary>
/// 方案保存结束回调。
/// </summary>
private void OnSolutionSaveEnd(ImvsSdkDefine.IMVS_SOLUTION_SAVE_END_INFO endInfo)
{
WriteLog($"方案保存结束,状态码:{endInfo.nStatus}");
}
/// <summary>
/// VM.Core 方案执行状态回调。
/// </summary>
/// <param name="workStatusInfo">执行状态信息。</param>
private void VmSolution_OnWorkStatusEvent(ImvsSdkDefine.IMVS_MODULE_WORK_STAUS workStatusInfo)
{
if (!IsHandleCreated || IsDisposed)
{
return;
}
BeginInvoke(new Action(() =>
{
try
{
if (workStatusInfo.nWorkStatus != 0)
{
return;
}
if (_currentProcedure == null)
{
return;
}
// 连续执行阶段仅使用流程结束回调做渲染,避免与流程回调并发读取同一份结果。
if (_isContinuousRunning)
{
return;
}
RenderProcedureResultToPictureBox(_currentProcedure, "方案执行状态回调");
}
catch (Exception ex)
{
WriteLog($"方案执行回调异常:{ex.Message}");
}
}));
}
/// <summary>
/// 流程执行结束回调:读取输出并渲染到 pictureBox。
/// </summary>
private void Process_OnWorkEndStatusCallBack(object sender, EventArgs e)
{
try
{
var procedure = sender as VmProcedure;
if (procedure == null)
{
return;
}
RenderProcedureResultToPictureBox(procedure, "流程结束回调");
}
catch (Exception ex)
{
WriteLog($"流程执行回调异常:{ex.Message}");
}
}
/// <summary>
/// 将流程结果图像、ROI、识别文本渲染到 pictureBox。
/// </summary>
/// <param name="procedure">流程对象。</param>
/// <param name="triggerSource">触发源描述(用于日志)。</param>
private void RenderProcedureResultToPictureBox(VmProcedure procedure, string triggerSource)
{
if (procedure == null)
{
return;
}
if (System.Threading.Interlocked.Exchange(ref _renderingGuard, 1) == 1)
{
return;
}
try
{
// 字符串读取仅尝试字符串候选名,避免对 int 输出调用 GetOutputString 引发 SDK 内部异常。
var code = GetProcedureOutputString(procedure, _renderCodeOutputName, _procedureOutputCodeName, "out0", "code", "Code");
// 整型读取仅尝试整型候选名。
var codeNum = GetProcedureOutputInt(procedure, _procedureOutputNumberName, "out");
var bitmap = GetProcedureOutputBitmap(procedure, _renderImageOutputName, "ImageData", "Image", "image");
if (bitmap == null)
{
WriteLog($"{triggerSource}未读取到可渲染图像,输出名候选:{_renderImageOutputName}/ImageData/Image/image");
return;
}
try
{
var roiList = GetProcedureOutputBoxList(procedure, _renderRoiOutputName, "InputROI", "rect", "outline", "ROI", "roi");
var renderBitmap = DrawROIOnBitmap(bitmap, roiList, Color.Cyan, 2, code);
SetPictureBoxImage(renderBitmap);
WriteLog($"{triggerSource}渲染完成Code={code}CodeNumber={codeNum}ROI数={roiList.Count}");
}
finally
{
bitmap.Dispose();
}
}
finally
{
System.Threading.Interlocked.Exchange(ref _renderingGuard, 0);
}
}
/// <summary>
/// 从流程输出中读取图像并转换为 Bitmap。
/// </summary>
/// <param name="procedure">流程对象。</param>
/// <param name="outputNames">候选输出名列表。</param>
/// <returns>Bitmap 对象;读取失败返回 null。</returns>
private Bitmap GetProcedureOutputBitmap(VmProcedure procedure, params string[] outputNames)
{
foreach (var outputName in outputNames)
{
if (string.IsNullOrWhiteSpace(outputName))
{
continue;
}
try
{
var imageData = procedure.ModuResult.GetOutputImageV2(outputName);
if (imageData == null)
{
continue;
}
var bitmap = imageData.ToBitmap();
if (bitmap != null)
{
return bitmap;
}
}
catch
{
// 候选输出名逐个容错尝试,避免单个输出不存在导致主流程中断。
}
}
return null;
}
/// <summary>
/// 从流程输出中读取 ROI 列表。
/// </summary>
/// <param name="procedure">流程对象。</param>
/// <param name="outputNames">候选输出名列表。</param>
/// <returns>ROI 列表;读取失败返回空列表。</returns>
private List<RectBox> GetProcedureOutputBoxList(VmProcedure procedure, params string[] outputNames)
{
foreach (var outputName in outputNames)
{
if (string.IsNullOrWhiteSpace(outputName))
{
continue;
}
try
{
var boxList = procedure.ModuResult.GetOutputBoxArray(outputName);
if (boxList != null)
{
return boxList;
}
}
catch
{
// 候选输出名逐个容错尝试,避免单个输出不存在导致主流程中断。
}
}
return new List<RectBox>();
}
/// <summary>
/// 在图像上绘制 ROI 轮廓和识别文本。
/// </summary>
/// <param name="sourceBitmap">源图像。</param>
/// <param name="boxList">ROI 列表。</param>
/// <param name="color">ROI 颜色。</param>
/// <param name="thickness">线宽。</param>
/// <param name="code">识别文本。</param>
/// <returns>绘制后的新 Bitmap。</returns>
private Bitmap DrawROIOnBitmap(Bitmap sourceBitmap, List<RectBox> boxList, Color color, int thickness, string code)
{
if (sourceBitmap == null)
{
return null;
}
var bitmap = new Bitmap(sourceBitmap);
using (var graphics = Graphics.FromImage(bitmap))
using (var pen = new Pen(color, thickness))
{
if (boxList != null && boxList.Count > 0)
{
foreach (var rect in boxList)
{
var cx = rect.CenterPoint.X;
var cy = rect.CenterPoint.Y;
var width = rect.BoxWidth;
var height = rect.BoxHeight;
var angle = rect.Angle;
var points = new System.Drawing.PointF[4];
points[0] = new System.Drawing.PointF(-width / 2f, -height / 2f);
points[1] = new System.Drawing.PointF(width / 2f, -height / 2f);
points[2] = new System.Drawing.PointF(width / 2f, height / 2f);
points[3] = new System.Drawing.PointF(-width / 2f, height / 2f);
var rad = angle * Math.PI / 180.0;
for (var i = 0; i < points.Length; i++)
{
var x = points[i].X;
var y = points[i].Y;
points[i].X = (float)(x * Math.Cos(rad) - y * Math.Sin(rad) + cx);
points[i].Y = (float)(x * Math.Sin(rad) + y * Math.Cos(rad) + cy);
}
graphics.DrawPolygon(pen, points);
}
}
if (!string.IsNullOrWhiteSpace(code))
{
using (var font = new Font("Arial", 16, FontStyle.Bold))
using (var textBrush = new SolidBrush(Color.Orange))
using (var backgroundBrush = new SolidBrush(Color.FromArgb(160, 0, 0, 0)))
{
var textSize = graphics.MeasureString(code, font);
var x = Math.Max(0, bitmap.Width - textSize.Width - 12f);
const float y = 10f;
graphics.FillRectangle(backgroundBrush, x - 4f, y - 2f, textSize.Width + 8f, textSize.Height + 4f);
graphics.DrawString(code, font, textBrush, x, y);
}
}
}
return bitmap;
}
/// <summary>
/// 安全更新 pictureBox 图像并释放旧图,避免内存泄漏。
/// </summary>
/// <param name="bitmap">新图像。</param>
private void SetPictureBoxImage(Bitmap bitmap)
{
if (bitmap == null)
{
return;
}
if (!IsHandleCreated || IsDisposed)
{
bitmap.Dispose();
return;
}
void UpdateAction()
{
var oldImage = pictureBox1.Image;
pictureBox1.Image = bitmap;
oldImage?.Dispose();
}
if (pictureBox1.InvokeRequired)
{
pictureBox1.BeginInvoke(new Action(UpdateAction));
}
else
{
UpdateAction();
}
}
/// <summary>
/// 清空 pictureBox 图像并释放旧图。
/// </summary>
private void ClearPictureBoxImage()
{
if (!IsHandleCreated || IsDisposed)
{
return;
}
void ClearAction()
{
var oldImage = pictureBox1.Image;
pictureBox1.Image = null;
oldImage?.Dispose();
}
if (pictureBox1.InvokeRequired)
{
pictureBox1.BeginInvoke(new Action(ClearAction));
}
else
{
ClearAction();
}
}
/// <summary>
/// 条码模块执行结果回调。
/// </summary>
private void BcrModuleResultCallBackArrived(object sender, EventArgs e)
{
if (!(sender is IMVSBcrModuTool tool))
{
return;
}
var codeStr = string.Empty;
try
{
if (tool.ModuResult?.CodeStr != null && tool.ModuResult.CodeStr.Count > 0)
{
codeStr = tool.ModuResult.CodeStr[0];
}
}
catch (Exception ex)
{
WriteLog($"读取条码结果异常:{ex.Message}");
}
if (!IsHandleCreated || IsDisposed)
{
return;
}
BeginInvoke(new Action(() =>
{
WriteLog($"条码识别结果:{codeStr}");
}));
}
/// <summary>
/// 统一日志输出(当前工程无独立日志控件,先写控制台)。
/// </summary>
/// <param name="message">日志内容。</param>
private void WriteLog(string message)
{
var consoleLogText = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] {message}";
var listBoxLogText = $"[{DateTime.Now:HH:mm:ss.fff}] {message}";
Console.WriteLine(consoleLogText);
if (!IsHandleCreated || IsDisposed)
{
return;
}
if (listBox1.InvokeRequired)
{
listBox1.BeginInvoke(new Action(() =>
{
listBox1.Items.Add(listBoxLogText);
listBox1.TopIndex = listBox1.Items.Count - 1;
}));
}
else
{
listBox1.Items.Add(listBoxLogText);
listBox1.TopIndex = listBox1.Items.Count - 1;
}
}
/// <summary>
/// 更新按钮启用状态,防止流程重入和非法操作。
/// </summary>
/// <param name="busy">当前是否忙碌。</param>
private void UpdateUiState(bool busy)
{
_isBusy = busy;
var canLoadNewSolution = !busy && !_isSolutionLoading;
var canOperateLoadedSolution = !busy && _isSolutionLoaded && !_isSolutionLoading;
btnSelctedSolution.Enabled = !busy && !_isSolutionLoading;
btnLoadSolution.Enabled = canLoadNewSolution;
btnExecutSolution.Enabled = canOperateLoadedSolution;
btnSaveSolution.Enabled = canOperateLoadedSolution;
btnProImport.Enabled = canOperateLoadedSolution;
btnProExpo.Enabled = canOperateLoadedSolution;
btnProDel.Enabled = canOperateLoadedSolution;
btnProExecOnce.Enabled = canOperateLoadedSolution;
btnProExecAlway.Enabled = canOperateLoadedSolution;
btnProExecStop.Enabled = canOperateLoadedSolution && _isContinuousRunning;
btnModuleBindingPar.Enabled = canOperateLoadedSolution;
btnModuleExec.Enabled = canOperateLoadedSolution;
btnModuleRenderResult.Enabled = canOperateLoadedSolution;
cmbProcdure.Enabled = canOperateLoadedSolution;
cmbModule.Enabled = canOperateLoadedSolution;
// 保持连续执行按钮文案与状态一致。
btnProExecAlway.Text = _isContinuousRunning ? "停止连续" : "连续执行";
}
/// <summary>
/// 校验并返回文本框中的方案路径。
/// </summary>
/// <returns>合法的完整路径。</returns>
private string GetValidatedSolutionPath()
{
var path = txtSolutionAddress.Text?.Trim() ?? string.Empty;
if (string.IsNullOrWhiteSpace(path))
{
throw new InvalidOperationException("请先选择方案文件。\n支持格式*.sol");
}
if (!File.Exists(path))
{
throw new FileNotFoundException("方案文件不存在,请重新选择。", path);
}
return path;
}
/// <summary>
/// 根据已加载方案,尝试把首个模块绑定到渲染控件以显示结果。
/// </summary>
private void BindFirstModuleToRenderControl()
{
// 界面已切换为 pictureBox 渲染,加载新方案时先清空上次显示图像。
ClearPictureBoxImage();
}
/// <summary>
/// 统一异常处理:记录日志并弹出错误提示。
/// </summary>
/// <param name="caption">弹窗标题。</param>
/// <param name="ex">异常对象。</param>
private void HandleException(string caption, Exception ex)
{
var vmException = VmExceptionTool.GetVmException(ex);
var errorMessage = vmException == null
? ex.Message
: $"{vmException.errorMessage} (Code={vmException.errorCode})";
WriteLog($"{caption}失败:{errorMessage}");
MessageBox.Show(this, errorMessage, caption, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
/// <summary>
/// 校验当前是否已加载方案。
/// </summary>
private bool EnsureSolutionLoaded(string operationName)
{
if (_isSolutionLoaded)
{
return true;
}
MessageBox.Show(this, $"请先加载方案后再{operationName}。", operationName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
return false;
}
/// <summary>
/// 判断异常是否为“方案正在加载中”错误。
/// </summary>
/// <param name="ex">异常对象。</param>
/// <returns>若是加载中错误则返回 true。</returns>
private static bool IsSolutionLoadingVmException(Exception ex)
{
if (ex == null)
{
return false;
}
var vmException = VmExceptionTool.GetVmException(ex);
return vmException != null && vmException.errorCode == VmErrorCodeSolutionLoading;
}
/// <summary>
/// 判断异常是否为“模块已处于连续执行”错误。
/// </summary>
/// <param name="ex">异常对象。</param>
/// <returns>若是连续执行中错误则返回 true。</returns>
private static bool IsModuleContinueExecuteVmException(Exception ex)
{
if (ex == null)
{
return false;
}
var vmException = VmExceptionTool.GetVmException(ex);
return vmException != null && vmException.errorCode == VmErrorCodeModuleContinueExecute;
}
/// <summary>
/// 调用 SDK 加载方案,并针对“方案正在加载中”做有限重试。
/// </summary>
/// <param name="solutionPath">方案路径。</param>
/// <param name="retryCount">最大重试次数。</param>
/// <param name="retryDelayMs">每次重试间隔(毫秒)。</param>
private void LoadSolutionWithRetry(string solutionPath, int retryCount, int retryDelayMs)
{
Exception lastException = null;
for (var attempt = 0; attempt <= retryCount; attempt++)
{
try
{
_solutionManager.LoadSolution(solutionPath, string.Empty);
return;
}
catch (Exception ex)
{
if (!IsSolutionLoadingVmException(ex) || attempt >= retryCount)
{
throw;
}
lastException = ex;
WriteLog($"方案仍在加载中,准备重试:第{attempt + 1}/{retryCount}次,间隔{retryDelayMs}ms。");
System.Threading.Thread.Sleep(retryDelayMs);
}
}
if (lastException != null)
{
throw lastException;
}
}
/// <summary>
/// 从 AppSettings 读取配置值,读取失败或为空时返回默认值。
/// </summary>
/// <param name="key">配置键名。</param>
/// <param name="defaultValue">默认值。</param>
/// <returns>配置值或默认值。</returns>
private static string GetAppSettingOrDefault(string key, string defaultValue)
{
try
{
var value = ConfigurationManager.AppSettings[key];
return string.IsNullOrWhiteSpace(value) ? defaultValue : value.Trim();
}
catch
{
return defaultValue;
}
}
/// <summary>
/// 尝试获取当前选中的流程对象。
/// </summary>
private bool TryGetSelectedProcedure(out VmProcedure procedure)
{
procedure = null;
var selectedProcedureName = cmbProcdure.Text?.Trim() ?? string.Empty;
if (string.IsNullOrWhiteSpace(selectedProcedureName))
{
MessageBox.Show(this, "请先在流程列表中选择目标流程。", "流程操作", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return false;
}
procedure = VmSolution.Instance[selectedProcedureName] as VmProcedure;
if (procedure == null)
{
MessageBox.Show(this, $"未找到流程:{selectedProcedureName}。请刷新流程列表后重试。", "流程操作", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return false;
}
return true;
}
/// <summary>
/// 尝试获取当前选中的模块对象。
/// </summary>
private bool TryGetSelectedModule(out VmModule module)
{
module = null;
var selectedProcedureName = cmbProcdure.Text?.Trim() ?? string.Empty;
var selectedModuleName = cmbModule.Text?.Trim() ?? string.Empty;
if (string.IsNullOrWhiteSpace(selectedProcedureName) || string.IsNullOrWhiteSpace(selectedModuleName))
{
MessageBox.Show(this, "请先选择流程和模块。", "模块操作", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return false;
}
module = VmSolution.Instance[$"{selectedProcedureName}.{selectedModuleName}"] as VmModule;
if (module == null)
{
MessageBox.Show(this, $"未找到模块:{selectedProcedureName}.{selectedModuleName}。", "模块操作", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return false;
}
return true;
}
/// <summary>
/// 刷新流程下拉列表。
/// </summary>
private void UpdateProcedureList()
{
var previous = cmbProcdure.Text?.Trim() ?? string.Empty;
cmbProcdure.Items.Clear();
var processInfoList = VmSolution.Instance.GetAllProcedureList();
if (processInfoList.nNum <= 0 || processInfoList.astProcessInfo == null)
{
WriteLog("方案中未检测到流程。");
cmbModule.Items.Clear();
return;
}
for (var i = 0; i < processInfoList.nNum; i++)
{
cmbProcdure.Items.Add(processInfoList.astProcessInfo[i].strProcessName);
}
if (!string.IsNullOrWhiteSpace(previous) && cmbProcdure.Items.Contains(previous))
{
cmbProcdure.SelectedItem = previous;
}
else
{
cmbProcdure.SelectedIndex = 0;
}
}
/// <summary>
/// 刷新当前流程对应模块列表。
/// </summary>
private void UpdateModuleList()
{
cmbModule.Items.Clear();
if (!TryGetSelectedProcedure(out var procedure))
{
return;
}
var moduleInfoList = procedure.GetAllModuleList();
if (moduleInfoList.nNum <= 0 || moduleInfoList.astModuleInfo == null)
{
WriteLog($"流程[{cmbProcdure.Text}]中未检测到模块。");
return;
}
for (var i = 0; i < moduleInfoList.nNum; i++)
{
cmbModule.Items.Add(moduleInfoList.astModuleInfo[i].strDisplayName);
}
cmbModule.SelectedIndex = 0;
}
/// <summary>
/// 清理流程与模块相关状态。
/// </summary>
private void ResetProcedureAndModuleState()
{
StopContinuousRunIfNeeded("重置流程与模块状态");
UnbindProcedureWorkEndCallback();
_currentProcedure = null;
if (_activeBcrTool != null)
{
_activeBcrTool.ModuleResultCallBackArrived -= BcrModuleResultCallBackArrived;
_activeBcrTool = null;
}
cmbProcdure.Items.Clear();
cmbModule.Items.Clear();
vmParamsConfigControl1.ModuleSource = null;
ClearPictureBoxImage();
}
/// <summary>
/// 读取流程字符串输出(多候选容错读取)。
/// </summary>
private string GetProcedureOutputString(VmProcedure procedure, params string[] outputNames)
{
if (procedure == null || outputNames == null || outputNames.Length == 0)
{
return string.Empty;
}
foreach (var outputName in outputNames)
{
if (string.IsNullOrWhiteSpace(outputName))
{
continue;
}
try
{
var output = procedure.ModuResult.GetOutputString(outputName);
if (output.astStringVal != null && output.astStringVal.Length > 0)
{
return output.astStringVal[0].strValue;
}
}
catch
{
// 忽略输出不存在等异常,保持流程主链路稳定。
}
}
return string.Empty;
}
/// <summary>
/// 读取流程整型输出(多候选容错读取)。
/// </summary>
private string GetProcedureOutputInt(VmProcedure procedure, params string[] outputNames)
{
if (procedure == null || outputNames == null || outputNames.Length == 0)
{
return string.Empty;
}
foreach (var outputName in outputNames)
{
if (string.IsNullOrWhiteSpace(outputName))
{
continue;
}
try
{
var output = procedure.ModuResult.GetOutputInt(outputName);
if (output.pIntVal != null && output.pIntVal.Length > 0)
{
return output.pIntVal[0].ToString();
}
}
catch
{
// 忽略输出不存在等异常,保持流程主链路稳定。
}
}
return string.Empty;
}
/// <summary>
/// 选择方案按钮事件:打开文件选择框并回填路径。
/// </summary>
private void btnSelctedSolution_Click(object sender, EventArgs e)
{
using (var dialog = new OpenFileDialog())
{
dialog.Title = "请选择 VisionMaster 方案文件";
dialog.Filter = "VisionMaster方案文件 (*.sol)|*.sol|所有文件 (*.*)|*.*";
dialog.CheckFileExists = true;
dialog.Multiselect = false;
if (dialog.ShowDialog(this) != DialogResult.OK)
{
return;
}
txtSolutionAddress.Text = dialog.FileName;
WriteLog($"已选择方案文件:{dialog.FileName}");
}
}
/// <summary>
/// 加载方案按钮事件:校验路径并调用 SolutionManager.LoadSolution。
/// </summary>
private void btnLoadSolution_Click(object sender, EventArgs e)
{
if (_isBusy || _isSolutionLoading)
{
if (_isSolutionLoading)
{
MessageBox.Show(this, "方案正在加载中,请等待加载完成后再操作。", "加载方案", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
return;
}
var keepBusyUntilLoadEndCallback = false;
try
{
UpdateUiState(true);
var solutionPath = GetValidatedSolutionPath();
// 设置服务就绪,避免部分环境首次调用方案接口时报服务未就绪异常。
_solutionManager.SetServerReadyEvent();
_isSolutionLoading = true;
_pendingLoadSolutionPath = solutionPath;
// 若已有方案,先关闭,避免方案切换场景下状态残留。
if (_isSolutionLoaded)
{
_solutionManager.CloseSolution();
_isSolutionLoaded = false;
ResetProcedureAndModuleState();
WriteLog("已关闭旧方案。\n开始加载新方案。");
}
LoadSolutionWithRetry(solutionPath, retryCount: 12, retryDelayMs: 200);
keepBusyUntilLoadEndCallback = true;
WriteLog($"方案加载请求已提交:{solutionPath}");
}
catch (Exception ex)
{
_isSolutionLoading = false;
_isSolutionLoaded = false;
_pendingLoadSolutionPath = string.Empty;
ResetProcedureAndModuleState();
HandleException("加载方案", ex);
}
finally
{
if (!keepBusyUntilLoadEndCallback)
{
UpdateUiState(false);
}
}
}
/// <summary>
/// 执行方案按钮事件:调用 SolutionManager.ExecuteOnce 执行一次流程。
/// </summary>
private void btnExecutSolution_Click(object sender, EventArgs e)
{
if (_isBusy)
{
return;
}
if (!_isSolutionLoaded)
{
MessageBox.Show(this, "请先加载方案后再执行。", "执行方案", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
try
{
UpdateUiState(true);
_solutionManager.ExecuteOnce();
WriteLog("方案执行完成ExecuteOnce。");
MessageBox.Show(this, "方案执行完成。", "执行方案", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
HandleException("执行方案", ex);
}
finally
{
UpdateUiState(false);
}
}
/// <summary>
/// 保存方案按钮事件:默认覆盖保存到当前方案路径。
/// </summary>
private void btnSaveSolution_Click(object sender, EventArgs e)
{
if (_isBusy)
{
return;
}
if (!_isSolutionLoaded)
{
MessageBox.Show(this, "请先加载方案后再保存。", "保存方案", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
try
{
UpdateUiState(true);
var savePath = string.IsNullOrWhiteSpace(_currentSolutionPath)
? GetValidatedSolutionPath()
: _currentSolutionPath;
_solutionManager.SaveSolution(savePath, string.Empty, IntPtr.Zero, 0);
WriteLog($"方案保存成功:{savePath}");
MessageBox.Show(this, "方案保存成功。", "保存方案", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
HandleException("保存方案", ex);
}
finally
{
UpdateUiState(false);
}
}
/// <summary>
/// 关闭窗体时释放 SDK 相关资源。
/// </summary>
/// <param name="e">事件参数。</param>
protected override void OnFormClosed(FormClosedEventArgs e)
{
try
{
ReleaseSdkResources();
}
finally
{
base.OnFormClosed(e);
}
}
private void btnProImport_Click(object sender, EventArgs e)
{
if (_isBusy)
{
return;
}
if (!EnsureSolutionLoaded("导入流程"))
{
return;
}
try
{
UpdateUiState(true);
string procedurePath;
using (var dialog = new OpenFileDialog())
{
dialog.Title = "请选择流程文件";
dialog.Filter = "流程文件 (*.prc)|*.prc|所有文件 (*.*)|*.*";
dialog.CheckFileExists = true;
dialog.Multiselect = false;
if (dialog.ShowDialog(this) != DialogResult.OK)
{
WriteLog("用户取消导入流程。\n");
return;
}
procedurePath = dialog.FileName;
}
VmProcedure.Load(procedurePath);
UpdateProcedureList();
WriteLog($"流程导入成功:{procedurePath}");
MessageBox.Show(this, "流程导入成功。", "导入流程", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
HandleException("导入流程", ex);
}
finally
{
UpdateUiState(false);
}
}
private void btnProExpo_Click(object sender, EventArgs e)
{
if (_isBusy)
{
return;
}
if (!EnsureSolutionLoaded("导出流程"))
{
return;
}
if (!TryGetSelectedProcedure(out var procedure))
{
return;
}
using (var dialog = new SaveFileDialog())
{
dialog.Title = "请选择流程导出路径";
dialog.Filter = "流程文件 (*.prc)|*.prc|所有文件 (*.*)|*.*";
dialog.FileName = $"{cmbProcdure.Text}.prc";
if (dialog.ShowDialog(this) != DialogResult.OK)
{
return;
}
try
{
UpdateUiState(true);
procedure.SaveAs(dialog.FileName);
WriteLog($"流程导出成功:{dialog.FileName}");
MessageBox.Show(this, "流程导出成功。", "导出流程", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
HandleException("导出流程", ex);
}
finally
{
UpdateUiState(false);
}
}
}
private void btnProDel_Click(object sender, EventArgs e)
{
if (_isBusy)
{
return;
}
if (!EnsureSolutionLoaded("删除流程"))
{
return;
}
var selectedProcedureName = cmbProcdure.Text?.Trim() ?? string.Empty;
if (string.IsNullOrWhiteSpace(selectedProcedureName))
{
MessageBox.Show(this, "请先选择需要删除的流程。", "删除流程", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
if (MessageBox.Show(this, $"确认删除流程:{selectedProcedureName} 吗?", "删除流程", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes)
{
return;
}
try
{
UpdateUiState(true);
VmSolution.Instance.DeleteOneProcedure(selectedProcedureName);
UnbindProcedureWorkEndCallback();
_currentProcedure = null;
vmParamsConfigControl1.ModuleSource = null;
ClearPictureBoxImage();
UpdateProcedureList();
cmbModule.Items.Clear();
WriteLog($"删除流程成功:{selectedProcedureName}");
}
catch (Exception ex)
{
HandleException("删除流程", ex);
}
finally
{
UpdateUiState(false);
}
}
private void btnProExecOnce_Click(object sender, EventArgs e)
{
if (_isBusy)
{
return;
}
if (!EnsureSolutionLoaded("执行流程"))
{
return;
}
if (!TryGetSelectedProcedure(out var procedure))
{
return;
}
try
{
UpdateUiState(true);
StopContinuousRunIfNeeded("执行单次流程");
UnbindProcedureWorkEndCallback();
_currentProcedure = procedure;
_currentProcedure.OnWorkEndStatusCallBack -= Process_OnWorkEndStatusCallBack;
_currentProcedure.OnWorkEndStatusCallBack += Process_OnWorkEndStatusCallBack;
_currentProcedure.Run();
WriteLog($"执行指定流程一次成功:{cmbProcdure.Text}");
}
catch (Exception ex)
{
HandleException("执行指定流程一次", ex);
}
finally
{
UpdateUiState(false);
}
}
private void btnProExecAlway_Click(object sender, EventArgs e)
{
if (_isBusy)
{
return;
}
if (!EnsureSolutionLoaded("连续执行流程"))
{
return;
}
if (!TryGetSelectedProcedure(out var procedure))
{
return;
}
var selectedProcedureName = cmbProcdure.Text?.Trim() ?? string.Empty;
try
{
UpdateUiState(true);
if (_isContinuousRunning)
{
// 当前按钮处于“停止连续”语义:点击后先停止;若选中流程已变更,则继续启动新流程连续执行。
var runningProcedureName = _continuousProcedureName;
StopContinuousRunIfNeeded("用户点击连续执行按钮");
if (string.Equals(runningProcedureName, selectedProcedureName, StringComparison.OrdinalIgnoreCase))
{
return;
}
}
procedure.ContinuousRunEnable = true;
UnbindProcedureWorkEndCallback();
_currentProcedure = procedure;
_currentProcedure.OnWorkEndStatusCallBack -= Process_OnWorkEndStatusCallBack;
_currentProcedure.OnWorkEndStatusCallBack += Process_OnWorkEndStatusCallBack;
_currentProcedure.Run();
SetContinuousRunState(true, selectedProcedureName);
WriteLog($"连续执行流程已开启:{cmbProcdure.Text}");
}
catch (Exception ex)
{
if (IsModuleContinueExecuteVmException(ex))
{
SetContinuousRunState(true, selectedProcedureName);
WriteLog($"流程[{selectedProcedureName}]已处于连续执行状态,已同步运行状态。\n若需停止请点击“停止执行”。");
return;
}
HandleException("连续执行流程", ex);
}
finally
{
UpdateUiState(false);
}
}
private void btnProExecStop_Click(object sender, EventArgs e)
{
if (_isBusy)
{
return;
}
if (!_isSolutionLoaded)
{
return;
}
if (!_isContinuousRunning)
{
WriteLog("当前未处于连续执行状态,无需停止。");
return;
}
StopContinuousRunIfNeeded("用户点击停止按钮");
UpdateUiState(false);
}
private void btnModuleBindingPar_Click(object sender, EventArgs e)
{
if (_isBusy)
{
return;
}
if (!EnsureSolutionLoaded("绑定模块参数"))
{
return;
}
if (!TryGetSelectedModule(out var module))
{
return;
}
try
{
vmParamsConfigControl1.ModuleSource = module;
WriteLog($"绑定模块参数成功:{cmbProcdure.Text}.{cmbModule.Text}");
}
catch (Exception ex)
{
HandleException("绑定模块参数", ex);
}
}
private void btnModuleExec_Click(object sender, EventArgs e)
{
if (_isBusy)
{
return;
}
if (!EnsureSolutionLoaded("执行模块"))
{
return;
}
if (!TryGetSelectedModule(out var module))
{
return;
}
try
{
UpdateUiState(true);
module.Run();
if (TryGetSelectedProcedure(out var procedure))
{
_currentProcedure = procedure;
RenderProcedureResultToPictureBox(procedure, "模块执行");
}
if (_activeBcrTool != null)
{
_activeBcrTool.ModuleResultCallBackArrived -= BcrModuleResultCallBackArrived;
_activeBcrTool = null;
}
var bcrTool = module as IMVSBcrModuTool;
if (bcrTool != null)
{
_activeBcrTool = bcrTool;
_activeBcrTool.EnableResultCallback();
_activeBcrTool.ModuleResultCallBackArrived -= BcrModuleResultCallBackArrived;
_activeBcrTool.ModuleResultCallBackArrived += BcrModuleResultCallBackArrived;
WriteLog($"模块[{cmbModule.Text}]执行完成,已启用条码结果回调。");
}
else
{
WriteLog($"模块[{cmbModule.Text}]执行完成(非条码模块)。");
}
}
catch (Exception ex)
{
HandleException("执行模块", ex);
}
finally
{
UpdateUiState(false);
}
}
private void btnModuleRenderResult_Click(object sender, EventArgs e)
{
if (_isBusy)
{
return;
}
if (!EnsureSolutionLoaded("渲染模块结果"))
{
return;
}
if (!TryGetSelectedModule(out var module))
{
return;
}
try
{
if (TryGetSelectedProcedure(out var procedure))
{
_currentProcedure = procedure;
RenderProcedureResultToPictureBox(procedure, "模块结果渲染");
}
WriteLog($"模块结果刷新完成:{cmbProcdure.Text}.{cmbModule.Text}");
}
catch (Exception ex)
{
HandleException("渲染模块结果", ex);
}
}
/// <summary>
/// 流程切换联动:自动刷新模块列表,并清空旧图像显示。
/// </summary>
private void cmbProcdure_SelectedIndexChanged(object sender, EventArgs e)
{
if (_isBusy || !_isSolutionLoaded)
{
return;
}
try
{
if (_isContinuousRunning && !string.Equals(_continuousProcedureName, cmbProcdure.Text?.Trim() ?? string.Empty, StringComparison.OrdinalIgnoreCase))
{
StopContinuousRunIfNeeded("切换流程");
}
UpdateModuleList();
vmParamsConfigControl1.ModuleSource = null;
ClearPictureBoxImage();
WriteLog($"流程切换完成:{cmbProcdure.Text}");
}
catch (Exception ex)
{
HandleException("切换流程", ex);
}
}
private void cmbModule_DropDown(object sender, EventArgs e)
{
if (_isBusy || !_isSolutionLoaded)
{
return;
}
try
{
UpdateModuleList();
WriteLog($"流程[{cmbProcdure.Text}]模块列表刷新完成。");
}
catch (Exception ex)
{
HandleException("刷新模块列表", ex);
}
}
private void cmbProcdure_DropDown(object sender, EventArgs e)
{
if (_isBusy || !_isSolutionLoaded)
{
return;
}
try
{
UpdateProcedureList();
WriteLog("流程列表刷新完成。");
}
catch (Exception ex)
{
HandleException("刷新流程列表", ex);
}
}
/// <summary>
/// 窗体关闭前释放 SDK 相关资源。
/// </summary>
/// <param name="sender">事件源。</param>
/// <param name="e">关闭事件参数。</param>
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
ReleaseSdkResources();
}
private void MainForm_Load(object sender, EventArgs e)
{
}
}
}