C3204
2026-01-13 510dc693e91b291f36667088f47923591d25c98f
LB_SmartVision/VisionForm.cs
@@ -3,19 +3,23 @@
using LB_SmartVision.Forms.Pages;
using LB_SmartVision.Forms.Pages.CameraPage;
using LB_SmartVision.Forms.Pages.CommunicatorPage;
using LB_SmartVision.Forms.Pages.HistoricalData;
using LB_SmartVision.Forms.Pages.MESPage;
using LB_SmartVision.Forms.Pages.MotionControlPage;
using LB_SmartVision.Forms.Pages.ProcessPage;
using LB_SmartVision.Forms.Pages.SettingPage;
using LB_SmartVision.Forms.Pages.UserManagementPage;
using LB_SmartVision.ProcessRun;
using LB_SmartVision.SQL;
using LB_SmartVision.Tool;
using LB_SmartVisionCommon;
using LB_SmartVisionLoginUI;
using LB_VisionProcesses;
using LB_VisionProcesses.Cameras;
using LB_VisionProcesses.Cameras.HRCameras;
using LB_VisionProcesses.Cameras.LBCameras;
using LB_VisionProcesses.Communicators;
using LB_VisionProcesses.Communicators.SiemensS7;
using LB_VisionProcesses.Communicators.TCom;
using LB_VisionProcesses.Forms;
using log4net.Config;
@@ -40,8 +44,9 @@
{
    public partial class VisionForm : Form
    {
        AllProcessesPage AllProcessesPage = new AllProcessesPage();
        AllProcessesPage AllProcessesPages = new AllProcessesPage();
        CamerasEditPage CamerasEditPage = new CamerasEditPage();
        HistoricalDataEditPage HistoricalDataEditPage = new HistoricalDataEditPage();
        CommunicatorsEditPage CommunicatorsEditPage = new CommunicatorsEditPage();
        SettingEditPage SettingEditPage = new SettingEditPage();
        MESEditPage MESEditPage = new MESEditPage();
@@ -62,11 +67,16 @@
            Assembly_LB_VisionProcessesDll = Assembly.Load(Assembly_LB_VisionProcessesBytes);
            GlobalVar.dicCommunicators.DictionaryChanged += CommunicatorsChanged;
            GlobalVar.dicCameras.DictionaryChanged += CamerasChanged;
            GlobalVar.dicProcesses.DictionaryChanged += ProcessRunBllChanged;
            //最开始就清空所有Tab页
            materialTabControl.TabPages.Clear();
            materialTabControl.Controls.Add(AllProcessesPage);
            AllProcessesPages.controlsPanel.Dock = DockStyle.Fill;
            materialTabControl.Controls.Add(AllProcessesPages);
            HistoricalDataEditPage.LogInfo += LogInfo;
            materialTabControl.Controls.Add(new MyPage(HistoricalDataEditPage));
            CamerasEditPage.LogInfo += LogInfo;
            materialTabControl.Controls.Add(new MyPage(CamerasEditPage));
@@ -90,7 +100,6 @@
            {
                //materialTabControl.TabPages[i].Font= new Font("Microsoft YaHei UI", 18F, FontStyle.Regular, GraphicsUnit.Point, 0);
            }
            materialTabSelector.BaseTabControl = materialTabControl;
            //materialTabSelector.Font = new Font("Microsoft YaHei UI", 18F, FontStyle.Regular, GraphicsUnit.Point, 0);
        }
@@ -232,7 +241,30 @@
        private void CommunicatorsChanged(object? sender, DictionaryChangedEventArgs<string, BaseCommunicator> e)
        {
            try
            {
                switch (e.ChangeType)
                {
                    case DictionaryChangeType.Added:
                        e.NewValue.TriggerRunMessageReceived += TriggerRunMessageReceived;
                        LogInfo($"通讯口[{e.NewValue.CommunicatorName}]加载触发通讯", LogInfoType.INFO);
                        e.NewValue.CommunicatorName = e.NewKey;
                        break;
                    case DictionaryChangeType.Renamed:
                        string OldCommunicatorName = e.OldKey;
                        string NewCommunicatorName = e.NewKey;
                        LogInfo(string.Format("重命名通讯口名[{0}]修改为[{1}]", OldCommunicatorName, NewCommunicatorName), LogInfoType.INFO);
                        e.NewValue.CommunicatorName = NewCommunicatorName;
                        break;
                    case DictionaryChangeType.Removed:
                        if (e.OldValue != null && e.OldValue is BaseCommunicator)
                            e.OldValue.Disconnect();
                        e.OldValue.TriggerRunMessageReceived -= TriggerRunMessageReceived;
                        LogInfo($"通讯口[{e.OldValue.CommunicatorName}]移除触发通讯", LogInfoType.INFO);
                        break;
                }
            }
            catch { }
        }
        private void LogInfo(string strLog, LogInfoType infoType)
@@ -241,7 +273,7 @@
            {
                return;
            }
            string strInfo = DateTime.Now.ToString("[yyyy:MM:HH:mm:ss:fff] ");
            string strInfo = DateTime.Now.ToString("[yyyy:MM:dd:HH:mm:ss:fff] ");
            strInfo += strLog;
            if (infoType != LogInfoType.NOSHOW)
            {
@@ -257,19 +289,30 @@
                        switch (infoType)
                        {
                            case LogInfoType.INFO:
                                this.rich_Info.SelectionColor = Color.Wheat;
                                break;
                                {
                                    this.rich_Info.SelectionColor = Color.Wheat;
                                    AsyncLogHelper.Info(strLog);
                                    break;
                                }
                            case LogInfoType.WARN:
                                this.rich_Info.SelectionColor = Color.LightGoldenrodYellow;
                                break;
                                {
                                    this.rich_Info.SelectionColor = Color.Yellow;
                                    AsyncLogHelper.Warn(strLog);
                                    break;
                                }
                            case LogInfoType.PASS:
                                this.rich_Info.SelectionColor = Color.Green;
                                break;
                                {
                                    this.rich_Info.SelectionColor = Color.Green;
                                    AsyncLogHelper.Info(strLog);
                                    break;
                                }
                            case LogInfoType.ERROR:
                                this.rich_Info.SelectionColor = Color.Red;
                                break;
                                {
                                    this.rich_Info.SelectionColor = Color.Red;
                                    AsyncLogHelper.Error(strLog);
                                    break;
                                }
                        }
                        // 更新 UI 控件,比如显示接收到的消息
                        this.rich_Info.AppendText(strInfo);
                        this.rich_Info.AppendText("\r\n");
@@ -281,32 +324,43 @@
                {
                    if (this.rich_Info.Lines.Length > 1000)
                    {
                        this.rich_Info.Clear();
                    }
                    // 如果已经在 UI 线程上,直接更新 UI
                    switch (infoType)
                    {
                        case LogInfoType.INFO:
                            this.rich_Info.SelectionColor = Color.Wheat;
                            break;
                            {
                                this.rich_Info.SelectionColor = Color.Wheat;
                                AsyncLogHelper.Info(strLog);
                                break;
                            }
                        case LogInfoType.WARN:
                            this.rich_Info.SelectionColor = Color.Yellow;
                            break;
                            {
                                this.rich_Info.SelectionColor = Color.Yellow;
                                AsyncLogHelper.Warn(strLog);
                                break;
                            }
                        case LogInfoType.PASS:
                            this.rich_Info.SelectionColor = Color.Green;
                            break;
                            {
                                this.rich_Info.SelectionColor = Color.Green;
                                AsyncLogHelper.Info(strLog);
                                break;
                            }
                        case LogInfoType.ERROR:
                            this.rich_Info.SelectionColor = Color.Red;
                            break;
                            {
                                this.rich_Info.SelectionColor = Color.Red;
                                AsyncLogHelper.Error(strLog);
                                break;
                            }
                    }
                    this.rich_Info.AppendText(strInfo);
                    this.rich_Info.AppendText("\r\n");
                    this.rich_Info.SelectionStart = this.rich_Info.Text.Length;
                    this.rich_Info.ScrollToCaret();
                }
            }
            AsyncLogHelper.Info(strLog);
        }
        public static bool SaveAllLayout()
@@ -497,13 +551,35 @@
            }
        }
        private void CamerasChanged(object sender, DictionaryChangedEventArgs<string, BaseCamera> e)
        {
            try
            {
                switch (e.ChangeType)
                {
                    case DictionaryChangeType.Added:
                        e.NewValue.TriggerRunMessageReceived += TriggerRunMessageReceived;
                        LogInfo($"相机[{e.NewValue.SN}]加载触发通讯", LogInfoType.INFO);
                        e.NewValue.SN = e.NewKey;
                        break;
                    case DictionaryChangeType.Removed:
                        if (e.OldValue != null && e.OldValue is BaseCommunicator)
                            e.OldValue.CloseDevice();
                        e.OldValue.TriggerRunMessageReceived -= TriggerRunMessageReceived;
                        LogInfo($"相机[{e.OldValue.SN}]移除触发通讯", LogInfoType.INFO);
                        break;
                }
            }
            catch { }
        }
        private void VisionForm_Load(object sender, EventArgs e)
        {
            XmlConfigurator.Configure(new System.IO.FileInfo("log4net.config"));
            string[] paths = {
            @"生产日志\Run_Log",
            @"生产日志\Debug_Log",
            @"生产日志\Error_Log",
            @"生产日志\Run",
            @"生产日志\Debug",
            @"生产日志\Error",
            @"生产日志\Fatal",
            @"生产日志\Warn",
            };
@@ -519,72 +595,100 @@
            }
            LB_SmartVision.Tool.Tool.ReadStringConfig("User ID", out string User_ID);
            LB_SmartVision.Tool.Tool.ReadStringConfig("Password", out string Password);
            GlobalVar.strProductName = DateBaseName;
            //加载通讯
            foreach (BaseCommunicator com in GlobalVar.dicCommunicators.Values)
            {
                com.Disconnect();
            }
            GlobalVar.dicCommunicators.Clear();
            if (LoadAllCommunicators(GlobalVar.allCommunicatorsConnectionStringPath))
            {
                LogInfo("通讯加载成功", LogInfoType.PASS);
            }
            else
            {
                LogInfo("通讯加载失败", LogInfoType.ERROR);
            }
            //加载相机
            foreach (BaseCamera camera in GlobalVar.dicCameras.Values)
            {
                camera.Dispose();
            }
            GlobalVar.dicCameras.Clear();
            if (LoadAllCameras(GlobalVar.allCamerasConnectionStringPath))
            {
                LogInfo("相机加载成功", LogInfoType.PASS);
            }
            else
            {
                LogInfo("相机加载失败", LogInfoType.ERROR);
            //必须先加载相机和通讯端口,因为流程加载过程中会用到相机和通讯口
            }
            //加载全局变量
            IProcess.dicGlobalVars.Clear();
            if (LoadAllProcessVars(GlobalVar.allProcessVarsPath))
            {
                LogInfo("全局变量加载成功", LogInfoType.PASS);
            }
            else
            {
                LogInfo("全局变量加载失败", LogInfoType.ERROR);
            }
            //加载流程
            GlobalVar.dicProcesses.Clear();
            if (LoadAllProcess(GlobalVar.allProcessConnectionStringPath))
            {
                LogInfo("流程加载成功", LogInfoType.PASS);
            }
            else
            {
                LogInfo("流程加载失败", LogInfoType.ERROR);
            }
            //加载触发设置
            if (LoadAllProcessSetting(GlobalVar.allProcessSettingStringPath))
            {
                LogInfo("流程设置加载成功", LogInfoType.PASS);
            }
            else
            {
                LogInfo("流程设置加载失败", LogInfoType.ERROR);
            }
            //加载流程布局
            GlobalVar.dicLayout.Clear();
            if (LoadAllLayout(GlobalVar.allLayoutPath))
            {
                LogInfo("流程布局加载成功", LogInfoType.PASS);
            }
            else
            {
                LogInfo("流程布局加载失败", LogInfoType.ERROR);
            }
            //加载流程表格
            GlobalVar.dicCsvSetting.Clear();
            if (LoadAllCsv(GlobalVar.allCsvPath))
            {
                LogInfo("流程表格加载成功", LogInfoType.PASS);
            }
            else
            {
                LogInfo("流程表格加载失败", LogInfoType.ERROR);
            }
            //显示所有产品
            com_ProductName.Items.Clear();
            LB_SmartVision.Tool.Tool.ReadStringConfig("产品列表", out string Products);
            List<string> lstProduct = (Products.Split(',')).ToList();
            foreach (string DatabaseName in lstProduct)
            {
                com_ProductName.Items.Add(DatabaseName);
            }
            com_ProductName.Items.Add("新增");
            com_ProductName.Text = GlobalVar.strProductName;
            this.WindowState = FormWindowState.Maximized;
            DatabaseRecordProductDataHelper.InitializeDatabase();
        }
        public void SaveAllSetting()
@@ -614,7 +718,6 @@
                strJson = streamReader.ReadToEnd();
                streamReader.Close();
            }
            GlobalVar.allCamerasConnectionString = JsonConvert.DeserializeObject<ConcurrentDictionary<string, string>>(strJson);
            if (GlobalVar.allCamerasConnectionString == null)
            {
@@ -628,18 +731,23 @@
                switch (brand)
                {
                    case CameraBrand.HRCamera:
                        camera = new HRCamera();
                        break;
                        {
                            camera = new HRCamera();
                            break;
                        }
                    case CameraBrand.LBCamera:
                        //camera = new LBCamera();
                        break;
                        {
                            camera = new LBCamera();
                            break;
                        }
                    default:
                        MessageBox.Show($"[{CameraConnectionString.Key}]品牌不支持!", "异常");
                        continue;
                        {
                            MessageBox.Show($"[{CameraConnectionString.Key}]品牌不支持!", "异常");
                            continue;
                        }
                }
                camera.SN = CameraConnectionString.Key;
                if (!camera.InitDevice(CameraConnectionString.Key, this.Handle))
                if (!camera.InitDevice(CameraConnectionString.Key, IntPtr.Zero))
                {
                    LogInfo($"初始化相机[{CameraConnectionString.Key}]失败", LogInfoType.ERROR);
                    if (camera != null)
@@ -665,8 +773,9 @@
                    string CameraBrand = item.Value.Brand.ToString();//"1111"
                    if (string.IsNullOrEmpty(CameraSN) || string.IsNullOrEmpty(CameraBrand))
                    {
                        break;
                    }
                    GlobalVar.allCamerasConnectionString.TryAdd(CameraSN, CameraBrand);
                }
                var settings = new JsonSerializerSettings
@@ -731,61 +840,122 @@
                {
                    string CommunicatorName = CommunicatorConnectionString.Key;
                    string CommunicatorAddress = CommunicatorConnectionString.Value;
                    // 定义正则表达式以提取协议、IP 地址和端口
                    //1.    \((.*?)\):\(和 \) 是用于匹配括号的转义字符。
                    //      (.*?) 是一个非贪婪的匹配,用来匹配类名(MyProcesses.Communicators.TCPServer 或 MyProcesses.Communicators.UARTPort)。
                    //2.    ([^:] +):匹配冒号之前的部分,即地址(127.0.0.1 或 COM5)。这里使用了[^:] 来匹配除了冒号之外的任意字符。
                    //3.    (\d +) :匹配端口号,确保它匹配一个或多个数字。
                    string pattern = @"^\((?<ClassName>[^)]+)\)\[(?<IP>[^]]+)\]\[(?<PORT>[^]]+)\]$";
                    Match match = Regex.Match(CommunicatorAddress, pattern);
                    if (match.Success)
                    if (!string.IsNullOrEmpty(CommunicatorAddress) && CommunicatorAddress.Contains("SiemensLBS7"))
                    {
                        string ClassName = match.Groups["ClassName"].Value;   // "TCP"
                        string IP = match.Groups["IP"].Value;          // "127.0.0.1"
                        string PORT = match.Groups["PORT"].Value;        // "1111"
                        // 定义正则表达式以提取协议、IP 地址和端口
                        //1.    \((.*?)\):\(和 \) 是用于匹配括号的转义字符。
                        //      (.*?) 是一个非贪婪的匹配,用来匹配类名(MyProcesses.Communicators.TCPServer 或 MyProcesses.Communicators.UARTPort)。
                        //2.    ([^:] +):匹配冒号之前的部分,即地址(127.0.0.1 或 COM5)。这里使用了[^:] 来匹配除了冒号之外的任意字符。
                        //3.    (\d +) :匹配端口号,确保它匹配一个或多个数字。
                        if (string.IsNullOrEmpty(ClassName) || string.IsNullOrEmpty(IP) || string.IsNullOrEmpty(PORT))
                            break;
                        string pattern = @"^\((?<ClassName>[^)]+)\)\[(?<IP>[^]]+)\]\[(?<Slot>[^]]+)\]\[(?<CpuType>[^]]+)\]\[(?<PlcAddress>[^]]+)\]$";
                        Match match = Regex.Match(CommunicatorAddress, pattern);
                        //利用反射创建实例
                        Type type = IProcess.GetExecutingAssembly().GetType(ClassName);
                        if (type == null)
                        if (match.Success)
                        {
                            Debug.WriteLine("Class not found.");
                            return false;
                        }
                        var Communicator = Activator.CreateInstance(type, CommunicatorName) as BaseCommunicator;
                            string ClassName = match.Groups["ClassName"].Value;   // "TCP"
                            string IP = match.Groups["IP"].Value;          // "127.0.0.1"
                            string Slot = match.Groups["Slot"].Value;        // "1111"
                            string CpuType= match.Groups["CpuType"].Value;
                            string PlcAddress = match.Groups["PlcAddress"].Value;
                            if (string.IsNullOrEmpty(ClassName) || string.IsNullOrEmpty(IP) || string.IsNullOrEmpty(Slot) || string.IsNullOrEmpty(CpuType) || string.IsNullOrEmpty(PlcAddress))
                                break;
                        if (Communicator == null)
                        {
                            Debug.WriteLine("BaseCommunicator not found.");
                            return false;
                        }
                            //利用反射创建实例
                            Type type = IProcess.GetExecutingAssembly().GetType(ClassName);
                            if (type == null)
                            {
                                Debug.WriteLine("Class not found.");
                                return false;
                            }
                            var Communicator = Activator.CreateInstance(type, CommunicatorName) as BaseCommunicator;
                        //TCP客户端最后再连接
                        if (Communicator is TCPClient)
                        {
                            clientsCommunicatorsConnectionString.TryAdd(CommunicatorConnectionString.Key, CommunicatorConnectionString.Value);
                            continue;
                        }
                            if (Communicator == null)
                            {
                                Debug.WriteLine("BaseCommunicator not found.");
                                return false;
                            }
                        Communicator.CommunicatorConnections.Add("地址", IP);
                        Communicator.CommunicatorConnections.Add("端口", PORT);
                        Communicator.CommunicatorName = CommunicatorName;
                        if (!Communicator.Connect())
                            LogInfo($"初始化通讯口[{CommunicatorName}]失败,原因是{Communicator.Msg}", LogInfoType.ERROR);
                            Communicator.CommunicatorConnections.Add("地址", IP);
                            Communicator.CommunicatorConnections.Add("端口", Slot);
                            Communicator.CommunicatorConnections.Add("型号", CpuType);
                            Communicator.CommunicatorConnections.Add("变量地址", PlcAddress);
                            Communicator.CommunicatorName = CommunicatorName;
                            if (!Communicator.Connect())
                            {
                                LogInfo($"初始化通讯口[{CommunicatorName}]失败,原因是{Communicator.Msg}", LogInfoType.ERROR);
                            }
                            else
                            {
                                LogInfo($"初始化通讯口[{CommunicatorName}]成功", LogInfoType.PASS);
                            }
                            GlobalVar.dicCommunicators.TryAdd(CommunicatorName, Communicator);
                        }
                        else
                            LogInfo($"初始化通讯口[{CommunicatorName}]成功", LogInfoType.PASS);
                        GlobalVar.dicCommunicators.TryAdd(CommunicatorName, Communicator);
                        {
                            Debug.WriteLine("No match found.");
                        }
                    }
                    else
                    {
                        Debug.WriteLine("No match found.");
                    }
                        // 定义正则表达式以提取协议、IP 地址和端口
                        //1.    \((.*?)\):\(和 \) 是用于匹配括号的转义字符。
                        //      (.*?) 是一个非贪婪的匹配,用来匹配类名(MyProcesses.Communicators.TCPServer 或 MyProcesses.Communicators.UARTPort)。
                        //2.    ([^:] +):匹配冒号之前的部分,即地址(127.0.0.1 或 COM5)。这里使用了[^:] 来匹配除了冒号之外的任意字符。
                        //3.    (\d +) :匹配端口号,确保它匹配一个或多个数字。
                        string pattern = @"^\((?<ClassName>[^)]+)\)\[(?<IP>[^]]+)\]\[(?<PORT>[^]]+)\]$";
                        Match match = Regex.Match(CommunicatorAddress, pattern);
                        if (match.Success)
                        {
                            string ClassName = match.Groups["ClassName"].Value;   // "TCP"
                            string IP = match.Groups["IP"].Value;          // "127.0.0.1"
                            string PORT = match.Groups["PORT"].Value;        // "1111"
                            if (string.IsNullOrEmpty(ClassName) || string.IsNullOrEmpty(IP) || string.IsNullOrEmpty(PORT))
                                break;
                            //利用反射创建实例
                            Type type = IProcess.GetExecutingAssembly().GetType(ClassName);
                            if (type == null)
                            {
                                Debug.WriteLine("Class not found.");
                                return false;
                            }
                            var Communicator = Activator.CreateInstance(type, CommunicatorName) as BaseCommunicator;
                            if (Communicator == null)
                            {
                                Debug.WriteLine("BaseCommunicator not found.");
                                return false;
                            }
                            //TCP客户端最后再连接
                            if (Communicator is TCPClient)
                            {
                                clientsCommunicatorsConnectionString.TryAdd(CommunicatorConnectionString.Key, CommunicatorConnectionString.Value);
                                continue;
                            }
                            Communicator.CommunicatorConnections.Add("地址", IP);
                            Communicator.CommunicatorConnections.Add("端口", PORT);
                            Communicator.CommunicatorName = CommunicatorName;
                            if (!Communicator.Connect())
                            {
                                LogInfo($"初始化通讯口[{CommunicatorName}]失败,原因是{Communicator.Msg}", LogInfoType.ERROR);
                            }
                            else
                            {
                                LogInfo($"初始化通讯口[{CommunicatorName}]成功", LogInfoType.PASS);
                            }
                            GlobalVar.dicCommunicators.TryAdd(CommunicatorName, Communicator);
                        }
                        else
                        {
                            Debug.WriteLine("No match found.");
                        }
                    }
                }
                //TCP客户端最后连接
@@ -810,7 +980,9 @@
                        string PORT = match.Groups[3].Value;        // "1111"
                        if (string.IsNullOrEmpty(ClassName) || string.IsNullOrEmpty(IP) || string.IsNullOrEmpty(PORT))
                        {
                            break;
                        }
                        //利用反射创建实例
                        Type type = IProcess.GetExecutingAssembly().GetType(ClassName);
@@ -831,10 +1003,13 @@
                        Communicator.CommunicatorConnections.Add("端口", PORT);
                        Communicator.CommunicatorName = CommunicatorName;
                        if (!Communicator.Connect())
                        {
                            LogInfo($"初始化通讯口[{CommunicatorName}]失败,原因是{Communicator.Msg}", LogInfoType.ERROR);
                        }
                        else
                        {
                            LogInfo($"初始化通讯口[{CommunicatorName}]成功", LogInfoType.PASS);
                        }
                        GlobalVar.dicCommunicators.TryAdd(CommunicatorName, Communicator);
                    }
                    else
@@ -861,14 +1036,27 @@
                    string ClassName = item.Value.GetType().FullName;// "TCP"
                    string IP = item.Value.CommunicatorConnections["地址"].ToString();//"127.0.0.1"
                    string PORT = item.Value.CommunicatorConnections["端口"].ToString();//"1111"
                    if (string.IsNullOrEmpty(ClassName) || string.IsNullOrEmpty(IP) || string.IsNullOrEmpty(PORT))
                        break;
                    string CommunicatorConnectionString = $"({ClassName})[{IP}][{PORT}]";
                    GlobalVar.allCommunicatorsConnectionString.TryAdd(item.Key, CommunicatorConnectionString);
                    if (!string.IsNullOrEmpty(ClassName) && ClassName.Contains("SiemensLBS7"))
                    {
                        string CpuType = item.Value.CommunicatorConnections["型号"].ToString();
                        string PlcAddress = item.Value.CommunicatorConnections["变量地址"].ToString();
                        if (string.IsNullOrEmpty(ClassName) || string.IsNullOrEmpty(IP) || string.IsNullOrEmpty(PORT) || string.IsNullOrEmpty(CpuType) || string.IsNullOrEmpty(PlcAddress))
                        {
                            break;
                        }
                        string CommunicatorConnectionString = $"({ClassName})[{IP}][{PORT}][{CpuType}][{PlcAddress}]";
                        GlobalVar.allCommunicatorsConnectionString.TryAdd(item.Key, CommunicatorConnectionString);
                    }
                    else
                    {
                        if (string.IsNullOrEmpty(ClassName) || string.IsNullOrEmpty(IP) || string.IsNullOrEmpty(PORT))
                        {
                            break;
                        }
                        string CommunicatorConnectionString = $"({ClassName})[{IP}][{PORT}]";
                        GlobalVar.allCommunicatorsConnectionString.TryAdd(item.Key, CommunicatorConnectionString);
                    }
                }
                var settings = new JsonSerializerSettings
                {
                    Formatting = Formatting.Indented,
@@ -919,17 +1107,19 @@
                List<string> lstProcessName = JsonConvert.DeserializeObject<List<string>>(strJson);
                if (lstProcessName == null)
                {
                    return false;
                }
                // 使用方式
                var sortedKeys = lstProcessName
                    .OrderBy(k => k, new NaturalStringComparer())
                    .ToList();
                GlobalVar.dicProcesses.Clear();
                foreach (var ProcessName in sortedKeys)
                {
                    GlobalVar.dicProcesses.TryAdd(ProcessName
                        , new ProcessRunBll(ProcessName, GlobalVar.dicCameras, GlobalVar.dicCommunicators));
                }
                return true;
            }
            catch { return false; }
@@ -966,7 +1156,9 @@
                foreach (var process in GlobalVar.dicProcesses.Values)
                {
                    if (!process.Save(out string msg))
                    {
                        LogInfo($"流程[{process.Name}]保存失败,原因:{msg}", LogInfoType.NOSHOW);
                    }
                }
                try
@@ -1380,12 +1572,12 @@
                    {
                        string title = layout.Title;
                        string strImagePath = layout.SaveImageDir;
                        if (!AllProcessesPage.dicProcessControls.ContainsKey(title))
                        if (!AllProcessesPages.dicProcessControls.ContainsKey(title))
                        {
                            continue;
                        }
                        RunBll.GetImage(layout, out InputImage, out RecordImage);
                        AllProcessesPage.dicProcessControls[title].ShowHoImage(RecordImage);
                        AllProcessesPages.dicProcessControls[title].ShowHoImage(RecordImage);
                        if (!string.IsNullOrEmpty(layout.SaveImageDir))
                        {
                            string fileNameHead = layout.SaveImageHead;
@@ -1440,6 +1632,27 @@
                }
            });
        }
        private void VisionForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            SaveAllSetting();
            if (MessageBox.Show("是否关闭软件?", "提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning) != DialogResult.OK)//
            {
                e.Cancel = true;
                return;
            }
            //关闭窗体释放资源
            AsyncLogHelper.Dispose();
            foreach (BaseCamera camera in GlobalVar.dicCameras.Values)
            {
                camera.Dispose();
            }
            foreach (BaseCommunicator communicator in GlobalVar.dicCommunicators.Values)
            {
                communicator.Disconnect();
            }
            FormClosing -= VisionForm_FormClosing;
        }
    }
}