diff --git a/.windsurf/rules/projectrules.md b/.windsurf/rules/projectrules.md new file mode 100644 index 0000000..75edeab --- /dev/null +++ b/.windsurf/rules/projectrules.md @@ -0,0 +1,10 @@ +--- +trigger: always_on +--- +## 当前是使用海康的VisionMaster 算法平台进行二次开发的程序 + +## 你可以联网查询你需要的信息,当前的SDK下的VisionMaster算法平台SDK开发指南V4.3.1(.NET).chm 是开发文档,你可以参考使用 + +## 使用VisionMaster 易于使用的开发界面和Winform 灵活的二次开发的方式组合使用,完成客户定制化的要求 + +## 程序需要详细的注释 diff --git a/AwakenGPUToolLog.txt b/AwakenGPUToolLog.txt new file mode 100644 index 0000000..227a285 --- /dev/null +++ b/AwakenGPUToolLog.txt @@ -0,0 +1,2 @@ +Awake Gpu start +GPU OK. diff --git a/HkVisionPro.App/App.config b/HkVisionPro.App/App.config index 186a2d1..b1ac8fd 100644 --- a/HkVisionPro.App/App.config +++ b/HkVisionPro.App/App.config @@ -5,6 +5,16 @@ + + + + + + + + + + diff --git a/HkVisionPro.App/HkVisionPro.App.csproj b/HkVisionPro.App/HkVisionPro.App.csproj index 9ffc377..1c84c66 100644 --- a/HkVisionPro.App/HkVisionPro.App.csproj +++ b/HkVisionPro.App/HkVisionPro.App.csproj @@ -194,6 +194,10 @@ False + + + ..\packages\ReaLTaiizor.3.8.1.5\lib\net48\ReaLTaiizor.dll + False @@ -281,6 +285,10 @@ False + + ..\packages\Stateless.5.20.1\lib\net462\Stateless.dll + + False @@ -290,9 +298,6 @@ False - - False - False @@ -571,6 +576,7 @@ + @@ -600,6 +606,7 @@ Resources.resx True + SettingsSingleFileGenerator Settings.Designer.cs @@ -609,11 +616,12 @@ Settings.settings True + + PreserveNewest + - - - + \ No newline at end of file diff --git a/HkVisionPro.App/MainForm.Designer.cs b/HkVisionPro.App/MainForm.Designer.cs index 7c53ccb..08b86a2 100644 --- a/HkVisionPro.App/MainForm.Designer.cs +++ b/HkVisionPro.App/MainForm.Designer.cs @@ -30,16 +30,44 @@ { System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); this.btnLoadSolution = new System.Windows.Forms.Button(); - this.vmRenderControl1 = new VMControls.Winform.Release.VmRenderControl(); this.txtSolutionAddress = new System.Windows.Forms.TextBox(); this.btnSelctedSolution = new System.Windows.Forms.Button(); this.btnExecutSolution = new System.Windows.Forms.Button(); this.btnSaveSolution = new System.Windows.Forms.Button(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.label3 = new System.Windows.Forms.Label(); + this.cmbProcdure = new System.Windows.Forms.ComboBox(); + this.label4 = new System.Windows.Forms.Label(); + this.vmProcedureConfigControl1 = new VMControls.Winform.Release.VmProcedureConfigControl(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.btnProExecStop = new System.Windows.Forms.Button(); + this.btnProExecAlway = new System.Windows.Forms.Button(); + this.btnProExecOnce = new System.Windows.Forms.Button(); + this.btnProDel = new System.Windows.Forms.Button(); + this.btnProExpo = new System.Windows.Forms.Button(); + this.btnProImport = new System.Windows.Forms.Button(); + this.groupBox3 = new System.Windows.Forms.GroupBox(); + this.label2 = new System.Windows.Forms.Label(); + this.cmbModule = new System.Windows.Forms.ComboBox(); + this.btnModuleRenderResult = new System.Windows.Forms.Button(); + this.btnModuleExec = new System.Windows.Forms.Button(); + this.btnModuleBindingPar = new System.Windows.Forms.Button(); + this.listBox1 = new System.Windows.Forms.ListBox(); + this.groupBox4 = new System.Windows.Forms.GroupBox(); + this.pictureBox1 = new System.Windows.Forms.PictureBox(); + this.groupBox6 = new System.Windows.Forms.GroupBox(); + this.vmParamsConfigControl1 = new VMControls.Winform.Release.VmParamsConfigControl(); + this.groupBox1.SuspendLayout(); + this.groupBox2.SuspendLayout(); + this.groupBox3.SuspendLayout(); + this.groupBox4.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); + this.groupBox6.SuspendLayout(); this.SuspendLayout(); // // btnLoadSolution // - this.btnLoadSolution.Location = new System.Drawing.Point(784, 156); + this.btnLoadSolution.Location = new System.Drawing.Point(103, 93); this.btnLoadSolution.Name = "btnLoadSolution"; this.btnLoadSolution.Size = new System.Drawing.Size(127, 38); this.btnLoadSolution.TabIndex = 0; @@ -47,28 +75,16 @@ this.btnLoadSolution.UseVisualStyleBackColor = true; this.btnLoadSolution.Click += new System.EventHandler(this.btnLoadSolution_Click); // - // vmRenderControl1 - // - this.vmRenderControl1.BackColor = System.Drawing.Color.Black; - this.vmRenderControl1.CoordinateInfoVisible = true; - this.vmRenderControl1.ImageSource = null; - this.vmRenderControl1.Location = new System.Drawing.Point(1, 1); - this.vmRenderControl1.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); - this.vmRenderControl1.ModuleSource = null; - this.vmRenderControl1.Name = "vmRenderControl1"; - this.vmRenderControl1.Size = new System.Drawing.Size(776, 742); - this.vmRenderControl1.TabIndex = 1; - // // txtSolutionAddress // - this.txtSolutionAddress.Location = new System.Drawing.Point(784, 39); + this.txtSolutionAddress.Location = new System.Drawing.Point(103, 20); this.txtSolutionAddress.Name = "txtSolutionAddress"; - this.txtSolutionAddress.Size = new System.Drawing.Size(386, 23); + this.txtSolutionAddress.Size = new System.Drawing.Size(393, 23); this.txtSolutionAddress.TabIndex = 2; // // btnSelctedSolution // - this.btnSelctedSolution.Location = new System.Drawing.Point(1043, 68); + this.btnSelctedSolution.Location = new System.Drawing.Point(369, 49); this.btnSelctedSolution.Name = "btnSelctedSolution"; this.btnSelctedSolution.Size = new System.Drawing.Size(127, 38); this.btnSelctedSolution.TabIndex = 3; @@ -78,7 +94,7 @@ // // btnExecutSolution // - this.btnExecutSolution.Location = new System.Drawing.Point(917, 156); + this.btnExecutSolution.Location = new System.Drawing.Point(236, 93); this.btnExecutSolution.Name = "btnExecutSolution"; this.btnExecutSolution.Size = new System.Drawing.Size(127, 38); this.btnExecutSolution.TabIndex = 4; @@ -88,7 +104,7 @@ // // btnSaveSolution // - this.btnSaveSolution.Location = new System.Drawing.Point(1050, 156); + this.btnSaveSolution.Location = new System.Drawing.Point(369, 93); this.btnSaveSolution.Name = "btnSaveSolution"; this.btnSaveSolution.Size = new System.Drawing.Size(127, 38); this.btnSaveSolution.TabIndex = 5; @@ -96,35 +112,316 @@ this.btnSaveSolution.UseVisualStyleBackColor = true; this.btnSaveSolution.Click += new System.EventHandler(this.btnSaveSolution_Click); // + // groupBox1 + // + this.groupBox1.Controls.Add(this.label3); + this.groupBox1.Controls.Add(this.btnSelctedSolution); + this.groupBox1.Controls.Add(this.btnSaveSolution); + this.groupBox1.Controls.Add(this.btnExecutSolution); + this.groupBox1.Controls.Add(this.txtSolutionAddress); + this.groupBox1.Controls.Add(this.btnLoadSolution); + this.groupBox1.Location = new System.Drawing.Point(882, 4); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(505, 144); + this.groupBox1.TabIndex = 6; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "方案操作"; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(30, 23); + this.label3.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(68, 17); + this.label3.TabIndex = 6; + this.label3.Text = "方案路径:"; + // + // cmbProcdure + // + this.cmbProcdure.FormattingEnabled = true; + this.cmbProcdure.Location = new System.Drawing.Point(188, 21); + this.cmbProcdure.Margin = new System.Windows.Forms.Padding(2); + this.cmbProcdure.Name = "cmbProcdure"; + this.cmbProcdure.Size = new System.Drawing.Size(307, 25); + this.cmbProcdure.TabIndex = 8; + this.cmbProcdure.DropDown += new System.EventHandler(this.cmbProcdure_DropDown); + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(103, 24); + this.label4.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(68, 17); + this.label4.TabIndex = 7; + this.label4.Text = "流程列表:"; + // + // vmProcedureConfigControl1 + // + this.vmProcedureConfigControl1.Dock = System.Windows.Forms.DockStyle.Left; + this.vmProcedureConfigControl1.Location = new System.Drawing.Point(20, 60); + this.vmProcedureConfigControl1.Margin = new System.Windows.Forms.Padding(2); + this.vmProcedureConfigControl1.Name = "vmProcedureConfigControl1"; + this.vmProcedureConfigControl1.Size = new System.Drawing.Size(439, 781); + this.vmProcedureConfigControl1.TabIndex = 7; +// TODO: “”的代码生成失败,原因是出现异常“无效的基元类型: System.IntPtr。请考虑使用 CodeObjectCreateExpression。”。 + // + // groupBox2 + // + this.groupBox2.Controls.Add(this.cmbProcdure); + this.groupBox2.Controls.Add(this.label4); + this.groupBox2.Controls.Add(this.btnProExecStop); + this.groupBox2.Controls.Add(this.btnProExecAlway); + this.groupBox2.Controls.Add(this.btnProExecOnce); + this.groupBox2.Controls.Add(this.btnProDel); + this.groupBox2.Controls.Add(this.btnProExpo); + this.groupBox2.Controls.Add(this.btnProImport); + this.groupBox2.Location = new System.Drawing.Point(882, 151); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Size = new System.Drawing.Size(505, 145); + this.groupBox2.TabIndex = 7; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "流程操作"; + // + // btnProExecStop + // + this.btnProExecStop.Location = new System.Drawing.Point(372, 97); + this.btnProExecStop.Name = "btnProExecStop"; + this.btnProExecStop.Size = new System.Drawing.Size(127, 38); + this.btnProExecStop.TabIndex = 8; + this.btnProExecStop.Text = "停止执行"; + this.btnProExecStop.UseVisualStyleBackColor = true; + this.btnProExecStop.Click += new System.EventHandler(this.btnProExecStop_Click); + // + // btnProExecAlway + // + this.btnProExecAlway.Location = new System.Drawing.Point(239, 97); + this.btnProExecAlway.Name = "btnProExecAlway"; + this.btnProExecAlway.Size = new System.Drawing.Size(127, 38); + this.btnProExecAlway.TabIndex = 7; + this.btnProExecAlway.Text = "连续执行"; + this.btnProExecAlway.UseVisualStyleBackColor = true; + this.btnProExecAlway.Click += new System.EventHandler(this.btnProExecAlway_Click); + // + // btnProExecOnce + // + this.btnProExecOnce.Location = new System.Drawing.Point(106, 97); + this.btnProExecOnce.Name = "btnProExecOnce"; + this.btnProExecOnce.Size = new System.Drawing.Size(127, 38); + this.btnProExecOnce.TabIndex = 6; + this.btnProExecOnce.Text = "执行一次"; + this.btnProExecOnce.UseVisualStyleBackColor = true; + this.btnProExecOnce.Click += new System.EventHandler(this.btnProExecOnce_Click); + // + // btnProDel + // + this.btnProDel.Location = new System.Drawing.Point(372, 53); + this.btnProDel.Name = "btnProDel"; + this.btnProDel.Size = new System.Drawing.Size(127, 38); + this.btnProDel.TabIndex = 5; + this.btnProDel.Text = "删除"; + this.btnProDel.UseVisualStyleBackColor = true; + this.btnProDel.Click += new System.EventHandler(this.btnProDel_Click); + // + // btnProExpo + // + this.btnProExpo.Location = new System.Drawing.Point(239, 53); + this.btnProExpo.Name = "btnProExpo"; + this.btnProExpo.Size = new System.Drawing.Size(127, 38); + this.btnProExpo.TabIndex = 4; + this.btnProExpo.Text = "导出"; + this.btnProExpo.UseVisualStyleBackColor = true; + this.btnProExpo.Click += new System.EventHandler(this.btnProExpo_Click); + // + // btnProImport + // + this.btnProImport.Location = new System.Drawing.Point(106, 53); + this.btnProImport.Name = "btnProImport"; + this.btnProImport.Size = new System.Drawing.Size(127, 38); + this.btnProImport.TabIndex = 0; + this.btnProImport.Text = "导入"; + this.btnProImport.UseVisualStyleBackColor = true; + this.btnProImport.Click += new System.EventHandler(this.btnProImport_Click); + // + // groupBox3 + // + this.groupBox3.Controls.Add(this.label2); + this.groupBox3.Controls.Add(this.cmbModule); + this.groupBox3.Controls.Add(this.btnModuleRenderResult); + this.groupBox3.Controls.Add(this.btnModuleExec); + this.groupBox3.Controls.Add(this.btnModuleBindingPar); + this.groupBox3.Location = new System.Drawing.Point(882, 302); + this.groupBox3.Name = "groupBox3"; + this.groupBox3.Size = new System.Drawing.Size(505, 112); + this.groupBox3.TabIndex = 9; + this.groupBox3.TabStop = false; + this.groupBox3.Text = "模块操作"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(33, 22); + this.label2.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(68, 17); + this.label2.TabIndex = 9; + this.label2.Text = "模块列表:"; + // + // cmbModule + // + this.cmbModule.FormattingEnabled = true; + this.cmbModule.Location = new System.Drawing.Point(106, 19); + this.cmbModule.Name = "cmbModule"; + this.cmbModule.Size = new System.Drawing.Size(389, 25); + this.cmbModule.TabIndex = 9; + this.cmbModule.DropDown += new System.EventHandler(this.cmbModule_DropDown); + // + // btnModuleRenderResult + // + this.btnModuleRenderResult.Location = new System.Drawing.Point(372, 61); + this.btnModuleRenderResult.Name = "btnModuleRenderResult"; + this.btnModuleRenderResult.Size = new System.Drawing.Size(127, 38); + this.btnModuleRenderResult.TabIndex = 5; + this.btnModuleRenderResult.Text = "渲染结果"; + this.btnModuleRenderResult.UseVisualStyleBackColor = true; + this.btnModuleRenderResult.Click += new System.EventHandler(this.btnModuleRenderResult_Click); + // + // btnModuleExec + // + this.btnModuleExec.Location = new System.Drawing.Point(239, 61); + this.btnModuleExec.Name = "btnModuleExec"; + this.btnModuleExec.Size = new System.Drawing.Size(127, 38); + this.btnModuleExec.TabIndex = 4; + this.btnModuleExec.Text = "执行模块"; + this.btnModuleExec.UseVisualStyleBackColor = true; + this.btnModuleExec.Click += new System.EventHandler(this.btnModuleExec_Click); + // + // btnModuleBindingPar + // + this.btnModuleBindingPar.Location = new System.Drawing.Point(106, 61); + this.btnModuleBindingPar.Name = "btnModuleBindingPar"; + this.btnModuleBindingPar.Size = new System.Drawing.Size(127, 38); + this.btnModuleBindingPar.TabIndex = 0; + this.btnModuleBindingPar.Text = "绑定参数"; + this.btnModuleBindingPar.UseVisualStyleBackColor = true; + this.btnModuleBindingPar.Click += new System.EventHandler(this.btnModuleBindingPar_Click); + // + // listBox1 + // + this.listBox1.FormattingEnabled = true; + this.listBox1.ItemHeight = 17; + this.listBox1.Location = new System.Drawing.Point(882, 422); + this.listBox1.Name = "listBox1"; + this.listBox1.Size = new System.Drawing.Size(516, 429); + this.listBox1.TabIndex = 10; + // + // groupBox4 + // + this.groupBox4.Controls.Add(this.pictureBox1); + this.groupBox4.Location = new System.Drawing.Point(463, 2); + this.groupBox4.Margin = new System.Windows.Forms.Padding(2); + this.groupBox4.Name = "groupBox4"; + this.groupBox4.Padding = new System.Windows.Forms.Padding(2); + this.groupBox4.Size = new System.Drawing.Size(414, 412); + this.groupBox4.TabIndex = 11; + this.groupBox4.TabStop = false; + this.groupBox4.Text = "视图窗口"; + // + // pictureBox1 + // + this.pictureBox1.Dock = System.Windows.Forms.DockStyle.Fill; + this.pictureBox1.Location = new System.Drawing.Point(2, 18); + this.pictureBox1.Name = "pictureBox1"; + this.pictureBox1.Size = new System.Drawing.Size(410, 392); + this.pictureBox1.TabIndex = 0; + this.pictureBox1.TabStop = false; + // + // groupBox6 + // + this.groupBox6.Controls.Add(this.vmParamsConfigControl1); + this.groupBox6.Location = new System.Drawing.Point(463, 422); + this.groupBox6.Margin = new System.Windows.Forms.Padding(2); + this.groupBox6.Name = "groupBox6"; + this.groupBox6.Padding = new System.Windows.Forms.Padding(2); + this.groupBox6.Size = new System.Drawing.Size(409, 439); + this.groupBox6.TabIndex = 12; + this.groupBox6.TabStop = false; + this.groupBox6.Text = "参数配置"; + // + // vmParamsConfigControl1 + // + this.vmParamsConfigControl1.BackColor = System.Drawing.Color.White; + this.vmParamsConfigControl1.Dock = System.Windows.Forms.DockStyle.Fill; + this.vmParamsConfigControl1.Location = new System.Drawing.Point(2, 18); + this.vmParamsConfigControl1.ModuleSource = null; + this.vmParamsConfigControl1.Name = "vmParamsConfigControl1"; + this.vmParamsConfigControl1.ParamsConfig = null; + this.vmParamsConfigControl1.Size = new System.Drawing.Size(405, 419); + this.vmParamsConfigControl1.TabIndex = 0; + // // MainForm // this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 17F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(1182, 744); - this.Controls.Add(this.btnSaveSolution); - this.Controls.Add(this.btnExecutSolution); - this.Controls.Add(this.btnSelctedSolution); - this.Controls.Add(this.txtSolutionAddress); - this.Controls.Add(this.vmRenderControl1); - this.Controls.Add(this.btnLoadSolution); + this.ClientSize = new System.Drawing.Size(1400, 861); + this.Controls.Add(this.groupBox6); + this.Controls.Add(this.groupBox4); + this.Controls.Add(this.listBox1); + this.Controls.Add(this.groupBox3); + this.Controls.Add(this.groupBox2); + this.Controls.Add(this.vmProcedureConfigControl1); + this.Controls.Add(this.groupBox1); this.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.Margin = new System.Windows.Forms.Padding(4); this.Name = "MainForm"; this.Text = "主窗体"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MainForm_FormClosing); + this.Load += new System.EventHandler(this.MainForm_Load); + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.groupBox2.ResumeLayout(false); + this.groupBox2.PerformLayout(); + this.groupBox3.ResumeLayout(false); + this.groupBox3.PerformLayout(); + this.groupBox4.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); + this.groupBox6.ResumeLayout(false); this.ResumeLayout(false); - this.PerformLayout(); } #endregion private System.Windows.Forms.Button btnLoadSolution; - private VMControls.Winform.Release.VmRenderControl vmRenderControl1; private System.Windows.Forms.TextBox txtSolutionAddress; private System.Windows.Forms.Button btnSelctedSolution; private System.Windows.Forms.Button btnExecutSolution; private System.Windows.Forms.Button btnSaveSolution; + private System.Windows.Forms.GroupBox groupBox1; + private VMControls.Winform.Release.VmProcedureConfigControl vmProcedureConfigControl1; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.Button btnProDel; + private System.Windows.Forms.Button btnProExpo; + private System.Windows.Forms.Button btnProImport; + private System.Windows.Forms.Button btnProExecStop; + private System.Windows.Forms.Button btnProExecAlway; + private System.Windows.Forms.Button btnProExecOnce; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.GroupBox groupBox3; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.ComboBox cmbModule; + private System.Windows.Forms.Button btnModuleRenderResult; + private System.Windows.Forms.Button btnModuleExec; + private System.Windows.Forms.Button btnModuleBindingPar; + private System.Windows.Forms.ListBox listBox1; + private System.Windows.Forms.GroupBox groupBox4; + private System.Windows.Forms.GroupBox groupBox6; + private VMControls.Winform.Release.VmParamsConfigControl vmParamsConfigControl1; + private System.Windows.Forms.ComboBox cmbProcdure; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.PictureBox pictureBox1; } } diff --git a/HkVisionPro.App/MainForm.cs b/HkVisionPro.App/MainForm.cs index 8bf8dab..e865cb4 100644 --- a/HkVisionPro.App/MainForm.cs +++ b/HkVisionPro.App/MainForm.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Collections.Generic; using System.ComponentModel; using System.Data; @@ -7,32 +8,2113 @@ 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 : Form + 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) { } diff --git a/HkVisionPro.App/SDK/VisionMaster算法平台SDK开发指南V4.3.1(.NET).chm b/HkVisionPro.App/SDK/VisionMaster算法平台SDK开发指南V4.3.1(.NET).chm new file mode 100644 index 0000000..b2822c2 Binary files /dev/null and b/HkVisionPro.App/SDK/VisionMaster算法平台SDK开发指南V4.3.1(.NET).chm differ diff --git a/HkVisionPro.App/packages.config b/HkVisionPro.App/packages.config new file mode 100644 index 0000000..51af7b7 --- /dev/null +++ b/HkVisionPro.App/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/HkVisionPro.slnx b/HkVisionPro.slnx index 64ae7bf..3452c12 100644 --- a/HkVisionPro.slnx +++ b/HkVisionPro.slnx @@ -1,3 +1,3 @@ - +