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 HkVisionPro.App.Automation;
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
{
///
/// VisionMaster 方案管理对象(负责加载、执行、保存方案)。
///
private readonly SolutionManager _solutionManager;
///
/// 当前已加载的方案文件完整路径。
///
private string _currentSolutionPath = string.Empty;
///
/// 当前待完成加载的方案路径(用于异步加载结束时提交状态)。
///
private string _pendingLoadSolutionPath = string.Empty;
///
/// 当前是否已成功加载方案。
///
private bool _isSolutionLoaded;
///
/// 当前是否正在进行加载/执行/保存过程(用于按钮防重入)。
///
private bool _isBusy;
///
/// 当前是否处于方案加载过程(SDK 可能异步加载,需显式防重入)。
///
private bool _isSolutionLoading;
///
/// 当前是否处于流程连续执行状态。
///
private bool _isContinuousRunning;
///
/// 当前连续执行的流程名称。
///
private string _continuousProcedureName = string.Empty;
///
/// 渲染互斥标记(0=空闲,1=渲染中),用于避免连续执行阶段并发读取结果。
///
private int _renderingGuard;
///
/// 当前选择的流程对象(用于流程执行与结果读取)。
///
private VmProcedure _currentProcedure;
///
/// 当前激活的条码模块工具对象(用于结果回调订阅与解绑)。
///
private IMVSBcrModuTool _activeBcrTool;
///
/// VM.Core 方案事件是否已注册。
///
private bool _isVmSolutionEventRegistered;
///
/// 关闭阶段资源是否已释放(用于防止 FormClosing 与 OnFormClosed 重复释放)。
///
private bool _isClosingResourcesReleased;
///
/// 渲染图像输出名称(支持配置,默认 Image)。
///
private readonly string _renderImageOutputName;
///
/// 渲染主 ROI 输出名称(支持配置,默认 InputR1)。
///
private readonly string _renderRoiOutputName1;
///
/// 渲染次 ROI 输出名称(支持配置,默认 InputR2)。
///
private readonly string _renderRoiOutputName2;
///
/// 渲染判定结果1整型输出名称(支持配置,默认 Result1)。
///
private readonly string _renderResultIntOutputName1;
///
/// 渲染判定结果2整型输出名称(支持配置,默认 Result2)。
///
private readonly string _renderResultIntOutputName2;
///
/// VisionMaster: 方案正在加载中的错误码(IMVS_EC_SOLUTION_LOADING)。
///
private const int VmErrorCodeSolutionLoading = -536870899;
///
/// VisionMaster: 模块已处于连续执行中的错误码(IMVS_EC_MODULE_CONTINUE_EXECUTE)。
///
private const int VmErrorCodeModuleContinueExecute = -536870127;
///
/// 自动装配状态机配置。
///
private readonly AutoAssemblyOptions _autoAssemblyOptions;
///
/// 自动装配流程控制器(Stateless 状态机)。
///
private readonly AutoAssemblyWorkflowController _autoAssemblyController;
///
/// 方案加载完成后是否自动启动自动装配流程。
///
private readonly bool _autoRunEnabledAfterSolutionLoaded;
///
/// 自动运行结果面板上次显示状态(用于层切换时重置结果显示)。
///
private AutoAssemblyState _lastDisplayedAutoAssemblyState = AutoAssemblyState.Idle;
///
/// ReaLTaiizor Poison 样式管理器(用于统一暗色主题风格元数据)。
///
private readonly PoisonStyleManager _poisonStyleManager;
///
/// 主背景颜色。
///
private static readonly Color DarkThemeMainBackColor = Color.FromArgb(28, 28, 30);
///
/// 分组容器背景颜色。
///
private static readonly Color DarkThemePanelBackColor = Color.FromArgb(36, 36, 40);
///
/// 输入类控件背景颜色。
///
private static readonly Color DarkThemeInputBackColor = Color.FromArgb(45, 45, 48);
///
/// 前景文字颜色。
///
private static readonly Color DarkThemeForeColor = Color.FromArgb(230, 230, 230);
///
/// 主题强调色(按钮边框、选中态)。
///
private static readonly Color DarkThemeAccentColor = Color.FromArgb(0, 174, 219);
///
/// 分组框边框颜色。
///
private static readonly Color DarkThemeGroupBorderColor = Color.FromArgb(78, 78, 82);
///
/// 分组框标题底色。
///
private static readonly Color DarkThemeGroupTitleBackColor = Color.FromArgb(48, 48, 52);
///
/// 列表选中项背景色。
///
private static readonly Color DarkThemeSelectedBackColor = Color.FromArgb(0, 120, 170);
public MainForm()
{
InitializeComponent();
_poisonStyleManager = components == null ? new PoisonStyleManager() : new PoisonStyleManager(components);
_solutionManager = new SolutionManager();
_renderImageOutputName = GetAppSettingOrDefault("RenderImageOutputName", "ImageData0");
_renderRoiOutputName1 = GetAppSettingOrDefault("RenderRoiOutputName", "InputR1");
_renderRoiOutputName2 = GetAppSettingOrDefault("RenderRoiOutputName2", "InputR2");
_renderResultIntOutputName1 = GetAppSettingOrDefault("RenderResultIntOutputName1", "Result1");
_renderResultIntOutputName2 = GetAppSettingOrDefault("RenderResultIntOutputName2", "Result2");
_autoAssemblyOptions = BuildAutoAssemblyOptions();
_autoRunEnabledAfterSolutionLoaded = GetAppSettingBoolOrDefault("AutoRunEnabledAfterSolutionLoaded", true);
_autoAssemblyController = new AutoAssemblyWorkflowController(
_autoAssemblyOptions,
WriteLog,
(procedure, triggerSource) => RenderProcedureResultToPictureBox(procedure, triggerSource));
_autoAssemblyController.StatusChanged += AutoAssemblyController_StatusChanged;
RegisterSolutionCallbacks();
RegisterVmSolutionCallbacks();
// 统一联动:流程切换时自动刷新模块列表。
cmbProcdure.SelectedIndexChanged += cmbProcdure_SelectedIndexChanged;
// 视图改为 pictureBox 展示时,统一设置渲染行为。
pictureBox1.SizeMode = PictureBoxSizeMode.Zoom;
pictureBox1.BackColor = Color.Black;
InitializeDarkTheme();
InitializeAutoRunStatusPanel();
UpdateUiState(false);
txtSolutionAddress.Text = string.Empty;
}
///
/// 初始化自动运行结果显示面板。
///
private void InitializeAutoRunStatusPanel()
{
lblRunState.AutoSize = false;
lblRunState.TextAlign = ContentAlignment.MiddleCenter;
lblRunState.ForeColor = DarkThemeForeColor;
lblLayer.TextAlign = ContentAlignment.MiddleCenter;
lblLayer.ForeColor = Color.White;
lblLayer.BackColor = GetLayerBackgroundColor(AutoAssemblyState.Idle);
lblStableOkTime.ForeColor = Color.Gainsboro;
lblStableOkTime.Text = "维持时间:0/0 ms";
SetResultLabelStatus(lblTotalResult, "总结果", "待检测", Color.Gainsboro);
SetResultLabelStatus(lblComponent1, "组件1", "待检测", Color.Gainsboro);
SetResultLabelStatus(lblComponent2, "组件2", "待检测", Color.Gainsboro);
var initMessage = _autoRunEnabledAfterSolutionLoaded
? "自动流程待启动(等待方案加载)"
: "自动流程已禁用(配置开关关闭)";
UpdateAutoRunResultDisplay(AutoAssemblyState.Idle, initMessage, 0, string.Empty, 0, _autoAssemblyOptions.StableOkMilliseconds);
SyncAutoRunButtonState();
}
///
/// 自动装配状态变化事件:刷新“自动运行结果”显示。
///
/// 事件源对象。
/// 状态变化参数。
private void AutoAssemblyController_StatusChanged(object sender, AutoAssemblyStatusChangedEventArgs e)
{
if (e == null)
{
return;
}
SyncAutoRunButtonState();
UpdateAutoRunResultDisplay(
e.State,
e.Message,
e.ProductIndex,
e.ActiveProcedureName,
e.StableElapsedMilliseconds,
e.StableTargetMilliseconds);
}
///
/// 更新自动运行结果显示控件(线程安全)。
///
/// 自动流程状态。
/// 状态描述。
/// 当前产品序号(从 1 开始)。
/// 当前流程名。
/// 稳定计时已累计毫秒数。
/// 稳定计时目标毫秒数。
private void UpdateAutoRunResultDisplay(
AutoAssemblyState state,
string message,
int productIndex,
string activeProcedureName,
int stableElapsedMilliseconds,
int stableTargetMilliseconds)
{
if (!IsHandleCreated || IsDisposed)
{
return;
}
void UpdateAction()
{
var elapsed = Math.Max(0, stableElapsedMilliseconds);
var target = Math.Max(0, stableTargetMilliseconds);
if (target > 0)
{
elapsed = Math.Min(elapsed, target);
}
lblRunState.ForeColor = GetAutoRunStateColor(state);
lblRunState.Text = BuildAutoRunDisplayText(
state,
message,
productIndex,
activeProcedureName,
elapsed,
target);
lblLayer.BackColor = GetLayerBackgroundColor(state);
lblLayer.ForeColor = Color.White;
lblLayer.Text = BuildLayerDisplayText(state, activeProcedureName);
lblStableOkTime.ForeColor = Color.Gainsboro;
lblStableOkTime.Text = $"维持时间:{elapsed}/{target} ms";
var stateChanged = state != _lastDisplayedAutoAssemblyState;
if (stateChanged && IsLayerInspectingState(state))
{
SetResultLabelStatus(lblTotalResult, "总结果", "待检测", Color.Gainsboro);
SetResultLabelStatus(lblComponent1, "组件1", "待检测", Color.Gainsboro);
SetResultLabelStatus(lblComponent2, "组件2", "待检测", Color.Gainsboro);
}
else if (!IsLayerInspectingState(state))
{
ApplyNonInspectingStateResultDisplay(state);
}
_lastDisplayedAutoAssemblyState = state;
}
if (lblRunState.InvokeRequired)
{
lblRunState.BeginInvoke(new Action(UpdateAction));
}
else
{
UpdateAction();
}
}
///
/// 构造自动运行结果展示文本。
///
/// 自动流程状态。
/// 状态描述。
/// 当前产品序号。
/// 当前流程名。
/// 稳定计时累计毫秒数。
/// 稳定计时目标毫秒数。
/// 用于显示的多行文本。
private static string BuildAutoRunDisplayText(
AutoAssemblyState state,
string message,
int productIndex,
string activeProcedureName,
int stableElapsedMilliseconds,
int stableTargetMilliseconds)
{
var displayProductIndex = productIndex <= 0 ? 1 : productIndex;
var displayProcedure = string.IsNullOrWhiteSpace(activeProcedureName) ? "-" : activeProcedureName.Trim();
var displayMessage = string.IsNullOrWhiteSpace(message) ? "-" : message.Trim();
return $"{GetAutoRunStateTitle(state)} | 产品#{displayProductIndex} | 流程:{displayProcedure} | {displayMessage}";
}
///
/// 获取自动状态标题文本。
///
/// 自动流程状态。
/// 状态标题。
private static string GetAutoRunStateTitle(AutoAssemblyState state)
{
switch (state)
{
case AutoAssemblyState.LayerAInspecting:
return "A层检测中";
case AutoAssemblyState.LayerBInspecting:
return "B层检测中";
case AutoAssemblyState.LayerCInspecting:
return "C层检测中";
case AutoAssemblyState.ProductCompleted:
return "整机检测OK";
case AutoAssemblyState.Faulted:
return "自动流程异常";
case AutoAssemblyState.Stopped:
return "自动流程已停止";
default:
return "自动流程空闲";
}
}
///
/// 获取自动状态标题颜色。
///
/// 自动流程状态。
/// 状态颜色。
private static Color GetAutoRunStateColor(AutoAssemblyState state)
{
switch (state)
{
case AutoAssemblyState.LayerAInspecting:
return Color.FromArgb(64, 196, 255);
case AutoAssemblyState.LayerBInspecting:
return Color.FromArgb(255, 179, 71);
case AutoAssemblyState.LayerCInspecting:
return Color.FromArgb(170, 132, 255);
case AutoAssemblyState.ProductCompleted:
return Color.FromArgb(120, 220, 130);
case AutoAssemblyState.Faulted:
return Color.FromArgb(255, 99, 99);
case AutoAssemblyState.Stopped:
return Color.FromArgb(200, 200, 200);
default:
return DarkThemeForeColor;
}
}
///
/// 根据状态构造“当前层”标签展示文本。
///
/// 自动流程状态。
/// 当前流程名。
/// 层展示文本。
private static string BuildLayerDisplayText(AutoAssemblyState state, string activeProcedureName)
{
var procedureName = string.IsNullOrWhiteSpace(activeProcedureName) ? "-" : activeProcedureName.Trim();
switch (state)
{
case AutoAssemblyState.LayerAInspecting:
return $"当前层:A层({procedureName})";
case AutoAssemblyState.LayerBInspecting:
return $"当前层:B层({procedureName})";
case AutoAssemblyState.LayerCInspecting:
return $"当前层:C层({procedureName})";
case AutoAssemblyState.ProductCompleted:
return "当前层:整机完成";
case AutoAssemblyState.Faulted:
return "当前层:故障";
case AutoAssemblyState.Stopped:
return "当前层:已停止";
default:
return "当前层:空闲";
}
}
///
/// 获取层标签背景色,保证白字可读性。
///
/// 自动流程状态。
/// 背景色。
private static Color GetLayerBackgroundColor(AutoAssemblyState state)
{
switch (state)
{
case AutoAssemblyState.LayerAInspecting:
return Color.FromArgb(38, 109, 190);
case AutoAssemblyState.LayerBInspecting:
return Color.FromArgb(179, 113, 36);
case AutoAssemblyState.LayerCInspecting:
return Color.FromArgb(108, 78, 163);
case AutoAssemblyState.ProductCompleted:
return Color.FromArgb(36, 128, 84);
case AutoAssemblyState.Faulted:
return Color.FromArgb(170, 55, 55);
case AutoAssemblyState.Stopped:
return Color.FromArgb(88, 88, 92);
default:
return Color.FromArgb(68, 68, 72);
}
}
///
/// 判断状态是否为层级检测状态。
///
/// 自动流程状态。
/// 是否处于 A/B/C 层检测。
private static bool IsLayerInspectingState(AutoAssemblyState state)
{
return state == AutoAssemblyState.LayerAInspecting
|| state == AutoAssemblyState.LayerBInspecting
|| state == AutoAssemblyState.LayerCInspecting;
}
///
/// 设置结果标签文本与颜色。
///
/// 目标标签。
/// 标题。
/// 值。
/// 前景色。
private static void SetResultLabelStatus(Label label, string title, string value, Color foreColor)
{
if (label == null)
{
return;
}
label.ForeColor = foreColor;
label.Text = $"{title}:{value}";
}
///
/// 根据流程状态更新非检测态的结果展示。
///
/// 自动流程状态。
private void ApplyNonInspectingStateResultDisplay(AutoAssemblyState state)
{
if (state == AutoAssemblyState.ProductCompleted)
{
SetResultLabelStatus(lblTotalResult, "总结果", "OK", Color.LimeGreen);
SetResultLabelStatus(lblComponent1, "组件1", "OK", Color.LimeGreen);
SetResultLabelStatus(lblComponent2, "组件2", "OK", Color.LimeGreen);
return;
}
if (state == AutoAssemblyState.Faulted)
{
SetResultLabelStatus(lblTotalResult, "总结果", "NG", Color.Red);
SetResultLabelStatus(lblComponent1, "组件1", "NG", Color.Red);
SetResultLabelStatus(lblComponent2, "组件2", "NG", Color.Red);
return;
}
SetResultLabelStatus(lblTotalResult, "总结果", "待检测", Color.Gainsboro);
SetResultLabelStatus(lblComponent1, "组件1", "待检测", Color.Gainsboro);
SetResultLabelStatus(lblComponent2, "组件2", "待检测", Color.Gainsboro);
}
///
/// 根据当前流程输出判定结果刷新“总结果/组件结果”标签。
///
/// 组件1结果。
/// 组件2结果。
private void UpdateAutoRunResultByProcedureResult(string result1, string result2)
{
if (_autoAssemblyController == null || !_autoAssemblyController.IsRunning)
{
return;
}
if (!IsHandleCreated || IsDisposed)
{
return;
}
void UpdateAction()
{
var component1Ok = string.Equals(result1?.Trim(), "1", StringComparison.Ordinal);
var component2Ok = string.Equals(result2?.Trim(), "1", StringComparison.Ordinal);
var totalOk = component1Ok && component2Ok;
SetResultLabelStatus(lblComponent1, "组件1", component1Ok ? "OK" : "NG", component1Ok ? Color.LimeGreen : Color.Red);
SetResultLabelStatus(lblComponent2, "组件2", component2Ok ? "OK" : "NG", component2Ok ? Color.LimeGreen : Color.Red);
SetResultLabelStatus(lblTotalResult, "总结果", totalOk ? "OK" : "NG", totalOk ? Color.LimeGreen : Color.Red);
}
if (lblTotalResult.InvokeRequired)
{
lblTotalResult.BeginInvoke(new Action(UpdateAction));
}
else
{
UpdateAction();
}
}
///
/// 按配置构建自动装配流程参数。
///
/// 自动流程配置对象。
private AutoAssemblyOptions BuildAutoAssemblyOptions()
{
return new AutoAssemblyOptions
{
ProcedureAName = GetAppSettingOrDefault("AutoRunProcedureAName", "A"),
ProcedureBName = GetAppSettingOrDefault("AutoRunProcedureBName", "B"),
ProcedureCName = GetAppSettingOrDefault("AutoRunProcedureCName", "C"),
OkIntOutputName = GetAppSettingOrDefault("AutoRunOkIntOutputName", _renderResultIntOutputName1),
OkIntOutputName2 = GetAppSettingOrDefault("AutoRunOkIntOutputName2", _renderResultIntOutputName2),
OkIntValue = GetAppSettingIntOrDefault("AutoRunOkIntValue", 1),
StableOkMilliseconds = GetAppSettingIntOrDefault("AutoRunStableMilliseconds", 2000),
};
}
///
/// 方案加载成功后按配置启动自动装配流程。
///
private void StartAutoAssemblyIfConfigured()
{
if (!_autoRunEnabledAfterSolutionLoaded)
{
UpdateAutoRunResultDisplay(
AutoAssemblyState.Idle,
"自动流程已禁用(AutoRunEnabledAfterSolutionLoaded=false)",
0,
string.Empty,
0,
_autoAssemblyOptions.StableOkMilliseconds);
return;
}
if (!_isSolutionLoaded)
{
return;
}
try
{
// 切入自动模式前清理手动执行上下文,避免两套执行链路并发冲突。
StopContinuousRunIfNeeded("切换到自动装配流程");
UnbindProcedureWorkEndCallback();
_currentProcedure = null;
_autoAssemblyController.Start();
}
catch (Exception ex)
{
HandleException("启动自动装配流程", ex);
}
}
///
/// 停止自动装配流程(若运行中)。
///
/// 停止原因。
private void StopAutoAssemblyIfRunning(string reason)
{
try
{
_autoAssemblyController.Stop(reason);
SyncAutoRunButtonState();
}
catch (Exception ex)
{
WriteLog($"停止自动装配流程异常:{ex.Message}");
}
}
///
/// 同步自动运行按钮文本(线程安全)。
/// 运行中显示“停止自动”,未运行显示“自动运行”。
///
private void SyncAutoRunButtonState()
{
if (!IsHandleCreated || IsDisposed)
{
return;
}
void UpdateAction()
{
btnAutoRun.Text = _autoAssemblyController.IsRunning ? "停止自动" : "自动运行";
}
if (btnAutoRun.InvokeRequired)
{
btnAutoRun.BeginInvoke(new Action(UpdateAction));
}
else
{
UpdateAction();
}
}
///
/// 初始化 ReaLTaiizor 暗色主题,并将原生 WinForms 控件统一成暗色风格。
///
private void InitializeDarkTheme()
{
_poisonStyleManager.Owner = this;
_poisonStyleManager.Theme = ThemeStyle.Dark;
_poisonStyleManager.Style = ColorStyle.Teal;
StyleManager = _poisonStyleManager;
Theme = ThemeStyle.Dark;
Style = ColorStyle.Teal;
ApplyDarkThemeToNativeControls(this);
}
///
/// 递归应用暗色主题到原生 WinForms 控件。
///
/// 递归起始控件。
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);
}
}
}
///
/// 设置分组框控件暗色样式。
///
/// 目标分组框。
private void ApplyGroupBoxTheme(GroupBox groupBox)
{
groupBox.BackColor = DarkThemePanelBackColor;
groupBox.ForeColor = DarkThemeForeColor;
groupBox.Paint -= ThemedGroupBox_Paint;
groupBox.Paint += ThemedGroupBox_Paint;
}
///
/// 自定义绘制分组框标题与边框,统一暗色视觉层级。
///
/// 事件发起对象。
/// 绘制事件参数。
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);
}
///
/// 设置按钮控件暗色样式。
///
/// 目标按钮。
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;
}
///
/// 自定义绘制按钮文本,确保暗色主题下文字始终可读。
///
/// 事件发起对象。
/// 绘制事件参数。
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);
}
///
/// 设置文本框控件暗色样式。
///
/// 目标文本框。
private void ApplyTextBoxTheme(TextBox textBox)
{
textBox.BorderStyle = BorderStyle.FixedSingle;
textBox.BackColor = DarkThemeInputBackColor;
textBox.ForeColor = DarkThemeForeColor;
}
///
/// 设置下拉框控件暗色样式,并接管绘制以统一列表区域颜色。
///
/// 目标下拉框。
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;
}
///
/// 设置列表框控件暗色样式,并接管绘制以统一选中高亮。
///
/// 目标列表框。
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;
}
///
/// 设置标签控件暗色样式。
///
/// 目标标签。
private void ApplyLabelTheme(Label label)
{
label.BackColor = Color.Transparent;
label.ForeColor = DarkThemeForeColor;
}
///
/// 设置图片显示区域暗色样式。
///
/// 目标图片框。
private void ApplyPictureBoxTheme(PictureBox pictureBox)
{
pictureBox.BackColor = Color.Black;
pictureBox.BorderStyle = BorderStyle.FixedSingle;
}
///
/// 自定义绘制暗色下拉框项。
///
/// 事件发起对象。
/// 绘制事件参数。
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();
}
///
/// 自定义绘制暗色列表框项。
///
/// 事件发起对象。
/// 绘制事件参数。
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();
}
///
/// 注册 VisionMaster 方案生命周期回调,用于关键节点日志输出。
///
private void RegisterSolutionCallbacks()
{
_solutionManager.OnSolutionLoadBeginCallBack += OnSolutionLoadBegin;
_solutionManager.OnSolutionLoadProgressCallBack += OnSolutionLoadProgress;
_solutionManager.OnSolutionLoadEndCallBack += OnSolutionLoadEnd;
_solutionManager.OnSolutionSaveBeginCallBack += OnSolutionSaveBegin;
_solutionManager.OnSolutionSaveProgressCallBack += OnSolutionSaveProgress;
_solutionManager.OnSolutionSaveEndCallBack += OnSolutionSaveEnd;
}
///
/// 注销 VisionMaster 回调,避免窗体关闭后的委托残留。
///
private void UnregisterSolutionCallbacks()
{
_solutionManager.OnSolutionLoadBeginCallBack -= OnSolutionLoadBegin;
_solutionManager.OnSolutionLoadProgressCallBack -= OnSolutionLoadProgress;
_solutionManager.OnSolutionLoadEndCallBack -= OnSolutionLoadEnd;
_solutionManager.OnSolutionSaveBeginCallBack -= OnSolutionSaveBegin;
_solutionManager.OnSolutionSaveProgressCallBack -= OnSolutionSaveProgress;
_solutionManager.OnSolutionSaveEndCallBack -= OnSolutionSaveEnd;
}
///
/// 注册 VM.Core 执行状态回调。
///
private void RegisterVmSolutionCallbacks()
{
if (_isVmSolutionEventRegistered)
{
return;
}
VmSolution.OnWorkStatusEvent += VmSolution_OnWorkStatusEvent;
_isVmSolutionEventRegistered = true;
}
///
/// 注销 VM.Core 执行状态回调。
///
private void UnregisterVmSolutionCallbacks()
{
if (!_isVmSolutionEventRegistered)
{
return;
}
VmSolution.OnWorkStatusEvent -= VmSolution_OnWorkStatusEvent;
_isVmSolutionEventRegistered = false;
}
///
/// 解绑当前流程结束回调,防止重复订阅和对象残留。
///
private void UnbindProcedureWorkEndCallback()
{
if (_currentProcedure == null)
{
return;
}
try
{
_currentProcedure.OnWorkEndStatusCallBack -= Process_OnWorkEndStatusCallBack;
}
catch (Exception ex)
{
WriteLog($"解绑流程结束回调异常:{ex.Message}");
}
}
///
/// 更新连续执行状态及按钮文案。
///
/// 是否连续执行中。
/// 连续执行流程名称。
private void SetContinuousRunState(bool isRunning, string procedureName)
{
_isContinuousRunning = isRunning;
_continuousProcedureName = isRunning ? (procedureName ?? string.Empty) : string.Empty;
btnProExecAlway.Text = isRunning ? "停止连续" : "连续执行";
}
///
/// 停止当前连续执行流程(若有),并重置连续执行状态。
///
/// 停止原因(用于日志)。
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);
}
}
///
/// 释放 VisionMaster 相关资源(幂等,允许重复调用)。
///
private void ReleaseSdkResources()
{
if (_isClosingResourcesReleased)
{
return;
}
_isClosingResourcesReleased = true;
try
{
UnregisterSolutionCallbacks();
UnregisterVmSolutionCallbacks();
if (_activeBcrTool != null)
{
_activeBcrTool.ModuleResultCallBackArrived -= BcrModuleResultCallBackArrived;
_activeBcrTool = null;
}
StopAutoAssemblyIfRunning("窗体关闭释放资源");
_autoAssemblyController.StatusChanged -= AutoAssemblyController_StatusChanged;
_autoAssemblyController.Dispose();
StopContinuousRunIfNeeded("窗体关闭释放资源");
UnbindProcedureWorkEndCallback();
_currentProcedure = null;
vmParamsConfigControl1.ModuleSource = null;
ClearPictureBoxImage();
if (_isSolutionLoaded)
{
_solutionManager.CloseSolution();
_isSolutionLoaded = false;
}
_currentSolutionPath = string.Empty;
}
catch (Exception ex)
{
WriteLog($"关闭窗体时释放资源异常:{ex.Message}");
}
}
///
/// 方案加载开始回调。
///
private void OnSolutionLoadBegin(ImvsSdkDefine.IMVS_SOLUTION_LOAD_BEGEIN_INFO beginInfo)
{
_isSolutionLoading = true;
WriteLog("方案加载开始。");
if (!IsHandleCreated || IsDisposed)
{
return;
}
BeginInvoke(new Action(() => UpdateUiState(_isBusy)));
}
///
/// 方案加载进度回调。
///
private void OnSolutionLoadProgress(ImvsSdkDefine.IMVS_SOLUTION_LOAD_PROCESS_INFO progressInfo)
{
WriteLog($"方案加载进度:{progressInfo.nProcess}%");
}
///
/// 方案加载结束回调。
///
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();
StartAutoAssemblyIfConfigured();
WriteLog($"方案加载成功:{_currentSolutionPath}");
MessageBox.Show(this, "方案加载成功。", "加载方案", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
_isSolutionLoaded = false;
StopAutoAssemblyIfRunning("方案加载失败");
ResetProcedureAndModuleState();
MessageBox.Show(this, $"方案加载失败,状态码:{endInfo.nStatus}", "加载方案", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
catch (Exception ex)
{
_isSolutionLoaded = false;
StopAutoAssemblyIfRunning("加载方案回调异常");
ResetProcedureAndModuleState();
HandleException("加载方案回调处理", ex);
}
finally
{
_pendingLoadSolutionPath = string.Empty;
UpdateUiState(false);
}
}));
}
///
/// 方案保存开始回调。
///
private void OnSolutionSaveBegin(ImvsSdkDefine.IMVS_SOLUTION_SAVE_BEGEIN_INFO beginInfo)
{
WriteLog("方案保存开始。");
}
///
/// 方案保存进度回调。
///
private void OnSolutionSaveProgress(ImvsSdkDefine.IMVS_SOLUTION_SAVE_PROCESS_INFO progressInfo)
{
WriteLog($"方案保存进度:{progressInfo.nProcess}%");
}
///
/// 方案保存结束回调。
///
private void OnSolutionSaveEnd(ImvsSdkDefine.IMVS_SOLUTION_SAVE_END_INFO endInfo)
{
WriteLog($"方案保存结束,状态码:{endInfo.nStatus}");
}
///
/// VM.Core 方案执行状态回调。
///
/// 执行状态信息。
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}");
}
}));
}
///
/// 流程执行结束回调:读取输出并渲染到 pictureBox。
///
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}");
}
}
///
/// 将流程结果(图像、ROI、识别文本)渲染到 pictureBox。
///
/// 流程对象。
/// 触发源描述(用于日志)。
private void RenderProcedureResultToPictureBox(VmProcedure procedure, string triggerSource)
{
if (procedure == null)
{
return;
}
if (System.Threading.Interlocked.Exchange(ref _renderingGuard, 1) == 1)
{
return;
}
try
{
var result1 = GetProcedureOutputInt(procedure, _renderResultIntOutputName1);
var result2 = GetProcedureOutputInt(procedure, _renderResultIntOutputName2);
var isResult1Ok = string.Equals(result1?.Trim(), "1", StringComparison.Ordinal);
var isResult2Ok = string.Equals(result2?.Trim(), "1", StringComparison.Ordinal);
var pairResult = isResult1Ok && isResult2Ok ? "OK" : "NG";
var overlayText = $"R1={result1}, R2={result2}, Total={pairResult}";
UpdateAutoRunResultByProcedureResult(result1, result2);
var bitmap = GetProcedureOutputBitmap(procedure, _renderImageOutputName);
if (bitmap == null)
{
WriteLog($"{triggerSource}未读取到可渲染图像,固定输出名:{_renderImageOutputName}");
return;
}
try
{
var primaryRoiList = GetProcedureOutputBoxList(procedure, _renderRoiOutputName1);
var secondaryRoiList = GetProcedureOutputBoxList(procedure, _renderRoiOutputName2);
var mergedRoiList = MergeRoiLists(primaryRoiList, secondaryRoiList);
var roiColor = pairResult == "OK" ? Color.Lime : Color.Red;
var renderBitmap = DrawROIOnBitmap(bitmap, mergedRoiList, roiColor, 2, overlayText);
SetPictureBoxImage(renderBitmap);
WriteLog($"{triggerSource}渲染完成:Result1={result1},Result2={result2},Total={pairResult},ROI数={mergedRoiList.Count}");
}
finally
{
bitmap.Dispose();
}
}
finally
{
System.Threading.Interlocked.Exchange(ref _renderingGuard, 0);
}
}
///
/// 从流程输出中读取图像并转换为 Bitmap。
///
/// 流程对象。
/// 候选输出名列表。
/// Bitmap 对象;读取失败返回 null。
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;
}
///
/// 从流程输出中读取 ROI 列表。
///
/// 流程对象。
/// 候选输出名列表。
/// ROI 列表;读取失败返回空列表。
private List 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();
}
///
/// 合并多组 ROI 列表,避免流程多目标输出时遗漏展示。
///
/// 待合并 ROI 列表集合。
/// 合并后的 ROI 列表。
private static List MergeRoiLists(params List[] roiGroups)
{
var merged = new List();
if (roiGroups == null || roiGroups.Length == 0)
{
return merged;
}
foreach (var roiGroup in roiGroups)
{
if (roiGroup == null || roiGroup.Count == 0)
{
continue;
}
merged.AddRange(roiGroup);
}
return merged;
}
///
/// 在图像上绘制 ROI 轮廓和识别文本。
///
/// 源图像。
/// ROI 列表。
/// ROI 颜色。
/// 线宽。
/// 识别文本。
/// 绘制后的新 Bitmap。
private Bitmap DrawROIOnBitmap(Bitmap sourceBitmap, List 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;
}
///
/// 安全更新 pictureBox 图像并释放旧图,避免内存泄漏。
///
/// 新图像。
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();
}
}
///
/// 清空 pictureBox 图像并释放旧图。
///
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();
}
}
///
/// 条码模块执行结果回调。
///
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}");
}));
}
///
/// 统一日志输出(当前工程无独立日志控件,先写控制台)。
///
/// 日志内容。
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;
}
}
///
/// 更新按钮启用状态,防止流程重入和非法操作。
///
/// 当前是否忙碌。
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;
btnAutoRun.Enabled = canOperateLoadedSolution;
cmbProcdure.Enabled = canOperateLoadedSolution;
cmbModule.Enabled = canOperateLoadedSolution;
// 保持连续执行按钮文案与状态一致。
btnProExecAlway.Text = _isContinuousRunning ? "停止连续" : "连续执行";
SyncAutoRunButtonState();
}
///
/// 校验并返回文本框中的方案路径。
///
/// 合法的完整路径。
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;
}
///
/// 根据已加载方案,尝试把首个模块绑定到渲染控件以显示结果。
///
private void BindFirstModuleToRenderControl()
{
// 界面已切换为 pictureBox 渲染,加载新方案时先清空上次显示图像。
ClearPictureBoxImage();
}
///
/// 统一异常处理:记录日志并弹出错误提示。
///
/// 弹窗标题。
/// 异常对象。
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);
}
///
/// 校验当前是否已加载方案。
///
private bool EnsureSolutionLoaded(string operationName)
{
if (_isSolutionLoaded)
{
return true;
}
MessageBox.Show(this, $"请先加载方案后再{operationName}。", operationName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
return false;
}
///
/// 判断异常是否为“方案正在加载中”错误。
///
/// 异常对象。
/// 若是加载中错误则返回 true。
private static bool IsSolutionLoadingVmException(Exception ex)
{
if (ex == null)
{
return false;
}
var vmException = VmExceptionTool.GetVmException(ex);
return vmException != null && vmException.errorCode == VmErrorCodeSolutionLoading;
}
///
/// 判断异常是否为“模块已处于连续执行”错误。
///
/// 异常对象。
/// 若是连续执行中错误则返回 true。
private static bool IsModuleContinueExecuteVmException(Exception ex)
{
if (ex == null)
{
return false;
}
var vmException = VmExceptionTool.GetVmException(ex);
return vmException != null && vmException.errorCode == VmErrorCodeModuleContinueExecute;
}
///
/// 调用 SDK 加载方案,并针对“方案正在加载中”做有限重试。
///
/// 方案路径。
/// 最大重试次数。
/// 每次重试间隔(毫秒)。
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;
}
}
///
/// 从 AppSettings 读取配置值,读取失败或为空时返回默认值。
///
/// 配置键名。
/// 默认值。
/// 配置值或默认值。
private static string GetAppSettingOrDefault(string key, string defaultValue)
{
try
{
var value = ConfigurationManager.AppSettings[key];
return string.IsNullOrWhiteSpace(value) ? defaultValue : value.Trim();
}
catch
{
return defaultValue;
}
}
///
/// 从 AppSettings 读取整型配置,失败时返回默认值。
///
/// 配置键名。
/// 默认值。
/// 解析后的整型值。
private static int GetAppSettingIntOrDefault(string key, int defaultValue)
{
try
{
var value = ConfigurationManager.AppSettings[key];
if (string.IsNullOrWhiteSpace(value))
{
return defaultValue;
}
return int.TryParse(value.Trim(), out var parsedValue) ? parsedValue : defaultValue;
}
catch
{
return defaultValue;
}
}
///
/// 从 AppSettings 读取布尔配置,失败时返回默认值。
///
/// 配置键名。
/// 默认值。
/// 解析后的布尔值。
private static bool GetAppSettingBoolOrDefault(string key, bool defaultValue)
{
try
{
var value = ConfigurationManager.AppSettings[key];
if (string.IsNullOrWhiteSpace(value))
{
return defaultValue;
}
return bool.TryParse(value.Trim(), out var parsedValue) ? parsedValue : defaultValue;
}
catch
{
return defaultValue;
}
}
///
/// 尝试获取当前选中的流程对象。
///
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;
}
///
/// 尝试获取当前选中的模块对象。
///
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;
}
///
/// 刷新流程下拉列表。
///
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;
}
}
///
/// 刷新当前流程对应模块列表。
///
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;
}
///
/// 清理流程与模块相关状态。
///
private void ResetProcedureAndModuleState()
{
StopAutoAssemblyIfRunning("重置流程与模块状态");
StopContinuousRunIfNeeded("重置流程与模块状态");
UnbindProcedureWorkEndCallback();
_currentProcedure = null;
if (_activeBcrTool != null)
{
_activeBcrTool.ModuleResultCallBackArrived -= BcrModuleResultCallBackArrived;
_activeBcrTool = null;
}
cmbProcdure.Items.Clear();
cmbModule.Items.Clear();
vmParamsConfigControl1.ModuleSource = null;
ClearPictureBoxImage();
}
///
/// 读取流程整型输出(多候选容错读取)。
///
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;
}
///
/// 选择方案按钮事件:打开文件选择框并回填路径。
///
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}");
}
}
///
/// 加载方案按钮事件:校验路径并调用 SolutionManager.LoadSolution。
///
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);
}
}
}
///
/// 执行方案按钮事件:调用 SolutionManager.ExecuteOnce 执行一次流程。
///
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);
}
}
///
/// 保存方案按钮事件:默认覆盖保存到当前方案路径。
///
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);
}
}
///
/// 关闭窗体时释放 SDK 相关资源。
///
/// 事件参数。
protected override void OnFormClosed(FormClosedEventArgs e)
{
try
{
ReleaseSdkResources();
}
finally
{
base.OnFormClosed(e);
}
}
///
/// 导入流程按钮事件:从外部 .prc 文件导入流程到当前方案,并刷新流程列表。
/// 该方法包含完整的前置状态检查(忙碌态、方案是否已加载)与异常处理,避免 UI 重入导致的状态错乱。
///
/// 事件源控件(导入按钮)。
/// 标准事件参数。
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;
}
// 通过 VM SDK 加载流程文件到当前方案上下文。
VmProcedure.Load(procedurePath);
UpdateProcedureList();
WriteLog($"流程导入成功:{procedurePath}");
MessageBox.Show(this, "流程导入成功。", "导入流程", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
HandleException("导入流程", ex);
}
finally
{
UpdateUiState(false);
}
}
///
/// 导出流程按钮事件:将当前选中的流程导出为独立 .prc 文件。
///
/// 事件源控件(导出按钮)。
/// 标准事件参数。
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;
}
}
// 显式开启 SDK 连续执行标记,并重新绑定流程结束回调。
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);
}
}
///
/// 渲染结果按钮事件:不重新执行模块,仅根据当前流程最新输出重绘图像/ROI/识别结果。
///
/// 事件源控件(渲染结果按钮)。
/// 标准事件参数。
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);
}
}
///
/// 流程切换联动:自动刷新模块列表,并清空旧图像显示。
///
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);
}
}
///
/// 窗体关闭前释放 SDK 相关资源。
///
/// 事件源。
/// 关闭事件参数。
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
ReleaseSdkResources();
}
///
/// 主窗体加载事件。
/// 当前初始化逻辑已在构造函数中完成(主题、回调、UI 状态),
/// 因此此处暂不添加额外业务处理,预留后续扩展入口。
///
/// 事件源控件(主窗体)。
/// 标准事件参数。
private void MainForm_Load(object sender, EventArgs e)
{
}
///
/// 自动运行按钮事件。
/// 点击后执行自动流程启停切换:
/// - 未运行:启动自动状态机;
/// - 运行中:停止自动状态机。
///
/// 事件源控件(自动运行按钮)。
/// 标准事件参数。
private void btnAutoRun_Click(object sender, EventArgs e)
{
if (_isBusy)
{
return;
}
if (!EnsureSolutionLoaded("自动运行"))
{
return;
}
try
{
UpdateUiState(true);
if (_autoAssemblyController.IsRunning)
{
StopAutoAssemblyIfRunning("用户点击自动运行按钮停止");
WriteLog("自动装配流程已停止(按钮触发)。");
return;
}
// 启动自动流程前清理手动流程执行上下文,确保执行链路互斥。
StopContinuousRunIfNeeded("点击自动运行按钮");
UnbindProcedureWorkEndCallback();
_currentProcedure = null;
_autoAssemblyController.Start();
SyncAutoRunButtonState();
WriteLog("自动装配流程已启动(按钮触发)。");
}
catch (Exception ex)
{
HandleException("自动运行按钮操作", ex);
}
finally
{
UpdateUiState(false);
}
}
}
}