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); } } } }