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