From 1afe548ac40cc7f975917ef9bf2e156595cf81f3 Mon Sep 17 00:00:00 2001
From: alielie <alielie@git.com>
Date: 星期四, 16 四月 2026 08:58:04 +0800
Subject: [PATCH] 优化;

---
 HMI.UI/Converter/DateStrConverter.cs                   |   29 
 Plugin.TestResult/SingleCell.xaml                      |   66 
 WPFProject/Controls/MainTabControl.xaml                |    2 
 HMI.LogViewer/LogViewer.xaml                           |   25 
 Plugin.TestResult/DataTable.xaml                       |   27 
 Plugin.TsSupport/TsExecutionWrapper.cs                 |  214 +
 Plugin.UICollection/TcpDebugger.xaml.cs                |  393 +++
 Plugin.TestResult/TestResultView.xaml.cs               |  170 
 Plugin.TsExecution/Plugin.TsExecution.csproj           |   21 
 Plugin.DataViewer/Plugin.DataViewer.csproj             |   26 
 Plugin.TestResult/Model/TcpServer.cs                   |   27 
 SimpleUtil/HMI.Simple.Util/ProductUtil.cs              |   89 
 Plugin.TsExecution/ScreenTouchWin.xaml.cs              |   67 
 WPFProject/Settings.ini                                |    6 
 HMI.ParameterManage/ParameterModal.xaml                |    4 
 WPFProject/PanuonWindow.xaml.cs                        |   73 
 Util.ManualDebug/StatusBtn.xaml                        |   75 
 Plugin.TestResult/Lixun/SNValidator.cs                 |   91 
 Plugin.TestResult/ViewControl.xaml.cs                  |  171 +
 OPCClient/OPCClient.csproj                             |    1 
 Plugin.TsExecution/ScreenGrid.xaml                     |   15 
 Util.ManualDebug/ManualDebug.xaml                      |  133 +
 WPFProject/App.config                                  |    2 
 Plugin.TsExecution/TsExecution.xaml.cs                 |  171 +
 Util.ManualDebug/StatusBtn.xaml.cs                     |   40 
 HMI.Core/Util/Sqlite3Helper.cs                         |   40 
 HMI.Core/Setting/ParamSetting.cs                       |   30 
 Plugin.TsExecution/ScreenTouch.xaml.cs                 |  156 +
 Util.ManualDebug/AssemblyInfo.cs                       |   10 
 HMI.ParameterManage/SettingModal.xaml                  |   17 
 Plugin.UICollection/AssemblyInfo.cs                    |   10 
 Plugin.TsSupport/ITestExecutionWrapper.cs              |   47 
 HMI.UI/Style/PanuonResource.xaml                       |   19 
 WPFProject/PanuonWindow.xaml                           |   45 
 Plugin.TsExecution/SingleControl.xaml                  |  143 
 WPFProject/WPFProject.sln                              |   70 
 HMI.UnitTest/App.xaml                                  |    2 
 Plugin.DataViewer/DataViewerControl.xaml.cs            |  800 ++++++
 HMI.Util/packages.config                               |    1 
 Plugin.DataViewer/DataViewerControl.xaml               |  480 +++
 HMI.Core/Properties/AssemblyInfo.cs                    |    4 
 HMI.Util/ExcelHelper.cs                                |   49 
 Plugin.TestResult/ManualDebugWindow.xaml.cs            |   95 
 HMI.LogViewer/LogViewer.xaml.cs                        |  142 
 HMI.StdControl/HMI.StdControl.csproj                   |    7 
 HMI.Core/Util/HexUtil.cs                               |   22 
 HMI.UI/Converter/ResultConverter.cs                    |   31 
 HMI.Util/Alarm/AlarmUtil.cs                            |    8 
 HMI.UI/Converter/RadioEnumConverter.cs                 |   19 
 Plugin.TestResult/packages.config                      |    1 
 CellStepSender/Properties/AssemblyInfo.cs              |    4 
 HMI.UI/MaskControl.xaml                                |    4 
 Plugin.TsExecution/Properties/AssemblyInfo.cs          |    4 
 WPFProject/App.xaml.cs                                 |    2 
 WPFProject/UI/ResourceUI.xaml                          |   63 
 Plugin.TestResult/SingleCell.xaml.cs                   |  230 +
 Plugin.TestResult/ViewControl.xaml                     |   26 
 Plugin.TestResult/DataTable.xaml.cs                    |  118 
 WPFProject/ResourceListener.cs                         |  135 +
 HMI.Core/FontSizeConverter.cs                          |    1 
 HMI.UI/HMI.UI.csproj                                   |   10 
 HMI.ParameterManage/ParameterManage.xaml.cs            |   32 
 Plugin.TestResult/Util/PNUtil.cs                       |   56 
 SimpleUtil/HMI.Simple.Util/Properties/AssemblyInfo.cs  |    5 
 WPFProject/Lang/zh-CN/app.json                         |    8 
 HMI.Core/Manager/ProductManager.cs                     |   24 
 WPFProject/Lang/zh-CN/TestResult.json                  |    2 
 Plugin.TestResult/ManualDebugWindow.xaml               |   21 
 CellStepSender/TcpClientTool3.cs                       |   81 
 HMI.UnitTest/MainWindow.xaml                           |   12 
 CellStepSender/StepSender.cs                           |    4 
 HMI.DataViewer/DataViewer.xaml.cs                      |   22 
 CellStepSender/CellStepSender.csproj                   |    1 
 Plugin.TsSupport/Plugin.TsSupport.csproj               |    3 
 HMI.UnitTest/MainWindow.xaml.cs                        |  105 
 HMI.ParameterManage/Model/XmlModel.cs                  |    6 
 HMI.UI/Style/MsgBoxStyle.xaml                          |   55 
 Plugin.TestResult/Scanner.xaml.cs                      |   52 
 Plugin.TsExecution/ScreenGrid.xaml.cs                  |   45 
 Plugin.TsExecution/ScreenTouch.xaml                    |   14 
 WPFProject/Lang/en-US/TestResult.json                  |    2 
 HMI.UI/RequirePasswordWindow.xaml                      |    1 
 HMI.Core/Entity/ParameterData.cs                       |    7 
 HMI.Core/Util/PathHelper.cs                            |   20 
 Plugin.UICollection/TcpDebugger.xaml                   |  176 +
 HMI.ParameterManage/SettingModal.xaml.cs               |  199 +
 WPFProject/WPFProject.csproj                           |   19 
 Util.ManualDebug/ManualDebug.xaml.cs                   |  467 +++
 HMI.AlermInfoManage/HMI.AlarmInfoManage.csproj         |    1 
 Plugin.UICollection/Plugin.UICollection.csproj         |   25 
 Plugin.DOControl/Plugin.DOControl.csproj               |    1 
 SimpleUtil/HMI.Simple.Util/ParameterUtil.cs            |   73 
 Plugin.TsExecution/ScreenTouchWin.xaml                 |   20 
 HMI.AlermInfoManage/AlarmInfoManage.xaml.cs            |   16 
 Util.ManualDebug/ManualDebugStyle.xaml                 |  130 +
 Plugin.TestResult/Plugin.TestResult.csproj             |   20 
 Plugin.TsExecution/SingleControl.xaml.cs               |  130 +
 HMI.ParameterManage/ParameterModal.xaml.cs             |    2 
 HMI.StdControl/StdSettingCheckBox.xaml.cs              |  155 +
 Plugin.CurrentSensorControl/BurnInSettingModal.xaml.cs |   12 
 Plugin.TestResult/InfoWindow.xaml                      |    4 
 Plugin.TsSupport/TeststandWrapper.cs                   |  228 +
 Util.ManualDebug/Util.ManualDebug.csproj               |   33 
 Plugin.DataViewer/CustomSortColumn.cs                  |  108 
 HMI.Util/HMI.Util.csproj                               |    5 
 WPFProject/Controls/MainTabControl.xaml.cs             |   24 
 Plugin.DataViewer/AssemblyInfo.cs                      |   12 
 /dev/null                                              |   15 
 Plugin.CurrentSensorControl/BurnInOven.xaml.cs         |    2 
 WPFProject/Properties/AssemblyInfo.cs                  |    2 
 SimpleUtil/HMI.Simple.Util/Parameter.cs                |    1 
 HMI.StdControl/StdSettingCheckBox.xaml                 |   14 
 HMI.UnitTest/HMI.UnitTest.csproj                       |   12 
 HMI.UI/RequirePasswordByUser.xaml                      |   33 
 HMI.UI/RequirePasswordByUser.xaml.cs                   |   62 
 WPFProject/Lang/en-US/app.json                         |   10 
 HMI.Util/IniFileHelper.cs                              |   32 
 WPFProject/UI/ResourceUI.xaml.cs                       |   91 
 118 files changed, 6,804 insertions(+), 691 deletions(-)

diff --git a/CellStepSender/CellStepSender.csproj b/CellStepSender/CellStepSender.csproj
index 7bb8947..351972d 100644
--- a/CellStepSender/CellStepSender.csproj
+++ b/CellStepSender/CellStepSender.csproj
@@ -43,6 +43,7 @@
   <ItemGroup>
     <Compile Include="StepSender.cs" />
     <Compile Include="TcpClientTool.cs" />
+    <Compile Include="TcpClientTool3.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
diff --git a/CellStepSender/Properties/AssemblyInfo.cs b/CellStepSender/Properties/AssemblyInfo.cs
index 136a4b8..00223c1 100644
--- a/CellStepSender/Properties/AssemblyInfo.cs
+++ b/CellStepSender/Properties/AssemblyInfo.cs
@@ -29,5 +29,5 @@
 //      鐢熸垚鍙�
 //      淇鍙�
 //
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: AssemblyVersion("1.0.0.1")]
+[assembly: AssemblyFileVersion("1.0.0.1")]
diff --git a/CellStepSender/StepSender.cs b/CellStepSender/StepSender.cs
index 6ca40d1..1914c0b 100644
--- a/CellStepSender/StepSender.cs
+++ b/CellStepSender/StepSender.cs
@@ -28,10 +28,10 @@
         {
 
             errorMsg = null;
-            TcpClientTool client = null;
+            TcpClientTool3 client = null;
             try
             {
-                client = new TcpClientTool(ip, port);
+                client = new TcpClientTool3(ip, port);
                 string res = null;
                 client.ExecuteMessageChanged += (sender, data) =>
                 {
diff --git a/CellStepSender/TcpClientTool3.cs b/CellStepSender/TcpClientTool3.cs
new file mode 100644
index 0000000..993c28b
--- /dev/null
+++ b/CellStepSender/TcpClientTool3.cs
@@ -0,0 +1,81 @@
+锘縰sing System;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace CellStepSender
+{
+    public class TcpClientTool3
+    {
+        private string _ip;
+        private int _port;
+        private bool _isConnected = false; // 鏄惁杩炴帴
+        private Socket _clientSocket; // 濂楁帴瀛�
+
+        public event EventHandler<string> ExecuteMessageChanged;
+
+        public TcpClientTool3(string ip, int port)
+        {
+            this._ip = ip;
+            this._port = port;
+        }
+
+        public void Connect()
+        {
+            try
+            {
+                _clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+                IPAddress ipaddress = IPAddress.Parse(this._ip);
+                IPEndPoint endpoint = new IPEndPoint(ipaddress, _port);
+                _clientSocket.Connect(endpoint);
+                _isConnected = true;
+            }
+            catch (Exception ex)
+            {
+                _clientSocket.Dispose();
+                _isConnected = false;
+                throw ex;
+            }
+        }
+
+        public void Disconnect()
+        {
+            _clientSocket.Dispose();
+        }
+
+        public void Send(string data)
+        {
+            byte[] arrClientSendMsg = Encoding.UTF8.GetBytes(data);
+            lock (this)
+            {
+                _clientSocket.Send(arrClientSendMsg);
+            }
+        }
+
+        public bool SendStep(
+            string ip, int port,
+            int ovenIndex, int cellIndex, string step, string value, string limitUp,
+            string limitDown, string unit, string result, string updateTime, out string errorMsg)
+        {
+
+            errorMsg = null;
+            try
+            {
+                if (!_isConnected)
+                {
+                    errorMsg = "Not Connected";
+                    return false;
+                }
+
+                string msg = $"{ovenIndex};{cellIndex};{step},{value},{limitUp},{limitDown},{unit},{result},{updateTime}\n";
+                Send(msg);
+                return true;
+            }
+            catch (Exception ex)
+            {
+                errorMsg = ex.Message;
+                return false;
+            }
+        }
+    }
+}
diff --git a/HMI.AlermInfoManage/AlarmInfoManage.xaml.cs b/HMI.AlermInfoManage/AlarmInfoManage.xaml.cs
index 19006db..02cb527 100644
--- a/HMI.AlermInfoManage/AlarmInfoManage.xaml.cs
+++ b/HMI.AlermInfoManage/AlarmInfoManage.xaml.cs
@@ -315,7 +315,14 @@
         {
             try
             {
-                AlarmTexts = XmlHelper.DeserializeFromXml<ObservableCollection<AlarmText>>(alarmTextPath);
+                if (string.IsNullOrEmpty(alarmTextPath) || !File.Exists(alarmTextPath))
+                {
+                    AlarmTexts = new ObservableCollection<AlarmText>();
+                }
+                else
+                {
+                    AlarmTexts = XmlHelper.DeserializeFromXml<ObservableCollection<AlarmText>>(alarmTextPath);
+                }
             }
             catch (Exception ex)
             {
@@ -323,8 +330,6 @@
                 AlarmTexts = new ObservableCollection<AlarmText>();
             }
         }
-
-        
 
         private void UserControl_Loaded(object sender, RoutedEventArgs e)
         {
@@ -339,6 +344,11 @@
             Loaded -= UserControl_Loaded;
         }
 
+        public override void DoClose(object sender, EventArgs e)
+        {
+            // AlarmUtil.OnAlarm -= AlarmUtil_OnAlarm;
+        }
+
         //private void MonitorTimer_Elapsed_bak(object sender, ElapsedEventArgs e)
         //{
         //    try
diff --git a/HMI.AlermInfoManage/HMI.AlarmInfoManage.csproj b/HMI.AlermInfoManage/HMI.AlarmInfoManage.csproj
index 767066e..360439f 100644
--- a/HMI.AlermInfoManage/HMI.AlarmInfoManage.csproj
+++ b/HMI.AlermInfoManage/HMI.AlarmInfoManage.csproj
@@ -150,7 +150,6 @@
       <Generator>ResXFileCodeGenerator</Generator>
       <LastGenOutput>Resources.Designer.cs</LastGenOutput>
     </EmbeddedResource>
-    <None Include="app.config" />
     <None Include="packages.config" />
     <None Include="Properties\Settings.settings">
       <Generator>SettingsSingleFileGenerator</Generator>
diff --git a/HMI.AlermInfoManage/app.config b/HMI.AlermInfoManage/app.config
deleted file mode 100644
index a0cefc2..0000000
--- a/HMI.AlermInfoManage/app.config
+++ /dev/null
@@ -1,11 +0,0 @@
-锘�<?xml version="1.0" encoding="utf-8"?>
-<configuration>
-  <runtime>
-    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
-      <dependentAssembly>
-        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
-        <bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" />
-      </dependentAssembly>
-    </assemblyBinding>
-  </runtime>
-</configuration>
\ No newline at end of file
diff --git a/HMI.Core/Entity/ParameterData.cs b/HMI.Core/Entity/ParameterData.cs
index 7c67c8d..a86f6c4 100644
--- a/HMI.Core/Entity/ParameterData.cs
+++ b/HMI.Core/Entity/ParameterData.cs
@@ -9,6 +9,13 @@
         public string Name { get; set; }
         [ExcelColumn("璁剧疆椤圭被鍨�")]
         public string Type { get; set; }
+
+        [ExcelColumn("鏄惁涓烘暟缁�")]
+        public bool IsArray { get; set; } = false;
+
+        [ExcelColumn("鏁扮粍闀垮害")]
+        public int Length { get; set; } = 1;
+
         [ExcelColumn("鍊�")]
         public string Value { get; set; }
         [ExcelColumn("鍗曚綅")]
diff --git a/HMI.Core/FontSizeConverter.cs b/HMI.Core/FontSizeConverter.cs
index ecadb25..2498c4c 100644
--- a/HMI.Core/FontSizeConverter.cs
+++ b/HMI.Core/FontSizeConverter.cs
@@ -8,6 +8,7 @@
     {
         public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
         {
+            /// "{size},{鍩哄噯鍊紏,{鏈�灏忓�紏,{鏈�澶у�紏"
             if (parameter == null) return 15;
 
             string[] ps = parameter.ToString().Split(',');
diff --git a/HMI.Core/Manager/ProductManager.cs b/HMI.Core/Manager/ProductManager.cs
index 94f7edf..96e10d6 100644
--- a/HMI.Core/Manager/ProductManager.cs
+++ b/HMI.Core/Manager/ProductManager.cs
@@ -1,4 +1,5 @@
 锘縰sing HMI.Core.Entity;
+using HMI.Core.Setting;
 using HMI.Core.Util;
 using System.Collections.Generic;
 using System.IO;
@@ -11,13 +12,32 @@
     /// </summary>
     public class ProductManager
     {
+        public static string GetFolder()
+        {
+            if (ParamSetting.ShareConfig != null)
+            {
+                return Path.Combine(ParamSetting.ShareConfig, "Product");
+            }
+            return Path.Combine(PathHelper.ConfigFolder, "Product");
+        }
+
+        public static string GetDataFilePath()
+        {
+            return Path.Combine(GetFolder(), "Product.data");
+        }
+
+        public static string GetParamFolderByProductName(string productName)
+        {
+            return Path.Combine(GetFolder(), "Parameter", $"Parameter_{productName}.data");
+        }
+
         /// <summary>
         /// 鑾峰彇淇濆瓨鐨勪骇鍝�
         /// </summary>
         /// <returns></returns>
         public static T GetProducts<T>() where T : IList<ProductData>
         {
-            string path = PathHelper.ProductDataFile;
+            string path = GetDataFilePath();
             if (!File.Exists(path)) return default(T);
             T datas = XmlHelper.DeserializeFromXml<T>(path);
             return datas;
@@ -29,7 +49,7 @@
         /// <returns></returns>
         public static T GetParametersByProductName<T>(string productName) where T : IList<ParameterData>
         {
-            string path = Path.Combine(PathHelper.ProductParameterFolder, $"Parameter_{productName}.data");
+            string path = GetParamFolderByProductName(productName);
             if (!File.Exists(path)) return default(T);
             T datas = XmlHelper.DeserializeFromXml<T>(path);
             return datas;
diff --git a/HMI.Core/Properties/AssemblyInfo.cs b/HMI.Core/Properties/AssemblyInfo.cs
index fa4d953..9b1611c 100644
--- a/HMI.Core/Properties/AssemblyInfo.cs
+++ b/HMI.Core/Properties/AssemblyInfo.cs
@@ -32,5 +32,5 @@
 //鍙互鎸囧畾鎵�鏈夎繖浜涘�硷紝涔熷彲浠ヤ娇鐢ㄢ�滅敓鎴愬彿鈥濆拰鈥滀慨璁㈠彿鈥濈殑榛樿鍊�
 //閫氳繃浣跨敤 "*"锛屽涓嬫墍绀�:
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: AssemblyVersion("1.0.0.1")]
+[assembly: AssemblyFileVersion("1.0.0.1")]
diff --git a/HMI.Core/Setting/ParamSetting.cs b/HMI.Core/Setting/ParamSetting.cs
index 974bc46..e01cc33 100644
--- a/HMI.Core/Setting/ParamSetting.cs
+++ b/HMI.Core/Setting/ParamSetting.cs
@@ -34,6 +34,9 @@
         /// 鍚敤鎵爜鎹㈠瀷
         /// </summary>
         public static bool EnableAutoModel { get; private set; } = false;
+        public static string ShareConfig { get; private set; } = null;
+        public static bool ShowBurnInAbort { get; private set; } = true;
+        public static string SocketNames { get; private set; } = string.Empty;
 
         static ParamSetting()
         {
@@ -148,6 +151,33 @@
             {
 
             }
+
+            try
+            {
+                ShareConfig = ParameterManager.GetCommonParameterByName<string>("ShareConfig", null);
+            }
+            catch
+            {
+
+            }
+
+            try
+            {
+                ShowBurnInAbort = ParameterManager.GetCommonParameterByName<bool>("ShowBurnInAbort", true);
+            }
+            catch
+            {
+
+            }
+
+            try
+            {
+                SocketNames = ParameterManager.GetCommonParameterByName<string>("SocketNames", string.Empty);
+            }
+            catch
+            {
+
+            }
         }
     }
 }
diff --git a/HMI.Core/Util/HexUtil.cs b/HMI.Core/Util/HexUtil.cs
index 546bd63..19a79b7 100644
--- a/HMI.Core/Util/HexUtil.cs
+++ b/HMI.Core/Util/HexUtil.cs
@@ -2,15 +2,15 @@
 
 namespace HMI.Core.Util
 {
-    public class HexUtil
-    {
-        public static byte[] ToBytesFromHexString(string hexString)
-        {
-            //浠� ' ' 鍒嗗壊瀛楃涓诧紝骞跺幓鎺夌┖瀛楃
-            string[] chars = hexString.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
-            byte[] returnBytes;
-            returnBytes = BitConverter.GetBytes(Convert.ToInt32(chars[0]));
-            return returnBytes;
-        }
-    }
+    //public class HexUtil
+    //{
+    //    public static byte[] ToBytesFromHexString(string hexString)
+    //    {
+    //        //浠� ' ' 鍒嗗壊瀛楃涓诧紝骞跺幓鎺夌┖瀛楃
+    //        string[] chars = hexString.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
+    //        byte[] returnBytes;
+    //        returnBytes = BitConverter.GetBytes(Convert.ToInt32(chars[0]));
+    //        return returnBytes;
+    //    }
+    //}
 }
diff --git a/HMI.Core/Util/PathHelper.cs b/HMI.Core/Util/PathHelper.cs
index 956c8bc..092287d 100644
--- a/HMI.Core/Util/PathHelper.cs
+++ b/HMI.Core/Util/PathHelper.cs
@@ -50,16 +50,8 @@
         /// </summary>
         public static string ParameterDataPath { get; private set; }
 
-        /// <summary>
-        /// 浜у搧鏁版嵁鐩綍
-        /// </summary>
-        public static string ProductDataFolder { get; private set; }
-        public static string ProductParameterFolder { get; private set; }
-
-        /// <summary>
-        /// 浜у搧鏁版嵁婧愭枃浠�
-        /// </summary>
-        public static string ProductDataFile { get; private set; }
+        
+       
 
         /// <summary>
         /// 娴嬭瘯椤规暟鎹洰褰�
@@ -94,11 +86,11 @@
             ParameterFolder = InitFolder(ConfigFolder + "\\Parameter");
             ParameterDataPath = ParameterFolder + "\\CommonParameter.data";
 
-            ProductDataFolder = InitFolder(ConfigFolder + "\\Product");
-            ProductParameterFolder = InitFolder(ProductDataFolder + "\\Parameter");
-            ProductDataFile = ProductDataFolder + "\\Product.data";
+            //ProductDataFolder = InitFolder(ConfigFolder + "\\Product");
+            //ProductParameterFolder = InitFolder(ProductDataFolder + "\\Parameter");
+            //ProductDataFile = ProductDataFolder + "\\Product.data";
 
-            TestItemDataFolder = InitFolder(ProductDataFolder + "\\TestItem");
+            // TestItemDataFolder = InitFolder(ProductDataFolder + "\\TestItem");
             TestItemDataFile = TestItemDataFolder + "\\TestItem.data";
 
             LogViewerDir = InitFolder(ConfigFolder + "\\LogViewer");
diff --git a/HMI.Core/Util/Sqlite3Helper.cs b/HMI.Core/Util/Sqlite3Helper.cs
index bc979d4..57fe091 100644
--- a/HMI.Core/Util/Sqlite3Helper.cs
+++ b/HMI.Core/Util/Sqlite3Helper.cs
@@ -52,6 +52,46 @@
             }
         }
 
+        public long InsertWithId(string Sql)
+        {
+            try
+            {
+                if (Sql.EndsWith(";"))
+                {
+                    Sql += "SELECT last_insert_rowid();";
+                }
+                else
+                {
+                    Sql += ";SELECT last_insert_rowid();";
+                }
+                using (var cmd = new SQLiteCommand(Sql, conn))
+                {
+                    return (long)cmd.ExecuteScalar();
+                }
+            }
+            catch (Exception ex)
+            {
+                string message = "ExecuteSql Error:" + ex.Message;
+                throw new Exception(message);
+            }
+        }
+
+        public object SelectOne(string Sql)
+        {
+            try
+            {
+                using (var cmd = new SQLiteCommand(Sql, conn))
+                {
+                    return cmd.ExecuteScalar();
+                }
+            }
+            catch (Exception ex)
+            {
+                string message = "ExecuteSql Error:" + ex.Message;
+                throw new Exception(message);
+            }
+        }
+
         public int ExecuteBatch(IEnumerable<string> sqls)
         {
             SQLiteTransaction tr = null;
diff --git a/HMI.DataViewer/DataViewer.xaml.cs b/HMI.DataViewer/DataViewer.xaml.cs
index 283734b..d704535 100644
--- a/HMI.DataViewer/DataViewer.xaml.cs
+++ b/HMI.DataViewer/DataViewer.xaml.cs
@@ -1,6 +1,7 @@
 锘縰sing HMI.Base;
 using HMI.Core.Util;
 using HMI.DataViewer.Entity;
+using HMI.UI.Converter;
 using HMI.UI.Util;
 using NPOI.SS.Formula.Functions;
 using NPOI.SS.UserModel;
@@ -19,22 +20,7 @@
 
 namespace HMI.DataViewer
 {
-    public class DefaultValueConverter : IValueConverter
-    {
-        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
-        {
-            if (value is DateTime date)
-            {
-                return date.ToString("yyyy-MM-dd HH:mm:ss");
-            }
-            return value.ToString();
-        }
-
-        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
-        {
-            return value;
-        }
-    }
+    
 
     /// <summary>
     /// UserControl1.xaml 鐨勪氦浜掗�昏緫
@@ -65,8 +51,6 @@
         }
 
         private Dictionary<string, DataBase_Setting> Map = new Dictionary<string, DataBase_Setting>();
-
-        private IValueConverter defaultConverter = new DefaultValueConverter();
 
         private void Button_Click(object sender, RoutedEventArgs e)
         {
@@ -285,7 +269,7 @@
                 Header = header,
                 Binding = new System.Windows.Data.Binding(bindingPath)
                 {
-                    Converter = defaultConverter
+                    Converter = DateStrConverter.Instance
                 },
                 Width = new DataGridLength(1, DataGridLengthUnitType.Star)
             };
diff --git a/HMI.LogViewer/LogViewer.xaml b/HMI.LogViewer/LogViewer.xaml
index 684a64e..004f847 100644
--- a/HMI.LogViewer/LogViewer.xaml
+++ b/HMI.LogViewer/LogViewer.xaml
@@ -71,7 +71,7 @@
                 <StackPanel DockPanel.Dock="Right" Style="{StaticResource ButtonGroup}">
                     <Button Style="{StaticResource QueryBtn}"
                             x:Name="queryBtn" Click="Button_Click"/>
-                    <Button Style="{StaticResource RfhBtn}"
+                    <Button Style="{StaticResource ResetBtn}"
                             x:Name="resetBtn" Click="Button_Click"/>
                     <!--<Button x:Name="exportBtn"
                             Style="{StaticResource ExportBtn}" Click="Button_Click"/>-->
@@ -88,14 +88,16 @@
                         <ComboBoxItem Content="Error"/>
                     </ComboBox>
 
-                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="{DynamicResource App.StartTime}"/>
-                    <pu:DateTimePicker Style="{StaticResource TimePicker}" x:Name="startTimeInput"/>
-
-                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="{DynamicResource App.EndTime}"/>
-                    <pu:DateTimePicker Style="{StaticResource TimePicker}" x:Name="endTimeInput"/>
-
                     <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="{DynamicResource log.source}"/>
                     <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="sourceInput" Width="180"/>
+
+                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="{DynamicResource App.StartTime}"/>
+                    <pu:DateTimePicker Style="{StaticResource TimePicker}" IsEnabled="{Binding AutoRfh,Converter={StaticResource reverseBool}}" 
+                                       x:Name="startTimeInput"/>
+
+                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="{DynamicResource App.EndTime}"/>
+                    <pu:DateTimePicker Style="{StaticResource TimePicker}" IsEnabled="{Binding AutoRfh,Converter={StaticResource reverseBool}}" 
+                                       x:Name="endTimeInput"/>
 
                     <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="{DynamicResource log.autoRefresh}"/>
                     <CheckBox x:Name="autoRfhInput" HorizontalAlignment="Center" VerticalAlignment="Center"
@@ -159,7 +161,7 @@
                             <GridViewColumn.CellTemplate>
                                 <DataTemplate>
                                     <Border Style="{StaticResource CellBorder}">
-                                        <TextBlock VerticalAlignment="Center" Text="{Binding time}"/>
+                                        <TextBlock VerticalAlignment="Center" Text="{Binding time,Converter={x:Static uic:DateStrConverter.Instance},ConverterParameter='yyyy-MM-dd HH:mm:ss.fff'}"/>
                                     </Border>
                                 </DataTemplate>
                             </GridViewColumn.CellTemplate>
@@ -177,6 +179,7 @@
                                 </DataTemplate>
                             </GridViewColumn.CellTemplate>
                         </GridViewColumn>
+
                         <GridViewColumn Width="1420">
                             <GridViewColumn.Header>
                                 <TextBlock Text="{DynamicResource log.text}"/>
@@ -194,6 +197,12 @@
                 </ListView.View>
             </ListView>
 
+            <StackPanel Grid.Row="3" HorizontalAlignment="Right">
+                <pu:Pagination Style="{StaticResource DefaultPage}" 
+                               CurrentPage="{Binding CurrentPage}" 
+                               MaxPage="{Binding MaxPage}"/>
+            </StackPanel>
+
             <!--<DataGrid Grid.Row="2" x:Name="table" 
                       CellStyle="{StaticResource sbCell}"
                   RowHeight="{StaticResource nanD}" Style="{StaticResource DefaultDataGrid}">
diff --git a/HMI.LogViewer/LogViewer.xaml.cs b/HMI.LogViewer/LogViewer.xaml.cs
index 3920fbb..a2fc8f4 100644
--- a/HMI.LogViewer/LogViewer.xaml.cs
+++ b/HMI.LogViewer/LogViewer.xaml.cs
@@ -33,23 +33,26 @@
 
         private Timer autoRfhTimer;
 
-        private int currentPage = 1;
-        public int CurrentPage
+        private long pageSize = 50;
+
+        private long currentPage = 1;
+        public long CurrentPage
         {
             get => currentPage;
             set
             {
                 Set(ref currentPage, value);
+                Filtrate(value, pageSize);
             }
         }
 
-        private int maxPage = 0;
-        public int MaxPage
+        private long maxPage = 1;
+        public long MaxPage
         {
             get => maxPage;
             set
             {
-                Set(ref currentPage, value);
+                Set(ref maxPage, value);
             }
         }
 
@@ -65,6 +68,7 @@
             };
             autoRfhTimer.Elapsed += AutoRfhTimer_Elapsed;
             InitializeComponent();
+            QueryReset();
         }
 
         public override void InitFinish()
@@ -93,7 +97,6 @@
             //}
         }
 
-
         private void UserControl_Loaded(object sender, RoutedEventArgs e)
         {
             Loaded -= UserControl_Loaded;
@@ -103,6 +106,10 @@
         {
             Refresh100items();
         }
+
+        //private void RefreshTable() { 
+        //    if(CurrentPage == )
+        //}
 
         private void Button_Click(object sender, RoutedEventArgs e)
         {
@@ -114,6 +121,7 @@
                     break;
                 case "resetBtn":
                     QueryReset();
+                    CurrentPage = 1;
                     break;
                 case "exportBtn":
                     DoExport();
@@ -144,9 +152,10 @@
                     return;
                 }
 
-                queryBtn.IsEnabled = false;
-                autoRfhInput.IsEnabled = false;
-                Filtrate();
+                AutoRfh = false;
+                //queryBtn.IsEnabled = false;
+                //autoRfhInput.IsEnabled = false;
+                CurrentPage = 1;
             }
             catch (Exception ex)
             {
@@ -162,31 +171,60 @@
 
         private void QueryReset()
         {
+            var today = DateTime.Now;
+            var start = DateTime.Parse(today.ToString("yyyy-MM-dd 00:00:00")).AddDays(-7);
+            var end = DateTime.Parse(today.ToString("yyyy-MM-dd 00:00:00")).AddDays(1);
+
             levelInput.Text = null;
-            startTimeInput.SelectedDateTime = null;
-            endTimeInput.SelectedDateTime = null;
-            startTimeInput.Text = null;
-            table.ItemsSource = null;
+            startTimeInput.SelectedDateTime = start;
+            endTimeInput.SelectedDateTime = end;
         }
 
-        private void Filtrate()
+        private void Filtrate(long page, long size)
         {
-            string str = LoadSql();
-            DataTable dt = new DataTable();
-            var files = GetOldDbFiles(PathHelper.LogViewerDir, new DateTime?[] { startTimeInput.SelectedDateTime,
+            try
+            {
+                var files = GetOldDbFiles(PathHelper.LogViewerDir, new DateTime?[] { startTimeInput.SelectedDateTime,
                 endTimeInput.SelectedDateTime });
-            foreach (string file in files)
-            {
-                using (SqliteHelper sql = new SqliteHelper(file))
+                if (files.Length <= 0)
                 {
-                    dt.Merge(sql.GetDataTable(str));
+                    table.ItemsSource = new DataTable().DefaultView;
+                    return;
                 }
+
+                using (Sqlite3Helper helper = new Sqlite3Helper(files[0]))
+                {
+                    for (int i = 0; i < files.Length; i++)
+                    {
+                        var attach = $"ATTACH DATABASE '{files[i]}' AS 'db{i}'";
+                        helper.Execute(attach);
+                    }
+
+                    string sql = string.Join(" UNION ALL ", GetSqls(files));
+
+                    string sortName = "time", sortDirection = "DESC";
+
+                    string baseSql = $"SELECT level,time,source,text FROM ({sql}) as _temp";
+
+                    string countSql = $"SELECT COUNT(1) FROM ({sql}) as __temp";
+
+                    long total = (long)helper.SelectOne(countSql);
+                    long pages = (total/pageSize)+ ((total % pageSize) > 0 ? 1 : 0);
+
+                    sql = $"{baseSql} ORDER BY {sortName} {sortDirection} LIMIT {size} OFFSET {(page - 1) * size} ";
+
+                    DataTable dt = helper.GetDataTable(sql);
+                    table.ItemsSource = dt.DefaultView;
+
+                    MaxPage = pages;
+                }
+
             }
-            if (dt.Rows.Count > 0)
+            catch (Exception ex)
             {
-                dt.DefaultView.Sort = "time Desc";
+                log.Error("Select Log Error.", ex);
+                MsgBoxHelper2.Error($"鏌ヨ澶辫触: {ex.Message}");
             }
-            table.ItemsSource = dt.DefaultView;
         }
 
         public static string[] GetOldDbFiles(string folderPath, DateTime?[] range)
@@ -217,35 +255,40 @@
         }
 
         /// <summary>
-        /// 鍔犺浇鐣岄潰鍒ゆ柇鏁版嵁搴撹鍙�
+        /// 
         /// </summary>
-        /// <returns> str鏁版嵁搴撶殑璇彞 </returns>
-        private string LoadSql()
+        /// <param name="files"></param>
+        /// <returns></returns>
+        private string[] GetSqls(string[] files)
         {
-            string sql = " select level,time,source,text from log";
-            List<string> datas = new List<string>();
-            if (!string.IsNullOrEmpty(levelInput.Text))
+            string[] sqls = new string[files.Length];
+            for (int i = 0; i < files.Length; i++)
             {
-                datas.Add($"level='{levelInput.Text}'");
-            }
-            if (!string.IsNullOrEmpty(startTimeInput.Text))
-            {
-                datas.Add($"time>='{startTimeInput.Text}'");
-            }
-            if (!string.IsNullOrEmpty(endTimeInput.Text))
-            {
-                datas.Add($"time<='{endTimeInput.Text}'");
-            }
-            if (!string.IsNullOrEmpty(sourceInput.Text))
-            {
-                datas.Add($"source='{sourceInput.Text}'");
-            }
+                string sql = $"SELECT level,time,source,text FROM db{i}.log";
+                List<string> datas = new List<string>();
+                if (!string.IsNullOrEmpty(levelInput.Text))
+                {
+                    datas.Add($"level='{levelInput.Text}'");
+                }
+                if (!string.IsNullOrEmpty(startTimeInput.Text))
+                {
+                    datas.Add($"time>='{startTimeInput.Text}'");
+                }
+                if (!string.IsNullOrEmpty(endTimeInput.Text))
+                {
+                    datas.Add($"time<'{endTimeInput.Text}'");
+                }
+                if (!string.IsNullOrEmpty(sourceInput.Text))
+                {
+                    datas.Add($"source='{sourceInput.Text}'");
+                }
 
-            string condition = string.Join(" AND ", datas);
-            condition = string.IsNullOrEmpty(condition) ? string.Empty : $" WHERE {condition}";
-
-            sql = $"{sql}{condition} order by time desc";
-            return sql;
+                string condition = string.Join(" AND ", datas);
+                condition = string.IsNullOrEmpty(condition) ? string.Empty : $" WHERE {condition}";
+                sql = $"{sql}{condition}";
+                sqls[i] = sql;
+            }
+            return sqls;
         }
 
         /// <summary>
@@ -267,6 +310,7 @@
                         this.Dispatcher.BeginInvoke((Action)delegate ()
                         {
                             table.ItemsSource = dt.DefaultView;
+                            MaxPage = 1;
                         });
                     }
                 }
diff --git a/HMI.ParameterManage/Model/XmlModel.cs b/HMI.ParameterManage/Model/XmlModel.cs
index da86c39..92210e2 100644
--- a/HMI.ParameterManage/Model/XmlModel.cs
+++ b/HMI.ParameterManage/Model/XmlModel.cs
@@ -191,7 +191,7 @@
         /// <param name="datas"></param>
         private void SaveProduct(IList<ProductData> datas)
         {
-            XmlHelper.SerializeToXml(PathHelper.ProductDataFile, datas);
+            XmlHelper.SerializeToXml(ProductManager.GetDataFilePath(), datas);
         }
         #endregion
 
@@ -257,13 +257,13 @@
         /// <param name="datas"></param>
         private void SaveParameter(IList<ParameterData> datas)
         {
-            string fileName = Path.Combine(PathHelper.ProductDataFolder, "Parameter", $"Parameter_{SelectedProduct.Name}.data");
+            string fileName = ProductManager.GetParamFolderByProductName(SelectedProduct.Name);
             XmlHelper.SerializeToXml(fileName, datas);
         }
 
         private void SaveParameter(string product, IList<ParameterData> datas)
         {
-            string fileName = Path.Combine(PathHelper.ProductDataFolder, "Parameter", $"Parameter_{product}.data");
+            string fileName = ProductManager.GetParamFolderByProductName(product);
             XmlHelper.SerializeToXml(fileName, datas);
         }
         #endregion
diff --git a/HMI.ParameterManage/ParameterManage.xaml.cs b/HMI.ParameterManage/ParameterManage.xaml.cs
index 73685f9..23e543f 100644
--- a/HMI.ParameterManage/ParameterManage.xaml.cs
+++ b/HMI.ParameterManage/ParameterManage.xaml.cs
@@ -1,11 +1,16 @@
 锘縰sing HMI.Base;
 using HMI.Core.Entity;
+using HMI.Core.Manager;
 using HMI.Core.Util;
 using HMI.ParameterManage.Model;
 using HMI.UI.Util;
+using NPOI.POIFS.NIO;
+using NPOI.SS.Formula.Functions;
 using System.Linq;
+using System.Text.RegularExpressions;
 using System.Windows;
 using System.Windows.Controls;
+using System.Windows.Markup;
 
 namespace HMI.ParameterManage
 {
@@ -97,13 +102,34 @@
                 ToastHelper.Warning(Window.GetWindow(this), this.lang("Pm.PleaseSelectProduct"));
                 return;
             }
-            var win = new ParameterModal(Model)
+            //var win = new ParameterModal(Model)
+            //{
+            //    Title = data == null ? this.lang("Pm.Parameter.Add") : this.lang("Pm.Parameter.Edit"),
+            //    Owner = Window.GetWindow(this),
+            //};
+            //if (win.Open(Model.SelectedProduct.Name, data ?? new ParameterData { ProductName = Model.SelectedProduct.Name }, Model.groups) == true)
+            //{
+            //    Model.LoadParameters();
+            //}
+
+            SettingModal modal = new SettingModal()
             {
                 Title = data == null ? this.lang("Pm.Parameter.Add") : this.lang("Pm.Parameter.Edit"),
-                Owner = Window.GetWindow(this),
+                Owner = Window.GetWindow(this)
             };
-            if (win.Open(Model.SelectedProduct.Name, data ?? new ParameterData { ProductName = Model.SelectedProduct.Name }, Model.groups) == true)
+            var target = data ?? new ParameterData();
+            if (modal.Open(target, Model.Parameters, Model.groups, Model.SelectedProduct.Name) == true)
             {
+                //if (index == -1)
+                //{
+                //    Datasource.Add(modal.Data);
+                //    ParameterManager.SaveCommonParameter(Datasource);
+                //}
+                //else
+                //{
+                //    ParameterManager.SaveCommonParameter(Datasource);
+                //}
+                Model.SaveOrUpdateParameter(target);
                 Model.LoadParameters();
             }
         }
diff --git a/HMI.ParameterManage/ParameterModal.xaml b/HMI.ParameterManage/ParameterModal.xaml
index c93342f..2fb7f11 100644
--- a/HMI.ParameterManage/ParameterModal.xaml
+++ b/HMI.ParameterManage/ParameterModal.xaml
@@ -46,6 +46,10 @@
                 </ComboBox>
             </pu:FormGroup>
 
+            <pu:FormGroup x:Name="FgIsArray" Header="IsArray" dp:FormGroupHelper.IsRequired="True" GroupName="MyFormGroup">
+                <CheckBox x:Name="CbIsArray" />
+            </pu:FormGroup>
+
             <pu:FormGroup x:Name="FgValue" Style="{StaticResource RequiredInputFormGroupStyle}" 
                           Header="{DynamicResource Pm.Parameter.Value}" 
 dp:FormGroupHelper.IsRequired="True" GroupName="MyFormGroup">
diff --git a/HMI.ParameterManage/ParameterModal.xaml.cs b/HMI.ParameterManage/ParameterModal.xaml.cs
index d7553d4..c89f0ea 100644
--- a/HMI.ParameterManage/ParameterModal.xaml.cs
+++ b/HMI.ParameterManage/ParameterModal.xaml.cs
@@ -81,6 +81,7 @@
             {
                 Data.Name = TbName.Text;
                 Data.Type = CbType.Text;
+                Data.IsArray = CbIsArray.IsChecked == true;
                 Data.Value = GetTargetValue();
                 Data.Unit = TbUnit.Text;
                 Data.Description = TbDescription.Text;
@@ -95,6 +96,7 @@
             Data = data;
             TbName.Text = Data.Name;
             CbType.SelectedValue = Data.Type;
+            CbIsArray.IsChecked = Data.IsArray;
             CbGroup.ItemsSource = groups;
             UpdateWindow(Data.Type);
             UpdateForm(Data);
diff --git a/HMI.ParameterManage/SettingModal.xaml b/HMI.ParameterManage/SettingModal.xaml
index 37370f6..1078321 100644
--- a/HMI.ParameterManage/SettingModal.xaml
+++ b/HMI.ParameterManage/SettingModal.xaml
@@ -19,6 +19,13 @@
     <Grid Margin="10">
         <StackPanel x:Name="formPanel">
 
+            <pu:FormGroup x:Name="FgProductName"  Visibility="Collapsed"
+              Header="{DynamicResource Pm.Product.Name}" 
+              dp:FormGroupHelper.IsRequired="True" 
+              GroupName="MyFormGroup">
+                <TextBox x:Name="TbProductName" IsReadOnly="True" Style="{StaticResource EditorTextBoxStyle}" />
+            </pu:FormGroup>
+
             <pu:FormGroup x:Name="FgName"
                           dp:FormGroupHelper.ValidateMethod="ValidateName"
                           Header="{DynamicResource Pm.Parameter.Name}" 
@@ -37,6 +44,14 @@
                     <ComboBoxItem>Bool</ComboBoxItem>
                     <ComboBoxItem>String</ComboBoxItem>
                 </ComboBox>
+            </pu:FormGroup>
+
+            <pu:FormGroup x:Name="FgIsArray" Header="IsArray" dp:FormGroupHelper.IsRequired="True" GroupName="MyFormGroup">
+                <CheckBox x:Name="CbIsArray" Checked="CbIsArray_Checked" Unchecked="CbIsArray_Checked"/>
+            </pu:FormGroup>
+
+            <pu:FormGroup x:Name="FgLength" Visibility="Collapsed" Header="Length" dp:FormGroupHelper.IsRequired="True" GroupName="MyFormGroup">
+                <ui:NumberInput x:Name="NiLength" TextChanged="NiLength_TextChanged" Style="{StaticResource EditorTextBoxStyle}"/>
             </pu:FormGroup>
 
             <pu:FormGroup x:Name="FgValue" Style="{StaticResource RequiredInputFormGroupStyle}" 
@@ -58,6 +73,8 @@
                         <TextBox x:Name="TbStringValue" Style="{StaticResource EditorTextBoxStyle}"/>
                         <Button Grid.Column="1" Click="Button_Click" Style="{StaticResource SearchButton}"/>
                     </Grid>
+                    <DataGrid Visibility="Collapsed" 
+                              x:Name="arrayInput" MinHeight="100" MaxHeight="200" CanUserAddRows="False" CanUserDeleteRows="False"></DataGrid>
                 </Grid>
             </pu:FormGroup>
 
diff --git a/HMI.ParameterManage/SettingModal.xaml.cs b/HMI.ParameterManage/SettingModal.xaml.cs
index 308485e..22569d6 100644
--- a/HMI.ParameterManage/SettingModal.xaml.cs
+++ b/HMI.ParameterManage/SettingModal.xaml.cs
@@ -3,17 +3,62 @@
 using HMI.UI.DpUtil;
 using HMI.UI.Util;
 using Microsoft.Win32;
+using NPOI.SS.Formula.Functions;
+using NPOI.SS.Formula.PTG;
 using Panuon.WPF.UI;
 using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.Linq;
-using System.Text.RegularExpressions;
 using System.Windows.Controls;
 
 namespace HMI.ParameterManage
 {
+    public class BoolData
+    {
+        public int Index { get; }
+        public bool Value { get; set; }
+
+        public BoolData(int index)
+        {
+            this.Index = index;
+        }
+    }
+
+    public class IntData
+    {
+        public int Index { get; }
+        public int Value { get; set; }
+
+        public IntData(int index)
+        {
+            this.Index = index;
+        }
+    }
+
+    public class DoubleData
+    {
+        public int Index { get; }
+        public double Value { get; set; }
+
+        public DoubleData(int index)
+        {
+            this.Index = index;
+        }
+    }
+
+    public class StringData
+    {
+        public int Index { get; }
+        public string Value { get; set; } = string.Empty;
+
+        public StringData(int index)
+        {
+            this.Index = index;
+        }
+    }
+
     /// <summary>
     /// ModalWindow.xaml 鐨勪氦浜掗�昏緫
     /// </summary>
@@ -93,6 +138,9 @@
             {
                 Data.Name = TbName.Text;
                 Data.Type = CbType.Text;
+                Data.IsArray = CbIsArray.IsChecked == true;
+                Data.Length = int.Parse(NiLength.Text);
+
                 Data.Value = GetTargetValue();
                 Data.Unit = TbUnit.Text;
                 Data.Description = TbDescription.Text;
@@ -102,14 +150,23 @@
             return flag;
         }
 
-        public bool? Open(ParameterData data, ObservableCollection<ParameterData> datasource, List<string> groups)
+        public bool? Open(ParameterData data, ObservableCollection<ParameterData> datasource, List<string> groups,string productName = null)
         {
+            if (!string.IsNullOrEmpty(productName))
+            {
+                FgProductName.Visibility = System.Windows.Visibility.Visible;
+                TbProductName.Text = productName;
+            }
+
             Datasource = datasource;
             Data = data;
             TbGroup.ItemsSource = groups;
 
             TbName.Text = Data.Name;
             CbType.SelectedValue = Data.Type;
+            CbIsArray.IsChecked = Data.IsArray;
+            NiLength.Text = Data.Length.ToString();
+
             UpdateWindow(Data.Type);
             UpdateForm(Data);
 
@@ -123,6 +180,12 @@
         private string GetTargetValue()
         {
             var type = CbType.Text;
+            if (CbIsArray.IsChecked == true)
+            {
+                var arr = (object[])arrayInput.ItemsSource;
+                return string.Join(",", arr.Select(e => e.GetType().GetProperty("Value").GetValue(e).ToString()).ToArray());
+            }
+
             if (type == "Int" || type == "Double")
             {
                 return NiNumberValue.Text;
@@ -139,17 +202,25 @@
 
         private void UpdateForm(ParameterData data)
         {
-            if (Data.Type == "Int" || Data.Type == "Int")
+            if (data.IsArray)
             {
-                NiNumberValue.Text = Data.Value;
-            }
-            else if (Data.Type == "Bool")
-            {
-                CbBoolValue.SelectedValue = Data.Value;
+                string[] arr = data.Value.Split(',');
+                arrayInput.ItemsSource = RefreshArray(arr, Data.Type);
             }
             else
             {
-                TbStringValue.Text = Data.Value;
+                if (Data.Type == "Int" || Data.Type == "Int")
+                {
+                    NiNumberValue.Text = Data.Value;
+                }
+                else if (Data.Type == "Bool")
+                {
+                    CbBoolValue.SelectedValue = Data.Value;
+                }
+                else
+                {
+                    TbStringValue.Text = Data.Value;
+                }
             }
         }
 
@@ -183,6 +254,10 @@
                 val = ((ComboBoxItem)e.AddedItems[0]).Content.ToString();
             }
             UpdateWindow(val);
+            if (CbIsArray.IsChecked == true)
+            {
+                arrayInput.ItemsSource = RefreshArray(GetLength(), val);
+            }
         }
 
         private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
@@ -193,5 +268,111 @@
                 TbStringValue.Text = ofd.FileName;
             }
         }
+
+        private void CbIsArray_Checked(object sender, System.Windows.RoutedEventArgs e)
+        {
+            bool isChecked = ((CheckBox)sender).IsChecked == true;
+            if (isChecked)
+            {
+                FgLength.Visibility = System.Windows.Visibility.Visible;
+                arrayInput.Visibility = System.Windows.Visibility.Visible;
+                arrayInput.ItemsSource = RefreshArray(GetLength(), CbType.Text);
+            }
+            else
+            {
+                FgLength.Visibility = System.Windows.Visibility.Collapsed;
+                arrayInput.Visibility = System.Windows.Visibility.Collapsed;
+            }
+        }
+
+        private object[] RefreshArray(int length, string type)
+        {
+            var arr = new object[length];
+            for (int i = 0; i < length; i++)
+            {
+                if (type == "Int")
+                {
+                    arr[i] = new IntData(i);
+                }
+                else if (type == "Double")
+                {
+                    arr[i] = new DoubleData(i);
+                }
+                else if (type == "Bool")
+                {
+                    arr[i] = new BoolData(i);
+                }
+                else
+                {
+                    arr[i] = new StringData(i);
+                }
+            }
+            return arr;
+        }
+
+        private object[] RefreshArray(string[] source, string type)
+        {
+            var arr = new object[source.Length];
+            type = string.IsNullOrEmpty(type) ? "String" : type;
+            for (int i = 0; i < arr.Length; i++)
+            {
+                if (type == "Int")
+                {
+                    arr[i] = new IntData(i)
+                    {
+                        Value = int.Parse(source[i])
+                    };
+                }
+                else if (type == "Double")
+                {
+                    arr[i] = new DoubleData(i)
+                    {
+                        Value = double.Parse(source[i])
+                    };
+                }
+                else if (type == "Bool")
+                {
+                    arr[i] = new BoolData(i)
+                    {
+                        Value = bool.Parse(source[i])
+                    };
+                }
+                else
+                {
+                    arr[i] = new StringData(i)
+                    {
+                        Value = source[i]
+                    };
+                }
+            }
+            return arr;
+        }
+
+        private int GetLength()
+        {
+            try
+            {
+                string text = NiLength.Text;
+                if (text.Contains("."))
+                {
+                    text = text.Split('.')[0];
+                }
+                int length = int.Parse(text);
+                return length > 0 ? length : 1;
+            }
+            catch
+            {
+                return 1;
+            }
+        }
+
+        private void NiLength_TextChanged(object sender, TextChangedEventArgs e)
+        {
+            if (CbIsArray.IsChecked != true)
+            {
+                return;
+            }
+            arrayInput.ItemsSource = RefreshArray(GetLength(), CbType.Text);
+        }
     }
 }
diff --git a/HMI.StdControl/HMI.StdControl.csproj b/HMI.StdControl/HMI.StdControl.csproj
index a9e7344..d30adf5 100644
--- a/HMI.StdControl/HMI.StdControl.csproj
+++ b/HMI.StdControl/HMI.StdControl.csproj
@@ -46,12 +46,19 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="StdSettingCheckBox.xaml.cs">
+      <DependentUpon>StdSettingCheckBox.xaml</DependentUpon>
+    </Compile>
     <Compile Include="StdEnableMes.xaml.cs">
       <DependentUpon>StdEnableMes.xaml</DependentUpon>
     </Compile>
     <Compile Include="StdUIEventArg.cs" />
   </ItemGroup>
   <ItemGroup>
+    <Page Include="StdSettingCheckBox.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
     <Page Include="StdEnableMes.xaml">
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
diff --git a/HMI.StdControl/StdSettingCheckBox.xaml b/HMI.StdControl/StdSettingCheckBox.xaml
new file mode 100644
index 0000000..ddda536
--- /dev/null
+++ b/HMI.StdControl/StdSettingCheckBox.xaml
@@ -0,0 +1,14 @@
+锘�<UserControl x:Class="HMI.StdControl.StdSettingCheckBox"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:HMI.StdControl"
+             mc:Ignorable="d" 
+             x:Name="self"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <CheckBox x:Name="CbEnableMes" IsChecked="{Binding IsChecked,ElementName=self,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" 
+              VerticalContentAlignment="Center"
+              IsEnabled="{Binding CbEnabled,ElementName=self}"
+              Content="{Binding Text,ElementName=self}" />
+</UserControl>
diff --git a/HMI.StdControl/StdSettingCheckBox.xaml.cs b/HMI.StdControl/StdSettingCheckBox.xaml.cs
new file mode 100644
index 0000000..9450c9c
--- /dev/null
+++ b/HMI.StdControl/StdSettingCheckBox.xaml.cs
@@ -0,0 +1,155 @@
+锘縰sing HMI.Core.Manager;
+using System;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace HMI.StdControl
+{
+    /// <summary>
+    /// StdEnableMes.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class StdSettingCheckBox : UserControl, INotifyPropertyChanged
+    {
+
+        #region ...
+        public virtual event PropertyChangedEventHandler PropertyChanged;
+
+        protected virtual void Set([CallerMemberName] string name = null)
+        {
+
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+        }
+
+        protected void Set<T>(ref T old, T @new, [CallerMemberName] string propertyName = null)
+        {
+            old = @new;
+            if (propertyName != null)
+            {
+                Set(propertyName);
+            }
+        }
+        #endregion
+
+        private event EventHandler<StdUIEventArg> valueChanged;
+        public event EventHandler<StdUIEventArg> ValueChanged
+        {
+            add
+            {
+                valueChanged += value;
+            }
+            remove
+            {
+                valueChanged -= value;
+            }
+        }
+
+        public static DependencyProperty IsCheckedProperty = DependencyProperty.Register(
+                            "IsChecked",
+                            typeof(bool),
+                            typeof(StdSettingCheckBox),
+                            new FrameworkPropertyMetadata(false));
+
+        public static DependencyProperty ValueProperty = DependencyProperty.Register(
+                            "Value",
+                            typeof(string),
+                            typeof(StdSettingCheckBox),
+                            new FrameworkPropertyMetadata(null));
+
+        public static DependencyProperty CbEnabledProperty = DependencyProperty.Register(
+                            "CbEnabled",
+                            typeof(bool),
+                            typeof(StdSettingCheckBox),
+                            new FrameworkPropertyMetadata(true));
+
+        public static DependencyProperty TextProperty = DependencyProperty.Register(
+                            "Text",
+                            typeof(string),
+                            typeof(StdSettingCheckBox),
+                            new FrameworkPropertyMetadata(null));
+
+
+        public bool CbEnabled
+        {
+            get => (bool)GetValue(CbEnabledProperty);
+            set
+            {
+                SetValue(CbEnabledProperty, value);
+            }
+        }
+
+        public string Value
+        {
+            get => (string)GetValue(ValueProperty);
+            set
+            {
+                SetValue(ValueProperty, value);
+            }
+        }
+
+        public string Text
+        {
+            get => (string)GetValue(TextProperty);
+            set
+            {
+                SetValue(TextProperty, value);
+            }
+        }
+
+        public bool IsChecked
+        {
+            get => (bool)GetValue(IsCheckedProperty);
+            set
+            {
+                if (WriteConfig(Value, value))
+                {
+                    SetValue(IsCheckedProperty, value);
+                }
+            }
+        }
+
+        public StdSettingCheckBox()
+        {
+            InitializeComponent();
+            this.Loaded += StdSettingCheckBox_Loaded;
+        }
+
+        private void StdSettingCheckBox_Loaded(object sender, RoutedEventArgs e)
+        {
+            try
+            {
+                var value = ParameterManager.GetCommonParameterByName(Value);
+                if (value == null)
+                {
+                    this.Visibility = System.Windows.Visibility.Collapsed;
+                }
+                else
+                {
+                    IsChecked = bool.Parse(value.Value);
+                }
+            }
+            catch
+            {
+                IsChecked = false;
+            }
+        }
+
+        private bool WriteConfig(string name, bool value)
+        {
+            try
+            {
+                valueChanged?.Invoke(this, new StdUIEventArg()
+                {
+                    StringValue = name,
+                    BoolValue = value
+                });
+                return ParameterManager.UpdateCommonParameter(name, value, out string errMsg);
+            }
+            catch (Exception ex)
+            {
+                return false;
+            }
+        }
+    }
+}
diff --git a/HMI.UI/Converter/DateStrConverter.cs b/HMI.UI/Converter/DateStrConverter.cs
new file mode 100644
index 0000000..3be6f7d
--- /dev/null
+++ b/HMI.UI/Converter/DateStrConverter.cs
@@ -0,0 +1,29 @@
+锘縰sing System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace HMI.UI.Converter
+{
+
+    public class DateStrConverter : IValueConverter
+    {
+        public static DateStrConverter Instance { get; } = new DateStrConverter();
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value is DateTime date)
+            {
+                if (parameter != null)
+                {
+                    return date.ToString(parameter.ToString());
+                }
+                return date.ToString("yyyy-MM-dd HH:mm:ss");
+            }
+            return value.ToString();
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return DateTime.Parse(value.ToString());
+        }
+    }
+}
diff --git a/HMI.UI/Converter/RadioEnumConverter.cs b/HMI.UI/Converter/RadioEnumConverter.cs
new file mode 100644
index 0000000..d003850
--- /dev/null
+++ b/HMI.UI/Converter/RadioEnumConverter.cs
@@ -0,0 +1,19 @@
+锘縰sing System;
+using System.Windows.Data;
+
+namespace HMI.UI.Converter
+{
+    public class RadioEnumConverter : IValueConverter
+    {
+        public static RadioEnumConverter Instance { get; } = new RadioEnumConverter();
+        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+        {
+            return value?.Equals(parameter);
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+        {
+            return value?.Equals(true) == true ? parameter : Binding.DoNothing;
+        }
+    }
+}
diff --git a/HMI.UI/Converter/ResultConverter.cs b/HMI.UI/Converter/ResultConverter.cs
new file mode 100644
index 0000000..901b410
--- /dev/null
+++ b/HMI.UI/Converter/ResultConverter.cs
@@ -0,0 +1,31 @@
+锘縰sing System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace HMI.UI.Converter
+{
+    public class ResultConverter : IValueConverter
+    {
+        public static ResultConverter Instance = new ResultConverter();
+
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value == null) return value;
+            var str = value.ToString();
+            if (str.Equals("ok", StringComparison.OrdinalIgnoreCase) || str.Equals("pass", StringComparison.OrdinalIgnoreCase))
+            {
+                return "PASS";
+            }
+            if (str.Equals("ng", StringComparison.OrdinalIgnoreCase) || str.Equals("fail", StringComparison.OrdinalIgnoreCase))
+            {
+                return "FAIL";
+            }
+            return value;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return value;
+        }
+    }
+}
diff --git a/HMI.UI/HMI.UI.csproj b/HMI.UI/HMI.UI.csproj
index bef777c..0530193 100644
--- a/HMI.UI/HMI.UI.csproj
+++ b/HMI.UI/HMI.UI.csproj
@@ -98,6 +98,9 @@
     <Compile Include="Controls\treegrid\TreeItemConverter.cs" />
     <Compile Include="Controls\treegrid\TreeNodeExtend.cs" />
     <Compile Include="Controls\VisualHost.cs" />
+    <Compile Include="Converter\DateStrConverter.cs" />
+    <Compile Include="Converter\RadioEnumConverter.cs" />
+    <Compile Include="Converter\ResultConverter.cs" />
     <Compile Include="Converter\ReverseBoolConverter.cs" />
     <Compile Include="Converter\IndentConverter.cs" />
     <Compile Include="Converter\CollectionHasItem.cs" />
@@ -135,6 +138,9 @@
     <Compile Include="MDI\Workspace.xaml.cs">
       <DependentUpon>Workspace.xaml</DependentUpon>
     </Compile>
+    <Compile Include="RequirePasswordByUser.xaml.cs">
+      <DependentUpon>RequirePasswordByUser.xaml</DependentUpon>
+    </Compile>
     <Compile Include="RequirePasswordWindow.xaml.cs">
       <DependentUpon>RequirePasswordWindow.xaml</DependentUpon>
     </Compile>
@@ -152,6 +158,10 @@
     <Compile Include="Util\MsgBoxHelper.cs" />
     <Compile Include="Util\MsgBoxHelper2.cs" />
     <Compile Include="Util\ToastHelper.cs" />
+    <Page Include="RequirePasswordByUser.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
     <Page Include="RequirePasswordWindow.xaml">
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
diff --git a/HMI.UI/MaskControl.xaml b/HMI.UI/MaskControl.xaml
index b44ebf4..4f603ef 100644
--- a/HMI.UI/MaskControl.xaml
+++ b/HMI.UI/MaskControl.xaml
@@ -21,7 +21,7 @@
                     </Grid.ColumnDefinitions>
                     <Border Padding="10,5,0,5" Background="{DynamicResource PrimaryColor}"
                    CornerRadius="5,5,0,0">
-                        <Label Padding="2" BorderThickness="0" Margin="0" Foreground="#FFF" x:Name="titleLabel" Content="璇风◢鍚�"/>
+                        <Label Padding="2" Background="Transparent" BorderThickness="0" Margin="0" Foreground="#FFF" x:Name="titleLabel" Content="璇风◢鍚�"/>
                     </Border>
                     <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center"
                                 VerticalAlignment="Center"
@@ -48,7 +48,7 @@
                                 </EventTrigger>
                             </TextBlock.Triggers>
                         </TextBlock>
-                        <TextBlock Grid.Row="1" Margin="20,0,0,0" Grid.Column="1" x:Name="contentLabel" 
+                        <TextBlock Grid.Row="1" Margin="20,0,0,0" Grid.Column="1" Background="Transparent" x:Name="contentLabel" 
                                    VerticalAlignment="Center" TextWrapping="Wrap"/>
                     </StackPanel>                    
                 </Grid>
diff --git a/HMI.UI/RequirePasswordByUser.xaml b/HMI.UI/RequirePasswordByUser.xaml
new file mode 100644
index 0000000..8f0a0e4
--- /dev/null
+++ b/HMI.UI/RequirePasswordByUser.xaml
@@ -0,0 +1,33 @@
+锘�<pu:WindowX x:Class="HMI.Base.RequirePasswordByUser"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        xmlns:local="clr-namespace:HMI.Base"
+        mc:Ignorable="d"
+        Style="{StaticResource ModalWindow}"
+        xmlns:pu="https://opensource.panuon.com/wpf-ui"
+        xmlns:dp="clr-namespace:HMI.UI.DpUtil"
+        Loaded="WindowX_Loaded"
+        FontSize="14"
+        WindowStartupLocation="CenterOwner"
+        Height="200"
+        pu:WindowXModalDialog.OKButtonContent="纭畾"
+        Title="璇疯緭鍏ュ瘑鐮�" Width="500">
+    <Grid dp:GridHelper.Space="10">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+        </Grid.RowDefinitions>
+        <pu:FormGroup Header="褰撳墠鐢ㄦ埛" Orientation="Horizontal" Style="{StaticResource InputFormGroupStyle}">
+            <TextBlock Text="{Binding Username}"/>
+        </pu:FormGroup>
+        <pu:FormGroup Grid.Row="1" Header="瀵嗙爜" Orientation="Horizontal" 
+                      Style="{StaticResource RequiredInputFormGroupStyle}">
+            <PasswordBox x:Name="PwdPassword" VerticalContentAlignment="Center"
+             Style="{StaticResource SignInPasswordBoxStyle}" 
+             pu:PasswordBoxHelper.Watermark="{DynamicResource App.Password}"/>
+        </pu:FormGroup>
+    </Grid>
+</pu:WindowX>
diff --git a/HMI.UI/RequirePasswordByUser.xaml.cs b/HMI.UI/RequirePasswordByUser.xaml.cs
new file mode 100644
index 0000000..598cad0
--- /dev/null
+++ b/HMI.UI/RequirePasswordByUser.xaml.cs
@@ -0,0 +1,62 @@
+锘縰sing HMI.Core.Entity;
+using HMI.Core.Util;
+using HMI.UI.Util;
+using Panuon.WPF.UI;
+using System.ComponentModel;
+
+namespace HMI.Base
+{
+    /// <summary>
+    /// RequirePasswordWindow.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class RequirePasswordByUser : WindowX
+    {
+        public RequirePasswordByUser(string username)
+        {
+            this.Username = username;
+            this.DataContext = this;
+            InitializeComponent();
+        }
+
+        private string username;
+        public string Username
+        {
+            get => username;
+            set
+            {
+                Set(ref username, value);
+            }
+        }
+
+        protected override void OnClosing(CancelEventArgs e)
+        {
+            //Press Continue Button
+            if (DialogResult == true)
+            {
+                try
+                {
+                    using (var helper = new Sqlite3Helper(PathHelper.DatabasePath))
+                    {
+                        if (!helper.HasData($"SELECT 1 FROM user WHERE username = '{Username}' AND password = '{PwdPassword.Password}';"))
+                        {
+                            ToastHelper.Error(this, "瀵嗙爜閿欒");
+                            e.Cancel = true;
+                        }
+                    }
+                }
+                catch (System.Exception ex)
+                {
+                    ToastHelper.Error(this, $"楠岃瘉鐢ㄦ埛淇℃伅澶辫触: {ex.Message}");
+                    e.Cancel = true;
+                }
+            }
+            base.OnClosing(e);
+        }
+
+        private void WindowX_Loaded(object sender, System.Windows.RoutedEventArgs e)
+        {
+            Username = UserInfo.User.Username;
+            PwdPassword.Password = string.Empty;
+        }
+    }
+}
diff --git a/HMI.UI/RequirePasswordWindow.xaml b/HMI.UI/RequirePasswordWindow.xaml
index 89656c1..4cd14fa 100644
--- a/HMI.UI/RequirePasswordWindow.xaml
+++ b/HMI.UI/RequirePasswordWindow.xaml
@@ -11,6 +11,7 @@
         Loaded="WindowX_Loaded"
         FontSize="14"
         Height="200"
+        WindowStartupLocation="CenterOwner"
         pu:WindowXModalDialog.OKButtonContent="纭畾"
         Title="璇疯緭鍏ュ瘑鐮�" Width="500">
     <Grid dp:GridHelper.Space="10">
diff --git a/HMI.UI/Style/MsgBoxStyle.xaml b/HMI.UI/Style/MsgBoxStyle.xaml
index 1f577a1..39c0a68 100644
--- a/HMI.UI/Style/MsgBoxStyle.xaml
+++ b/HMI.UI/Style/MsgBoxStyle.xaml
@@ -3,11 +3,10 @@
                         xmlns:pu="https://opensource.panuon.com/wpf-ui"
                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:puconfig="clr-namespace:Panuon.WPF.UI.Configurations;assembly=Panuon.WPF.UI">
-
-    <pu:MessageBoxXSettings x:Key="MsgBoxWarn" 
-                                 InteropOwnersMask="False"
-                                 InverseButtonsSequence="False">
-        <pu:MessageBoxXSettings.WindowXStyle>
+    <pu:MessageBoxXSetting x:Key="MsgBoxWarn" 
+         InteropOwnersMask="False"
+         InverseButtonsSequence="False">
+        <pu:MessageBoxXSetting.WindowXStyle>
             <Style TargetType="pu:WindowX" BasedOn="{StaticResource {x:Static pu:MessageBoxX.WindowXStyleKey}}">
                 <Setter Property="pu:WindowXCaption.Background" Value="{DynamicResource WarningColor}" />
                 <Setter Property="pu:WindowXCaption.Foreground" Value="{DynamicResource WarningColorContract}" />
@@ -15,9 +14,9 @@
                 <Setter Property="Height" Value="200" />
                 <Setter Property="SizeToContent" Value="Manual" />
             </Style>
-        </pu:MessageBoxXSettings.WindowXStyle>
+        </pu:MessageBoxXSetting.WindowXStyle>
 
-        <pu:MessageBoxXSettings.ButtonStyle>
+        <pu:MessageBoxXSetting.ButtonStyle>
             <Style TargetType="Button" BasedOn="{StaticResource {x:Static pu:MessageBoxX.ButtonStyleKey}}">
 
                 <Style.Triggers>
@@ -33,20 +32,20 @@
                         <Setter Property="ContentTemplate">
                             <Setter.Value>
                                 <DataTemplate>
-                                    <TextBlock Text="{Binding}" TextDecorations="Underline" />
+                                    <TextBlock Text="{DynamicResource App.Cancel}" TextDecorations="Underline" />
                                 </DataTemplate>
                             </Setter.Value>
                         </Setter>
                     </Trigger>
                 </Style.Triggers>
             </Style>
-        </pu:MessageBoxXSettings.ButtonStyle>
-    </pu:MessageBoxXSettings>
+        </pu:MessageBoxXSetting.ButtonStyle>
+    </pu:MessageBoxXSetting>
 
-    <pu:MessageBoxXSettings x:Key="MsgBoxError" 
-                             InteropOwnersMask="False"
-                             InverseButtonsSequence="False">
-        <pu:MessageBoxXSettings.WindowXStyle>
+    <pu:MessageBoxXSetting x:Key="MsgBoxError" 
+     InteropOwnersMask="False"
+     InverseButtonsSequence="False">
+        <pu:MessageBoxXSetting.WindowXStyle>
             <Style TargetType="pu:WindowX" BasedOn="{StaticResource {x:Static pu:MessageBoxX.WindowXStyleKey}}">
                 <Setter Property="pu:WindowXCaption.Background" Value="{DynamicResource DangerColor}" />
                 <Setter Property="pu:WindowXCaption.Foreground" Value="{DynamicResource DangerColorContrast}" />
@@ -54,9 +53,9 @@
                 <Setter Property="Height" Value="200" />
                 <Setter Property="SizeToContent" Value="Manual" />
             </Style>
-        </pu:MessageBoxXSettings.WindowXStyle>
+        </pu:MessageBoxXSetting.WindowXStyle>
 
-        <pu:MessageBoxXSettings.ButtonStyle>
+        <pu:MessageBoxXSetting.ButtonStyle>
             <Style TargetType="Button" BasedOn="{StaticResource {x:Static pu:MessageBoxX.ButtonStyleKey}}">
 
                 <Style.Triggers>
@@ -72,20 +71,20 @@
                         <Setter Property="ContentTemplate">
                             <Setter.Value>
                                 <DataTemplate>
-                                    <TextBlock Text="{Binding}" TextDecorations="Underline" />
+                                    <TextBlock Text="{DynamicResource App.Cancel}" TextDecorations="Underline" />
                                 </DataTemplate>
                             </Setter.Value>
                         </Setter>
                     </Trigger>
                 </Style.Triggers>
             </Style>
-        </pu:MessageBoxXSettings.ButtonStyle>
-    </pu:MessageBoxXSettings>
+        </pu:MessageBoxXSetting.ButtonStyle>
+    </pu:MessageBoxXSetting>
 
-    <pu:MessageBoxXSettings x:Key="MsgBoxInfo" 
-                             InteropOwnersMask="False"
-                             InverseButtonsSequence="False">
-        <pu:MessageBoxXSettings.WindowXStyle>
+    <pu:MessageBoxXSetting x:Key="MsgBoxInfo" 
+     InteropOwnersMask="False"
+     InverseButtonsSequence="False">
+        <pu:MessageBoxXSetting.WindowXStyle>
             <Style TargetType="pu:WindowX" BasedOn="{StaticResource {x:Static pu:MessageBoxX.WindowXStyleKey}}">
                 <Setter Property="pu:WindowXCaption.Background" Value="{DynamicResource PrimaryColor}" />
                 <Setter Property="pu:WindowXCaption.Foreground" Value="{DynamicResource PrimaryColorContract}" />
@@ -93,9 +92,9 @@
                 <Setter Property="Height" Value="200" />
                 <Setter Property="SizeToContent" Value="Manual" />
             </Style>
-        </pu:MessageBoxXSettings.WindowXStyle>
+        </pu:MessageBoxXSetting.WindowXStyle>
 
-        <pu:MessageBoxXSettings.ButtonStyle>
+        <pu:MessageBoxXSetting.ButtonStyle>
             <Style TargetType="Button" BasedOn="{StaticResource {x:Static pu:MessageBoxX.ButtonStyleKey}}">
 
                 <Style.Triggers>
@@ -111,15 +110,15 @@
                         <Setter Property="ContentTemplate">
                             <Setter.Value>
                                 <DataTemplate>
-                                    <TextBlock Text="{Binding}" TextDecorations="Underline" />
+                                    <TextBlock Text="{DynamicResource App.Cancel}" TextDecorations="Underline" />
                                 </DataTemplate>
                             </Setter.Value>
                         </Setter>
                     </Trigger>
                 </Style.Triggers>
             </Style>
-        </pu:MessageBoxXSettings.ButtonStyle>
-    </pu:MessageBoxXSettings>
+        </pu:MessageBoxXSetting.ButtonStyle>
+    </pu:MessageBoxXSetting>
 
     <pu:ToastSetting x:Key="toastSuccess" Spacing="25">
         <pu:ToastSetting.LabelStyle>
diff --git a/HMI.UI/Style/PanuonResource.xaml b/HMI.UI/Style/PanuonResource.xaml
index 9c5b052..bccebeb 100644
--- a/HMI.UI/Style/PanuonResource.xaml
+++ b/HMI.UI/Style/PanuonResource.xaml
@@ -225,6 +225,7 @@
         <Setter Property="pu:WindowXModalDialog.OKButtonContent" 
                 Value="{DynamicResource App.Save}"/>
         <Setter Property="pu:WindowXModalDialog.ButtonPanelMargin" Value="0,30,10,10"/>
+        <Setter Property="InteropOwnersMask" Value="False"/>
     </Style>
 
     <Style x:Key="DefaultListBox" TargetType="ListBox" BasedOn="{StaticResource {x:Static pu:StyleKeys.ListBoxStyle}}">
@@ -319,4 +320,22 @@
             </Setter.Value>
         </Setter>
     </Style>
+
+    <Style x:Key="DefaultPageItem" TargetType="pu:PaginationItem">
+        <Setter Property="Cursor" Value="Hand" />
+        <Setter Property="MinWidth" Value="30" />
+        <Setter Property="Width" Value="Auto" />
+        <Setter Property="Width" Value="Auto" />
+    </Style>
+
+    <Style x:Key="DefaultPage" TargetType="pu:Pagination">
+        <Setter Property="Height" Value="30"/>
+        <Setter Property="HorizontalAlignment" Value="Right"/>
+        <Setter Property="Foreground" Value="Black" />
+        <Setter Property="ItemsBackground" Value="Transparent"/>
+        <Setter Property="ItemsCornerRadius" Value="12"/>
+        <Setter Property="ItemsSelectedBackground" Value="{DynamicResource SecondPrimaryColor}"/>
+        <Setter Property="ItemContainerStyle" Value="{StaticResource DefaultPageItem}"/>
+    </Style>
+   
 </ResourceDictionary>
\ No newline at end of file
diff --git a/HMI.UnitTest/App.xaml b/HMI.UnitTest/App.xaml
index a3b7d0f..ad0c1c0 100644
--- a/HMI.UnitTest/App.xaml
+++ b/HMI.UnitTest/App.xaml
@@ -16,7 +16,7 @@
                 <ResourceDictionary Source="/HMI.UI;component/Style/PanuonResource.xaml"/>
                 <ResourceDictionary Source="/HMI.UI;component/Style/MsgBoxStyle.xaml"/>
             </ResourceDictionary.MergedDictionaries>
-            <Style TargetType="{x:Type pu:FormGroup}" BasedOn="{StaticResource InputFormGroupStyle}"></Style>
+            <!--<Style TargetType="{x:Type pu:FormGroup}" BasedOn="{StaticResource InputFormGroupStyle}"></Style>-->
             <uic:PathImageConverter x:Key="path2Image"/>
             <uic:DataGridRowNumberConverter x:Key="rowNumber"/>
             <uic:VisibilityBoolConverter x:Key="vbConverter"/>
diff --git a/HMI.UnitTest/HMI.UnitTest.csproj b/HMI.UnitTest/HMI.UnitTest.csproj
index 01946e0..148a599 100644
--- a/HMI.UnitTest/HMI.UnitTest.csproj
+++ b/HMI.UnitTest/HMI.UnitTest.csproj
@@ -131,6 +131,10 @@
     <None Include="App.config" />
   </ItemGroup>
   <ItemGroup>
+    <ProjectReference Include="..\..\UILib\UILib.csproj">
+      <Project>{27325fe0-3ce2-4cd5-8eb5-7e45cc441479}</Project>
+      <Name>UILib</Name>
+    </ProjectReference>
     <ProjectReference Include="..\ConnectionUtil\ConnectionUtil.csproj">
       <Project>{d0806fa6-3f6d-4304-81b8-ead9da7cb5b2}</Project>
       <Name>ConnectionUtil</Name>
@@ -167,10 +171,18 @@
       <Project>{202d9a7b-96b3-4ad4-9977-fc4263207a02}</Project>
       <Name>Plugin.TsExecution</Name>
     </ProjectReference>
+    <ProjectReference Include="..\Plugin.UICollection\Plugin.UICollection.csproj">
+      <Project>{22dc8d91-50b2-47ca-86a8-29a6da48f388}</Project>
+      <Name>Plugin.UICollection</Name>
+    </ProjectReference>
     <ProjectReference Include="..\SimpleUtil\HMI.Simple.Util\HMI.Simple.Util.csproj">
       <Project>{cf5bebe9-2803-49c3-b773-73d082e0984a}</Project>
       <Name>HMI.Simple.Util</Name>
     </ProjectReference>
+    <ProjectReference Include="..\Util.ManualDebug\Util.ManualDebug.csproj">
+      <Project>{d0944e62-ee52-4abe-832a-a09d98814db1}</Project>
+      <Name>Util.ManualDebug</Name>
+    </ProjectReference>
   </ItemGroup>
   <ItemGroup>
     <Folder Include="Entity\" />
diff --git a/HMI.UnitTest/MainWindow.xaml b/HMI.UnitTest/MainWindow.xaml
index 801296c..adb3447 100644
--- a/HMI.UnitTest/MainWindow.xaml
+++ b/HMI.UnitTest/MainWindow.xaml
@@ -13,19 +13,15 @@
     <Grid x:Name="page">
         <Grid.RowDefinitions>
             <RowDefinition Height="Auto"/>
-            <RowDefinition Height="Auto"/>
-            <RowDefinition Height="Auto"/>
-            <RowDefinition Height="Auto"/>
-            <RowDefinition Height="Auto"/>
-            <RowDefinition Height="Auto"/>
-            <RowDefinition/>
+            <RowDefinition />
         </Grid.RowDefinitions>
         <Button Click="Button_Click">娴嬭瘯</Button>
         <!--<Button Click="Button_Click2" Grid.Row="1">鍙戦��</Button>
         <Button Click="Button_Click3" Grid.Row="2">鍙戦�乀CP</Button>
         <Button Click="Button_Click4" Grid.Row="4">鍙戦�丏ata</Button>-->
-        <TextBox x:Name="input" Height="0" Grid.Row="3"></TextBox>
+        <!--<TextBox x:Name="input" Height="0" Grid.Row="3"></TextBox>
         <TextBlock Grid.Row="5" Height="0" x:Name="console"></TextBlock>
-        <Border Grid.Row="6" x:Name="border"></Border>
+        <Border Grid.Row="6" x:Name="border"></Border>-->
+        <Border Grid.Row="1" x:Name="border2"></Border>
     </Grid>
 </pu:WindowX>
diff --git a/HMI.UnitTest/MainWindow.xaml.cs b/HMI.UnitTest/MainWindow.xaml.cs
index 2c18c54..4dc0549 100644
--- a/HMI.UnitTest/MainWindow.xaml.cs
+++ b/HMI.UnitTest/MainWindow.xaml.cs
@@ -3,16 +3,15 @@
 using HMI.Core.Util;
 using HMI.Simple.Util;
 using log4net;
-using ModBusTcp;
 using Panuon.WPF.UI;
 using Plugin.DataBoard;
 using Plugin.TestResult_WPF;
 using Plugin.TsExecution;
+using Plugin.UICollection;
 using System;
 using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
 using System.Windows;
+using Util.ManualDebug;
 namespace UnitTest
 {
     /// <summary>
@@ -27,22 +26,9 @@
             LangUtil.Load();
             InitializeComponent();
             //RebuildDb();
+            border2.Child = new TcpDebugger();
 
-            ModBusTcpController controller = new ModBusTcpController("127.0.0.1", 502);
-            if (controller.Start(out msg)) {
-                bool res;
-                int? target;
-
-                controller.WriteBool("1", true, out msg);
-                controller.ReadDOBool("1", out res, out msg);
-                Console.WriteLine(res);
-                controller.ReadDOInt("1", out target, out msg);
-                Console.WriteLine(target); 
-                controller.WriteInt("1", 1, out msg);
-                controller.ReadDOInt("1", out target, out msg);
-                Console.WriteLine(target);
-                Console.WriteLine(target);
-            }
+           
 
 
             //page.Children.Add(new UserControl1());
@@ -142,6 +128,12 @@
 
         private void Button_Click(object sender, RoutedEventArgs e)
         {
+            var win = new ManualDebug()
+            {
+                seqPath = "C:\\Users\\chrry\\Desktop\\鎵嬪姩璋冭瘯绀轰緥.seq"
+            };
+
+            win.Show();
             //Task.Run(() =>
             //{
             //    for (int j = 0; j < 1000; j++)
@@ -198,7 +190,7 @@
             //});
 
             //tsExecution.UpdatePositionStatus(0, "Passed");
-            tsExecution.UpdatePositionStatus(0, "Failed");
+            //tsExecution.UpdatePositionStatus(0, "Failed");
             //tsExecution.UpdatePositionProcessValue(0, 12);
             //tsExecution.PausePosition(0);
             //tsExecution.SetPositionBarcode(0, "123456789");
@@ -231,73 +223,6 @@
             Console.WriteLine($"{e.Data}");
         }
 
-        private void Client_OnData(object sender, PipeDataEventArgs e)
-        {
-            Dispatcher.Invoke(() =>
-            {
-                console.Text += e.Data;
-            });
-        }
-
-        private void Button_Click2(object sender, RoutedEventArgs e)
-        {
-            var allStart = DateTime.Now;
-            for (int i = 0; i < 100; i++)
-            {
-                var start = DateTime.Now;
-                if (client.SendMessage("ping", out string res))
-                {
-                    var interval = (DateTime.Now - start).TotalMilliseconds;
-                    Console.WriteLine($"{interval}: {res}");
-                }
-            }
-            Console.WriteLine($"Total: {(DateTime.Now - allStart).TotalMilliseconds}");
-        }
-
-        private void Button_Click3(object sender, RoutedEventArgs e)
-        {
-            try
-            {
-                var allStart = DateTime.Now;
-                for (int i = 0; i < 500; i++)
-                {
-                    var start = DateTime.Now;
-                    string data = input.Text;
-                    string str = $"{{\"command\":5,\"value\":{data},\"id\":\"DITag0\"}}\r\n";
-                    string res = tcpClient.SendMessage(str);
-                    var interval = (DateTime.Now - start).TotalMilliseconds;
-                    Console.WriteLine($"{interval}: {res}");
-                }
-                Console.WriteLine($"Total: {(DateTime.Now - allStart).TotalMilliseconds}");
-            }
-            catch (Exception ex)
-            {
-
-            }
-        }
-
-        private void Button_Click4(object sender, RoutedEventArgs e)
-        {
-            try
-            {
-                var allStart = DateTime.Now;
-                for (int i = 0; i < 500; i++)
-                {
-                    var start = DateTime.Now;
-                    string data = input.Text;
-                    string str = $"{{\"command\":5,\"value\":{data},\"id\":\"DITag0\"}}";
-                    client.SendMessage(str, out string res);
-                    var interval = (DateTime.Now - start).TotalMilliseconds;
-                    Console.WriteLine($"{interval}: {res}");
-                }
-                Console.WriteLine($"Total: {(DateTime.Now - allStart).TotalMilliseconds}");
-
-            }
-            catch (Exception ex)
-            {
-                Console.WriteLine(ex.StackTrace);
-            }
-        }
 
         private void HandlePlugin(object obj)
         {
@@ -318,10 +243,10 @@
 
         private void Window_Loaded(object sender, RoutedEventArgs e)
         {
-            var plugin = new TsExecution();
-            HandlePlugin(plugin);
-            border.Child = plugin;
-            tsExecution = plugin;
+            //var plugin = new TsExecution();
+            //HandlePlugin(plugin);
+            //border.Child = plugin;
+            //tsExecution = plugin;
             this.Loaded -= Window_Loaded;
         }
     }
diff --git a/HMI.Util/Alarm/AlarmUtil.cs b/HMI.Util/Alarm/AlarmUtil.cs
index f846bd6..1044e1e 100644
--- a/HMI.Util/Alarm/AlarmUtil.cs
+++ b/HMI.Util/Alarm/AlarmUtil.cs
@@ -2,6 +2,7 @@
 using HMI.Core.Entity;
 using HMI.Core.Setting;
 using HMI.Core.Util;
+using log4net;
 using S7Driver;
 using System;
 using System.Collections.Generic;
@@ -18,8 +19,11 @@
 
         public event EventHandler<LoadMsg> LoadMessageAppend;
 
+        private static ILog log = LogManager.GetLogger("AlarmHelper");
+
         internal AlarmHelper()
         {
+
         }
 
         public ObservableCollection<AlarmSetting> BlockSettings { get; private set; }
@@ -178,7 +182,7 @@
             }
             catch (Exception ex)
             {
-                //log.Error("Read OPCServer Data Error.", ex);
+                log.Error("Read PLC Alarm Data Error.", ex);
             }
             finally
             {
@@ -279,7 +283,7 @@
             }
             catch (Exception ex)
             {
-                //log.Error("PcTimer Execute Failed", ex);
+                log.Error("Read MSMQ Alarm Data Failed", ex);
             }
             finally
             {
diff --git a/HMI.Util/ExcelHelper.cs b/HMI.Util/ExcelHelper.cs
index 9e795dc..0708a5c 100644
--- a/HMI.Util/ExcelHelper.cs
+++ b/HMI.Util/ExcelHelper.cs
@@ -1,10 +1,12 @@
 锘縰sing HMI.Core;
 using HMI.Core.Util;
 using NPOI.HSSF.UserModel;
+using NPOI.SS.Formula.Functions;
 using NPOI.SS.UserModel;
 using NPOI.XSSF.UserModel;
 using System;
 using System.Collections.Generic;
+using System.Data;
 using System.IO;
 using System.Linq;
 using System.Reflection;
@@ -55,6 +57,53 @@
             }
         }
 
+        public static void ExportExcel<T>(string targetPath, DataTable table)
+        {
+            Type type = typeof(T);
+            var properties = PropertyHelper.GetAtterProperty<T, ExcelColumnAttribute>(out var attrs);
+            if (properties == null || properties.Count <= 0)
+            {
+                throw new Exception("No Attribute");
+            }
+            IWorkbook workbook = null;
+            if (targetPath.EndsWith(".xlsx"))
+            {
+                workbook = new XSSFWorkbook();
+            }
+            else if (targetPath.EndsWith(".xls"))
+            {
+                workbook = new HSSFWorkbook();
+            }
+            workbook.CreateSheet("sheet1");////鏂板缓涓�涓伐浣滆〃
+            ISheet SheetOne = workbook.GetSheet("Sheet1");
+            IRow row1 = SheetOne.CreateRow(0);
+            for (int i = 0; i < properties.Count; i++)
+            {
+                row1.CreateCell(i).SetCellValue(LangUtil.Get((attrs[i]).Name));
+            }
+
+            for (int i = 0; i < table.Rows.Count; i++)
+            {
+                IRow rowTemp = SheetOne.CreateRow(i + 1);
+                for (int j = 0; j < properties.Count; j++)
+                {
+                    var attr = attrs[j];
+                    object obj = attr.IgnoreExport ? "" : table.Rows[i][properties[j].Name];
+                    string value = obj?.ToString();
+                    if (obj is DateTime time)
+                    {
+                        value = time.ToString("yyyy-MM-dd HH:mm:ss");
+                    }
+                    rowTemp.CreateCell(j).SetCellValue(value);
+                }
+            }
+
+            using (FileStream fs = new FileStream(targetPath, FileMode.OpenOrCreate))
+            {
+                workbook.Write(fs);
+            }
+        }
+
 
         public static List<T> ReadFromExcel<T>(string sourcePath)
         {
diff --git a/HMI.Util/HMI.Util.csproj b/HMI.Util/HMI.Util.csproj
index 7488068..fc1f1c8 100644
--- a/HMI.Util/HMI.Util.csproj
+++ b/HMI.Util/HMI.Util.csproj
@@ -52,6 +52,9 @@
     <Reference Include="ICSharpCode.SharpZipLib, Version=1.0.0.999, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
       <HintPath>..\WPFProject\packages\SharpZipLib.1.0.0\lib\net45\ICSharpCode.SharpZipLib.dll</HintPath>
     </Reference>
+    <Reference Include="log4net, Version=3.0.4.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\log4net.3.0.4\lib\net462\log4net.dll</HintPath>
+    </Reference>
     <Reference Include="NPOI, Version=2.4.1.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1, processorArchitecture=MSIL">
       <HintPath>..\WPFProject\packages\NPOI.2.4.1\lib\net45\NPOI.dll</HintPath>
     </Reference>
@@ -65,7 +68,9 @@
       <HintPath>..\WPFProject\packages\NPOI.2.4.1\lib\net45\NPOI.OpenXmlFormats.dll</HintPath>
     </Reference>
     <Reference Include="System" />
+    <Reference Include="System.Configuration" />
     <Reference Include="System.Core" />
+    <Reference Include="System.Web" />
     <Reference Include="System.Xml.Linq" />
     <Reference Include="System.Data.DataSetExtensions" />
     <Reference Include="Microsoft.CSharp" />
diff --git a/HMI.Util/IniFileHelper.cs b/HMI.Util/IniFileHelper.cs
index f6e7d4e..1c2959b 100644
--- a/HMI.Util/IniFileHelper.cs
+++ b/HMI.Util/IniFileHelper.cs
@@ -26,6 +26,9 @@
         private static extern uint GetPrivateProfileStringA(string section, string key,
             string def, Byte[] retVal, int size, string filePath);
 
+        [DllImport("kernel32")]
+        private static extern int GetPrivateProfileSection(string section, byte[] retVal, int size, string filePath);
+
         #endregion
         public static List<string> ReadSections()
         {
@@ -108,5 +111,34 @@
                 return true;
         }
         #endregion
+
+        public static Dictionary<string, string> GetKeyValuesBySection(string iniFile, string section)
+        {
+            int size = 65536;
+            byte[] buffer = new byte[size];
+
+            Dictionary<string, string> result = new Dictionary<string, string>();
+            int bytesRead = GetPrivateProfileSection(section, buffer, size, iniFile);
+            if (bytesRead > 0)
+            {
+                string sectionData = Encoding.Default.GetString(buffer, 0, bytesRead - 1);
+                string[] keyValuePairs = sectionData.Split('\0');
+
+                foreach (string pair in keyValuePairs)
+                {
+                    if (!string.IsNullOrEmpty(pair))
+                    {
+                        int equalSignIndex = pair.IndexOf('=');
+                        if (equalSignIndex > 0)
+                        {
+                            string key = pair.Substring(0, equalSignIndex).Trim();
+                            string value = pair.Substring(equalSignIndex + 1).Trim();
+                            result[key] = value;
+                        }
+                    }
+                }
+            }
+            return result;
+        }
     }
 }
diff --git a/HMI.Util/packages.config b/HMI.Util/packages.config
index 5ed5cff..72b7273 100644
--- a/HMI.Util/packages.config
+++ b/HMI.Util/packages.config
@@ -1,5 +1,6 @@
 锘�<?xml version="1.0" encoding="utf-8"?>
 <packages>
+  <package id="log4net" version="3.0.4" targetFramework="net472" />
   <package id="NPOI" version="2.4.1" targetFramework="net472" />
   <package id="SharpZipLib" version="1.0.0" targetFramework="net472" />
 </packages>
\ No newline at end of file
diff --git a/OPCClient/OPCClient.csproj b/OPCClient/OPCClient.csproj
index 13f20fc..d7aa75b 100644
--- a/OPCClient/OPCClient.csproj
+++ b/OPCClient/OPCClient.csproj
@@ -73,7 +73,6 @@
     <Compile Include="Properties\AssemblyInfo.cs" />
   </ItemGroup>
   <ItemGroup>
-    <None Include="app.config" />
     <None Include="packages.config" />
   </ItemGroup>
   <ItemGroup>
diff --git a/OPCClient/app.config b/OPCClient/app.config
deleted file mode 100644
index a0cefc2..0000000
--- a/OPCClient/app.config
+++ /dev/null
@@ -1,11 +0,0 @@
-锘�<?xml version="1.0" encoding="utf-8"?>
-<configuration>
-  <runtime>
-    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
-      <dependentAssembly>
-        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
-        <bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" />
-      </dependentAssembly>
-    </assemblyBinding>
-  </runtime>
-</configuration>
\ No newline at end of file
diff --git a/Plugin.CurrentSensorControl/BurnInOven.xaml.cs b/Plugin.CurrentSensorControl/BurnInOven.xaml.cs
index 72d9c5a..10b2b6c 100644
--- a/Plugin.CurrentSensorControl/BurnInOven.xaml.cs
+++ b/Plugin.CurrentSensorControl/BurnInOven.xaml.cs
@@ -14,7 +14,7 @@
     {
         private BaseConfig config;
 
-        private static readonly string[] datas = new string[] { "_CoolerStatus", "_LEDLight", "_Req", "_SetTemp", "_SaveTemp", "_TempDown", "_TempUp", "_AvgTemp" };
+        private static readonly string[] datas = new string[] { "_LEDLight", "_Req", "_SetTemp", "_SaveTemp", "_TempDown", "_TempUp", "_AvgTemp" };
 
         [ObservableProperty]
         private string header;
diff --git a/Plugin.CurrentSensorControl/BurnInSettingModal.xaml.cs b/Plugin.CurrentSensorControl/BurnInSettingModal.xaml.cs
index 16abaa4..576146b 100644
--- a/Plugin.CurrentSensorControl/BurnInSettingModal.xaml.cs
+++ b/Plugin.CurrentSensorControl/BurnInSettingModal.xaml.cs
@@ -125,6 +125,14 @@
             }
         }
 
+        private void TryDeleteData(PLCTagData data)
+        {
+            if (MsgBoxHelper2.WarnChoose($"纭畾鍒犻櫎鏁版嵁: {data.Name} 鍚楋紵") == System.Windows.MessageBoxResult.OK)
+            {
+                Datasource.Remove(data);
+            }
+        }
+
         private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
         {
             var btn = sender as Button;
@@ -135,10 +143,12 @@
                 case "importBtn":
                     DoImport();
                     break;
+                case "delBtn":
+                    TryDeleteData((PLCTagData)btn.DataContext);
+                    break;
                 default:
                     break;
             }
-
         }
     }
 }
diff --git a/Plugin.DOControl/Plugin.DOControl.csproj b/Plugin.DOControl/Plugin.DOControl.csproj
index bbf2eb1..1637a2c 100644
--- a/Plugin.DOControl/Plugin.DOControl.csproj
+++ b/Plugin.DOControl/Plugin.DOControl.csproj
@@ -118,7 +118,6 @@
       <Generator>ResXFileCodeGenerator</Generator>
       <LastGenOutput>Resources.Designer.cs</LastGenOutput>
     </EmbeddedResource>
-    <None Include="app.config" />
     <None Include="packages.config" />
     <None Include="Properties\Settings.settings">
       <Generator>SettingsSingleFileGenerator</Generator>
diff --git a/Plugin.DOControl/app.config b/Plugin.DOControl/app.config
deleted file mode 100644
index a0cefc2..0000000
--- a/Plugin.DOControl/app.config
+++ /dev/null
@@ -1,11 +0,0 @@
-锘�<?xml version="1.0" encoding="utf-8"?>
-<configuration>
-  <runtime>
-    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
-      <dependentAssembly>
-        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
-        <bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" />
-      </dependentAssembly>
-    </assemblyBinding>
-  </runtime>
-</configuration>
\ No newline at end of file
diff --git a/Plugin.DataViewer/AssemblyInfo.cs b/Plugin.DataViewer/AssemblyInfo.cs
new file mode 100644
index 0000000..8a284a8
--- /dev/null
+++ b/Plugin.DataViewer/AssemblyInfo.cs
@@ -0,0 +1,12 @@
+using HMI.Core.Annotation;
+using System.Windows;
+
+[assembly:ThemeInfo(
+    ResourceDictionaryLocation.None,            //where theme specific resource dictionaries are located
+                                                //(used if a resource is not found in the page,
+                                                // or application resource dictionaries)
+    ResourceDictionaryLocation.SourceAssembly   //where the generic resource dictionary is located
+                                                //(used if a resource is not found in the page,
+                                                // app, or any theme specific resource dictionaries)
+)]
+[assembly: PluginClassName("Plugin.DataViewer.DataViewerControl")]
diff --git a/Plugin.DataViewer/CustomSortColumn.cs b/Plugin.DataViewer/CustomSortColumn.cs
new file mode 100644
index 0000000..793d2f1
--- /dev/null
+++ b/Plugin.DataViewer/CustomSortColumn.cs
@@ -0,0 +1,108 @@
+锘縰sing System.ComponentModel;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+
+namespace Plugin.DataViewer
+{
+    public class CustomSortColumnConverter : IValueConverter
+    {
+        public static CustomSortColumnConverter Instance = new CustomSortColumnConverter();
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value as ListSortDirection? == ListSortDirection.Ascending)
+            {
+                return true;
+            }
+
+            if (value as ListSortDirection? == ListSortDirection.Descending)
+            {
+                return false;
+            }
+            return null;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return value;
+        }
+    }
+
+    public interface ICustomSortColumn
+    {
+        public bool? IsAsc { get; set; }
+
+        void UpdateOther(bool? value);
+        DataGrid GetOwner();
+    }
+
+    public class CustomSortTemplateColumn : DataGridTemplateColumn, ICustomSortColumn
+    {
+        public static DependencyProperty IsAscProperty = DependencyProperty.Register(
+                            "IsAsc",
+                            typeof(bool?),
+                            typeof(DataGridTemplateColumn),
+                            new FrameworkPropertyMetadata(null));
+
+        public bool? IsAsc
+        {
+            get => (bool?)GetValue(IsAscProperty);
+            set
+            {
+                SetValue(IsAscProperty, value);
+            }
+        }
+
+        public void UpdateOther(bool? value)
+        {
+            foreach (var col in DataGridOwner.Columns)
+            {
+                if (col == this) continue;
+                if (col is ICustomSortColumn csc)
+                {
+                    csc.IsAsc = value;
+                }
+            }
+        }
+
+        public DataGrid GetOwner()
+        {
+            return this.DataGridOwner;
+        }
+    }
+
+    public class CustomSortTextColumn : DataGridTextColumn, ICustomSortColumn
+    {
+        public static DependencyProperty IsAscProperty = DependencyProperty.Register(
+                            "IsAsc",
+                            typeof(bool?),
+                            typeof(DataGridTextColumn),
+                            new FrameworkPropertyMetadata(null));
+
+        public bool? IsAsc
+        {
+            get => (bool?)GetValue(IsAscProperty);
+            set
+            {
+                SetValue(IsAscProperty, value);
+            }
+        }
+        public void UpdateOther(bool? value)
+        {
+            foreach (var col in DataGridOwner.Columns)
+            {
+                if (col == this) continue;
+                if (col is ICustomSortColumn csc)
+                {
+                    csc.IsAsc = value;
+                }
+            }
+        }
+
+        public DataGrid GetOwner()
+        {
+            return this.DataGridOwner;
+        }
+    }
+}
diff --git a/Plugin.DataViewer/DataViewerControl.xaml b/Plugin.DataViewer/DataViewerControl.xaml
new file mode 100644
index 0000000..f25c54e
--- /dev/null
+++ b/Plugin.DataViewer/DataViewerControl.xaml
@@ -0,0 +1,480 @@
+锘�<hb:SimpleMaskControl x:Class="Plugin.DataViewer.DataViewerControl"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:Plugin.DataViewer"
+             mc:Ignorable="d" 
+             xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
+             xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
+             xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI"
+             xmlns:pu="https://opensource.panuon.com/wpf-ui"
+             xmlns:sc="clr-namespace:System.ComponentModel;assembly=System"
+             xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <UserControl.Resources>
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <pu:StyleDictionary Includes="Label" />
+            </ResourceDictionary.MergedDictionaries>
+
+            <Style x:Key="SB" TargetType="DataGridColumnHeader">
+                <Setter Property="Foreground" Value="{DynamicResource PrimaryColorContract}"/>
+                <!--<Setter Property="Background" Value="{DynamicResource PrimaryColor}"/>
+            <Setter Property="Foreground" Value="{DynamicResource PrimaryColorContract}"/>
+            <Setter Property="BorderBrush" Value="{DynamicResource PrimaryColorContract}"/>
+            <Setter Property="BorderThickness" Value="0,0,1,1"/>-->
+                <Setter Property="Template">
+                    <Setter.Value>
+                        <ControlTemplate TargetType="DataGridColumnHeader">
+                            <Border BorderBrush="#FFF" Background="{DynamicResource PrimaryColor}"
+                                MouseLeftButtonDown="Grid_MouseLeftButtonUp"
+                                Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=DataGridColumnHeader}}"
+                                BorderThickness="0,0,1,1">
+                                <Grid Margin="{TemplateBinding Padding}">
+                                    <Grid.ColumnDefinitions>
+                                        <ColumnDefinition Width="Auto" />
+                                        <ColumnDefinition Width="*" />
+                                        <ColumnDefinition Width="Auto" />
+                                        <ColumnDefinition Width="Auto" />
+                                    </Grid.ColumnDefinitions>
+                                    <Thumb x:Name="PART_LeftHeaderGripper" Grid.Column="0" HorizontalAlignment="Left"
+Width="1" BorderThickness="0" BorderBrush="Transparent"
+Cursor="SizeWE"/>
+                                    <ContentPresenter Grid.Column="0"
+                                    VerticalAlignment="Center"
+                                    Content="{Binding Column.Header,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=DataGridColumnHeader}}"/>
+                                    <TextBlock VerticalAlignment="Center" Grid.Column="2" Text="&#xe603;" x:Name="sorter"> <!---->
+                                        <TextBlock.Style>
+                                            <Style TargetType="TextBlock">
+                                                <Setter Property="FontFamily" Value="{StaticResource iconfont}"/>
+                                                <Setter Property="Visibility" Value="Collapsed"/>
+                                                <Style.Triggers>
+
+                                                    <!--<DataTrigger Binding="{Binding Column.CustomSort,
+                                                    RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=DataGridColumnHeader}}" Value="{x:Null}">
+                                                    <Setter  Property="Visibility" Value="Collapsed"/>
+                                                </DataTrigger>-->
+
+                                                    <DataTrigger Binding="{Binding Column.IsAsc,
+                                                    RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=DataGridColumnHeader}}" Value="false">
+                                                        <Setter Property="Visibility" Value="Visible"/>
+                                                    </DataTrigger>
+
+                                                    <DataTrigger Binding="{Binding Column.IsAsc,
+                                                    RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=DataGridColumnHeader}}" Value="True">
+                                                        <Setter Property="Visibility" Value="Visible"/>
+                                                        <Setter Property="LayoutTransform">
+                                                            <Setter.Value>
+                                                                <RotateTransform Angle="180"/>
+                                                            </Setter.Value>
+                                                        </Setter>
+                                                    </DataTrigger>
+                                                </Style.Triggers>
+                                            </Style>
+                                        </TextBlock.Style>
+                                    </TextBlock>
+                                    <Thumb x:Name="PART_RightHeaderGripper" Grid.Column="3" HorizontalAlignment="Right" Width="1" BorderThickness="0" 
+                                       BorderBrush="Transparent" Cursor="SizeWE"/>
+                                </Grid>
+                            </Border>
+                        </ControlTemplate>
+                    </Setter.Value>
+                </Setter>
+            </Style>
+
+            <Style x:Key="ThisDataGridRow" TargetType="DataGridRow">
+                <Setter Property="Background" Value="White"/>
+                <Style.Triggers>
+                    <Trigger Property="AlternationIndex" Value="1">
+                        <Setter Property="Background" Value="#eeeeee"/>
+                    </Trigger>
+                    <DataTrigger Binding="{Binding result,Converter={x:Static uic:ResultConverter.Instance}}" Value="FAIL">
+                        <Setter Property="Background" Value="{DynamicResource DangerColor}"/>
+                        <Setter Property="Foreground" Value="{DynamicResource SecondPrimaryColorContract}"/>
+                    </DataTrigger>
+                    <DataTrigger Binding="{Binding status,Converter={x:Static uic:ResultConverter.Instance}}" Value="FAIL">
+                        <Setter Property="Background" Value="{DynamicResource DangerColor}"/>
+                        <Setter Property="Foreground" Value="{DynamicResource SecondPrimaryColorContract}"/>
+                    </DataTrigger>
+                </Style.Triggers>
+            </Style>
+
+            <Style x:Key="OutLabel" TargetType="Label">
+                <Setter Property="Height" Value="25"/>
+                <Setter Property="Padding" Value="0"/>
+                <Setter Property="VerticalAlignment" Value="Center"/>
+                <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
+                <Setter Property="VerticalContentAlignment" Value="Stretch"/>
+                <Setter Property="Background" Value="#ffffff"/>
+                <Setter Property="BorderThickness" Value="2"/>
+                <Setter Property="FontSize" Value="10"/>
+                <Setter Property="Foreground" Value="#000000"/>
+            </Style>
+
+            <Style x:Key="InLabel" TargetType="Label">
+                <Setter Property="VerticalContentAlignment" Value="Center"/>
+                <Setter Property="BorderThickness" Value="0"/>
+                <Setter Property="pu:LabelHelper.CornerRadius" Value="2,0,0,2"/>
+            </Style>
+        </ResourceDictionary>
+    </UserControl.Resources>
+
+    <Border Style="{StaticResource CardBoarder}">
+        <Grid>
+            <Grid dp:GridHelper.Space="10" dp:GridHelper.SpaceIgnore="ALL">
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition/>
+                </Grid.RowDefinitions>
+
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="4*"/>
+                    <ColumnDefinition Width="6*"/>
+                </Grid.ColumnDefinitions>
+
+                <DockPanel Grid.ColumnSpan="2">
+                    <!--<StackPanel DockPanel.Dock="Right" Style="{StaticResource ButtonGroup}">
+                    <Button Style="{StaticResource AddBtn}" x:Name="addBtn" Click="Button_Click"/>
+                    <Button Style="{StaticResource EditBtn}" x:Name="editBtn" Click="Button_Click"/>
+                    <Button Style="{StaticResource RfhBtn}" x:Name="rfhBtn" Click="Button_Click"/>
+                    <Button Style="{StaticResource DelBtn}" x:Name="delBtn" Click="Button_Click"/>
+                </StackPanel>-->
+                    <TextBlock Style="{StaticResource TitleBox}" Text="{DynamicResource Dv.Title}"/>
+                </DockPanel>
+
+                <!--<Grid>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition/>
+                    <ColumnDefinition/>
+                </Grid.ColumnDefinitions>
+            </Grid>-->
+                <StackPanel Grid.Row="1" Grid.ColumnSpan="2" HorizontalAlignment="Left" Style="{StaticResource ButtonGroup}">
+
+                    <TextBlock Style="{StaticResource TitleBox}" FontSize="{StaticResource ContentFontSize2}" 
+       Text="SN"/>
+                    <pu:SearchBox x:Name="snInput" Tag="sn" SearchTextChanged="SearchTextChanged" MinWidth="180" />
+
+                    <TextBlock Style="{StaticResource TitleBox}" FontSize="{StaticResource ContentFontSize2}" 
+                           Text="绌翠綅鍙�"/>
+                    <pu:SearchBox x:Name="positionInput" Tag="position" SearchTextChanged="SearchTextChanged" MinWidth="180" />
+
+                    <TextBlock Style="{StaticResource TitleBox}" FontSize="{StaticResource ContentFontSize2}" Text="鍨嬪彿"/>
+                    <pu:SearchBox x:Name="modelInput" Tag="model" SearchTextChanged="SearchTextChanged" MinWidth="180" />
+
+                    <TextBlock Style="{StaticResource TitleBox}" FontSize="{StaticResource ContentFontSize2}" Text="缁撴灉"/>
+                    <pu:SearchBox x:Name="resultInput" Tag="result" SearchTextChanged="SearchTextChanged" MinWidth="180" />
+
+                    <TextBlock Style="{StaticResource TitleBox}" FontSize="{StaticResource ContentFontSize2}" 
+                           Text="{DynamicResource App.StartTime}"/>
+                    <pu:DateTimePicker Style="{StaticResource TimePicker}"
+                                   SelectedDateTime="{x:Null}" x:Name="startTimeInput"/>
+                    <TextBlock Style="{StaticResource TitleBox}" FontSize="{StaticResource ContentFontSize2}" 
+                           Text="{DynamicResource App.EndTime}"/>
+                    <pu:DateTimePicker Style="{StaticResource TimePicker}"
+                                   SelectedDateTime="{x:Null}" x:Name="endTimeInput"/>
+
+                    <Button Style="{StaticResource QueryBtn}"
+                        x:Name="srhBtn" Click="Button_Click"/>
+
+                    <Button Style="{StaticResource ResetBtn}"
+                        x:Name="resetBtn" Click="Button_Click"/>
+                </StackPanel>
+
+                <Grid  Grid.Row="2" Grid.Column="0" dp:GridHelper.Space="5" dp:GridHelper.SpaceIgnore="ALL">
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition/>
+                        <RowDefinition Height="Auto"/>
+                    </Grid.RowDefinitions>
+
+                    <DockPanel>
+                        <Button DockPanel.Dock="Right" Style="{StaticResource ExportBtn}"
+x:Name="exportBtn" Click="Button_Click"/>
+                        <StackPanel Orientation="Horizontal" dp:StackPanelHelper.ChildSpace="10">
+
+                        <Label
+Height="25"
+Padding="0"   
+HorizontalContentAlignment="Stretch"
+VerticalContentAlignment="Stretch"
+pu:LabelHelper.CornerRadius="2"
+Background="#ffffff"
+BorderBrush="{DynamicResource PrimaryColor}"
+BorderThickness="2"
+FontSize="10"
+FontWeight="Bold"
+Foreground="#000000">
+                            <DockPanel Margin="-1" MinWidth="80">
+                                <Label VerticalContentAlignment="Center" BorderThickness="0" Content="ALL" Foreground="{DynamicResource PrimaryColorContract}" 
+                          pu:LabelHelper.CornerRadius="2,0,0,2" DockPanel.Dock="Left" Background="{DynamicResource PrimaryColor}" />
+                                <TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="14" Foreground="Black">
+    <Run Text="{Binding TotalCount}"/>
+                                </TextBlock>
+                            </DockPanel>
+                        </Label>
+
+                        <Label
+Height="25"
+Padding="0"   
+HorizontalContentAlignment="Stretch"
+VerticalContentAlignment="Stretch"
+pu:LabelHelper.CornerRadius="2"
+Background="#ffffff"
+BorderBrush="{DynamicResource SuccessColor}"
+BorderThickness="2"
+FontSize="10"
+FontWeight="Bold"
+Foreground="#000000">
+                            <DockPanel Margin="-1" MinWidth="80">
+                                <Label VerticalContentAlignment="Center" BorderThickness="0" Content="PASS" Foreground="{DynamicResource SuccessColorContrast}" 
+                          pu:LabelHelper.CornerRadius="2,0,0,2" DockPanel.Dock="Left" Background="{DynamicResource SuccessColor}"/>
+                                <TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="14" Foreground="Black">
+<Run Text="{Binding PassCount}"/>
+                                </TextBlock>
+                            </DockPanel>
+                        </Label>
+
+                        <Label
+Height="25"
+Padding="0"   
+HorizontalContentAlignment="Stretch"
+VerticalContentAlignment="Stretch"
+pu:LabelHelper.CornerRadius="2"
+Background="#ffffff"
+BorderBrush="{DynamicResource DangerColor}"
+BorderThickness="2"
+FontSize="10"
+                        FontWeight="Bold"
+Foreground="#000000">
+                            <DockPanel Margin="-1" MinWidth="80">
+                                <Label VerticalContentAlignment="Center" BorderThickness="0" Content="FAIL" Foreground="{DynamicResource DangerColorContrast}" 
+                          pu:LabelHelper.CornerRadius="2,0,0,2" DockPanel.Dock="Left" Background="{DynamicResource DangerColor}" />
+                                <TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="14" Foreground="Black">
+<Run Text="{Binding FailCount}"/>
+                                </TextBlock>
+                            </DockPanel>
+                        </Label>
+                        
+                        </StackPanel>
+
+                    </DockPanel>
+
+
+
+                    <DataGrid Grid.Row="1" CanUserSortColumns="False"
+                      AutoGenerateColumns="False" x:Name="resultTable"
+                      RowStyle="{StaticResource ThisDataGridRow}"
+                      Style="{StaticResource DefaultDataGrid}"
+                      ColumnHeaderStyle="{StaticResource SB}"
+                      VirtualizingStackPanel.CacheLength="10"
+                      SelectedItem="{Binding SelectedResult}"
+                      MouseDoubleClick="resultTable_MouseDoubleClick"
+                      ColumnHeaderHeight="30">
+                        <DataGrid.Columns>
+                            <DataGridTemplateColumn Header="#" IsReadOnly="True" Width="Auto">
+                                <DataGridTemplateColumn.CellTemplate>
+                                    <DataTemplate>
+                                        <TextBlock Text="{Binding Converter={StaticResource rowNumber},
+RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGridRow},AncestorLevel=1}}"/>
+                                    </DataTemplate>
+                                </DataGridTemplateColumn.CellTemplate>
+                            </DataGridTemplateColumn>
+
+                            <local:CustomSortTextColumn IsReadOnly="True" Width="*" Binding="{Binding sn}">
+                                <local:CustomSortTextColumn.Header>
+                                    <TextBlock Text="SN"/>
+                                </local:CustomSortTextColumn.Header>
+                            </local:CustomSortTextColumn>
+
+                            <local:CustomSortTextColumn IsReadOnly="True"  Width="80" Binding="{Binding position}">
+                                <local:CustomSortTextColumn.Header>
+                                    <TextBlock Text="绌翠綅鍙�"/>
+                                </local:CustomSortTextColumn.Header>
+                            </local:CustomSortTextColumn>
+
+                            <local:CustomSortTextColumn IsReadOnly="True"  Width="80" Binding="{Binding model}">
+                                <local:CustomSortTextColumn.Header>
+                                    <TextBlock Text="鍨嬪彿"/>
+                                </local:CustomSortTextColumn.Header>
+                            </local:CustomSortTextColumn>
+
+                            <local:CustomSortTextColumn IsReadOnly="True"  Width="80" Binding="{Binding result}">
+                                <local:CustomSortTextColumn.Header>
+                                    <TextBlock Text="娴嬭瘯缁撴灉"/>
+                                </local:CustomSortTextColumn.Header>
+                            </local:CustomSortTextColumn>
+
+                            <local:CustomSortTextColumn IsReadOnly="True"  Width="160" Binding="{Binding time,Converter={x:Static uic:DateStrConverter.Instance}}">
+                                <local:CustomSortTextColumn.Header>
+                                    <TextBlock Text="娴嬭瘯鏃堕棿"/>
+                                </local:CustomSortTextColumn.Header>
+                            </local:CustomSortTextColumn>
+
+                        </DataGrid.Columns>
+                    </DataGrid>
+
+                    <pu:Pagination Grid.Row="2" Height="30" Margin="10" HorizontalAlignment="Right" Foreground="Black"
+ItemsBackground="Transparent"
+ItemsCornerRadius="12"
+ItemsSelectedBackground="{DynamicResource SecondPrimaryColor}"
+                            x:Name="page" CurrentPageChanged="page_CurrentPageChanged"
+                            CurrentPage="{Binding CurrentPage}" 
+                                        MaxPage="{Binding TotalPage}">
+                        <pu:Pagination.ItemContainerStyle>
+                            <Style TargetType="pu:PaginationItem">
+                                <Setter Property="MinWidth" Value="30" />
+                                <Setter Property="Width" Value="Auto" />
+                                <Setter Property="Width" Value="Auto" />
+                            </Style>
+                        </pu:Pagination.ItemContainerStyle>
+                    </pu:Pagination>
+                </Grid>
+
+
+                <Grid Grid.Row="2" Grid.Column="1"  dp:GridHelper.Space="5" dp:GridHelper.SpaceIgnore="ALL">
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition/>
+                        <RowDefinition Height="Auto"/>
+                    </Grid.RowDefinitions>
+
+                    <DockPanel>
+                        <Button DockPanel.Dock="Right" Style="{StaticResource ExportBtn}"
+x:Name="exportItemBtn" Click="Button_Click"/>
+                        <Button DockPanel.Dock="Right" Margin="0,0,10,0" Style="{StaticResource QueryBtn}"
+x:Name="srhItemBtn" Click="Button_Click"/>
+                        <StackPanel Orientation="Horizontal" dp:StackPanelHelper.ChildSpace="10">
+                            <Label>
+                                <DockPanel Margin="-1" MinWidth="80" >
+                                    <Label VerticalContentAlignment="Center" BorderThickness="0" Content="娴嬭瘯缁撴灉" Foreground="{DynamicResource SuccessColorContrast}" 
+                          pu:LabelHelper.CornerRadius="2,0,0,2" DockPanel.Dock="Left" Background="Transparent"/>
+                                    <TextBlock VerticalAlignment="Center" Padding="0,0,5,0"
+                                               HorizontalAlignment="Center" FontSize="14" Foreground="{DynamicResource SuccessColorContrast}">
+<Run Text="{Binding SelectedStatus,Converter={x:Static uic:ResultConverter.Instance}}"/>
+                                    </TextBlock>
+                                </DockPanel>
+
+                                <Label.Style>
+                                    <Style TargetType="Label" BasedOn="{StaticResource {x:Static pu:StyleKeys.LabelStyle}}">
+                                        <Setter Property="pu:LabelHelper.CornerRadius" Value="2"/>
+                                        <Setter Property="Background" Value="{DynamicResource SuccessColor}"/>
+                                        <Setter Property="BorderBrush" Value="{DynamicResource SuccessColor}"/>
+                                        <Setter Property="Height" Value="25"/>
+                                        <Setter Property="Padding" Value="0"/>
+                                        <Setter Property="HorizontalContentAlignment" Value="Center"/>
+                                        <Setter Property="VerticalContentAlignment" Value="Center"/>
+                                        <Setter Property="BorderThickness" Value="2"/>
+                                        <Setter Property="FontSize" Value="10"/>
+                                        <Setter Property="FontWeight" Value="Bold"/>
+                                        <Setter Property="Foreground" Value="Black"/>
+
+                                        <Style.Triggers>
+                                            <DataTrigger Binding="{Binding SelectedStatus}" Value="{x:Null}">
+                                                <Setter Property="Visibility" Value="Collapsed"/>
+                                            </DataTrigger>
+                                            <DataTrigger Binding="{Binding SelectedStatus,Converter={x:Static uic:ResultConverter.Instance}}" Value="FAIL">
+                                                <Setter Property="Background" Value="{DynamicResource DangerColor}"/>
+                                                <Setter Property="BorderBrush" Value="{DynamicResource DangerColor}"/>
+                                            </DataTrigger>
+                                        </Style.Triggers>
+                                    </Style>
+                                </Label.Style>                                
+                            </Label>
+
+                            <TextBlock Style="{StaticResource TitleBox}" FontSize="{StaticResource ContentFontSize2}" Text="娴嬭瘯椤�"/>
+                            <pu:SearchBox x:Name="stepInput" Tag="step" SearchTextChanged="SearchTextChanged" MinWidth="180" />
+
+                            <TextBlock Style="{StaticResource TitleBox}" FontSize="{StaticResource ContentFontSize2}" Text="缁撴灉"/>
+                            <pu:SearchBox x:Name="statusInput" Tag="status" SearchTextChanged="SearchTextChanged" MinWidth="180" />
+                            
+                        </StackPanel>
+                    </DockPanel>
+
+                    <DataGrid Grid.Row="1" x:Name="testItemTable" ColumnHeaderHeight="30"
+                          ColumnHeaderStyle="{StaticResource SB}"
+                      RowStyle="{StaticResource ThisDataGridRow}"
+                          Style="{StaticResource DefaultDataGrid}">
+                        <DataGrid.Columns>
+                            <DataGridTemplateColumn Header="#" IsReadOnly="True" CanUserResize="False">
+                                <DataGridTemplateColumn.CellTemplate>
+                                    <DataTemplate>
+                                        <TextBlock Text="{Binding Converter={StaticResource rowNumber},
+RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGridRow},AncestorLevel=1}}"/>
+                                    </DataTemplate>
+                                </DataGridTemplateColumn.CellTemplate>
+                            </DataGridTemplateColumn>
+
+                            <local:CustomSortTextColumn IsReadOnly="True" Width="Auto" Binding="{Binding sn}">
+                                <local:CustomSortTextColumn.Header>
+                                    <TextBlock Text="SN"/>
+                                </local:CustomSortTextColumn.Header>
+                            </local:CustomSortTextColumn>
+
+                            <local:CustomSortTextColumn IsReadOnly="True"  Width="*" Binding="{Binding step}">
+                                <local:CustomSortTextColumn.Header>
+                                    <TextBlock Text="娴嬭瘯椤�"/>
+                                </local:CustomSortTextColumn.Header>
+                            </local:CustomSortTextColumn>
+
+                            <local:CustomSortTextColumn IsReadOnly="True"  Width="*" Binding="{Binding value}">
+                                <local:CustomSortTextColumn.Header>
+                                    <TextBlock Text="娴嬭瘯鍊�"/>
+                                </local:CustomSortTextColumn.Header>
+                            </local:CustomSortTextColumn>
+
+                            <local:CustomSortTextColumn IsReadOnly="True"  Width="80" Binding="{Binding limitDown}">
+                                <local:CustomSortTextColumn.Header>
+                                    <TextBlock Text="涓嬮檺"/>
+                                </local:CustomSortTextColumn.Header>
+                            </local:CustomSortTextColumn>
+
+                            <local:CustomSortTextColumn IsReadOnly="True"  Width="80" Binding="{Binding limitUp}">
+                                <local:CustomSortTextColumn.Header>
+                                    <TextBlock Text="涓婇檺"/>
+                                </local:CustomSortTextColumn.Header>
+                            </local:CustomSortTextColumn>
+
+                            <local:CustomSortTextColumn IsReadOnly="True"  Width="80" Binding="{Binding unit}">
+                                <local:CustomSortTextColumn.Header>
+                                    <TextBlock Text="鍗曚綅"/>
+                                </local:CustomSortTextColumn.Header>
+                            </local:CustomSortTextColumn>
+
+                            <local:CustomSortTextColumn IsReadOnly="True"  Width="80" Binding="{Binding status}">
+                                <local:CustomSortTextColumn.Header>
+                                    <TextBlock Text="缁撴灉"/>
+                                </local:CustomSortTextColumn.Header>
+                            </local:CustomSortTextColumn>
+
+                            <local:CustomSortTextColumn IsReadOnly="True"  Width="160" Binding="{Binding time,Converter={x:Static uic:DateStrConverter.Instance}}">
+                                <local:CustomSortTextColumn.Header>
+                                    <TextBlock Text="娴嬭瘯鏃堕棿"/>
+                                </local:CustomSortTextColumn.Header>
+                            </local:CustomSortTextColumn>
+
+                        </DataGrid.Columns>
+                    </DataGrid>
+
+                    <pu:Pagination Grid.Row="2" Height="30" HorizontalAlignment="Right" Foreground="Black" ItemsBackground="Transparent"
+ItemsCornerRadius="12"
+ItemsSelectedBackground="{DynamicResource SecondPrimaryColor}"
+                            x:Name="itemPage" CurrentPageChanged="page_CurrentPageChanged"
+                            CurrentPage="{Binding TestItemPage}" 
+                                        MaxPage="{Binding TestItemTotalPage}">
+                        <pu:Pagination.ItemContainerStyle>
+                            <Style TargetType="pu:PaginationItem">
+                                <Setter Property="MinWidth" Value="30" />
+                                <Setter Property="Cursor" Value="Hand" />
+                                <Setter Property="Width" Value="Auto" />
+                            </Style>
+                        </pu:Pagination.ItemContainerStyle>
+                    </pu:Pagination>
+                </Grid>
+                <ui:MaskControl Grid.Row="2" Grid.Column="1" x:Name="mask2"/>
+            </Grid>
+            <ui:MaskControl x:Name="topMask"/>
+        </Grid>
+    </Border>
+</hb:SimpleMaskControl>
diff --git a/Plugin.DataViewer/DataViewerControl.xaml.cs b/Plugin.DataViewer/DataViewerControl.xaml.cs
new file mode 100644
index 0000000..372bba9
--- /dev/null
+++ b/Plugin.DataViewer/DataViewerControl.xaml.cs
@@ -0,0 +1,800 @@
+锘縰sing CommunityToolkit.Mvvm.ComponentModel;
+using HMI.Base;
+using HMI.Core;
+using HMI.Core.Setting;
+using HMI.Core.Util;
+using HMI.UI.Util;
+using HMI.Util;
+using Microsoft.Win32;
+using Panuon.WPF.UI;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Data;
+using System.IO;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+
+namespace Plugin.DataViewer
+{
+    public class TestResult
+    {
+        [ExcelColumn("SN")]
+        public string sn { get; set; }
+
+        [ExcelColumn("绌翠綅鍙�")]
+        public string position { get; set; }
+
+        [ExcelColumn("鍨嬪彿")]
+        public string model { get; set; }
+
+        [ExcelColumn("娴嬭瘯缁撴灉")]
+        public string result { get; set; }
+
+        [ExcelColumn("娴嬭瘯鏃堕棿")]
+        public string time { get; set; }
+    }
+
+    public class SerialData
+    {
+        [ExcelColumn("SN")]
+        public string sn { get; set; }
+
+        [ExcelColumn("绌翠綅鍙�")]
+        public string position { get; set; }
+
+        [ExcelColumn("鍨嬪彿")]
+        public string model { get; set; }
+
+        [ExcelColumn("娴嬭瘯椤�")]
+        public string step { get; set; }
+
+        [ExcelColumn("娴嬭瘯鍊�")]
+        public string value { get; set; }
+
+        [ExcelColumn("涓嬮檺")]
+        public string limitDown { get; set; }
+
+        [ExcelColumn("涓婇檺")]
+        public string limitUp { get; set; }
+        [ExcelColumn("缁撴灉")]
+        public string status { get; set; }
+
+        [ExcelColumn("鍗曚綅")]
+        public string unit { get; set; }
+
+        [ExcelColumn("娴嬭瘯鏃堕棿")]
+        public string time { get; set; }
+    }
+
+    public class SortParam
+    {
+        public string name { get; set; }
+        public ListSortDirection direction { get; set; }
+        public string sort { get; set; }
+    }
+
+    public class ResultData
+    {
+        public string sn { get; set; }
+        public string position { get; set; }
+    }
+
+    /// <summary>
+    /// DataViewerControl.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    [ObservableObject]
+    public partial class DataViewerControl : SimpleMaskControl
+    {
+        [ObservableProperty]
+        private DataView resultView;
+
+        [ObservableProperty]
+        private ObservableCollection<ResultData> resultData;
+
+        [ObservableProperty]
+        private long currentPage = 1;
+
+        [ObservableProperty]
+        private long totalPage = 0;
+
+        private int pageSize = 30;
+
+        [ObservableProperty]
+        private long totalCount;
+
+        [ObservableProperty]
+        private long passCount;
+
+        [ObservableProperty]
+        private long failCount;
+
+        [ObservableProperty]
+        private long testItemPage = 1;
+
+        [ObservableProperty]
+        private long testItemTotalPage = 0;
+
+        [ObservableProperty]
+        private object selectedResult;
+
+        [ObservableProperty]
+        private string selectedStatus;
+
+        private SortParam sorter;
+        private SortParam itemSorter;
+        public DataViewerControl()
+        {
+            DataContext = this;
+            InitializeComponent();
+            Reset();
+        }
+
+        private string[] GetFiles(DateTime? startTime, DateTime? endTime)
+        {
+            string dir = ParamSetting.DataLogFolder;
+            string startName = $"Data_{startTime?.ToString("yyyyMMdd")}";
+            string endName = $"Data_{endTime?.ToString("yyyyMMdd")}";
+            var oldFiles = Directory.GetFiles(dir, "Data_*.db");
+            oldFiles = oldFiles.Where(file =>
+            {
+                var fileName = Path.GetFileNameWithoutExtension(file);
+                bool flag = true;
+                flag &= (string.Compare(fileName, startName) == 1 || fileName == startName);
+                flag &= (string.Compare(endName, fileName) == 1 || fileName == endName);
+                return flag;
+            }).ToArray();
+            return oldFiles;
+        }
+
+        private void Reset()
+        {
+            var today = DateTime.Now;
+            var startTime = today.AddDays(-7);
+            var end = today.AddDays(1);
+
+            snInput.Text = string.Empty;
+            positionInput.Text = string.Empty;
+            modelInput.Text = string.Empty;
+            resultInput.Text = string.Empty;
+
+            startTimeInput.SelectedDateTime = DateTime.Parse(startTime.ToString("yyyy-MM-dd 00:00:00"));
+            endTimeInput.SelectedDateTime = DateTime.Parse(end.ToString("yyyy-MM-dd 00:00:00"));
+        }
+
+        private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
+        {
+            var btn = sender as Button;
+            if (btn.Name == "resetBtn")
+            {
+                Reset();
+                if (CurrentPage == 1)
+                {
+                    SelectPage(1, this.pageSize);
+                }
+                else
+                {
+                    CurrentPage = 1;
+                }
+                return;
+            }
+            if (btn.Name == "srhBtn")
+            {
+                if (CurrentPage == 1)
+                {
+                    SelectPage(1, this.pageSize);
+                }
+                else
+                {
+                    CurrentPage = 1;
+                }
+                return;
+            }
+
+            if (btn.Name == "srhItemBtn")
+            {
+                if (TestItemPage == 1)
+                {
+                    RefreshTestItem(this.selectResultId, 1, this.pageSize);
+                }
+                else
+                {
+                    TestItemPage = 1;
+                }
+                return;
+            }
+
+            if (btn.Name == "exportBtn")
+            {
+                TryExport();
+            }
+
+            if (btn.Name == "exportItemBtn")
+            {
+                TryExportItem();
+            }
+        }
+
+        private void TryExport()
+        {
+            SaveFileDialog sfd = new SaveFileDialog
+            {
+                Filter = "Excel 鏂囦欢 (*.xls)|*.xls"
+            };
+            if (sfd.ShowDialog() != true)
+            {
+                return;
+            }
+            string filePath = sfd.FileName;
+
+            DateTime? startTime = startTimeInput.SelectedDateTime, endTime = endTimeInput.SelectedDateTime;
+
+            topMask.ShowMask(this, () =>
+            {
+                var table = SelectResult(startTime, endTime);
+
+                ExcelHelper.ExportExcel<TestResult>(filePath, table);
+
+            }, (ex) =>
+            {
+                log.Error("Export Result Failed.", ex);
+                Dispatcher.Invoke(() =>
+                {
+                    MsgBoxHelper2.Error($"瀵煎嚭澶辫触锛亄ex.Message}");
+                });
+            }, "瀵煎嚭涓�,璇风◢鍚�...");
+        }
+
+        private void TryExportItem()
+        {
+            SaveFileDialog sfd = new SaveFileDialog
+            {
+                Filter = "Excel 鏂囦欢 (*.xls)|*.xls"
+            };
+            if (sfd.ShowDialog() != true)
+            {
+                return;
+            }
+            string filePath = sfd.FileName;
+
+            DateTime? startTime = startTimeInput.SelectedDateTime, endTime = endTimeInput.SelectedDateTime;
+
+            topMask.ShowMask(this, () =>
+            {
+                var table = SelectItem(startTime, endTime);
+
+                ExcelHelper.ExportExcel<SerialData>(filePath, table);
+
+            }, (ex) =>
+            {
+                log.Error("Export Result Failed.", ex);
+                Dispatcher.Invoke(() =>
+                {
+                    MsgBoxHelper2.Error($"瀵煎嚭澶辫触锛亄ex.Message}");
+                });
+            }, "瀵煎嚭涓�,璇风◢鍚�...");
+        }
+
+        private DataTable SelectResult(DateTime? startTime, DateTime? endTime)
+        {
+            var files = GetFiles(startTime, endTime);
+            if (files == null || files.Length <= 0)
+            {
+                return new DataTable();
+            }
+            using (var helper = new Sqlite3Helper(files[0]))
+            {
+                for (int i = 0; i < files.Length; i++)
+                {
+                    var attach = $"ATTACH DATABASE '{files[i]}' AS 'db{i}'";
+                    helper.Execute(attach);
+                }
+
+                for (int i = 0; i < files.Length; i++)
+                {
+                    if (!helper.HasData($"SELECT 1 FROM pragma_table_info('testResult','db{i}') WHERE name = 'resultId';"))
+                    {
+                        helper.Execute($"ALTER TABLE db{i}.testResult ADD resultId TEXT;");
+                    }
+                }
+
+                string sql = string.Join(" UNION ALL ", GetResultSqlsByFiles(files, startTime, endTime));
+                string sortName = "time", sortDirection = "DESC";
+                string baseSql = $"SELECT id,sn,model,station,position,result,time,resultId FROM ({sql}) as _temp";
+
+
+
+                sql = $"{baseSql} ORDER BY {sortName} {sortDirection}";
+
+                return helper.GetDataTable(sql);
+
+                //for (int i = 0; i < files.Length; i++)
+                //{
+                //    helper.Execute($"DETACH DATABASE db{i}");
+                //}
+            }
+        }
+
+        private DataTable SelectItem(DateTime? startTime, DateTime? endTime)
+        {
+            if (string.IsNullOrEmpty(this.selectResultId))
+            {
+                return new DataTable();
+            }
+            var files = GetFiles(startTime, endTime);
+            if (files == null || files.Length <= 0)
+            {
+                return new DataTable();
+            }
+            using (var helper = new Sqlite3Helper(files[0]))
+            {
+                for (int i = 0; i < files.Length; i++)
+                {
+                    var attach = $"ATTACH DATABASE '{files[i]}' AS 'db{i}'";
+                    helper.Execute(attach);
+                }
+
+                for (int i = 0; i < files.Length; i++)
+                {
+                    if (!helper.HasData($"SELECT 1 FROM pragma_table_info('serialData','db{i}') WHERE name = 'resultId';"))
+                    {
+                        helper.Execute($"ALTER TABLE db{i}.serialData ADD resultId TEXT;");
+                    }
+                }
+
+                string sql = string.Join(" UNION ALL ", GetTestItemSqlsByFiles(this.selectResultId, files));
+
+                string sortName = "time", sortDirection = "DESC";
+
+                string baseSql = $"SELECT sn, position, model,step, limitDown, limitUp,unit, value,status,time FROM ({sql}) as _temp";
+
+                sql = $"{baseSql} ORDER BY {sortName} {sortDirection}";
+
+                return helper.GetDataTable(sql);
+
+
+                //for (int i = 0; i < files.Length; i++)
+                //{
+                //    helper.Execute($"DETACH DATABASE db{i}");
+                //}
+            }
+        }
+
+        private void SelectPage(long page, long pageSize)
+        {
+            try
+            {
+                var files = GetFiles(startTimeInput.SelectedDateTime, endTimeInput.SelectedDateTime);
+                if (files == null || files.Length <= 0)
+                {
+                    return;
+                }
+                using (var helper = new Sqlite3Helper(files[0]))
+                {
+                    for (int i = 0; i < files.Length; i++)
+                    {
+                        var attach = $"ATTACH DATABASE '{files[i]}' AS 'db{i}'";
+                        helper.Execute(attach);
+                    }
+
+                    for (int i = 0; i < files.Length; i++)
+                    {
+                        if (!helper.HasData($"SELECT 1 FROM pragma_table_info('testResult','db{i}') WHERE name = 'resultId';"))
+                        {
+                            helper.Execute($"ALTER TABLE db{i}.testResult ADD resultId TEXT;");
+                        }
+                    }
+
+                    string sql = string.Join(" UNION ALL ", GetResultSqlsByFiles(files, startTimeInput.SelectedDateTime, endTimeInput.SelectedDateTime));
+                    string sortName = "time", sortDirection = "DESC";
+
+                    if (sorter != null)
+                    {
+                        sortName = sorter.name;
+                        sortDirection = sorter.sort;
+                    }
+                    string countSql = $"SELECT COUNT(1) FROM ({sql}) as _temp";
+                    long total = (long)helper.SelectOne(countSql);
+
+                    string baseSql = $"SELECT id,sn,model,station,position,result,time,resultId FROM ({sql}) as _temp";
+
+
+
+                    sql = $"{baseSql} ORDER BY {sortName} {sortDirection} LIMIT {pageSize} OFFSET {(page - 1) * pageSize}";
+
+                    var dt = helper.GetDataTable(sql);
+
+                    TotalPage = total / pageSize + (total % pageSize > 0 ? 1 : 0);
+                    resultTable.ItemsSource = dt.DefaultView;
+
+                    sql = $"SELECT result,COUNT(1) as num FROM ({baseSql}) GROUP BY result";
+                    var nums = helper.GetDataTable(sql);
+
+                    long all = 0, pass = 0, fail = 0;
+                    foreach (DataRow row in nums.Rows)
+                    {
+                        if ("ok".Equals(row["result"].ToString(), StringComparison.OrdinalIgnoreCase)
+                            || "pass".Equals(row["result"].ToString(), StringComparison.OrdinalIgnoreCase))
+                        {
+                            pass += (long)row["num"];
+                            all += (long)row["num"];
+                        }
+                        if ("ng".Equals(row["result"].ToString(), StringComparison.OrdinalIgnoreCase)
+                            || "fail".Equals(row["result"].ToString(), StringComparison.OrdinalIgnoreCase))
+                        {
+                            fail += (long)row["num"];
+                            all += (long)row["num"];
+                        }
+                    }
+
+                    TotalCount = all;
+                    PassCount = pass;
+                    FailCount = fail;
+
+                    //for (int i = 0; i < files.Length; i++)
+                    //{
+                    //    helper.Execute($"DETACH DATABASE db{i}");
+                    //}
+                }
+            }
+            catch (Exception ex)
+            {
+                log.Error("Select TestResult Error.", ex);
+
+            }
+        }
+
+        private void RefreshTestItem(string resultId, long page, int pageSize)
+        {
+            try
+            {
+                if (string.IsNullOrEmpty(resultId))
+                {
+                    testItemTable.ItemsSource = new DataTable().DefaultView;
+                    return;
+                }
+
+                var files = GetFiles(startTimeInput.SelectedDateTime, endTimeInput.SelectedDateTime);
+                if (files == null || files.Length <= 0)
+                {
+                    return;
+                }
+
+                string sql = string.Join(" UNION ALL ", GetTestItemSqlsByFiles(resultId, files));
+                string sortName = "time", sortDirection = "DESC";
+                if (itemSorter != null)
+                {
+                    sortName = itemSorter.name;
+                    sortDirection = itemSorter.sort;
+                }
+
+                mask2.ShowMask(null, () =>
+                {
+                    using (var helper = new Sqlite3Helper(files[0]))
+                    {
+                        for (int i = 0; i < files.Length; i++)
+                        {
+                            var attach = $"ATTACH DATABASE '{files[i]}' AS 'db{i}'";
+                            helper.Execute(attach);
+                        }
+
+                        for (int i = 0; i < files.Length; i++)
+                        {
+                            if (!helper.HasData($"SELECT 1 FROM pragma_table_info('serialData','db{i}') WHERE name = 'resultId';"))
+                            {
+                                helper.Execute($"ALTER TABLE db{i}.serialData ADD resultId TEXT;");
+                            }
+                        }
+
+
+                        string countSql = $"SELECT COUNT(1) FROM ({sql}) as _temp";
+                        long total = (long)helper.SelectOne(countSql);
+
+                        string baseSql = $"SELECT sn, model,step, limitDown, limitUp,unit, value,status,time FROM ({sql}) as _temp";
+
+                        sql = $"{baseSql} ORDER BY {sortName} {sortDirection} LIMIT {pageSize} OFFSET {(page - 1) * pageSize}";
+
+                        var dt = helper.GetDataTable(sql);
+
+
+                        //for (int i = 0; i < files.Length; i++)
+                        //{
+                        //    helper.Execute($"DETACH DATABASE db{i}");
+                        //}
+
+                        Dispatcher.BeginInvoke(() =>
+                        {
+                            TestItemTotalPage = total / pageSize + (total % pageSize > 0 ? 1 : 0);
+                            testItemTable.ItemsSource = dt.DefaultView;
+                        }, System.Windows.Threading.DispatcherPriority.Normal);
+                    }
+                }, (ex) =>
+                {
+                    log.Error("Select TestItem Error.", ex);
+                },
+                "鏌ヨ涓�...");
+            }
+            catch (Exception ex)
+            {
+                log.Error("Try Select TestItem Error.", ex);
+            }
+        }
+
+        public string[] GetResultSqlsByFiles(string[] files, DateTime? startTime, DateTime? endTime)
+        {
+            string sn = string.Empty, position = string.Empty, model = string.Empty, result = string.Empty;
+            Dispatcher.Invoke(() =>
+            {
+                sn = snInput.Text;
+                position = positionInput.Text;
+                model = modelInput.Text;
+                result = resultInput.Text;
+            });
+
+            string[] sqls = new string[files.Length];
+            List<string> querys = new List<string>();
+            if (!string.IsNullOrEmpty(sn))
+            {
+                querys.Add($"sn = '{sn}'");
+            }
+
+            if (!string.IsNullOrEmpty(position))
+            {
+                querys.Add($"position = '{position}'");
+            }
+
+            if (!string.IsNullOrEmpty(model))
+            {
+                querys.Add($"model = '{model}'");
+            }
+
+            if (!string.IsNullOrEmpty(result))
+            {
+                querys.Add($"result = '{result}'");
+            }
+
+            if (startTime != null)
+            {
+                querys.Add($"time >= '{startTime.Value.ToString("yyyy-MM-dd HH:mm:ss")}'");
+            }
+
+            if (endTime != null)
+            {
+                querys.Add($"time < '{endTime.Value.ToString("yyyy-MM-dd HH:mm:ss")}'");
+            }
+
+            string segment = "";
+            if (querys.Count > 0)
+            {
+                segment = $" WHERE {string.Join(" AND ", querys)}";
+            }
+
+            for (int i = 0; i < files.Length; i++)
+            {
+                sqls[i] = $"SELECT id,sn,model,station,position,result,time,resultId FROM db{i}.testResult {segment}";
+            }
+            return sqls;
+        }
+
+        private string[] GetTestItemSqlsByFiles(string resultId, string[] files)
+        {
+            List<string> querys = new List<string>() {
+                $"resultId = '{resultId}'"
+            };
+            string step = string.Empty, status = string.Empty;
+            Dispatcher.Invoke(() =>
+            {
+                step = stepInput.Text;
+                status = statusInput.Text;
+            });
+
+
+            if (!string.IsNullOrEmpty(step))
+            {
+                querys.Add($"step = '{step}'");
+            }
+
+            if (!string.IsNullOrEmpty(status))
+            {
+                querys.Add($"status = '{status}'");
+            }
+
+            string segment = string.Join(" AND ", querys); ;
+
+            string[] sqls = new string[files.Length];
+            for (int i = 0; i < files.Length; i++)
+            {
+                sqls[i] = $"SELECT sn, position, model,step, limitDown, limitUp,unit, value,status,time FROM db{i}.serialData WHERE {segment}";
+            }
+            return sqls;
+        }
+
+        public string[] GetSource(string[] files, string name, string tableName, string value)
+        {
+            try
+            {
+                if (files == null || files.Length <= 0)
+                {
+                    return new string[0];
+                }
+                string[] GetModelSqls()
+                {
+                    string[] sqls = new string[files.Length];
+                    for (int i = 0; i < files.Length; i++)
+                    {
+                        sqls[i] = $"SELECT DISTINCT {name} FROM db{i}.{tableName} WHERE {name} LIKE '%{value}%'";
+                    }
+                    return sqls;
+                }
+
+                using (var helper = new Sqlite3Helper(files[0]))
+                {
+                    for (int i = 0; i < files.Length; i++)
+                    {
+                        var attach = $"ATTACH DATABASE '{files[i]}' AS 'db{i}'";
+                        helper.Execute(attach);
+                    }
+                    string sql = string.Join(" UNION ", GetModelSqls());
+                    sql = $"SELECT DISTINCT {name} FROM ({sql}) LIMIT 10";
+
+                    var dt = helper.GetDataTable(sql);
+                    return dt.Select().Select(row => row[name].ToString()).ToArray();
+                }
+                return new string[0];
+            }
+            catch (Exception ex)
+            {
+                return new string[0];
+            }
+        }
+
+
+        private void SearchTextChanged(object sender, Panuon.WPF.SearchTextChangedRoutedEventArgs e)
+        {
+            SearchBox sb = sender as SearchBox;
+            string tag = sb.Tag.ToString(), value = sb.Text;
+            var files = GetFiles(startTimeInput.SelectedDateTime, endTimeInput.SelectedDateTime);
+            Task.Run(() =>
+            {
+                string[] items = new string[0];
+                if (tag == "step" || tag == "status")
+                {
+                    items = GetSource(files, tag, "serialData", value);
+                }
+                else
+                {
+                    items = GetSource(files, tag, "testResult", value);
+                }
+                Dispatcher.BeginInvoke(new Action(() =>
+                {
+                    sb.ItemsSource = items;
+                }), System.Windows.Threading.DispatcherPriority.Normal);
+            });
+        }
+
+        private void Grid_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            var border = sender as Border;
+            var header = border.Tag as DataGridColumnHeader;
+            var col = header.Column as ICustomSortColumn;
+
+            if (col == null) return;
+
+            col.UpdateOther(null);
+
+            var dataGrid = col.GetOwner();
+
+            if (col.IsAsc == null)
+            {
+                col.IsAsc = true;
+
+                if (dataGrid == resultTable)
+                {
+                    sorter = new SortParam { name = header.Column.SortMemberPath, direction = ListSortDirection.Ascending, sort = "ASC" };
+                }
+                else { 
+                    itemSorter = new SortParam { name = header.Column.SortMemberPath, direction = ListSortDirection.Ascending, sort = "ASC" };
+                }
+                
+            }
+            else if (col.IsAsc == true)
+            {
+                col.IsAsc = false;
+                if (dataGrid == resultTable)
+                {
+                    sorter = new SortParam { name = header.Column.SortMemberPath, direction = ListSortDirection.Descending, sort = "DESC" };
+                }
+                else
+                {
+                    itemSorter = new SortParam { name = header.Column.SortMemberPath, direction = ListSortDirection.Descending, sort = "DESC" };
+                }
+            }
+            else
+            {
+                col.IsAsc = null;
+                if (dataGrid == resultTable)
+                {
+                    sorter = null;
+                }
+                else
+                {
+                    itemSorter = null;
+                }
+            }
+
+            if (dataGrid == resultTable)
+            {
+                if (CurrentPage == 1)
+                {
+                    SelectPage(1, this.pageSize);
+                }
+                else
+                {
+                    CurrentPage = 1;
+                }
+            }
+            else
+            {
+                if (TestItemPage == 1)
+                {
+                    RefreshTestItem(this.selectResultId, 1, this.pageSize);
+                }
+                else
+                {
+                    TestItemPage = 1;
+                }
+            }
+        }
+
+        private void page_CurrentPageChanged(object sender, Panuon.WPF.SelectedValueChangedRoutedEventArgs<int> e)
+        {
+            if (sender == page)
+            {
+                SelectPage(e.NewValue, this.pageSize);
+            }
+            else
+            {
+                RefreshTestItem(this.selectResultId, e.NewValue, this.pageSize);
+            }
+        }
+
+        private string selectResultId;
+
+        partial void OnSelectedResultChanged(object value)
+        {
+            if (value != null)
+            {
+                return;
+            }
+            var selectedRow = value as DataRowView;
+            var resultId = selectedRow?["resultId"]?.ToString();
+            this.SelectedStatus = selectedRow?["result"]?.ToString();
+            this.selectResultId = resultId;
+
+            if (TestItemPage == 1)
+            {
+                RefreshTestItem(resultId, 1, this.pageSize);
+            }
+            else
+            {
+                TestItemPage = 1;
+            }
+        }
+
+        private void resultTable_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            var selectedRow = this.SelectedResult as DataRowView;
+            var resultId = selectedRow?["resultId"]?.ToString();
+            this.SelectedStatus = selectedRow?["result"]?.ToString();
+            this.selectResultId = resultId;
+
+            if (TestItemPage == 1)
+            {
+                RefreshTestItem(resultId, 1, this.pageSize);
+            }
+            else
+            {
+                TestItemPage = 1;
+            }
+        }
+    }
+}
diff --git a/Plugin.DataViewer/Plugin.DataViewer.csproj b/Plugin.DataViewer/Plugin.DataViewer.csproj
new file mode 100644
index 0000000..71ea8f2
--- /dev/null
+++ b/Plugin.DataViewer/Plugin.DataViewer.csproj
@@ -0,0 +1,26 @@
+锘�<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Library</OutputType>
+    <TargetFramework>net472</TargetFramework>
+    <Nullable>enable</Nullable>
+    <LangVersion>10.0</LangVersion>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <UseWPF>true</UseWPF>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
+    <PackageReference Include="log4net" Version="3.0.4" />
+    <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
+    <PackageReference Include="Panuon.WPF.UI" Version="1.3.0.2" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\HMI.Base\HMI.Base.csproj" />
+    <ProjectReference Include="..\HMI.Core\HMI.Core.csproj" />
+    <ProjectReference Include="..\HMI.UI\HMI.UI.csproj" />
+    <ProjectReference Include="..\HMI.Util\HMI.Util.csproj" />
+  </ItemGroup>
+
+</Project>
diff --git a/Plugin.TestResult/DataTable.xaml b/Plugin.TestResult/DataTable.xaml
index 1b28592..12a6b61 100644
--- a/Plugin.TestResult/DataTable.xaml
+++ b/Plugin.TestResult/DataTable.xaml
@@ -35,12 +35,12 @@
                 <RowDefinition/>
                 <RowDefinition/>
             </Grid.RowDefinitions>
-            
+
             <Grid.ColumnDefinitions>
                 <ColumnDefinition Width="Auto"/>
                 <ColumnDefinition/>
             </Grid.ColumnDefinitions>
-            
+
             <TextBlock FontSize="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl}, 
 Converter={StaticResource FontSizeConverter}, ConverterParameter='20,940'}"
                    FontWeight="Bold"
@@ -75,7 +75,7 @@
         <!--<local:Chart Grid.Row="2" x:Name="chart" BorderThickness="0,1,0,1" BorderBrush="Black" 
                       MouseDoubleClick="chart_MouseDoubleClick"/>-->
 
-        <StatusBar FontSize="18" Grid.Row="3">
+        <StatusBar FontSize="14" Grid.Row="3">
             <StatusBar.ItemsPanel>
                 <ItemsPanelTemplate>
                     <DockPanel/>
@@ -123,23 +123,29 @@
             </StatusBarItem>
 
             <StatusBarItem >
-                <TextBox IsReadOnly="True" Background="Transparent" BorderThickness="0" Text="{Binding statusText}"/>
+                <TextBox IsReadOnly="True" FontSize="16" Background="Transparent" BorderThickness="0" Text="{Binding statusText}"/>
             </StatusBarItem>
         </StatusBar>
 
-        <Grid Grid.Row="4">
-            <Grid.ColumnDefinitions>
+        <UniformGrid Grid.Row="4" Rows="1">
+            <!--<UniformGrid.ColumnDefinitions>
                 <ColumnDefinition/>
                 <ColumnDefinition/>
+                <ColumnDefinition Width="Auto"/>
                 <ColumnDefinition/>
-            </Grid.ColumnDefinitions>
+            </UniformGrid.ColumnDefinitions>-->
             <Button Click="ScanWindow_Click" Style="{StaticResource PrimaryButton}"
         Content="{DynamicResource Tr.Scan}"/>
             <Button Grid.Column="1" Click="ClearAllSnr_Click" Style="{StaticResource DangerButton}"
+                    IsEnabled="{Binding IsTesting,Converter={StaticResource reverseBool}}"
 Content="{DynamicResource Tr.ClearAllSNR}"/>
             <!--<Button Grid.Column="2" Click="SetStationGlobal_Click" Style="{StaticResource SuccessButton}"
-        Content="{DynamicResource App.Start}"/>-->
-            <Button Grid.Column="2" Click="StartSingleClick">
+        Content="{DynamicResource App.Start}"/> Background="#1296db"-->
+
+            <Button Grid.Column="2" Click="Abort_Click" x:Name="abortBtn"
+        Style="{DynamicResource DangerButton}" Background="#1296db" Content="Abort"/>
+
+            <Button Grid.Column="3" Click="StartSingleClick">
                 <Button.Style>
                     <Style TargetType="Button" BasedOn="{StaticResource SuccessButton}">
                         <Setter Property="Content" Value="{DynamicResource App.Start}"/>
@@ -152,7 +158,6 @@
                     </Style>
                 </Button.Style>
             </Button>
-        </Grid>
-        
+        </UniformGrid>
     </Grid>
 </UserControl>
\ No newline at end of file
diff --git a/Plugin.TestResult/DataTable.xaml.cs b/Plugin.TestResult/DataTable.xaml.cs
index 1ac79ce..2c3272e 100644
--- a/Plugin.TestResult/DataTable.xaml.cs
+++ b/Plugin.TestResult/DataTable.xaml.cs
@@ -1,10 +1,13 @@
-锘縰sing HMI.Core.Setting;
+锘縰sing HMI.Base;
+using HMI.Core.Setting;
 using HMI.Core.Util;
+using HMI.LogUtil;
 using HMI.UI.Util;
 using HMI.Util;
 using Plugin.TestResult;
 using Plugin.TestResult.Util;
 using Plugin.TestResult_WPF.Model;
+using Plugin.TsSupport;
 using SuperSocket.SocketBase.Config;
 using SuperSocket.SocketBase.Protocol;
 using System;
@@ -15,6 +18,7 @@
 using System.Timers;
 using System.Windows;
 using System.Windows.Controls;
+using System.Windows.Markup;
 
 namespace Plugin.TestResult_WPF
 {
@@ -90,7 +94,7 @@
         public int index { get; private set; }
         private readonly int cellCount;
         private readonly string chartTitle;
-        private readonly TestResultView parent;
+        public readonly TestResultView parent;
 
         private BaseConfig config;
         private LiveChart chart;
@@ -137,7 +141,7 @@
             {
                 var cellRow = (i / config.PositionColumn) + 1;
                 var cellCol = (i % config.PositionColumn) + 1;
-                var cellTitle = $"{title}-{cellRow}-{cellCol}";                
+                var cellTitle = $"{title}-{cellRow}-{cellCol}";
 
                 var cell = new SingleCell(this, cellTitle, i, config.CellListMaxSize, countIniPath);
                 mainGrid.Children.Add(cell);
@@ -152,6 +156,7 @@
             testTimeTimer.AutoReset = true;
             testTimeTimer.Elapsed += TestTimeTimer_Elapsed;
             if (!ParamSetting.ShowCoolerSetting) coolerSetting.Visibility = Visibility.Collapsed;
+            if (!ParamSetting.ShowBurnInAbort) abortBtn.Visibility = Visibility.Collapsed;
         }
 
         private void TestTimeTimer_Elapsed(object sender, ElapsedEventArgs e)
@@ -306,13 +311,42 @@
             {
                 if (MsgBoxHelper2.WarnChoose(this.lang("Tr.MakeSureStopOvenTest", title)) == MessageBoxResult.OK)
                 {
+                    LogHelper.Info("UI", $"鑰佸寲鐐塠{title}]鍋滄鎸夐挳鎸変笅", out _);
+                    ViewControl.Logger.Info($"鑰佸寲鐐塠{title}]鍋滄鎸夐挳鎸変笅");
                     parent.SetStartFlag(this.index, false);
                 }
             }
             else
             {
-                ClearChartData();
-                parent.SetStartFlag(this.index, true);
+                /// 妫�鏌ユ槸鍚﹀叧闂�
+                if (parent.executionWrapper?.GetStationGlobalValue<bool>($"StationState.DoorClose[{this.index}]", 0) != true)
+                {
+                    /// 娌″叧闂ㄤ笉鍏佽鍚姩
+                    MsgBoxHelper2.Warn("璇峰厛鍏抽棴鑸遍棬锛�");
+                    return;
+                }
+
+                /// 妫�鏌ユ槸鍚︽湁鏉$爜
+                bool flag = false;
+                foreach (var cell in cells)
+                {
+                    if (!string.IsNullOrEmpty(cell.sn))
+                    {
+                        flag = true;
+                        break;
+                    }
+                }
+                if (flag)
+                {
+                    LogHelper.Info("UI", $"鑰佸寲鐐塠{title}]鍚姩鎸夐挳鎸変笅", out _);
+                    ViewControl.Logger.Info($"鑰佸寲鐐塠{title}]鍚姩鎸夐挳鎸変笅");
+                    ClearChartData();
+                    parent.SetStartFlag(this.index, true);
+                }
+                else
+                {
+                    MsgBoxHelper2.Warn("璇峰厛瀹屾垚鎵爜锛�");
+                }
             }
         }
 
@@ -346,8 +380,9 @@
         private void Scanner_ComplateClick(object sender, RoutedEventArgs e)
         {
             string paramName = $"StationState.Scanner[{index}]";
-            parent.engine.Globals.SetValBoolean(paramName, 1, true);
-            //parent.engine.CommitGlobalsToDisk(promptOnSaveConflicts: false);
+
+            parent.executionWrapper.SetStationGlobalValue<bool>(paramName, 1, true);
+            //parent.engine.Globals.SetValBoolean(paramName, 1, true);
         }
 
         private void Scanner_Closed(object sender, EventArgs e)
@@ -357,7 +392,8 @@
 
         private void Scanner_FinishBarcode(object sender, BarcodeEventArg e)
         {
-            if (parent.SequenceContext == null) return;
+            if (parent.executionWrapper == null) return;
+            //if (parent.SequenceContext == null) return;
 
             int rows = (config.CellCount / config.PositionColumn) + 1;
             CellInfo cellInfo = ScannerParser.GetCellInfoByCode(CbProduct.SelectedItem?.ToString(), mainGrid.Columns, rows, e.positionCode);
@@ -381,9 +417,11 @@
                     throw new Exception(this.lang("Tr.PositionCantClearSNR", oldCellInfo.OriginCode));
                 }
                 // 绉婚櫎鏉$爜
-                parent.engine.Globals.SetValString(oldCellInfo.LookUpStr, 1, "");
-                parent.engine.CommitGlobalsToDisk(promptOnSaveConflicts: false);
+                parent.executionWrapper.SetStationGlobalValue<string>(oldCellInfo.LookUpStr, 1, "");
+                parent.executionWrapper.CommitToDisk(false);
 
+                //parent.engine.Globals.SetValString(oldCellInfo.LookUpStr, 1, "");
+                //parent.engine.CommitGlobalsToDisk(promptOnSaveConflicts: false);
                 oldCell.SetStatus("waiting");
                 oldCell.ClearSN();
                 return;
@@ -397,8 +435,11 @@
             }
 
             // 鍐欏叆鏉$爜
-            parent.engine.Globals.SetValString(LookupStr, 1, snrCode);
-            parent.engine.CommitGlobalsToDisk(promptOnSaveConflicts: false);
+            parent.executionWrapper.SetStationGlobalValue<string>(LookupStr, 1, snrCode);
+            parent.executionWrapper.CommitToDisk(false);
+
+            //parent.engine.Globals.SetValString(LookupStr, 1, snrCode);
+            //parent.engine.CommitGlobalsToDisk(promptOnSaveConflicts: false);
 
             TestResultView.nameTables[cellInfo.OvenName].GetSingleCell(cellInfo.Index).SetStatus("hadCode");
             TestResultView.nameTables[cellInfo.OvenName].GetSingleCell(cellInfo.Index).sn = snrCode;
@@ -532,8 +573,6 @@
             string strs = datas_10015[2];
             string[] values = strs.Split(',');
 
-            //string step = datas_10015[2];
-            //string result = datas_10015[3];
             GetSingleCell(cellIndex).SetInfos(new Info
             {
                 step = values[0],
@@ -747,10 +786,10 @@
 
         private void ClearAllSnr_Click(object sender, RoutedEventArgs e)
         {
-            var str = $"StationState.DoorClose[{index}]";
-            if (parent.engine.Globals.GetValBoolean(str, 0) == true)
+            if (parent.executionWrapper?.GetStationGlobalValue<bool>($"StationState.DoorClose[{this.index}]", 0) == true)
             {
-                MsgBoxHelper.Error(Window.GetWindow(this), this.lang("Tr.CantClearSNR"));
+                /// 鍏抽棬浜嗗氨涓嶅厑璁告竻鏉$爜
+                MsgBoxHelper2.Warn(this.lang("Tr.CantClearSNR"));
                 return;
             }
 
@@ -779,8 +818,7 @@
 
         public void TryClearCellSN(int cellIndex)
         {
-            var str = $"StationState.DoorClose[{this.index}]";
-            if (parent.engine.Globals.GetValBoolean(str, 0) == true)
+            if (parent.executionWrapper?.GetStationGlobalValue<bool>($"StationState.DoorClose[{this.index}]", 0) == true)
             {
                 MsgBoxHelper.Error(Window.GetWindow(this), this.lang("Tr.CantClearSNR"));
                 return;
@@ -795,18 +833,50 @@
             string LookupStr = $"StationState.SN[{dutNo}]";
             var lastSN = cell.sn;
             cell.SetStatus("waiting");
-            parent.engine.Globals.SetValString(LookupStr, 1, "");
-            parent.engine.CommitGlobalsToDisk(promptOnSaveConflicts: false);
+
+            parent.executionWrapper.SetStationGlobalValue<string>(LookupStr, 1, "");
+            parent.executionWrapper.CommitToDisk(false);
+
+            //parent.engine.Globals.SetValString(LookupStr, 1, "");
+            //parent.engine.CommitGlobalsToDisk(promptOnSaveConflicts: false);
             if (!string.IsNullOrEmpty(lastSN)) Scanner.RemoveSNR(lastSN);
             cell.ClearSN();
         }
 
-        private void Button_Click_2(object sender, RoutedEventArgs e)
+        private void Abort_Click(object sender, RoutedEventArgs e)
         {
-            if (MsgBoxHelper.WarnMessage(Window.GetWindow(this), this.lang("Tr.MakeSureStopOvenTest", title)) == MessageBoxResult.OK)
+            if (!parent.IsRunning)
             {
-                parent.SetStartFlag(this.index, false);
+                MsgBoxHelper.WarnMessage(this, this.lang("Tr.StartSeqFirst"), null, MessageBoxButton.OK);
+                return;
             }
+
+
+
+
+            string key = $"StationState.Start[{this.index}]";
+            //if (parent.executionWrapper?.GetStationGlobalValue<bool>(key, 0) == true)
+            //{
+            //    MsgBoxHelper2.Warn("娴嬭瘯鍚姩涓紝涓嶅厑璁窤bort");
+            //    return;
+            //}
+            var win = new RequirePasswordByUser("admin")
+            {
+                Owner = Window.GetWindow(this)
+            };
+            if (win.ShowDialog() != true)
+            {
+                return;
+            }
+
+            if (MsgBoxHelper2.WarnChoose("璇风‘璁よ�佸寲浠撳湪瀹夊叏娓╁害锛屾按鍐锋満鍚规按瀹屾垚锛屽崟鍑籓K鎸夐挳浠撲綅鍥炲埌鍒濆鍖栫姸鎬�") != MessageBoxResult.OK)
+            {
+                return;
+            }
+            LogHelper.Info("UI", "Abort鎸夐挳鎸変笅", out _);
+            ViewControl.Logger.Info("Abort鎸夐挳鎸変笅");
+            key = $"StationState.Abort[{this.index}]";
+            parent.executionWrapper?.SetStationGlobalValue(key, 0, true);
         }
     }
 }
diff --git a/Plugin.TestResult/InfoWindow.xaml b/Plugin.TestResult/InfoWindow.xaml
index 7d9d601..169411f 100644
--- a/Plugin.TestResult/InfoWindow.xaml
+++ b/Plugin.TestResult/InfoWindow.xaml
@@ -33,7 +33,9 @@
               CanUserDeleteRows="False"
               IsReadOnly="True"
               ColumnWidth="*"
-              VirtualizingStackPanel.VirtualizationMode="Standard"
+              VirtualizingPanel.IsContainerVirtualizable="True"
+              VirtualizingPanel.IsVirtualizing="True"
+              VirtualizingStackPanel.VirtualizationMode="Recycling"
               FontSize="16">
             <DataGrid.Resources>
                 <Style TargetType="ScrollBar" BasedOn="{StaticResource ScrollStyle_Mobile}"/>
diff --git a/Plugin.TestResult/Lixun/SNValidator.cs b/Plugin.TestResult/Lixun/SNValidator.cs
new file mode 100644
index 0000000..353b322
--- /dev/null
+++ b/Plugin.TestResult/Lixun/SNValidator.cs
@@ -0,0 +1,91 @@
+锘縰sing HMI.Core.Manager;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Plugin.TestResult.Lixun
+{
+    public class SNValidator
+    {
+        public static bool ValidateSN(string model, string sn, out string msg)
+        {
+            msg = string.Empty;
+            string validate = string.Empty;
+            try
+            {
+                validate = ProductManager.GetParameterByProductAndName(model, "SNRValidate")?.Value;
+                if (string.IsNullOrEmpty(validate))
+                {
+                    msg = $"褰撳墠鍨嬪彿[{model}]鏈厤缃甋NRValidate鍙傛暟锛屾棤娉曟牎楠孲N鍚堟硶鎬�";
+                    return true;
+                }
+                string[] arr = validate.Split(';');
+                if (arr.Length == 1)
+                {
+                    if (!sn.Contains(arr[0]))
+                    {
+                        msg = $"鏉$爜涓嶅寘鍚浐瀹氱爜[{arr[0]}]";
+                        return false;
+                    }
+                    return true;
+                }
+
+                if (arr.Length == 2)
+                {
+                    int length = int.Parse(arr[1]);
+                    if (!sn.Contains(arr[0]))
+                    {
+                        msg = $"鏉$爜涓嶅寘鍚浐瀹氱爜[{arr[0]}]";
+                        return false;
+                    }
+                    if (sn.Length != length)
+                    {
+                        msg = $"鏉$爜闀垮害搴斾负[{length}]";
+                        return false;
+                    }
+                    return true;
+                }
+
+                if (arr.Length >= 3)
+                {
+                    int length = int.Parse(arr[1]);
+                    int startIndex = int.Parse(arr[2]);
+
+                    if (sn.Length != length)
+                    {
+                        msg = $"鏉$爜闀垮害搴斾负[{length}]";
+                        return false;
+                    }
+
+                    if (startIndex >= sn.Length)
+                    {
+                        msg = $"鏉$爜闀胯灏忎簬璧峰涓嬫爣[{validate}]";
+                        return false;
+                    }
+
+                    if (arr[0].Length > sn.Length)
+                    {
+                        msg = $"鏉$爜闀胯灏忎簬鍥哄畾鐮侀暱搴{validate}]";
+                        return false;
+                    }
+
+                    if (sn.Substring(startIndex, arr[0].Length) != arr[0])
+                    {
+                        msg = $"鍥哄畾鐮佹鏌ュけ璐ワ細 [{validate}]";
+                        return false;
+                    }
+                    return true;
+                }
+                msg = $"褰撳墠鍨嬪彿[{model}]閰嶇疆鍙傛暟[{validate}]瑙f瀽澶辫触";
+                return false;
+            }
+            catch (Exception ex)
+            {
+                msg = $"褰撳墠鍨嬪彿[{model}]閰嶇疆鍙傛暟[{validate}]瑙f瀽澶辫触";
+                return false;
+            }
+        }
+    }
+}
diff --git a/Plugin.TestResult/ManualDebugWindow.xaml b/Plugin.TestResult/ManualDebugWindow.xaml
new file mode 100644
index 0000000..9313b73
--- /dev/null
+++ b/Plugin.TestResult/ManualDebugWindow.xaml
@@ -0,0 +1,21 @@
+锘�<pu:WindowX x:Class="Plugin.TestResult.ManualDebugWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        mc:Ignorable="d"
+        xmlns:sb="clr-namespace:Util.SB;assembly=Util.ManualDebug"
+        Title="ManualDebugWindow" 
+        xmlns:pu="https://opensource.panuon.com/wpf-ui"
+        Style="{StaticResource ModalWindow}"
+        Closing="Window_Closing"
+        WindowStartupLocation="CenterScreen"
+        pu:WindowXModalDialog.Buttons="{x:Null}"
+        Height="1000" Width="900">
+    <Grid x:Name="sb">
+        <sb:ManualDebug OvenName="{Binding OvenName,Mode=OneWay}" 
+    CellName="{Binding CellName,Mode=OneWay}" 
+    OvenIndex="{Binding OvenIndex,Mode=OneWay}" 
+    CellIndex="{Binding CellIndex,Mode=OneWay}"/>
+    </Grid>
+</pu:WindowX>
diff --git a/Plugin.TestResult/ManualDebugWindow.xaml.cs b/Plugin.TestResult/ManualDebugWindow.xaml.cs
new file mode 100644
index 0000000..c40668b
--- /dev/null
+++ b/Plugin.TestResult/ManualDebugWindow.xaml.cs
@@ -0,0 +1,95 @@
+锘縰sing HMI.Util;
+using Panuon.WPF.UI;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Windows;
+using Util.SB;
+
+namespace Plugin.TestResult
+{
+    /// <summary>
+    /// ManualDebugWindow.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class ManualDebugWindow : WindowX
+    {
+
+        private ManualDebug debug;
+
+        public ManualDebugWindow(string seqPath, string modelPath)
+        {
+            DataContext = this;
+            InitializeComponent();
+            debug = (ManualDebug)sb.Children[0];
+            ReadBtns();
+            //List<BtnConfig> settings = new List<BtnConfig> { new BtnConfig { Title = "涓婄數婵�娲�", Name = "Active" } };
+            debug.seqPath = seqPath;
+            debug.modelPath = modelPath;
+        }
+
+        private string ovenName;
+        public string OvenName
+        {
+            get => ovenName;
+            set
+            {
+                Set(ref ovenName, value);
+            }
+        }
+
+        private string cellName;
+        public string CellName
+        {
+            get => cellName;
+            set
+            {
+                Set(ref cellName, value);
+            }
+        }
+
+        private int ovenIndex;
+        public int OvenIndex
+        {
+            get => ovenIndex;
+            set
+            {
+                Set(ref ovenIndex, value);
+            }
+        }
+
+        private int cellIndex;
+        public int CellIndex
+        {
+            get => cellIndex;
+            set
+            {
+                Set(ref cellIndex, value);
+            }
+        }
+
+        private void ReadBtns()
+        {
+            string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Settings.ini");
+            var map = IniFileHelper.GetKeyValuesBySection(path, "Btns");
+            List<BtnConfig> settings = new List<BtnConfig>();
+            foreach (var kv in map)
+            {
+                settings.Add(new BtnConfig { Title = kv.Key, Name = kv.Value });
+            }
+            debug.RefreshButtons(settings);
+        }
+
+        private void Window_Closing(object sender, CancelEventArgs e)
+        {
+            if (MessageBox.Show("纭畾瑕佸叧闂悧锛�", "鎻愮ず", MessageBoxButton.OKCancel) == MessageBoxResult.OK)
+            {
+                debug.Stop();
+            }
+            else
+            {
+                e.Cancel = true;
+            }
+        }
+    }
+}
diff --git a/Plugin.TestResult/Model/TcpServer.cs b/Plugin.TestResult/Model/TcpServer.cs
index 056e4ca..2f1fc8c 100644
--- a/Plugin.TestResult/Model/TcpServer.cs
+++ b/Plugin.TestResult/Model/TcpServer.cs
@@ -1,8 +1,6 @@
-锘縰sing ScottPlot.Colormaps;
-using SuperSocket.SocketBase;
+锘縰sing SuperSocket.SocketBase;
 using SuperSocket.SocketBase.Protocol;
 using System;
-using System.Collections;
 using System.Collections.Generic;
 using System.Text;
 
@@ -13,7 +11,6 @@
         public override StringRequestInfo Filter(byte[] readBuffer, int offset, int length, bool toBeCopied, out int rest)
         {
             rest = 0;
-            StringBuilder sb = new StringBuilder();
             int i = offset;
             List<byte> bytes = new List<byte>();
             while (i < readBuffer.Length)
@@ -23,17 +20,33 @@
                 {
                     break;
                 }
-                sb.Append((char)b);
                 bytes.Add(b);
             }
             byte[] arr = bytes.ToArray();
-            return new StringRequestInfo(DateTime.Now + "", Encoding.UTF8.GetString(arr), null);//sb.ToString()
+            return new StringRequestInfo(DateTime.Now.ToString("yyyyMMddHHmmssfff"), Encoding.UTF8.GetString(arr), null);//sb.ToString()
         }
     }
 
+    public class StringParser : IRequestInfoParser<StringRequestInfo>
+    {
+
+        public StringRequestInfo ParseRequestInfo(string source)
+        {
+            return new StringRequestInfo(DateTime.Now.ToString("yyyyMMddHHmmssfff"), source, null);
+        }
+    }
+
+    //public class TcpServer : AppServer<TcpSession>
+    //{
+    //    public TcpServer() : base(new DefaultReceiveFilterFactory<DTReceiveFilter, StringRequestInfo>())
+    //    {
+
+    //    }
+    //}
+
     public class TcpServer : AppServer<TcpSession>
     {
-        public TcpServer() : base(new DefaultReceiveFilterFactory<DTReceiveFilter, StringRequestInfo>())
+        public TcpServer() : base(new TerminatorReceiveFilterFactory("\n", Encoding.UTF8, new StringParser()))
         {
 
         }
diff --git a/Plugin.TestResult/Plugin.TestResult.csproj b/Plugin.TestResult/Plugin.TestResult.csproj
index 18a7c6d..dd34f6b 100644
--- a/Plugin.TestResult/Plugin.TestResult.csproj
+++ b/Plugin.TestResult/Plugin.TestResult.csproj
@@ -103,6 +103,9 @@
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\Libs\NationalInstruments.TestStand.Interop.UI.AxControls.dll</HintPath>
     </Reference>
+    <Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
+    </Reference>
     <Reference Include="OpenTK, Version=3.3.1.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
       <HintPath>..\WPFProject\packages\OpenTK.3.3.1\lib\net20\OpenTK.dll</HintPath>
     </Reference>
@@ -206,6 +209,10 @@
       <DependentUpon>CoolerStatus.xaml</DependentUpon>
     </Compile>
     <Compile Include="CSVPrinter.cs" />
+    <Compile Include="Lixun\SNValidator.cs" />
+    <Compile Include="ManualDebugWindow.xaml.cs">
+      <DependentUpon>ManualDebugWindow.xaml</DependentUpon>
+    </Compile>
     <Compile Include="Model\BaseConfig.cs" />
     <Compile Include="Model\CellInfo.cs" />
     <Compile Include="Model\Info.cs" />
@@ -257,6 +264,10 @@
       <Generator>MSBuild:Compile</Generator>
     </Page>
     <Page Include="InfoWindow.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="ManualDebugWindow.xaml">
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>
@@ -321,7 +332,6 @@
       <Generator>ResXFileCodeGenerator</Generator>
       <LastGenOutput>Resources.Designer.cs</LastGenOutput>
     </EmbeddedResource>
-    <None Include="app.config" />
     <None Include="Config\log4net.config" />
     <None Include="Config\log4net.unix.config" />
     <None Include="packages.config" />
@@ -382,6 +392,14 @@
       <Project>{3068124e-f12b-473e-99a0-2da5a0385d05}</Project>
       <Name>Plugin.TsSupport</Name>
     </ProjectReference>
+    <ProjectReference Include="..\SimpleUtil\HMI.LogUtil\HMI.LogUtil.csproj">
+      <Project>{6d0aab08-0f3e-48ce-b755-9d29bc8f1225}</Project>
+      <Name>HMI.LogUtil</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\Util.ManualDebug\Util.ManualDebug.csproj">
+      <Project>{d0944e62-ee52-4abe-832a-a09d98814db1}</Project>
+      <Name>Util.ManualDebug</Name>
+    </ProjectReference>
   </ItemGroup>
   <ItemGroup>
     <Resource Include="Resource\abort.png" />
diff --git a/Plugin.TestResult/Scanner.xaml.cs b/Plugin.TestResult/Scanner.xaml.cs
index 991d647..48bf1c5 100644
--- a/Plugin.TestResult/Scanner.xaml.cs
+++ b/Plugin.TestResult/Scanner.xaml.cs
@@ -4,6 +4,7 @@
 using HMI.UI.Util;
 using HMI.Util;
 using Panuon.WPF.UI;
+using Plugin.TestResult.Lixun;
 using Plugin.TestResult.Model;
 using Plugin.TestResult.Util;
 using Plugin.TestResult_WPF.Model;
@@ -228,10 +229,34 @@
                     var EnableMes = ParameterManager.GetCommonParameterByName<bool>("EnableMes", false);
                     if (ParamSetting.CustomerCode == "LIXUN" && EnableMes)
                     {
-                        if (PNUtil.GetPN(ParamSetting.MesAddress, snr, out string pn, out string msg))
+                        var table = TestResultView.tables[this.ovenIndex];
+                        string currentPn = table.CbProduct.Text;
+
+                        /// 妫�鏌ユ牸寮�
+                        if (!SNValidator.ValidateSN(currentPn, snr, out string msg))
                         {
-                            var table = TestResultView.tables[this.ovenIndex];
-                            string currentPn = table.CbProduct.Text;
+                            TestResultView.tables[this.ovenIndex].GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
+                            MsgBoxHelper2.Error($"妫�鏌ヤ骇鍝佹潯鐮佸紓甯革細{msg}");
+                            snrText.Text = string.Empty;
+                            snrText.Focus();
+                            return;
+                        }
+
+                        /// 鑾峰彇绔欏悕
+                        string stationName = ProductManager.GetParameterByProductAndName(currentPn, "MES绔欎綅")?.Value;
+                        if (string.IsNullOrEmpty(stationName))
+                        {
+                            TestResultView.tables[this.ovenIndex].GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
+                            MsgBoxHelper2.Error($"妫�鏌ヤ骇鍝佹潯鐮佸紓甯革紝褰撳墠浜у搧[{currentPn}]鏈缃弬鏁癕ES绔欎綅");
+                            snrText.Text = string.Empty;
+                            snrText.Focus();
+                            return;
+                        }
+
+                        /// 妫�鏌N
+                        if (PNUtil.GetPN(ParamSetting.MesAddress, stationName, snr, out string pn, out msg))
+                        {
+                            
                             if (pn != currentPn)
                             {
                                 TestResultView.tables[this.ovenIndex].GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
@@ -249,6 +274,27 @@
                             snrText.Focus();
                             return;
                         }
+
+                        /// 妫�鏌ュ伐搴�
+                        if (PNUtil.CheckIn(ViewControl.Logger, ParamSetting.MesAddress, stationName, snr, out bool needCheckIn, out msg))
+                        {
+                            if (needCheckIn != true)
+                            {
+                                TestResultView.tables[this.ovenIndex].GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
+                                MsgBoxHelper2.Error($"鏉$爜[{snr}]宸ュ簭涓嶅湪鏈珯锛歿msg}");
+                                snrText.Text = string.Empty;
+                                snrText.Focus();
+                                return;
+                            }
+                        }
+                        else
+                        {
+                            TestResultView.tables[this.ovenIndex].GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
+                            MsgBoxHelper2.Error($"鏉$爜[{snr}]CheckIn澶辫触锛歿msg}");
+                            snrText.Text = string.Empty;
+                            snrText.Focus();
+                            return;
+                        }
                     }
                     //var _ = ScannerParser.GetCellInfoByCode(this.model, this.columns, this.rows, snr);
                     //ToastHelper.Warning(this, "浜у搧鐮佹牸寮忓紓甯�");
diff --git a/Plugin.TestResult/SingleCell.xaml b/Plugin.TestResult/SingleCell.xaml
index f3d8cca..9a6c1dc 100644
--- a/Plugin.TestResult/SingleCell.xaml
+++ b/Plugin.TestResult/SingleCell.xaml
@@ -9,6 +9,7 @@
 xmlns:pu="https://opensource.panuon.com/wpf-ui"
 xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI"
              x:Name="uc"
+             PreviewMouseRightButtonDown="OnPreviewMouseRightButtonDown"
              MouseDoubleClick="UserControl_MouseDoubleClick"
              d:DesignHeight="450" d:DesignWidth="800">
     <UserControl.Resources>
@@ -19,13 +20,19 @@
         <ToolTip Padding="5" BorderThickness="1" BorderBrush="Black" Background="Yellow" Content="{Binding tooltipContent}"/>
     </UserControl.ToolTip>-->
 
-    <UserControl.ContextMenu>
+    <!--<UserControl.ContextMenu>
         <ContextMenu>
-            <!--<MenuItem IsCheckable="True" IsChecked="{Binding CellIsDisabled,Mode=TwoWay}"  Header="绂佺敤"/>-->
-            <MenuItem IsCheckable="False" x:Name="clearSNRItem" Click="MenuItem_Click" Header="{DynamicResource Tr.ClearSNR}"/>
+            <MenuItem IsCheckable="False" DataContext="{Binding table}" IsEnabled="{Binding IsTesting,Converter={StaticResource reverseBool}}" x:Name="clearSNRItem" Click="MenuItem_Click" Header="{DynamicResource Tr.ClearSNR}"/>
             <MenuItem IsCheckable="False" x:Name="exportCSVItem" Click="MenuItem_Click" Header="{DynamicResource App.ExportCSV}"/>
+            <MenuItem IsCheckable="False" x:Name="manualDebugItem" Click="MenuItem_Click" Header="鎵嬪姩璋冭瘯">
+                <MenuItem.Style>
+                    <Style TargetType="MenuItem" BasedOn="{StaticResource {x:Static pu:StyleKeys.MenuItemStyle}}">
+
+                    </Style>
+                </MenuItem.Style>
+            </MenuItem>
         </ContextMenu>
-    </UserControl.ContextMenu>
+    </UserControl.ContextMenu>-->
 
     <UserControl.Style>
         <Style TargetType="UserControl">
@@ -65,32 +72,14 @@
     </UserControl.Style>
     <Grid>
         <Grid.RowDefinitions>
-            <RowDefinition Height="Auto"/>
-            <RowDefinition Height="18"/>
+            <RowDefinition />
+            <RowDefinition Height="14"/>
+            <RowDefinition Height="14"/>
         </Grid.RowDefinitions>
         <Grid.ColumnDefinitions>
             <ColumnDefinition/>
             <ColumnDefinition Width="Auto"/>
         </Grid.ColumnDefinitions>
-        <StackPanel Orientation="Horizontal" Background="LightGray" Grid.Row="1" Grid.Column="1" VerticalAlignment="Top"  HorizontalAlignment="Right">
-            <TextBlock Foreground="Red" Text="{DynamicResource Ts.ProbeAlarm}">
-                <TextBlock.Style>
-                    <Style TargetType="TextBlock">
-                        <Setter Property="Visibility" Value="Collapsed"/>
-                        <Style.Triggers>
-                            <DataTrigger Binding="{Binding probeAlarm}" Value="True">
-                                <Setter Property="Visibility" Value="Visible"/>
-                            </DataTrigger>
-                        </Style.Triggers>
-                    </Style>
-                </TextBlock.Style>
-            </TextBlock>
-            <TextBlock Margin="0">
-                <Run Text="{Binding ProbeCount}"/>
-                <Run Text="/"/>
-                <Run Text="{Binding ProbeWarn}"/>
-            </TextBlock>
-        </StackPanel>
         <Label Grid.Row="0" Grid.Column="0" HorizontalContentAlignment="Left" VerticalContentAlignment="Top" Padding="0" Margin="5,5,0,0" 
       FontSize="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl}, 
    Converter={StaticResource FontSizeConverter}, ConverterParameter='8,103'}" 
@@ -106,9 +95,10 @@
                 </Style>
             </Label.Style>
         </Label>
-        <ui:ScrollLabel Grid.Row="1" Grid.Column="0" Margin="3,0,0,5" Padding="0" Value="{Binding sn}"/>
 
-        <Image Grid.Row="0" Grid.Column="1" Width="30" HorizontalAlignment="Right" Height="30" Margin="0,5,5,0">
+        <Image Grid.Row="0" Grid.Column="1" Width="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl}, 
+   Converter={StaticResource FontSizeConverter}, ConverterParameter='10,20'}" HorizontalAlignment="Right"
+               Margin="0,5,5,0">
             <Image.Style>
                 <Style TargetType="Image">
                     <Setter Property="HorizontalAlignment" Value="Center"/>
@@ -148,5 +138,27 @@
                 </Style>
             </Image.Style>
         </Image>
+
+        <ui:ScrollLabel Grid.Row="1" Grid.Column="0" FontSize="10" Grid.ColumnSpan="2" Margin="3,0,0,0" Padding="0" Value="{Binding sn}"/>
+
+        <StackPanel Orientation="Horizontal" Background="LightGray" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" VerticalAlignment="Top" HorizontalAlignment="Right">
+            <TextBlock Foreground="Red" Text="{DynamicResource Ts.ProbeAlarm}" FontSize="10">
+                <TextBlock.Style>
+                    <Style TargetType="TextBlock">
+                        <Setter Property="Visibility" Value="Collapsed"/>
+                        <Style.Triggers>
+                            <DataTrigger Binding="{Binding probeAlarm}" Value="True">
+                                <Setter Property="Visibility" Value="Visible"/>
+                            </DataTrigger>
+                        </Style.Triggers>
+                    </Style>
+                </TextBlock.Style>
+            </TextBlock>
+            <TextBlock Margin="0" FontSize="10">
+        <Run Text="{Binding ProbeCount}"/>
+        <Run Text="/"/>
+        <Run Text="{Binding ProbeWarn}"/>
+            </TextBlock>
+        </StackPanel>
     </Grid>
 </UserControl>
diff --git a/Plugin.TestResult/SingleCell.xaml.cs b/Plugin.TestResult/SingleCell.xaml.cs
index bae64dd..7d5659d 100644
--- a/Plugin.TestResult/SingleCell.xaml.cs
+++ b/Plugin.TestResult/SingleCell.xaml.cs
@@ -3,8 +3,10 @@
 using HMI.Core.Manager;
 using HMI.Core.Setting;
 using HMI.Core.Util;
+using HMI.UI.Util;
 using HMI.Util;
 using PLCHelper;
+using Plugin.TestResult;
 using Plugin.TestResult.Util;
 using Plugin.TestResult_WPF.Constant;
 using Plugin.TestResult_WPF.Model;
@@ -49,7 +51,7 @@
         private InfoWindow window;
         private int maxSize;
 
-        private DataTable table;
+        public DataTable table { get; private set; }
 
         public SingleCell(DataTable table, string title, int index, int maxSize, string iniFile)
         {
@@ -296,47 +298,66 @@
 
         public void SetStatus(string status)
         {
-            var oldStatus = this.status;
-            this.status = status;
-            if (oldStatus != "testing" && status == "testing")
+            Dispatcher.Invoke(() =>
             {
-                UpdateProbeCount(ProbeCount + 1);
-                var now = DateTime.Now;
-                string dateStr = now.ToString("yyyyMMdd");
-                visualTestId = $"{title}_{now.ToString("yyyyMMddHHmmssfff")}";
-                helper = CheckSqlHelper(dateStr);
-            }
+                var oldStatus = this.status;
+                this.status = status;
+                if (oldStatus != "testing" && status == "testing")
+                {
+                    UpdateProbeCount(ProbeCount + 1);
+                    var now = DateTime.Now;
+                    string dateStr = now.ToString("yyyyMMdd");
+                    visualTestId = $"{title}_{now.ToString("yyyyMMddHHmmssfff")}";
+                    helper = CheckSqlHelper(dateStr);
+                    InsertARecourd();
+                }
 
-            if (oldStatus != "ok" && status == "ok")
-            {
-                SaveInfoToDB();
-            }
+                if (oldStatus != "ok" && status == "ok")
+                {
+                    SaveInfoToDB();
+                }
 
-            if (oldStatus != "ng" && status == "ng")
+                if (oldStatus != "ng" && status == "ng")
+                {
+                    SaveInfoToDB();
+                }
+            });
+        }
+
+        private void InsertARecourd()
+        {
+            try
             {
-                SaveInfoToDB();
+                string model = table?.CbProduct?.SelectedItem?.ToString();
+                var timestr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+                var sql = $"INSERT INTO testResult (sn,model,station,position,result,time,resultId) VALUES ('{sn}','{model}','','{title}','{this.status}','{timestr}','{visualTestId}');";
+                helper.Execute(sql);
+            }
+            catch (Exception ex)
+            {
+                ViewControl.Logger.Error($"Cell[{title}] SaveToDB Error.", ex);
             }
         }
 
         public void WriteCSV(Info info)
         {
-            try
-            {
-                if (ParamSetting.CustomerCode != "LIXUN")
-                {
-                    return;
-                }
-                string fileName = this.sn?.Replace("/", "#")?.Replace("\\", "#") + ".csv";
-                string dir = Path.Combine(ParamSetting.DataLogFolder, "csv", this.title);
-                string title = $"{this.lang("Test.Name")},{this.lang("Test.Value")},{this.lang("Test.LimitDown")},{this.lang("Test.LimitUp")},{this.lang("Test.Unit")},{this.lang("Test.Result")},{this.lang("Test.TestTime")}";
-                string line = $"{info.step},{info.value},{info.limitDown},{info.limitUp},{info.unit},{info.result},{info.updateTime}";
+            //try
+            //{
+            //    if (ParamSetting.CustomerCode != "LIXUN")
+            //    {
+            //        return;
+            //    }
+            //    string fileName = this.sn?.Replace("/", "#")?.Replace("\\", "#") + ".csv";
+            //    string dir = Path.Combine(ParamSetting.DataLogFolder, "csv", this.title);
+            //    string title = $"{this.lang("Test.Name")},{this.lang("Test.Value")},{this.lang("Test.LimitDown")},{this.lang("Test.LimitUp")},{this.lang("Test.Unit")},{this.lang("Test.Result")},{this.lang("Test.TestTime")}";
+            //    string line = $"{info.step},{info.value},{info.limitDown},{info.limitUp},{info.unit},{info.result},{info.updateTime}";
 
-                CSVPrinter.Print(dir, fileName, title, line);
-                // ReverseCSV(Path.Combine(dir, fileName));
-            }
-            catch (Exception ex)
-            {
-            }
+            //    CSVPrinter.Print(dir, fileName, title, line);
+            //    // ReverseCSV(Path.Combine(dir, fileName));
+            //}
+            //catch (Exception ex)
+            //{
+            //}
         }
 
         public void SendToChart(Info info)
@@ -378,30 +399,47 @@
                 allInfos.Add(info);
                 SendToChart(info);
                 WriteCSV(info);
-                if (queue.Count >= maxSize)
+
+                /// 鐩墠鍏堜笉闄愬埗鏁伴噺锛屽悗缁鏋滄暟鎹繃澶氬鑷存�ц兘闂鍐嶈�冭檻闄愬埗鏁伴噺
+                //if (queue.Count >= maxSize)
+                //{
+                //    queue.TryDequeue(out var first);
+                //    queue.Enqueue(info);
+                //    this.infos = new ObservableCollection<Info>(queue);
+                //    if (window != null) window.UpdateSource(this.infos);
+                //}
+                //else
+                //{
+                //    queue.Enqueue(info);
+                //    if (window != null)
+                //    {
+                //        window.Dispatcher.Invoke(() =>
+                //        {
+                //            this.infos.Add(info);
+                //        });
+                //    }
+                //    else
+                //    {
+                //        Dispatcher.Invoke(() =>
+                //        {
+                //            this.infos.Add(info);
+                //        });
+                //    }
+                //}
+
+                if (window != null)
                 {
-                    queue.TryDequeue(out var first);
-                    queue.Enqueue(info);
-                    this.infos = new ObservableCollection<Info>(queue);
-                    if (window != null) window.UpdateSource(this.infos);
+                    window.Dispatcher.Invoke(() =>
+                    {
+                        this.infos.Add(info);
+                    });
                 }
                 else
                 {
-                    queue.Enqueue(info);
-                    if (window != null)
+                    Dispatcher.Invoke(() =>
                     {
-                        window.Dispatcher.Invoke(() =>
-                        {
-                            this.infos.Add(info);
-                        });
-                    }
-                    else
-                    {
-                        Dispatcher.Invoke(() =>
-                        {
-                            this.infos.Add(info);
-                        });
-                    }
+                        this.infos.Add(info);
+                    });
                 }
 
                 Dispatcher.Invoke(() =>
@@ -813,8 +851,10 @@
             {
                 try
                 {
+                    string model = table?.CbProduct?.SelectedItem?.ToString();
                     var timestr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
-                    var sql = $"INSERT INTO testResult (sn,station,position,result,time,resultId) VALUES ('{sn}','','{title}','{this.status}','{timestr}','{visualTestId}');";
+                    //var sql = $"INSERT INTO testResult (sn,model,station,position,result,time,resultId) VALUES ('{sn}','{model}','','{title}','{this.status}','{timestr}','{visualTestId}');";
+                    var sql = $"UPDATE testResult SET time = '{timestr}', result = '{this.status}' WHERE resultId = '{visualTestId}'";
                     helper.Execute(sql);
                     helper.Dispose();
                     helper = null;
@@ -850,11 +890,42 @@
                 case "exportCSVItem":
                     TryExportCSV();
                     break;
+                case "manualDebugItem":
+                    OpenManualDebugWindow();
+                    break;
                 default:
                     break;
             }
         }
 
+        /// <summary>
+        /// 鎵撳紑鎵嬪姩璋冭瘯绐楀彛
+        /// </summary>
+        private void OpenManualDebugWindow()
+        {
+            string manualDebugSeq = ParameterManager.GetCommonParameterByName("ManualDebugSeqPath")?.Value;
+            if (string.IsNullOrEmpty(manualDebugSeq))
+            {
+                MsgBoxHelper2.Error("璇峰厛璁剧疆鎵嬪姩璋冭瘯搴忓垪璺緞[ManualDebugSeqPath]!");
+                return;
+            }
+            string modelPath = table.parent.testModelFile;
+            var win = new ManualDebugWindow(manualDebugSeq, modelPath)
+            {
+                Title = $"{this.title}鎵嬪姩璋冭瘯",
+                Owner = Window.GetWindow(this),
+                OvenIndex = this.table.index,
+                OvenName = this.table.title,
+
+                CellIndex = this.index,
+                CellName = this.title
+            };
+            win.ShowDialog();
+        }
+
+        /// <summary>
+        /// 灏濊瘯瀵煎嚭CSV
+        /// </summary>
         private void TryExportCSV()
         {
             System.Windows.Forms.SaveFileDialog sfd = new System.Windows.Forms.SaveFileDialog
@@ -885,5 +956,60 @@
             return S7Context.WriteData(RunTimeVariablePool.DEFAULT_PLC_NAME,
                 $"Oven{table.index + 1}_Cell{index + 1}_Light_NG", on, out msg);
         }
+
+        private void OnPreviewMouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+
+            ContextMenu contextMenu = new ContextMenu();
+            contextMenu.Closed += ContextMenu_Closed;
+
+            //<  />
+            //<
+            //< MenuItem IsCheckable = "False" x: Name = "manualDebugItem" Click = "MenuItem_Click" Header = "鎵嬪姩璋冭瘯" >
+            //    < MenuItem.Style >
+            //        < Style TargetType = "MenuItem" BasedOn = "{StaticResource {x:Static pu:StyleKeys.MenuItemStyle}}" >
+
+            //        </ Style >
+            //    </ MenuItem.Style >
+            //</ MenuItem >
+
+            var item = new MenuItem
+            {
+                IsCheckable = false,
+                IsEnabled = !table.IsTesting,
+                Name = "clearSNRItem",
+                Header = this.lang("Tr.ClearSNR"),
+            };
+            item.Click += MenuItem_Click;
+            contextMenu.Items.Add(item);
+
+            item = new MenuItem
+            {
+                IsCheckable = false,
+                Name = "exportCSVItem",
+                Header = this.lang("App.ExportCSV"),
+            };
+            item.Click += MenuItem_Click;
+            contextMenu.Items.Add(item);
+
+            if (table.parent.inManualDebugMode)
+            {
+                item = new MenuItem
+                {
+                    IsCheckable = false,
+                    Name = "manualDebugItem",
+                    Header = this.lang("鎵嬪姩璋冭瘯"),
+                };
+                item.Click += MenuItem_Click;
+                contextMenu.Items.Add(item);
+            }
+
+            this.ContextMenu = contextMenu;
+        }
+
+        private void ContextMenu_Closed(object sender, RoutedEventArgs e)
+        {
+            this.ContextMenu = null;
+        }
     }
 }
diff --git a/Plugin.TestResult/TestResultView.xaml.cs b/Plugin.TestResult/TestResultView.xaml.cs
index 1c93201..3712dc3 100644
--- a/Plugin.TestResult/TestResultView.xaml.cs
+++ b/Plugin.TestResult/TestResultView.xaml.cs
@@ -8,6 +8,7 @@
 using Plugin.TsSupport;
 using System;
 using System.Collections.Generic;
+using System.Runtime.InteropServices;
 using System.Windows;
 using System.Windows.Controls;
 
@@ -26,23 +27,31 @@
         public static Dictionary<string, DataTable> nameTables { get; private set; } = new Dictionary<string, DataTable>();
         private readonly BaseConfig config;
 
-        public AxApplicationMgr testApp;
+
+
         public string testModelFile;
         public string testSeqFile;
-        /// <summary>
-        /// 娴嬭瘯搴忓垪
-        /// </summary>
-        private SequenceFile testSeq = null;
-        public Engine engine;
-        private Execution execution = null;
-        /// <summary>
-        /// 娴嬭瘯搴忓垪鎿嶄綔鍙ユ焺
-        /// </summary>
-        public SequenceContext SequenceContext { get; private set; } = null;
-        /// <summary>
-        /// 娴嬭瘯妯″瀷搴忓垪
-        /// </summary>
-        private SequenceFile modeSeq;
+        public bool inManualDebugMode = false;
+
+        #region TS鐩稿叧
+        //public AxApplicationMgr testApp;
+        ///// <summary>
+        ///// 娴嬭瘯搴忓垪
+        ///// </summary>
+        //private SequenceFile testSeq = null;
+        //public Engine engine;
+        //private Execution execution = null;
+        ///// <summary>
+        ///// 娴嬭瘯搴忓垪鎿嶄綔鍙ユ焺
+        ///// </summary>
+        //public SequenceContext SequenceContext { get; private set; } = null;
+        ///// <summary>
+        ///// 娴嬭瘯妯″瀷搴忓垪
+        ///// </summary>
+        //private SequenceFile modeSeq;
+
+        public ITestExecutionWrapper executionWrapper;
+        #endregion
 
         public TestResultView() : this(new BaseConfig { testing = false })
         {
@@ -53,7 +62,7 @@
 
         public void RefreshCooler()
         {
-            
+
         }
 
         private void RefreshNumberConfig(BaseConfig config)
@@ -133,18 +142,27 @@
 
 
             InitializeComponent();
-            testApp = TestAppManager.GetAxApplicationMgr();
-            testApp.ExitApplication += this.axApplicationMgr_ExitApplication;
-            testApp.UIMessageEvent += this.axApplicationMgr_UIMessageEvent;
-            testApp.ExecutionClosed += this.axApplicationMgr_ExecutionClosed;
+
+            executionWrapper = new TsExecutionWrapper(); //new TsExecutionWrapper();
+            if (executionWrapper is TsExecutionWrapper tsWrapper)
+            {
+                tsWrapper.AxApplicationMgr.ExitApplication += this.axApplicationMgr_ExitApplication;
+                tsWrapper.AxApplicationMgr.UIMessageEvent += this.axApplicationMgr_UIMessageEvent;
+                tsWrapper.AxApplicationMgr.ExecutionClosed += this.axApplicationMgr_ExecutionClosed;
+                ((System.ComponentModel.ISupportInitialize)(tsWrapper.AxApplicationMgr)).BeginInit();//myClient涓虹涓夋柟鎺т欢鍚�
+                host.Child = tsWrapper.AxApplicationMgr;
+                ((System.ComponentModel.ISupportInitialize)(tsWrapper.AxApplicationMgr)).EndInit();
+            }
 
 
-            ((System.ComponentModel.ISupportInitialize)(this.testApp)).BeginInit();//myClient涓虹涓夋柟鎺т欢鍚�
-            host.Child = testApp;
-            ((System.ComponentModel.ISupportInitialize)(this.testApp)).EndInit();
 
-            //InitTestStandEngine();
-            //StartTestSequence();
+            //testApp = TestAppManager.GetAxApplicationMgr();
+            //testApp.ExitApplication += this.axApplicationMgr_ExitApplication;
+            //testApp.UIMessageEvent += this.axApplicationMgr_UIMessageEvent;
+            //testApp.ExecutionClosed += this.axApplicationMgr_ExecutionClosed;
+            //((System.ComponentModel.ISupportInitialize)(this.testApp)).BeginInit();//myClient涓虹涓夋柟鎺т欢鍚�
+            //host.Child = testApp;
+            //((System.ComponentModel.ISupportInitialize)(this.testApp)).EndInit();
         }
 
         private void Refresh()
@@ -203,12 +221,13 @@
         {
             try
             {
-                this.engine = this.testApp.GetEngine();
-                this.engine.PersistBreakpoints = false;
-                this.testApp.IsEditor = true;
-                this.testApp.LoginOnStart = false;
-                this.testApp.Start();
-                this.modeSeq = this.engine.GetSequenceFileEx(testModelFile);
+                //this.engine = this.testApp.GetEngine();
+                //this.engine.PersistBreakpoints = false;
+                //this.testApp.IsEditor = true;
+                //this.testApp.LoginOnStart = false;
+                //this.testApp.Start();
+                //this.modeSeq = this.engine.GetSequenceFileEx(testModelFile);
+                executionWrapper.Init();
             }
             catch (Exception ex)
             {
@@ -222,10 +241,10 @@
             try
             {
                 if (string.IsNullOrEmpty(testSeqFile)) return;
-                this.modeSeq = this.engine.GetSequenceFileEx(@"C:\Program Files\National Instruments\TestStand 2020\Components\Models\TestStandModels\ParallelModel.seq");
-                this.testSeq = this.engine.GetSequenceFileEx(this.testSeqFile, 0, TypeConflictHandlerTypes.ConflictHandler_UseGlobalType);
-                this.execution = this.engine.NewExecution(this.testSeq, "Test UUTs", this.modeSeq, false, 0, null, null, null);
-                this.engine.Globals.SetValBoolean("StationState.StopTest", 0, false);
+                executionWrapper?.StartSequence(this.testSeqFile,
+                    @"C:\Program Files\National Instruments\TestStand 2020\Components\Models\TestStandModels\ParallelModel.seq");
+
+                executionWrapper.SetStationGlobalValue<bool>("StationState.StopTest", 0, false);
             }
             catch (Exception ex)
             {
@@ -238,13 +257,24 @@
         {
             try
             {
-                if (this.SequenceContext == null)
-                    return;
-                bool val = this.SequenceContext.StationGlobals.Exists("StationState.StopTest", 0);
-                if (val)
+                executionWrapper?.SetStationGlobalValue<bool>("StationState.StopTest", 0, true, true);
+                
+                try
                 {
-                    this.SequenceContext.StationGlobals.SetValBoolean("StationState.StopTest", 0, true);
-                    bool isStop = this.execution.WaitForEndEx(2000, true, null, null);
+                    executionWrapper?.WaitForExit();
+                    executionWrapper?.ReleaseSeqFile();
+                }
+                catch (Exception ex)
+                {
+                    ViewControl.Logger.Error("Wait For Exit error.", ex);
+                    //if (ex is NullReferenceException ne)
+                    //{
+
+                    //}
+                    //else
+                    //{
+                    //    throw ex;
+                    //}
                 }
             }
             catch (Exception ex)
@@ -256,7 +286,7 @@
 
         private void axApplicationMgr_ExitApplication(object sender, EventArgs e)
         {
-            Environment.ExitCode = testApp.ExitCode;
+            //Environment.ExitCode = testApp.ExitCode;
             //TSHelper.DoSynchronousGCForCOMObjectDestruction();
         }
 
@@ -286,7 +316,11 @@
                 if (MethodCode == 0)
                 {
                     /// 璁剧疆鍙ユ焺
-                    this.SequenceContext = (SequenceContext)e.uiMsg.ActiveXData;
+                    //this.SequenceContext = (SequenceContext)e.uiMsg.ActiveXData;
+                    if (executionWrapper is TsExecutionWrapper tsWrapper)
+                    {
+                        tsWrapper.context = (SequenceContext)e.uiMsg.ActiveXData;
+                    }
                     return;
                 }
 
@@ -425,6 +459,7 @@
                     int ovenIndex = int.Parse(datas_10015[0]);
                     int cellIndex = int.Parse(datas_10015[1]);
                     tables[ovenIndex].GetSingleCell(cellIndex).ClearInfo();
+                    //GC.Collect();
                     return;
                 }
                 if (MethodCode == 12)
@@ -550,11 +585,13 @@
                     int index = int.Parse(datas[0]);
                     if (MsgBoxHelper2.WarnChoose(datas[1], "鎻愮ず") == MessageBoxResult.OK)
                     {
-                        SequenceContext.StationGlobals.SetValNumber($"StationState.Pipe[{index}]", 1, 1);
+                        executionWrapper.SetStationGlobalValue<int>($"StationState.Pipe[{index}]", 1, 1);
+                        //SequenceContext.StationGlobals.SetValNumber($"StationState.Pipe[{index}]", 1, 1);
                     }
                     else
                     {
-                        SequenceContext.StationGlobals.SetValNumber($"StationState.Pipe[{index}]", 1, 0);
+                        executionWrapper.SetStationGlobalValue<int>($"StationState.Pipe[{index}]", 1, 0);
+                        //SequenceContext.StationGlobals.SetValNumber($"StationState.Pipe[{index}]", 1, 0);
                     }
                     return;
                 }
@@ -567,20 +604,34 @@
 
         public void SetStartFlag(int index, bool data)
         {
-            if (this.SequenceContext == null) return;
             string name = tables[index].title;
+            ViewControl.Logger?.Info($"灏嗚�佸寲鐐塠{name}] 鐨凷tart鍙橀噺璁剧疆涓� {data};");
             string key = $"StationState.Start[{index}]";
-            this.engine.Globals.SetValBoolean(key, 1, data);
-            //bool val = this.engine.Globals.GetValBoolean(key, 0);
-            //this.SequenceContext.StationGlobals.SetValBoolean(key, 0, data);
-            //bool val = this.SequenceContext.StationGlobals.GetValBoolean(key, 0);
+
+            executionWrapper?.SetStationGlobalValue<bool>(key, 1, data);
+
+            if (data == false)
+            {
+                key = $"StationState.Scanner[{index}]";
+                executionWrapper?.SetStationGlobalValue<bool>(key, 1, false);
+            }
+        }
+
+        public bool? GetStartFlag(int index)
+        {
+            string key = $"StationState.Start[{index}]";
+            return executionWrapper.GetStationGlobalValue<bool>(key, 0);
         }
 
         public void ReleaseResource()
         {
-            testApp.ExitApplication -= this.axApplicationMgr_ExitApplication;
-            testApp.UIMessageEvent -= this.axApplicationMgr_UIMessageEvent;
-            testApp.ExecutionClosed -= this.axApplicationMgr_ExecutionClosed;
+            if (executionWrapper is TsExecutionWrapper tsWrapper)
+            {
+                tsWrapper.AxApplicationMgr.ExitApplication -= this.axApplicationMgr_ExitApplication;
+                tsWrapper.AxApplicationMgr.UIMessageEvent -= this.axApplicationMgr_UIMessageEvent;
+                tsWrapper.AxApplicationMgr.ExecutionClosed -= this.axApplicationMgr_ExecutionClosed;
+            }
+
             foreach (var table in tables)
             {
                 table.RelaseResource();
@@ -611,9 +662,10 @@
             for (int i = 0; i < tables.Length; i++)
             {
                 var table = tables[i];
-                // table.SetCurrentProduct(currentProduct);
                 string LookupStr = $"StationState.Model[{i}]";
-                string tableModel = engine?.Globals?.GetValString(LookupStr, 1);
+
+                string tableModel = executionWrapper?.GetStationGlobalValue<string>(LookupStr, 1);
+                //engine?.Globals?.GetValString(LookupStr, 1);
                 table.SetCurrentProduct(tableModel);
             }
         }
@@ -621,8 +673,12 @@
         public void SetOvenProduct(int index, string product)
         {
             string LookupStr = $"StationState.Model[{index}]";
-            engine?.Globals?.SetValString(LookupStr, 1, product);
-            engine?.CommitGlobalsToDisk(promptOnSaveConflicts: false);
+            executionWrapper?.SetStationGlobalValue<string>(LookupStr, 1, product);
+            executionWrapper?.CommitToDisk(false);
+        }
+
+        public void RefreshProducts()
+        {
         }
     }
 }
diff --git a/Plugin.TestResult/Util/PNUtil.cs b/Plugin.TestResult/Util/PNUtil.cs
index 0b80a8b..d7bf7ec 100644
--- a/Plugin.TestResult/Util/PNUtil.cs
+++ b/Plugin.TestResult/Util/PNUtil.cs
@@ -1,4 +1,7 @@
 锘縰sing HMI.Core.Util;
+using log4net;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
 using Plugin.TestResult_WPF;
 using System;
 using System.Net.Http;
@@ -15,13 +18,14 @@
 
     public class PNUtil
     {
-        public static bool GetPN(string mesAddress, string sn, out string pn, out string msg)
+        public static bool GetPN(string mesAddress, string stationName, string sn, out string pn, out string msg)
         {
             pn = null;
             msg = string.Empty;
             try
             {
-                string json = $"{{\"csn\":\"{sn}\",\"station_name\":\"OP170Burn-In鑰佸寲\"}}";
+                //OP170Burn-In鑰佸寲
+                string json = $"{{\"csn\":\"{sn}\",\"station_name\":\"{stationName}\"}}";
                 HttpClient val = new HttpClient();
                 ViewControl.Logger.Debug($"Get PN Send: [{json}]");
                 try
@@ -52,5 +56,53 @@
                 return false;
             }
         }
+
+
+        public static bool CheckIn(ILog log, string mesAddress, string stationName, string sn, out bool needCheckIn, out string msg)
+        {
+            JObject data = new JObject();
+            data.Add("cmd", "QUERY_RECORD");
+            data.Add("p", "UOP");
+
+            data.Add("sn", sn);
+            data.Add("station_name", stationName);
+            data.Add("op", "A0000001");
+
+            needCheckIn = false;
+            msg = string.Empty;
+            string sendData = string.Empty;
+            string resData = null;
+            try
+            {
+                sendData = data.ToString(Formatting.None);
+                using (HttpClient val = new HttpClient())
+                {
+                    StringContent val2 = new StringContent(sendData, Encoding.UTF8, "application/json");
+                    HttpResponseMessage result = val.PostAsync("http://" + mesAddress + "/mes-module-system-2.1.1/lixun/api/checkIn", (HttpContent)(object)val2).Result;
+                    result.EnsureSuccessStatusCode();
+                    resData = result.Content.ReadAsStringAsync().Result;
+                    JObject httpResult = JObject.Parse(resData);
+
+                    log.Info("CheckIn Send: " + httpResult.Value<string>("sendStr"));
+                    log.Info("CheckIn Receive: " + httpResult.Value<string>("receiveStr"));
+                    log.Info("CheckIn Get: " + resData);
+
+                    msg = httpResult.Value<string>("msg");
+                    if (httpResult.Value<int>("code") == 200)
+                    {
+                        needCheckIn = httpResult.Value<bool>("needCheckIn");
+                        return true;
+                    }
+                    log.Error("CheckIn Error: " + msg);
+                    return false;
+                }
+            }
+            catch (Exception ex)
+            {
+                msg = ex.Message + " in " + ex.StackTrace + ".SendStr: " + sendData + ",ResultStr: " + resData;
+                log.Error("CheckIn Unknow Error: " + msg);
+                return false;
+            }
+        }
     }
 }
diff --git a/Plugin.TestResult/ViewControl.xaml b/Plugin.TestResult/ViewControl.xaml
index 27fb5e6..0b2de18 100644
--- a/Plugin.TestResult/ViewControl.xaml
+++ b/Plugin.TestResult/ViewControl.xaml
@@ -41,11 +41,24 @@
             <ColumnDefinition Width="*"/>
         </Grid.ColumnDefinitions>-->
         <local:TestResultView x:Name="testResultView" Grid.Row="1" Grid.Column="0" Loaded="testResultView_Loaded" />
-        <DockPanel Margin="10,0" Grid.Row="0" Grid.Column="1" Background="#FFF" VerticalAlignment="Center">
+        <DockPanel Margin="10,0" Grid.Row="0" Grid.Column="1" LastChildFill="False" Background="#FFF" VerticalAlignment="Center">
             <pu:FormGroup DockPanel.Dock="Right">
                 <StackPanel Orientation="Horizontal" VerticalAlignment="Center" dp:StackPanelHelper.ChildSpace="20">
-                    <Button x:Name="btnStart" Click="Start_Click" Style="{StaticResource SuccessButton}"
-                       Width="100" FontSize="14" Padding="0" Height="30" Content="{DynamicResource App.Start}"/>
+                    <Button x:Name="btnStart" Click="Start_Click"
+                       Width="100" FontSize="14" Padding="0" Height="30" Content="{DynamicResource App.Start}">
+                        <Button.Style>
+                            <Style TargetType="Button" BasedOn="{StaticResource SuccessButton}">
+                                <Style.Triggers>
+                                    <DataTrigger Binding="{Binding EnableManualDebug}" Value="True">
+                                        <Setter Property="IsEnabled" Value="False"/>
+                                    </DataTrigger>
+                                    <DataTrigger Binding="{Binding IsRunning}" Value="True">
+                                        <Setter Property="IsEnabled" Value="False"/>
+                                    </DataTrigger>
+                                </Style.Triggers>
+                            </Style>
+                        </Button.Style>
+                    </Button>
 
                     <Button x:Name="btnStop" Click="Stop_Click" Style="{StaticResource DangerButton}"
                             Width="100" FontSize="14" Padding="0" Height="30"
@@ -83,6 +96,13 @@
                                   ValueChanged="StdEnableMes_ValueChanged"/>
             </pu:FormGroup>
 
+            <pu:FormGroup x:Name="FgEnableManualDebug" Visibility="Collapsed">
+                <CheckBox FontSize="20"
+                          IsChecked="{Binding EnableManualDebug,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" 
+          VerticalContentAlignment="Center"
+          Content="鎵嬪姩璋冭瘯" />
+            </pu:FormGroup>
+
 
             <!--<pu:FormGroup Header="{DynamicResource Tr.Status}" FontSize="16">
                 <StackPanel Orientation="Horizontal" dp:StackPanelHelper.ChildSpace="10">
diff --git a/Plugin.TestResult/ViewControl.xaml.cs b/Plugin.TestResult/ViewControl.xaml.cs
index 47d1fe5..d7e2f96 100644
--- a/Plugin.TestResult/ViewControl.xaml.cs
+++ b/Plugin.TestResult/ViewControl.xaml.cs
@@ -4,6 +4,7 @@
 using HMI.Core.Manager;
 using HMI.Core.Setting;
 using HMI.Core.Util;
+using HMI.LogUtil;
 using HMI.StdControl;
 using HMI.UI.Util;
 using log4net;
@@ -11,6 +12,7 @@
 using NationalInstruments.TestStand.Interop.UI.Ax;
 using PLCHelper;
 using Plugin.TestResult.Util;
+using Plugin.TsSupport;
 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
@@ -25,10 +27,6 @@
     /// </summary>
     public partial class ViewControl : PluginMaskControl, IAlarmHandler
     {
-        /// <summary>
-        /// 娴嬭瘯搴忓垪鎿嶄綔鍙ユ焺
-        /// </summary>
-        private SequenceContext SequenceContext = null;
 
         private System.Timers.Timer timer;
 
@@ -59,6 +57,22 @@
             set => Set(ref _fileName, value);
         }
 
+        private bool enableManualDebug;
+        public bool EnableManualDebug
+        {
+            get => enableManualDebug;
+            set
+            {
+                if (IsRunning)
+                {
+                    MsgBoxHelper2.Warn("璇峰厛缁撴潫鑷姩娴嬭瘯");
+                    return;
+                }
+                testResultView.inManualDebugMode = value;
+                Set(ref enableManualDebug, value);
+            }
+        }
+
         public override void SetLogger(ILog logger)
         {
             base.SetLogger(logger);
@@ -74,13 +88,20 @@
                 Set(ref isRunning, value);
                 testResultView.IsRunning = value;
                 this.modelSelect.IsEnabled = !value;
-                this.btnStart.IsEnabled = !value;
                 this.btnStop.IsEnabled = value;
                 stdEnableMes.CbEnabled = !value;
             }
         }
 
         private bool AutoExecuteSeq = true;
+
+        #region TS鐩稿叧
+
+        /// <summary>
+        /// 娴嬭瘯搴忓垪鎿嶄綔鍙ユ焺
+        /// </summary>
+        private SequenceContext SequenceContext = null;
+        #endregion
 
         public ViewControl()
         {
@@ -90,9 +111,18 @@
             timer.Interval = 1000;
             timer.Elapsed += Timer_Elapsed;
             InitializeComponent();
-            testResultView.testApp.ExitApplication += new EventHandler(this.axApplicationMgr_ExitApplication);
-            testResultView.testApp.UIMessageEvent += new _ApplicationMgrEvents_UIMessageEventEventHandler(this.axApplicationMgr_UIMessageEvent);
-            testResultView.testApp.ExecutionClosed += new _ApplicationMgrEvents_ExecutionClosedEventHandler(this.axApplicationMgr_ExecutionClosed);
+
+            if (testResultView.executionWrapper is TsExecutionWrapper tsWrapper)
+            {
+                tsWrapper.AxApplicationMgr.ExitApplication += new EventHandler(this.axApplicationMgr_ExitApplication);
+                tsWrapper.AxApplicationMgr.UIMessageEvent += new _ApplicationMgrEvents_UIMessageEventEventHandler(this.axApplicationMgr_UIMessageEvent);
+                tsWrapper.AxApplicationMgr.ExecutionClosed += new _ApplicationMgrEvents_ExecutionClosedEventHandler(this.axApplicationMgr_ExecutionClosed);
+            }
+
+
+            //testResultView.testApp.ExitApplication += new EventHandler(this.axApplicationMgr_ExitApplication);
+            //testResultView.testApp.UIMessageEvent += new _ApplicationMgrEvents_UIMessageEventEventHandler(this.axApplicationMgr_UIMessageEvent);
+            //testResultView.testApp.ExecutionClosed += new _ApplicationMgrEvents_ExecutionClosedEventHandler(this.axApplicationMgr_ExecutionClosed);
         }
 
         private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
@@ -144,26 +174,35 @@
                 testResultView.testSeqFile = string.Empty;
                 FileName = string.Empty;
             }
+            RefreshUIConfig();
         }
 
-        public override void DoClose(object sender, EventArgs e)
+        /// <summary>
+        /// 鍒锋柊UI閰嶇疆
+        /// </summary>
+        private void RefreshUIConfig()
         {
             try
             {
-                timer?.Stop();
-                testResultView.ReleaseResource();
+                /// 杩愯涓氨涓嶇
+                if (IsRunning)
+                {
+                    return;
+                }
+                bool enableManualDebug = ParameterManager.GetCommonParameterByName("EnableManualDebug", false);
+                if (enableManualDebug)
+                {
+                    FgEnableManualDebug.Visibility = Visibility.Visible;
+                }
+                else
+                {
+                    FgEnableManualDebug.Visibility = Visibility.Collapsed;
+                    EnableManualDebug = false;
+                }
             }
-            catch
+            catch (Exception ex)
             {
-            }
-        }
-
-        public override void OnClose(object sender, CancelEventArgs e)
-        {
-            if (IsRunning)
-            {
-                MsgBoxHelper2.Warn("璇峰厛鍋滄鑰佸寲娴嬭瘯锛�");
-                e.Cancel = true;
+                log.Error("RefreshUIConfig Error.", ex);
             }
         }
 
@@ -265,18 +304,20 @@
                 productSource.Add(prd.Name);
             }
             testResultView.SetProductSource(productSource);
+            testResultView.SetCurrentProduct(null);
 
-            string model = testResultView.engine.Globals.GetValString("ProductModel", 1);
-            if (!string.IsNullOrEmpty(model))
-            {
-                this.modelSelect.SelectedItem = model;
-                testResultView.SetCurrentProduct(model);
-            }
-            else if (products.Count > 0)
-            {
-                this.modelSelect.SelectedIndex = 0;
-                testResultView.SetCurrentProduct(products[0].Name);
-            }
+            //string model = testResultView.executionWrapper.GetStationGlobalValue<string>("ProductModel", 1);
+            ////testResultView.engine.Globals.GetValString("ProductModel", 1);
+            //if (!string.IsNullOrEmpty(model))
+            //{
+            //    this.modelSelect.SelectedItem = model;
+            //    testResultView.SetCurrentProduct(model);
+            //}
+            //else if (products.Count > 0)
+            //{
+            //    this.modelSelect.SelectedIndex = 0;
+            //    testResultView.SetCurrentProduct(products[0].Name);
+            //}
         }
 
         private void modelSelect_SelectionChanged(object sender, SelectionChangedEventArgs e)
@@ -285,8 +326,12 @@
             {
                 string currentModel = this.modelSelect.SelectedItem.ToString();
                 List<ParameterData> parameters = ProductManager.GetParametersByProductName(currentModel);
-                testResultView.engine.Globals.SetValString("ProductModel", 0, currentModel);
-                testResultView.engine.CommitGlobalsToDisk(promptOnSaveConflicts: false);
+
+                testResultView.executionWrapper.SetStationGlobalValue<string>("ProductModel", 0, currentModel);
+                testResultView.executionWrapper.CommitToDisk(false);
+
+                //testResultView.engine.Globals.SetValString("ProductModel", 0, currentModel);
+                //testResultView.engine.CommitGlobalsToDisk(promptOnSaveConflicts: false);
             }
             catch (Exception ex)
             {
@@ -304,6 +349,8 @@
                     IsRunning = false;
                     return;
                 }
+                LogHelper.Info("UI", "鎬诲惎鍔ㄦ寜閽寜涓�", out _);
+                log.Info("鎬诲惎鍔ㄦ寜閽寜涓�");
                 ShowMask(() =>
                 {
                     testResultView.StartTestSequence();
@@ -332,7 +379,30 @@
         {
             try
             {
+                bool breakStop = false;
+                foreach (var oven in TestResultView.tables)
+                {
+                    if (oven.IsTesting)
+                    {
+                        breakStop = true;
+                        break;
+                    }
+                }
+                if (breakStop)
+                {
+                    MsgBoxHelper2.Warn(this.lang("Tr.OvenIsTesting"));
+                    return;
+                }
+
+                if (MsgBoxHelper2.WarnChoose(this.lang("Tr.MakeSureStopAllTest")) != MessageBoxResult.OK)
+                {
+                    return;
+                }
+
+                LogHelper.Info("UI", "鎬诲仠姝㈡寜閽寜涓�", out _);
+                log.Info("鎬诲仠姝㈡寜閽寜涓�");
                 testResultView.StopTest();
+                /// 宸茬粡鍔犱簡WaitForExit,杩欓噷鍙互鏀逛负false
                 IsRunning = false;
             }
             catch (Exception ex)
@@ -368,13 +438,15 @@
 
         private void StdEnableMes_ValueChanged(object sender, StdUIEventArg e)
         {
-            testResultView.SequenceContext?.StationGlobals.SetValBoolean($"SystemSetting.{e.StringValue}", 1, e.BoolValue);
+            testResultView.executionWrapper.SetStationGlobalValue<bool>($"SystemSetting.{e.StringValue}", 1, e.BoolValue);
+            //testResultView.SequenceContext?.StationGlobals.SetValBoolean($"SystemSetting.{e.StringValue}", 1, e.BoolValue);
         }
 
         public void HandleAlarm(AlarmText alarm)
         {
             if (alarm.Level >= 4 && TestResultView.tables != null)
             {
+                log.Info($"4绾ф姤璀﹁Е鍙�: [{alarm.AlarmInfo}],灏嗗仠姝㈡墍鏈夌倝浣撴祴璇�");
                 foreach (var oven in TestResultView.tables)
                 {
                     testResultView.SetStartFlag(oven.index, false);
@@ -398,8 +470,37 @@
             if (alarm.Level >= 3)
             {
                 /// 3绾т互涓婄殑鎶ヨ浼氬仠姝㈡祴璇�
+                log.Info($"3绾ф姤璀﹁Е鍙�: [{alarm.AlarmInfo}],灏嗗仠姝㈣�佸寲鐐塠{targetOven.title}]娴嬭瘯");
                 testResultView.SetStartFlag(targetOven.index, false);
             }
         }
+
+        public override void Active()
+        {
+            base.Active();
+            LoadProduct();
+            RefreshUIConfig();
+        }
+
+        public override void OnClose(object sender, CancelEventArgs e)
+        {
+            if (IsRunning)
+            {
+                MsgBoxHelper2.Warn("璇峰厛鍋滄鑰佸寲娴嬭瘯锛�");
+                e.Cancel = true;
+            }
+        }
+
+        public override void DoClose(object sender, EventArgs e)
+        {
+            try
+            {
+                timer?.Stop();
+                testResultView.ReleaseResource();
+            }
+            catch
+            {
+            }
+        }
     }
 }
diff --git a/Plugin.TestResult/app.config b/Plugin.TestResult/app.config
deleted file mode 100644
index 2a0b690..0000000
--- a/Plugin.TestResult/app.config
+++ /dev/null
@@ -1,15 +0,0 @@
-锘�<?xml version="1.0" encoding="utf-8"?>
-<configuration>
-  <runtime>
-    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
-      <dependentAssembly>
-        <assemblyIdentity name="log4net" publicKeyToken="669e0ddf0bb1aa2a" culture="neutral" />
-        <bindingRedirect oldVersion="0.0.0.0-3.0.4.0" newVersion="3.0.4.0" />
-      </dependentAssembly>
-      <dependentAssembly>
-        <assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
-        <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
-      </dependentAssembly>
-    </assemblyBinding>
-  </runtime>
-</configuration>
\ No newline at end of file
diff --git a/Plugin.TestResult/packages.config b/Plugin.TestResult/packages.config
index 6b259f1..d8df014 100644
--- a/Plugin.TestResult/packages.config
+++ b/Plugin.TestResult/packages.config
@@ -9,6 +9,7 @@
   <package id="LiveChartsCore.SkiaSharpView.WPF" version="2.0.0-rc5.4" targetFramework="net472" />
   <package id="log4net" version="3.0.4" targetFramework="net472" />
   <package id="Microsoft.Bcl.AsyncInterfaces" version="8.0.0" targetFramework="net472" />
+  <package id="Newtonsoft.Json" version="13.0.3" targetFramework="net472" />
   <package id="OpenTK" version="3.3.1" targetFramework="net472" />
   <package id="OpenTK.GLWpfControl" version="3.3.0" targetFramework="net472" />
   <package id="Panuon.WPF" version="1.1.3" targetFramework="net472" />
diff --git a/Plugin.TsExecution/Plugin.TsExecution.csproj b/Plugin.TsExecution/Plugin.TsExecution.csproj
index cfce9b3..ef4aad0 100644
--- a/Plugin.TsExecution/Plugin.TsExecution.csproj
+++ b/Plugin.TsExecution/Plugin.TsExecution.csproj
@@ -156,6 +156,18 @@
     <Reference Include="WindowsFormsIntegration" />
   </ItemGroup>
   <ItemGroup>
+    <Page Include="ScreenGrid.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+    <Page Include="ScreenTouch.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="ScreenTouchWin.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
     <Page Include="WorkOrderWin.xaml">
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
@@ -173,6 +185,15 @@
       <SubType>Designer</SubType>
     </Page>
     <Compile Include="Entity\TestStep.cs" />
+    <Compile Include="ScreenGrid.xaml.cs">
+      <DependentUpon>ScreenGrid.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="ScreenTouch.xaml.cs">
+      <DependentUpon>ScreenTouch.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="ScreenTouchWin.xaml.cs">
+      <DependentUpon>ScreenTouchWin.xaml</DependentUpon>
+    </Compile>
     <Compile Include="WorkOrderWin.xaml.cs">
       <DependentUpon>WorkOrderWin.xaml</DependentUpon>
     </Compile>
diff --git a/Plugin.TsExecution/Properties/AssemblyInfo.cs b/Plugin.TsExecution/Properties/AssemblyInfo.cs
index 7fb240e..8c79373 100644
--- a/Plugin.TsExecution/Properties/AssemblyInfo.cs
+++ b/Plugin.TsExecution/Properties/AssemblyInfo.cs
@@ -49,6 +49,6 @@
 //      鐢熸垚鍙�
 //      淇鍙�
 //
-[assembly: AssemblyVersion("1.0.0.2")]
-[assembly: AssemblyFileVersion("1.0.0.2")]
+[assembly: AssemblyVersion("1.0.0.3")]
+[assembly: AssemblyFileVersion("1.0.0.3")]
 [assembly: PluginClassName("Plugin.TsExecution.TsExecution")]
diff --git a/Plugin.TsExecution/ScreenGrid.xaml b/Plugin.TsExecution/ScreenGrid.xaml
new file mode 100644
index 0000000..ff9f9f5
--- /dev/null
+++ b/Plugin.TsExecution/ScreenGrid.xaml
@@ -0,0 +1,15 @@
+锘�<hb:NotifyControl x:Class="Plugin.TsExecution.ScreenGrid"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:Plugin.TsExecution"
+             mc:Ignorable="d"  
+             xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI" 
+             xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base" 
+             Background="LightGray" Padding="0" Margin="0"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <Grid x:Name="grid" dp:GridHelper.Space="2" Margin="0">
+        
+    </Grid>
+</hb:NotifyControl>
diff --git a/Plugin.TsExecution/ScreenGrid.xaml.cs b/Plugin.TsExecution/ScreenGrid.xaml.cs
new file mode 100644
index 0000000..6409d77
--- /dev/null
+++ b/Plugin.TsExecution/ScreenGrid.xaml.cs
@@ -0,0 +1,45 @@
+锘縰sing HMI.Base;
+using HMI.UI.DpUtil;
+using System.Collections.ObjectModel;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Plugin.TsExecution
+{
+    public class ScreenGridConfig
+    {
+        public int rows { get; set; }
+        public int columns { get; set; }
+        public ObservableCollection<bool> values { get; set; }
+    }
+
+    /// <summary>
+    /// ScreenTouch.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class ScreenGrid : NotifyControl
+    {
+        public ScreenGrid(int rows, int columns)
+        {
+            InitializeComponent();
+
+            for (int i = 0; i < rows; i++)
+            {
+                grid.RowDefinitions.Add(new RowDefinition());
+            }
+
+            for (int i = 0; i < columns; i++)
+            {
+                grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto});
+            }
+        }
+
+        public void AddScreen(ScreenTouch screen, int row, int col)
+        {
+            grid.Children.Add(screen);
+            Grid.SetRow(screen, row);
+            Grid.SetColumn(screen, col);
+
+            GridHelper.RefreshGridSpace(grid);
+        }
+    }
+}
diff --git a/Plugin.TsExecution/ScreenTouch.xaml b/Plugin.TsExecution/ScreenTouch.xaml
new file mode 100644
index 0000000..f5ee1f2
--- /dev/null
+++ b/Plugin.TsExecution/ScreenTouch.xaml
@@ -0,0 +1,14 @@
+锘�<hb:NotifyControl x:Class="Plugin.TsExecution.ScreenTouch"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:Plugin.TsExecution"
+             mc:Ignorable="d"  
+             xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI" 
+             xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base" 
+             d:DesignHeight="450" d:DesignWidth="800">
+    <UniformGrid x:Name="uniform" dp:UniformGridHelper.InnerBorder="0.3" Background="LightGray"
+                 dp:UniformGridHelper.InnerBorderBrush="Transparent">
+    </UniformGrid>
+</hb:NotifyControl>
diff --git a/Plugin.TsExecution/ScreenTouch.xaml.cs b/Plugin.TsExecution/ScreenTouch.xaml.cs
new file mode 100644
index 0000000..4145554
--- /dev/null
+++ b/Plugin.TsExecution/ScreenTouch.xaml.cs
@@ -0,0 +1,156 @@
+锘縰sing HMI.Base;
+using System;
+using System.Collections.ObjectModel;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Media;
+
+namespace Plugin.TsExecution
+{
+    public class ColorConverter : IMultiValueConverter
+    {
+        public static ColorConverter Instance = new ColorConverter();
+        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (values[0] is ObservableCollection<string> list && values[1] is string tag)
+            {
+                if (list?.Contains(tag) == true) return Brushes.Green;
+                return Brushes.White;
+            }
+            return values;
+        }
+
+        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
+        {
+            return null;
+        }
+    }
+
+    /// <summary>
+    /// ScreenTouch.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class ScreenTouch : NotifyControl
+    {
+        public static readonly DependencyProperty ValuesProperty = DependencyProperty.Register(
+            nameof(Values),
+            typeof(ObservableCollection<bool>),
+            typeof(ScreenTouch),
+            new PropertyMetadata(null, OnValuesChanged));
+
+        private static void OnValuesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            if (d is ScreenTouch control)
+            {
+                // 濡傛灉鏈夋棫鐨勯泦鍚堬紝鍙栨秷璁㈤槄
+                if (e.OldValue is ObservableCollection<bool> oldCollection)
+                {
+                    oldCollection.CollectionChanged -= control.Value_CollectionChanged;
+                }
+
+                // 濡傛灉鏈夋柊鐨勯泦鍚堬紝璁㈤槄鍙樻洿浜嬩欢
+                if (e.NewValue is ObservableCollection<bool> newCollection)
+                {
+                    newCollection.CollectionChanged += control.Value_CollectionChanged;
+                }
+
+                // 鍒锋柊鎵�鏈夊崟鍏冩牸
+                control.PaintPoints();
+            }
+        }
+
+
+        public ScreenTouch(int rows, int columns)
+        {
+            this.rows = rows;
+            this.columns = columns;
+            InitializeComponent();
+            Refresh();
+            Values = new ObservableCollection<bool>(new bool[rows * columns]);
+        }
+
+        public ObservableCollection<bool> Values
+        {
+            get => (ObservableCollection<bool>)GetValue(ValuesProperty);
+            set
+            {
+                SetValue(ValuesProperty, value);
+            }
+        }
+
+        private void Value_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
+        {
+            PaintPoints();
+        }
+
+        public int rows { get; private set; }
+        public int columns { get; private set; }
+
+        private Border[] children;
+
+        public void Refresh()
+        {
+            uniform.Children.Clear();
+            uniform.Rows = rows;
+            uniform.Columns = columns;
+
+            children = new Border[rows * columns];
+
+
+            for (int i = 0; i < children.Length; i++)
+            {
+                var border = new Border
+                {
+                    BorderThickness = new Thickness(0),
+                    Background = Brushes.White
+                };
+                children[i] = border;
+                uniform.Children.Add(border);
+            }
+        }
+
+        public void Refresh(int rows, int columns)
+        {
+            this.rows = rows;
+            this.columns = columns;
+            Refresh();
+        }
+
+        public void Clear()
+        {
+            Values = new ObservableCollection<bool>(new bool[rows * columns]);
+        }
+
+        public void DrawPoint(int x, int y)
+        {
+            Values[y * columns + x] = true;
+        }
+
+        public void DrawPoints(int[][] points)
+        {
+            var temp = new ObservableCollection<bool>(new bool[rows * columns]);
+            foreach (int[] point in points)
+            {
+                //children[point[0]][point[1]].Background = Brushes.Green;
+                temp[point[1] * columns + point[0]] = true;
+            }
+            Values = temp;
+        }
+
+        private void PaintPoints()
+        {
+            for (int i = 0; i < Values.Count; i++)
+            {
+                if (Values[i])
+                {
+                    children[i].Background = Brushes.Green;
+                }
+                else
+                {
+                    children[i].Background = Brushes.White;
+                }
+            }
+        }
+    }
+}
diff --git a/Plugin.TsExecution/ScreenTouchWin.xaml b/Plugin.TsExecution/ScreenTouchWin.xaml
new file mode 100644
index 0000000..cd99df0
--- /dev/null
+++ b/Plugin.TsExecution/ScreenTouchWin.xaml
@@ -0,0 +1,20 @@
+锘�<pu:WindowX x:Class="Plugin.TsExecution.ScreenTouchWin"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        xmlns:local="clr-namespace:Plugin.TsExecution"
+        mc:Ignorable="d"
+        xmlns:pw="https://opensource.panuon.com/wpf"
+        xmlns:pu="https://opensource.panuon.com/wpf-ui"
+        SizeChanged="Window_SizeChanged"
+        Style="{StaticResource ModalWindow}"
+        pu:WindowXModalDialog.Buttons="{x:Null}"
+        WindowStartupLocation="CenterScreen"
+        WindowState="Maximized"
+        Background="LightGray"
+        Title="Screens" Height="450" Width="800">
+    <Grid x:Name="grid" HorizontalAlignment="Center">
+        
+    </Grid>
+</pu:WindowX>
diff --git a/Plugin.TsExecution/ScreenTouchWin.xaml.cs b/Plugin.TsExecution/ScreenTouchWin.xaml.cs
new file mode 100644
index 0000000..b4f2c7d
--- /dev/null
+++ b/Plugin.TsExecution/ScreenTouchWin.xaml.cs
@@ -0,0 +1,67 @@
+锘縰sing Panuon.WPF.UI;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+
+namespace Plugin.TsExecution
+{
+    /// <summary>
+    /// ScreenTouchWin.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class ScreenTouchWin : WindowX
+    {
+        private ScreenGrid screenGrid;
+        private ScreenTouch[] screens;
+
+        public ScreenTouchWin(ScreenTouch[] configs)
+        {
+            screenGrid = new ScreenGrid(1, configs.Length);
+            screens = new ScreenTouch[configs.Length];
+            InitializeComponent();
+
+            int space = 2 * (screens.Length + 1);
+            for (int i = 0; i < screens.Length; i++)
+            {
+                var screen = new ScreenTouch(configs[i].rows, configs[i].columns)
+                {
+                    Width = Math.Max(Math.Floor((this.ActualHeight / 9 * 16 - space) / screens.Length), 0),
+                    Height = Math.Max(Math.Floor((this.ActualHeight / 9 * 16 - space) / screens.Length / 16 * 9), 0)
+                };
+
+                screen.SetBinding(ScreenTouch.ValuesProperty, new Binding(nameof(ScreenTouch.Values)) { Source = configs[i], Mode = BindingMode.OneWay });
+
+                screens[i] = screen;
+                screenGrid.AddScreen(screen, 0, i);
+            }
+
+            grid.Children.Add(screenGrid);
+        }
+
+        private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
+        {
+            if (screenGrid == null)
+            {
+                return;
+            }
+            int space = 2 * (screens.Length + 1);
+
+            for (int i = 0; i < screens.Length; i++)
+            {
+                var screen = screens[i];
+                screen.Width = Math.Max(Math.Floor((this.ActualHeight / 9 * 16 - space) / screens.Length), 0);
+                screen.Height = Math.Max(Math.Floor((this.ActualHeight / 9 * 16 - space) / screens.Length / 16 * 9), 0);
+            }
+        }
+    }
+}
diff --git a/Plugin.TsExecution/SingleControl.xaml b/Plugin.TsExecution/SingleControl.xaml
index 905a810..8a31373 100644
--- a/Plugin.TsExecution/SingleControl.xaml
+++ b/Plugin.TsExecution/SingleControl.xaml
@@ -15,6 +15,7 @@
              FontSize="{Binding ActualHeight,RelativeSource={RelativeSource AncestorType=UserControl}, 
 Converter={StaticResource hfc}, ConverterParameter='12,816'}"
              x:Name="control"
+             SizeChanged="control_SizeChanged"
              d:DesignHeight="450" d:DesignWidth="800">
     <UserControl.Resources>
         <ResourceDictionary>
@@ -51,21 +52,20 @@
             <RowDefinition Height="Auto"/>
         </Grid.RowDefinitions>
 
-        <Grid dp:GridHelper.InnerBorder="1" dp:GridHelper.Space="2" 
+        <Grid dp:GridHelper.InnerBorder="1" dp:GridHelper.Space="2" x:Name="tableHeader"
               dp:GridHelper.InnerBorderBrush="{DynamicResource CommonBoarder}">
             <Grid.RowDefinitions>
-                <RowDefinition Height="Auto"/>
                 <RowDefinition/>
                 <RowDefinition/>
             </Grid.RowDefinitions>
             <Grid.ColumnDefinitions>
                 <ColumnDefinition Width="Auto"/>
-                <ColumnDefinition/>
+                <ColumnDefinition />
+                <ColumnDefinition Width="Auto"/>
             </Grid.ColumnDefinitions>
             <StackPanel Grid.RowSpan="3">
                 <StackPanel.Style>
                     <Style TargetType="StackPanel">
-                        <Setter Property="Background" Value="DeepSkyBlue"/>
                         <Style.Triggers>
                             <DataTrigger Binding="{Binding Status}" Value="Passed">
                                 <Setter Property="Background" Value="{StaticResource passedColor}"/>
@@ -89,6 +89,7 @@
                 <Label>
                     <Label.Style>
                         <Style TargetType="Label">
+                            <Setter Property="VerticalAlignment" Value="Center"/>
                             <Setter Property="Padding" Value="10,10,10,0"/>
                             <Setter Property="Margin" Value="0"/>
                             <Setter Property="FontFamily" Value="{StaticResource iconfont}"/>
@@ -123,7 +124,7 @@
                     </Label.Style>
                 </Label>
 
-                <Label Content="{Binding Status}" VerticalContentAlignment="Center"
+                <Label Content="{Binding Status}" VerticalAlignment="Center" VerticalContentAlignment="Center"
                        Padding="0"
                        Margin="0"
                        HorizontalContentAlignment="Center"
@@ -135,7 +136,7 @@
 
             <!--<Label HorizontalContentAlignment="Left" Grid.Column="1" Content="{Binding Status}"/>-->
 
-            <Label Style="{StaticResource baseLabel}" Background="#FFF" Grid.Row="1" Grid.Column="1">
+            <Label Style="{StaticResource baseLabel}" Background="#FFF" Grid.Row="0" Grid.Column="1">
                 <StackPanel Orientation="Horizontal">
                     <TextBlock Style="{StaticResource baseText}" Text="{DynamicResource Ts.Total}"/>
                     <TextBlock Style="{StaticResource baseText}" Margin="2,0,18,0" Text="{Binding TotalNum}"/>
@@ -150,7 +151,7 @@
                 </StackPanel>
             </Label>
 
-            <Label Style="{StaticResource baseLabel}" Background="#FFF" Grid.Row="2" Grid.Column="1">
+            <Label Style="{StaticResource baseLabel}" Background="#FFF" Grid.Row="1" Grid.Column="1">
                 <StackPanel Orientation="Horizontal">
                     <Label Style="{StaticResource baseLabel}" Content="{DynamicResource Ts.CurrentSnr}"/>
                     <Label Style="{StaticResource baseLabel}" Margin="2,0,18,0" Content="{Binding Barcode}"/>
@@ -158,6 +159,9 @@
                     <Label Style="{StaticResource baseLabel}" Margin="2,0,18,0" Content="{Binding ElapseTime}"/>
                 </StackPanel>
             </Label>
+            <Border x:Name="screenContainer" BorderThickness="0" Grid.Column="2" Grid.RowSpan="2" MouseLeftButtonDown="screenContainer_MouseLeftButtonDown">
+               
+            </Border>
         </Grid>
 
         <Grid Grid.Row="2" x:Name="simplePanel" dp:GridHelper.Space="2" dp:GridHelper.SpaceIgnore="TOP">
@@ -337,7 +341,12 @@
                 </ListView.View>
             </ListView>-->
 
-            <DataGrid Grid.Row="0" Style="{StaticResource DefaultDataGrid}" Margin="2,0,2,2"
+           
+
+            <Canvas Grid.Row="0" x:Name="canvas" Background="Transparent">
+                <DataGrid Canvas.Left="0" Canvas.Top="0" Height="{Binding ActualHeight,ElementName=canvas}" 
+                          Width="{Binding ActualWidth,ElementName=canvas}" 
+                          Style="{StaticResource DefaultDataGrid}" Margin="2,0,2,2"
                   FontSize="{DynamicResource tableFontSize}"
                   dp:DataGridHelper.ShowRowNumber="True"
                   VirtualizingPanel.IsContainerVirtualizable="True"
@@ -346,74 +355,75 @@
                   RowHeight="{StaticResource nanD}"
                   ColumnHeaderHeight="{Binding ActualWidth,ElementName=control,
 Converter={StaticResource fc}, ConverterParameter='32,1093,30,45'}"
-                      HorizontalScrollBarVisibility="Disabled"
+                  HorizontalScrollBarVisibility="Disabled"
                   ItemsSource="{Binding Datasource}" x:Name="table">
 
-                <DataGrid.Columns>
-                    <DataGridTextColumn Header="#" 
+                    <DataGrid.Columns>
+                        <DataGridTextColumn Header="#" 
                             ElementStyle="{StaticResource TableTextBlock}"
             Binding="{Binding Header, RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}, Mode=FindAncestor}}" 
                         Width="0.4*"/>
-                    <DataGridTextColumn Width="2.5*" Binding="{Binding Name}" 
+                        <DataGridTextColumn Width="2.5*" Binding="{Binding Name}" 
                             ElementStyle="{StaticResource TableTextBlock}">
-                        <DataGridTextColumn.Header>
-                            <TextBlock Text="{DynamicResource Test.Name}"/>
-                        </DataGridTextColumn.Header>
-                    </DataGridTextColumn>
-                    <DataGridTextColumn Width="0.8*" Binding="{Binding Unit}" 
+                            <DataGridTextColumn.Header>
+                                <TextBlock Text="{DynamicResource Test.Name}"/>
+                            </DataGridTextColumn.Header>
+                        </DataGridTextColumn>
+                        <DataGridTextColumn Width="0.8*" Binding="{Binding Unit}" 
                             ElementStyle="{StaticResource TableTextBlock}">
-                        <DataGridTextColumn.Header>
-                            <TextBlock Text="{DynamicResource Test.Unit}"/>
-                        </DataGridTextColumn.Header>
-                    </DataGridTextColumn>
-                    <DataGridTextColumn Width="2*" Binding="{Binding Value}" 
+                            <DataGridTextColumn.Header>
+                                <TextBlock Text="{DynamicResource Test.Unit}"/>
+                            </DataGridTextColumn.Header>
+                        </DataGridTextColumn>
+                        <DataGridTextColumn Width="2*" Binding="{Binding Value}" 
                             ElementStyle="{StaticResource TableTextBlock}">
-                        <DataGridTextColumn.Header>
-                            <TextBlock Text="{DynamicResource Test.Value}"/>
-                        </DataGridTextColumn.Header>
-                    </DataGridTextColumn>
-                    <DataGridTextColumn Width="1.5*" Binding="{Binding LimitDown}" 
+                            <DataGridTextColumn.Header>
+                                <TextBlock Text="{DynamicResource Test.Value}"/>
+                            </DataGridTextColumn.Header>
+                        </DataGridTextColumn>
+                        <DataGridTextColumn Width="1.5*" Binding="{Binding LimitDown}" 
                             ElementStyle="{StaticResource TableTextBlock}">
-                        <DataGridTextColumn.Header>
-                            <TextBlock Text="{DynamicResource Test.LimitDown}"/>
-                        </DataGridTextColumn.Header>
-                    </DataGridTextColumn>
-                    <DataGridTextColumn Width="1.5*" Binding="{Binding LimitUp}" 
+                            <DataGridTextColumn.Header>
+                                <TextBlock Text="{DynamicResource Test.LimitDown}"/>
+                            </DataGridTextColumn.Header>
+                        </DataGridTextColumn>
+                        <DataGridTextColumn Width="1.5*" Binding="{Binding LimitUp}" 
                             ElementStyle="{StaticResource TableTextBlock}">
-                        <DataGridTextColumn.Header>
-                            <TextBlock Text="{DynamicResource Test.LimitUp}"/>
-                        </DataGridTextColumn.Header>
-                    </DataGridTextColumn>
-                    <DataGridTextColumn Width="0.6*" Binding="{Binding ConsumeTime}"
+                            <DataGridTextColumn.Header>
+                                <TextBlock Text="{DynamicResource Test.LimitUp}"/>
+                            </DataGridTextColumn.Header>
+                        </DataGridTextColumn>
+                        <DataGridTextColumn Width="0.6*" Binding="{Binding ConsumeTime}"
         ElementStyle="{StaticResource TableTextBlock}">
-                        <DataGridTextColumn.Header>
-                            <TextBlock Text="{DynamicResource Test.ConsumeTime}"/>
-                        </DataGridTextColumn.Header>
-                    </DataGridTextColumn>
-                    <DataGridTemplateColumn Width="1*">
-                        <DataGridTemplateColumn.Header>
-                            <TextBlock Text="{DynamicResource Test.Result}"/>
-                        </DataGridTemplateColumn.Header>
-                        <DataGridTemplateColumn.CellTemplate>
-                            <DataTemplate>
-                                <Grid x:Name="border" Margin="0">
-                                    <TextBlock x:Name="text" Margin="-1" Padding="0" VerticalAlignment="Center" Text="{Binding Result}"/>
-                                </Grid>
-                                <DataTemplate.Triggers>
-                                    <DataTrigger Binding="{Binding Result}" Value="Passed">
-                                        <Setter TargetName="border" Property="Background" Value="{StaticResource passedColor}"/>
-                                        <Setter TargetName="text" Property="Foreground" Value="#000"/>
-                                    </DataTrigger>
-                                    <DataTrigger Binding="{Binding Result}" Value="Failed">
-                                        <Setter TargetName="border" Property="Background" Value="{DynamicResource DangerColor}"/>
-                                        <Setter TargetName="text" Property="Foreground" Value="#000"/>
-                                    </DataTrigger>
-                                </DataTemplate.Triggers>
-                            </DataTemplate>
-                        </DataGridTemplateColumn.CellTemplate>
-                    </DataGridTemplateColumn>
-                </DataGrid.Columns>
-            </DataGrid>
+                            <DataGridTextColumn.Header>
+                                <TextBlock Text="{DynamicResource Test.ConsumeTime}"/>
+                            </DataGridTextColumn.Header>
+                        </DataGridTextColumn>
+                        <DataGridTemplateColumn Width="1*">
+                            <DataGridTemplateColumn.Header>
+                                <TextBlock Text="{DynamicResource Test.Result}"/>
+                            </DataGridTemplateColumn.Header>
+                            <DataGridTemplateColumn.CellTemplate>
+                                <DataTemplate>
+                                    <Grid x:Name="border" Margin="0">
+                                        <TextBlock x:Name="text" Margin="-1" Padding="0" VerticalAlignment="Center" Text="{Binding Result}"/>
+                                    </Grid>
+                                    <DataTemplate.Triggers>
+                                        <DataTrigger Binding="{Binding Result}" Value="Passed">
+                                            <Setter TargetName="border" Property="Background" Value="{StaticResource passedColor}"/>
+                                            <Setter TargetName="text" Property="Foreground" Value="#000"/>
+                                        </DataTrigger>
+                                        <DataTrigger Binding="{Binding Result}" Value="Failed">
+                                            <Setter TargetName="border" Property="Background" Value="{DynamicResource DangerColor}"/>
+                                            <Setter TargetName="text" Property="Foreground" Value="#000"/>
+                                        </DataTrigger>
+                                    </DataTemplate.Triggers>
+                                </DataTemplate>
+                            </DataGridTemplateColumn.CellTemplate>
+                        </DataGridTemplateColumn>
+                    </DataGrid.Columns>
+                </DataGrid>
+            </Canvas>
 
             <Grid Grid.Row="1" Height="20" Margin="0">
                 <ProgressBar Grid.Row="3" x:Name="testProcess" Value="{Binding ProcessValue}"/>
@@ -424,7 +434,8 @@
 
             <Grid Background="#f1eded" Grid.Row="2" Height="25">
                 <DockPanel>
-                    <Label Padding="0" FontSize="12" VerticalContentAlignment="Center" Content="{Binding CurrentStep}"/>
+                    <Label Margin="10,0,0,0" Padding="0" FontSize="16" VerticalContentAlignment="Center" Content="{Binding SocketName}"/>
+                    <Label Margin="20,0,0,0" Padding="0" FontSize="14" VerticalContentAlignment="Center" Content="{Binding CurrentStep}"/>
 
                     <StackPanel DockPanel.Dock="Right" Orientation="Horizontal" Margin="0,0,5,0">
                         <!--<Button FontSize="12" x:Name="scannerSetting" 
diff --git a/Plugin.TsExecution/SingleControl.xaml.cs b/Plugin.TsExecution/SingleControl.xaml.cs
index e0bbebe..3e3c21a 100644
--- a/Plugin.TsExecution/SingleControl.xaml.cs
+++ b/Plugin.TsExecution/SingleControl.xaml.cs
@@ -1,4 +1,5 @@
-锘縰sing HMI.Core.Manager;
+锘縰sing HMI.Core.Entity;
+using HMI.Core.Manager;
 using HMI.Core.Util;
 using HMI.Util;
 using NationalInstruments.TestStand.Interop.API;
@@ -7,6 +8,7 @@
 using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.IO;
+using System.Linq;
 using System.Runtime.CompilerServices;
 using System.Timers;
 using System.Windows;
@@ -36,11 +38,13 @@
             }
         }
         #endregion
-        private bool isLoadOK = false;
         private int index;
         private string CountIniPath;
 
         private string status = "Ready";
+
+        private ScreenTouch[] screens;
+        private ScreenGrid screenGrid;
 
         public string Status
         {
@@ -127,6 +131,16 @@
                         Set("TestInfo");
                     }
                 }
+            }
+        }
+
+        private string socketName = string.Empty;
+        public string SocketName
+        {
+            get { return socketName; }
+            set
+            {
+                Set(ref socketName, value);
             }
         }
 
@@ -550,5 +564,117 @@
                 //Canvas.SetTop(modal, (scannerPanel.ActualHeight - scannerVisualModal.ActualHeight) / 2);
             }
         }
+
+        public void RefreshScreen(int[][] configs)
+        {
+            //foreach (UIElement item in canvas.Children)
+            //{
+            //    if (item is ScreenTouch)
+            //    {
+            //        canvas.Children.Remove(item);
+            //    }
+            //}
+            screenContainer.Child = null;
+
+
+            screenGrid = new ScreenGrid(1, configs.Length);
+
+            screens = new ScreenTouch[configs.Length];
+
+            int space = 2 * (screens.Length + 1);
+            for (int i = 0; i < configs.Length; i++)
+            {
+                var screen = new ScreenTouch(configs[i][0], configs[i][1])
+                {
+                    Width = Math.Max((tableHeader.ActualHeight / 9 * 16 - space) / screens.Length, 0),
+                    Height = Math.Max((tableHeader.ActualHeight / 9 * 16 - space) / screens.Length / 16 * 9, 0)
+                };
+                screens[i] = screen;
+                screenGrid.AddScreen(screen, 0, i);
+            }
+            screenContainer.Child = screenGrid;
+
+            //canvas.Children.Add(screens[0]);
+            //Canvas.SetBottom(screens[0], 0);
+            //Canvas.SetRight(screens[0], 0);
+            //Panel.SetZIndex(screens[0], 999);
+        }
+
+        public void DrawScreenPoint(int index, int x, int y)
+        {
+            screens[index].DrawPoint(x, y);
+        }
+
+        public void DrawScreenPoints(int index, int[][] points)
+        {
+            screens[index].DrawPoints(points);
+        }
+
+        public void ClearScreenPoint(int index)
+        {
+            screens[index].Clear();
+        }
+
+        public void RefreshProduct(ProductData product)
+        {
+            if (product == null)
+            {
+                screenContainer.Child = null;
+                return;
+            }
+            try
+            {
+                string screenSize = ProductManager.GetParameterByProductAndName(product.Name, "ScreenTouch")?.Value;
+                if (string.IsNullOrEmpty(screenSize))
+                {
+                    screenContainer.Child = null;
+                    return;
+                }
+                string[] arr = screenSize.Split(';');
+                int[][] configs = new int[arr.Length][];
+
+                for (int i = 0; i < arr.Length; i++)
+                {
+                    string[] size = arr[i].Split(',');
+                    configs[i] = new int[2] { int.Parse(size[0]), int.Parse(size[1]) };
+                }
+                RefreshScreen(configs);
+            }
+            catch (Exception ex)
+            {
+                TsExecution.logger.Error("RefreshProduct Failed.", ex);
+                screenContainer.Child = null;
+            }
+        }
+
+        private void control_SizeChanged(object sender, SizeChangedEventArgs e)
+        {
+            if (screenGrid == null)
+            {
+                return;
+            }
+            //screenGrid.Height = tableHeader.ActualHeight - 2;
+            //screenGrid.Width = tableHeader.ActualHeight / 9 * 16 - 2;
+
+            int space = 2 * (screens.Length + 1);
+
+            for (int i = 0; i < screens.Length; i++)
+            {
+                var screen = screens[i];
+                screen.Width = Math.Max(Math.Floor((tableHeader.ActualHeight / 9 * 16 - space) / screens.Length), 0);
+                screen.Height = Math.Max(Math.Floor(screen.Width / 16 * 9), 0);
+            }
+        }
+
+        private void screenContainer_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            if (e.ClickCount == 2)
+            {
+                new ScreenTouchWin(screens)
+                {
+                    Owner = Window.GetWindow(this)
+                }.ShowDialog();
+            }
+        }
     }
 }
diff --git a/Plugin.TsExecution/TsExecution.xaml.cs b/Plugin.TsExecution/TsExecution.xaml.cs
index 289d5be..321bd3b 100644
--- a/Plugin.TsExecution/TsExecution.xaml.cs
+++ b/Plugin.TsExecution/TsExecution.xaml.cs
@@ -20,6 +20,7 @@
 using System.ComponentModel;
 using System.IO;
 using System.Linq;
+using System.Runtime.InteropServices;
 using System.Timers;
 using System.Windows;
 using System.Windows.Controls;
@@ -50,6 +51,8 @@
         private SequenceFile modeSeq = null;
         public AxApplicationMgr AxApplicationMgr;
         private Execution execution = null;
+
+        private SequenceContext sequenceContext = null;
 
         private Chart chart;
         #endregion
@@ -101,6 +104,13 @@
             {
                 Set(ref selectedProduct, value);
                 CurrentProductModel = value?.Name;
+                if (tsSockets != null)
+                {
+                    foreach (SingleControl socket in tsSockets)
+                    {
+                        socket.RefreshProduct(value);
+                    }
+                }
                 SetTestSeqFile(value);
             }
         }
@@ -446,6 +456,13 @@
                 SelectedProduct = tempProduct;
             }
             RefershUI();
+            if (tsSockets != null)
+            {
+                foreach (SingleControl socket in tsSockets)
+                {
+                    socket.RefreshProduct(SelectedProduct);
+                }
+            }
             SampleStatus = false;
         }
 
@@ -471,24 +488,31 @@
         private void InitTsEngine()
         {
             AxApplicationMgr = TestAppManager.GetAxApplicationMgr();
+
+
+            //AxApplicationMgr. += AxApplicationMgr_EndExecution;
             AxApplicationMgr.EndExecution += AxApplicationMgr_EndExecution;
-            AxApplicationMgr.ExitApplication += new EventHandler(this.AxApplicationMgr_ExitApplication);
-            AxApplicationMgr.UIMessageEvent +=
-                new _ApplicationMgrEvents_UIMessageEventEventHandler(this.AxApplicationMgr_UIMessageEvent);
-            AxApplicationMgr.ExecutionClosed +=
-                new _ApplicationMgrEvents_ExecutionClosedEventHandler(this.AxApplicationMgr_ExecutionClosed);
+
+            AxApplicationMgr.ExitApplication += this.AxApplicationMgr_ExitApplication;
+            AxApplicationMgr.UIMessageEvent += this.AxApplicationMgr_UIMessageEvent;
+            AxApplicationMgr.ExecutionClosed += this.AxApplicationMgr_ExecutionClosed;
 
             ((System.ComponentModel.ISupportInitialize)(AxApplicationMgr)).BeginInit();
             host.Child = AxApplicationMgr;
             ((System.ComponentModel.ISupportInitialize)(AxApplicationMgr)).EndInit();
         }
 
+        private volatile bool canSwitchModel = false;
+        private volatile Action switchAction = null;
+
         private void AxApplicationMgr_EndExecution(object sender, _ApplicationMgrEvents_EndExecutionEvent e)
         {
             log.Info("AxApplicationMgr_EndExecution");
             if (ReleaseSequenceFile())
             {
+                IsTesting = false;
                 log.Info("Release SeqFile Success!");
+                if (canSwitchModel) switchAction?.Invoke();
             }
             else
             {
@@ -594,10 +618,21 @@
         private void RefershUI()
         {
             panel.Children.Clear();
+            string[] names = ParamSetting.SocketNames.Split(',');
+
             //閲嶆柊鐢熸垚瀛愰潰鏉�
             for (int i = 0; i < this.socketNum; i++)
             {
-                SingleControl control = new SingleControl(i, CountIniPath, this.probeWarn);
+                string name = string.Empty;
+                if (i < names.Length)
+                {
+                    name = names[i];
+                }
+
+                SingleControl control = new SingleControl(i, CountIniPath, this.probeWarn)
+                {
+                    SocketName = name
+                };
                 panel.Children.Add(control);
                 tsSockets.Add(control);
 
@@ -1027,7 +1062,7 @@
         private void StartSequence()
         {
             if (!isLoadOK) return;
-
+            engine?.Globals.SetValBoolean("StationState.StopTest", 0, false);
             if (ParamSetting.EnableAutoModel)
             {
                 try
@@ -1085,35 +1120,12 @@
 
         public void StopSequenceClick()
         {
+            canSwitchModel = false;
+            switchAction = null;
             ShowMask(() =>
             {
                 bool stoped = StopSequence();
-                if (stoped)
-                {
-                    IsTesting = false;
-                }
             }, null, null, LangUtil.Get("App.SequenceIsStopping"));
-            //new MaskWindow((WindowX)Window.GetWindow(this),
-            //        (win) =>
-            //        {
-            //            try
-            //            {
-            //                bool stoped = StopSequence();
-            //                if (stoped)
-            //                {
-            //                    IsTesting = false;
-            //                }
-            //            }
-            //            finally
-            //            {
-            //                Dispatcher.Invoke(() =>
-            //                {
-
-            //                    win.Close();
-            //                });
-            //            }
-            //        }
-            //        ).Info(LangUtil.Get("App.SequenceIsStopping"));
         }
 
         /// <summary>
@@ -1128,18 +1140,15 @@
                     for (int i = 0; i < tsSockets.Count; i++)
                     {
                         var context = this.tsSockets[i].SequenceFileContext;
-                        //bool val = context.StationGlobals.Exists("StationState.StopTest", 0);
-                        //if (val)
-                        //{
-                        //    context.StationGlobals.SetValBoolean("StationState.StopTest", 0, true);
-                        //    bool isStop = this.execution.WaitForEndEx(2000, true, null, null);
-                        //}
                         context?.StationGlobals.SetValBoolean("StationState.StopTest", 1, true);
                     }
                 }
                 if (this.execution == null) return true;
 
-                bool res = this.execution.WaitForEndEx(2000, true, null, null);
+                //bool res = this.execution.WaitForEndEx(-1, true, null, null);
+                bool res = this.execution?.WaitForEndEx(-1, true, Type.Missing, null) == true;
+                Marshal.ReleaseComObject(this.execution);
+                this.execution = null;
                 return res;
             }
             catch (Exception ex)
@@ -1174,6 +1183,7 @@
 
         private void StartClick()
         {
+            canSwitchModel = true;
             ShowMask(() =>
             {
                 StartSequence();
@@ -1358,7 +1368,7 @@
                     this.tsSockets[index].ElapseTime = strData;
                     break;
                 case (int)UIMessageCodes.UIMsg_EndExecution://鎵ц缁撴潫
-                    IsTesting = false;
+                    //IsTesting = false;
                     //this.ReleaseSequenceFile();
                     break;
                 case 10030://鏄剧ず娴嬭瘯杩涘害
@@ -1405,11 +1415,75 @@
                 case 10034:
                     CurrentProductModel = strData;
                     break;
+                case 10035:
+                    string[] arr_10035 = strData.Split(';');
+
+                    int[][] points = new int[arr_10035.Length - 1][];
+
+                    for (int i = 1; i < arr_10035.Length; i++)
+                    {
+                        string[] strs = arr_10035[i].Split(',');
+                        points[i - 1] = new int[2] { int.Parse(strs[0]), int.Parse(strs[1]) };
+                    }
+                    this.tsSockets[index].DrawScreenPoints(int.Parse(arr_10035[0]), points);
+                    break;
+                case 10036:
+                    this.tsSockets[index].ClearScreenPoint(int.Parse(strData));
+                    break;
                 case 10099:
+                    string[] arr = strData.Split(',');
+                    if (canSwitchModel) SwitchSequenceByModel(index, arr[0], arr[1]);
                     break;
                 default:
                     break;
             }
+        }
+
+        private void SwitchSequenceByModel(int index, string model, string snr)
+        {
+            Dispatcher.Invoke(() =>
+            {
+                var target = Products.FirstOrDefault(e => e.Name == model);
+
+                ShowMask(() =>
+                {
+                    bool stoped = StopSequence();
+                    //if (stoped)
+                    //{
+                    //    IsTesting = false;
+                    //}
+                }, null, null, "鎹㈠瀷涓紝璇风◢鍚�...");
+
+                if (target == null)
+                {
+                    MsgBoxHelper2.Warn($"璇峰厛鍦ㄤ骇鍝佺鐞嗛〉闈㈤厤缃瀷鍙� [{model}] 鐨勪骇鍝佷俊鎭紒");
+                    return;
+                }
+
+                switchAction = () =>
+                {
+                    switchAction = null;
+                    log?.Info($"灏濊瘯鑷姩鎹㈠瀷: {switchAction}");
+                    if (canSwitchModel)
+                    {
+                        Dispatcher.Invoke(() =>
+                        {
+                            try
+                            {
+                                log?.Info("寮�濮嬭嚜鍔ㄦ崲鍨�");
+                                SelectedProduct = target;
+                                engine?.Globals.SetValString($"CacheSNR[{index}]", 1, snr);
+                                engine?.CommitGlobalsToDisk(false);
+                                StartClick();
+                            }
+                            catch (Exception ex)
+                            {
+                                log?.Error("鑷姩鎹㈠瀷澶辫触", ex);
+                            }
+                        });
+                    }
+                };
+            });
         }
 
         private void ShowAlarm(AlarmText alarm)
@@ -1433,7 +1507,7 @@
 
                 try
                 {
-                    log.Info($"Event: {eventCode}, NumericData: {index}, StringData: {strData}");
+                    log.Debug($"Event: {eventCode}, NumericData: {index}, StringData: {strData}");
                 }
                 catch
                 {
@@ -1441,6 +1515,8 @@
                 }
                 if (eventCode == 10001)
                 {
+                    sequenceContext = (SequenceContext)e.uiMsg.ActiveXData;
+
                     tsSockets[index].SequenceFileContext = (SequenceContext)e.uiMsg.ActiveXData;
                     return;
                 }
@@ -1522,6 +1598,10 @@
             {
                 lock (this)
                 {
+                    if (this.testSeq == null)
+                    {
+                        return true;
+                    }
                     //execution.Abort();
                     int count = 10;
                     while (count > 0)
@@ -1531,15 +1611,18 @@
                             if (this.testSeq == null)
                                 return true;
 
-                            if (this.engine.ReleaseSequenceFileEx(this.testSeq, 4))
+                            if (this.engine?.ReleaseSequenceFileEx(this.testSeq, 4) == true)
                             {
+                                Marshal.ReleaseComObject(this.testSeq);
                                 this.testSeq = null;
+                                // GC.Collect();
                             }
-                            if (this.engine.ReleaseSequenceFileEx(this.modeSeq, 4))
+                            if (this.engine?.ReleaseSequenceFileEx(this.modeSeq, 4) == true)
                             {
+                                Marshal.ReleaseComObject(this.modeSeq);
                                 this.modeSeq = null;
                             }
-                            this.engine.UnloadAllModules();
+                            this.engine?.UnloadAllModules();
                             return this.testSeq == null && this.modeSeq == null;
                         }
                         catch (Exception ex)
diff --git a/Plugin.TsSupport/ITestExecutionWrapper.cs b/Plugin.TsSupport/ITestExecutionWrapper.cs
new file mode 100644
index 0000000..2a80a38
--- /dev/null
+++ b/Plugin.TsSupport/ITestExecutionWrapper.cs
@@ -0,0 +1,47 @@
+锘縰sing NationalInstruments.TestStand.Interop.API;
+using System;
+
+namespace Plugin.TsSupport
+{
+    public class ExecutionEndEvent
+    {
+        public object Origin { get; set; }
+        public object Data { get; set; }
+    }
+
+    public class UIMessageEvent
+    {
+        public object Origin { get; set; }
+        public object Data { get; set; }
+        public int EventCode { get; set; }
+        public double NumberData { get; set; }
+        public bool BoolData { get; set; }
+        public string StrData { get; set; }
+    }
+
+    public interface ITestExecutionWrapper
+    {
+        event EventHandler<UIMessageEvent> UIMessageEvent;
+
+        event EventHandler<ExecutionEndEvent> EndExecutionEvent;
+
+        object Init();
+
+        object StartSequence(string filePath, string modelPath);
+
+        T GetStationGlobalValue<T>(string key, int option);
+
+        T GetFileGlobalValue<T>(string key, int option);
+
+        void SetStationGlobalValue<T>(string key, int option, T value, bool inContext = false);
+
+        void SetFileGlobalValue<T>(string key, int option, T value);
+
+        void CommitToDisk(bool option);
+
+        bool WaitForExit();
+
+        bool ReleaseSeqFile();
+        void SetContext(object context);
+    }
+}
diff --git a/Plugin.TsSupport/Plugin.TsSupport.csproj b/Plugin.TsSupport/Plugin.TsSupport.csproj
index 9306243..ec9f306 100644
--- a/Plugin.TsSupport/Plugin.TsSupport.csproj
+++ b/Plugin.TsSupport/Plugin.TsSupport.csproj
@@ -87,8 +87,11 @@
     <Reference Include="System.Xml" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="ITestExecutionWrapper.cs" />
     <Compile Include="TestAppManager.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="TeststandWrapper.cs" />
+    <Compile Include="TsExecutionWrapper.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
 </Project>
\ No newline at end of file
diff --git a/Plugin.TsSupport/TeststandWrapper.cs b/Plugin.TsSupport/TeststandWrapper.cs
new file mode 100644
index 0000000..b3c7f07
--- /dev/null
+++ b/Plugin.TsSupport/TeststandWrapper.cs
@@ -0,0 +1,228 @@
+锘縰sing NationalInstruments.TestStand.Interop.API;
+using NationalInstruments.TestStand.Interop.UI.Ax;
+using System;
+using System.Diagnostics;
+
+namespace Plugin.TsSupport
+{
+    public class TeststandWrapper : ITestExecutionWrapper
+    {
+        #region TestStand浜嬩欢
+        /// <summary>
+        /// TS 寮曟搸
+        /// </summary>
+        private Engine engine = null;
+
+        /// <summary>
+        /// 娴嬭瘯搴忓垪璺緞
+        /// </summary>
+        private string testSeqFile = string.Empty;
+
+        /// <summary>
+        /// 娴嬭瘯搴忓垪
+        /// </summary>
+        private SequenceFile testSeq = null;
+
+        /// <summary>
+        /// 娴嬭瘯妯″瀷搴忓垪
+        /// </summary>
+        private SequenceFile modeSeq = null;
+
+        /// <summary>
+        /// 杩愯瀹炰緥
+        /// </summary>
+        private Execution execution = null;
+
+        public SequenceContext context { get; set; } = null;
+
+        /// <summary>
+        /// 浜嬩欢绠$悊鍣�
+        /// </summary>
+        public AxApplicationMgr AxApplicationMgr { get; private set; }
+        #endregion
+
+        public event EventHandler<UIMessageEvent> UIMessageEvent;
+        public event EventHandler<ExecutionEndEvent> EndExecutionEvent;
+
+        public TeststandWrapper()
+        {
+            AxApplicationMgr = TestAppManager.GetAxApplicationMgr();
+            AxApplicationMgr.EndExecution += AxApplicationMgr_EndExecution;
+            AxApplicationMgr.UIMessageEvent += AxApplicationMgr_UIMessageEvent;
+
+            //AxApplicationMgr.ExitApplication += new EventHandler(this.AxApplicationMgr_ExitApplication);
+            //AxApplicationMgr.ExecutionClosed +=
+            //    new _ApplicationMgrEvents_ExecutionClosedEventHandler(this.AxApplicationMgr_ExecutionClosed);
+        }
+
+        private void AxApplicationMgr_UIMessageEvent(object sender, _ApplicationMgrEvents_UIMessageEventEvent e)
+        {
+            UIMessageEvent?.Invoke(this, new UIMessageEvent()
+            {
+                Origin = e,
+                EventCode = Convert.ToInt32(e.uiMsg.Event),
+                NumberData = Convert.ToInt16(e.uiMsg.NumericData),
+                Data = e.uiMsg.ActiveXData,
+                StrData = e.uiMsg.StringData
+            });
+        }
+
+        private void AxApplicationMgr_EndExecution(object sender, _ApplicationMgrEvents_EndExecutionEvent e)
+        {
+            EndExecutionEvent?.Invoke(this, new ExecutionEndEvent() { Origin = e });
+        }
+
+        public object Init()
+        {
+            this.engine = this.AxApplicationMgr.GetEngine();
+            this.engine.PersistBreakpoints = false;
+            this.AxApplicationMgr.IsEditor = true;
+            this.AxApplicationMgr.LoginOnStart = false;
+            this.AxApplicationMgr.Start();
+            return this.AxApplicationMgr;
+        }
+
+        public object StartSequence(string filePath, string modelPath)
+        {
+            this.testSeqFile = filePath;
+
+            this.modeSeq = this.engine.GetSequenceFileEx(modelPath);
+            this.testSeq = this.engine.GetSequenceFileEx(this.testSeqFile, 2, TypeConflictHandlerTypes.ConflictHandler_UseGlobalType);
+            this.execution = this.engine.NewExecution(this.testSeq, "Test UUTs", this.modeSeq, false, 0, null, null, null);
+            return execution;
+        }
+
+        public T GetFileGlobalValue<T>(string key, int option)
+        {
+            if (typeof(T) == typeof(bool))
+            {
+                return (T)(object)context?.FileGlobals?.GetValBoolean(key, option);
+            }
+            else if (typeof(T) == typeof(string))
+            {
+                return (T)(object)context?.FileGlobals?.GetValString(key, option);
+            }
+            else if (typeof(T) == typeof(int))
+            {
+                return (T)(object)context?.FileGlobals?.GetValNumber(key, option);
+            }
+            else if (typeof(T) == typeof(double))
+            {
+                return (T)(object)context?.FileGlobals?.GetValNumber(key, option);
+            }
+            else if (typeof(T) == typeof(float))
+            {
+                return (T)(object)context?.FileGlobals?.GetValNumber(key, option);
+            }
+            return default(T);
+        }
+
+        public T GetStationGlobalValue<T>(string key, int option)
+        {
+            if (typeof(T) == typeof(bool))
+            {
+                return (T)(object)engine?.Globals?.GetValBoolean(key, option);
+            }
+            else if (typeof(T) == typeof(string))
+            {
+                return (T)(object)engine?.Globals?.GetValString(key, option);
+            }
+            else if (typeof(T) == typeof(int))
+            {
+                return (T)(object)engine?.Globals?.GetValNumber(key, option);
+            }
+            else if (typeof(T) == typeof(double))
+            {
+                return (T)(object)engine?.Globals?.GetValNumber(key, option);
+            }
+            else if (typeof(T) == typeof(float))
+            {
+                return (T)(object)engine?.Globals?.GetValNumber(key, option);
+            }
+            return default(T);
+        }
+
+        public void SetFileGlobalValue<T>(string key, int option, T value)
+        {
+            if (value is bool bVal)
+            {
+                context?.FileGlobals?.SetValBoolean(key, option, bVal);
+            }
+            else if (value is string sVal)
+            {
+                context?.FileGlobals?.SetValString(key, option, sVal);
+            }
+            else if (value is int iVal)
+            {
+                context?.FileGlobals?.SetValNumber(key, option, iVal);
+            }
+            else if (value is double dVal)
+            {
+                context?.FileGlobals?.SetValNumber(key, option, dVal);
+            }
+            else if (value is float fVal)
+            {
+                context?.FileGlobals?.SetValNumber(key, option, fVal);
+            }
+        }
+
+        public void SetStationGlobalValue<T>(string key, int option, T value, bool inContext = false)
+        {
+            PropertyObject obj = null;
+            if (inContext)
+            {
+                obj = context?.StationGlobals;
+            }
+            else
+            {
+                obj = engine?.Globals;
+            }
+
+            if (value is bool bVal)
+            {
+                obj?.SetValBoolean(key, option, bVal);
+            }
+            else if (value is string sVal)
+            {
+                obj?.SetValString(key, option, sVal);
+            }
+            else if (value is int iVal)
+            {
+                obj?.SetValNumber(key, option, iVal);
+            }
+            else if (value is double dVal)
+            {
+                obj?.SetValNumber(key, option, dVal);
+            }
+            else if (value is float fVal)
+            {
+                obj?.SetValNumber(key, option, fVal);
+            }
+            //engine?.CommitGlobalsToDisk(promptOnSaveConflicts: false);
+        }
+
+        public void CommitToDisk(bool option)
+        {
+            engine?.CommitGlobalsToDisk(option);
+        }
+
+        public bool WaitForExit()
+        {
+            return this.execution?.WaitForEndEx(-1, true, Type.Missing, null) == true;
+        }
+
+        public bool ReleaseSeqFile()
+        {
+            if (testSeq != null)
+            {
+                return engine?.ReleaseSequenceFileEx(testSeq, 4) == true;
+            }
+            return true;
+        }
+
+        public void SetContext(object context)
+        {
+            this.context = (SequenceContext)context;
+        }
+    }
+}
diff --git a/Plugin.TsSupport/TsExecutionWrapper.cs b/Plugin.TsSupport/TsExecutionWrapper.cs
new file mode 100644
index 0000000..837bc75
--- /dev/null
+++ b/Plugin.TsSupport/TsExecutionWrapper.cs
@@ -0,0 +1,214 @@
+锘縰sing NationalInstruments.TestStand.Interop.API;
+using NationalInstruments.TestStand.Interop.UI.Ax;
+using System;
+
+namespace Plugin.TsSupport
+{
+    public class TsExecutionWrapper : ITestExecutionWrapper
+    {
+
+        #region TestStand浜嬩欢
+        /// <summary>
+        /// TS 寮曟搸
+        /// </summary>
+        private Engine engine = null;
+
+        /// <summary>
+        /// 娴嬭瘯搴忓垪璺緞
+        /// </summary>
+        private string testSeqFile = string.Empty;
+
+        /// <summary>
+        /// 娴嬭瘯搴忓垪
+        /// </summary>
+        private SequenceFile testSeq = null;
+
+        /// <summary>
+        /// 娴嬭瘯妯″瀷搴忓垪
+        /// </summary>
+        private SequenceFile modeSeq = null;
+
+        /// <summary>
+        /// 杩愯瀹炰緥
+        /// </summary>
+        private Execution execution = null;
+
+        public SequenceContext context { get; set; } = null;
+
+        /// <summary>
+        /// 浜嬩欢绠$悊鍣�
+        /// </summary>
+        public AxApplicationMgr AxApplicationMgr { get; private set; }
+
+        public event _ApplicationMgrEvents_EndExecutionEventHandler OnEndExecution;
+
+        public event EventHandler<UIMessageEvent> UIMessageEvent;
+        public event EventHandler<ExecutionEndEvent> EndExecutionEvent;
+
+        public TsExecutionWrapper()
+        {
+            AxApplicationMgr = TestAppManager.GetAxApplicationMgr();
+            //AxApplicationMgr.EndExecution += AxApplicationMgr_EndExecution;
+            //AxApplicationMgr.ExitApplication += new EventHandler(this.AxApplicationMgr_ExitApplication);
+            //AxApplicationMgr.UIMessageEvent +=
+            //    new _ApplicationMgrEvents_UIMessageEventEventHandler(this.AxApplicationMgr_UIMessageEvent);
+            //AxApplicationMgr.ExecutionClosed +=
+            //    new _ApplicationMgrEvents_ExecutionClosedEventHandler(this.AxApplicationMgr_ExecutionClosed);
+        }
+
+        #endregion
+
+        public object Init()
+        {
+            this.engine = this.AxApplicationMgr.GetEngine();
+            this.engine.PersistBreakpoints = false;
+            this.AxApplicationMgr.IsEditor = true;
+            this.AxApplicationMgr.LoginOnStart = false;
+            this.AxApplicationMgr.Start();
+            return this.AxApplicationMgr;
+        }
+
+        public object StartSequence(string filePath, string modelPath)
+        {
+            this.testSeqFile = filePath;
+
+            this.modeSeq = this.engine.GetSequenceFileEx(modelPath);
+            this.testSeq = this.engine.GetSequenceFileEx(this.testSeqFile, 2, TypeConflictHandlerTypes.ConflictHandler_UseGlobalType);
+            this.execution = this.engine.NewExecution(this.testSeq, "Test UUTs", this.modeSeq, false, 0, null, null, null);
+            return execution;
+        }
+
+        public T GetFileGlobalValue<T>(string key, int option)
+        {
+            if (typeof(T) == typeof(bool))
+            {
+                return (T)(object)context?.FileGlobals?.GetValBoolean(key, option);
+            }
+            else if (typeof(T) == typeof(string))
+            {
+                return (T)(object)context?.FileGlobals?.GetValString(key, option);
+            }
+            else if (typeof(T) == typeof(int))
+            {
+                return (T)(object)context?.FileGlobals?.GetValNumber(key, option);
+            }
+            else if (typeof(T) == typeof(double))
+            {
+                return (T)(object)context?.FileGlobals?.GetValNumber(key, option);
+            }
+            else if (typeof(T) == typeof(float))
+            {
+                return (T)(object)context?.FileGlobals?.GetValNumber(key, option);
+            }
+            return default(T);
+        }
+
+        public T GetStationGlobalValue<T>(string key, int option)
+        {
+            if (typeof(T) == typeof(bool))
+            {
+                return (T)(object)engine?.Globals?.GetValBoolean(key, option);
+            }
+            else if (typeof(T) == typeof(string))
+            {
+                return (T)(object)engine?.Globals?.GetValString(key, option);
+            }
+            else if (typeof(T) == typeof(int))
+            {
+                return (T)(object)engine?.Globals?.GetValNumber(key, option);
+            }
+            else if (typeof(T) == typeof(double))
+            {
+                return (T)(object)engine?.Globals?.GetValNumber(key, option);
+            }
+            else if (typeof(T) == typeof(float))
+            {
+                return (T)(object)engine?.Globals?.GetValNumber(key, option);
+            }
+            return default(T);
+        }
+
+        public void SetFileGlobalValue<T>(string key, int option, T value)
+        {
+            if (value is bool bVal)
+            {
+                context?.FileGlobals?.SetValBoolean(key, option, bVal);
+            }
+            else if (value is string sVal)
+            {
+                context?.FileGlobals?.SetValString(key, option, sVal);
+            }
+            else if (value is int iVal)
+            {
+                context?.FileGlobals?.SetValNumber(key, option, iVal);
+            }
+            else if (value is double dVal)
+            {
+                context?.FileGlobals?.SetValNumber(key, option, dVal);
+            }
+            else if (value is float fVal)
+            {
+                context?.FileGlobals?.SetValNumber(key, option, fVal);
+            }
+        }
+
+        public void SetStationGlobalValue<T>(string key, int option, T value, bool inContext = false)
+        {
+            PropertyObject obj = null;
+            if (inContext)
+            {
+                obj = context?.StationGlobals;
+            }
+            else
+            {
+                obj = engine?.Globals;
+            }
+
+            if (value is bool bVal)
+            {
+                obj?.SetValBoolean(key, option, bVal);
+            }
+            else if (value is string sVal)
+            {
+                obj?.SetValString(key, option, sVal);
+            }
+            else if (value is int iVal)
+            {
+                obj?.SetValNumber(key, option, iVal);
+            }
+            else if (value is double dVal)
+            {
+                obj?.SetValNumber(key, option, dVal);
+            }
+            else if (value is float fVal)
+            {
+                obj?.SetValNumber(key, option, fVal);
+            }
+            //engine?.CommitGlobalsToDisk(promptOnSaveConflicts: false);
+        }
+
+        public void CommitToDisk(bool option)
+        {
+            engine?.CommitGlobalsToDisk(option);
+        }
+
+        public bool WaitForExit()
+        {
+            return this.execution?.WaitForEndEx(-1, true, Type.Missing, null) == true;
+        }
+
+        public bool ReleaseSeqFile()
+        {
+            if (testSeq != null)
+            {
+                return engine?.ReleaseSequenceFileEx(testSeq, 4) == true;
+            }
+            return true;
+        }
+
+        public void SetContext(object context)
+        {
+
+        }
+    }
+}
diff --git a/Plugin.UICollection/AssemblyInfo.cs b/Plugin.UICollection/AssemblyInfo.cs
new file mode 100644
index 0000000..cc29e7f
--- /dev/null
+++ b/Plugin.UICollection/AssemblyInfo.cs
@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly:ThemeInfo(
+    ResourceDictionaryLocation.None,            //where theme specific resource dictionaries are located
+                                                //(used if a resource is not found in the page,
+                                                // or application resource dictionaries)
+    ResourceDictionaryLocation.SourceAssembly   //where the generic resource dictionary is located
+                                                //(used if a resource is not found in the page,
+                                                // app, or any theme specific resource dictionaries)
+)]
diff --git a/Plugin.UICollection/Plugin.UICollection.csproj b/Plugin.UICollection/Plugin.UICollection.csproj
new file mode 100644
index 0000000..97514d3
--- /dev/null
+++ b/Plugin.UICollection/Plugin.UICollection.csproj
@@ -0,0 +1,25 @@
+锘�<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Library</OutputType>
+    <TargetFramework>net472</TargetFramework>
+    <Nullable>enable</Nullable>
+    <LangVersion>10.0</LangVersion>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <UseWPF>true</UseWPF>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
+    <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
+    <PackageReference Include="Panuon.WPF.UI" Version="1.3.0.2" />
+    <PackageReference Include="TouchSocket" Version="4.2.0" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\UILib\UILib.csproj" />
+    <ProjectReference Include="..\..\UtilLib\UtilLib.csproj" />
+    <ProjectReference Include="..\HMI.UI\HMI.UI.csproj" />
+  </ItemGroup>
+
+</Project>
diff --git a/Plugin.UICollection/TcpDebugger.xaml b/Plugin.UICollection/TcpDebugger.xaml
new file mode 100644
index 0000000..2224d08
--- /dev/null
+++ b/Plugin.UICollection/TcpDebugger.xaml
@@ -0,0 +1,176 @@
+锘�<UserControl x:Class="Plugin.UICollection.TcpDebugger"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:Plugin.UICollection"
+             mc:Ignorable="d" 
+             xmlns:pu="https://opensource.panuon.com/wpf-ui"
+             xmlns:dp="clr-namespace:UILib.DpUtil;assembly=UILib"
+             xmlns:uic="clr-namespace:UILib.Converter;assembly=UILib"
+             FontSize="16"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <UserControl.Resources>
+        <ResourceDictionary>
+            <!--<ResourceDictionary.MergedDictionaries>
+                <pu:StyleDictionary Excludes="Button" />
+            </ResourceDictionary.MergedDictionaries>-->
+            <Style TargetType="ComboBox"/>
+            <Style TargetType="ComboBoxItem"/>
+            <Style TargetType="Button">
+                
+            </Style>
+            <Style TargetType="GroupBox">
+
+            </Style>
+            <Style TargetType="TextBox">
+                <Setter Property="VerticalContentAlignment" Value="Center"/>
+                <Setter Property="MinWidth" Value="120"/>
+                <Setter Property="Height" Value="Auto"/>
+            </Style>
+        </ResourceDictionary>
+    </UserControl.Resources>
+
+    <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition/>
+            <RowDefinition Height="Auto"/>
+        </Grid.RowDefinitions>
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="Auto"/>
+            <ColumnDefinition/>
+        </Grid.ColumnDefinitions>
+        <StackPanel dp:StackPanelHelper.ChildSpace="10" Margin="0,10,0,10">
+            <pu:FormGroup Header="IP" GroupName="client">
+                <TextBox Text="{Binding Ip}"/>
+            </pu:FormGroup>
+
+            <pu:FormGroup Header="Port" GroupName="client">
+                <TextBox Text="{Binding Port}"/>
+            </pu:FormGroup>
+
+            <pu:FormGroup Header="瀹㈡埛绔鍙�" GroupName="client">
+                <TextBox Text="{Binding ClientPort}"/>
+            </pu:FormGroup>
+
+            <Label>
+                <Label.Style>
+                    <Style TargetType="Label">
+                        <Setter Property="Foreground" Value="White"/>
+                        <Setter Property="Background" Value="Red"/>
+                        <Setter Property="VerticalContentAlignment" Value="Center"/>
+                        <Setter Property="HorizontalContentAlignment" Value="Center"/>
+                        <Setter Property="Content" Value="鏈繛鎺�"/>
+                        <Style.Triggers>
+                            <DataTrigger Binding="{Binding IsConnected}" Value="True">
+                                <Setter Property="Background" Value="Green"/>
+                                <Setter Property="Content" Value="宸茶繛鎺�"/>
+                            </DataTrigger>
+                        </Style.Triggers>
+                    </Style>
+                </Label.Style>
+            </Label>
+
+            <Button Tag="Connect" Content="杩炴帴" Click="Button_Click"/>
+            <GroupBox Header="鎺ユ敹璁剧疆">
+                <StackPanel dp:StackPanelHelper.ChildSpace="10" Margin="0,10">
+                    <StackPanel Orientation="Horizontal">
+                        <RadioButton Content="瀛楃涓�" IsChecked="{Binding ReceiveDataType,Converter={x:Static uic:RadioEnumConverter.Instance},ConverterParameter={x:Static local:DataType.String}}" GroupName="ReceiveType"/>
+                        <RadioButton Content="Hex"  IsChecked="{Binding ReceiveDataType,Converter={x:Static uic:RadioEnumConverter.Instance},ConverterParameter={x:Static local:DataType.HEX}}" GroupName="ReceiveType"/>
+                    </StackPanel>
+                    <pu:FormGroup Header="缂栫爜鏍煎紡">
+                        <ComboBox x:Name="receiveEncodeInput" SelectedValue="{Binding ReceiveEncode}"/>
+                        <pu:FormGroup.Style>
+                            <Style TargetType="pu:FormGroup">
+                                <Setter Property="IsEnabled" Value="False"/>
+                                <Style.Triggers>
+                                    <DataTrigger Binding="{Binding ReceiveDataType}" Value="{x:Static local:DataType.String}">
+                                        <Setter Property="IsEnabled" Value="True"/>
+                                    </DataTrigger>
+                                </Style.Triggers>
+                            </Style>
+                        </pu:FormGroup.Style>
+                    </pu:FormGroup>
+                </StackPanel>
+            </GroupBox>
+        </StackPanel>
+
+        <StackPanel Grid.Row="1" Grid.Column="0">
+            <GroupBox Header="鍙戦�佽缃�">
+                <StackPanel dp:StackPanelHelper.ChildSpace="10" Margin="0,10">
+                    <StackPanel Orientation="Horizontal">
+                        <RadioButton Content="瀛楃涓�" IsChecked="{Binding SendDataType,Converter={x:Static uic:RadioEnumConverter.Instance},ConverterParameter={x:Static local:DataType.String}}" GroupName="SendType"/>
+                        <RadioButton Content="Hex"  IsChecked="{Binding SendDataType,Converter={x:Static uic:RadioEnumConverter.Instance},ConverterParameter={x:Static local:DataType.HEX}}" GroupName="SendType"/>
+                    </StackPanel>
+
+                    <pu:FormGroup Header="缂栫爜鏍煎紡">
+                        <ComboBox x:Name="sendEncodeInput" SelectedValue="{Binding SendEncode}"/>
+                        <pu:FormGroup.Style>
+                            <Style TargetType="pu:FormGroup">
+                                <Setter Property="IsEnabled" Value="False"/>
+                                <Style.Triggers>
+                                    <DataTrigger Binding="{Binding SendDataType}" Value="{x:Static local:DataType.String}">
+                                        <Setter Property="IsEnabled" Value="True"/>
+                                    </DataTrigger>
+                                </Style.Triggers>
+                            </Style>
+                        </pu:FormGroup.Style>
+                    </pu:FormGroup>
+
+                    <pu:FormGroup Header="鍒嗛殧绗�">
+                        <ComboBox ItemsSource="{Binding EndMarkSource}" SelectedValue="{Binding EndMark}" DisplayMemberPath="Name" SelectedValuePath="Type"/>
+                        <pu:FormGroup.Style>
+                            <Style TargetType="pu:FormGroup">
+                                <Setter Property="IsEnabled" Value="False"/>
+                                <Style.Triggers>
+                                    <DataTrigger Binding="{Binding SendDataType}" Value="{x:Static local:DataType.String}">
+                                        <Setter Property="IsEnabled" Value="True"/>
+                                    </DataTrigger>
+                                </Style.Triggers>
+                            </Style>
+                        </pu:FormGroup.Style>
+                    </pu:FormGroup>
+                    <pu:FormGroup Header="鑷畾涔夊垎闅旂">
+                        <TextBox Text="{Binding CustomEndMark}"/>
+                        <pu:FormGroup.Style>
+                            <Style TargetType="pu:FormGroup">
+                                <Setter Property="IsEnabled" Value="False"/>
+                                <Style.Triggers>
+                                    <MultiDataTrigger>
+                                        <MultiDataTrigger.Conditions>
+                                            <Condition Binding="{Binding SendDataType}" Value="{x:Static local:DataType.String}"/>
+                                            <Condition Binding="{Binding EndMark}" Value="2"/>
+                                        </MultiDataTrigger.Conditions>
+                                        <Setter Property="IsEnabled" Value="True"/>
+                                    </MultiDataTrigger>
+                                </Style.Triggers>
+                            </Style>
+                        </pu:FormGroup.Style>
+                    </pu:FormGroup>
+                </StackPanel>
+            </GroupBox>
+        </StackPanel>
+
+        <Grid Grid.Row="1" Grid.Column="1">
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition/>
+                <ColumnDefinition Width="Auto"/>
+            </Grid.ColumnDefinitions>
+            <GroupBox Header="鏁版嵁鍙戦��">
+                <TextBox x:Name="sendBox" TextWrapping="Wrap" VerticalContentAlignment="Top"
+                         PreviewTextInput="TextBox_PreviewTextInput"/>
+            </GroupBox>
+            <Button Grid.Column="1" Padding="20" Content="鍙戦��" Tag="Send" Click="Button_Click"/>
+        </Grid>
+
+        <Grid Grid.Row="0" Grid.Column="1">
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition/>
+                <ColumnDefinition Width="Auto"/>
+            </Grid.ColumnDefinitions>
+            <GroupBox Header="鏁版嵁鎺ユ敹">
+                <RichTextBox x:Name="console" IsReadOnly="True"/>
+            </GroupBox>
+        </Grid>
+    </Grid>
+</UserControl>
diff --git a/Plugin.UICollection/TcpDebugger.xaml.cs b/Plugin.UICollection/TcpDebugger.xaml.cs
new file mode 100644
index 0000000..3b52834
--- /dev/null
+++ b/Plugin.UICollection/TcpDebugger.xaml.cs
@@ -0,0 +1,393 @@
+锘縰sing CommunityToolkit.Mvvm.ComponentModel;
+using System.Collections.ObjectModel;
+using System.Globalization;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Media;
+using TouchSocket.Core;
+using TouchSocket.Sockets;
+using UtilLib;
+
+namespace Plugin.UICollection
+{
+    public enum DataType
+    {
+        String = 0,
+        HEX = 1
+    }
+
+    public enum SupportEncode
+    {
+        GBK = 0,
+        ASCII = 1,
+        UTF8 = 2
+    }
+
+    public class EndMarkSource
+    {
+        public string Name { get; set; }
+        public int Type { get; set; }
+    }
+
+    public class ByteStringConverter : IValueConverter
+    {
+        private TcpDebugger control { get; set; }
+
+        public ByteStringConverter(TcpDebugger control)
+        {
+            this.control = control;
+        }
+
+        public static string GroupByTwo(string str)
+        {
+            if (string.IsNullOrEmpty(str))
+                return string.Empty;
+
+            StringBuilder result = new StringBuilder();
+            for (int i = 0; i < str.Length; i += 2)
+            {
+                if (i > 0)
+                    result.Append(' ');
+
+                int length = Math.Min(2, str.Length - i);
+                result.Append(str.Substring(i, length));
+            }
+            return result.ToString();
+        }
+
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            DataType sendType = control.SendDataType;
+            if (sendType != DataType.HEX)
+            {
+                return value;
+            }
+            var str = value.ToString();
+            return GroupByTwo(str);
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            DataType sendType = control.SendDataType;
+            if (sendType != DataType.HEX)
+            {
+                return value;
+            }
+            return value.ToString().Replace(" ", "");
+        }
+    }
+
+    /// <summary>
+    /// TcpDebugger.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    [ObservableObject]
+    public partial class TcpDebugger : UserControl
+    {
+
+        [ObservableProperty]
+        private string ip = "127.0.0.1";
+
+        [ObservableProperty]
+        private int port;
+
+        [ObservableProperty]
+        private int clientPort;
+
+        [ObservableProperty]
+        private DataType receiveDataType = DataType.String;
+
+        [ObservableProperty]
+        private DataType sendDataType = DataType.String;
+
+        [ObservableProperty]
+        private bool isConnected = false;
+
+        [ObservableProperty]
+        private string sendText;
+
+        [ObservableProperty]
+        private SupportEncode receiveEncode;
+
+        [ObservableProperty]
+        private SupportEncode sendEncode;
+
+        [ObservableProperty]
+        private ObservableCollection<EndMarkSource> endMarkSource = new ObservableCollection<EndMarkSource> {
+            new EndMarkSource{ Name="鏃�",Type=-1},
+            new EndMarkSource{ Name="\\r",Type=0},
+            new EndMarkSource{ Name="\\r\\n",Type=1},
+            new EndMarkSource{ Name="鑷畾涔�",Type=2},
+        };
+
+        [ObservableProperty]
+        private int endMark;
+
+        [ObservableProperty]
+        private string customEndMark;
+
+        private ByteStringConverter bsConverter;
+
+
+        private TcpClient client;
+
+        public TcpDebugger()
+        {
+            DataContext = this;
+            InitializeComponent();
+            sendEncodeInput.ItemsSource = Enum.GetValues(typeof(SupportEncode)).Cast<SupportEncode>();
+            receiveEncodeInput.ItemsSource = Enum.GetValues(typeof(SupportEncode)).Cast<SupportEncode>();
+            DataObject.AddPastingHandler(sendBox, OnPasting);
+            bsConverter = new ByteStringConverter(this);
+            BindingOperations.SetBinding(sendBox, TextBox.TextProperty, new Binding(nameof(SendText))
+            {
+                Source = this,
+                Mode = BindingMode.TwoWay,
+                UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
+                Converter = bsConverter
+            });
+        }
+
+        private void Button_Click(object sender, RoutedEventArgs e)
+        {
+            var btn = (Button)sender;
+            if (btn.Tag.ToString() == "Connect")
+            {
+                TryConnect();
+                return;
+            }
+
+            if (btn.Tag.ToString() == "Send")
+            {
+                TrySend();
+                return;
+            }
+
+        }
+
+        private void TryConnect()
+        {
+            try
+            {
+                if (IsConnected) return;
+                var config = new TouchSocketConfig().SetRemoteIPHost($"tcp://{Ip}:{Port}");
+                client = new TcpClient();
+                client.Received = TcpReceived;
+                client.Connected = (sender, e) =>
+                {
+                    Dispatcher.Invoke(() =>
+                    {
+                        Add("杩炴帴", "杩炴帴鎴愬姛", Brushes.Green);
+                        IsConnected = true;
+                    });
+                    return EasyTask.CompletedTask;
+                };
+                client.Closed = (sender, e) =>
+                {
+                    Dispatcher.Invoke(() =>
+                    {
+                        Add("杩炴帴", "杩炴帴鏂紑", Brushes.Red);
+                        IsConnected = false;
+                    });
+                    return EasyTask.CompletedTask;
+                };
+                client.SetupAsync(config);
+                client.ConnectAsync();
+            }
+            catch (Exception ex)
+            {
+                IsConnected = false;
+                // throw ex;
+            }
+        }
+
+        private Task TcpReceived(object sender, ReceivedDataEventArgs e)
+        {
+            try
+            {
+                var datas = e.Memory.Span.ToArray();
+                string msg = null;
+                if (ReceiveDataType == DataType.HEX)
+                {
+                    msg = HexUtil.ByteArrayToHex(datas);
+                }
+                else
+                {
+                    if (ReceiveEncode == SupportEncode.GBK)
+                    {
+                        msg = Encoding.Default.GetString(datas);
+                    }
+                    else if (ReceiveEncode == SupportEncode.ASCII)
+                    {
+                        msg = Encoding.ASCII.GetString(datas);
+                    }
+                    else
+                    {
+
+                        msg = Encoding.UTF8.GetString(datas);
+                    }
+                }
+                Add("鎺ユ敹", msg, Brushes.Green);
+            }
+            catch (Exception ex)
+            {
+                //log.Error($"TcpReceived Error.", ex);
+            }
+            return EasyTask.CompletedTask;
+        }
+
+        private byte[] GetBytes(out string msg)
+        {
+            msg = null;
+            if (string.IsNullOrEmpty(SendText))
+            {
+                return null;
+            }
+            if (SendDataType == DataType.String)
+            {
+                msg = SendText + GetEndMark();
+                if (SendEncode == SupportEncode.GBK)
+                {
+                    return Encoding.Default.GetBytes(msg);
+                }
+                if (SendEncode == SupportEncode.ASCII)
+                {
+                    return Encoding.ASCII.GetBytes(msg);
+                }
+                if (SendEncode == SupportEncode.UTF8)
+                {
+                    return Encoding.UTF8.GetBytes(msg);
+                }
+                return null;
+            }
+            msg = SendText;
+            if (msg.Length % 2 != 0)
+            {
+                msg = msg.Insert(msg.Length - 1, "0");
+            }
+            msg = ByteStringConverter.GroupByTwo(msg);
+            return HexUtil.HexToByteArray(msg);
+        }
+
+        private string GetEndMark()
+        {
+            if (EndMark == -1)
+            {
+                return string.Empty;
+            }
+            if (EndMark == 0)
+            {
+                return "\r";
+            }
+            if (EndMark == 1)
+            {
+                return "\r\n";
+            }
+            if (EndMark == 2)
+            {
+                return CustomEndMark;
+            }
+            return string.Empty;
+        }
+
+        public void TrySend()
+        {
+            if (!IsConnected)
+            {
+                MessageBox.Show("璇峰厛杩炴帴", "鎻愮ず");
+                return;
+            }
+            var datas = GetBytes(out string msg);
+            if (datas != null)
+            {
+                client.SendAsync(datas).Wait();
+                Add("鍙戦��", msg, Brushes.Blue);
+                // SendText = string.Empty;
+            }
+        }
+
+        public void Add(string type, string text, Brush color)
+        {
+            Dispatcher.BeginInvoke(() =>
+            {
+                string time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+                Run run = new Run();
+                run.Text = $"{time} {type}\r\n";
+                run.Foreground = Brushes.Gray;
+
+                Paragraph paragraph = new Paragraph(run)
+                {
+                    LineHeight = 1
+                };
+
+                run = new Run();
+                run.Text = $"{text}\r\n";
+                run.Foreground = color;
+                paragraph.Inlines.Add(run);
+                //涔熷彲浠ユ彃鍏ュ埌鏈�鍚庨潰
+                console.Document.Blocks.InsertAfter(console.Document.Blocks.LastBlock, paragraph);
+
+                //鎻掑叆鍒版渶鍓嶉潰
+                //rtxtLog.Document.Blocks.Add(paragraph);
+
+                // 婊氬姩鑷虫渶鍚庤
+                //rtxtLog.ScrollToEnd();
+                //Console.ScrollToHome();
+                console.ScrollToEnd();
+                // 鍒犻櫎
+                //if (Console.Document.Blocks.Count > 10000)
+                //{
+                //    for (int i = 1000; i < Console.Document.Blocks.Count; i++)
+                //    {
+                //        _ = Console.Document.Blocks.Remove(Console.Document.Blocks.FirstBlock);
+                //    }
+                //}
+
+            }, System.Windows.Threading.DispatcherPriority.Normal);
+        }
+
+        private static readonly Regex HexRegex = new Regex("^[0-9A-Fa-f]+$");
+
+        private void TextBox_PreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
+        {
+            if (SendDataType != DataType.HEX)
+            {
+                return;
+            }
+            var tb = (TextBox)sender;
+            string segment = e.Text;
+
+            if (!HexRegex.IsMatch(segment))
+            {
+                tb.Text = tb.Text.Replace(segment, "");
+                tb.CaretIndex = tb.Text.Length;
+                e.Handled = true;
+                return;
+            }
+            // tb.Text = tb.Text.ToUpper();
+
+            base.OnPreviewTextInput(e);
+        }
+
+        private void OnPasting(object sender, DataObjectPastingEventArgs e)
+        {
+            if (SendDataType != DataType.HEX)
+            {
+                return;
+            }
+
+            if (e.DataObject.GetDataPresent(typeof(string)))
+            {
+                string text = (string)e.DataObject.GetData(typeof(string));
+                text = text.Replace(" ", "");
+                if (!HexRegex.IsMatch(text))
+                {
+                    e.CancelCommand();
+                }
+            }
+        }
+    }
+}
diff --git a/SimpleUtil/HMI.Simple.Util/Parameter.cs b/SimpleUtil/HMI.Simple.Util/Parameter.cs
index 71237da..c8650ce 100644
--- a/SimpleUtil/HMI.Simple.Util/Parameter.cs
+++ b/SimpleUtil/HMI.Simple.Util/Parameter.cs
@@ -5,6 +5,7 @@
         public string Id { get; set; }
         public string Name { get; set; }
         public string Type { get; set; }
+        public bool IsArray { get; set; }
         public string Value { get; set; }
         public string Unit { get; set; }
         public string Description { get; set; }
diff --git a/SimpleUtil/HMI.Simple.Util/ParameterUtil.cs b/SimpleUtil/HMI.Simple.Util/ParameterUtil.cs
index ae461c2..8e1054e 100644
--- a/SimpleUtil/HMI.Simple.Util/ParameterUtil.cs
+++ b/SimpleUtil/HMI.Simple.Util/ParameterUtil.cs
@@ -59,21 +59,72 @@
         private static void WriteParameterData(SequenceContext context, ParameterData data)
         {
             string prefix = "SystemSetting";
-            if (data.Type == "Bool")
+
+            if (data.IsArray)
             {
-                context.StationGlobals.SetValBoolean($"{prefix}.{data.Name}", 1, bool.Parse(data.Value));
-            }
-            else if (data.Type == "Int")
-            {
-                context.StationGlobals.SetValNumber($"{prefix}.{data.Name}", 1, int.Parse(data.Value));
-            }
-            else if (data.Type == "Double")
-            {
-                context.StationGlobals.SetValNumber($"{prefix}.{data.Name}", 1, double.Parse(data.Value));
+                PropertyValueTypes type;
+                if (data.Type == "Bool")
+                {
+                    type = PropertyValueTypes.PropValType_Boolean;
+                }
+                else if (data.Type == "Int")
+                {
+                    type = PropertyValueTypes.PropValType_Number;
+                }
+                else if (data.Type == "Double")
+                {
+                    type = PropertyValueTypes.PropValType_Number;
+                }
+                else
+                {
+                    type = PropertyValueTypes.PropValType_String;
+                }
+                string[] arr = data.Value?.Split(',') ?? new string[0];
+                if (arr.Length > 0)
+                {
+                    context.StationGlobals.DeleteSubProperty($"{prefix}.{data.Name}", 2);
+
+                    context.StationGlobals.NewSubProperty($"{prefix}.{data.Name}", type, true, null, 1);
+                    context.StationGlobals.GetPropertyObject($"{prefix}.{data.Name}", 0).SetNumElements(arr.Length);
+                    for (int i = 0; i < arr.Length; i++)
+                    {
+                        if (type == PropertyValueTypes.PropValType_String)
+                        {
+                            context.StationGlobals.SetValString($"{prefix}.{data.Name}[{i}]", 0, arr[i]);
+                        }
+                        if (data.Type == "Int")
+                        {
+                            context.StationGlobals.SetValNumber($"{prefix}.{data.Name}[{i}]", 0, int.Parse(arr[i]));
+                        }
+                        if (data.Type == "Double")
+                        {
+                            context.StationGlobals.SetValNumber($"{prefix}.{data.Name}[{i}]", 0, double.Parse(arr[i]));
+                        }
+                        if (data.Type == "Bool")
+                        {
+                            context.StationGlobals.SetValBoolean($"{prefix}.{data.Name}[{i}]", 0, bool.Parse(arr[i]));
+                        }
+                    }
+                }
             }
             else
             {
-                context.StationGlobals.SetValString($"{prefix}.{data.Name}", 1, data.Value);
+                if (data.Type == "Bool")
+                {
+                    context.StationGlobals.SetValBoolean($"{prefix}.{data.Name}", 1, bool.Parse(data.Value));
+                }
+                else if (data.Type == "Int")
+                {
+                    context.StationGlobals.SetValNumber($"{prefix}.{data.Name}", 1, int.Parse(data.Value));
+                }
+                else if (data.Type == "Double")
+                {
+                    context.StationGlobals.SetValNumber($"{prefix}.{data.Name}", 1, double.Parse(data.Value));
+                }
+                else
+                {
+                    context.StationGlobals.SetValString($"{prefix}.{data.Name}", 1, data.Value);
+                }
             }
         }
 
diff --git a/SimpleUtil/HMI.Simple.Util/ProductUtil.cs b/SimpleUtil/HMI.Simple.Util/ProductUtil.cs
index 9a7e156..fe8c631 100644
--- a/SimpleUtil/HMI.Simple.Util/ProductUtil.cs
+++ b/SimpleUtil/HMI.Simple.Util/ProductUtil.cs
@@ -10,22 +10,86 @@
         private static void WriteParameterData(object instance, ParameterData data)
         {
             SequenceContext context = (SequenceContext)instance;
-            if (data.Type == "Bool")
+            string name = $"DUTSetting.{data.Name}";
+
+            if (data.IsArray)
             {
-                context.FileGlobals.SetValBoolean($"DUTSetting.{data.Name}", 1, bool.Parse(data.Value));
-            }
-            else if (data.Type == "Int")
-            {
-                context.FileGlobals.SetValNumber($"DUTSetting.{data.Name}", 1, int.Parse(data.Value));
-            }
-            else if (data.Type == "Double")
-            {
-                context.FileGlobals.SetValNumber($"DUTSetting.{data.Name}", 1, double.Parse(data.Value));
+                PropertyValueTypes type;
+                if (data.Type == "Bool")
+                {
+                    type = PropertyValueTypes.PropValType_Boolean;
+                }
+                else if (data.Type == "Int")
+                {
+                    type = PropertyValueTypes.PropValType_Number;
+                }
+                else if (data.Type == "Double")
+                {
+                    type = PropertyValueTypes.PropValType_Number;
+                }
+                else
+                {
+                    type = PropertyValueTypes.PropValType_String;
+                }
+                string[] arr = data.Value?.Split(',') ?? new string[0];
+                if (arr.Length > 0)
+                {
+                    context.FileGlobals.DeleteSubProperty(name, 2);
+
+                    context.FileGlobals.NewSubProperty(name, type, true, null, 1);
+                    context.FileGlobals.GetPropertyObject(name, 0).SetNumElements(arr.Length);
+                    for (int i = 0; i < arr.Length; i++)
+                    {
+                        if (type == PropertyValueTypes.PropValType_String)
+                        {
+                            context.FileGlobals.SetValString($"{name}[{i}]", 0, arr[i]);
+                        }
+                        if (data.Type == "Int")
+                        {
+                            context.FileGlobals.SetValNumber($"{name}[{i}]", 0, int.Parse(arr[i]));
+                        }
+                        if (data.Type == "Double")
+                        {
+                            context.FileGlobals.SetValNumber($"{name}[{i}]", 0, double.Parse(arr[i]));
+                        }
+                        if (data.Type == "Bool")
+                        {
+                            context.FileGlobals.SetValBoolean($"{name}[{i}]", 0, bool.Parse(arr[i]));
+                        }
+                    }
+                }
             }
             else
             {
-                context.FileGlobals.SetValString($"DUTSetting.{data.Name}", 1, data.Value);
+                if (data.Type == "Bool")
+                {
+                    context.FileGlobals.SetValBoolean(name, 1, bool.Parse(data.Value));
+                }
+                else if (data.Type == "Int")
+                {
+                    context.FileGlobals.SetValNumber(name, 1, int.Parse(data.Value));
+                }
+                else if (data.Type == "Double")
+                {
+                    context.FileGlobals.SetValNumber(name, 1, double.Parse(data.Value));
+                }
+                else
+                {
+                    context.FileGlobals.SetValString(name, 1, data.Value);
+                }
             }
+
+
+        }
+
+        private static string GetDataFilePath()
+        {
+            string path = ParameterUtil.GetCommonParameterValue("ShareConfig", out _);
+            if (path == null)
+            {
+                path = Path.Combine(RegUtil.GetLocaltion(), "Config");
+            }
+            return path;
         }
 
         /// <summary>
@@ -40,8 +104,7 @@
             errMsg = string.Empty;
             try
             {
-                string dir = RegUtil.GetLocaltion();
-                string path = Path.Combine(dir, "Config", "Product", "Parameter", $"Parameter_{model}.data");
+                string path = Path.Combine(GetDataFilePath(), "Product", "Parameter", $"Parameter_{model}.data");
                 return WriteConfigToContextByPath(path, instance, out errMsg);
             }
             catch (Exception ex)
diff --git a/SimpleUtil/HMI.Simple.Util/Properties/AssemblyInfo.cs b/SimpleUtil/HMI.Simple.Util/Properties/AssemblyInfo.cs
index 72ab98f..78f8be5 100644
--- a/SimpleUtil/HMI.Simple.Util/Properties/AssemblyInfo.cs
+++ b/SimpleUtil/HMI.Simple.Util/Properties/AssemblyInfo.cs
@@ -1,5 +1,4 @@
 锘縰sing System.Reflection;
-using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 
 // 鏈夊叧绋嬪簭闆嗙殑涓�鑸俊鎭敱浠ヤ笅
@@ -29,5 +28,5 @@
 //      鐢熸垚鍙�
 //      淇鍙�
 //
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.2")]
+[assembly: AssemblyVersion("1.0.0.3")]
+[assembly: AssemblyFileVersion("1.0.0.3")]
diff --git a/Util.ManualDebug/AssemblyInfo.cs b/Util.ManualDebug/AssemblyInfo.cs
new file mode 100644
index 0000000..cc29e7f
--- /dev/null
+++ b/Util.ManualDebug/AssemblyInfo.cs
@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly:ThemeInfo(
+    ResourceDictionaryLocation.None,            //where theme specific resource dictionaries are located
+                                                //(used if a resource is not found in the page,
+                                                // or application resource dictionaries)
+    ResourceDictionaryLocation.SourceAssembly   //where the generic resource dictionary is located
+                                                //(used if a resource is not found in the page,
+                                                // app, or any theme specific resource dictionaries)
+)]
diff --git a/Util.ManualDebug/ManualDebug.xaml b/Util.ManualDebug/ManualDebug.xaml
new file mode 100644
index 0000000..ca818b0
--- /dev/null
+++ b/Util.ManualDebug/ManualDebug.xaml
@@ -0,0 +1,133 @@
+<UserControl x:Class="Util.SB.ManualDebug"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        xmlns:local="clr-namespace:Util.SB"
+        mc:Ignorable="d"
+        x:Name="self"
+        MinHeight="400" MinWidth="500">
+
+    <UserControl.Resources>
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <ResourceDictionary Source="/Util.ManualDebug;component/ManualDebugStyle.xaml"/>
+            </ResourceDictionary.MergedDictionaries>
+        </ResourceDictionary>
+    </UserControl.Resources>
+
+    <Grid>
+        <Grid Margin="10">
+            <Grid.RowDefinitions>
+                <RowDefinition Height="Auto"/>
+                <RowDefinition Height="5"/>
+                <RowDefinition Height="*"/>
+                <RowDefinition Height="5"/>
+                <RowDefinition Height="*"/>
+            </Grid.RowDefinitions>
+            <WindowsFormsHost x:Name="host" Visibility="Collapsed"></WindowsFormsHost>
+
+            <!-- 鎸夐挳鍜岀姸鎬佸尯鍩� -->
+            <Border Grid.Row="0" Background="{StaticResource TsPanelBrush}" 
+                BorderBrush="{StaticResource BorderBrush}" 
+                BorderThickness="1" CornerRadius="4" Padding="15">
+                <StackPanel>
+                    <TextBlock Text="鎵嬪姩璋冭瘯鎺у埗" FontSize="14" FontWeight="SemiBold" 
+                           Foreground="{StaticResource PrimaryBrush}" Margin="0,0,0,15"/>
+
+                    <WrapPanel x:Name="btnPanel">
+
+                    </WrapPanel>
+                </StackPanel>
+            </Border>
+
+            <!-- 鍒嗛殧鏉�1 -->
+            <GridSplitter Grid.Row="1" Height="5" HorizontalAlignment="Stretch" 
+                      Background="Transparent" Cursor="SizeNS"/>
+
+            <!-- TX/RX 鏁版嵁鍖哄煙 -->
+            <Border Grid.Row="2" Background="{StaticResource TsPanelBrush}" 
+                BorderBrush="{StaticResource BorderBrush}" 
+                BorderThickness="1" CornerRadius="4" Padding="10">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+
+                    <StackPanel Orientation="Horizontal" Grid.Row="0" Margin="0,0,0,10">
+                        <TextBlock Text="娴嬭瘯鏁版嵁" FontSize="14" FontWeight="SemiBold" 
+                               Foreground="{StaticResource PrimaryBrush}" VerticalAlignment="Center"/>
+                        <Button Content="娓呯┖" Style="{StaticResource ActionButtonStyle}" 
+                            Margin="15,0,0,0" Click="OnClearClick"/>
+                    </StackPanel>
+
+                    <DataGrid x:Name="DataGridComm" Grid.Row="1"
+                          ItemsSource="{Binding TestData,ElementName=self}">
+                        <DataGrid.Columns>
+                            <DataGridTextColumn Header="娴嬭瘯椤�" Binding="{Binding Name}" Width="120" IsReadOnly="True"/>
+                            <DataGridTextColumn Header="鏀�" Binding="{Binding Receive}" Width="*" IsReadOnly="True"/>
+                            <DataGridTextColumn Header="鍙�" Binding="{Binding Send}" Width="*" IsReadOnly="True"/>
+                            <DataGridTextColumn Header="鍗曚綅" Binding="{Binding Unit}" Width="60" IsReadOnly="True"/>
+                            <DataGridTextColumn Header="涓婇檺" Binding="{Binding UpperLimit}" Width="80" IsReadOnly="True"/>
+                            <DataGridTextColumn Header="涓嬮檺" Binding="{Binding LowerLimit}" Width="80" IsReadOnly="True"/>
+                            <DataGridTextColumn Header="缁撴灉" Binding="{Binding Result}" Width="70" IsReadOnly="True"/>
+                            <DataGridTextColumn Header="鏃堕棿" Binding="{Binding Time}" Width="90" IsReadOnly="True"/>
+                        </DataGrid.Columns>
+                        <DataGrid.RowStyle>
+                            <Style TargetType="DataGridRow">
+                                <Style.Triggers>
+                                    <DataTrigger Value="True">
+                                        <DataTrigger.Binding>
+                                            <MultiBinding Converter="{x:Static local:ResultFailedConverter.Instance}">
+                                                <Binding Path="Result"/>
+                                            </MultiBinding>
+                                        </DataTrigger.Binding>
+                                        <Setter Property="Background" Value="#FFE0E0"/>
+                                        <Setter Property="Foreground" Value="#C53030"/>
+                                    </DataTrigger>
+                                </Style.Triggers>
+                            </Style>
+                        </DataGrid.RowStyle>
+                    </DataGrid>
+                </Grid>
+            </Border>
+
+            <!-- 鍒嗛殧鏉�2 -->
+            <GridSplitter Grid.Row="3" Height="5" HorizontalAlignment="Stretch" 
+                      Background="Transparent" Cursor="SizeNS"/>
+
+            <!-- 鏃ュ織鍖哄煙 -->
+            <Border Grid.Row="4" Background="{StaticResource TsPanelBrush}" 
+                BorderBrush="{StaticResource BorderBrush}" 
+                BorderThickness="1" CornerRadius="4" Padding="10">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+
+                    <StackPanel Orientation="Horizontal" Grid.Row="0" Margin="0,0,0,8">
+                        <TextBlock Text="鏃ュ織" FontSize="14" FontWeight="SemiBold" 
+                               Foreground="{StaticResource PrimaryBrush}" VerticalAlignment="Center"/>
+                        <Button Content="娓呯┖鏃ュ織" Style="{StaticResource ActionButtonStyle}" 
+                            Margin="15,0,0,0" Click="OnClearLogClick"/>
+                    </StackPanel>
+
+                    <RichTextBox x:Name="LogRichTextBox" Grid.Row="1"
+                             IsReadOnly="True"
+                             VerticalScrollBarVisibility="Auto"
+                             FontFamily="Consolas"
+                             FontSize="11"
+                             BorderThickness="1"
+                             BorderBrush="Black" 
+                             Padding="4"/>
+                </Grid>
+            </Border>
+        </Grid>
+        <Border Background="#AAFFFFFF" x:Name="mask">
+            <TextBlock Text="鍒濆鍖栦腑..." 
+                       FontSize="16" FontWeight="Bold" Foreground="{StaticResource PrimaryBrush}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
+        </Border>
+    </Grid>
+</UserControl>
diff --git a/Util.ManualDebug/ManualDebug.xaml.cs b/Util.ManualDebug/ManualDebug.xaml.cs
new file mode 100644
index 0000000..7fa336e
--- /dev/null
+++ b/Util.ManualDebug/ManualDebug.xaml.cs
@@ -0,0 +1,467 @@
+using Plugin.TsSupport;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Media;
+
+namespace Util.SB
+{
+    public class BtnConfig
+    {
+        public string Title { get; set; }
+        public string Name { get; set; }
+    }
+
+    // 结果失败判断转换器(支持不区分大小写)
+    public class ResultFailedConverter : IMultiValueConverter
+    {
+        public static readonly ResultFailedConverter Instance = new ResultFailedConverter();
+        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (values.Length > 0 && values[0] is string)
+            {
+                string result = (string)values[0];
+                string upper = result.ToUpperInvariant();
+                return upper == "NG" || upper == "FAIL";
+            }
+            return false;
+        }
+
+        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+
+    // 日志级别枚举
+    public enum LogLevel
+    {
+        Debug = 0,
+        Info = 1,
+        Warn = 2,
+        Error = 3
+    }
+    
+    public partial class ManualDebug : UserControl
+    {
+        public static readonly DependencyProperty OvenNameProperty = DependencyProperty.Register(
+                            "OvenName",
+                            typeof(string),
+                            typeof(ManualDebug),
+                            new PropertyMetadata(""));
+
+        public static readonly DependencyProperty CellNameProperty = DependencyProperty.Register(
+                            "CellName",
+                            typeof(string),
+                            typeof(ManualDebug),
+                            new PropertyMetadata(""));
+
+        public static readonly DependencyProperty OvenIndexProperty = DependencyProperty.Register(
+                            "OvenIndex",
+                            typeof(int),
+                            typeof(ManualDebug),
+                            new PropertyMetadata(0));
+
+        public static readonly DependencyProperty CellIndexProperty = DependencyProperty.Register(
+                            "CellIndex",
+                            typeof(int),
+                            typeof(ManualDebug),
+                            new PropertyMetadata(0));
+
+        public string OvenName
+        {
+            get { return (string)GetValue(OvenNameProperty); }
+            set
+            {
+                SetValue(OvenNameProperty, value);
+            }
+        }
+
+        public string CellName
+        {
+            get { return (string)GetValue(CellNameProperty); }
+            set { SetValue(CellNameProperty, value); }
+        }
+
+        public int OvenIndex
+        {
+            get { return (int)GetValue(OvenIndexProperty); }
+            set { SetValue(OvenIndexProperty, value); }
+        }
+
+        public int CellIndex
+        {
+            get { return (int)GetValue(CellIndexProperty); }
+            set { SetValue(CellIndexProperty, value); }
+        }
+
+        public ObservableCollection<TestDataItem> TestData { get; private set; } = new ObservableCollection<TestDataItem>();
+
+        private Dictionary<string, StatusBtn> btnCache = new Dictionary<string, StatusBtn>();
+
+        // 日志级别颜色
+        private readonly SolidColorBrush _debugBrush;
+        private readonly SolidColorBrush _infoBrush;
+        private readonly SolidColorBrush _warnBrush;
+        private readonly SolidColorBrush _errorBrush;
+
+        private ITestExecutionWrapper executionWrapper;
+
+        public string seqPath { get; set; }
+        public string modelPath { get; set; } = "C:\\Program Files\\National Instruments\\TestStand 2020\\Components\\Models\\TestStandModels\\ParallelModel.seq";
+
+        public ManualDebug()
+        {
+            _debugBrush = new SolidColorBrush(Color.FromRgb(0x71, 0x80, 0x8A)); // 灰色
+            _infoBrush = new SolidColorBrush(Colors.Black);
+            _warnBrush = new SolidColorBrush(Color.FromRgb(0xD6, 0x9E, 0x2E));  // 橙黄色
+            _errorBrush = new SolidColorBrush(Color.FromRgb(0xC5, 0x30, 0x30));   // 红色
+            this.Loaded += ManualDebug_Loaded;
+            InitializeComponent();
+            RefreshButtons(null);
+        }
+
+        private void ManualDebug_Loaded(object sender, RoutedEventArgs e)
+        {
+            Task.Run(() =>
+            {
+                System.Threading.Thread.Sleep(500);
+                Dispatcher.BeginInvoke(() =>
+                {
+                    InitWrapper();
+                    StartSeq();
+                    mask.Visibility = Visibility.Collapsed;
+                });
+            });
+        }
+
+        public void RefreshButtons(List<BtnConfig> settings)
+        {
+            if (settings == null) return;
+
+            for (int i = 0; i < settings.Count; i++) {
+                var setting = settings[i];
+
+                var margin = new Thickness(0, 0, 20, 0);
+                if (i == settings.Count - 1)
+                {
+                    margin = new Thickness(0);
+                }
+
+                var btn = new StatusBtn
+                {
+                    Title = setting.Title,
+                    Tag = setting,
+                    Margin = margin
+                };
+                btn.BtnClick += Btn_BtnClick;
+                btnCache[setting.Name] = btn;
+                btnPanel.Children.Add(btn);
+            }
+        }
+
+        public void StartSeq()
+        {
+            try
+            {
+                executionWrapper?.StartSequence(seqPath, modelPath);
+            }
+            catch (Exception ex)
+            {
+                AppendLog(LogLevel.Error, $"启动测试序列失败 {ex.Message} {ex.StackTrace}");
+            }
+        }
+
+        private void InitWrapper()
+        {
+            try
+            {
+                var wrapper = new TeststandWrapper();
+                var AxApplicationMgr = wrapper.AxApplicationMgr;
+                ((System.ComponentModel.ISupportInitialize)(AxApplicationMgr)).BeginInit();
+                host.Child = AxApplicationMgr;
+                ((System.ComponentModel.ISupportInitialize)(AxApplicationMgr)).EndInit();
+                wrapper.Init();
+                executionWrapper = wrapper;
+
+                wrapper.UIMessageEvent += Wrapper_UIMessageEvent;
+            }
+            catch (Exception ex)
+            {
+                AppendLog(LogLevel.Error, $"初始化Teststand {ex.Message} {ex.StackTrace}");
+            }
+        }
+
+        private void Wrapper_UIMessageEvent(object sender, UIMessageEvent e)
+        {
+            try
+            {
+                if (e.EventCode == 10000)
+                {
+                    executionWrapper?.SetContext(e.Data);
+                    return;
+                }
+                if (e.EventCode == 10001)
+                {
+                    string[] arr = e.StrData.Split(',');
+                    Dispatcher.BeginInvoke(() =>
+                    {
+                        AddTestData(arr[0], arr[1], arr[2], arr[3], arr[4], arr[5], arr[6], arr[7]);
+                    });
+                    return;
+                }
+                if (e.EventCode == 10002)
+                {
+                    string[] arr = e.StrData.Split(',');
+                    LogLevel level = (LogLevel)int.Parse(arr[0]);
+                    Dispatcher.BeginInvoke(() =>
+                    {
+                        AppendLog(level, arr[1]);
+                    });
+                    return;
+                }
+                if (e.EventCode == 10003)
+                {
+                    string[] arr = e.StrData.Split(',');
+                    DebugStatus status = (DebugStatus)int.Parse(arr[1]);
+                    Dispatcher.BeginInvoke(() =>
+                    {
+                        SetStatus(arr[0], status);
+                    });
+                    return;
+                }
+            }
+            catch (Exception ex)
+            {
+                AppendLog(LogLevel.Error, $"处理UI信息失败 {ex.Message} {ex.StackTrace}");
+            }
+        }
+
+
+
+        private void Btn_BtnClick(object sender, RoutedEventArgs e)
+        {
+            var btn = (StatusBtn)sender;
+            BtnConfig config = (BtnConfig)btn.Tag;
+            StartSingleTest(config.Name);
+        }
+
+        private void StartSingleTest(string name)
+        {
+            try
+            {
+                string key = $"Running.OvenName";
+                executionWrapper?.SetFileGlobalValue(key, 1, OvenName);
+
+                key = $"Running.CellName";
+                executionWrapper?.SetFileGlobalValue(key, 1, CellName);
+
+                key = $"Running.OvenIndex";
+                executionWrapper?.SetFileGlobalValue(key, 1, OvenIndex);
+
+                key = $"Running.CellIndex";
+                executionWrapper?.SetFileGlobalValue(key, 1, CellIndex);
+
+                key = $"Running.ActionName";
+                executionWrapper?.SetFileGlobalValue(key, 1, name);
+            }
+            catch (Exception ex)
+            {
+                AppendLog(LogLevel.Error, $"启动功能失败 {ex.Message} {ex.StackTrace}");
+            }
+        }
+
+        // 设置状态显示
+        private void SetStatus(string labelName, DebugStatus status)
+        {
+            if (btnCache.ContainsKey(labelName))
+            {
+                var btn = btnCache[labelName];
+                btn.Status = status;
+            }
+        }
+
+        // 重置状态显示
+        private void ResetStatus(string labelName)
+        {
+            SetStatus(labelName, DebugStatus.Idle);
+        }
+
+        // 设置测试中状态并禁用按钮
+        private void SetTestingStatus(string labelName, Button button)
+        {
+            SetStatus(labelName, DebugStatus.Testing);
+            button.IsEnabled = false;
+        }
+
+        private void OnClearClick(object sender, RoutedEventArgs e)
+        {
+            TestData.Clear();
+        }
+
+        private void OnClearLogClick(object sender, RoutedEventArgs e)
+        {
+            LogRichTextBox.Document.Blocks.Clear();
+        }
+
+        private void AddTestData(string name, string send, string receive, string unit, string upper, string lower, string result, string time)
+        {
+            TestData.Add(new TestDataItem
+            {
+                Name = name,
+                Send = send,
+                Receive = receive,
+                Unit = unit,
+                UpperLimit = upper,
+                LowerLimit = lower,
+                Result = result,
+                Time = time
+            });
+
+            if (DataGridComm.Items.Count > 0)
+            {
+                DataGridComm.ScrollIntoView(DataGridComm.Items[DataGridComm.Items.Count - 1]);
+            }
+        }
+
+        // 获取日志级别对应的颜色
+        private Brush GetLogLevelBrush(LogLevel level)
+        {
+            switch (level)
+            {
+                case LogLevel.Debug:
+                    return _debugBrush;
+                case LogLevel.Info:
+                    return _infoBrush;
+                case LogLevel.Warn:
+                    return _warnBrush;
+                case LogLevel.Error:
+                    return _errorBrush;
+                default:
+                    return _infoBrush;
+            }
+        }
+
+        // 添加带级别的日志
+        private void AppendLog(LogLevel level, string message)
+        {
+            Dispatcher.BeginInvoke(() =>
+            {
+                string timestamp = DateTime.Now.ToString("HH:mm:ss.fff");
+                string levelStr = level.ToString().ToUpper();
+
+                Paragraph? paragraph = LogRichTextBox.Document.Blocks.LastBlock as Paragraph;
+                if (paragraph == null)
+                {
+                    paragraph = new Paragraph();
+                    LogRichTextBox.Document.Blocks.Add(paragraph);
+                }
+
+                // 时间戳(灰色)
+                Run timestampRun = new Run("[" + timestamp + "] ") { Foreground = _debugBrush };
+                paragraph.Inlines.Add(timestampRun);
+
+                // 级别标签
+                Brush levelBrush = GetLogLevelBrush(level);
+                Run levelRun = new Run("[" + levelStr + "] ") { Foreground = levelBrush, FontWeight = FontWeights.SemiBold };
+                paragraph.Inlines.Add(levelRun);
+
+                // 消息内容
+                Run messageRun = new Run(message + "\n") { Foreground = levelBrush };
+                paragraph.Inlines.Add(messageRun);
+
+                LogRichTextBox.ScrollToEnd();
+            });
+        }
+
+        // 兼容旧调用方式,默认 Info 级别
+        private void AppendLog(string message)
+        {
+            AppendLog(LogLevel.Info, message);
+        }
+
+        public void Stop()
+        {
+            try
+            {
+                executionWrapper?.SetFileGlobalValue("Running.StopTest", 1, true);
+                executionWrapper?.WaitForExit();
+            }
+            catch (Exception ex)
+            {
+
+            }
+        }
+    }
+
+    public class TestDataItem : INotifyPropertyChanged
+    {
+        private string _name = "";
+        private string _receive = "";
+        private string _send = "";
+        private string _unit = "";
+        private string _upperLimit = "";
+        private string _lowerLimit = "";
+        private string _result = "";
+        private string _time = "";
+
+        public string Name
+        {
+            get { return _name; }
+            set { _name = value; OnPropertyChanged("Name"); }
+        }
+
+        public string Receive
+        {
+            get { return _receive; }
+            set { _receive = value; OnPropertyChanged("Receive"); }
+        }
+
+        public string Send
+        {
+            get { return _send; }
+            set { _send = value; OnPropertyChanged("Send"); }
+        }
+
+        public string Unit
+        {
+            get { return _unit; }
+            set { _unit = value; OnPropertyChanged("Unit"); }
+        }
+
+        public string UpperLimit
+        {
+            get { return _upperLimit; }
+            set { _upperLimit = value; OnPropertyChanged("UpperLimit"); }
+        }
+
+        public string LowerLimit
+        {
+            get { return _lowerLimit; }
+            set { _lowerLimit = value; OnPropertyChanged("LowerLimit"); }
+        }
+
+        public string Result
+        {
+            get { return _result; }
+            set { _result = value; OnPropertyChanged("Result"); }
+        }
+
+        public string Time
+        {
+            get { return _time; }
+            set { _time = value; OnPropertyChanged("Time"); }
+        }
+
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        protected virtual void OnPropertyChanged(string propertyName)
+        {
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+        }
+    }
+}
diff --git a/Util.ManualDebug/ManualDebugStyle.xaml b/Util.ManualDebug/ManualDebugStyle.xaml
new file mode 100644
index 0000000..cf459fb
--- /dev/null
+++ b/Util.ManualDebug/ManualDebugStyle.xaml
@@ -0,0 +1,130 @@
+<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+
+    <!-- 棰滆壊瀹氫箟 -->
+    <SolidColorBrush x:Key="TsBgBrush" Color="#F0F2F5"/>
+    <SolidColorBrush x:Key="TsPanelBrush" Color="#FFFFFF"/>
+    <SolidColorBrush x:Key="BorderBrush" Color="#A0AEC0"/>
+    <SolidColorBrush x:Key="PrimaryBrush" Color="#2E5C8A"/>
+
+    <!-- 鐘舵�侀鑹茬敾鍒� -->
+    <SolidColorBrush x:Key="StatusOKBrush" Color="#2D8659"/>
+    <SolidColorBrush x:Key="StatusNGBrush" Color="#C53030"/>
+    <SolidColorBrush x:Key="StatusTestingBrush" Color="#3182CE"/>
+    <SolidColorBrush x:Key="StatusDefaultBrush" Color="#E0E0E0"/>
+    <SolidColorBrush x:Key="BorderDarkBrush" Color="#718096"/>
+
+    <!-- ActionButtonStyle 鎸夐挳鏍峰紡 -->
+    <Style x:Key="ActionButtonStyle" TargetType="Button">
+        <Setter Property="Height" Value="22"/>
+        <Setter Property="Padding" Value="8,0"/>
+        <Setter Property="Background" Value="White"/>
+        <Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
+        <Setter Property="BorderThickness" Value="1"/>
+        <Setter Property="FontSize" Value="11"/>
+        <Setter Property="Cursor" Value="Hand"/>
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate TargetType="Button">
+                    <Border Background="{TemplateBinding Background}"
+                            BorderBrush="{TemplateBinding BorderBrush}"
+                            BorderThickness="{TemplateBinding BorderThickness}"
+                            CornerRadius="2"
+                            Padding="{TemplateBinding Padding}">
+                        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
+                    </Border>
+                    <ControlTemplate.Triggers>
+                        <Trigger Property="IsMouseOver" Value="True">
+                            <Setter Property="Background" Value="#F0F0F0"/>
+                        </Trigger>
+                    </ControlTemplate.Triggers>
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
+
+    <!-- ManualDebug 绐楀彛涓撶敤鎸夐挳鏍峰紡 -->
+    <Style x:Key="DebugButtonStyle" TargetType="Button">
+        <Setter Property="Width" Value="100"/>
+        <Setter Property="Height" Value="32"/>
+        <Setter Property="Background" Value="White"/>
+        <Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
+        <Setter Property="BorderThickness" Value="1"/>
+        <Setter Property="FontSize" Value="11"/>
+        <Setter Property="Cursor" Value="Hand"/>
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate TargetType="Button">
+                    <Border Background="{TemplateBinding Background}"
+                            BorderBrush="{TemplateBinding BorderBrush}"
+                            BorderThickness="{TemplateBinding BorderThickness}"
+                            CornerRadius="4"
+                            Padding="{TemplateBinding Padding}">
+                        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
+                    </Border>
+                    <ControlTemplate.Triggers>
+                        <Trigger Property="IsMouseOver" Value="True">
+                            <Setter Property="Background" Value="#E8E8E8"/>
+                        </Trigger>
+                        <Trigger Property="IsPressed" Value="True">
+                            <Setter Property="Background" Value="#D0D0D0"/>
+                        </Trigger>
+                        <Trigger Property="IsEnabled" Value="False">
+                            <Setter Property="Background" Value="LightGray"/>
+                        </Trigger>
+                    </ControlTemplate.Triggers>
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
+
+    <!-- DataGrid鏍峰紡 -->
+    <Style TargetType="DataGrid">
+        <Setter Property="AutoGenerateColumns" Value="False"/>
+        <Setter Property="CanUserAddRows" Value="False"/>
+        <Setter Property="CanUserDeleteRows" Value="False"/>
+        <Setter Property="HeadersVisibility" Value="Column"/>
+        <Setter Property="GridLinesVisibility" Value="All"/>
+        <Setter Property="HorizontalGridLinesBrush" Value="{StaticResource BorderBrush}"/>
+        <Setter Property="VerticalGridLinesBrush" Value="{StaticResource BorderBrush}"/>
+        <Setter Property="BorderBrush" Value="{StaticResource BorderDarkBrush}"/>
+        <Setter Property="BorderThickness" Value="1"/>
+        <Setter Property="Background" Value="White"/>
+        <Setter Property="RowBackground" Value="White"/>
+        <Setter Property="AlternatingRowBackground" Value="#F5F5F5"/>
+        <Setter Property="FontSize" Value="11"/>
+    </Style>
+
+    <Style TargetType="DataGridColumnHeader">
+        <Setter Property="Background" Value="#F5F5F5"/>
+        <Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
+        <Setter Property="BorderThickness" Value="0,0,1,1"/>
+        <Setter Property="Padding" Value="4"/>
+        <Setter Property="Height" Value="26"/>
+        <Setter Property="FontWeight" Value="Normal"/>
+    </Style>
+
+    <Style TargetType="DataGridCell">
+        <Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
+        <Setter Property="BorderThickness" Value="0"/>
+        <Setter Property="Padding" Value="2,4"/>
+        <Style.Triggers>
+            <Trigger Property="IsSelected" Value="True">
+                <Setter Property="Background" Value="#BBDEFB"/>
+                <Setter Property="Foreground" Value="Black"/>
+            </Trigger>
+        </Style.Triggers>
+    </Style>
+
+    <Style TargetType="DataGridRow">
+        <Style.Triggers>
+            <Trigger Property="IsSelected" Value="True">
+                <Setter Property="Background" Value="#BBDEFB"/>
+            </Trigger>
+            <Trigger Property="IsMouseOver" Value="True">
+                <Setter Property="Background" Value="#E3F2FD"/>
+            </Trigger>
+        </Style.Triggers>
+    </Style>
+
+</ResourceDictionary>
diff --git a/Util.ManualDebug/StatusBtn.xaml b/Util.ManualDebug/StatusBtn.xaml
new file mode 100644
index 0000000..7b96029
--- /dev/null
+++ b/Util.ManualDebug/StatusBtn.xaml
@@ -0,0 +1,75 @@
+锘�<UserControl x:Class="Util.SB.StatusBtn"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:Util.SB"
+             mc:Ignorable="d" 
+             d:DesignHeight="450" d:DesignWidth="800">
+    <UserControl.Resources>
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <ResourceDictionary Source="/Util.ManualDebug;component/ManualDebugStyle.xaml"/>
+            </ResourceDictionary.MergedDictionaries>
+
+            <Style x:Key="StatusTextStyle" TargetType="TextBlock">
+                <Setter Property="HorizontalAlignment" Value="Center"/>
+                <Setter Property="VerticalAlignment" Value="Center"/>
+                <Setter Property="FontWeight" Value="SemiBold"/>
+                <Setter Property="FontSize" Value="12"/>
+                <Setter Property="Text" Value="-"/>
+                <Style.Triggers>
+                    <DataTrigger Binding="{Binding Status}" Value="{x:Static local:DebugStatus.NG}">
+                        <Setter Property="Text" Value="NG"/>
+                        <Setter Property="Foreground" Value="#FFF"/>
+                    </DataTrigger>
+                    <DataTrigger Binding="{Binding Status}" Value="{x:Static local:DebugStatus.OK}">
+                        <Setter Property="Text" Value="OK"/>
+                        <Setter Property="Foreground" Value="#FFF"/>
+                    </DataTrigger>
+                    <DataTrigger Binding="{Binding Status}" Value="{x:Static local:DebugStatus.Testing}">
+                        <Setter Property="Text" Value="TESTING"/>
+                        <Setter Property="Foreground" Value="#FFF"/>
+                    </DataTrigger>
+                </Style.Triggers>
+            </Style>
+
+            <!-- 鐘舵�佽竟妗嗘牱寮� -->
+            <Style x:Key="StatusBorderStyle" TargetType="Border">
+                <Setter Property="Background" Value="{StaticResource StatusDefaultBrush}"/>
+                <Setter Property="CornerRadius" Value="2"/>
+                <Setter Property="HorizontalAlignment" Value="Center"/>
+                <Setter Property="Width" Value="100"/>
+                <Setter Property="Height" Value="24"/>
+                <Setter Property="Margin" Value="0,5,0,0"/>
+
+                <Style.Triggers>
+                    <DataTrigger Binding="{Binding Status}" Value="{x:Static local:DebugStatus.NG}">
+                        <Setter Property="Background" Value="{StaticResource StatusNGBrush}"/>
+                    </DataTrigger>
+                    <DataTrigger Binding="{Binding Status}" Value="{x:Static local:DebugStatus.OK}">
+                        <Setter Property="Background" Value="{StaticResource StatusOKBrush}"/>
+                    </DataTrigger>
+                    <DataTrigger Binding="{Binding Status}" Value="{x:Static local:DebugStatus.Testing}">
+                        <Setter Property="Background" Value="{StaticResource StatusTestingBrush}"/>
+                    </DataTrigger>
+                </Style.Triggers>
+            </Style>
+
+            <Style x:Key="btnStyle" TargetType="Button" BasedOn="{StaticResource DebugButtonStyle}">
+                <Style.Triggers>
+                    <DataTrigger Binding="{Binding Status}" Value="{x:Static local:DebugStatus.Testing}">
+                        <Setter Property="IsEnabled" Value="False"/>
+                    </DataTrigger>
+                </Style.Triggers>
+            </Style>
+        </ResourceDictionary>
+
+    </UserControl.Resources>
+    <StackPanel>
+        <Button x:Name="btn" Content="{Binding Title}" Style="{StaticResource btnStyle}" Click="Button_Click"/>
+        <Border Style="{StaticResource StatusBorderStyle}">
+            <TextBlock Style="{StaticResource StatusTextStyle}"/>
+        </Border>
+    </StackPanel>
+</UserControl>
diff --git a/Util.ManualDebug/StatusBtn.xaml.cs b/Util.ManualDebug/StatusBtn.xaml.cs
new file mode 100644
index 0000000..11c2783
--- /dev/null
+++ b/Util.ManualDebug/StatusBtn.xaml.cs
@@ -0,0 +1,40 @@
+锘縰sing CommunityToolkit.Mvvm.ComponentModel;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Util.SB
+{
+    // 鐘舵�佹灇涓�
+    public enum DebugStatus
+    {
+        Idle = 0,    // 绌洪棽
+        Testing = 1, // 娴嬭瘯涓�
+        OK = 2,      // 閫氳繃
+        NG = 3       // 澶辫触
+    }
+    /// <summary>
+    /// StatusBtn.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    [ObservableObject]
+    public partial class StatusBtn : UserControl
+    {
+        public event RoutedEventHandler BtnClick;
+
+        public StatusBtn()
+        {
+            DataContext = this;
+            InitializeComponent();
+        }
+
+        [ObservableProperty]
+        private string title = string.Empty;
+
+        [ObservableProperty]
+        private DebugStatus status = DebugStatus.Idle;
+
+        private void Button_Click(object sender, RoutedEventArgs e)
+        {
+            BtnClick?.Invoke(this, e);
+        }
+    }
+}
diff --git a/Util.ManualDebug/Util.ManualDebug.csproj b/Util.ManualDebug/Util.ManualDebug.csproj
new file mode 100644
index 0000000..fcf9c6e
--- /dev/null
+++ b/Util.ManualDebug/Util.ManualDebug.csproj
@@ -0,0 +1,33 @@
+锘�<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+	  <TargetFramework>net472</TargetFramework>
+	  <Nullable>enable</Nullable>
+	  <LangVersion>10.0</LangVersion>
+	  <ImplicitUsings>enable</ImplicitUsings>
+	  <UseWPF>true</UseWPF>
+	  <Platforms>AnyCPU;x64;x86</Platforms>
+	  <FileVersion>1.0.0.0</FileVersion>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
+    <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Plugin.TsSupport\Plugin.TsSupport.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Reference Include="NationalInstruments.TestStand.Interop.API">
+      <HintPath>..\Libs\NationalInstruments.TestStand.Interop.API.dll</HintPath>
+    </Reference>
+    <Reference Include="NationalInstruments.TestStand.Interop.UI.AxControls">
+      <HintPath>..\Libs\NationalInstruments.TestStand.Interop.UI.AxControls.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="WindowsFormsIntegration" />
+  </ItemGroup>
+
+</Project>
diff --git a/WPFProject/App.config b/WPFProject/App.config
index d9a1988..fabccd6 100644
--- a/WPFProject/App.config
+++ b/WPFProject/App.config
@@ -9,7 +9,7 @@
 	</startup>
 	<runtime>
 		<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
-			<probing privatePath="Libs;ThirdParty;Plugin;3rds;Api;" />
+			<probing privatePath="Libs;ThirdParty;Plugin;3rds;Api;OpenTap;" />
 			<dependentAssembly>
 				<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
 				<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
diff --git a/WPFProject/App.xaml.cs b/WPFProject/App.xaml.cs
index c63a2e2..f20a0c6 100644
--- a/WPFProject/App.xaml.cs
+++ b/WPFProject/App.xaml.cs
@@ -93,7 +93,7 @@
         {
             var process = GetProcess();
 
-            mutex = new Mutex(true, "TestPlatform_OnlyRun_CRNS");
+            mutex = new Mutex(true, "TestPlatform_OnlyRun_CRNS__");
             if (mutex.WaitOne(0, false))//&& process == null
             {
                 WriteRegister();
diff --git a/WPFProject/Controls/MainTabControl.xaml b/WPFProject/Controls/MainTabControl.xaml
index 3e49600..d48c9c8 100644
--- a/WPFProject/Controls/MainTabControl.xaml
+++ b/WPFProject/Controls/MainTabControl.xaml
@@ -159,7 +159,7 @@
             <DockPanel Background="Transparent">
                 <Border DockPanel.Dock="Left" BorderThickness="1,1,0,1" Background="Transparent" BorderBrush="{StaticResource PrimaryColor}" Width="50"></Border>
                 <ListBox DockPanel.Dock="Left" Grid.Row="0" x:Name="Menu"
- Style="{StaticResource ListBoxItemStyle1}" FontSize="16"
+ Style="{StaticResource ListBoxItemStyle1}" FontSize="14"
  BorderBrush="{StaticResource PrimaryColor}"
  BorderThickness="1"
  SelectionChanged="Menu_SelectionChanged"
diff --git a/WPFProject/Controls/MainTabControl.xaml.cs b/WPFProject/Controls/MainTabControl.xaml.cs
index a476ebd..72ae3eb 100644
--- a/WPFProject/Controls/MainTabControl.xaml.cs
+++ b/WPFProject/Controls/MainTabControl.xaml.cs
@@ -1,6 +1,7 @@
 锘縰sing HMI.Base;
 using HMI.Core.Api;
 using Panuon.WPF.UI;
+using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.ComponentModel;
@@ -11,6 +12,12 @@
 
 namespace WPFProject.Controls
 {
+    public class TabChangedEventArg
+    {
+        public IPlugin oldValue { get; set; }
+        public IPlugin newValue { get; set; }
+    }
+
     /// <summary>
     /// MainTabControl.xaml 鐨勪氦浜掗�昏緫
     /// </summary>
@@ -46,21 +53,7 @@
         }
 
         // Tab鍒囨崲浜嬩欢
-        public static readonly RoutedEvent OnTabChangeEvent =
-             EventManager.RegisterRoutedEvent("OnTabChange", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MenuTabControl));
-
-        public event RoutedEventHandler OnTabChange
-        {
-            add
-            {
-                AddHandler(OnTabChangeEvent, value);
-            }
-
-            remove
-            {
-                RemoveHandler(OnTabChangeEvent, value);
-            }
-        }
+        public event EventHandler<TabChangedEventArg> TabChanged;
 
         private ObservableCollection<PluginInfo> _MenuItems;
 
@@ -193,6 +186,7 @@
                 {
                     page.Child = (UIElement)content;
                 }
+                TabChanged?.Invoke(this, new TabChangedEventArg { oldValue = last, newValue = newPlugin });
             }
         }
 
diff --git a/WPFProject/Lang/en-US/TestResult.json b/WPFProject/Lang/en-US/TestResult.json
index cc222d3..ae6c6b8 100644
--- a/WPFProject/Lang/en-US/TestResult.json
+++ b/WPFProject/Lang/en-US/TestResult.json
@@ -50,5 +50,7 @@
   "Tr.CantClearSNR": "Program is running, can't clear snr!",
   "Tr.MakeSureStopOvenTest": "Are you sure you want stop [{0}] test?",
   "Tr.PositionCantClearSNR": "Position [{0}] can't clear snr now!",
+  "Tr.MakeSureStopAllTest": "Are you sure you want to stop test?",
+  "Tr.OvenIsTesting": "Oven is testing, can't stop test.'",
   "Tr.TestProgram": "Test Program"
 }
\ No newline at end of file
diff --git a/WPFProject/Lang/en-US/app.json b/WPFProject/Lang/en-US/app.json
index 88da64d..ebacd15 100644
--- a/WPFProject/Lang/en-US/app.json
+++ b/WPFProject/Lang/en-US/app.json
@@ -104,7 +104,7 @@
   "App.MenuLoadFailed": "{0} load failed: {1} {2}",
   "App.MenuCount": "There are {0} menus is loading...",
   "App.SelectIcon": "Select icon",
-  "App.Welcome": "Welcom! {0}",
+  "App.Welcome": "Welcome! {0}",
   "App.MakeSureLogout": "Are you sure you want to logout?",
   "App.MakeSureRestartApp": "Are you sure you want to restart锛�",
   "App.NoExportData": "No Data To Export",
@@ -159,5 +159,11 @@
   "App.ScannerSetting": "Scanner Setting",
   "App.SequenceIsStarting": "Starting Test,Please Wait...",
   "App.Status": "Status",
-  "App.LoadPluginError": "Load plugin error,please check."
+  "App.LoadPluginError": "Load plugin error,please check.",
+  "App.Version": "Ver.",
+  "App.PluginVersion": "Plugin Ver.",
+  "App.CPUUsage": "CPU Usage",
+  "App.RemUsage": "Rem Usage",
+  "App.TotalCPUUsage": "PC CPU Usage",
+  "App.TotalRemUsage": "PC Rem Usage"
 }
\ No newline at end of file
diff --git a/WPFProject/Lang/zh-CN/TestResult.json b/WPFProject/Lang/zh-CN/TestResult.json
index 0d6ea0e..411cb80 100644
--- a/WPFProject/Lang/zh-CN/TestResult.json
+++ b/WPFProject/Lang/zh-CN/TestResult.json
@@ -50,5 +50,7 @@
   "Tr.CantClearSNR": "绋嬪簭杩愯涓紝涓嶈兘娓呯┖鏉$爜",
   "Tr.MakeSureStopOvenTest": "纭疄鍋滄鑰佸寲鐐塠{0}]鐨勬祴璇曞悧锛�",
   "Tr.PositionCantClearSNR": "绌翠綅[{0}]褰撳墠涓嶅厑璁告竻闄ゆ潯鐮侊紒",
+  "Tr.MakeSureStopAllTest": "纭畾瑕佸仠姝㈡墍鏈夋祴璇曞悧锛�",
+  "Tr.OvenIsTesting": "鏈変粨浣嶆鍦ㄦ祴璇曚腑锛屼笉鑳藉仠姝㈡祴璇�",
   "Tr.TestProgram": "娴嬭瘯绋嬪簭"
 }
\ No newline at end of file
diff --git a/WPFProject/Lang/zh-CN/app.json b/WPFProject/Lang/zh-CN/app.json
index 6684cdf..6194e08 100644
--- a/WPFProject/Lang/zh-CN/app.json
+++ b/WPFProject/Lang/zh-CN/app.json
@@ -158,5 +158,11 @@
   "App.ScannerSetting": "鎵爜鏋缃�",
   "App.SequenceIsStarting": "姝e湪鍚姩娴嬭瘯搴忓垪锛岃绋嶅悗...",
   "App.Status": "鐘舵��",
-  "App.LoadPluginError": "鍔犺浇鍑洪敊锛岃纭"
+  "App.LoadPluginError": "鍔犺浇鍑洪敊锛岃纭",
+  "App.Version": "杞欢鐗堟湰:",
+  "App.PluginVersion": "鎻掍欢鐗堟湰:",
+  "App.CPUUsage": "CPU浣跨敤",
+  "App.RemUsage": "鍐呭瓨浣跨敤",
+  "App.TotalCPUUsage": "鎬籆PU浣跨敤",
+  "App.TotalRemUsage": "鎬诲唴瀛樹娇鐢�"
 }
\ No newline at end of file
diff --git a/WPFProject/PanuonWindow.xaml b/WPFProject/PanuonWindow.xaml
index 943d9d0..b783a52 100644
--- a/WPFProject/PanuonWindow.xaml
+++ b/WPFProject/PanuonWindow.xaml
@@ -22,7 +22,7 @@
             pu:WindowXCaption.HeaderAlignment="Stretch"
             Title="TestPlatform"
             pu:WindowXCaption.Background="Transparent"
-            pu:WindowXCaption.Height="80"
+            pu:WindowXCaption.Height="60"
             IsOverlayerVisible="True"
             pu:WindowXCaption.Foreground="#FFF">
     <Window.Resources>
@@ -40,7 +40,6 @@
             <Setter Property="pu:MenuHelper.TopLevelItemsHoverBackground" Value="{x:Null}"/>
             <Setter Property="pu:MenuHelper.TopLevelDropDownPlacement" Value="BottomLeft"/>
         </Style>
-
     </Window.Resources>
     <Window.Background>
         <ImageBrush Stretch="Uniform" AlignmentX="Left" AlignmentY="Top"  
@@ -49,9 +48,10 @@
 
     <pu:WindowXCaption.HeaderTemplate>
         <DataTemplate>
-            <DockPanel DataContext="{Binding ElementName=window}" WindowChrome.IsHitTestVisibleInChrome="True" VerticalAlignment="Center" Height="100">
+            <DockPanel DataContext="{Binding ElementName=window}" WindowChrome.IsHitTestVisibleInChrome="True" 
+                       VerticalAlignment="Center" Height="80">
                 <StackPanel Orientation="Horizontal">
-                    <Image DockPanel.Dock="Left" Source="{Binding WindowLogo}" Margin="5,0,20,0" Height="60" VerticalAlignment="Center" Stretch="Fill"
+                    <Image DockPanel.Dock="Left" Source="{Binding WindowLogo}" Margin="5,0,20,0" Height="40" VerticalAlignment="Center" Stretch="Fill"
   RenderOptions.BitmapScalingMode="HighQuality" />
                     <TextBlock FontSize="30" VerticalAlignment="Center" Text="{Binding WindowTitle}">
                         <TextBlock.Effect>
@@ -71,9 +71,7 @@
                     </Grid.RowDefinitions>
                     <StackPanel Grid.Row="0" VerticalAlignment="Top" Margin="10,10,0,0" Orientation="Horizontal">
                         <Menu Margin="0" Padding="0" VerticalAlignment="Top"
-  VerticalContentAlignment="Center" Style="{StaticResource UserMenu}"
-  
-  Background="Transparent">
+  VerticalContentAlignment="Center" Style="{StaticResource UserMenu}" Background="Transparent">
                             <MenuItem x:Name="userMenu" Header="{Binding WelcomeTitle}" FontSize="16">
                                 <MenuItem Icon="&#xe61d;" Cursor="Hand" FontSize="14" Header="閫�鍑虹櫥褰�" 
                                       x:Name="MiLogout" Click="MenuItem_Click"/>
@@ -131,11 +129,14 @@
                             </Button.Style>
                         </Button>
                     </StackPanel>
-                    <Button Grid.Row="1" Margin="20,0,0,20" Foreground="#FFF" 
-                            Visibility="{Binding Alarms.Count,Converter={StaticResource cv}}"
-                            HorizontalContentAlignment="Left" Style="{StaticResource LinkButton}"
-                            Click="Clear_AllAlarm_Click"
-                            Content="娓呴櫎鍏ㄩ儴鎶ヨ"/>
+                    <DockPanel Margin="20,0,0,20" Grid.Row="1" LastChildFill="False">
+                        <Button DockPanel.Dock="Left" Foreground="#FFF"  
+    Visibility="{Binding Alarms.Count,Converter={StaticResource cv}}"
+    HorizontalContentAlignment="Left" Style="{StaticResource LinkButton}"
+    Click="Clear_AllAlarm_Click"
+    Content="娓呴櫎鍏ㄩ儴鎶ヨ"/>
+                        <!--<local:ResourceUI x:Name="resourceUI" Loaded="resourceUI_Loaded" DockPanel.Dock="Right"/>--> 
+                    </DockPanel>
                 </Grid>
 
                 <ListBox x:Name="list"
@@ -234,7 +235,7 @@
                             <Setter Property="Padding" Value="0"/>
                             <Setter Property="VerticalContentAlignment" Value="Center"/>
                             <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
-                            <Setter Property="Height" Value="70"/>
+                            <Setter Property="Height" Value="50"/>
                             <Setter Property="Margin" Value="0,0,0,1"/>
                             <Setter Property="BorderThickness" Value="0"/>
                         </Style>
@@ -254,13 +255,27 @@
             <RowDefinition Height="Auto"/>
         </Grid.RowDefinitions>
 
-        <ctl:MainTabControl Grid.Row="0" x:Name="menuTab">
+        <ctl:MainTabControl Grid.Row="0" x:Name="menuTab" TabChanged="menuTab_TabChanged">
 
         </ctl:MainTabControl>
 
         <!--<ctl:MenuTabControl Grid.Row="1" x:Name="menuTab" OnTabChange="menuTab_OnTabChange"> </ctl:MenuTabControl>-->
 
 
-        <!--<StatusBar Grid.Row="1" Background="{StaticResource HoriBar}"></StatusBar>-->
+        <DockPanel Grid.Row="1" Height="20" Background="{StaticResource PrimaryColor}" LastChildFill="False">
+
+            <TextBlock Margin="20,0,0,0" FontSize="14" VerticalAlignment="Center" Foreground="#FFF">
+<Run Text="{DynamicResource App.Version}"/> 
+<Run Text="{Binding SoftwareVersion}"/>
+            </TextBlock>
+
+            <TextBlock Margin="20,0,0,0" FontSize="14" VerticalAlignment="Center" Foreground="#FFF">
+<Run Text="{DynamicResource App.PluginVersion}"/> 
+<Run Text="{Binding PluginVersion}"/>
+            </TextBlock>
+
+            <local:ResourceUI FontSize="14" Margin="0,1,20,1" DockPanel.Dock="Right" x:Name="resourceUI"/>
+
+        </DockPanel>
     </Grid>
 </pu:WindowX>
diff --git a/WPFProject/PanuonWindow.xaml.cs b/WPFProject/PanuonWindow.xaml.cs
index 3ed72b3..06799af 100644
--- a/WPFProject/PanuonWindow.xaml.cs
+++ b/WPFProject/PanuonWindow.xaml.cs
@@ -5,12 +5,14 @@
 using HMI.Core.Util;
 using HMI.UI.Util;
 using HMI.Util.Alarm;
+using log4net;
 using Panuon.WPF.UI;
 using System;
 using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.IO;
 using System.Linq;
+using System.Threading.Tasks;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Media.Imaging;
@@ -33,9 +35,11 @@
     /// </summary>
     public partial class PanuonWindow : WindowX, IPluginContainer, IAlarmShow
     {
+        private ILog log = LogManager.GetLogger("MainWindow");
         public PanuonWindow()
         {
             this.Loaded += PanuonWindow_Loaded;
+            SoftwareVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
         }
 
         private UserInfo currentUser;
@@ -73,6 +77,26 @@
             get => Alarms?.Count ?? 0;
         }
 
+        private string softwareVersion;
+        public string SoftwareVersion
+        {
+            get => softwareVersion;
+            set
+            {
+                Set(ref softwareVersion, value);
+            }
+        }
+
+        private string pluginVersion;
+        public string PluginVersion
+        {
+            get => pluginVersion;
+            set
+            {
+                Set(ref pluginVersion, value);
+            }
+        }
+
         public ListBox sb
         {
             get; set;
@@ -80,17 +104,28 @@
 
         private void PanuonWindow_Loaded(object sender, RoutedEventArgs e)
         {
-            DataContext = this;
-            RefrshConfig();
-            InitializeComponent();
-            AlarmShowHelper.Register(this);
-            var sb = WindowXCaption.GetHeaderTemplate(this);
-            //sb.FindName("list",this.Con);
+            try
+            {
+                DataContext = this;
+                RefrshConfig();
+                InitializeComponent();
+                AlarmShowHelper.Register(this);
 
-            WelcomeTitle = this.lang("App.Welcome", UserInfo.User.Name);
-            ShowMenu = false;
-            Application.Current.MainWindow = this;
-            SystemSetting.SystemSettingChanged += SystemSetting_SystemSettingChanged;
+                WelcomeTitle = this.lang("App.Welcome", UserInfo.User.Name);
+                ShowMenu = false;
+                Application.Current.MainWindow = this;
+                SystemSetting.SystemSettingChanged += SystemSetting_SystemSettingChanged;
+
+                Task.Run(() =>
+                {
+                    ResourceListener.Instance.RefreshEvent += this.resourceUI.Refresh;
+                    ResourceListener.Instance.Start();
+                });
+            }
+            catch (Exception ex)
+            {
+                log.Error("Window Loaded Error.", ex);
+            }
         }
 
         private void RefrshConfig()
@@ -223,6 +258,7 @@
         {
             try
             {
+                ResourceListener.Instance.Stop();
                 AlarmUtil.Release();
                 PluginLoader.Reset();
                 UserInfo.Logout();
@@ -232,7 +268,7 @@
             {
 
             }
-            Application.Current.Shutdown();
+            Application.Current.Shutdown(0);
         }
 
         private void Button_Click(object sender, RoutedEventArgs e)
@@ -296,6 +332,21 @@
             Alarms?.Clear();
         }
 
+        private void menuTab_TabChanged(object sender, Controls.TabChangedEventArg e)
+        {
+            Dispatcher.BeginInvoke(() =>
+            {
+                if (e.newValue == null)
+                {
+                    PluginVersion = string.Empty;
+                }
+                else
+                {
+                    PluginVersion = e.newValue.GetType().Assembly.GetName().Version.ToString();
+                }
+            }, System.Windows.Threading.DispatcherPriority.Normal);
+        }
+
         //private void BaseWindow_Loaded(object sender, RoutedEventArgs e)
         //{
         //    var s = (Style)this.Resources["WindowButton"];
diff --git a/WPFProject/Properties/AssemblyInfo.cs b/WPFProject/Properties/AssemblyInfo.cs
index a10859f..c79e593 100644
--- a/WPFProject/Properties/AssemblyInfo.cs
+++ b/WPFProject/Properties/AssemblyInfo.cs
@@ -53,6 +53,6 @@
 //鍙互鎸囧畾鎵�鏈夎繖浜涘�硷紝涔熷彲浠ヤ娇鐢ㄢ�滅敓鎴愬彿鈥濆拰鈥滀慨璁㈠彿鈥濈殑榛樿鍊�
 //閫氳繃浣跨敤 "*"锛屽涓嬫墍绀�:
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyVersion("1.0.0.1")]
 [assembly: AssemblyFileVersion("1.0.0.0")]
 [assembly: XmlConfigurator(ConfigFile = "Config/log4net.config", Watch = true)]
\ No newline at end of file
diff --git a/WPFProject/ResourceListener.cs b/WPFProject/ResourceListener.cs
new file mode 100644
index 0000000..a03f00e
--- /dev/null
+++ b/WPFProject/ResourceListener.cs
@@ -0,0 +1,135 @@
+锘縰sing HMI.Simple.Util;
+using log4net;
+using System;
+using System.Diagnostics;
+using System.Management;
+using System.Timers;
+using System.Xml.Linq;
+
+namespace WPFProject
+{
+    public class RunTimeState
+    {
+        public float cpuRate { get; set; }
+        public double remRate { get; set; }
+
+        public float totalCpuRate { get; set; }
+        public double totalRemRate { get; set; }
+    }
+
+    public class ResourceListener
+    {
+        public event EventHandler<RunTimeState> RefreshEvent;
+        private Timer timer;
+        private System.Threading.CancellationTokenSource _can;
+
+        private static ResourceListener instance;
+        public static ResourceListener Instance
+        {
+            get
+            {
+
+                if (instance == null)
+                {
+                    instance = new ResourceListener();
+                }
+                return instance;
+            }
+        }
+        private ILog log = LogManager.GetLogger("ResourceListener");
+
+        // CPU鍗犵敤
+        private PerformanceCounter totalCpuCounter;
+
+        private PerformanceCounter cpuCounter;
+
+        private ResourceListener()
+        {
+            totalCpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
+            cpuCounter = new PerformanceCounter("Process", "% Processor Time", Process.GetCurrentProcess().ProcessName, true);
+        }
+
+        public void Start()
+        {
+            _can = new System.Threading.CancellationTokenSource();
+            Refresh();
+            timer = new Timer();
+            timer.AutoReset = false;
+            timer.Interval = 30000;
+            timer.Elapsed += Timer_Elapsed;
+            timer.Enabled = true;
+        }
+
+        private void Refresh()
+        {
+            try
+            {
+                var _cpuCount = Environment.ProcessorCount;
+                ObjectQuery wql = new ObjectQuery("SELECT * FROM Win32_OperatingSystem");
+                ManagementObjectSearcher searcher = new ManagementObjectSearcher(wql);
+
+                string prcName = Process.GetCurrentProcess().ProcessName;
+                var counter_Exec = new PerformanceCounter("Process", "Working Set - Private", prcName);
+                ulong dWsp_Exec = (ulong)(counter_Exec.RawValue / 1024);
+
+                var totalCpuRate = totalCpuCounter.NextValue();
+                var cpuRate = cpuCounter.NextValue() / _cpuCount;
+
+                foreach (ManagementObject obj in searcher.Get())
+                {
+                    ulong totalVisibleMemorySize = (ulong)obj["TotalVisibleMemorySize"];
+                    ulong freePhysicalMemory = (ulong)obj["FreePhysicalMemory"];
+
+                    ulong usedMemory = totalVisibleMemorySize - freePhysicalMemory;
+                    string info = $"\r\n鎬诲唴瀛�: {totalVisibleMemorySize / 1024} MB\r\n";
+
+                    info += $"宸蹭娇鐢ㄥ唴瀛�: {usedMemory / 1024} MB\r\n";
+                    double usagePercent = usedMemory * 100d / totalVisibleMemorySize;
+                    info += $"鍐呭瓨鍗犵敤鐜�: {usagePercent:F2}%\r\n";
+
+                    info += $"搴旂敤鍗犵敤鍐呭瓨: {dWsp_Exec / 1024} MB\r\n";
+                    double appPercent = dWsp_Exec * 100d / totalVisibleMemorySize;
+                    info += $"搴旂敤鍐呭瓨鍗犵敤鐜�: {appPercent:F2}%";
+                    RefreshEvent?.Invoke(this, new RunTimeState
+                    {
+                        cpuRate = cpuRate,
+                        remRate = appPercent,
+                        totalCpuRate = totalCpuRate,
+                        totalRemRate = usagePercent
+                    });
+                    log.Info(info);
+                    if (usagePercent > 80)
+                    {
+                        // AlarmUtil.CreateAlarm("App", "鍐呭瓨鍗犵敤杩囬珮锛岃秴80%", out _);
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                log.Error("Refresh Resource Error.", ex);
+            }
+        }
+
+        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
+        {
+            try
+            {
+                timer.Enabled = false;
+                _can.Token.ThrowIfCancellationRequested();
+                Refresh();
+                timer.Enabled = true;
+            }
+            catch
+            {
+
+            }
+        }
+
+        public void Stop()
+        {
+            _can?.Cancel();
+            _can?.Dispose();
+            instance = null;
+        }
+    }
+}
diff --git a/WPFProject/Settings.ini b/WPFProject/Settings.ini
new file mode 100644
index 0000000..2f052e0
--- /dev/null
+++ b/WPFProject/Settings.ini
@@ -0,0 +1,6 @@
+[Btns]
+;名称=ActionName
+上电激活=Active
+CAN测试=CanTest
+LIN测试=LinTest
+DoIP测试=DOIPTest
\ No newline at end of file
diff --git a/WPFProject/UI/ResourceUI.xaml b/WPFProject/UI/ResourceUI.xaml
new file mode 100644
index 0000000..6a2050e
--- /dev/null
+++ b/WPFProject/UI/ResourceUI.xaml
@@ -0,0 +1,63 @@
+锘�<hb:NotifyControl x:Class="WPFProject.ResourceUI"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:WPFProject.UI"
+             mc:Ignorable="d"
+             xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
+             xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
+             xmlns:pu="https://opensource.panuon.com/wpf-ui"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <hb:NotifyControl.Resources>
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <pu:StyleDictionary Includes="Label" />
+            </ResourceDictionary.MergedDictionaries>
+            <Style x:Key="def" TargetType="Label" BasedOn="{StaticResource {x:Static pu:StyleKeys.LabelStyle}}">
+                <Setter Property="pu:LabelHelper.CornerRadius" Value="3"/>
+                <Setter Property="Padding" Value="5,0"/>
+                <Setter Property="Margin" Value="0"/>
+                <Setter Property="VerticalContentAlignment" Value="Center"/>
+                <Setter Property="Foreground" Value="#FFF"/>
+            </Style>
+        </ResourceDictionary>
+    </hb:NotifyControl.Resources>
+    
+    <Grid dp:GridHelper.Space="10" dp:GridHelper.SpaceIgnore="ALL">
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="Auto"/>
+            <ColumnDefinition Width="Auto"/>
+            <ColumnDefinition Width="Auto"/>
+            <ColumnDefinition Width="Auto"/>
+        </Grid.ColumnDefinitions>
+
+        <Label Grid.Column="0" Style="{StaticResource def}" x:Name="l1">
+            <TextBlock>
+    <Run Text="{DynamicResource App.CPUUsage}"/>
+    <Run Text="{Binding CpuRate}"/>
+            </TextBlock>
+        </Label>
+
+        <Label Grid.Column="1" Style="{StaticResource def}" x:Name="l2">
+            <TextBlock>
+    <Run Text="{DynamicResource App.RemUsage}"/>
+    <Run Text="{Binding RemRate}"/>
+            </TextBlock>
+        </Label>
+
+        <Label Grid.Column="2" Style="{StaticResource def}" x:Name="l3">
+            <TextBlock Grid.Column="2">
+    <Run Text="{DynamicResource App.TotalCPUUsage}"/>
+    <Run Text="{Binding TotalCpuRate}"/>
+            </TextBlock>
+        </Label>
+
+        <Label Grid.Column="3" Style="{StaticResource def}" x:Name="l4">
+            <TextBlock Grid.Column="3">
+    <Run Text="{DynamicResource App.TotalRemUsage}"/>
+    <Run Text="{Binding TotalRemRate}"/>
+            </TextBlock>
+        </Label>
+    </Grid>
+</hb:NotifyControl>
diff --git a/WPFProject/UI/ResourceUI.xaml.cs b/WPFProject/UI/ResourceUI.xaml.cs
new file mode 100644
index 0000000..7efd5f2
--- /dev/null
+++ b/WPFProject/UI/ResourceUI.xaml.cs
@@ -0,0 +1,91 @@
+锘縰sing HMI.Base;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+
+namespace WPFProject
+{
+    /// <summary>
+    /// ResourceListener.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class ResourceUI : NotifyControl
+    {
+        private string cpuRate;
+        public string CpuRate
+        {
+            get => cpuRate;
+            set
+            {
+                Set(ref cpuRate, value);
+            }
+        }
+
+        private string remRate;
+        public string RemRate
+        {
+            get => remRate;
+            set
+            {
+                Set(ref remRate, value);
+            }
+        }
+
+        private string totalCpuRate;
+        public string TotalCpuRate
+        {
+            get => totalCpuRate;
+            set
+            {
+                Set(ref totalCpuRate, value);
+            }
+        }
+
+        private string totalRemRate;
+        public string TotalRemRate
+        {
+            get => totalRemRate;
+            set
+            {
+                Set(ref totalRemRate, value);
+            }
+        }
+
+        public ResourceUI()
+        {
+            DataContext = this;
+            InitializeComponent();
+        }
+
+        public void Refresh(object sender, RunTimeState state)
+        {
+            Dispatcher.BeginInvoke(() =>
+            {
+                CpuRate = $"{state.cpuRate:F1}%";
+                SetColor(state.cpuRate, l1);
+                RemRate = $"{state.remRate:F1}%";
+                SetColor(state.remRate, l2);
+
+                TotalCpuRate = $"{state.totalCpuRate:F1}%";
+                SetColor(state.totalCpuRate, l3);
+                TotalRemRate = $"{state.totalRemRate:F1}%";
+                SetColor(state.totalRemRate, l4);
+            }, System.Windows.Threading.DispatcherPriority.Normal);
+        }
+
+        private void SetColor(double val, Label label)
+        {
+            if (val > 80)
+            {
+                label.Background = Brushes.Red;
+            }
+            else if (val > 60)
+            {
+                label.Background = Brushes.Orange;
+            }
+            else
+            {
+                label.Background = Application.Current.FindResource("SecondPrimaryColor") as Brush;
+            }
+        }
+    }
+}
diff --git a/WPFProject/WPFProject.csproj b/WPFProject/WPFProject.csproj
index 6e6566b..10f5341 100644
--- a/WPFProject/WPFProject.csproj
+++ b/WPFProject/WPFProject.csproj
@@ -198,6 +198,10 @@
     <Compile Include="PanuonWindow.xaml.cs">
       <DependentUpon>PanuonWindow.xaml</DependentUpon>
     </Compile>
+    <Compile Include="ResourceListener.cs" />
+    <Compile Include="UI\ResourceUI.xaml.cs">
+      <DependentUpon>ResourceUI.xaml</DependentUpon>
+    </Compile>
     <Compile Include="UI\TestWindow.xaml.cs">
       <DependentUpon>TestWindow.xaml</DependentUpon>
     </Compile>
@@ -239,6 +243,10 @@
       <DependentUpon>MenuTabControl.xaml</DependentUpon>
     </Compile>
     <Page Include="PanuonWindow.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="UI\ResourceUI.xaml">
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>
@@ -299,6 +307,9 @@
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Content>
     <Content Include="鍙樻洿璁板綍\2025-12-24.txt" />
+    <None Include="Settings.ini">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
     <Resource Include="Icon\b1.jpg" />
     <Resource Include="Icon\b2.jpg" />
     <Resource Include="Icon\b3.jpg" />
@@ -437,6 +448,10 @@
     <ProjectReference Include="..\ModBusTcpController\ModBusTcpController.csproj">
       <Project>{49db0c9b-b239-4608-a91e-f5ed43afaf96}</Project>
       <Name>ModBusTcpController</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\Plugin.DataViewer\Plugin.DataViewer.csproj">
+      <Project>{dbfe5040-963f-4bbf-93fb-26b3f18fda90}</Project>
+      <Name>Plugin.DataViewer</Name>
     </ProjectReference>
     <ProjectReference Include="..\Plugin.SeqEditor\Plugin.SeqEditor.csproj">
       <Project>{1260f995-1ae9-17b6-feb5-c430c1416bec}</Project>
@@ -587,6 +602,8 @@
 move /y "$(TargeDir)Plugin.ModelManage.dll" $(TargeDir)Plugin\Plugin.ModelManage.dll"
 move /y "$(TargeDir)Plugin.CurrentSensorControl.dll" $(TargeDir)Plugin\Plugin.CurrentSensorControl.dll"
 move /y "$(TargeDir)Plugin.TsSupport.dll" $(TargeDir)Plugin\Plugin.TsSupport.dll"
+move /y "$(TargeDir)Plugin.DataViewer.dll" $(TargeDir)Plugin\Plugin.DataViewer.dll"
+move /y "$(TargeDir)Util.ManualDebug.dll" $(TargeDir)Plugin\Util.ManualDebug.dll"
 
 setlocal enabledelayedexpansion
 
@@ -609,7 +626,7 @@
 )
 
 for %25%25F in ("%25source_folder%25\*") do (
-    if /I not "%25%25~nxF"=="$(TargetFileName)" if /I not "%25%25~nxF"=="$(TargetFileName).config" if /I not "%25%25~nxF"=="$(TargetName).pdb" if /I not "%25%25~nxF"=="clear.bat" if /I not "%25%25~nxF"=="瀹夎MSMQ.bat" (
+    if /I not "%25%25~nxF"=="$(TargetFileName)" if /I not "%25%25~nxF"=="$(TargetFileName).config" if /I not "%25%25~nxF"=="$(TargetName).pdb" if /I not "%25%25~nxF"=="clear.bat" if /I not "%25%25~nxF"=="瀹夎MSMQ.bat" if /I not "%25%25~nxF"=="Settings.ini" (
         move /Y "%25%25F" "%25destination_folder%25"
     )
 )
diff --git a/WPFProject/WPFProject.sln b/WPFProject/WPFProject.sln
index ba5a291..b4b4b91 100644
--- a/WPFProject/WPFProject.sln
+++ b/WPFProject/WPFProject.sln
@@ -75,6 +75,16 @@
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin.SeqEditor", "..\Plugin.SeqEditor\Plugin.SeqEditor.csproj", "{1260F995-1AE9-17B6-FEB5-C430C1416BEC}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin.DataViewer", "..\Plugin.DataViewer\Plugin.DataViewer.csproj", "{DBFE5040-963F-4BBF-93FB-26B3F18FDA90}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin.UICollection", "..\Plugin.UICollection\Plugin.UICollection.csproj", "{22DC8D91-50B2-47CA-86A8-29A6DA48F388}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UtilLib", "..\..\UtilLib\UtilLib.csproj", "{A132F114-A26B-4428-BAEC-4851F38C0E14}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UILib", "..\..\UILib\UILib.csproj", "{27325FE0-3CE2-4CD5-8EB5-7E45CC441479}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Util.ManualDebug", "..\Util.ManualDebug\Util.ManualDebug.csproj", "{D0944E62-EE52-4ABE-832A-A09D98814DB1}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -505,6 +515,66 @@
 		{1260F995-1AE9-17B6-FEB5-C430C1416BEC}.Release|x64.Build.0 = Release|x64
 		{1260F995-1AE9-17B6-FEB5-C430C1416BEC}.Release|x86.ActiveCfg = Release|x86
 		{1260F995-1AE9-17B6-FEB5-C430C1416BEC}.Release|x86.Build.0 = Release|x86
+		{DBFE5040-963F-4BBF-93FB-26B3F18FDA90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{DBFE5040-963F-4BBF-93FB-26B3F18FDA90}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{DBFE5040-963F-4BBF-93FB-26B3F18FDA90}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{DBFE5040-963F-4BBF-93FB-26B3F18FDA90}.Debug|x64.Build.0 = Debug|Any CPU
+		{DBFE5040-963F-4BBF-93FB-26B3F18FDA90}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{DBFE5040-963F-4BBF-93FB-26B3F18FDA90}.Debug|x86.Build.0 = Debug|Any CPU
+		{DBFE5040-963F-4BBF-93FB-26B3F18FDA90}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{DBFE5040-963F-4BBF-93FB-26B3F18FDA90}.Release|Any CPU.Build.0 = Release|Any CPU
+		{DBFE5040-963F-4BBF-93FB-26B3F18FDA90}.Release|x64.ActiveCfg = Release|Any CPU
+		{DBFE5040-963F-4BBF-93FB-26B3F18FDA90}.Release|x64.Build.0 = Release|Any CPU
+		{DBFE5040-963F-4BBF-93FB-26B3F18FDA90}.Release|x86.ActiveCfg = Release|Any CPU
+		{DBFE5040-963F-4BBF-93FB-26B3F18FDA90}.Release|x86.Build.0 = Release|Any CPU
+		{22DC8D91-50B2-47CA-86A8-29A6DA48F388}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{22DC8D91-50B2-47CA-86A8-29A6DA48F388}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{22DC8D91-50B2-47CA-86A8-29A6DA48F388}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{22DC8D91-50B2-47CA-86A8-29A6DA48F388}.Debug|x64.Build.0 = Debug|Any CPU
+		{22DC8D91-50B2-47CA-86A8-29A6DA48F388}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{22DC8D91-50B2-47CA-86A8-29A6DA48F388}.Debug|x86.Build.0 = Debug|Any CPU
+		{22DC8D91-50B2-47CA-86A8-29A6DA48F388}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{22DC8D91-50B2-47CA-86A8-29A6DA48F388}.Release|Any CPU.Build.0 = Release|Any CPU
+		{22DC8D91-50B2-47CA-86A8-29A6DA48F388}.Release|x64.ActiveCfg = Release|Any CPU
+		{22DC8D91-50B2-47CA-86A8-29A6DA48F388}.Release|x64.Build.0 = Release|Any CPU
+		{22DC8D91-50B2-47CA-86A8-29A6DA48F388}.Release|x86.ActiveCfg = Release|Any CPU
+		{22DC8D91-50B2-47CA-86A8-29A6DA48F388}.Release|x86.Build.0 = Release|Any CPU
+		{A132F114-A26B-4428-BAEC-4851F38C0E14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{A132F114-A26B-4428-BAEC-4851F38C0E14}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{A132F114-A26B-4428-BAEC-4851F38C0E14}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{A132F114-A26B-4428-BAEC-4851F38C0E14}.Debug|x64.Build.0 = Debug|Any CPU
+		{A132F114-A26B-4428-BAEC-4851F38C0E14}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{A132F114-A26B-4428-BAEC-4851F38C0E14}.Debug|x86.Build.0 = Debug|Any CPU
+		{A132F114-A26B-4428-BAEC-4851F38C0E14}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{A132F114-A26B-4428-BAEC-4851F38C0E14}.Release|Any CPU.Build.0 = Release|Any CPU
+		{A132F114-A26B-4428-BAEC-4851F38C0E14}.Release|x64.ActiveCfg = Release|Any CPU
+		{A132F114-A26B-4428-BAEC-4851F38C0E14}.Release|x64.Build.0 = Release|Any CPU
+		{A132F114-A26B-4428-BAEC-4851F38C0E14}.Release|x86.ActiveCfg = Release|Any CPU
+		{A132F114-A26B-4428-BAEC-4851F38C0E14}.Release|x86.Build.0 = Release|Any CPU
+		{27325FE0-3CE2-4CD5-8EB5-7E45CC441479}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{27325FE0-3CE2-4CD5-8EB5-7E45CC441479}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{27325FE0-3CE2-4CD5-8EB5-7E45CC441479}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{27325FE0-3CE2-4CD5-8EB5-7E45CC441479}.Debug|x64.Build.0 = Debug|Any CPU
+		{27325FE0-3CE2-4CD5-8EB5-7E45CC441479}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{27325FE0-3CE2-4CD5-8EB5-7E45CC441479}.Debug|x86.Build.0 = Debug|Any CPU
+		{27325FE0-3CE2-4CD5-8EB5-7E45CC441479}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{27325FE0-3CE2-4CD5-8EB5-7E45CC441479}.Release|Any CPU.Build.0 = Release|Any CPU
+		{27325FE0-3CE2-4CD5-8EB5-7E45CC441479}.Release|x64.ActiveCfg = Release|Any CPU
+		{27325FE0-3CE2-4CD5-8EB5-7E45CC441479}.Release|x64.Build.0 = Release|Any CPU
+		{27325FE0-3CE2-4CD5-8EB5-7E45CC441479}.Release|x86.ActiveCfg = Release|Any CPU
+		{27325FE0-3CE2-4CD5-8EB5-7E45CC441479}.Release|x86.Build.0 = Release|Any CPU
+		{D0944E62-EE52-4ABE-832A-A09D98814DB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{D0944E62-EE52-4ABE-832A-A09D98814DB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{D0944E62-EE52-4ABE-832A-A09D98814DB1}.Debug|x64.ActiveCfg = Debug|x64
+		{D0944E62-EE52-4ABE-832A-A09D98814DB1}.Debug|x64.Build.0 = Debug|x64
+		{D0944E62-EE52-4ABE-832A-A09D98814DB1}.Debug|x86.ActiveCfg = Debug|x86
+		{D0944E62-EE52-4ABE-832A-A09D98814DB1}.Debug|x86.Build.0 = Debug|x86
+		{D0944E62-EE52-4ABE-832A-A09D98814DB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{D0944E62-EE52-4ABE-832A-A09D98814DB1}.Release|Any CPU.Build.0 = Release|Any CPU
+		{D0944E62-EE52-4ABE-832A-A09D98814DB1}.Release|x64.ActiveCfg = Release|Any CPU
+		{D0944E62-EE52-4ABE-832A-A09D98814DB1}.Release|x64.Build.0 = Release|Any CPU
+		{D0944E62-EE52-4ABE-832A-A09D98814DB1}.Release|x86.ActiveCfg = Release|Any CPU
+		{D0944E62-EE52-4ABE-832A-A09D98814DB1}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

--
Gitblit v1.9.1