2123 lines
72 KiB
C#
2123 lines
72 KiB
C#
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)
|
||
{
|
||
|
||
}
|
||
}
|
||
}
|