视觉修改
This commit is contained in:
		
							
								
								
									
										113
									
								
								Check.Main/UI/FormControlPanel.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								Check.Main/UI/FormControlPanel.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | ||||
| namespace Check.Main.UI | ||||
| { | ||||
|     partial class FormControlPanel | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Required designer variable. | ||||
|         /// </summary> | ||||
|         private System.ComponentModel.IContainer components = null; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Clean up any resources being used. | ||||
|         /// </summary> | ||||
|         /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> | ||||
|         protected override void Dispose(bool disposing) | ||||
|         { | ||||
|             if (disposing && (components != null)) | ||||
|             { | ||||
|                 components.Dispose(); | ||||
|             } | ||||
|             base.Dispose(disposing); | ||||
|         } | ||||
|  | ||||
|         #region Windows Form Designer generated code | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Required method for Designer support - do not modify | ||||
|         /// the contents of this method with the code editor. | ||||
|         /// </summary> | ||||
|         private void InitializeComponent() | ||||
|         { | ||||
|             this.uiTableLayoutPanel1 = new Sunny.UI.UITableLayoutPanel(); | ||||
|             this.btnStartDevice = new Sunny.UI.UIButton(); | ||||
|             this.btnStartCheck = new Sunny.UI.UIButton(); | ||||
|             this.uiTableLayoutPanel1.SuspendLayout(); | ||||
|             this.SuspendLayout(); | ||||
|             //  | ||||
|             // uiTableLayoutPanel1 | ||||
|             //  | ||||
|             this.uiTableLayoutPanel1.ColumnCount = 2; | ||||
|             this.uiTableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); | ||||
|             this.uiTableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); | ||||
|             this.uiTableLayoutPanel1.Controls.Add(this.btnStartDevice, 0, 1); | ||||
|             this.uiTableLayoutPanel1.Controls.Add(this.btnStartCheck, 0, 3); | ||||
|             this.uiTableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; | ||||
|             this.uiTableLayoutPanel1.Location = new System.Drawing.Point(0, 0); | ||||
|             this.uiTableLayoutPanel1.Name = "uiTableLayoutPanel1"; | ||||
|             this.uiTableLayoutPanel1.RowCount = 5; | ||||
|             this.uiTableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 11.38585F)); | ||||
|             this.uiTableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 35.35353F)); | ||||
|             this.uiTableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 7.575758F)); | ||||
|             this.uiTableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.83838F)); | ||||
|             this.uiTableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 11.38585F)); | ||||
|             this.uiTableLayoutPanel1.Size = new System.Drawing.Size(230, 198); | ||||
|             this.uiTableLayoutPanel1.TabIndex = 0; | ||||
|             this.uiTableLayoutPanel1.TagString = null; | ||||
|             //  | ||||
|             // btnStartDevice | ||||
|             //  | ||||
|             this.uiTableLayoutPanel1.SetColumnSpan(this.btnStartDevice, 2); | ||||
|             this.btnStartDevice.Cursor = System.Windows.Forms.Cursors.Hand; | ||||
|             this.btnStartDevice.Dock = System.Windows.Forms.DockStyle.Fill; | ||||
|             this.btnStartDevice.FillPressColor = System.Drawing.Color.LimeGreen; | ||||
|             this.btnStartDevice.FillSelectedColor = System.Drawing.Color.LimeGreen; | ||||
|             this.btnStartDevice.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); | ||||
|             this.btnStartDevice.Location = new System.Drawing.Point(3, 25); | ||||
|             this.btnStartDevice.MinimumSize = new System.Drawing.Size(1, 1); | ||||
|             this.btnStartDevice.Name = "btnStartDevice"; | ||||
|             this.btnStartDevice.Size = new System.Drawing.Size(224, 64); | ||||
|             this.btnStartDevice.TabIndex = 0; | ||||
|             this.btnStartDevice.Text = "启动设备"; | ||||
|             this.btnStartDevice.TipsFont = new System.Drawing.Font("宋体", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); | ||||
|             this.btnStartDevice.Click += new System.EventHandler(this.btnStartDevice_Click); | ||||
|             //  | ||||
|             // btnStartCheck | ||||
|             //  | ||||
|             this.uiTableLayoutPanel1.SetColumnSpan(this.btnStartCheck, 2); | ||||
|             this.btnStartCheck.Cursor = System.Windows.Forms.Cursors.Hand; | ||||
|             this.btnStartCheck.Dock = System.Windows.Forms.DockStyle.Fill; | ||||
|             this.btnStartCheck.FillPressColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(192)))), ((int)(((byte)(0))))); | ||||
|             this.btnStartCheck.FillSelectedColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(192)))), ((int)(((byte)(0))))); | ||||
|             this.btnStartCheck.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); | ||||
|             this.btnStartCheck.Location = new System.Drawing.Point(3, 110); | ||||
|             this.btnStartCheck.MinimumSize = new System.Drawing.Size(1, 1); | ||||
|             this.btnStartCheck.Name = "btnStartCheck"; | ||||
|             this.btnStartCheck.Size = new System.Drawing.Size(224, 61); | ||||
|             this.btnStartCheck.TabIndex = 1; | ||||
|             this.btnStartCheck.Text = "开始检测"; | ||||
|             this.btnStartCheck.TipsFont = new System.Drawing.Font("宋体", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); | ||||
|             this.btnStartCheck.Click += new System.EventHandler(this.btnStartCheck_Click); | ||||
|             //  | ||||
|             // FormControlPanel | ||||
|             //  | ||||
|             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); | ||||
|             this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; | ||||
|             this.ClientSize = new System.Drawing.Size(230, 198); | ||||
|             this.ControlBox = false; | ||||
|             this.Controls.Add(this.uiTableLayoutPanel1); | ||||
|             this.MaximizeBox = false; | ||||
|             this.MinimizeBox = false; | ||||
|             this.Name = "FormControlPanel"; | ||||
|             this.Text = "启动管理"; | ||||
|             this.uiTableLayoutPanel1.ResumeLayout(false); | ||||
|             this.ResumeLayout(false); | ||||
|  | ||||
|         } | ||||
|  | ||||
|         #endregion | ||||
|  | ||||
|         private Sunny.UI.UITableLayoutPanel uiTableLayoutPanel1; | ||||
|         private Sunny.UI.UIButton btnStartDevice; | ||||
|         private Sunny.UI.UIButton btnStartCheck; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										264
									
								
								Check.Main/UI/FormControlPanel.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										264
									
								
								Check.Main/UI/FormControlPanel.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,264 @@ | ||||
| using Check.Main.Camera; | ||||
| using Check.Main.Common; | ||||
| using Check.Main.Infer; | ||||
| using System; | ||||
| 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 WeifenLuo.WinFormsUI.Docking; | ||||
|  | ||||
| namespace Check.Main.UI | ||||
| { | ||||
|     public partial class FormControlPanel : DockContent | ||||
|     { | ||||
|  | ||||
|         private bool _isDeviceReady = false; // 新的状态:设备是否已准备好 | ||||
|         private bool _isDetecting = false;  // 新的状态:是否正在检测中 | ||||
|  | ||||
|         // 用于跟踪设备运行状态的私有标志 | ||||
|         private bool _isDeviceRunning = false; | ||||
|         public FormControlPanel() | ||||
|         { | ||||
|             InitializeComponent(); | ||||
|  | ||||
|             ConfigurationManager.OnConfigurationChanged += HandleConfigurationChanged; | ||||
|             UpdateUI(); | ||||
|  | ||||
|         } | ||||
|         /// <summary> | ||||
|         /// 处理全局配置在其他地方(如 FrmConfig)被更改的事件 | ||||
|         /// </summary> | ||||
|         private void HandleConfigurationChanged() | ||||
|         { | ||||
|             // 这是一个安全措施。如果设备正在运行时配置发生了变化, | ||||
|             // 最安全的做法是停止设备,以防止出现不可预知的行为。 | ||||
|             if (_isDeviceRunning) | ||||
|             { | ||||
|                 // 使用 Invoke 确保UI更新在正确的线程上执行 | ||||
|                 this.Invoke((Action)(() => | ||||
|                 { | ||||
|                     ThreadSafeLogger.Log("相机配置已在运行时发生更改,设备将自动停止。请重新启动设备以应用新配置。"); | ||||
|                     //MessageBox.Show("相机配置已在运行时发生更改,设备将自动停止。请重新启动设备以应用新配置。", | ||||
|                     //                "配置变更", MessageBoxButtons.OK, MessageBoxIcon.Information); | ||||
|                     // 触发与点击“关闭设备”按钮相同的逻辑 | ||||
|                     btnStartDevice_Click(this, EventArgs.Empty); | ||||
|                 })); | ||||
|             } | ||||
|         } | ||||
|         private void btnStartDevice_Click(object sender, EventArgs e) | ||||
|         { | ||||
|             if (_isDeviceReady)//_isDeviceRunning | ||||
|             { | ||||
|                 // --- 关闭流程 --- | ||||
|                 ThreadSafeLogger.Log("用户点击“关闭设备”,开始完整关闭流程..."); | ||||
|                 // 如果正在检测,先停止它 | ||||
|                 if (_isDetecting) | ||||
|                 { | ||||
|                     btnStartCheck_Click(this, EventArgs.Empty); // 调用停止检测的逻辑 | ||||
|                 } | ||||
|                 var mainForm = this.DockPanel.FindForm() as FrmMain; | ||||
|                 CameraManager.Shutdown(); | ||||
|                 _isDeviceReady = false; | ||||
|  | ||||
|                 //// 1. 停止硬触发模拟器 | ||||
|                 //CameraManager.StopHardwareTriggerSimulator(); | ||||
|  | ||||
|                 //// 2. 如果检测正在运行,则停止 | ||||
|                 //if (DetectionCoordinator.IsDetectionRunning) | ||||
|                 //{ | ||||
|                 //    DetectionCoordinator.StopDetection(); | ||||
|                 //    UpdateDetectionButtonUI(); | ||||
|                 //} | ||||
|  | ||||
|                 // 3. 执行完整的系统关闭(包括相机硬件和检测协调器) | ||||
|                 //CameraManager.Shutdown(); | ||||
|                 //YoloModelManager.Shutdown(); | ||||
|                 //_isDeviceRunning = false; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 // --- 启动流程 --- | ||||
|                 ThreadSafeLogger.Log("用户点击“启动设备”,开始新的启动流程..."); | ||||
|  | ||||
|                 // 1. 从单一数据源获取完整的配置对象 | ||||
|                 var config = ConfigurationManager.GetCurrentConfig(); | ||||
|                 // 2. 验证相机配置的有效性 | ||||
|                 if (config.CameraSettings == null || !config.CameraSettings.Any(c => c.IsEnabled)) | ||||
|                 { | ||||
|                     ThreadSafeLogger.Log("没有已启用的相机配置,启动中止。"); | ||||
|                     return; | ||||
|                 } | ||||
|                 // 3. 获取主窗体引用 | ||||
|                 var mainForm = this.DockPanel.FindForm() as FrmMain; | ||||
|                 if (mainForm == null) | ||||
|                 { | ||||
|                     ThreadSafeLogger.Log("无法找到主窗体,启动中止。"); | ||||
|                     return; | ||||
|                 } | ||||
|                 // 4. 执行新的启动流程: | ||||
|                 //    第一步:初始化系统。这会准备好相机硬件、UI窗口和所有后台处理线程。 | ||||
|                 CameraManager.PrepareAll(config, mainForm); | ||||
|                 _isDeviceReady = true; | ||||
|  | ||||
|  | ||||
|                 //YoloModelManager.Initialize(config.ModelSettings); | ||||
|                 //CameraManager.Initialize(config, mainForm); | ||||
|                 ////    第二步:命令所有相机开始采集图像。 | ||||
|                 //CameraManager.StartAll(); | ||||
|  | ||||
|                 //// 5. 如果有任何相机配置为软触发模式,我们启动模拟器来模拟触发信号 | ||||
|                 //if (config.CameraSettings.Any(c => c.IsEnabled && c.TriggerMode == TriggerModeType.Software)) | ||||
|                 //{ | ||||
|                 //    ThreadSafeLogger.Log("检测到软触发相机,启动触发模拟器。"); | ||||
|                 //    CameraManager.TriggerInterval = 100; // 根据需要设置间隔 | ||||
|                 //    CameraManager.StartHardwareTriggerSimulator(); | ||||
|                 //} | ||||
|  | ||||
|                 //_isDeviceRunning = true; | ||||
|             } | ||||
|             UpdateUI();//UpdateDeviceButtonUI(); | ||||
|         } | ||||
|  | ||||
|         private void btnStartCheck_Click(object sender, EventArgs e) | ||||
|         { | ||||
|             if (_isDetecting) | ||||
|             { | ||||
|                 // --- 停止检测 --- | ||||
|                 ThreadSafeLogger.Log("用户点击“停止检测”,暂停数据流..."); | ||||
|                 // 停止硬触发模拟器 | ||||
|                 CameraManager.StopHardwareTriggerSimulator(); | ||||
|                 // 停止相机采集 | ||||
|                 CameraManager.StopAll(); | ||||
|  | ||||
|                 // 停止统计 | ||||
|                 DetectionCoordinator.StopDetection(); | ||||
|  | ||||
|                 _isDetecting = false; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 // --- 开始检测 --- | ||||
|                 ThreadSafeLogger.Log("用户点击“开始检测”,启动数据流..."); | ||||
|  | ||||
|                 // 命令相机开始采集 | ||||
|                 CameraManager.StartAll(); | ||||
|  | ||||
|                 // 启动硬触发模拟器(如果需要) | ||||
|                 var config = ConfigurationManager.GetCurrentConfig(); | ||||
|                 if (config.CameraSettings.Any(c => c.IsEnabled && c.TriggerMode == TriggerModeType.Software)) | ||||
|                 { | ||||
|                     CameraManager.TriggerInterval = 100; | ||||
|                     CameraManager.StartHardwareTriggerSimulator(); | ||||
|                 } | ||||
|  | ||||
|                 // 开始统计 | ||||
|                 DetectionCoordinator.StartDetection(); | ||||
|  | ||||
|                 _isDetecting = true; | ||||
|             } | ||||
|             UpdateUI(); | ||||
|  | ||||
|             //if (!_isDeviceRunning && !DetectionCoordinator.IsDetectionRunning) | ||||
|             //{ | ||||
|             //    ThreadSafeLogger.Log("设备未启动,无法开始检测。"); | ||||
|             //    return; | ||||
|             //} | ||||
|  | ||||
|             //// 现在调用 DetectionCoordinator 中的方法 | ||||
|             //if (DetectionCoordinator.IsDetectionRunning) | ||||
|             //{ | ||||
|             //    DetectionCoordinator.StopDetection(); | ||||
|             //} | ||||
|             //else | ||||
|             //{ | ||||
|             //    DetectionCoordinator.StartDetection(); | ||||
|             //} | ||||
|             //UpdateDetectionButtonUI(); | ||||
|         } | ||||
|  | ||||
|         #region UI 更新辅助方法 | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 根据设备运行状态更新“设备”按钮的UI(文本和颜色)。 | ||||
|         /// </summary> | ||||
|         private void UpdateDeviceButtonUI() | ||||
|         { | ||||
|             if (_isDeviceRunning) | ||||
|             { | ||||
|                 btnStartDevice.Text = "关闭设备"; | ||||
|                 btnStartDevice.BackColor = Color.Salmon; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 btnStartDevice.Text = "启动设备"; | ||||
|                 btnStartDevice.BackColor = SystemColors.Control; | ||||
|             } | ||||
|         } | ||||
|         // 统一的UI更新方法 | ||||
|         private void UpdateUI() | ||||
|         { | ||||
|             // --- 更新“设备”按钮 --- | ||||
|             if (_isDeviceReady) | ||||
|             { | ||||
|                 btnStartDevice.Text = "关闭设备"; | ||||
|                 btnStartDevice.BackColor = Color.Salmon; | ||||
|                 btnStartCheck.Enabled = true; // 设备就绪后,检测按钮才可用 | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 btnStartDevice.Text = "启动设备"; | ||||
|                 btnStartDevice.BackColor = SystemColors.Control; | ||||
|                 btnStartCheck.Enabled = false; // 设备未就绪,检测按钮不可用 | ||||
|             } | ||||
|  | ||||
|             // --- 更新“检测”按钮 --- | ||||
|             if (_isDetecting) | ||||
|             { | ||||
|                 btnStartCheck.Text = "停止检测"; | ||||
|                 btnStartCheck.BackColor = Color.LightGreen; | ||||
|                 btnStartDevice.Enabled = false; // 正在检测时,不允许关闭设备 | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 btnStartCheck.Text = "开始检测"; | ||||
|                 btnStartCheck.BackColor = SystemColors.Control; | ||||
|                 if (_isDeviceReady) btnStartDevice.Enabled = true; // 停止检测后,允许关闭设备 | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 根据检测运行状态更新“检测”按钮的UI(文本和颜色)。 | ||||
|         /// </summary> | ||||
|         private void UpdateDetectionButtonUI() | ||||
|         { | ||||
|             //if (CameraManager.IsDetectionRunning) | ||||
|             //{ | ||||
|             //    btnStartCheck.Text = "停止检测"; | ||||
|             //    btnStartCheck.BackColor = Color.LightGreen; | ||||
|             //} | ||||
|             //else | ||||
|             //{ | ||||
|             //    btnStartCheck.Text = "启动检测"; | ||||
|             //    btnStartCheck.BackColor = SystemColors.Control; | ||||
|             //} | ||||
|             if (DetectionCoordinator.IsDetectionRunning) | ||||
|             { | ||||
|                 btnStartCheck.Text = "停止检测"; | ||||
|                 btnStartCheck.BackColor = Color.LightGreen; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 btnStartCheck.Text = "启动检测"; | ||||
|                 btnStartCheck.BackColor = SystemColors.Control; | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         #endregion | ||||
|     } | ||||
| } | ||||
							
								
								
									
										120
									
								
								Check.Main/UI/FormControlPanel.resx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								Check.Main/UI/FormControlPanel.resx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,120 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <root> | ||||
|   <!--  | ||||
|     Microsoft ResX Schema  | ||||
|      | ||||
|     Version 2.0 | ||||
|      | ||||
|     The primary goals of this format is to allow a simple XML format  | ||||
|     that is mostly human readable. The generation and parsing of the  | ||||
|     various data types are done through the TypeConverter classes  | ||||
|     associated with the data types. | ||||
|      | ||||
|     Example: | ||||
|      | ||||
|     ... ado.net/XML headers & schema ... | ||||
|     <resheader name="resmimetype">text/microsoft-resx</resheader> | ||||
|     <resheader name="version">2.0</resheader> | ||||
|     <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> | ||||
|     <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> | ||||
|     <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> | ||||
|     <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> | ||||
|     <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> | ||||
|         <value>[base64 mime encoded serialized .NET Framework object]</value> | ||||
|     </data> | ||||
|     <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | ||||
|         <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> | ||||
|         <comment>This is a comment</comment> | ||||
|     </data> | ||||
|                  | ||||
|     There are any number of "resheader" rows that contain simple  | ||||
|     name/value pairs. | ||||
|      | ||||
|     Each data row contains a name, and value. The row also contains a  | ||||
|     type or mimetype. Type corresponds to a .NET class that support  | ||||
|     text/value conversion through the TypeConverter architecture.  | ||||
|     Classes that don't support this are serialized and stored with the  | ||||
|     mimetype set. | ||||
|      | ||||
|     The mimetype is used for serialized objects, and tells the  | ||||
|     ResXResourceReader how to depersist the object. This is currently not  | ||||
|     extensible. For a given mimetype the value must be set accordingly: | ||||
|      | ||||
|     Note - application/x-microsoft.net.object.binary.base64 is the format  | ||||
|     that the ResXResourceWriter will generate, however the reader can  | ||||
|     read any of the formats listed below. | ||||
|      | ||||
|     mimetype: application/x-microsoft.net.object.binary.base64 | ||||
|     value   : The object must be serialized with  | ||||
|             : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter | ||||
|             : and then encoded with base64 encoding. | ||||
|      | ||||
|     mimetype: application/x-microsoft.net.object.soap.base64 | ||||
|     value   : The object must be serialized with  | ||||
|             : System.Runtime.Serialization.Formatters.Soap.SoapFormatter | ||||
|             : and then encoded with base64 encoding. | ||||
|  | ||||
|     mimetype: application/x-microsoft.net.object.bytearray.base64 | ||||
|     value   : The object must be serialized into a byte array  | ||||
|             : using a System.ComponentModel.TypeConverter | ||||
|             : and then encoded with base64 encoding. | ||||
|     --> | ||||
|   <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> | ||||
|     <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> | ||||
|     <xsd:element name="root" msdata:IsDataSet="true"> | ||||
|       <xsd:complexType> | ||||
|         <xsd:choice maxOccurs="unbounded"> | ||||
|           <xsd:element name="metadata"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" use="required" type="xsd:string" /> | ||||
|               <xsd:attribute name="type" type="xsd:string" /> | ||||
|               <xsd:attribute name="mimetype" type="xsd:string" /> | ||||
|               <xsd:attribute ref="xml:space" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="assembly"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:attribute name="alias" type="xsd:string" /> | ||||
|               <xsd:attribute name="name" type="xsd:string" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="data"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||||
|                 <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> | ||||
|               <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> | ||||
|               <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> | ||||
|               <xsd:attribute ref="xml:space" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="resheader"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" type="xsd:string" use="required" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|         </xsd:choice> | ||||
|       </xsd:complexType> | ||||
|     </xsd:element> | ||||
|   </xsd:schema> | ||||
|   <resheader name="resmimetype"> | ||||
|     <value>text/microsoft-resx</value> | ||||
|   </resheader> | ||||
|   <resheader name="version"> | ||||
|     <value>2.0</value> | ||||
|   </resheader> | ||||
|   <resheader name="reader"> | ||||
|     <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||||
|   </resheader> | ||||
|   <resheader name="writer"> | ||||
|     <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||||
|   </resheader> | ||||
| </root> | ||||
							
								
								
									
										46
									
								
								Check.Main/UI/FormImageDisplay.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								Check.Main/UI/FormImageDisplay.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| namespace Check.Main.UI | ||||
| { | ||||
|     partial class FormImageDisplay | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Required designer variable. | ||||
|         /// </summary> | ||||
|         private System.ComponentModel.IContainer components = null; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Clean up any resources being used. | ||||
|         /// </summary> | ||||
|         /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> | ||||
|         protected override void Dispose(bool disposing) | ||||
|         { | ||||
|             if (disposing && (components != null)) | ||||
|             { | ||||
|                 components.Dispose(); | ||||
|             } | ||||
|             base.Dispose(disposing); | ||||
|         } | ||||
|  | ||||
|         #region Windows Form Designer generated code | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Required method for Designer support - do not modify | ||||
|         /// the contents of this method with the code editor. | ||||
|         /// </summary> | ||||
|         private void InitializeComponent() | ||||
|         { | ||||
|             this.SuspendLayout(); | ||||
|             //  | ||||
|             // FormImageDisplay | ||||
|             //  | ||||
|             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); | ||||
|             this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; | ||||
|             this.ClientSize = new System.Drawing.Size(508, 290); | ||||
|             this.Name = "FormImageDisplay"; | ||||
|             this.Text = "FormImageDisplay"; | ||||
|             this.ResumeLayout(false); | ||||
|  | ||||
|         } | ||||
|  | ||||
|         #endregion | ||||
|     } | ||||
| } | ||||
							
								
								
									
										74
									
								
								Check.Main/UI/FormImageDisplay.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								Check.Main/UI/FormImageDisplay.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | ||||
| using System; | ||||
| 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 WeifenLuo.WinFormsUI.Docking; | ||||
|  | ||||
| namespace Check.Main.UI | ||||
| { | ||||
|     public partial class FormImageDisplay : DockContent | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// 相机名称 | ||||
|         /// </summary> | ||||
|         public string CameraName { get; set; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 当此显示窗口发生特定事件时(如ROI裁剪),触发此事件以通知外部(如日志系统) | ||||
|         /// </summary> | ||||
|         public event Action<string> OnDisplayEvent; | ||||
|  | ||||
|         // 使用我们全新的自定义控件 | ||||
|         private ZoomPictureBox zoomPictureBox; | ||||
|         public FormImageDisplay() | ||||
|         { | ||||
|             InitializeComponent(); | ||||
|  | ||||
|             // 实例化新的控件 | ||||
|             zoomPictureBox = new ZoomPictureBox | ||||
|             { | ||||
|                 Dock = DockStyle.Fill, | ||||
|                 // 其他属性可以在这里设置,例如 | ||||
|                 // RectangleColor = Color.LawnGreen, | ||||
|                 // BackgroundFillColor = Color.FromArgb(45, 45, 48) | ||||
|             }; | ||||
|             this.Controls.Add(zoomPictureBox); | ||||
|  | ||||
|             // 订阅自定义控件的ROI裁剪完成事件 | ||||
|             zoomPictureBox.CroppingEnabled = false; | ||||
|             //zoomPictureBox.Cropped += ZoomPictureBox_Cropped; | ||||
|         } | ||||
|         /// <summary> | ||||
|         /// 更新显示的图像(线程安全)。 | ||||
|         /// 此方法现在将图像设置到 ZoomPictureBox1 控件中。 | ||||
|         /// </summary> | ||||
|         /// <param name="image">从相机事件传来的原始Bitmap</param> | ||||
|         public void UpdateImage(Bitmap image) | ||||
|         { | ||||
|             if (zoomPictureBox != null && !zoomPictureBox.IsDisposed) | ||||
|             { | ||||
|                 zoomPictureBox.SetImageThreadSafe(image); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 // 如果PictureBox已经被释放,那么我们也应该释放这个多余的图像 | ||||
|                 image?.Dispose(); | ||||
|             } | ||||
|         } | ||||
|         // 重写 Close 方法,确保在窗口关闭时,内部的控件和资源也能被妥善处理 | ||||
|         public new void Close() | ||||
|         { | ||||
|             // 取消事件订阅,防止内存泄漏 | ||||
|             if (zoomPictureBox != null) | ||||
|             { | ||||
|                 //zoomPictureBox.Cropped -= ZoomPictureBox_Cropped; | ||||
|             } | ||||
|             base.Close(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										120
									
								
								Check.Main/UI/FormImageDisplay.resx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								Check.Main/UI/FormImageDisplay.resx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,120 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <root> | ||||
|   <!--  | ||||
|     Microsoft ResX Schema  | ||||
|      | ||||
|     Version 2.0 | ||||
|      | ||||
|     The primary goals of this format is to allow a simple XML format  | ||||
|     that is mostly human readable. The generation and parsing of the  | ||||
|     various data types are done through the TypeConverter classes  | ||||
|     associated with the data types. | ||||
|      | ||||
|     Example: | ||||
|      | ||||
|     ... ado.net/XML headers & schema ... | ||||
|     <resheader name="resmimetype">text/microsoft-resx</resheader> | ||||
|     <resheader name="version">2.0</resheader> | ||||
|     <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> | ||||
|     <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> | ||||
|     <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> | ||||
|     <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> | ||||
|     <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> | ||||
|         <value>[base64 mime encoded serialized .NET Framework object]</value> | ||||
|     </data> | ||||
|     <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | ||||
|         <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> | ||||
|         <comment>This is a comment</comment> | ||||
|     </data> | ||||
|                  | ||||
|     There are any number of "resheader" rows that contain simple  | ||||
|     name/value pairs. | ||||
|      | ||||
|     Each data row contains a name, and value. The row also contains a  | ||||
|     type or mimetype. Type corresponds to a .NET class that support  | ||||
|     text/value conversion through the TypeConverter architecture.  | ||||
|     Classes that don't support this are serialized and stored with the  | ||||
|     mimetype set. | ||||
|      | ||||
|     The mimetype is used for serialized objects, and tells the  | ||||
|     ResXResourceReader how to depersist the object. This is currently not  | ||||
|     extensible. For a given mimetype the value must be set accordingly: | ||||
|      | ||||
|     Note - application/x-microsoft.net.object.binary.base64 is the format  | ||||
|     that the ResXResourceWriter will generate, however the reader can  | ||||
|     read any of the formats listed below. | ||||
|      | ||||
|     mimetype: application/x-microsoft.net.object.binary.base64 | ||||
|     value   : The object must be serialized with  | ||||
|             : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter | ||||
|             : and then encoded with base64 encoding. | ||||
|      | ||||
|     mimetype: application/x-microsoft.net.object.soap.base64 | ||||
|     value   : The object must be serialized with  | ||||
|             : System.Runtime.Serialization.Formatters.Soap.SoapFormatter | ||||
|             : and then encoded with base64 encoding. | ||||
|  | ||||
|     mimetype: application/x-microsoft.net.object.bytearray.base64 | ||||
|     value   : The object must be serialized into a byte array  | ||||
|             : using a System.ComponentModel.TypeConverter | ||||
|             : and then encoded with base64 encoding. | ||||
|     --> | ||||
|   <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> | ||||
|     <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> | ||||
|     <xsd:element name="root" msdata:IsDataSet="true"> | ||||
|       <xsd:complexType> | ||||
|         <xsd:choice maxOccurs="unbounded"> | ||||
|           <xsd:element name="metadata"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" use="required" type="xsd:string" /> | ||||
|               <xsd:attribute name="type" type="xsd:string" /> | ||||
|               <xsd:attribute name="mimetype" type="xsd:string" /> | ||||
|               <xsd:attribute ref="xml:space" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="assembly"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:attribute name="alias" type="xsd:string" /> | ||||
|               <xsd:attribute name="name" type="xsd:string" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="data"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||||
|                 <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> | ||||
|               <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> | ||||
|               <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> | ||||
|               <xsd:attribute ref="xml:space" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="resheader"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" type="xsd:string" use="required" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|         </xsd:choice> | ||||
|       </xsd:complexType> | ||||
|     </xsd:element> | ||||
|   </xsd:schema> | ||||
|   <resheader name="resmimetype"> | ||||
|     <value>text/microsoft-resx</value> | ||||
|   </resheader> | ||||
|   <resheader name="version"> | ||||
|     <value>2.0</value> | ||||
|   </resheader> | ||||
|   <resheader name="reader"> | ||||
|     <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||||
|   </resheader> | ||||
|   <resheader name="writer"> | ||||
|     <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||||
|   </resheader> | ||||
| </root> | ||||
							
								
								
									
										245
									
								
								Check.Main/UI/FormStatistics.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								Check.Main/UI/FormStatistics.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,245 @@ | ||||
| namespace Check.Main.UI | ||||
| { | ||||
|     partial class FormStatistics | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Required designer variable. | ||||
|         /// </summary> | ||||
|         private System.ComponentModel.IContainer components = null; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Clean up any resources being used. | ||||
|         /// </summary> | ||||
|         /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> | ||||
|         protected override void Dispose(bool disposing) | ||||
|         { | ||||
|             if (disposing && (components != null)) | ||||
|             { | ||||
|                 components.Dispose(); | ||||
|             } | ||||
|             base.Dispose(disposing); | ||||
|         } | ||||
|  | ||||
|         #region Windows Form Designer generated code | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Required method for Designer support - do not modify | ||||
|         /// the contents of this method with the code editor. | ||||
|         /// </summary> | ||||
|         private void InitializeComponent() | ||||
|         { | ||||
|             System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FormStatistics)); | ||||
|             this.uiTableLayoutPanel1 = new Sunny.UI.UITableLayoutPanel(); | ||||
|             this.toolStrip1 = new System.Windows.Forms.ToolStrip(); | ||||
|             this.uiLabel1 = new Sunny.UI.UILabel(); | ||||
|             this.uiLabel2 = new Sunny.UI.UILabel(); | ||||
|             this.uiLabel3 = new Sunny.UI.UILabel(); | ||||
|             this.uiLabel4 = new Sunny.UI.UILabel(); | ||||
|             this.txtOKNum = new Sunny.UI.UITextBox(); | ||||
|             this.txtNGNum = new Sunny.UI.UITextBox(); | ||||
|             this.txtTotal = new Sunny.UI.UITextBox(); | ||||
|             this.txtYieldRate = new Sunny.UI.UITextBox(); | ||||
|             this.toolStripButtonRest = new System.Windows.Forms.ToolStripButton(); | ||||
|             this.uiTableLayoutPanel1.SuspendLayout(); | ||||
|             this.toolStrip1.SuspendLayout(); | ||||
|             this.SuspendLayout(); | ||||
|             //  | ||||
|             // uiTableLayoutPanel1 | ||||
|             //  | ||||
|             this.uiTableLayoutPanel1.ColumnCount = 2; | ||||
|             this.uiTableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 45.88745F)); | ||||
|             this.uiTableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 54.11255F)); | ||||
|             this.uiTableLayoutPanel1.Controls.Add(this.txtYieldRate, 1, 3); | ||||
|             this.uiTableLayoutPanel1.Controls.Add(this.txtTotal, 1, 2); | ||||
|             this.uiTableLayoutPanel1.Controls.Add(this.txtNGNum, 1, 1); | ||||
|             this.uiTableLayoutPanel1.Controls.Add(this.toolStrip1, 0, 4); | ||||
|             this.uiTableLayoutPanel1.Controls.Add(this.uiLabel1, 0, 0); | ||||
|             this.uiTableLayoutPanel1.Controls.Add(this.uiLabel2, 0, 1); | ||||
|             this.uiTableLayoutPanel1.Controls.Add(this.uiLabel3, 0, 2); | ||||
|             this.uiTableLayoutPanel1.Controls.Add(this.uiLabel4, 0, 3); | ||||
|             this.uiTableLayoutPanel1.Controls.Add(this.txtOKNum, 1, 0); | ||||
|             this.uiTableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; | ||||
|             this.uiTableLayoutPanel1.Location = new System.Drawing.Point(0, 0); | ||||
|             this.uiTableLayoutPanel1.Name = "uiTableLayoutPanel1"; | ||||
|             this.uiTableLayoutPanel1.RowCount = 5; | ||||
|             this.uiTableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F)); | ||||
|             this.uiTableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F)); | ||||
|             this.uiTableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F)); | ||||
|             this.uiTableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F)); | ||||
|             this.uiTableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F)); | ||||
|             this.uiTableLayoutPanel1.Size = new System.Drawing.Size(231, 228); | ||||
|             this.uiTableLayoutPanel1.TabIndex = 0; | ||||
|             this.uiTableLayoutPanel1.TagString = null; | ||||
|             //  | ||||
|             // toolStrip1 | ||||
|             //  | ||||
|             this.uiTableLayoutPanel1.SetColumnSpan(this.toolStrip1, 2); | ||||
|             this.toolStrip1.Dock = System.Windows.Forms.DockStyle.Fill; | ||||
|             this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { | ||||
|             this.toolStripButtonRest}); | ||||
|             this.toolStrip1.Location = new System.Drawing.Point(0, 180); | ||||
|             this.toolStrip1.Name = "toolStrip1"; | ||||
|             this.toolStrip1.Size = new System.Drawing.Size(231, 48); | ||||
|             this.toolStrip1.TabIndex = 0; | ||||
|             this.toolStrip1.Text = "toolStrip1"; | ||||
|             //  | ||||
|             // uiLabel1 | ||||
|             //  | ||||
|             this.uiLabel1.Dock = System.Windows.Forms.DockStyle.Fill; | ||||
|             this.uiLabel1.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); | ||||
|             this.uiLabel1.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48))))); | ||||
|             this.uiLabel1.Location = new System.Drawing.Point(3, 0); | ||||
|             this.uiLabel1.Name = "uiLabel1"; | ||||
|             this.uiLabel1.Size = new System.Drawing.Size(100, 45); | ||||
|             this.uiLabel1.TabIndex = 1; | ||||
|             this.uiLabel1.Text = "良品数"; | ||||
|             this.uiLabel1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; | ||||
|             //  | ||||
|             // uiLabel2 | ||||
|             //  | ||||
|             this.uiLabel2.Dock = System.Windows.Forms.DockStyle.Fill; | ||||
|             this.uiLabel2.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); | ||||
|             this.uiLabel2.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48))))); | ||||
|             this.uiLabel2.Location = new System.Drawing.Point(3, 45); | ||||
|             this.uiLabel2.Name = "uiLabel2"; | ||||
|             this.uiLabel2.Size = new System.Drawing.Size(100, 45); | ||||
|             this.uiLabel2.TabIndex = 2; | ||||
|             this.uiLabel2.Text = "不良品数量"; | ||||
|             this.uiLabel2.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; | ||||
|             //  | ||||
|             // uiLabel3 | ||||
|             //  | ||||
|             this.uiLabel3.Dock = System.Windows.Forms.DockStyle.Fill; | ||||
|             this.uiLabel3.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); | ||||
|             this.uiLabel3.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48))))); | ||||
|             this.uiLabel3.Location = new System.Drawing.Point(3, 90); | ||||
|             this.uiLabel3.Name = "uiLabel3"; | ||||
|             this.uiLabel3.Size = new System.Drawing.Size(100, 45); | ||||
|             this.uiLabel3.TabIndex = 3; | ||||
|             this.uiLabel3.Text = "总数"; | ||||
|             this.uiLabel3.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; | ||||
|             //  | ||||
|             // uiLabel4 | ||||
|             //  | ||||
|             this.uiLabel4.Dock = System.Windows.Forms.DockStyle.Fill; | ||||
|             this.uiLabel4.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); | ||||
|             this.uiLabel4.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48))))); | ||||
|             this.uiLabel4.Location = new System.Drawing.Point(3, 135); | ||||
|             this.uiLabel4.Name = "uiLabel4"; | ||||
|             this.uiLabel4.Size = new System.Drawing.Size(100, 45); | ||||
|             this.uiLabel4.TabIndex = 4; | ||||
|             this.uiLabel4.Text = "良品率"; | ||||
|             this.uiLabel4.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; | ||||
|             //  | ||||
|             // txtOKNum | ||||
|             //  | ||||
|             this.txtOKNum.Cursor = System.Windows.Forms.Cursors.IBeam; | ||||
|             this.txtOKNum.Dock = System.Windows.Forms.DockStyle.Fill; | ||||
|             this.txtOKNum.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); | ||||
|             this.txtOKNum.Location = new System.Drawing.Point(110, 5); | ||||
|             this.txtOKNum.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); | ||||
|             this.txtOKNum.MinimumSize = new System.Drawing.Size(1, 16); | ||||
|             this.txtOKNum.Name = "txtOKNum"; | ||||
|             this.txtOKNum.Padding = new System.Windows.Forms.Padding(5); | ||||
|             this.txtOKNum.ShowText = false; | ||||
|             this.txtOKNum.Size = new System.Drawing.Size(117, 35); | ||||
|             this.txtOKNum.TabIndex = 5; | ||||
|             this.txtOKNum.Text = "0"; | ||||
|             this.txtOKNum.TextAlignment = System.Drawing.ContentAlignment.MiddleCenter; | ||||
|             this.txtOKNum.Watermark = ""; | ||||
|             //  | ||||
|             // txtNGNum | ||||
|             //  | ||||
|             this.txtNGNum.Cursor = System.Windows.Forms.Cursors.IBeam; | ||||
|             this.txtNGNum.Dock = System.Windows.Forms.DockStyle.Fill; | ||||
|             this.txtNGNum.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); | ||||
|             this.txtNGNum.Location = new System.Drawing.Point(110, 50); | ||||
|             this.txtNGNum.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); | ||||
|             this.txtNGNum.MinimumSize = new System.Drawing.Size(1, 16); | ||||
|             this.txtNGNum.Name = "txtNGNum"; | ||||
|             this.txtNGNum.Padding = new System.Windows.Forms.Padding(5); | ||||
|             this.txtNGNum.ShowText = false; | ||||
|             this.txtNGNum.Size = new System.Drawing.Size(117, 35); | ||||
|             this.txtNGNum.TabIndex = 6; | ||||
|             this.txtNGNum.Text = "0"; | ||||
|             this.txtNGNum.TextAlignment = System.Drawing.ContentAlignment.MiddleCenter; | ||||
|             this.txtNGNum.Watermark = ""; | ||||
|             //  | ||||
|             // txtTotal | ||||
|             //  | ||||
|             this.txtTotal.Cursor = System.Windows.Forms.Cursors.IBeam; | ||||
|             this.txtTotal.Dock = System.Windows.Forms.DockStyle.Fill; | ||||
|             this.txtTotal.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); | ||||
|             this.txtTotal.Location = new System.Drawing.Point(110, 95); | ||||
|             this.txtTotal.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); | ||||
|             this.txtTotal.MinimumSize = new System.Drawing.Size(1, 16); | ||||
|             this.txtTotal.Name = "txtTotal"; | ||||
|             this.txtTotal.Padding = new System.Windows.Forms.Padding(5); | ||||
|             this.txtTotal.ShowText = false; | ||||
|             this.txtTotal.Size = new System.Drawing.Size(117, 35); | ||||
|             this.txtTotal.TabIndex = 7; | ||||
|             this.txtTotal.Text = "0"; | ||||
|             this.txtTotal.TextAlignment = System.Drawing.ContentAlignment.MiddleCenter; | ||||
|             this.txtTotal.Watermark = ""; | ||||
|             //  | ||||
|             // txtYieldRate | ||||
|             //  | ||||
|             this.txtYieldRate.Cursor = System.Windows.Forms.Cursors.IBeam; | ||||
|             this.txtYieldRate.Dock = System.Windows.Forms.DockStyle.Fill; | ||||
|             this.txtYieldRate.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); | ||||
|             this.txtYieldRate.Location = new System.Drawing.Point(110, 140); | ||||
|             this.txtYieldRate.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); | ||||
|             this.txtYieldRate.MinimumSize = new System.Drawing.Size(1, 16); | ||||
|             this.txtYieldRate.Name = "txtYieldRate"; | ||||
|             this.txtYieldRate.Padding = new System.Windows.Forms.Padding(5); | ||||
|             this.txtYieldRate.ShowText = false; | ||||
|             this.txtYieldRate.Size = new System.Drawing.Size(117, 35); | ||||
|             this.txtYieldRate.TabIndex = 8; | ||||
|             this.txtYieldRate.Text = "0"; | ||||
|             this.txtYieldRate.TextAlignment = System.Drawing.ContentAlignment.MiddleCenter; | ||||
|             this.txtYieldRate.Watermark = ""; | ||||
|             //  | ||||
|             // toolStripButtonRest | ||||
|             //  | ||||
|             this.toolStripButtonRest.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; | ||||
|             this.toolStripButtonRest.Image = ((System.Drawing.Image)(resources.GetObject("toolStripButtonRest.Image"))); | ||||
|             this.toolStripButtonRest.ImageTransparentColor = System.Drawing.Color.Magenta; | ||||
|             this.toolStripButtonRest.Name = "toolStripButtonRest"; | ||||
|             this.toolStripButtonRest.Size = new System.Drawing.Size(60, 45); | ||||
|             this.toolStripButtonRest.Text = "重置计数"; | ||||
|             this.toolStripButtonRest.Click += new System.EventHandler(this.toolStripButtonRest_Click); | ||||
|             //  | ||||
|             // FormStatistics | ||||
|             //  | ||||
|             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); | ||||
|             this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; | ||||
|             this.ClientSize = new System.Drawing.Size(231, 228); | ||||
|             this.ControlBox = false; | ||||
|             this.Controls.Add(this.uiTableLayoutPanel1); | ||||
|             this.MaximizeBox = false; | ||||
|             this.MinimizeBox = false; | ||||
|             this.Name = "FormStatistics"; | ||||
|             this.Text = "统计"; | ||||
|             this.uiTableLayoutPanel1.ResumeLayout(false); | ||||
|             this.uiTableLayoutPanel1.PerformLayout(); | ||||
|             this.toolStrip1.ResumeLayout(false); | ||||
|             this.toolStrip1.PerformLayout(); | ||||
|             this.ResumeLayout(false); | ||||
|  | ||||
|         } | ||||
|  | ||||
|         #endregion | ||||
|  | ||||
|         private Sunny.UI.UITableLayoutPanel uiTableLayoutPanel1; | ||||
|         private System.Windows.Forms.ToolStrip toolStrip1; | ||||
|         private Sunny.UI.UITextBox txtYieldRate; | ||||
|         private Sunny.UI.UITextBox txtTotal; | ||||
|         private Sunny.UI.UITextBox txtNGNum; | ||||
|         private System.Windows.Forms.ToolStripButton toolStripButtonRest; | ||||
|         private Sunny.UI.UILabel uiLabel1; | ||||
|         private Sunny.UI.UILabel uiLabel2; | ||||
|         private Sunny.UI.UILabel uiLabel3; | ||||
|         private Sunny.UI.UILabel uiLabel4; | ||||
|         private Sunny.UI.UITextBox txtOKNum; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										89
									
								
								Check.Main/UI/FormStatistics.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								Check.Main/UI/FormStatistics.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| using Check.Main.Camera; | ||||
| using Check.Main.Common; | ||||
| using Check.Main.Infer; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.ComponentModel; | ||||
| using System.Data; | ||||
| using System.Drawing; | ||||
| using System.Globalization; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
| using System.Threading.Tasks; | ||||
| using System.Windows.Forms; | ||||
| using WeifenLuo.WinFormsUI.Docking; | ||||
|  | ||||
| namespace Check.Main.UI | ||||
| { | ||||
|     public partial class FormStatistics : DockContent | ||||
|     { | ||||
|         private readonly StatisticsData _statisticsData; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 将私有字段通过公共属性暴露,以便外部(如FormMain)访问。 | ||||
|         /// </summary> | ||||
|         public StatisticsData CurrentStatistics => _statisticsData; | ||||
|         public FormStatistics() | ||||
|         { | ||||
|             InitializeComponent(); | ||||
|             _statisticsData = new StatisticsData(); | ||||
|  | ||||
|             // 订阅CameraManager的检测完成事件 | ||||
|             DetectionCoordinator.OnDetectionCompleted += CameraManager_OnDetectionCompleted; | ||||
|             // 初始化UI显示 | ||||
|             UpdateUI(); | ||||
|         } | ||||
|         // 当CameraManager发布检测结果时,此方法被调用 | ||||
|         private void CameraManager_OnDetectionCompleted(object sender, DetectionResultEventArgs e) | ||||
|         { | ||||
|             // 更新统计数据 | ||||
|             _statisticsData.UpdateWithResult(e.IsOK); | ||||
|  | ||||
|             // 在UI线程上更新界面显示 | ||||
|             UpdateUI(); | ||||
|         } | ||||
|         // 更新所有标签的文本 | ||||
|         private void UpdateUI() | ||||
|         { | ||||
|             // 使用Invoke确保线程安全 | ||||
|             if (this.InvokeRequired) | ||||
|             { | ||||
|                 this.Invoke(new Action(UpdateUI)); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             txtOKNum.Text = _statisticsData.GoodCount.ToString(); | ||||
|             txtNGNum.Text = _statisticsData.NgCount.ToString(); | ||||
|             txtTotal.Text = _statisticsData.TotalCount.ToString(); | ||||
|             // 将良率格式化为百分比 | ||||
|             txtYieldRate.Text = _statisticsData.YieldRate.ToString("P2", CultureInfo.InvariantCulture); | ||||
|  | ||||
|             // 根据良率改变颜色以示提醒 | ||||
|             if (_statisticsData.YieldRate < 0.9 && _statisticsData.TotalCount > 10) | ||||
|             { | ||||
|                 txtYieldRate.ForeColor = Color.Red; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 txtYieldRate.ForeColor = Color.ForestGreen; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private void toolStripButtonRest_Click(object sender, EventArgs e) | ||||
|         { | ||||
|             StatisticsExporter.ExportToExcel(CurrentStatistics, "Reset"); | ||||
|  | ||||
|             _statisticsData.Reset(); | ||||
|             // 同时重置产品ID计数器 | ||||
|             DetectionCoordinator.ResetAllCounters(); | ||||
|             UpdateUI(); | ||||
|             ThreadSafeLogger.Log("统计数据已重置。"); | ||||
|         } | ||||
|         // 窗体关闭时,取消事件订阅,防止内存泄漏 | ||||
|         protected override void OnFormClosing(FormClosingEventArgs e) | ||||
|         { | ||||
|             DetectionCoordinator.OnDetectionCompleted -= CameraManager_OnDetectionCompleted; | ||||
|             base.OnFormClosing(e); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										139
									
								
								Check.Main/UI/FormStatistics.resx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								Check.Main/UI/FormStatistics.resx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,139 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <root> | ||||
|   <!--  | ||||
|     Microsoft ResX Schema  | ||||
|      | ||||
|     Version 2.0 | ||||
|      | ||||
|     The primary goals of this format is to allow a simple XML format  | ||||
|     that is mostly human readable. The generation and parsing of the  | ||||
|     various data types are done through the TypeConverter classes  | ||||
|     associated with the data types. | ||||
|      | ||||
|     Example: | ||||
|      | ||||
|     ... ado.net/XML headers & schema ... | ||||
|     <resheader name="resmimetype">text/microsoft-resx</resheader> | ||||
|     <resheader name="version">2.0</resheader> | ||||
|     <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> | ||||
|     <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> | ||||
|     <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> | ||||
|     <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> | ||||
|     <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> | ||||
|         <value>[base64 mime encoded serialized .NET Framework object]</value> | ||||
|     </data> | ||||
|     <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | ||||
|         <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> | ||||
|         <comment>This is a comment</comment> | ||||
|     </data> | ||||
|                  | ||||
|     There are any number of "resheader" rows that contain simple  | ||||
|     name/value pairs. | ||||
|      | ||||
|     Each data row contains a name, and value. The row also contains a  | ||||
|     type or mimetype. Type corresponds to a .NET class that support  | ||||
|     text/value conversion through the TypeConverter architecture.  | ||||
|     Classes that don't support this are serialized and stored with the  | ||||
|     mimetype set. | ||||
|      | ||||
|     The mimetype is used for serialized objects, and tells the  | ||||
|     ResXResourceReader how to depersist the object. This is currently not  | ||||
|     extensible. For a given mimetype the value must be set accordingly: | ||||
|      | ||||
|     Note - application/x-microsoft.net.object.binary.base64 is the format  | ||||
|     that the ResXResourceWriter will generate, however the reader can  | ||||
|     read any of the formats listed below. | ||||
|      | ||||
|     mimetype: application/x-microsoft.net.object.binary.base64 | ||||
|     value   : The object must be serialized with  | ||||
|             : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter | ||||
|             : and then encoded with base64 encoding. | ||||
|      | ||||
|     mimetype: application/x-microsoft.net.object.soap.base64 | ||||
|     value   : The object must be serialized with  | ||||
|             : System.Runtime.Serialization.Formatters.Soap.SoapFormatter | ||||
|             : and then encoded with base64 encoding. | ||||
|  | ||||
|     mimetype: application/x-microsoft.net.object.bytearray.base64 | ||||
|     value   : The object must be serialized into a byte array  | ||||
|             : using a System.ComponentModel.TypeConverter | ||||
|             : and then encoded with base64 encoding. | ||||
|     --> | ||||
|   <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> | ||||
|     <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> | ||||
|     <xsd:element name="root" msdata:IsDataSet="true"> | ||||
|       <xsd:complexType> | ||||
|         <xsd:choice maxOccurs="unbounded"> | ||||
|           <xsd:element name="metadata"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" use="required" type="xsd:string" /> | ||||
|               <xsd:attribute name="type" type="xsd:string" /> | ||||
|               <xsd:attribute name="mimetype" type="xsd:string" /> | ||||
|               <xsd:attribute ref="xml:space" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="assembly"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:attribute name="alias" type="xsd:string" /> | ||||
|               <xsd:attribute name="name" type="xsd:string" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="data"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||||
|                 <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> | ||||
|               <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> | ||||
|               <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> | ||||
|               <xsd:attribute ref="xml:space" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="resheader"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" type="xsd:string" use="required" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|         </xsd:choice> | ||||
|       </xsd:complexType> | ||||
|     </xsd:element> | ||||
|   </xsd:schema> | ||||
|   <resheader name="resmimetype"> | ||||
|     <value>text/microsoft-resx</value> | ||||
|   </resheader> | ||||
|   <resheader name="version"> | ||||
|     <value>2.0</value> | ||||
|   </resheader> | ||||
|   <resheader name="reader"> | ||||
|     <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||||
|   </resheader> | ||||
|   <resheader name="writer"> | ||||
|     <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||||
|   </resheader> | ||||
|   <metadata name="toolStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> | ||||
|     <value>17, 17</value> | ||||
|   </metadata> | ||||
|   <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> | ||||
|   <data name="toolStripButtonRest.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | ||||
|     <value> | ||||
|         iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 | ||||
|         YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIDSURBVDhPpZLrS5NhGMb3j4SWh0oRQVExD4gonkDpg4hG | ||||
|         YKxG6WBogkMZKgPNCEVJFBGdGETEvgwyO9DJE5syZw3PIlPEE9pgBCLZ5XvdMB8Ew8gXbl54nuf63dd9 | ||||
|         0OGSnwCahxbPRNPAPMw9Xpg6ZmF46kZZ0xSKzJPIrhpDWsVnpBhGkKx3nAX8Pv7z1zg8OoY/cITdn4fw | ||||
|         bf/C0kYAN3Ma/w3gWfZL5kzTKBxjWyK2DftwI9tyMYCZKXbNHaD91bLYJrDXsYbrWfUKwJrPE9M2M1Oc | ||||
|         VzOOpHI7Jr376Hi9ogHqFIANO0/MmmmbmSmm9a8ze+I4MrNWAdjtoJgWcx+PSzg166yZZ8xM8XvXDix9 | ||||
|         c4jIqFYAjoriBV9AhEPv1mH/sonogha0afbZMMZz+yreTGyhpusHwtNNCsA5U1zS4BLxzJIfg299qO32 | ||||
|         Ir7UJtZfftyATqeT+8o2D8JSjQrAJblrncYL7ZJ2+bfaFnC/1S1NjL3diRat7qrO7wLRP3HjWsojBeCo | ||||
|         mDEo5mNjuweFGvjWg2EBhCbpkW78htSHHwRyNdmgAFzPEee2iFkzayy2OLXzT4gr6UdUnlXrullsxxQ+ | ||||
|         kx0g8BTA3aZlButjSTyjODq/WcQcW/B/Je4OQhLvKQDnzN1mp0nnkvAhR8VuMzNrpm1mpjgkoVwB/v8D | ||||
|         TgDQASA1MVpwzwAAAABJRU5ErkJggg== | ||||
| </value> | ||||
|   </data> | ||||
| </root> | ||||
							
								
								
									
										175
									
								
								Check.Main/UI/FrmCamConfig.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								Check.Main/UI/FrmCamConfig.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,175 @@ | ||||
| namespace Check.Main.UI | ||||
| { | ||||
|     partial class FrmCamConfig | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Required designer variable. | ||||
|         /// </summary> | ||||
|         private System.ComponentModel.IContainer components = null; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Clean up any resources being used. | ||||
|         /// </summary> | ||||
|         /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> | ||||
|         protected override void Dispose(bool disposing) | ||||
|         { | ||||
|             if (disposing && (components != null)) | ||||
|             { | ||||
|                 components.Dispose(); | ||||
|             } | ||||
|             base.Dispose(disposing); | ||||
|         } | ||||
|  | ||||
|         #region Windows Form Designer generated code | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Required method for Designer support - do not modify | ||||
|         /// the contents of this method with the code editor. | ||||
|         /// </summary> | ||||
|         private void InitializeComponent() | ||||
|         { | ||||
|             System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FrmCamConfig)); | ||||
|             tableLayoutPanel1 = new TableLayoutPanel(); | ||||
|             toolStrip1 = new ToolStrip(); | ||||
|             toolBtnAdd = new ToolStripButton(); | ||||
|             toolBtnRemove = new ToolStripButton(); | ||||
|             toolBtnSet = new ToolStripButton(); | ||||
|             splitContainer1 = new SplitContainer(); | ||||
|             listBoxCameras = new ListBox(); | ||||
|             propertyGrid1 = new PropertyGrid(); | ||||
|             tableLayoutPanel1.SuspendLayout(); | ||||
|             toolStrip1.SuspendLayout(); | ||||
|             ((System.ComponentModel.ISupportInitialize)splitContainer1).BeginInit(); | ||||
|             splitContainer1.Panel1.SuspendLayout(); | ||||
|             splitContainer1.Panel2.SuspendLayout(); | ||||
|             splitContainer1.SuspendLayout(); | ||||
|             SuspendLayout(); | ||||
|             //  | ||||
|             // tableLayoutPanel1 | ||||
|             //  | ||||
|             tableLayoutPanel1.ColumnCount = 1; | ||||
|             tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F)); | ||||
|             tableLayoutPanel1.Controls.Add(toolStrip1, 0, 0); | ||||
|             tableLayoutPanel1.Controls.Add(splitContainer1, 0, 1); | ||||
|             tableLayoutPanel1.Dock = DockStyle.Fill; | ||||
|             tableLayoutPanel1.Location = new Point(0, 0); | ||||
|             tableLayoutPanel1.Margin = new Padding(4, 4, 4, 4); | ||||
|             tableLayoutPanel1.Name = "tableLayoutPanel1"; | ||||
|             tableLayoutPanel1.RowCount = 2; | ||||
|             tableLayoutPanel1.RowStyles.Add(new RowStyle()); | ||||
|             tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 100F)); | ||||
|             tableLayoutPanel1.Size = new Size(632, 560); | ||||
|             tableLayoutPanel1.TabIndex = 0; | ||||
|             //  | ||||
|             // toolStrip1 | ||||
|             //  | ||||
|             toolStrip1.Items.AddRange(new ToolStripItem[] { toolBtnAdd, toolBtnRemove, toolBtnSet }); | ||||
|             toolStrip1.Location = new Point(0, 0); | ||||
|             toolStrip1.Name = "toolStrip1"; | ||||
|             toolStrip1.Size = new Size(632, 25); | ||||
|             toolStrip1.TabIndex = 0; | ||||
|             toolStrip1.Text = "toolStrip1"; | ||||
|             //  | ||||
|             // toolBtnAdd | ||||
|             //  | ||||
|             toolBtnAdd.DisplayStyle = ToolStripItemDisplayStyle.Text; | ||||
|             toolBtnAdd.Image = (Image)resources.GetObject("toolBtnAdd.Image"); | ||||
|             toolBtnAdd.ImageTransparentColor = Color.Magenta; | ||||
|             toolBtnAdd.Name = "toolBtnAdd"; | ||||
|             toolBtnAdd.Size = new Size(36, 22); | ||||
|             toolBtnAdd.Text = "添加"; | ||||
|             toolBtnAdd.Click += toolBtnAdd_Click; | ||||
|             //  | ||||
|             // toolBtnRemove | ||||
|             //  | ||||
|             toolBtnRemove.DisplayStyle = ToolStripItemDisplayStyle.Text; | ||||
|             toolBtnRemove.Image = (Image)resources.GetObject("toolBtnRemove.Image"); | ||||
|             toolBtnRemove.ImageTransparentColor = Color.Magenta; | ||||
|             toolBtnRemove.Name = "toolBtnRemove"; | ||||
|             toolBtnRemove.Size = new Size(36, 22); | ||||
|             toolBtnRemove.Text = "移除"; | ||||
|             toolBtnRemove.Click += toolBtnRemove_Click; | ||||
|             //  | ||||
|             // toolBtnSet | ||||
|             //  | ||||
|             toolBtnSet.DisplayStyle = ToolStripItemDisplayStyle.Text; | ||||
|             toolBtnSet.Image = (Image)resources.GetObject("toolBtnSet.Image"); | ||||
|             toolBtnSet.ImageTransparentColor = Color.Magenta; | ||||
|             toolBtnSet.Name = "toolBtnSet"; | ||||
|             toolBtnSet.Size = new Size(60, 22); | ||||
|             toolBtnSet.Text = "应用配置"; | ||||
|             toolBtnSet.Click += toolBtnSet_Click; | ||||
|             //  | ||||
|             // splitContainer1 | ||||
|             //  | ||||
|             splitContainer1.Dock = DockStyle.Fill; | ||||
|             splitContainer1.Location = new Point(4, 29); | ||||
|             splitContainer1.Margin = new Padding(4, 4, 4, 4); | ||||
|             splitContainer1.Name = "splitContainer1"; | ||||
|             //  | ||||
|             // splitContainer1.Panel1 | ||||
|             //  | ||||
|             splitContainer1.Panel1.Controls.Add(listBoxCameras); | ||||
|             //  | ||||
|             // splitContainer1.Panel2 | ||||
|             //  | ||||
|             splitContainer1.Panel2.Controls.Add(propertyGrid1); | ||||
|             splitContainer1.Size = new Size(624, 527); | ||||
|             splitContainer1.SplitterDistance = 210; | ||||
|             splitContainer1.SplitterWidth = 5; | ||||
|             splitContainer1.TabIndex = 1; | ||||
|             //  | ||||
|             // listBoxCameras | ||||
|             //  | ||||
|             listBoxCameras.Dock = DockStyle.Fill; | ||||
|             listBoxCameras.FormattingEnabled = true; | ||||
|             listBoxCameras.ItemHeight = 17; | ||||
|             listBoxCameras.Location = new Point(0, 0); | ||||
|             listBoxCameras.Margin = new Padding(4, 4, 4, 4); | ||||
|             listBoxCameras.Name = "listBoxCameras"; | ||||
|             listBoxCameras.Size = new Size(210, 527); | ||||
|             listBoxCameras.TabIndex = 0; | ||||
|             listBoxCameras.SelectedIndexChanged += listBoxCameras_SelectedIndexChanged; | ||||
|             //  | ||||
|             // propertyGrid1 | ||||
|             //  | ||||
|             propertyGrid1.Dock = DockStyle.Fill; | ||||
|             propertyGrid1.Location = new Point(0, 0); | ||||
|             propertyGrid1.Margin = new Padding(4, 4, 4, 4); | ||||
|             propertyGrid1.Name = "propertyGrid1"; | ||||
|             propertyGrid1.Size = new Size(409, 527); | ||||
|             propertyGrid1.TabIndex = 0; | ||||
|             //  | ||||
|             // FrmCamConfig | ||||
|             //  | ||||
|             AutoScaleDimensions = new SizeF(7F, 17F); | ||||
|             AutoScaleMode = AutoScaleMode.Font; | ||||
|             ClientSize = new Size(632, 560); | ||||
|             Controls.Add(tableLayoutPanel1); | ||||
|             Margin = new Padding(4, 4, 4, 4); | ||||
|             Name = "FrmCamConfig"; | ||||
|             Text = "属性配置"; | ||||
|             tableLayoutPanel1.ResumeLayout(false); | ||||
|             tableLayoutPanel1.PerformLayout(); | ||||
|             toolStrip1.ResumeLayout(false); | ||||
|             toolStrip1.PerformLayout(); | ||||
|             splitContainer1.Panel1.ResumeLayout(false); | ||||
|             splitContainer1.Panel2.ResumeLayout(false); | ||||
|             ((System.ComponentModel.ISupportInitialize)splitContainer1).EndInit(); | ||||
|             splitContainer1.ResumeLayout(false); | ||||
|             ResumeLayout(false); | ||||
|  | ||||
|         } | ||||
|  | ||||
|         #endregion | ||||
|  | ||||
|         private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; | ||||
|         private System.Windows.Forms.ToolStrip toolStrip1; | ||||
|         private System.Windows.Forms.SplitContainer splitContainer1; | ||||
|         private System.Windows.Forms.ListBox listBoxCameras; | ||||
|         private System.Windows.Forms.PropertyGrid propertyGrid1; | ||||
|         private System.Windows.Forms.ToolStripButton toolBtnAdd; | ||||
|         private System.Windows.Forms.ToolStripButton toolBtnRemove; | ||||
|         private System.Windows.Forms.ToolStripButton toolBtnSet; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										193
									
								
								Check.Main/UI/FrmCamConfig.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								Check.Main/UI/FrmCamConfig.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,193 @@ | ||||
| using Check.Main.Camera; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.ComponentModel; | ||||
| using System.Data; | ||||
| using System.Drawing; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
| using System.Threading.Tasks; | ||||
| using System.Windows.Forms; | ||||
| using System.Xml.Serialization; | ||||
| using WeifenLuo.WinFormsUI.Docking; | ||||
|  | ||||
| namespace Check.Main.UI | ||||
| { | ||||
|     public partial class FrmCamConfig : DockContent | ||||
|     { | ||||
|        // public List<CameraSettings> _settingsList = new List<CameraSettings>(); | ||||
|         public List<CameraSettings> _settingsList { get; private set; } | ||||
|         private readonly string _configFilePath = Path.Combine(Application.StartupPath, "cameras.xml"); | ||||
|         public FrmCamConfig() | ||||
|         { | ||||
|             InitializeComponent(); | ||||
|             _settingsList = new List<CameraSettings>(); | ||||
|             LoadSettings(); | ||||
|             RefreshListBox(); | ||||
|             AdaptForDialogMode(); // 调整UI为对话框模式 | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 用于从外部接收一个设置列表进行编辑。 | ||||
|         /// </summary> | ||||
|         /// <param name="settingsToEdit">要编辑的相机设置列表</param> | ||||
|         public FrmCamConfig(List<CameraSettings> settingsToEdit) | ||||
|         { | ||||
|             InitializeComponent(); | ||||
|  | ||||
|             // 创建一个现有列表的副本进行编辑,这样如果用户点“取消”,原始列表不会被影响 | ||||
|             _settingsList = settingsToEdit != null | ||||
|                 ? settingsToEdit.Select(s => s.Clone() as CameraSettings).ToList() // 深度克隆更好,这里为简化用浅克隆 | ||||
|                 : new List<CameraSettings>(); | ||||
|  | ||||
|             // 别忘了为列表中的每个对象重新订阅事件 | ||||
|             foreach (var setting in _settingsList) | ||||
|             { | ||||
|                 setting.PropertyChanged += Setting_PropertyChanged; | ||||
|             } | ||||
|  | ||||
|             RefreshListBox(); | ||||
|             AdaptForDialogMode(); // 调整UI为对话框模式 | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 【新增的方法】 | ||||
|         /// 调整UI,使其更像一个对话框。 | ||||
|         /// </summary> | ||||
|         private void AdaptForDialogMode() | ||||
|         { | ||||
|             // 将“应用配置”按钮改为“确定” | ||||
|             toolBtnSet.Text = "确定"; | ||||
|             toolBtnSet.ToolTipText = "保存更改并关闭窗口"; | ||||
|  | ||||
|             // 新增一个“取消”按钮 | ||||
|             var toolBtnCancel = new ToolStripButton("取消") | ||||
|             { | ||||
|                 DisplayStyle = ToolStripItemDisplayStyle.Text, | ||||
|                 Alignment = ToolStripItemAlignment.Right // 靠右对齐 | ||||
|             }; | ||||
|             toolBtnCancel.Click += (sender, e) => { | ||||
|                 this.DialogResult = DialogResult.Cancel; | ||||
|                 this.Close(); | ||||
|             }; | ||||
|             toolStrip1.Items.Add(toolBtnCancel); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 【新增的事件处理方法】 | ||||
|         /// 当PropertyGrid中的属性值被用户修改后触发此事件。 | ||||
|         /// </summary> | ||||
|  | ||||
|         // 刷新左侧的相机列表 | ||||
|         private void RefreshListBox() | ||||
|         { | ||||
|             listBoxCameras.Items.Clear(); | ||||
|             foreach (var setting in _settingsList) | ||||
|             { | ||||
|                 listBoxCameras.Items.Add(setting.Name); | ||||
|             } | ||||
|         } | ||||
|         /// <summary> | ||||
|         /// 【全新的事件处理方法】 | ||||
|         /// 监听单个CameraSettings对象的属性变更通知。 | ||||
|         /// </summary> | ||||
|         private void Setting_PropertyChanged(object sender, PropertyChangedEventArgs e) | ||||
|         { | ||||
|             // 检查是不是“Name”属性发生了变化 | ||||
|             if (e.PropertyName == nameof(CameraSettings.Name)) | ||||
|             { | ||||
|                 // 'sender' 就是那个属性发生了变化的 CameraSettings 对象 | ||||
|                 var changedSetting = sender as CameraSettings; | ||||
|                 if (changedSetting == null) return; | ||||
|  | ||||
|                 // 在 _settingsList 中找到这个对象的索引 | ||||
|                 int index = _settingsList.IndexOf(changedSetting); | ||||
|  | ||||
|                 // 如果找到了,就更新ListBox中对应项的显示文本 | ||||
|                 if (index >= 0) | ||||
|                 { | ||||
|                     // 使用Invoke确保线程安全,虽然在此场景下通常不是问题,但这是个好习惯 | ||||
|                     this.Invoke(new Action(() => { | ||||
|                         listBoxCameras.Items[index] = changedSetting.Name; | ||||
|                     })); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private void listBoxCameras_SelectedIndexChanged(object sender, EventArgs e) | ||||
|         { | ||||
|             if (listBoxCameras.SelectedIndex >= 0) | ||||
|             { | ||||
|                 propertyGrid1.SelectedObject = _settingsList[listBoxCameras.SelectedIndex]; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 propertyGrid1.SelectedObject = null; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private void toolBtnAdd_Click(object sender, EventArgs e) | ||||
|         { | ||||
|             var newSetting = new CameraSettings { Name = $"Camera-{_settingsList.Count + 1}" }; | ||||
|             _settingsList.Add(newSetting); | ||||
|             newSetting.PropertyChanged += Setting_PropertyChanged; | ||||
|             RefreshListBox(); | ||||
|             listBoxCameras.SelectedIndex = listBoxCameras.Items.Count - 1; | ||||
|         } | ||||
|  | ||||
|         private void toolBtnRemove_Click(object sender, EventArgs e) | ||||
|         { | ||||
|             if (listBoxCameras.SelectedIndex >= 0) | ||||
|             { | ||||
|                 _settingsList.RemoveAt(listBoxCameras.SelectedIndex); | ||||
|                 propertyGrid1.SelectedObject = null; | ||||
|                 RefreshListBox(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private void toolBtnSet_Click(object sender, EventArgs e) | ||||
|         { | ||||
|             this.DialogResult = DialogResult.OK; | ||||
|             this.Close(); | ||||
|         } | ||||
|         // 加载XML配置文件 | ||||
|         private void LoadSettings() | ||||
|         { | ||||
|             if (!File.Exists(_configFilePath)) return; | ||||
|             try | ||||
|             { | ||||
|                 XmlSerializer serializer = new XmlSerializer(typeof(List<CameraSettings>)); | ||||
|                 using (FileStream fs = new FileStream(_configFilePath, FileMode.Open)) | ||||
|                 { | ||||
|                     _settingsList = (List<CameraSettings>)serializer.Deserialize(fs); | ||||
|                 } | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 MessageBox.Show("加载相机配置失败: " + ex.Message); | ||||
|             } | ||||
|             foreach (var setting in _settingsList) | ||||
|             { | ||||
|                 setting.PropertyChanged += Setting_PropertyChanged; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // 保存配置到XML文件 | ||||
|         private void SaveSettings() | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 XmlSerializer serializer = new XmlSerializer(typeof(List<CameraSettings>)); | ||||
|                 using (FileStream fs = new FileStream(_configFilePath, FileMode.Create)) | ||||
|                 { | ||||
|                     serializer.Serialize(fs, _settingsList); | ||||
|                 } | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 MessageBox.Show("保存相机配置失败: " + ex.Message); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										169
									
								
								Check.Main/UI/FrmCamConfig.resx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								Check.Main/UI/FrmCamConfig.resx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,169 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <root> | ||||
|   <!-- | ||||
|     Microsoft ResX Schema | ||||
|  | ||||
|     Version 2.0 | ||||
|  | ||||
|     The primary goals of this format is to allow a simple XML format | ||||
|     that is mostly human readable. The generation and parsing of the | ||||
|     various data types are done through the TypeConverter classes | ||||
|     associated with the data types. | ||||
|  | ||||
|     Example: | ||||
|  | ||||
|     ... ado.net/XML headers & schema ... | ||||
|     <resheader name="resmimetype">text/microsoft-resx</resheader> | ||||
|     <resheader name="version">2.0</resheader> | ||||
|     <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> | ||||
|     <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> | ||||
|     <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> | ||||
|     <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> | ||||
|     <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> | ||||
|         <value>[base64 mime encoded serialized .NET Framework object]</value> | ||||
|     </data> | ||||
|     <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | ||||
|         <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> | ||||
|         <comment>This is a comment</comment> | ||||
|     </data> | ||||
|  | ||||
|     There are any number of "resheader" rows that contain simple | ||||
|     name/value pairs. | ||||
|  | ||||
|     Each data row contains a name, and value. The row also contains a | ||||
|     type or mimetype. Type corresponds to a .NET class that support | ||||
|     text/value conversion through the TypeConverter architecture. | ||||
|     Classes that don't support this are serialized and stored with the | ||||
|     mimetype set. | ||||
|  | ||||
|     The mimetype is used for serialized objects, and tells the | ||||
|     ResXResourceReader how to depersist the object. This is currently not | ||||
|     extensible. For a given mimetype the value must be set accordingly: | ||||
|  | ||||
|     Note - application/x-microsoft.net.object.binary.base64 is the format | ||||
|     that the ResXResourceWriter will generate, however the reader can | ||||
|     read any of the formats listed below. | ||||
|  | ||||
|     mimetype: application/x-microsoft.net.object.binary.base64 | ||||
|     value   : The object must be serialized with | ||||
|             : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter | ||||
|             : and then encoded with base64 encoding. | ||||
|  | ||||
|     mimetype: application/x-microsoft.net.object.soap.base64 | ||||
|     value   : The object must be serialized with | ||||
|             : System.Runtime.Serialization.Formatters.Soap.SoapFormatter | ||||
|             : and then encoded with base64 encoding. | ||||
|  | ||||
|     mimetype: application/x-microsoft.net.object.bytearray.base64 | ||||
|     value   : The object must be serialized into a byte array | ||||
|             : using a System.ComponentModel.TypeConverter | ||||
|             : and then encoded with base64 encoding. | ||||
|     --> | ||||
|   <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> | ||||
|     <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> | ||||
|     <xsd:element name="root" msdata:IsDataSet="true"> | ||||
|       <xsd:complexType> | ||||
|         <xsd:choice maxOccurs="unbounded"> | ||||
|           <xsd:element name="metadata"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" use="required" type="xsd:string" /> | ||||
|               <xsd:attribute name="type" type="xsd:string" /> | ||||
|               <xsd:attribute name="mimetype" type="xsd:string" /> | ||||
|               <xsd:attribute ref="xml:space" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="assembly"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:attribute name="alias" type="xsd:string" /> | ||||
|               <xsd:attribute name="name" type="xsd:string" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="data"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||||
|                 <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> | ||||
|               <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> | ||||
|               <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> | ||||
|               <xsd:attribute ref="xml:space" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="resheader"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" type="xsd:string" use="required" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|         </xsd:choice> | ||||
|       </xsd:complexType> | ||||
|     </xsd:element> | ||||
|   </xsd:schema> | ||||
|   <resheader name="resmimetype"> | ||||
|     <value>text/microsoft-resx</value> | ||||
|   </resheader> | ||||
|   <resheader name="version"> | ||||
|     <value>2.0</value> | ||||
|   </resheader> | ||||
|   <resheader name="reader"> | ||||
|     <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||||
|   </resheader> | ||||
|   <resheader name="writer"> | ||||
|     <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||||
|   </resheader> | ||||
|   <metadata name="toolStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> | ||||
|     <value>17, 17</value> | ||||
|   </metadata> | ||||
|   <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> | ||||
|   <data name="toolBtnAdd.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | ||||
|     <value> | ||||
|         iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 | ||||
|         YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIDSURBVDhPpZLrS5NhGMb3j4SWh0oRQVExD4gonkDpg4hG | ||||
|         YKxG6WBogkMZKgPNCEVJFBGdGETEvgwyO9DJE5syZw3PIlPEE9pgBCLZ5XvdMB8Ew8gXbl54nuf63dd9 | ||||
|         0OGSnwCahxbPRNPAPMw9Xpg6ZmF46kZZ0xSKzJPIrhpDWsVnpBhGkKx3nAX8Pv7z1zg8OoY/cITdn4fw | ||||
|         bf/C0kYAN3Ma/w3gWfZL5kzTKBxjWyK2DftwI9tyMYCZKXbNHaD91bLYJrDXsYbrWfUKwJrPE9M2M1Oc | ||||
|         VzOOpHI7Jr376Hi9ogHqFIANO0/MmmmbmSmm9a8ze+I4MrNWAdjtoJgWcx+PSzg166yZZ8xM8XvXDix9 | ||||
|         c4jIqFYAjoriBV9AhEPv1mH/sonogha0afbZMMZz+yreTGyhpusHwtNNCsA5U1zS4BLxzJIfg299qO32 | ||||
|         Ir7UJtZfftyATqeT+8o2D8JSjQrAJblrncYL7ZJ2+bfaFnC/1S1NjL3diRat7qrO7wLRP3HjWsojBeCo | ||||
|         mDEo5mNjuweFGvjWg2EBhCbpkW78htSHHwRyNdmgAFzPEee2iFkzayy2OLXzT4gr6UdUnlXrullsxxQ+ | ||||
|         kx0g8BTA3aZlButjSTyjODq/WcQcW/B/Je4OQhLvKQDnzN1mp0nnkvAhR8VuMzNrpm1mpjgkoVwB/v8D | ||||
|         TgDQASA1MVpwzwAAAABJRU5ErkJggg== | ||||
| </value> | ||||
|   </data> | ||||
|   <data name="toolBtnRemove.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | ||||
|     <value> | ||||
|         iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 | ||||
|         YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIDSURBVDhPpZLrS5NhGMb3j4SWh0oRQVExD4gonkDpg4hG | ||||
|         YKxG6WBogkMZKgPNCEVJFBGdGETEvgwyO9DJE5syZw3PIlPEE9pgBCLZ5XvdMB8Ew8gXbl54nuf63dd9 | ||||
|         0OGSnwCahxbPRNPAPMw9Xpg6ZmF46kZZ0xSKzJPIrhpDWsVnpBhGkKx3nAX8Pv7z1zg8OoY/cITdn4fw | ||||
|         bf/C0kYAN3Ma/w3gWfZL5kzTKBxjWyK2DftwI9tyMYCZKXbNHaD91bLYJrDXsYbrWfUKwJrPE9M2M1Oc | ||||
|         VzOOpHI7Jr376Hi9ogHqFIANO0/MmmmbmSmm9a8ze+I4MrNWAdjtoJgWcx+PSzg166yZZ8xM8XvXDix9 | ||||
|         c4jIqFYAjoriBV9AhEPv1mH/sonogha0afbZMMZz+yreTGyhpusHwtNNCsA5U1zS4BLxzJIfg299qO32 | ||||
|         Ir7UJtZfftyATqeT+8o2D8JSjQrAJblrncYL7ZJ2+bfaFnC/1S1NjL3diRat7qrO7wLRP3HjWsojBeCo | ||||
|         mDEo5mNjuweFGvjWg2EBhCbpkW78htSHHwRyNdmgAFzPEee2iFkzayy2OLXzT4gr6UdUnlXrullsxxQ+ | ||||
|         kx0g8BTA3aZlButjSTyjODq/WcQcW/B/Je4OQhLvKQDnzN1mp0nnkvAhR8VuMzNrpm1mpjgkoVwB/v8D | ||||
|         TgDQASA1MVpwzwAAAABJRU5ErkJggg== | ||||
| </value> | ||||
|   </data> | ||||
|   <data name="toolBtnSet.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | ||||
|     <value> | ||||
|         iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 | ||||
|         YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIDSURBVDhPpZLrS5NhGMb3j4SWh0oRQVExD4gonkDpg4hG | ||||
|         YKxG6WBogkMZKgPNCEVJFBGdGETEvgwyO9DJE5syZw3PIlPEE9pgBCLZ5XvdMB8Ew8gXbl54nuf63dd9 | ||||
|         0OGSnwCahxbPRNPAPMw9Xpg6ZmF46kZZ0xSKzJPIrhpDWsVnpBhGkKx3nAX8Pv7z1zg8OoY/cITdn4fw | ||||
|         bf/C0kYAN3Ma/w3gWfZL5kzTKBxjWyK2DftwI9tyMYCZKXbNHaD91bLYJrDXsYbrWfUKwJrPE9M2M1Oc | ||||
|         VzOOpHI7Jr376Hi9ogHqFIANO0/MmmmbmSmm9a8ze+I4MrNWAdjtoJgWcx+PSzg166yZZ8xM8XvXDix9 | ||||
|         c4jIqFYAjoriBV9AhEPv1mH/sonogha0afbZMMZz+yreTGyhpusHwtNNCsA5U1zS4BLxzJIfg299qO32 | ||||
|         Ir7UJtZfftyATqeT+8o2D8JSjQrAJblrncYL7ZJ2+bfaFnC/1S1NjL3diRat7qrO7wLRP3HjWsojBeCo | ||||
|         mDEo5mNjuweFGvjWg2EBhCbpkW78htSHHwRyNdmgAFzPEee2iFkzayy2OLXzT4gr6UdUnlXrullsxxQ+ | ||||
|         kx0g8BTA3aZlButjSTyjODq/WcQcW/B/Je4OQhLvKQDnzN1mp0nnkvAhR8VuMzNrpm1mpjgkoVwB/v8D | ||||
|         TgDQASA1MVpwzwAAAABJRU5ErkJggg== | ||||
| </value> | ||||
|   </data> | ||||
| </root> | ||||
							
								
								
									
										171
									
								
								Check.Main/UI/FrmConfig.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								Check.Main/UI/FrmConfig.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,171 @@ | ||||
| namespace Check.Main.UI | ||||
| { | ||||
|     partial class FrmConfig | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Required designer variable. | ||||
|         /// </summary> | ||||
|         private System.ComponentModel.IContainer components = null; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Clean up any resources being used. | ||||
|         /// </summary> | ||||
|         /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> | ||||
|         protected override void Dispose(bool disposing) | ||||
|         { | ||||
|             if (disposing && (components != null)) | ||||
|             { | ||||
|                 components.Dispose(); | ||||
|             } | ||||
|             base.Dispose(disposing); | ||||
|         } | ||||
|  | ||||
|         #region Windows Form Designer generated code | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Required method for Designer support - do not modify | ||||
|         /// the contents of this method with the code editor. | ||||
|         /// </summary> | ||||
|         private void InitializeComponent() | ||||
|         { | ||||
|             System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FrmConfig)); | ||||
|             propertyGrid1 = new PropertyGrid(); | ||||
|             toolStrip1 = new ToolStrip(); | ||||
|             toolBtnApply = new ToolStripButton(); | ||||
|             toolBtnAddProduct = new ToolStripButton(); | ||||
|             tableLayoutPanel1 = new TableLayoutPanel(); | ||||
|             uiLabel1 = new Sunny.UI.UILabel(); | ||||
|             cmbProducts = new Sunny.UI.UIComboBox(); | ||||
|             toolBtnDeleteProduct = new ToolStripButton(); | ||||
|             toolStrip1.SuspendLayout(); | ||||
|             tableLayoutPanel1.SuspendLayout(); | ||||
|             SuspendLayout(); | ||||
|             //  | ||||
|             // propertyGrid1 | ||||
|             //  | ||||
|             tableLayoutPanel1.SetColumnSpan(propertyGrid1, 2); | ||||
|             propertyGrid1.Dock = DockStyle.Fill; | ||||
|             propertyGrid1.Location = new Point(3, 70); | ||||
|             propertyGrid1.Name = "propertyGrid1"; | ||||
|             propertyGrid1.Size = new Size(417, 345); | ||||
|             propertyGrid1.TabIndex = 0; | ||||
|             //  | ||||
|             // toolStrip1 | ||||
|             //  | ||||
|             tableLayoutPanel1.SetColumnSpan(toolStrip1, 2); | ||||
|             toolStrip1.Dock = DockStyle.Fill; | ||||
|             toolStrip1.Items.AddRange(new ToolStripItem[] { toolBtnApply, toolBtnAddProduct, toolBtnDeleteProduct }); | ||||
|             toolStrip1.Location = new Point(0, 0); | ||||
|             toolStrip1.Name = "toolStrip1"; | ||||
|             toolStrip1.Size = new Size(423, 28); | ||||
|             toolStrip1.TabIndex = 1; | ||||
|             toolStrip1.Text = "toolStrip1"; | ||||
|             //  | ||||
|             // toolBtnApply | ||||
|             //  | ||||
|             toolBtnApply.DisplayStyle = ToolStripItemDisplayStyle.Text; | ||||
|             toolBtnApply.Image = (Image)resources.GetObject("toolBtnApply.Image"); | ||||
|             toolBtnApply.ImageTransparentColor = Color.Magenta; | ||||
|             toolBtnApply.Name = "toolBtnApply"; | ||||
|             toolBtnApply.Size = new Size(60, 25); | ||||
|             toolBtnApply.Text = "应用配置"; | ||||
|             toolBtnApply.Click += toolBtnApply_Click; | ||||
|             //  | ||||
|             // toolBtnAddProduct | ||||
|             //  | ||||
|             toolBtnAddProduct.DisplayStyle = ToolStripItemDisplayStyle.Text; | ||||
|             toolBtnAddProduct.Image = (Image)resources.GetObject("toolBtnAddProduct.Image"); | ||||
|             toolBtnAddProduct.ImageTransparentColor = Color.Magenta; | ||||
|             toolBtnAddProduct.Name = "toolBtnAddProduct"; | ||||
|             toolBtnAddProduct.Size = new Size(60, 25); | ||||
|             toolBtnAddProduct.Text = "添加产品"; | ||||
|             toolBtnAddProduct.Click += toolBtnAddProduct_Click; | ||||
|             //  | ||||
|             // tableLayoutPanel1 | ||||
|             //  | ||||
|             tableLayoutPanel1.ColumnCount = 2; | ||||
|             tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 23.4042549F)); | ||||
|             tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 76.59574F)); | ||||
|             tableLayoutPanel1.Controls.Add(propertyGrid1, 0, 2); | ||||
|             tableLayoutPanel1.Controls.Add(toolStrip1, 0, 0); | ||||
|             tableLayoutPanel1.Controls.Add(uiLabel1, 0, 1); | ||||
|             tableLayoutPanel1.Controls.Add(cmbProducts, 1, 1); | ||||
|             tableLayoutPanel1.Dock = DockStyle.Fill; | ||||
|             tableLayoutPanel1.Location = new Point(0, 0); | ||||
|             tableLayoutPanel1.Name = "tableLayoutPanel1"; | ||||
|             tableLayoutPanel1.RowCount = 3; | ||||
|             tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 6.69856453F)); | ||||
|             tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 9.330144F)); | ||||
|             tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 83.97129F)); | ||||
|             tableLayoutPanel1.Size = new Size(423, 418); | ||||
|             tableLayoutPanel1.TabIndex = 2; | ||||
|             //  | ||||
|             // uiLabel1 | ||||
|             //  | ||||
|             uiLabel1.Dock = DockStyle.Fill; | ||||
|             uiLabel1.Font = new Font("宋体", 12F, FontStyle.Regular, GraphicsUnit.Point, 134); | ||||
|             uiLabel1.ForeColor = Color.FromArgb(48, 48, 48); | ||||
|             uiLabel1.Location = new Point(3, 28); | ||||
|             uiLabel1.Name = "uiLabel1"; | ||||
|             uiLabel1.Size = new Size(93, 39); | ||||
|             uiLabel1.TabIndex = 2; | ||||
|             uiLabel1.Text = "当前产品"; | ||||
|             uiLabel1.TextAlign = ContentAlignment.MiddleCenter; | ||||
|             //  | ||||
|             // cmbProducts | ||||
|             //  | ||||
|             cmbProducts.DataSource = null; | ||||
|             cmbProducts.Dock = DockStyle.Left; | ||||
|             cmbProducts.FillColor = Color.White; | ||||
|             cmbProducts.Font = new Font("宋体", 12F, FontStyle.Regular, GraphicsUnit.Point, 134); | ||||
|             cmbProducts.ItemHoverColor = Color.FromArgb(155, 200, 255); | ||||
|             cmbProducts.ItemSelectForeColor = Color.FromArgb(235, 243, 255); | ||||
|             cmbProducts.Location = new Point(103, 33); | ||||
|             cmbProducts.Margin = new Padding(4, 5, 4, 5); | ||||
|             cmbProducts.MinimumSize = new Size(63, 0); | ||||
|             cmbProducts.Name = "cmbProducts"; | ||||
|             cmbProducts.Padding = new Padding(0, 0, 30, 2); | ||||
|             cmbProducts.Size = new Size(225, 29); | ||||
|             cmbProducts.SymbolSize = 24; | ||||
|             cmbProducts.TabIndex = 3; | ||||
|             cmbProducts.TextAlignment = ContentAlignment.MiddleLeft; | ||||
|             cmbProducts.Watermark = ""; | ||||
|             cmbProducts.SelectedIndexChanged += cmbProducts_SelectedIndexChanged; | ||||
|             //  | ||||
|             // toolBtnDeleteProduct | ||||
|             //  | ||||
|             toolBtnDeleteProduct.DisplayStyle = ToolStripItemDisplayStyle.Text; | ||||
|             toolBtnDeleteProduct.Image = (Image)resources.GetObject("toolBtnDeleteProduct.Image"); | ||||
|             toolBtnDeleteProduct.ImageTransparentColor = Color.Magenta; | ||||
|             toolBtnDeleteProduct.Name = "toolBtnDeleteProduct"; | ||||
|             toolBtnDeleteProduct.Size = new Size(60, 25); | ||||
|             toolBtnDeleteProduct.Text = "删除产品"; | ||||
|             toolBtnDeleteProduct.Click += toolBtnDeleteProduct_Click; | ||||
|             //  | ||||
|             // FrmConfig | ||||
|             //  | ||||
|             AutoScaleDimensions = new SizeF(7F, 17F); | ||||
|             AutoScaleMode = AutoScaleMode.Font; | ||||
|             ClientSize = new Size(423, 418); | ||||
|             Controls.Add(tableLayoutPanel1); | ||||
|             Name = "FrmConfig"; | ||||
|             Text = "属性配置"; | ||||
|             toolStrip1.ResumeLayout(false); | ||||
|             toolStrip1.PerformLayout(); | ||||
|             tableLayoutPanel1.ResumeLayout(false); | ||||
|             tableLayoutPanel1.PerformLayout(); | ||||
|             ResumeLayout(false); | ||||
|         } | ||||
|  | ||||
|         #endregion | ||||
|  | ||||
|         private PropertyGrid propertyGrid1; | ||||
|         private ToolStrip toolStrip1; | ||||
|         private ToolStripButton toolBtnApply; | ||||
|         private TableLayoutPanel tableLayoutPanel1; | ||||
|         private ToolStripButton toolBtnAddProduct; | ||||
|         private Sunny.UI.UILabel uiLabel1; | ||||
|         private Sunny.UI.UIComboBox cmbProducts; | ||||
|         private ToolStripButton toolBtnDeleteProduct; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										163
									
								
								Check.Main/UI/FrmConfig.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								Check.Main/UI/FrmConfig.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,163 @@ | ||||
| using Check.Main.Camera; | ||||
| using Check.Main.Common; | ||||
| using Check.Main.Dispatch; | ||||
| using System; | ||||
| 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.Xml.Serialization; | ||||
| using WeifenLuo.WinFormsUI.Docking; | ||||
|  | ||||
| namespace Check.Main.UI | ||||
| { | ||||
|     public partial class FrmConfig : DockContent | ||||
|     { | ||||
|         //private ProcessConfig _mainSettings=new ProcessConfig(); | ||||
|         //private readonly string _configFilePath = Path.Combine(Application.StartupPath, "main_config.xml"); | ||||
|         public FrmConfig() | ||||
|         { | ||||
|             InitializeComponent(); | ||||
|             ProductManager.OnProductChanged += UpdateUIForNewProduct; | ||||
|             // 初始化UI | ||||
|             InitializeProductComboBox(); | ||||
|             //LoadSettings(); // 窗体加载时,读取主配置文件 | ||||
|             propertyGrid1.SelectedObject = ProductManager.CurrentConfig; //_mainSettings; // 将配置对象绑定到属性网格 | ||||
|             propertyGrid1.PropertyValueChanged += (s, e) => { ProductManager.SaveCurrentProductConfig(); }; // 任何属性改变后自动保存 | ||||
|         } | ||||
|         // 【新增】初始化产品下拉列表 | ||||
|         private void InitializeProductComboBox() | ||||
|         { | ||||
|             cmbProducts.DataSource = null; // 先清空数据源 | ||||
|             cmbProducts.DataSource = ProductManager.ProductList; | ||||
|             cmbProducts.SelectedItem = ProductManager.CurrentProductName; | ||||
|         } | ||||
|         // 【新增】当产品管理器中的产品切换后,此方法被调用以更新整个UI | ||||
|         private void UpdateUIForNewProduct() | ||||
|         { | ||||
|             // 使用Invoke确保线程安全 | ||||
|             if (this.InvokeRequired) | ||||
|             { | ||||
|                 this.Invoke(new Action(UpdateUIForNewProduct)); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             ThreadSafeLogger.Log($"UI正在更新以显示产品 '{ProductManager.CurrentProductName}' 的配置。"); | ||||
|             // 更新下拉列表的显示(如果产品列表也变了) | ||||
|             InitializeProductComboBox(); | ||||
|  | ||||
|             // 【关键】将PropertyGrid重新绑定到新产品的配置对象上 | ||||
|             propertyGrid1.SelectedObject = ProductManager.CurrentConfig; | ||||
|             propertyGrid1.Refresh(); // 强制刷新UI | ||||
|         } | ||||
|         private void toolBtnApply_Click(object sender, EventArgs e) | ||||
|         { | ||||
|             ThreadSafeLogger.Log("用户点击“应用”按钮..."); | ||||
|  | ||||
|             // 1. 确保在应用前,任何可能未触发 PropertyValueChanged 的更改都被保存。 | ||||
|             ConfigurationManager.SaveChanges(); | ||||
|  | ||||
|             // 2. 获取主窗体引用 | ||||
|             var mainForm = this.DockPanel.FindForm() as FrmMain; | ||||
|             mainForm?.ClearStatusStrip(); // 清理UI状态 | ||||
|  | ||||
|             // 3. 从 ConfigurationManager 获取最新的相机配置列表 | ||||
|             var cameraSettings = ConfigurationManager.GetCurrentConfig();//.CameraSettings; | ||||
|  | ||||
|             if (cameraSettings != null) | ||||
|             { | ||||
|                 // 使用全局配置来初始化或重新初始化相机。 | ||||
|                 // CameraManager.Initialize 内部会首先调用 Shutdown,所以这是个完整的重启流程。 | ||||
|                 CameraManager.Initialize(cameraSettings, mainForm); | ||||
|             } | ||||
|  | ||||
|             ThreadSafeLogger.Log("中央配置已成功应用到相机系统。"); | ||||
|             //MessageBox.Show("主配置已应用!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); | ||||
|         } | ||||
|  | ||||
|         private void cmbProducts_SelectedIndexChanged(object sender, EventArgs e) | ||||
|         { | ||||
|             if (cmbProducts.SelectedItem is string selectedProduct && selectedProduct != ProductManager.CurrentProductName) | ||||
|             { | ||||
|                 ProductManager.SwitchToProduct(selectedProduct); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private void toolBtnAddProduct_Click(object sender, EventArgs e) | ||||
|         { | ||||
|             // 使用一个简单的输入框来获取新产品名称 | ||||
|             string newName = ShowInputDialog("请输入新产品名称:"); | ||||
|             if (!string.IsNullOrWhiteSpace(newName)) | ||||
|             { | ||||
|                 if (ProductManager.AddNewProduct(newName)) | ||||
|                 { | ||||
|                     ThreadSafeLogger.Log($"成功添加新产品: {newName}"); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     MessageBox.Show("产品名称无效或已存在!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|         } | ||||
|         // 一个简单的输入对话框辅助方法 | ||||
|         public static string ShowInputDialog(string text) | ||||
|         { | ||||
|             Form prompt = new Form() | ||||
|             { | ||||
|                 Width = 300, | ||||
|                 Height = 150, | ||||
|                 FormBorderStyle = FormBorderStyle.FixedDialog, | ||||
|                 Text = text, | ||||
|                 StartPosition = FormStartPosition.CenterScreen | ||||
|             }; | ||||
|             Label textLabel = new Label() { Left = 50, Top = 20, Text = "产品名称:" }; | ||||
|             TextBox textBox = new TextBox() { Left = 50, Top = 50, Width = 200 }; | ||||
|             Button confirmation = new Button() { Text = "确定", Left = 150, Width = 100, Top = 80, DialogResult = DialogResult.OK }; | ||||
|             confirmation.Click += (sender, e) => { prompt.Close(); }; | ||||
|             prompt.Controls.Add(textBox); | ||||
|             prompt.Controls.Add(confirmation); | ||||
|             prompt.Controls.Add(textLabel); | ||||
|             prompt.AcceptButton = confirmation; | ||||
|  | ||||
|             return prompt.ShowDialog() == DialogResult.OK ? textBox.Text : ""; | ||||
|         } | ||||
|  | ||||
|         private void toolBtnDeleteProduct_Click(object sender, EventArgs e) | ||||
|         { | ||||
|             // 1. 获取当前选中的产品 | ||||
|             string productToDelete = ProductManager.CurrentProductName; | ||||
|  | ||||
|             if (string.IsNullOrWhiteSpace(productToDelete) || productToDelete == "DefaultProduct") | ||||
|             { | ||||
|                 MessageBox.Show("不能删除默认产品或无效产品!", "操作无效", MessageBoxButtons.OK, MessageBoxIcon.Warning); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             // 2. 弹出安全确认对话框 | ||||
|             var confirmResult = MessageBox.Show($"您确定要永久删除产品 '{productToDelete}' 吗?\n\n此操作不可恢复,将删除其所有相关配置!", | ||||
|                                                  "确认删除", | ||||
|                                                  MessageBoxButtons.YesNo, | ||||
|                                                  MessageBoxIcon.Warning); | ||||
|  | ||||
|             if (confirmResult == DialogResult.Yes) | ||||
|             { | ||||
|                 // 3. 调用核心删除逻辑 | ||||
|                 if (ProductManager.DeleteProduct(productToDelete)) | ||||
|                 { | ||||
|                     ThreadSafeLogger.Log($"用户成功删除了产品 '{productToDelete}'。"); | ||||
|                     // 无需在这里手动更新UI,因为DeleteProduct方法内部会触发 OnProductChanged 事件, | ||||
|                     // 而我们的 UpdateUIForNewProduct 方法会自动响应这个事件并刷新整个界面。 | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     MessageBox.Show($"删除产品 '{productToDelete}' 失败,请查看日志获取详细信息。", "删除失败", MessageBoxButtons.OK, MessageBoxIcon.Error); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										151
									
								
								Check.Main/UI/FrmConfig.resx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								Check.Main/UI/FrmConfig.resx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <root> | ||||
|   <!-- | ||||
|     Microsoft ResX Schema | ||||
|  | ||||
|     Version 2.0 | ||||
|  | ||||
|     The primary goals of this format is to allow a simple XML format | ||||
|     that is mostly human readable. The generation and parsing of the | ||||
|     various data types are done through the TypeConverter classes | ||||
|     associated with the data types. | ||||
|  | ||||
|     Example: | ||||
|  | ||||
|     ... ado.net/XML headers & schema ... | ||||
|     <resheader name="resmimetype">text/microsoft-resx</resheader> | ||||
|     <resheader name="version">2.0</resheader> | ||||
|     <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> | ||||
|     <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> | ||||
|     <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> | ||||
|     <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> | ||||
|     <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> | ||||
|         <value>[base64 mime encoded serialized .NET Framework object]</value> | ||||
|     </data> | ||||
|     <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | ||||
|         <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> | ||||
|         <comment>This is a comment</comment> | ||||
|     </data> | ||||
|  | ||||
|     There are any number of "resheader" rows that contain simple | ||||
|     name/value pairs. | ||||
|  | ||||
|     Each data row contains a name, and value. The row also contains a | ||||
|     type or mimetype. Type corresponds to a .NET class that support | ||||
|     text/value conversion through the TypeConverter architecture. | ||||
|     Classes that don't support this are serialized and stored with the | ||||
|     mimetype set. | ||||
|  | ||||
|     The mimetype is used for serialized objects, and tells the | ||||
|     ResXResourceReader how to depersist the object. This is currently not | ||||
|     extensible. For a given mimetype the value must be set accordingly: | ||||
|  | ||||
|     Note - application/x-microsoft.net.object.binary.base64 is the format | ||||
|     that the ResXResourceWriter will generate, however the reader can | ||||
|     read any of the formats listed below. | ||||
|  | ||||
|     mimetype: application/x-microsoft.net.object.binary.base64 | ||||
|     value   : The object must be serialized with | ||||
|             : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter | ||||
|             : and then encoded with base64 encoding. | ||||
|  | ||||
|     mimetype: application/x-microsoft.net.object.soap.base64 | ||||
|     value   : The object must be serialized with | ||||
|             : System.Runtime.Serialization.Formatters.Soap.SoapFormatter | ||||
|             : and then encoded with base64 encoding. | ||||
|  | ||||
|     mimetype: application/x-microsoft.net.object.bytearray.base64 | ||||
|     value   : The object must be serialized into a byte array | ||||
|             : using a System.ComponentModel.TypeConverter | ||||
|             : and then encoded with base64 encoding. | ||||
|     --> | ||||
|   <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> | ||||
|     <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> | ||||
|     <xsd:element name="root" msdata:IsDataSet="true"> | ||||
|       <xsd:complexType> | ||||
|         <xsd:choice maxOccurs="unbounded"> | ||||
|           <xsd:element name="metadata"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" use="required" type="xsd:string" /> | ||||
|               <xsd:attribute name="type" type="xsd:string" /> | ||||
|               <xsd:attribute name="mimetype" type="xsd:string" /> | ||||
|               <xsd:attribute ref="xml:space" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="assembly"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:attribute name="alias" type="xsd:string" /> | ||||
|               <xsd:attribute name="name" type="xsd:string" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="data"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||||
|                 <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> | ||||
|               <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> | ||||
|               <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> | ||||
|               <xsd:attribute ref="xml:space" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="resheader"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" type="xsd:string" use="required" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|         </xsd:choice> | ||||
|       </xsd:complexType> | ||||
|     </xsd:element> | ||||
|   </xsd:schema> | ||||
|   <resheader name="resmimetype"> | ||||
|     <value>text/microsoft-resx</value> | ||||
|   </resheader> | ||||
|   <resheader name="version"> | ||||
|     <value>2.0</value> | ||||
|   </resheader> | ||||
|   <resheader name="reader"> | ||||
|     <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||||
|   </resheader> | ||||
|   <resheader name="writer"> | ||||
|     <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||||
|   </resheader> | ||||
|   <metadata name="toolStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> | ||||
|     <value>17, 17</value> | ||||
|   </metadata> | ||||
|   <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> | ||||
|   <data name="toolBtnApply.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | ||||
|     <value> | ||||
|         iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 | ||||
|         YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACRSURBVDhPY/j27dt/SjDYACcnJ7IwigEf3n8kCZNswPNb | ||||
|         J/+f6DYF0yA+yQac6Db5f6hWCmwIiE+mC0wIu2DS2Vf/F1x6DefjwlgNyNr34r/0wkdgTMgQDAOQNRNj | ||||
|         CIoBOg0rMTTDMLIhIHbriZeYBmDTiIxBGkEYxge5liQDsGGQqykyAISpZwAlmIEywMAAAAc1/Jwvt6sN | ||||
|         AAAAAElFTkSuQmCC | ||||
| </value> | ||||
|   </data> | ||||
|   <data name="toolBtnAddProduct.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | ||||
|     <value> | ||||
|         iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 | ||||
|         YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACRSURBVDhPY/j27dt/SjDYACcnJ7IwigEf3n8kCZNswPNb | ||||
|         J/+f6DYF0yA+yQac6Db5f6hWCmwIiE+mC0wIu2DS2Vf/F1x6DefjwlgNyNr34r/0wkdgTMgQDAOQNRNj | ||||
|         CIoBOg0rMTTDMLIhIHbriZeYBmDTiIxBGkEYxge5liQDsGGQqykyAISpZwAlmIEywMAAAAc1/Jwvt6sN | ||||
|         AAAAAElFTkSuQmCC | ||||
| </value> | ||||
|   </data> | ||||
|   <data name="toolBtnDeleteProduct.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | ||||
|     <value> | ||||
|         iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 | ||||
|         YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACRSURBVDhPY/j27dt/SjDYACcnJ7IwigEf3n8kCZNswPNb | ||||
|         J/+f6DYF0yA+yQac6Db5f6hWCmwIiE+mC0wIu2DS2Vf/F1x6DefjwlgNyNr34r/0wkdgTMgQDAOQNRNj | ||||
|         CIoBOg0rMTTDMLIhIHbriZeYBmDTiIxBGkEYxge5liQDsGGQqykyAISpZwAlmIEywMAAAAc1/Jwvt6sN | ||||
|         AAAAAElFTkSuQmCC | ||||
| </value> | ||||
|   </data> | ||||
| </root> | ||||
							
								
								
									
										66
									
								
								Check.Main/UI/FrmLog.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								Check.Main/UI/FrmLog.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| namespace Check.Main.UI | ||||
| { | ||||
|     partial class FrmLog | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Required designer variable. | ||||
|         /// </summary> | ||||
|         private System.ComponentModel.IContainer components = null; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Clean up any resources being used. | ||||
|         /// </summary> | ||||
|         /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> | ||||
|         protected override void Dispose(bool disposing) | ||||
|         { | ||||
|             if (disposing && (components != null)) | ||||
|             { | ||||
|                 components.Dispose(); | ||||
|             } | ||||
|             base.Dispose(disposing); | ||||
|         } | ||||
|  | ||||
|         #region Windows Form Designer generated code | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Required method for Designer support - do not modify | ||||
|         /// the contents of this method with the code editor. | ||||
|         /// </summary> | ||||
|         private void InitializeComponent() | ||||
|         { | ||||
|             this.txtLog = new Sunny.UI.UIRichTextBox(); | ||||
|             this.SuspendLayout(); | ||||
|             //  | ||||
|             // txtLog | ||||
|             //  | ||||
|             this.txtLog.Dock = System.Windows.Forms.DockStyle.Fill; | ||||
|             this.txtLog.FillColor = System.Drawing.Color.White; | ||||
|             this.txtLog.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); | ||||
|             this.txtLog.Location = new System.Drawing.Point(0, 0); | ||||
|             this.txtLog.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); | ||||
|             this.txtLog.MinimumSize = new System.Drawing.Size(1, 1); | ||||
|             this.txtLog.Name = "txtLog"; | ||||
|             this.txtLog.Padding = new System.Windows.Forms.Padding(2); | ||||
|             this.txtLog.ReadOnly = true; | ||||
|             this.txtLog.ShowText = false; | ||||
|             this.txtLog.Size = new System.Drawing.Size(399, 299); | ||||
|             this.txtLog.TabIndex = 0; | ||||
|             this.txtLog.TextAlignment = System.Drawing.ContentAlignment.MiddleLeft; | ||||
|             //  | ||||
|             // FrmLog | ||||
|             //  | ||||
|             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); | ||||
|             this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; | ||||
|             this.ClientSize = new System.Drawing.Size(399, 299); | ||||
|             this.Controls.Add(this.txtLog); | ||||
|             this.Name = "FrmLog"; | ||||
|             this.Text = "FrmLog"; | ||||
|             this.ResumeLayout(false); | ||||
|  | ||||
|         } | ||||
|  | ||||
|         #endregion | ||||
|  | ||||
|         private Sunny.UI.UIRichTextBox txtLog; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										35
									
								
								Check.Main/UI/FrmLog.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								Check.Main/UI/FrmLog.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| using System; | ||||
| 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 WeifenLuo.WinFormsUI.Docking; | ||||
|  | ||||
| namespace Check.Main.UI | ||||
| { | ||||
|     public partial class FrmLog : DockContent | ||||
|     { | ||||
|         public FrmLog() | ||||
|         { | ||||
|             InitializeComponent(); | ||||
|         } | ||||
|         public void AddLog(string message) | ||||
|         { | ||||
|             if (txtLog.InvokeRequired) | ||||
|             { | ||||
|                 txtLog.BeginInvoke(new Action(() => AddLog(message))); | ||||
|                 return; | ||||
|             } | ||||
|             if (txtLog.Lines.Length > 500) | ||||
|             { | ||||
|                 txtLog.Clear(); | ||||
|             } | ||||
|             txtLog.AppendText(message + Environment.NewLine); | ||||
|             txtLog.ScrollToCaret(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										120
									
								
								Check.Main/UI/FrmLog.resx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								Check.Main/UI/FrmLog.resx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,120 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <root> | ||||
|   <!--  | ||||
|     Microsoft ResX Schema  | ||||
|      | ||||
|     Version 2.0 | ||||
|      | ||||
|     The primary goals of this format is to allow a simple XML format  | ||||
|     that is mostly human readable. The generation and parsing of the  | ||||
|     various data types are done through the TypeConverter classes  | ||||
|     associated with the data types. | ||||
|      | ||||
|     Example: | ||||
|      | ||||
|     ... ado.net/XML headers & schema ... | ||||
|     <resheader name="resmimetype">text/microsoft-resx</resheader> | ||||
|     <resheader name="version">2.0</resheader> | ||||
|     <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> | ||||
|     <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> | ||||
|     <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> | ||||
|     <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> | ||||
|     <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> | ||||
|         <value>[base64 mime encoded serialized .NET Framework object]</value> | ||||
|     </data> | ||||
|     <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | ||||
|         <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> | ||||
|         <comment>This is a comment</comment> | ||||
|     </data> | ||||
|                  | ||||
|     There are any number of "resheader" rows that contain simple  | ||||
|     name/value pairs. | ||||
|      | ||||
|     Each data row contains a name, and value. The row also contains a  | ||||
|     type or mimetype. Type corresponds to a .NET class that support  | ||||
|     text/value conversion through the TypeConverter architecture.  | ||||
|     Classes that don't support this are serialized and stored with the  | ||||
|     mimetype set. | ||||
|      | ||||
|     The mimetype is used for serialized objects, and tells the  | ||||
|     ResXResourceReader how to depersist the object. This is currently not  | ||||
|     extensible. For a given mimetype the value must be set accordingly: | ||||
|      | ||||
|     Note - application/x-microsoft.net.object.binary.base64 is the format  | ||||
|     that the ResXResourceWriter will generate, however the reader can  | ||||
|     read any of the formats listed below. | ||||
|      | ||||
|     mimetype: application/x-microsoft.net.object.binary.base64 | ||||
|     value   : The object must be serialized with  | ||||
|             : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter | ||||
|             : and then encoded with base64 encoding. | ||||
|      | ||||
|     mimetype: application/x-microsoft.net.object.soap.base64 | ||||
|     value   : The object must be serialized with  | ||||
|             : System.Runtime.Serialization.Formatters.Soap.SoapFormatter | ||||
|             : and then encoded with base64 encoding. | ||||
|  | ||||
|     mimetype: application/x-microsoft.net.object.bytearray.base64 | ||||
|     value   : The object must be serialized into a byte array  | ||||
|             : using a System.ComponentModel.TypeConverter | ||||
|             : and then encoded with base64 encoding. | ||||
|     --> | ||||
|   <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> | ||||
|     <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> | ||||
|     <xsd:element name="root" msdata:IsDataSet="true"> | ||||
|       <xsd:complexType> | ||||
|         <xsd:choice maxOccurs="unbounded"> | ||||
|           <xsd:element name="metadata"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" use="required" type="xsd:string" /> | ||||
|               <xsd:attribute name="type" type="xsd:string" /> | ||||
|               <xsd:attribute name="mimetype" type="xsd:string" /> | ||||
|               <xsd:attribute ref="xml:space" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="assembly"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:attribute name="alias" type="xsd:string" /> | ||||
|               <xsd:attribute name="name" type="xsd:string" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="data"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||||
|                 <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> | ||||
|               <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> | ||||
|               <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> | ||||
|               <xsd:attribute ref="xml:space" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="resheader"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" type="xsd:string" use="required" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|         </xsd:choice> | ||||
|       </xsd:complexType> | ||||
|     </xsd:element> | ||||
|   </xsd:schema> | ||||
|   <resheader name="resmimetype"> | ||||
|     <value>text/microsoft-resx</value> | ||||
|   </resheader> | ||||
|   <resheader name="version"> | ||||
|     <value>2.0</value> | ||||
|   </resheader> | ||||
|   <resheader name="reader"> | ||||
|     <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||||
|   </resheader> | ||||
|   <resheader name="writer"> | ||||
|     <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||||
|   </resheader> | ||||
| </root> | ||||
							
								
								
									
										145
									
								
								Check.Main/UI/ModelListEditor.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								Check.Main/UI/ModelListEditor.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,145 @@ | ||||
| namespace Check.Main.UI | ||||
| { | ||||
|     partial class ModelListEditor | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Required designer variable. | ||||
|         /// </summary> | ||||
|         private System.ComponentModel.IContainer components = null; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Clean up any resources being used. | ||||
|         /// </summary> | ||||
|         /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> | ||||
|         protected override void Dispose(bool disposing) | ||||
|         { | ||||
|             if (disposing && (components != null)) | ||||
|             { | ||||
|                 components.Dispose(); | ||||
|             } | ||||
|             base.Dispose(disposing); | ||||
|         } | ||||
|  | ||||
|         #region Windows Form Designer generated code | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Required method for Designer support - do not modify | ||||
|         /// the contents of this method with the code editor. | ||||
|         /// </summary> | ||||
|         private void InitializeComponent() | ||||
|         { | ||||
|             System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ModelListEditor)); | ||||
|             propertyGrid1 = new PropertyGrid(); | ||||
|             listBoxModels = new ListBox(); | ||||
|             toolStrip1 = new ToolStrip(); | ||||
|             toolBtnAdd = new ToolStripButton(); | ||||
|             toolBtnRemove = new ToolStripButton(); | ||||
|             toolBtnSet = new ToolStripButton(); | ||||
|             tableLayoutPanel1 = new TableLayoutPanel(); | ||||
|             toolStrip1.SuspendLayout(); | ||||
|             tableLayoutPanel1.SuspendLayout(); | ||||
|             SuspendLayout(); | ||||
|             //  | ||||
|             // propertyGrid1 | ||||
|             //  | ||||
|             propertyGrid1.Dock = DockStyle.Fill; | ||||
|             propertyGrid1.Location = new Point(245, 4); | ||||
|             propertyGrid1.Margin = new Padding(4); | ||||
|             propertyGrid1.Name = "propertyGrid1"; | ||||
|             propertyGrid1.Size = new Size(316, 400); | ||||
|             propertyGrid1.TabIndex = 1; | ||||
|             //  | ||||
|             // listBoxModels | ||||
|             //  | ||||
|             listBoxModels.Dock = DockStyle.Fill; | ||||
|             listBoxModels.FormattingEnabled = true; | ||||
|             listBoxModels.ItemHeight = 17; | ||||
|             listBoxModels.Location = new Point(4, 4); | ||||
|             listBoxModels.Margin = new Padding(4); | ||||
|             listBoxModels.Name = "listBoxModels"; | ||||
|             listBoxModels.Size = new Size(233, 400); | ||||
|             listBoxModels.TabIndex = 2; | ||||
|             listBoxModels.SelectedIndexChanged += listBoxModels_SelectedIndexChanged; | ||||
|             //  | ||||
|             // toolStrip1 | ||||
|             //  | ||||
|             toolStrip1.Items.AddRange(new ToolStripItem[] { toolBtnAdd, toolBtnRemove, toolBtnSet }); | ||||
|             toolStrip1.Location = new Point(0, 0); | ||||
|             toolStrip1.Name = "toolStrip1"; | ||||
|             toolStrip1.Size = new Size(565, 25); | ||||
|             toolStrip1.TabIndex = 3; | ||||
|             toolStrip1.Text = "toolStrip1"; | ||||
|             //  | ||||
|             // toolBtnAdd | ||||
|             //  | ||||
|             toolBtnAdd.DisplayStyle = ToolStripItemDisplayStyle.Text; | ||||
|             toolBtnAdd.Image = (Image)resources.GetObject("toolBtnAdd.Image"); | ||||
|             toolBtnAdd.ImageTransparentColor = Color.Magenta; | ||||
|             toolBtnAdd.Name = "toolBtnAdd"; | ||||
|             toolBtnAdd.Size = new Size(36, 22); | ||||
|             toolBtnAdd.Text = "添加"; | ||||
|             toolBtnAdd.Click += toolBtnAdd_Click; | ||||
|             //  | ||||
|             // toolBtnRemove | ||||
|             //  | ||||
|             toolBtnRemove.DisplayStyle = ToolStripItemDisplayStyle.Text; | ||||
|             toolBtnRemove.Image = (Image)resources.GetObject("toolBtnRemove.Image"); | ||||
|             toolBtnRemove.ImageTransparentColor = Color.Magenta; | ||||
|             toolBtnRemove.Name = "toolBtnRemove"; | ||||
|             toolBtnRemove.Size = new Size(36, 22); | ||||
|             toolBtnRemove.Text = "移除"; | ||||
|             toolBtnRemove.Click += toolBtnRemove_Click; | ||||
|             //  | ||||
|             // toolBtnSet | ||||
|             //  | ||||
|             toolBtnSet.DisplayStyle = ToolStripItemDisplayStyle.Text; | ||||
|             toolBtnSet.Image = (Image)resources.GetObject("toolBtnSet.Image"); | ||||
|             toolBtnSet.ImageTransparentColor = Color.Magenta; | ||||
|             toolBtnSet.Name = "toolBtnSet"; | ||||
|             toolBtnSet.Size = new Size(60, 22); | ||||
|             toolBtnSet.Text = "应用配置"; | ||||
|             toolBtnSet.Click += toolBtnSet_Click; | ||||
|             //  | ||||
|             // tableLayoutPanel1 | ||||
|             //  | ||||
|             tableLayoutPanel1.ColumnCount = 2; | ||||
|             tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 42.65487F)); | ||||
|             tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 57.34513F)); | ||||
|             tableLayoutPanel1.Controls.Add(listBoxModels, 0, 0); | ||||
|             tableLayoutPanel1.Controls.Add(propertyGrid1, 1, 0); | ||||
|             tableLayoutPanel1.Dock = DockStyle.Fill; | ||||
|             tableLayoutPanel1.Location = new Point(0, 25); | ||||
|             tableLayoutPanel1.Name = "tableLayoutPanel1"; | ||||
|             tableLayoutPanel1.RowCount = 1; | ||||
|             tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 50F)); | ||||
|             tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 50F)); | ||||
|             tableLayoutPanel1.Size = new Size(565, 408); | ||||
|             tableLayoutPanel1.TabIndex = 4; | ||||
|             //  | ||||
|             // ModelListEditor | ||||
|             //  | ||||
|             AutoScaleDimensions = new SizeF(7F, 17F); | ||||
|             AutoScaleMode = AutoScaleMode.Font; | ||||
|             ClientSize = new Size(565, 433); | ||||
|             Controls.Add(tableLayoutPanel1); | ||||
|             Controls.Add(toolStrip1); | ||||
|             Name = "ModelListEditor"; | ||||
|             Text = "模型配置"; | ||||
|             toolStrip1.ResumeLayout(false); | ||||
|             toolStrip1.PerformLayout(); | ||||
|             tableLayoutPanel1.ResumeLayout(false); | ||||
|             ResumeLayout(false); | ||||
|             PerformLayout(); | ||||
|         } | ||||
|  | ||||
|         #endregion | ||||
|  | ||||
|         private PropertyGrid propertyGrid1; | ||||
|         private ListBox listBoxModels; | ||||
|         private ToolStrip toolStrip1; | ||||
|         private ToolStripButton toolBtnAdd; | ||||
|         private ToolStripButton toolBtnRemove; | ||||
|         private ToolStripButton toolBtnSet; | ||||
|         private TableLayoutPanel tableLayoutPanel1; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										175
									
								
								Check.Main/UI/ModelListEditor.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								Check.Main/UI/ModelListEditor.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,175 @@ | ||||
| using Check.Main.Camera; | ||||
| using Check.Main.Infer; | ||||
| using System; | ||||
| 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.Xml.Serialization; | ||||
| using WeifenLuo.WinFormsUI.Docking; | ||||
|  | ||||
| namespace Check.Main.UI | ||||
| { | ||||
|     public partial class ModelListEditor : DockContent | ||||
|     { | ||||
|         public List<ModelSettings> _settingsList { get; private set; } | ||||
|         private readonly string _configFilePath = Path.Combine(Application.StartupPath, "model.xml"); | ||||
|         public ModelListEditor() | ||||
|         { | ||||
|             InitializeComponent(); | ||||
|             _settingsList = new List<ModelSettings>(); | ||||
|             LoadSettings(); | ||||
|             RefreshListBox(); | ||||
|             AdaptForDialogMode(); // 调整UI为对话框模式 | ||||
|         } | ||||
|         /// <summary> | ||||
|         /// 用于从外部接收一个设置列表进行编辑。 | ||||
|         /// </summary> | ||||
|         /// <param name="settingsToEdit">要编辑的相机设置列表</param> | ||||
|         public ModelListEditor(List<ModelSettings> settingsToEdit) | ||||
|         { | ||||
|             InitializeComponent(); | ||||
|  | ||||
|             // 创建一个现有列表的副本进行编辑,这样如果用户点“取消”,原始列表不会被影响 | ||||
|             _settingsList = settingsToEdit != null | ||||
|                 ? settingsToEdit.Select(s => s.Clone() as ModelSettings).ToList() // 深度克隆更好,这里为简化用浅克隆 | ||||
|                 : new List<ModelSettings>(); | ||||
|  | ||||
|             // 别忘了为列表中的每个对象重新订阅事件 | ||||
|             foreach (var setting in _settingsList) | ||||
|             { | ||||
|                 setting.PropertyChanged += Setting_PropertyChanged; | ||||
|             } | ||||
|  | ||||
|             RefreshListBox(); | ||||
|             AdaptForDialogMode(); // 调整UI为对话框模式 | ||||
|         } | ||||
|         /// <summary> | ||||
|         /// 【新增的方法】 | ||||
|         /// 调整UI,使其更像一个对话框。 | ||||
|         /// </summary> | ||||
|         private void AdaptForDialogMode() | ||||
|         { | ||||
|             // 将“应用配置”按钮改为“确定” | ||||
|             toolBtnSet.Text = "确定"; | ||||
|             toolBtnSet.ToolTipText = "保存更改并关闭窗口"; | ||||
|  | ||||
|             // 新增一个“取消”按钮 | ||||
|             var toolBtnCancel = new ToolStripButton("取消") | ||||
|             { | ||||
|                 DisplayStyle = ToolStripItemDisplayStyle.Text, | ||||
|                 Alignment = ToolStripItemAlignment.Right // 靠右对齐 | ||||
|             }; | ||||
|             toolBtnCancel.Click += (sender, e) => | ||||
|             { | ||||
|                 this.DialogResult = DialogResult.Cancel; | ||||
|                 this.Close(); | ||||
|             }; | ||||
|             toolStrip1.Items.Add(toolBtnCancel); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 【新增的事件处理方法】 | ||||
|         /// 当PropertyGrid中的属性值被用户修改后触发此事件。 | ||||
|         /// </summary> | ||||
|  | ||||
|         // 刷新左侧的相机列表 | ||||
|         private void RefreshListBox() | ||||
|         { | ||||
|             listBoxModels.Items.Clear(); | ||||
|             foreach (var setting in _settingsList) | ||||
|             { | ||||
|                 listBoxModels.Items.Add(setting.Name); | ||||
|             } | ||||
|         } | ||||
|         /// <summary> | ||||
|         /// 【全新的事件处理方法】 | ||||
|         /// 监听单个CameraSettings对象的属性变更通知。 | ||||
|         /// </summary> | ||||
|         private void Setting_PropertyChanged(object sender, PropertyChangedEventArgs e) | ||||
|         { | ||||
|             // 检查是不是“Name”属性发生了变化 | ||||
|             if (e.PropertyName == nameof(CameraSettings.Name)) | ||||
|             { | ||||
|                 // 'sender' 就是那个属性发生了变化的 CameraSettings 对象 | ||||
|                 var changedSetting = sender as ModelSettings; | ||||
|                 if (changedSetting == null) return; | ||||
|  | ||||
|                 // 在 _settingsList 中找到这个对象的索引 | ||||
|                 int index = _settingsList.IndexOf(changedSetting); | ||||
|  | ||||
|                 // 如果找到了,就更新ListBox中对应项的显示文本 | ||||
|                 if (index >= 0) | ||||
|                 { | ||||
|                     // 使用Invoke确保线程安全,虽然在此场景下通常不是问题,但这是个好习惯 | ||||
|                     this.Invoke(new Action(() => | ||||
|                     { | ||||
|                         listBoxModels.Items[index] = changedSetting.Name; | ||||
|                     })); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private void listBoxModels_SelectedIndexChanged(object sender, EventArgs e) | ||||
|         { | ||||
|             if (listBoxModels.SelectedIndex >= 0) | ||||
|             { | ||||
|                 propertyGrid1.SelectedObject = _settingsList[listBoxModels.SelectedIndex]; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 propertyGrid1.SelectedObject = null; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private void toolBtnAdd_Click(object sender, EventArgs e) | ||||
|         { | ||||
|             var newSetting = new ModelSettings { Name = $"model-{_settingsList.Count + 1}" }; | ||||
|             _settingsList.Add(newSetting); | ||||
|             newSetting.PropertyChanged += Setting_PropertyChanged; | ||||
|             RefreshListBox(); | ||||
|             listBoxModels.SelectedIndex = listBoxModels.Items.Count - 1; | ||||
|         } | ||||
|  | ||||
|         private void toolBtnRemove_Click(object sender, EventArgs e) | ||||
|         { | ||||
|             if (listBoxModels.SelectedIndex >= 0) | ||||
|             { | ||||
|                 _settingsList.RemoveAt(listBoxModels.SelectedIndex); | ||||
|                 propertyGrid1.SelectedObject = null; | ||||
|                 RefreshListBox(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private void toolBtnSet_Click(object sender, EventArgs e) | ||||
|         { | ||||
|             this.DialogResult = DialogResult.OK; | ||||
|             this.Close(); | ||||
|         } | ||||
|         // 加载XML配置文件 | ||||
|         private void LoadSettings() | ||||
|         { | ||||
|             if (!File.Exists(_configFilePath)) return; | ||||
|             try | ||||
|             { | ||||
|                 XmlSerializer serializer = new XmlSerializer(typeof(List<ModelSettings>)); | ||||
|                 using (FileStream fs = new FileStream(_configFilePath, FileMode.Open)) | ||||
|                 { | ||||
|                     _settingsList = (List<ModelSettings>)serializer.Deserialize(fs); | ||||
|                 } | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 MessageBox.Show("加载相机配置失败: " + ex.Message); | ||||
|             } | ||||
|             foreach (var setting in _settingsList) | ||||
|             { | ||||
|                 setting.PropertyChanged += Setting_PropertyChanged; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										172
									
								
								Check.Main/UI/ModelListEditor.resx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								Check.Main/UI/ModelListEditor.resx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,172 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <root> | ||||
|   <!-- | ||||
|     Microsoft ResX Schema | ||||
|  | ||||
|     Version 2.0 | ||||
|  | ||||
|     The primary goals of this format is to allow a simple XML format | ||||
|     that is mostly human readable. The generation and parsing of the | ||||
|     various data types are done through the TypeConverter classes | ||||
|     associated with the data types. | ||||
|  | ||||
|     Example: | ||||
|  | ||||
|     ... ado.net/XML headers & schema ... | ||||
|     <resheader name="resmimetype">text/microsoft-resx</resheader> | ||||
|     <resheader name="version">2.0</resheader> | ||||
|     <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> | ||||
|     <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> | ||||
|     <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> | ||||
|     <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> | ||||
|     <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> | ||||
|         <value>[base64 mime encoded serialized .NET Framework object]</value> | ||||
|     </data> | ||||
|     <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | ||||
|         <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> | ||||
|         <comment>This is a comment</comment> | ||||
|     </data> | ||||
|  | ||||
|     There are any number of "resheader" rows that contain simple | ||||
|     name/value pairs. | ||||
|  | ||||
|     Each data row contains a name, and value. The row also contains a | ||||
|     type or mimetype. Type corresponds to a .NET class that support | ||||
|     text/value conversion through the TypeConverter architecture. | ||||
|     Classes that don't support this are serialized and stored with the | ||||
|     mimetype set. | ||||
|  | ||||
|     The mimetype is used for serialized objects, and tells the | ||||
|     ResXResourceReader how to depersist the object. This is currently not | ||||
|     extensible. For a given mimetype the value must be set accordingly: | ||||
|  | ||||
|     Note - application/x-microsoft.net.object.binary.base64 is the format | ||||
|     that the ResXResourceWriter will generate, however the reader can | ||||
|     read any of the formats listed below. | ||||
|  | ||||
|     mimetype: application/x-microsoft.net.object.binary.base64 | ||||
|     value   : The object must be serialized with | ||||
|             : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter | ||||
|             : and then encoded with base64 encoding. | ||||
|  | ||||
|     mimetype: application/x-microsoft.net.object.soap.base64 | ||||
|     value   : The object must be serialized with | ||||
|             : System.Runtime.Serialization.Formatters.Soap.SoapFormatter | ||||
|             : and then encoded with base64 encoding. | ||||
|  | ||||
|     mimetype: application/x-microsoft.net.object.bytearray.base64 | ||||
|     value   : The object must be serialized into a byte array | ||||
|             : using a System.ComponentModel.TypeConverter | ||||
|             : and then encoded with base64 encoding. | ||||
|     --> | ||||
|   <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> | ||||
|     <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> | ||||
|     <xsd:element name="root" msdata:IsDataSet="true"> | ||||
|       <xsd:complexType> | ||||
|         <xsd:choice maxOccurs="unbounded"> | ||||
|           <xsd:element name="metadata"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" use="required" type="xsd:string" /> | ||||
|               <xsd:attribute name="type" type="xsd:string" /> | ||||
|               <xsd:attribute name="mimetype" type="xsd:string" /> | ||||
|               <xsd:attribute ref="xml:space" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="assembly"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:attribute name="alias" type="xsd:string" /> | ||||
|               <xsd:attribute name="name" type="xsd:string" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="data"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||||
|                 <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> | ||||
|               <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> | ||||
|               <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> | ||||
|               <xsd:attribute ref="xml:space" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="resheader"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" type="xsd:string" use="required" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|         </xsd:choice> | ||||
|       </xsd:complexType> | ||||
|     </xsd:element> | ||||
|   </xsd:schema> | ||||
|   <resheader name="resmimetype"> | ||||
|     <value>text/microsoft-resx</value> | ||||
|   </resheader> | ||||
|   <resheader name="version"> | ||||
|     <value>2.0</value> | ||||
|   </resheader> | ||||
|   <resheader name="reader"> | ||||
|     <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||||
|   </resheader> | ||||
|   <resheader name="writer"> | ||||
|     <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||||
|   </resheader> | ||||
|   <metadata name="toolStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> | ||||
|     <value>17, 17</value> | ||||
|   </metadata> | ||||
|   <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> | ||||
|   <data name="toolBtnAdd.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | ||||
|     <value> | ||||
|         iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 | ||||
|         YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIDSURBVDhPpZLrS5NhGMb3j4SWh0oRQVExD4gonkDpg4hG | ||||
|         YKxG6WBogkMZKgPNCEVJFBGdGETEvgwyO9DJE5syZw3PIlPEE9pgBCLZ5XvdMB8Ew8gXbl54nuf63dd9 | ||||
|         0OGSnwCahxbPRNPAPMw9Xpg6ZmF46kZZ0xSKzJPIrhpDWsVnpBhGkKx3nAX8Pv7z1zg8OoY/cITdn4fw | ||||
|         bf/C0kYAN3Ma/w3gWfZL5kzTKBxjWyK2DftwI9tyMYCZKXbNHaD91bLYJrDXsYbrWfUKwJrPE9M2M1Oc | ||||
|         VzOOpHI7Jr376Hi9ogHqFIANO0/MmmmbmSmm9a8ze+I4MrNWAdjtoJgWcx+PSzg166yZZ8xM8XvXDix9 | ||||
|         c4jIqFYAjoriBV9AhEPv1mH/sonogha0afbZMMZz+yreTGyhpusHwtNNCsA5U1zS4BLxzJIfg299qO32 | ||||
|         Ir7UJtZfftyATqeT+8o2D8JSjQrAJblrncYL7ZJ2+bfaFnC/1S1NjL3diRat7qrO7wLRP3HjWsojBeCo | ||||
|         mDEo5mNjuweFGvjWg2EBhCbpkW78htSHHwRyNdmgAFzPEee2iFkzayy2OLXzT4gr6UdUnlXrullsxxQ+ | ||||
|         kx0g8BTA3aZlButjSTyjODq/WcQcW/B/Je4OQhLvKQDnzN1mp0nnkvAhR8VuMzNrpm1mpjgkoVwB/v8D | ||||
|         TgDQASA1MVpwzwAAAABJRU5ErkJggg== | ||||
| </value> | ||||
|   </data> | ||||
|   <data name="toolBtnRemove.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | ||||
|     <value> | ||||
|         iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 | ||||
|         YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIDSURBVDhPpZLrS5NhGMb3j4SWh0oRQVExD4gonkDpg4hG | ||||
|         YKxG6WBogkMZKgPNCEVJFBGdGETEvgwyO9DJE5syZw3PIlPEE9pgBCLZ5XvdMB8Ew8gXbl54nuf63dd9 | ||||
|         0OGSnwCahxbPRNPAPMw9Xpg6ZmF46kZZ0xSKzJPIrhpDWsVnpBhGkKx3nAX8Pv7z1zg8OoY/cITdn4fw | ||||
|         bf/C0kYAN3Ma/w3gWfZL5kzTKBxjWyK2DftwI9tyMYCZKXbNHaD91bLYJrDXsYbrWfUKwJrPE9M2M1Oc | ||||
|         VzOOpHI7Jr376Hi9ogHqFIANO0/MmmmbmSmm9a8ze+I4MrNWAdjtoJgWcx+PSzg166yZZ8xM8XvXDix9 | ||||
|         c4jIqFYAjoriBV9AhEPv1mH/sonogha0afbZMMZz+yreTGyhpusHwtNNCsA5U1zS4BLxzJIfg299qO32 | ||||
|         Ir7UJtZfftyATqeT+8o2D8JSjQrAJblrncYL7ZJ2+bfaFnC/1S1NjL3diRat7qrO7wLRP3HjWsojBeCo | ||||
|         mDEo5mNjuweFGvjWg2EBhCbpkW78htSHHwRyNdmgAFzPEee2iFkzayy2OLXzT4gr6UdUnlXrullsxxQ+ | ||||
|         kx0g8BTA3aZlButjSTyjODq/WcQcW/B/Je4OQhLvKQDnzN1mp0nnkvAhR8VuMzNrpm1mpjgkoVwB/v8D | ||||
|         TgDQASA1MVpwzwAAAABJRU5ErkJggg== | ||||
| </value> | ||||
|   </data> | ||||
|   <data name="toolBtnSet.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | ||||
|     <value> | ||||
|         iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 | ||||
|         YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIDSURBVDhPpZLrS5NhGMb3j4SWh0oRQVExD4gonkDpg4hG | ||||
|         YKxG6WBogkMZKgPNCEVJFBGdGETEvgwyO9DJE5syZw3PIlPEE9pgBCLZ5XvdMB8Ew8gXbl54nuf63dd9 | ||||
|         0OGSnwCahxbPRNPAPMw9Xpg6ZmF46kZZ0xSKzJPIrhpDWsVnpBhGkKx3nAX8Pv7z1zg8OoY/cITdn4fw | ||||
|         bf/C0kYAN3Ma/w3gWfZL5kzTKBxjWyK2DftwI9tyMYCZKXbNHaD91bLYJrDXsYbrWfUKwJrPE9M2M1Oc | ||||
|         VzOOpHI7Jr376Hi9ogHqFIANO0/MmmmbmSmm9a8ze+I4MrNWAdjtoJgWcx+PSzg166yZZ8xM8XvXDix9 | ||||
|         c4jIqFYAjoriBV9AhEPv1mH/sonogha0afbZMMZz+yreTGyhpusHwtNNCsA5U1zS4BLxzJIfg299qO32 | ||||
|         Ir7UJtZfftyATqeT+8o2D8JSjQrAJblrncYL7ZJ2+bfaFnC/1S1NjL3diRat7qrO7wLRP3HjWsojBeCo | ||||
|         mDEo5mNjuweFGvjWg2EBhCbpkW78htSHHwRyNdmgAFzPEee2iFkzayy2OLXzT4gr6UdUnlXrullsxxQ+ | ||||
|         kx0g8BTA3aZlButjSTyjODq/WcQcW/B/Je4OQhLvKQDnzN1mp0nnkvAhR8VuMzNrpm1mpjgkoVwB/v8D | ||||
|         TgDQASA1MVpwzwAAAABJRU5ErkJggg== | ||||
| </value> | ||||
|   </data> | ||||
|   <metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> | ||||
|     <value>37</value> | ||||
|   </metadata> | ||||
| </root> | ||||
							
								
								
									
										381
									
								
								Check.Main/UI/ZoomPictureBox.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										381
									
								
								Check.Main/UI/ZoomPictureBox.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,381 @@ | ||||
| using OpenCvSharp; | ||||
| using OpenCvSharp.Extensions; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.ComponentModel; | ||||
| using System.Drawing; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
| using System.Threading.Tasks; | ||||
| using System.Windows.Forms; | ||||
|  | ||||
| namespace Check.Main.UI | ||||
| { | ||||
|     public class ZoomPictureBox : PictureBox, IDisposable | ||||
|     { | ||||
|         private float zoom = 1.0f; | ||||
|         private System.Drawing.Point imageLocation; | ||||
|         private Image image; | ||||
|         private bool dragging = false; | ||||
|         private System.Drawing.Point mouseDownPos; | ||||
|         private System.Drawing.Point imageLocationOnMouseDown; | ||||
|  | ||||
|         // 矩形绘制相关 | ||||
|         private bool isDrawingRect = false;      // 绘制状态 | ||||
|         private Rectangle currentRect;           // 当前绘制矩形 | ||||
|         private Rectangle storedRect;            // 上次绘制并固定的矩形 | ||||
|         private bool hasStoredRect = false;      // 是否存在固定矩形 | ||||
|         private System.Drawing.Point rectStartPoint; | ||||
|  | ||||
|         // 一个私有的锁对象,专门用于保护对 'image' 字段的访问。 | ||||
|         private readonly object imageLock = new object(); | ||||
|  | ||||
|         private bool croppingEnabled = true; | ||||
|         //private bool croppingEnabled = true; | ||||
|         [Category("Behavior")] | ||||
|         [Description("是否允许通过右键绘制矩形裁剪区域。设置为 false 则不再响应右键并清除已有矩形。")] | ||||
|         public bool CroppingEnabled | ||||
|         { | ||||
|             get => croppingEnabled; | ||||
|             set | ||||
|             { | ||||
|                 croppingEnabled = value; | ||||
|                 // 禁用裁剪时清除所有矩形和裁剪结果 | ||||
|                 if (!croppingEnabled) | ||||
|                 { | ||||
|                     isDrawingRect = false; | ||||
|                     hasStoredRect = false; | ||||
|                     LastCroppedMat?.Dispose(); | ||||
|                     LastCroppedMat = null; | ||||
|                 } | ||||
|                 Invalidate(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // 裁剪完成事件,外部可订阅以接收 Mat | ||||
|         public event Action<Mat> Cropped; | ||||
|  | ||||
|         public Mat LastCroppedMat { get; private set; } | ||||
|  | ||||
|         private Color backgroundFillColor = Color.LightSteelBlue; | ||||
|         private Color rectangleColor = Color.Red; | ||||
|         private int rectangleThickness = 2; | ||||
|  | ||||
|         [Category("Appearance")] | ||||
|         [Description("指定控件背景的填充颜色。使用 BackColor 属性同步更新。")] | ||||
|         public Color BackgroundFillColor | ||||
|         { | ||||
|             get => backgroundFillColor; | ||||
|             set | ||||
|             { | ||||
|                 backgroundFillColor = value; | ||||
|                 base.BackColor = value;  // 同步 WinForms BackColor | ||||
|                 Invalidate(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|  | ||||
|         [Category("Appearance")] | ||||
|         [Description("绘制矩形框的颜色。")] | ||||
|         public Color RectangleColor { get => rectangleColor; set { rectangleColor = value; Invalidate(); } } | ||||
|  | ||||
|         [Category("Appearance")] | ||||
|         [Description("绘制矩形框的线宽。")] | ||||
|         public int RectangleThickness { get => rectangleThickness; set { rectangleThickness = Math.Max(1, value); Invalidate(); } } | ||||
|  | ||||
|         public ZoomPictureBox() | ||||
|         { | ||||
|             DoubleBuffered = true; | ||||
|             ResizeRedraw = true; | ||||
|             base.BackColor = backgroundFillColor; | ||||
|             SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer, true); | ||||
|             MouseWheel += ZoomPictureBox_MouseWheel; | ||||
|             MouseDown += ZoomPictureBox_MouseDown; | ||||
|             MouseMove += ZoomPictureBox_MouseMove; | ||||
|             MouseUp += ZoomPictureBox_MouseUp; | ||||
|             DoubleClick += ZoomPictureBox_DoubleClick; | ||||
|         } | ||||
|         // 重写背景绘制,使用自定义背景色 | ||||
|         protected override void OnPaintBackground(PaintEventArgs e) | ||||
|         { | ||||
|             e.Graphics.Clear(backgroundFillColor); | ||||
|         } | ||||
|         //[Browsable(false)] | ||||
|         //public new Image Image | ||||
|         //{ | ||||
|         //    get => image; | ||||
|         //    set | ||||
|         //    { | ||||
|         //        image = value; | ||||
|         //        zoom = 1.0f; | ||||
|         //        LastCroppedMat?.Dispose(); | ||||
|         //        LastCroppedMat = null; | ||||
|         //        isDrawingRect = false; | ||||
|         //        hasStoredRect = false; | ||||
|         //        FitImage(); | ||||
|         //        Invalidate(); | ||||
|         //    } | ||||
|         //} | ||||
|         // 【关键优化】 | ||||
|         // 2. 重写 Control.Dispose 方法来释放我们自己的资源 | ||||
|         protected override void Dispose(bool disposing) | ||||
|         { | ||||
|             if (disposing) | ||||
|             { | ||||
|                 // 在这里释放托管和非托管资源 | ||||
|                 // 清理事件订阅 (虽然在这个类里没有,但这是个好习惯) | ||||
|  | ||||
|                 // 释放我们持有的最后一个图像资源 | ||||
|                 lock (imageLock) | ||||
|                 { | ||||
|                     this.image?.Dispose(); | ||||
|                     this.image = null; | ||||
|                 } | ||||
|  | ||||
|                 // 释放我们持有的最后一个裁剪结果资源 | ||||
|                 this.LastCroppedMat?.Dispose(); | ||||
|                 this.LastCroppedMat = null; | ||||
|             } | ||||
|  | ||||
|             // 调用基类的 Dispose 方法来完成标准清理 | ||||
|             base.Dispose(disposing); | ||||
|         } | ||||
|  | ||||
|         // 重写 Image 属性,但不做任何额外操作,因为我们将通过一个新方法来更新它。 | ||||
|         [Browsable(false)] | ||||
|         public new Image Image | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 // 在访问时也加锁,确保读取的是一个完整的对象 | ||||
|                 lock (imageLock) | ||||
|                 { | ||||
|                     return image; | ||||
|                 } | ||||
|             } | ||||
|             // set 访问器可以保持原样,但我们不再直接使用它来更新图像 | ||||
|             private set | ||||
|             { | ||||
|                 lock (imageLock) | ||||
|                 { | ||||
|                     image = value; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         protected override void OnPaint(PaintEventArgs e) | ||||
|         { | ||||
|             base.OnPaint(e); | ||||
|             var g = e.Graphics; | ||||
|             //在绘制前获取锁,确保在绘制期间,image对象不会被其他线程替换或释放 | ||||
|             lock (imageLock) | ||||
|             { | ||||
|                 if (image != null) | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         int w = (int)(image.Width * zoom); | ||||
|                         int h = (int)(image.Height * zoom); | ||||
|                         g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; | ||||
|                         g.DrawImage(image, new Rectangle(imageLocation, new System.Drawing.Size(w, h))); | ||||
|                     } | ||||
|                     catch (Exception) | ||||
|                     { | ||||
|  | ||||
|                         // 如果在绘制时仍然发生罕见的GDI+错误,静默忽略,避免程序崩溃。 | ||||
|                         // 这通常意味着图像状态仍然存在问题,但UI不会因此卡死。 | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             //if (image != null) | ||||
|             //{ | ||||
|             //    int w = (int)(image.Width * zoom); | ||||
|             //    int h = (int)(image.Height * zoom); | ||||
|             //    g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; | ||||
|             //    g.DrawImage(image, new Rectangle(imageLocation, new System.Drawing.Size(w, h))); | ||||
|             //} | ||||
|             // 仅当允许裁剪时才绘制矩形 | ||||
|             if (croppingEnabled) | ||||
|             { | ||||
|                 using (var pen = new Pen(rectangleColor, rectangleThickness)) | ||||
|                 { | ||||
|                     if (hasStoredRect) g.DrawRectangle(pen, storedRect); | ||||
|                     else if (isDrawingRect) g.DrawRectangle(pen, currentRect); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 一个线程安全的、用于更新显示图像的公共方法。 | ||||
|         /// 这个方法将替换掉直接的 pictureBox.Image = ... 赋值。 | ||||
|         /// </summary> | ||||
|         /// <param name="newImage">要显示的新图像。此方法会接管该对象的所有权。</param> | ||||
|         public void SetImageThreadSafe(Image newImage) | ||||
|         { | ||||
|             Image oldImage = null; | ||||
|  | ||||
|             lock (imageLock) | ||||
|             { | ||||
|                 // 1. 保存旧图像的引用,以便在锁外部释放它 | ||||
|                 oldImage = this.image; | ||||
|                 // 2. 将新图像赋值给成员字段 | ||||
|                 this.image = newImage; | ||||
|             } | ||||
|             // 3. 【关键】在锁的外部释放旧图像,避免长时间持有锁 | ||||
|             oldImage?.Dispose(); | ||||
|  | ||||
|             // 4. 计算自适应并触发重绘 | ||||
|             FitImage(); | ||||
|             Invalidate(); | ||||
|         } | ||||
|  | ||||
|         private void FitImage() | ||||
|         { | ||||
|             //if (image == null) return; | ||||
|             //float sx = (float)ClientSize.Width / image.Width; | ||||
|             //float sy = (float)ClientSize.Height / image.Height; | ||||
|             //zoom = Math.Min(sx, sy); | ||||
|             //CenterImage(); | ||||
|             //在访问image属性前获取锁 | ||||
|             lock (imageLock) | ||||
|             { | ||||
|                 if (image == null) return; | ||||
|  | ||||
|                 try | ||||
|                 { | ||||
|                     float sx = (float)ClientSize.Width / image.Width; | ||||
|                     float sy = (float)ClientSize.Height / image.Height; | ||||
|                     zoom = Math.Min(sx, sy); | ||||
|                     CenterImage(); | ||||
|                 } | ||||
|                 catch (Exception) | ||||
|                 { | ||||
|  | ||||
|                     // 忽略错误,同 OnPaint | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private void CenterImage() | ||||
|         { | ||||
|             if (image == null) return; | ||||
|             int w = (int)(image.Width * zoom); | ||||
|             int h = (int)(image.Height * zoom); | ||||
|             imageLocation = new System.Drawing.Point((ClientSize.Width - w) / 2, (ClientSize.Height - h) / 2); | ||||
|         } | ||||
|  | ||||
|         private void ZoomPictureBox_MouseWheel(object sender, MouseEventArgs e) | ||||
|         { | ||||
|             if (image == null) return; | ||||
|             float oldZoom = zoom; | ||||
|             zoom *= e.Delta > 0 ? 1.1f : 1 / 1.1f; | ||||
|             zoom = Math.Max(0.1f, Math.Min(zoom, 100f)); | ||||
|             var m = e.Location; | ||||
|             float ix = (m.X - imageLocation.X) / oldZoom; | ||||
|             float iy = (m.Y - imageLocation.Y) / oldZoom; | ||||
|             imageLocation = new System.Drawing.Point((int)(m.X - ix * zoom), (int)(m.Y - iy * zoom)); | ||||
|             Invalidate(); | ||||
|         } | ||||
|  | ||||
|         private void ZoomPictureBox_MouseDown(object sender, MouseEventArgs e) | ||||
|         { | ||||
|             if (image == null) return; | ||||
|             if (e.Button == MouseButtons.Left) | ||||
|             { | ||||
|                 hasStoredRect = false; | ||||
|                 LastCroppedMat?.Dispose(); | ||||
|                 LastCroppedMat = null; | ||||
|  | ||||
|                 dragging = true; | ||||
|                 mouseDownPos = e.Location; | ||||
|                 imageLocationOnMouseDown = imageLocation; | ||||
|                 Cursor = Cursors.Hand; | ||||
|             } | ||||
|             else if (e.Button == MouseButtons.Right && croppingEnabled) | ||||
|             { | ||||
|                 // 清除上次固定矩形和裁剪 | ||||
|                 hasStoredRect = false; | ||||
|                 LastCroppedMat?.Dispose(); | ||||
|                 LastCroppedMat = null; | ||||
|  | ||||
|                 // 开始新绘制 | ||||
|                 isDrawingRect = true; | ||||
|                 rectStartPoint = e.Location; | ||||
|                 currentRect = new Rectangle(e.Location, System.Drawing.Size.Empty); | ||||
|                 Invalidate(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private void ZoomPictureBox_MouseMove(object sender, MouseEventArgs e) | ||||
|         { | ||||
|             if (dragging) | ||||
|             { | ||||
|                 var delta = new System.Drawing.Point(e.X - mouseDownPos.X, e.Y - mouseDownPos.Y); | ||||
|                 imageLocation = new System.Drawing.Point(imageLocationOnMouseDown.X + delta.X, imageLocationOnMouseDown.Y + delta.Y); | ||||
|                 Invalidate(); | ||||
|             } | ||||
|             else if (isDrawingRect) | ||||
|             { | ||||
|                 int x = Math.Min(rectStartPoint.X, e.X); | ||||
|                 int y = Math.Min(rectStartPoint.Y, e.Y); | ||||
|                 int w = Math.Abs(e.X - rectStartPoint.X); | ||||
|                 int h = Math.Abs(e.Y - rectStartPoint.Y); | ||||
|                 currentRect = new Rectangle(x, y, w, h); | ||||
|                 Invalidate(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private void ZoomPictureBox_MouseUp(object sender, MouseEventArgs e) | ||||
|         { | ||||
|             if (e.Button == MouseButtons.Left && dragging) | ||||
|             { | ||||
|                 dragging = false; | ||||
|                 Cursor = Cursors.Default; | ||||
|             } | ||||
|             else if (e.Button == MouseButtons.Right && isDrawingRect) | ||||
|             { | ||||
|                 isDrawingRect = false; | ||||
|                 // 固定当前矩形框 | ||||
|                 storedRect = currentRect; | ||||
|                 hasStoredRect = true; | ||||
|                 if (croppingEnabled && image is Bitmap bmp) | ||||
|                 { | ||||
|                     this.LastCroppedMat?.Dispose(); | ||||
|                     this.LastCroppedMat = null; | ||||
|                     Mat srcMat = null; | ||||
|                     try | ||||
|                     { | ||||
|                         int ix = (int)((storedRect.X - imageLocation.X) / zoom); | ||||
|                         int iy = (int)((storedRect.Y - imageLocation.Y) / zoom); | ||||
|                         int iw = (int)(storedRect.Width / zoom); | ||||
|                         int ih = (int)(storedRect.Height / zoom); | ||||
|                         ix = Math.Max(0, ix); | ||||
|                         iy = Math.Max(0, iy); | ||||
|                         if (ix + iw > bmp.Width) iw = bmp.Width - ix; | ||||
|                         if (iy + ih > bmp.Height) ih = bmp.Height - iy; | ||||
|                         if (iw > 0 && ih > 0) | ||||
|                         { | ||||
|                             srcMat = BitmapConverter.ToMat(bmp); | ||||
|                             var roi = new Rect(ix, iy, iw, ih); | ||||
|                             LastCroppedMat = new Mat(srcMat, roi); | ||||
|                             Cropped?.Invoke(LastCroppedMat); | ||||
|                             bmp.Dispose(); | ||||
|                         } | ||||
|                     } | ||||
|                     finally | ||||
|                     { | ||||
|                         // 5.确保从Bitmap转换来的 srcMat 被释放 | ||||
|                         srcMat?.Dispose(); | ||||
|                     } | ||||
|                 } | ||||
|                 Invalidate(); | ||||
|             } | ||||
|         } | ||||
|         private void ZoomPictureBox_DoubleClick(object sender, EventArgs e) | ||||
|         { | ||||
|             if (image == null) return; | ||||
|             FitImage(); | ||||
|             Invalidate(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user