using System; using System.IO; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.Configuration; using ReaLTaiizor.Enum.Poison; using ReaLTaiizor.Forms; using ReaLTaiizor.Manager; using VM.PlatformSDKCS; using VM.Core; using IMVSBcrModuCs; using PointSetMODU_STDCs; namespace HkVisionPro.App { public partial class MainForm : PoisonForm { /// /// 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; /// /// 流程字符串输出名称(支持配置,默认 out)。 /// private readonly string _procedureOutputCodeName; /// /// 流程整数输出名称(支持配置,默认 out0)。 /// private readonly string _procedureOutputNumberName; /// /// 渲染图像输出名称(支持配置,默认 Image)。 /// private readonly string _renderImageOutputName; /// /// 渲染 ROI 输出名称(支持配置,默认 ROI)。 /// private readonly string _renderRoiOutputName; /// /// 渲染文本输出名称(支持配置,默认 code)。 /// private readonly string _renderCodeOutputName; /// /// VisionMaster: 方案正在加载中的错误码(IMVS_EC_SOLUTION_LOADING)。 /// private const int VmErrorCodeSolutionLoading = -536870899; /// /// VisionMaster: 模块已处于连续执行中的错误码(IMVS_EC_MODULE_CONTINUE_EXECUTE)。 /// private const int VmErrorCodeModuleContinueExecute = -536870127; /// /// 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(); _procedureOutputCodeName = GetAppSettingOrDefault("ProcedureOutputCodeName", "out"); _procedureOutputNumberName = GetAppSettingOrDefault("ProcedureOutputNumberName", "out0"); _renderImageOutputName = GetAppSettingOrDefault("RenderImageOutputName", "Image"); _renderRoiOutputName = GetAppSettingOrDefault("RenderRoiOutputName", "ROI"); _renderCodeOutputName = GetAppSettingOrDefault("RenderCodeOutputName", "code"); RegisterSolutionCallbacks(); RegisterVmSolutionCallbacks(); // 统一联动:流程切换时自动刷新模块列表。 cmbProcdure.SelectedIndexChanged += cmbProcdure_SelectedIndexChanged; // 视图改为 pictureBox 展示时,统一设置渲染行为。 pictureBox1.SizeMode = PictureBoxSizeMode.Zoom; pictureBox1.BackColor = Color.Black; InitializeDarkTheme(); UpdateUiState(false); txtSolutionAddress.Text = string.Empty; } /// /// 初始化 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; } 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(); WriteLog($"方案加载成功:{_currentSolutionPath}"); MessageBox.Show(this, "方案加载成功。", "加载方案", MessageBoxButtons.OK, MessageBoxIcon.Information); } else { _isSolutionLoaded = false; ResetProcedureAndModuleState(); MessageBox.Show(this, $"方案加载失败,状态码:{endInfo.nStatus}", "加载方案", MessageBoxButtons.OK, MessageBoxIcon.Error); } } catch (Exception ex) { _isSolutionLoaded = false; ResetProcedureAndModuleState(); HandleException("加载方案回调处理", ex); } finally { _pendingLoadSolutionPath = string.Empty; UpdateUiState(false); } })); } /// /// 方案保存开始回调。 /// 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 { // 字符串读取仅尝试字符串候选名,避免对 int 输出调用 GetOutputString 引发 SDK 内部异常。 var code = GetProcedureOutputString(procedure, _renderCodeOutputName, _procedureOutputCodeName, "out0", "code", "Code"); // 整型读取仅尝试整型候选名。 var codeNum = GetProcedureOutputInt(procedure, _procedureOutputNumberName, "out"); var bitmap = GetProcedureOutputBitmap(procedure, _renderImageOutputName, "ImageData", "Image", "image"); if (bitmap == null) { WriteLog($"{triggerSource}未读取到可渲染图像,输出名候选:{_renderImageOutputName}/ImageData/Image/image"); return; } try { var roiList = GetProcedureOutputBoxList(procedure, _renderRoiOutputName, "InputROI", "rect", "outline", "ROI", "roi"); var renderBitmap = DrawROIOnBitmap(bitmap, roiList, Color.Cyan, 2, code); SetPictureBoxImage(renderBitmap); WriteLog($"{triggerSource}渲染完成:Code={code},CodeNumber={codeNum},ROI数={roiList.Count}"); } finally { bitmap.Dispose(); } } finally { System.Threading.Interlocked.Exchange(ref _renderingGuard, 0); } } /// /// 从流程输出中读取图像并转换为 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 颜色。 /// 线宽。 /// 识别文本。 /// 绘制后的新 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; cmbProcdure.Enabled = canOperateLoadedSolution; cmbModule.Enabled = canOperateLoadedSolution; // 保持连续执行按钮文案与状态一致。 btnProExecAlway.Text = _isContinuousRunning ? "停止连续" : "连续执行"; } /// /// 校验并返回文本框中的方案路径。 /// /// 合法的完整路径。 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; } } /// /// 尝试获取当前选中的流程对象。 /// 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() { StopContinuousRunIfNeeded("重置流程与模块状态"); UnbindProcedureWorkEndCallback(); _currentProcedure = null; if (_activeBcrTool != null) { _activeBcrTool.ModuleResultCallBackArrived -= BcrModuleResultCallBackArrived; _activeBcrTool = null; } cmbProcdure.Items.Clear(); cmbModule.Items.Clear(); vmParamsConfigControl1.ModuleSource = null; ClearPictureBoxImage(); } /// /// 读取流程字符串输出(多候选容错读取)。 /// private string GetProcedureOutputString(VmProcedure procedure, params string[] outputNames) { if (procedure == null || outputNames == null || outputNames.Length == 0) { return string.Empty; } foreach (var outputName in outputNames) { if (string.IsNullOrWhiteSpace(outputName)) { continue; } try { var output = procedure.ModuResult.GetOutputString(outputName); if (output.astStringVal != null && output.astStringVal.Length > 0) { return output.astStringVal[0].strValue; } } catch { // 忽略输出不存在等异常,保持流程主链路稳定。 } } return string.Empty; } /// /// 读取流程整型输出(多候选容错读取)。 /// 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); } } private void btnProImport_Click(object sender, EventArgs e) { if (_isBusy) { return; } if (!EnsureSolutionLoaded("导入流程")) { return; } try { UpdateUiState(true); string procedurePath; using (var dialog = new OpenFileDialog()) { dialog.Title = "请选择流程文件"; dialog.Filter = "流程文件 (*.prc)|*.prc|所有文件 (*.*)|*.*"; dialog.CheckFileExists = true; dialog.Multiselect = false; if (dialog.ShowDialog(this) != DialogResult.OK) { WriteLog("用户取消导入流程。\n"); return; } procedurePath = dialog.FileName; } VmProcedure.Load(procedurePath); UpdateProcedureList(); WriteLog($"流程导入成功:{procedurePath}"); MessageBox.Show(this, "流程导入成功。", "导入流程", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { HandleException("导入流程", ex); } finally { UpdateUiState(false); } } private void btnProExpo_Click(object sender, EventArgs e) { if (_isBusy) { return; } if (!EnsureSolutionLoaded("导出流程")) { return; } if (!TryGetSelectedProcedure(out var procedure)) { return; } using (var dialog = new SaveFileDialog()) { dialog.Title = "请选择流程导出路径"; dialog.Filter = "流程文件 (*.prc)|*.prc|所有文件 (*.*)|*.*"; dialog.FileName = $"{cmbProcdure.Text}.prc"; if (dialog.ShowDialog(this) != DialogResult.OK) { return; } try { UpdateUiState(true); procedure.SaveAs(dialog.FileName); WriteLog($"流程导出成功:{dialog.FileName}"); MessageBox.Show(this, "流程导出成功。", "导出流程", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { HandleException("导出流程", ex); } finally { UpdateUiState(false); } } } private void btnProDel_Click(object sender, EventArgs e) { if (_isBusy) { return; } if (!EnsureSolutionLoaded("删除流程")) { return; } var selectedProcedureName = cmbProcdure.Text?.Trim() ?? string.Empty; if (string.IsNullOrWhiteSpace(selectedProcedureName)) { MessageBox.Show(this, "请先选择需要删除的流程。", "删除流程", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } if (MessageBox.Show(this, $"确认删除流程:{selectedProcedureName} 吗?", "删除流程", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes) { return; } try { UpdateUiState(true); VmSolution.Instance.DeleteOneProcedure(selectedProcedureName); UnbindProcedureWorkEndCallback(); _currentProcedure = null; vmParamsConfigControl1.ModuleSource = null; ClearPictureBoxImage(); UpdateProcedureList(); cmbModule.Items.Clear(); WriteLog($"删除流程成功:{selectedProcedureName}"); } catch (Exception ex) { HandleException("删除流程", ex); } finally { UpdateUiState(false); } } private void btnProExecOnce_Click(object sender, EventArgs e) { if (_isBusy) { return; } if (!EnsureSolutionLoaded("执行流程")) { return; } if (!TryGetSelectedProcedure(out var procedure)) { return; } try { UpdateUiState(true); StopContinuousRunIfNeeded("执行单次流程"); UnbindProcedureWorkEndCallback(); _currentProcedure = procedure; _currentProcedure.OnWorkEndStatusCallBack -= Process_OnWorkEndStatusCallBack; _currentProcedure.OnWorkEndStatusCallBack += Process_OnWorkEndStatusCallBack; _currentProcedure.Run(); WriteLog($"执行指定流程一次成功:{cmbProcdure.Text}"); } catch (Exception ex) { HandleException("执行指定流程一次", ex); } finally { UpdateUiState(false); } } private void btnProExecAlway_Click(object sender, EventArgs e) { if (_isBusy) { return; } if (!EnsureSolutionLoaded("连续执行流程")) { return; } if (!TryGetSelectedProcedure(out var procedure)) { return; } var selectedProcedureName = cmbProcdure.Text?.Trim() ?? string.Empty; try { UpdateUiState(true); if (_isContinuousRunning) { // 当前按钮处于“停止连续”语义:点击后先停止;若选中流程已变更,则继续启动新流程连续执行。 var runningProcedureName = _continuousProcedureName; StopContinuousRunIfNeeded("用户点击连续执行按钮"); if (string.Equals(runningProcedureName, selectedProcedureName, StringComparison.OrdinalIgnoreCase)) { return; } } procedure.ContinuousRunEnable = true; UnbindProcedureWorkEndCallback(); _currentProcedure = procedure; _currentProcedure.OnWorkEndStatusCallBack -= Process_OnWorkEndStatusCallBack; _currentProcedure.OnWorkEndStatusCallBack += Process_OnWorkEndStatusCallBack; _currentProcedure.Run(); SetContinuousRunState(true, selectedProcedureName); WriteLog($"连续执行流程已开启:{cmbProcdure.Text}"); } catch (Exception ex) { if (IsModuleContinueExecuteVmException(ex)) { SetContinuousRunState(true, selectedProcedureName); WriteLog($"流程[{selectedProcedureName}]已处于连续执行状态,已同步运行状态。\n若需停止请点击“停止执行”。"); return; } HandleException("连续执行流程", ex); } finally { UpdateUiState(false); } } private void btnProExecStop_Click(object sender, EventArgs e) { if (_isBusy) { return; } if (!_isSolutionLoaded) { return; } if (!_isContinuousRunning) { WriteLog("当前未处于连续执行状态,无需停止。"); return; } StopContinuousRunIfNeeded("用户点击停止按钮"); UpdateUiState(false); } private void btnModuleBindingPar_Click(object sender, EventArgs e) { if (_isBusy) { return; } if (!EnsureSolutionLoaded("绑定模块参数")) { return; } if (!TryGetSelectedModule(out var module)) { return; } try { vmParamsConfigControl1.ModuleSource = module; WriteLog($"绑定模块参数成功:{cmbProcdure.Text}.{cmbModule.Text}"); } catch (Exception ex) { HandleException("绑定模块参数", ex); } } private void btnModuleExec_Click(object sender, EventArgs e) { if (_isBusy) { return; } if (!EnsureSolutionLoaded("执行模块")) { return; } if (!TryGetSelectedModule(out var module)) { return; } try { UpdateUiState(true); module.Run(); if (TryGetSelectedProcedure(out var procedure)) { _currentProcedure = procedure; RenderProcedureResultToPictureBox(procedure, "模块执行"); } if (_activeBcrTool != null) { _activeBcrTool.ModuleResultCallBackArrived -= BcrModuleResultCallBackArrived; _activeBcrTool = null; } var bcrTool = module as IMVSBcrModuTool; if (bcrTool != null) { _activeBcrTool = bcrTool; _activeBcrTool.EnableResultCallback(); _activeBcrTool.ModuleResultCallBackArrived -= BcrModuleResultCallBackArrived; _activeBcrTool.ModuleResultCallBackArrived += BcrModuleResultCallBackArrived; WriteLog($"模块[{cmbModule.Text}]执行完成,已启用条码结果回调。"); } else { WriteLog($"模块[{cmbModule.Text}]执行完成(非条码模块)。"); } } catch (Exception ex) { HandleException("执行模块", ex); } finally { UpdateUiState(false); } } private void btnModuleRenderResult_Click(object sender, EventArgs e) { if (_isBusy) { return; } if (!EnsureSolutionLoaded("渲染模块结果")) { return; } if (!TryGetSelectedModule(out var module)) { return; } try { if (TryGetSelectedProcedure(out var procedure)) { _currentProcedure = procedure; RenderProcedureResultToPictureBox(procedure, "模块结果渲染"); } WriteLog($"模块结果刷新完成:{cmbProcdure.Text}.{cmbModule.Text}"); } catch (Exception ex) { HandleException("渲染模块结果", ex); } } /// /// 流程切换联动:自动刷新模块列表,并清空旧图像显示。 /// 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(); } private void MainForm_Load(object sender, EventArgs e) { } } }