From 4cac1b533e133fdc8b3def90a0f998820f51907a Mon Sep 17 00:00:00 2001
From: alielie <alielie@git.com>
Date: 星期三, 26 三月 2025 17:57:59 +0800
Subject: [PATCH] 优化UI,新增日志自动删除;

---
 HMI.ParameterManage/ProductModal.xaml.cs       |   19 
 HMI.ParameterManage/ParameterManage.xaml.cs    |    4 
 HMI.MenuManage/Entity/MenuData.cs              |    2 
 Plugin.TestResult/SingleCell.xaml              |   14 
 HMI.LogViewer/LogViewer.xaml                   |   74 
 Plugin.TestResult/DataTable.xaml               |   46 
 Plugin.TestResult/TestResultView.xaml.cs       |   22 
 HMI.ParameterManage/Model/SqlModel.cs          |    7 
 Plugin.DOControl/DOControl.xaml                |  185 +-
 WPFProject/Lang/zh-CN/app.json                 |    1 
 HMI.ParameterManage/HMI.ParameterManage.csproj |    9 
 WPFProject/Lang/zh-CN/DOControl.json           |   11 
 HMI.Core/Entity/NotifyBase.cs                  |    4 
 HMI.Core/Manager/ProductManager.cs             |   55 
 HMI.Core/Setting/SystemSetting.cs              |    8 
 WPFProject/Lang/zh-CN/TestResult.json          |    2 
 WPFProject/Models/Step.cs                      |   16 
 Plugin.DOControl/DataModal.xaml                |   85 +
 HMI.UI/Controls/NumberPanel.xaml               |   10 
 WPFProject/Models/Program.cs                   |    6 
 HMI.Core/Manager/TestItemManager.cs            |   11 
 HMI.UI/Controls/KeyboradWindow.xaml            |  234 ++++
 Plugin.TestResult/ViewControl.xaml.cs          |   14 
 HMI.ParameterManage/TestItemModal.xaml.cs      |    4 
 Plugin.ServoManage/Plugin.ServoManage.csproj   |    7 
 HMI.Core/Util/LoggerHolder.cs                  |   37 
 WPFProject/Lang/en-US/LogViewer.json           |   10 
 WPFProject/App.config                          |    4 
 Plugin.ServoManage/ServoManage.xaml.cs         |  107 +
 HMI.MenuManage/Modal/MenuModal.xaml.cs         |    9 
 Plugin.TsExecution/TsExecution.xaml.cs         |   10 
 HMI.Core/Annotation/ExcelColumn.cs             |    6 
 HMI.MenuManage/Model/MenuManageModel.cs        |    6 
 Plugin.TestResult/Scanner.xaml                 |   58 
 HMI.ParameterManage/Model/XmlModel.cs          |  142 +
 HMI.UI/Controls/KeyboradWindow.xaml.cs         |  110 +
 WPFProject/Lang/en-US/TestResult.json          |    4 
 Plugin.DOControl/ControlList.xaml              |  120 ++
 HMI.Core/Entity/ParameterData.cs               |    5 
 HMI.Core/Util/PathHelper.cs                    |   85 +
 HMI.Core/Entity/TestItemData.cs                |    5 
 HMI.ParameterManage/SettingModal.xaml          |   14 
 HMI.UI/Controls/NumberPanel.xaml.cs            |   11 
 HMI.Core/Manager/ParameterManager.cs           |   44 
 HMI.ParameterManage/ParameterSetting.xaml      |   15 
 HMI.ParameterManage/SettingModal.xaml.cs       |   12 
 HMI.Core/HMI.Core.csproj                       |   14 
 HMI.UI/Style/FormStyle.xaml                    |   16 
 HMI.Base/PluginLoader.cs                       |    2 
 Plugin.TestResult/InfoTable.xaml               |    6 
 Plugin.DOControl/ControlList.xaml.cs           |  224 ++++
 WPFProject/WPFProject.csproj                   |   19 
 HMI.IOMonitor/HMI.IOMonitor.csproj             |    7 
 HMI.Core/Entity/ProductData.cs                 |    7 
 HMI.ParameterManage/ParameterSetting.xaml.cs   |    5 
 WPFProject/TextFile1.txt                       |    1 
 WPFProject/PanuonWindow.xaml                   |    4 
 Plugin.ServoManage/ServoManage.xaml            |   29 
 Plugin.DOControl/Plugin.DOControl.csproj       |   20 
 WPFProject/Lang/en-US/setting.json             |    1 
 HMI.UI/Controls/NumberInput.cs                 |    0 
 Plugin.DOControl/DataModal.xaml.cs             |  157 ++
 HMI.ParameterManage/ParameterModal.xaml.cs     |    4 
 Plugin.DOControl/Entity/DOControlData.cs       |   60 +
 HMI.Util/ExcelHelper.cs                        |   21 
 HMI.MenuManage/Modal/MenuModal.xaml            |    7 
 HMI.LogViewer/LogViewer.xaml.cs                |  141 +
 HMI.MenuManage/MenuManage.xaml                 |    9 
 Plugin.DOControl/DOControl.xaml.cs             |  474 ++++++++
 WPFProject/Lang/zh-CN/setting.json             |    1 
 HMI.Util/HMI.Util.csproj                       |    7 
 WPFProject/Controls/MainTabControl.xaml.cs     |   26 
 WPFProject/Utils/LogCleanner.cs                |  109 +
 /dev/null                                      |   69 -
 WPFProject/Lang/en-US/DOControl.json           |   11 
 HMI.ParameterManage/TestItem.xaml.cs           |   16 
 WPFProject/App.xaml.cs                         |    6 
 WPFProject/Utils/CommonLogger.cs               |  103 +
 HMI.SettingControl/SystemConfig.xaml           |    5 
 Plugin.TestResult/ViewControl.xaml             |    5 
 WPFProject/Lang/en-US/app.json                 |    1 
 Plugin.TestResult/DataTable.xaml.cs            |   72 +
 WPFProject/Lang/zh-CN/LogViewer.json           |   10 
 HMI.Core/Api/ILogWriter.cs                     |   10 
 HMI.UI/HMI.UI.csproj                           |   16 
 85 files changed, 2,771 insertions(+), 588 deletions(-)

diff --git a/HMI.Base/PluginLoader.cs b/HMI.Base/PluginLoader.cs
index ae11ccb..e7be956 100644
--- a/HMI.Base/PluginLoader.cs
+++ b/HMI.Base/PluginLoader.cs
@@ -167,7 +167,7 @@
                 string sql = $@"SELECT m.* FROM menu AS m INNER JOIN role_menu_rel AS rrel ON m.code = rrel.menuCode
 INNER JOIN role AS r ON rrel.roleCode = r.code
 INNER JOIN user_role_rel AS urel ON r.code = urel.roleCode
-AND urel.username = '{user.Username}' ORDER BY m.level, m.id;";
+AND urel.username = '{user.Username}' ORDER BY m.level,m.sort, m.id;";
 
                 DataTable dt = sqliteHelper.GetDataTable(sql);
                 if (dt != null)
diff --git a/HMI.Util/ExcelColumn.cs b/HMI.Core/Annotation/ExcelColumn.cs
similarity index 69%
rename from HMI.Util/ExcelColumn.cs
rename to HMI.Core/Annotation/ExcelColumn.cs
index 500b8e9..c6ac1c8 100644
--- a/HMI.Util/ExcelColumn.cs
+++ b/HMI.Core/Annotation/ExcelColumn.cs
@@ -1,10 +1,6 @@
 锘縰sing System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
 
-namespace HMI.Util
+namespace HMI.Core
 {
     [AttributeUsage(AttributeTargets.Property)]
     public class ExcelColumnAttribute : Attribute
diff --git a/HMI.Core/Api/ILogWriter.cs b/HMI.Core/Api/ILogWriter.cs
new file mode 100644
index 0000000..5edf85b
--- /dev/null
+++ b/HMI.Core/Api/ILogWriter.cs
@@ -0,0 +1,10 @@
+锘縩amespace HMI.Core.Api
+{
+    public interface ILogWriter
+    {
+        void Debug(string source, string content);
+        void Info(string source, string content);
+        void Warn(string source, string content);
+        void Error(string source, string content);
+    }
+}
diff --git a/HMI.Core/Api/IProgram.cs b/HMI.Core/Api/IProgram.cs
deleted file mode 100644
index 6a04c6a..0000000
--- a/HMI.Core/Api/IProgram.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-锘縩amespace HMI.Core.Api
-{
-    public interface IProgram
-    {
-        void RunProgram();
-    }
-}
diff --git a/HMI.Core/Api/ISequnce.cs b/HMI.Core/Api/ISequnce.cs
deleted file mode 100644
index be92417..0000000
--- a/HMI.Core/Api/ISequnce.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-锘縰sing System.Collections.Generic;
-
-namespace HMI.Core.Api
-{
-    public interface ISequence
-    {
-        void SetSteps(List<IStep> steps);
-
-        IList<IStep> GetSteps();
-
-        ISequence AddStep(params IStep[] steps);
-
-        void SetPrograme(IProgram program);
-
-        IProgram GetProgram();
-
-        void RunSequnce();
-    }
-}
diff --git a/HMI.Core/Api/IStep.cs b/HMI.Core/Api/IStep.cs
deleted file mode 100644
index 6470b1b..0000000
--- a/HMI.Core/Api/IStep.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-锘縩amespace HMI.Core.Api
-{
-    public interface IStep
-    {
-        void RunStep();
-    }
-}
diff --git a/HMI.Core/Api/IStepRunner.cs b/HMI.Core/Api/IStepRunner.cs
deleted file mode 100644
index 82a85b2..0000000
--- a/HMI.Core/Api/IStepRunner.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-锘縰sing System.Collections.Generic;
-using HMI.Core.Entity;
-
-namespace HMI.Core.Api
-{
-    public interface IStepRunner
-    {
-        void RunStep(Dictionary<string, Param> param);
-    }
-}
diff --git a/HMI.Core/Entity/NotifyBase.cs b/HMI.Core/Entity/NotifyBase.cs
index 548084b..09b4634 100644
--- a/HMI.Core/Entity/NotifyBase.cs
+++ b/HMI.Core/Entity/NotifyBase.cs
@@ -1,10 +1,6 @@
 锘縰sing System;
-using System.Collections.Generic;
 using System.ComponentModel;
-using System.Linq;
 using System.Runtime.CompilerServices;
-using System.Text;
-using System.Threading.Tasks;
 using System.Windows.Input;
 
 namespace HMI.Core.Entity
diff --git a/HMI.Core/Entity/Param.cs b/HMI.Core/Entity/Param.cs
deleted file mode 100644
index 69c0449..0000000
--- a/HMI.Core/Entity/Param.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-锘縩amespace HMI.Core.Entity
-{
-    public class Param
-    {
-        private string _Name;
-
-        public string Name
-        {
-            get { return _Name; }
-            set { _Name = value; }
-        }
-
-        private string _Value;
-
-        public string Value
-        {
-            get { return _Value; }
-            set { _Value = value; }
-        }
-
-    }
-}
diff --git a/HMI.ParameterManage/Entity/ParameterData.cs b/HMI.Core/Entity/ParameterData.cs
similarity index 89%
rename from HMI.ParameterManage/Entity/ParameterData.cs
rename to HMI.Core/Entity/ParameterData.cs
index a4b9cd7..f0b4e30 100644
--- a/HMI.ParameterManage/Entity/ParameterData.cs
+++ b/HMI.Core/Entity/ParameterData.cs
@@ -1,7 +1,4 @@
-锘縰sing HMI.Core.Entity;
-using HMI.Util;
-
-namespace HMI.ParameterManage.Entity
+锘縩amespace HMI.Core.Entity
 {
     public class ParameterData : SelectBase
     {
diff --git a/HMI.ParameterManage/Entity/ProductData.cs b/HMI.Core/Entity/ProductData.cs
similarity index 71%
rename from HMI.ParameterManage/Entity/ProductData.cs
rename to HMI.Core/Entity/ProductData.cs
index e5a5b93..2189c8b 100644
--- a/HMI.ParameterManage/Entity/ProductData.cs
+++ b/HMI.Core/Entity/ProductData.cs
@@ -1,8 +1,6 @@
-锘�
-using HMI.Core.Entity;
-using System.Xml.Serialization;
+锘縰sing System.Xml.Serialization;
 
-namespace HMI.ParameterManage.Entity
+namespace HMI.Core.Entity
 {
     public class ProductData:SelectBase
     {
@@ -11,5 +9,6 @@
         public string Name { get; set; }
         public bool IsMaster { get; set; }
         public string ParentId { get; set; }
+        public string dir;
     }
 }
diff --git a/HMI.ParameterManage/Entity/TestItemData.cs b/HMI.Core/Entity/TestItemData.cs
similarity index 94%
rename from HMI.ParameterManage/Entity/TestItemData.cs
rename to HMI.Core/Entity/TestItemData.cs
index 3f0dbc6..7d6803a 100644
--- a/HMI.ParameterManage/Entity/TestItemData.cs
+++ b/HMI.Core/Entity/TestItemData.cs
@@ -1,7 +1,4 @@
-锘縰sing HMI.Core.Entity;
-using HMI.Util;
-
-namespace HMI.ParameterManage.Entity
+锘縩amespace HMI.Core.Entity
 {
     public class TestItemData : SelectBase
     {
diff --git a/HMI.Core/HMI.Core.csproj b/HMI.Core/HMI.Core.csproj
index b090fb1..bd6f331 100644
--- a/HMI.Core/HMI.Core.csproj
+++ b/HMI.Core/HMI.Core.csproj
@@ -86,26 +86,27 @@
     <Reference Include="WindowsBase" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="Annotation\ExcelColumn.cs" />
     <Compile Include="Api\IConfigManager.cs" />
     <Compile Include="Api\IConfigRefresh.cs" />
     <Compile Include="Api\IFolderOwner.cs" />
+    <Compile Include="Api\ILogWriter.cs" />
     <Compile Include="Api\IPlugin.cs" />
     <Compile Include="Api\IPluginContainer.cs" />
-    <Compile Include="Api\IProgram.cs" />
-    <Compile Include="Api\ISequnce.cs" />
-    <Compile Include="Api\IStep.cs" />
-    <Compile Include="Api\IStepRunner.cs" />
     <Compile Include="Annotation\InitAttribute.cs" />
     <Compile Include="Api\PluginPackage.cs" />
     <Compile Include="Entity\LangInfo.cs" />
     <Compile Include="Entity\NotifyBase.cs" />
-    <Compile Include="Entity\Param.cs" />
+    <Compile Include="Entity\ParameterData.cs" />
+    <Compile Include="Entity\ProductData.cs" />
     <Compile Include="Entity\SelectBase.cs" />
+    <Compile Include="Entity\TestItemData.cs" />
     <Compile Include="Entity\UserInfo.cs" />
     <Compile Include="Event\TabChange.cs" />
     <Compile Include="Exceptions\FormValidateException.cs" />
     <Compile Include="FontSizeConverter.cs" />
     <Compile Include="LangConverter.cs" />
+    <Compile Include="Manager\TestItemManager.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Setting\SystemSetting.cs" />
     <Compile Include="Util\BeanUtil.cs" />
@@ -113,8 +114,11 @@
     <Compile Include="Util\HexUtil.cs" />
     <Compile Include="Util\JsonUtil.cs" />
     <Compile Include="Util\LangUtil.cs" />
+    <Compile Include="Util\LoggerHolder.cs" />
     <Compile Include="Util\MSMQHelper.cs" />
+    <Compile Include="Manager\ParameterManager.cs" />
     <Compile Include="Util\PathHelper.cs" />
+    <Compile Include="Manager\ProductManager.cs" />
     <Compile Include="Util\SqliteHelper.cs" />
     <Compile Include="Util\UtilContainer.cs" />
     <Compile Include="Util\XmlHelper.cs" />
diff --git a/HMI.Core/Manager/ParameterManager.cs b/HMI.Core/Manager/ParameterManager.cs
new file mode 100644
index 0000000..68be8fd
--- /dev/null
+++ b/HMI.Core/Manager/ParameterManager.cs
@@ -0,0 +1,44 @@
+锘縰sing HMI.Core.Entity;
+using HMI.Core.Util;
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace HMI.Core.Manager
+{
+    /// <summary>
+    /// TODO 浼樺寲鏁版嵁婧愶紝闇�瑕佹敮鎸乻qlite鍜寈ml
+    /// </summary>
+    public class ParameterManager
+    {
+
+        /// <summary>
+        /// 鑾峰彇閫氱敤鍙傛暟
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <returns></returns>
+        public static T GetCommonParameter<T>() where T : IList<ParameterData>
+        {
+            string path = PathHelper.ParameterDataPath;
+            if (!File.Exists(path)) return default(T);
+            T datas = XmlHelper.DeserializeFromXml<T>(path);
+            return datas;
+        }
+
+        /// <summary>
+        /// 淇濆瓨閫氱敤鍙傛暟
+        /// </summary>
+        /// <param name="datas"></param>
+        /// <exception cref="Exception"></exception>
+        public static void SaveCommonParameter(IList<ParameterData> datas)
+        {
+            if (datas == null)
+            {
+                throw new Exception("涓嶈兘涓簄ull");
+            }
+            string path = PathHelper.ParameterDataPath;
+            XmlHelper.SerializeToXml(path, datas);
+        }
+
+    }
+}
diff --git a/HMI.Core/Manager/ProductManager.cs b/HMI.Core/Manager/ProductManager.cs
new file mode 100644
index 0000000..5d02c09
--- /dev/null
+++ b/HMI.Core/Manager/ProductManager.cs
@@ -0,0 +1,55 @@
+锘縰sing HMI.Core.Entity;
+using HMI.Core.Util;
+using System.Collections.Generic;
+using System.IO;
+
+namespace HMI.Core.Manager
+{
+    /// <summary>
+    /// TODO 浼樺寲鏁版嵁婧愶紝闇�瑕佹敮鎸乻qlite鍜寈ml
+    /// </summary>
+    public class ProductManager
+    {
+        /// <summary>
+        /// 鑾峰彇淇濆瓨鐨勪骇鍝�
+        /// </summary>
+        /// <returns></returns>
+        public static T GetProducts<T>() where T : IList<ProductData>
+        {
+            string path = PathHelper.ProductDataFile;
+            if (!File.Exists(path)) return default(T);
+            T datas = XmlHelper.DeserializeFromXml<T>(path);
+            return datas;
+        }
+
+        /// <summary>
+        /// 鑾峰彇淇濆瓨鐨勪骇鍝�
+        /// </summary>
+        /// <returns></returns>
+        public static T GetParametersByProductName<T>(string productName) where T : IList<ParameterData>
+        {
+            string path = Path.Combine(PathHelper.ProductParameterFolder, $"Parameter_{productName}.data");
+            if (!File.Exists(path)) return default(T);
+            T datas = XmlHelper.DeserializeFromXml<T>(path);
+            return datas;
+        }
+
+
+        public static List<ProductData> GetProducts()
+        {
+            List<ProductData> datas = GetProducts<List<ProductData>>();
+            return datas ?? new List<ProductData>();
+        }
+
+        /// <summary>
+        /// 鏍规嵁浜у搧鍚嶇О鑾峰彇浜у搧鍙傛暟
+        /// </summary>
+        /// <param name="productName"></param>
+        /// <returns></returns>
+        public static List<ParameterData> GetParametersByProductName(string productName)
+        {
+            List<ParameterData> datas = GetParametersByProductName<List<ParameterData>>(productName);
+            return datas ?? new List<ParameterData>();
+        }
+    }
+}
diff --git a/HMI.ParameterManage/Util/TestItemManager.cs b/HMI.Core/Manager/TestItemManager.cs
similarity index 77%
rename from HMI.ParameterManage/Util/TestItemManager.cs
rename to HMI.Core/Manager/TestItemManager.cs
index 4478c04..1b253fa 100644
--- a/HMI.ParameterManage/Util/TestItemManager.cs
+++ b/HMI.Core/Manager/TestItemManager.cs
@@ -1,11 +1,10 @@
-锘縰sing HMI.Core.Util;
-using HMI.ParameterManage.Entity;
+锘縰sing HMI.Core.Entity;
+using HMI.Core.Util;
 using System;
-using System.Collections;
 using System.Collections.Generic;
 using System.IO;
 
-namespace HMI.ParameterManage.Util
+namespace HMI.Core.Manager
 {
     /// <summary>
     /// TODO 浼樺寲鏁版嵁婧愶紝闇�瑕佹敮鎸乻qlite鍜寈ml
@@ -19,7 +18,7 @@
         /// <returns></returns>
         public static T GetTestItemByProduct<T>(string product) where T : IList<TestItemData>
         {
-            string path = Path.Combine(TestItem.FileFolder, $"TestItem_{product}.setting");
+            string path = Path.Combine(PathHelper.TestItemDataFolder, $"TestItem_{product}.setting");
             if (!File.Exists(path)) return default(T);
             T datas = XmlHelper.DeserializeFromXml<T>(path);
             return datas;
@@ -36,7 +35,7 @@
             {
                 throw new Exception("涓嶈兘涓簄ull");
             }
-            string path = Path.Combine(TestItem.FileFolder, $"TestItem_{product}.setting");
+            string path = Path.Combine(PathHelper.TestItemDataFolder, $"TestItem_{product}.setting");
             XmlHelper.SerializeToXml(path, datas);
         }
 
diff --git a/HMI.Core/Setting/SystemSetting.cs b/HMI.Core/Setting/SystemSetting.cs
index da6a63b..b7d76cf 100644
--- a/HMI.Core/Setting/SystemSetting.cs
+++ b/HMI.Core/Setting/SystemSetting.cs
@@ -30,6 +30,7 @@
         public string PlcIp { get; private set; }
         public bool PlcEnabled { get; private set; }
         public string CurrentLanguage { get; private set; } = "zh-CN";
+        public int LogExpiredDays { get; private set; } = 7;
 
         private static void LoadFromXml()
         {
@@ -106,5 +107,12 @@
             get => currentLanguage;
             set { Set(ref currentLanguage, value); }
         }
+
+        private int logExpiredDays;
+        public int LogExpiredDays
+        {
+            get => logExpiredDays;
+            set { Set(ref logExpiredDays, value); }
+        }
     }
 }
diff --git a/HMI.Core/Util/LoggerHolder.cs b/HMI.Core/Util/LoggerHolder.cs
new file mode 100644
index 0000000..1dba821
--- /dev/null
+++ b/HMI.Core/Util/LoggerHolder.cs
@@ -0,0 +1,37 @@
+锘縰sing HMI.Core.Api;
+
+namespace HMI.Core.Util
+{
+    public class LoggerHelper
+    {
+        public static ILogWriter Logger { get; private set; }
+
+        public static void Register(ILogWriter logger)
+        {
+            if (Logger == null)
+            {
+                Logger = logger;
+            }
+        }
+
+        public static void Debug(string source, string content)
+        {
+            Logger?.Debug(source, content);
+        }
+
+        public static void Info(string source, string content)
+        {
+            Logger?.Info(source, content);
+        }
+
+        public static void Warn(string source, string content)
+        {
+            Logger?.Warn(source, content);
+        }
+
+        public static void Error(string source, string content)
+        {
+            Logger?.Error(source, content);
+        }
+    }
+}
diff --git a/HMI.Core/Util/PathHelper.cs b/HMI.Core/Util/PathHelper.cs
index 0d67354..ad80093 100644
--- a/HMI.Core/Util/PathHelper.cs
+++ b/HMI.Core/Util/PathHelper.cs
@@ -5,13 +5,77 @@
 {
     public class PathHelper
     {
+        /// <summary>
+        /// 绋嬪簭鐩綍
+        /// </summary>
         public static string AppFolder { get; private set; } = RootPath();
+
+        /// <summary>
+        /// 鍥炬爣鐩綍
+        /// </summary>
         public static string IconFolder { get; private set; }
+
+        /// <summary>
+        /// Config鐩綍
+        /// </summary>
         public static string ConfigFolder { get; private set; }
+
+        /// <summary>
+        /// Sqlite鏁版嵁搴撶洰褰�
+        /// </summary>
         public static string DatabaseFolder { get; private set; }
+
+        /// <summary>
+        /// Sqlite鏁版嵁搴揚ath
+        /// </summary>
         public static string DatabasePath { get; private set; }
+
+        /// <summary>
+        /// 澶氳瑷�鐩綍
+        /// </summary>
         public static string LangFolder { get; private set; }
+
+        /// <summary>
+        /// 绯荤粺璁剧疆鐩綍
+        /// </summary>
         public static string SettingFolder { get; private set; }
+
+        /// <summary>
+        /// 鍙傛暟鐩綍
+        /// </summary>
+        public static string ParameterFolder { get; private set; }
+
+        /// <summary>
+        /// 鍙傛暟鏁版嵁婧愭枃浠�
+        /// </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>
+        /// 娴嬭瘯椤规暟鎹洰褰�
+        /// </summary>
+        public static string TestItemDataFolder { get; private set; }
+
+        /// <summary>
+        /// 娴嬭瘯椤规暟鎹簮鏂囦欢
+        /// </summary>
+        public static string TestItemDataFile { get; private set; }
+
+        /// <summary>
+        /// sqlite鏃ュ織鏁版嵁搴撴枃浠跺す
+        /// </summary>
+        public static string LogViewerDir { get; private set; }
+
 
         /// <summary>
         /// 搴旂敤绋嬪簭鐨勬牴鐩綍
@@ -27,6 +91,17 @@
             DatabasePath = DatabaseFolder + "\\database.db";
             LangFolder = InitFolder(path + "\\Lang");
             SettingFolder = InitFolder(ConfigFolder + "\\SystemSetting");
+            ParameterFolder = InitFolder(ConfigFolder + "\\Parameter");
+            ParameterDataPath = ParameterFolder + "\\CommonParameter.data";
+
+            ProductDataFolder = InitFolder(ConfigFolder + "\\Product");
+            ProductParameterFolder = InitFolder(ProductDataFolder + "\\Parameter");
+            ProductDataFile = ProductDataFolder + "\\Product.data";
+
+            TestItemDataFolder = InitFolder(ProductDataFolder + "\\TestItem");
+            TestItemDataFile = TestItemDataFolder + "\\TestItem.data";
+
+            LogViewerDir = InitFolder(ConfigFolder + "\\LogViewer");
             return path;
         }
 
@@ -77,16 +152,6 @@
         public static string TestItemFolder()
         {
             string Path = ProductionFolder() + "\\TestItem";
-            return Path;
-        }
-
-        /// <summary>
-        /// 璁剧疆鍙傛暟鏂囦欢澶�
-        /// </summary>
-        /// <returns>璁剧疆鍙傛暟鏂囦欢澶�</returns>
-        public static string ParameterFolder()
-        {
-            string Path = ProductionFolder() + "\\Parameter";
             return Path;
         }
 
diff --git a/HMI.IOMonitor/HMI.IOMonitor.csproj b/HMI.IOMonitor/HMI.IOMonitor.csproj
index c048582..c693278 100644
--- a/HMI.IOMonitor/HMI.IOMonitor.csproj
+++ b/HMI.IOMonitor/HMI.IOMonitor.csproj
@@ -68,9 +68,6 @@
     <Reference Include="NPOI.OpenXmlFormats, Version=2.4.1.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1, processorArchitecture=MSIL">
       <HintPath>..\WPFProject\packages\NPOI.2.4.1\lib\net45\NPOI.OpenXmlFormats.dll</HintPath>
     </Reference>
-    <Reference Include="OpcInterface">
-      <HintPath>..\Libs\OpcInterface.dll</HintPath>
-    </Reference>
     <Reference Include="Panuon.WPF, Version=1.1.2.2, Culture=neutral, processorArchitecture=MSIL">
       <HintPath>..\WPFProject\packages\Panuon.WPF.1.1.3\lib\net472\Panuon.WPF.dll</HintPath>
     </Reference>
@@ -142,6 +139,10 @@
     </None>
   </ItemGroup>
   <ItemGroup>
+    <ProjectReference Include="..\..\HMI.Old\OPCInterface\OpcInterface\OpcInterface.csproj">
+      <Project>{e1464149-2c0a-499d-ab90-0c72e6ed1c53}</Project>
+      <Name>OpcInterface</Name>
+    </ProjectReference>
     <ProjectReference Include="..\HMI.Base\HMI.Base.csproj">
       <Project>{2a6f9720-a890-4036-9d73-41a0dae28341}</Project>
       <Name>HMI.Base</Name>
diff --git a/HMI.LogViewer/LogViewer.xaml b/HMI.LogViewer/LogViewer.xaml
index f950fc0..8f9ea21 100644
--- a/HMI.LogViewer/LogViewer.xaml
+++ b/HMI.LogViewer/LogViewer.xaml
@@ -14,22 +14,7 @@
              Background="{StaticResource WorkspaceBackground}"
              d:DesignHeight="450" d:DesignWidth="800">
 
-    <UserControl.Resources>
-        <ResourceDictionary>
-            <ResourceDictionary.MergedDictionaries>
-                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonResource.xaml"/>
-                <ResourceDictionary Source="/HMI.UI;component/Style/FormStyle.xaml"/>
-                <ResourceDictionary Source="/HMI.UI;component/Style/Colors.xaml"/>
-                <ResourceDictionary Source="/HMI.UI;component/Resources/Font/Font.xaml"/>
-            </ResourceDictionary.MergedDictionaries>
-            <uic:PathImageConverter x:Key="path2Image"/>
-            <uic:DataGridRowNumberConverter x:Key="rowNumber"/>
-        </ResourceDictionary>
-    </UserControl.Resources>
-
     <Border Style="{StaticResource CardBoarder}">
-
-
         <Grid dp:GridHelper.Space="10" Cursor="">
             <Grid.RowDefinitions>
                 <RowDefinition Height="Auto"/>
@@ -38,7 +23,7 @@
             </Grid.RowDefinitions>
 
             <TextBlock FontSize="{StaticResource TitleFontSize}" 
-VerticalAlignment="Center" HorizontalAlignment="Left" Text="鏃ュ織鏌ヨ"/>
+VerticalAlignment="Center" HorizontalAlignment="Left" Text="{DynamicResource log.title}"/>
 
             <DockPanel Grid.Row="1" Cursor="">
                 <StackPanel DockPanel.Dock="Right" Style="{StaticResource ButtonGroup}">
@@ -51,42 +36,40 @@
                 </StackPanel>
 
                 <StackPanel Style="{StaticResource ButtonGroup}" HorizontalAlignment="Left">
-                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="绾у埆"/>
+                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="{DynamicResource log.level}"/>
                     <ComboBox Style="{StaticResource FormComboxStyle}" x:Name="levelInput" 
                               Width="180"
                               SelectedValuePath="Content">
-                        <ComboBoxItem Content="info"/>
-                        <ComboBoxItem Content="warn"/>
-                        <ComboBoxItem Content="error"/>
+                        <ComboBoxItem Content="Debug"/>
+                        <ComboBoxItem Content="Info"/>
+                        <ComboBoxItem Content="Warn"/>
+                        <ComboBoxItem Content="Error"/>
                     </ComboBox>
 
-                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="寮�濮嬫椂闂�"/>
+                    <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="缁撴潫鏃堕棿"/>
+                    <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="鏉ユ簮"/>
-                    <!--<ComboBox Style="{StaticResource FormComboxStyle}" x:Name="sourceInput" Width="180"></ComboBox>-->
+                    <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="瀹炴椂鍒锋柊"/>
+                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="{DynamicResource log.autoRefresh}"/>
                     <CheckBox x:Name="autoRfhInput" HorizontalAlignment="Center" VerticalAlignment="Center"
-                              IsChecked="{Binding AutoRfh,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
-                              />
+                              IsChecked="{Binding AutoRfh,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
                 </StackPanel>
             </DockPanel>
-
 
             <DataGrid Grid.Row="2" x:Name="table" Style="{StaticResource DefaultDataGrid}">
                 <DataGrid.RowStyle>
                     <Style TargetType="DataGridRow" BasedOn="{StaticResource {x:Static pu:StyleKeys.DataGridRowStyle}}">
                         <Style.Triggers>
-                            <DataTrigger Binding="{Binding type}" Value="warn">
+                            <DataTrigger Binding="{Binding level}" Value="Warn">
                                 <Setter Property="Background" Value="{StaticResource WarningColor}"/>
                                 <Setter Property="Foreground" Value="Black"/>
                             </DataTrigger>
-                            <DataTrigger Binding="{Binding type}" Value="error">
+                            <DataTrigger Binding="{Binding level}" Value="Error">
                                 <Setter Property="Background" Value="{StaticResource DangerColor}"/>
                                 <Setter Property="Foreground" Value="Black"/>
                             </DataTrigger>
@@ -100,13 +83,6 @@
                     </Style>
                 </DataGrid.RowStyle>
                 <DataGrid.Columns>
-                    <DataGridTemplateColumn Header="" Width="Auto" CanUserResize="False">
-                        <DataGridTemplateColumn.CellTemplate>
-                            <DataTemplate>
-                                <CheckBox VerticalAlignment="Center" IsChecked="{Binding Selected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
-                            </DataTemplate>
-                        </DataGridTemplateColumn.CellTemplate>
-                    </DataGridTemplateColumn>
                     <DataGridTemplateColumn Header="#" CanUserResize="False">
                         <DataGridTemplateColumn.CellTemplate>
                             <DataTemplate>
@@ -115,10 +91,26 @@
                             </DataTemplate>
                         </DataGridTemplateColumn.CellTemplate>
                     </DataGridTemplateColumn>
-                    <DataGridTextColumn Width="*" Binding="{Binding type}" Header="绾у埆" />
-                    <DataGridTextColumn Width="*" Binding="{Binding time}" Header="鏃堕棿" />
-                    <DataGridTextColumn Width="*" Binding="{Binding source}" Header="鏉ユ簮" />
-                    <DataGridTextColumn Width="*" Binding="{Binding text}" Header="鏂囨湰" />
+                    <DataGridTextColumn Width="*" Binding="{Binding level}">
+                        <DataGridTextColumn.Header>
+                            <TextBlock Text="{DynamicResource log.level}"/>
+                        </DataGridTextColumn.Header>
+                    </DataGridTextColumn>
+                    <DataGridTextColumn Width="*" Binding="{Binding time}">
+                        <DataGridTextColumn.Header>
+                            <TextBlock Text="{DynamicResource log.time}"/>
+                        </DataGridTextColumn.Header>
+                    </DataGridTextColumn>
+                    <DataGridTextColumn Width="*" Binding="{Binding source}">
+                        <DataGridTextColumn.Header>
+                            <TextBlock Text="{DynamicResource log.source}"/>
+                        </DataGridTextColumn.Header>
+                    </DataGridTextColumn>
+                    <DataGridTextColumn Width="*" Binding="{Binding text}">
+                        <DataGridTextColumn.Header>
+                            <TextBlock Text="{DynamicResource log.text}"/>
+                        </DataGridTextColumn.Header>
+                    </DataGridTextColumn>
                 </DataGrid.Columns>
             </DataGrid>
         </Grid>
diff --git a/HMI.LogViewer/LogViewer.xaml.cs b/HMI.LogViewer/LogViewer.xaml.cs
index 272d886..508aecf 100644
--- a/HMI.LogViewer/LogViewer.xaml.cs
+++ b/HMI.LogViewer/LogViewer.xaml.cs
@@ -5,6 +5,7 @@
 using System.Collections.Generic;
 using System.Data;
 using System.IO;
+using System.Linq;
 using System.Timers;
 using System.Windows;
 using System.Windows.Controls;
@@ -16,8 +17,6 @@
     /// </summary>
     public partial class LogViewer : PluginControl
     {
-        private string logDir;
-        private string todayLogFile;
 
         private bool autoRfh;
 
@@ -36,42 +35,34 @@
 
         public LogViewer()
         {
-            string logDir = Path.Combine(PathHelper.AppFolder, "Log");
-            todayLogFile = Path.Combine(logDir, $"log{DateTime.Now.ToString("yyyy-MM-dd")}.db");
-
-            if (!Directory.Exists(logDir))
-            {
-                Directory.CreateDirectory(logDir);
-            }
-            this.logDir = logDir;
             DataContext = this;
             InitializeComponent();
         }
 
         public override void InitFinish()
         {
-            base.InitFinish();
-            string baseFile = Path.Combine(logDir, "log.db");
+            //base.InitFinish();
+            //string baseFile = Path.Combine(logDir, "log.db");
 
-            try
-            {
-                if (!File.Exists(baseFile))
-                {
-                    SqliteHelper.CreateDatabase(baseFile);
-                }
-                SqliteHelper sqliteHelper = new SqliteHelper(baseFile);
-                sqliteHelper.ExecuteSql(@"CREATE TABLE IF NOT EXISTS log (
-                    id INTEGER PRIMARY KEY AUTOINCREMENT,
-                    type TEXT NOT NULL,
-                    text TEXT NOT NULL,
-                    source TEXT,
-                    time TIME
-                    );");
-            }
-            catch (Exception ex)
-            {
-                log.Error("Create Log File Failed.", ex);
-            }
+            //try
+            //{
+            //    if (!File.Exists(baseFile))
+            //    {
+            //        SqliteHelper.CreateDatabase(baseFile);
+            //    }
+            //    SqliteHelper sqliteHelper = new SqliteHelper(baseFile);
+            //    sqliteHelper.ExecuteSql(@"CREATE TABLE IF NOT EXISTS log (
+            //        id INTEGER PRIMARY KEY AUTOINCREMENT,
+            //        level TEXT NOT NULL,
+            //        text TEXT NOT NULL,
+            //        source TEXT,
+            //        time TIME
+            //        );");
+            //}
+            //catch (Exception ex)
+            //{
+            //    log.Error("Create Log File Failed.", ex);
+            //}
         }
 
 
@@ -113,16 +104,32 @@
         {
             try
             {
+                if (startTimeInput.SelectedDateTime == null && endTimeInput.SelectedDateTime == null)
+                {
+                    ToastHelper.Error(this.lang("log.timeCantBeNull"));
+                    return;
+                }
+
+                if (endTimeInput.SelectedDateTime != null && startTimeInput.SelectedDateTime != null &&
+                endTimeInput.SelectedDateTime < startTimeInput.SelectedDateTime)
+                {
+                    ToastHelper.Error(this.lang("log.timeRangeError"));
+                    return;
+                }
+
                 queryBtn.IsEnabled = false;
                 autoRfhInput.IsEnabled = false;
                 Filtrate();
-                queryBtn.IsEnabled = true;
-                autoRfhInput.IsEnabled = true;
             }
             catch (Exception ex)
             {
                 log.Error("Log Query Failed.", ex);
                 this.Error($"鏌ヨ澶辫触锛亄ex.Message}");
+            }
+            finally
+            {
+                queryBtn.IsEnabled = true;
+                autoRfhInput.IsEnabled = true;
             }
         }
 
@@ -139,16 +146,43 @@
         {
             string str = LoadSql();
             DataTable dt = new DataTable();
-            var files = Directory.GetFiles(logDir, "*.db");
+            var files = GetOldDbFiles(PathHelper.LogViewerDir, new DateTime?[] { startTimeInput.SelectedDateTime,
+                endTimeInput.SelectedDateTime });
             foreach (string file in files)
             {
                 SqliteHelper sql = new SqliteHelper(file);
                 dt.Merge(sql.GetDataTable(str));
                 sql.Close();
             }
-
             dt.DefaultView.Sort = "time Desc";
             table.ItemsSource = dt.DefaultView;
+        }
+
+        public static string[] GetOldDbFiles(string folderPath, DateTime?[] range)
+        {
+            if (!Directory.Exists(folderPath))
+            {
+                return new string[0];
+            }
+            // 鑾峰彇鎵�鏈�.db鏂囦欢骞剁瓫閫夊嚭7澶╁墠鏇存柊鐨勬枃浠�
+            var oldFiles = Directory.GetFiles(folderPath, "*.db");
+            try
+            {
+                oldFiles = oldFiles.Where(file =>
+                {
+                    var lastWriteTime = File.GetLastWriteTime(file);
+                    bool flag = true;
+                    if (range[0] != null) flag &= lastWriteTime >= range[0];
+                    if (range[1] != null) flag &= lastWriteTime <= range[1];
+                    return flag;
+                }).ToArray();
+
+                return oldFiles;
+            }
+            catch (Exception ex)
+            {
+                return new string[0];
+            }
         }
 
         /// <summary>
@@ -157,12 +191,11 @@
         /// <returns> str鏁版嵁搴撶殑璇彞 </returns>
         private string LoadSql()
         {
-            string sql = " select type,time,source,text from log";
-
+            string sql = " select level,time,source,text from log";
             List<string> datas = new List<string>();
             if (!string.IsNullOrEmpty(levelInput.Text))
             {
-                datas.Add($"type='{levelInput.Text}'");
+                datas.Add($"level='{levelInput.Text}'");
             }
             if (!string.IsNullOrEmpty(startTimeInput.Text))
             {
@@ -189,22 +222,22 @@
         /// </summary>
         private void Refresh100items()
         {
-            try
-            {
-                if (!File.Exists(todayLogFile)) return;
-                string str = "select type,time,source,text from log order by time desc limit 100";
-                SqliteHelper sql = new SqliteHelper(todayLogFile);
-                var dt = sql.GetDataTable(str);
-                this.Dispatcher.BeginInvoke((Action)delegate ()
-                {
-                    table.ItemsSource = dt.DefaultView;
-                });
-            }
-            catch (Exception ex)
-            {
-                log.Error("Log Auto Refresh Failed.", ex);
-                this.Error($"鑷姩鍒锋柊澶辫触锛亄ex.Message}");
-            }
+            //try
+            //{
+            //    if (!File.Exists(todayLogFile)) return;
+            //    string str = "select type,time,source,text from log order by time desc limit 100";
+            //    SqliteHelper sql = new SqliteHelper(todayLogFile);
+            //    var dt = sql.GetDataTable(str);
+            //    this.Dispatcher.BeginInvoke((Action)delegate ()
+            //    {
+            //        table.ItemsSource = dt.DefaultView;
+            //    });
+            //}
+            //catch (Exception ex)
+            //{
+            //    log.Error("Log Auto Refresh Failed.", ex);
+            //    this.Error($"鑷姩鍒锋柊澶辫触锛亄ex.Message}");
+            //}
         }
     }
 }
diff --git a/HMI.MenuManage/Entity/MenuData.cs b/HMI.MenuManage/Entity/MenuData.cs
index b555b54..ad1a017 100644
--- a/HMI.MenuManage/Entity/MenuData.cs
+++ b/HMI.MenuManage/Entity/MenuData.cs
@@ -22,6 +22,8 @@
         public string DllPath { get; set; }
         public string ClassName { get; set; }
 
+        public double? Sort { get; set; }
+
         private ObservableCollection<MenuData> _Children = new ObservableCollection<MenuData>();
 
         public ObservableCollection<MenuData> Children
diff --git a/HMI.MenuManage/MenuManage.xaml b/HMI.MenuManage/MenuManage.xaml
index 89888b6..234de24 100644
--- a/HMI.MenuManage/MenuManage.xaml
+++ b/HMI.MenuManage/MenuManage.xaml
@@ -17,8 +17,8 @@
              d:DesignHeight="450" d:DesignWidth="800">
     <Grid>
         <Grid.ColumnDefinitions>
-            <ColumnDefinition Width="7*"/>
-            <ColumnDefinition Width="3*"/>
+            <ColumnDefinition Width="8*"/>
+            <ColumnDefinition Width="2*"/>
         </Grid.ColumnDefinitions>
 
         <Border Style="{StaticResource CardBoarder}" Margin="0,0,2,0">
@@ -91,6 +91,11 @@
                                 <TextBlock Text="{DynamicResource HMI.Menu.ClassName}"/>
                             </DataGridTextColumn.Header>
                         </DataGridTextColumn>
+                        <DataGridTextColumn Width="50" Binding="{Binding Sort}">
+                            <DataGridTextColumn.Header>
+                                <TextBlock Text="{DynamicResource HMI.Menu.Sort}"/>
+                            </DataGridTextColumn.Header>
+                        </DataGridTextColumn>
                         <DataGridTemplateColumn Width="110" IsReadOnly="True" CanUserResize="False">
                             <DataGridTemplateColumn.Header>
                                 <TextBlock Text="{DynamicResource App.Opt}"/>
diff --git a/HMI.MenuManage/Modal/MenuModal.xaml b/HMI.MenuManage/Modal/MenuModal.xaml
index 8eac944..ce33c4e 100644
--- a/HMI.MenuManage/Modal/MenuModal.xaml
+++ b/HMI.MenuManage/Modal/MenuModal.xaml
@@ -30,7 +30,7 @@
 dp:FormGroupHelper.ValidateMethod="ValidateName">
                 <TextBox x:Name="TbName" Style="{StaticResource EditorTextBoxStyle}" />
             </pu:FormGroup>
-            
+
             <pu:FormGroup x:Name="FgIcon" Style="{StaticResource InputFormGroupStyle}"
                           Header="{DynamicResource HMI.Menu.Icon}" GroupName="MyFormGroup">
                 <Grid>
@@ -96,6 +96,11 @@
                           Header="{DynamicResource HMI.Menu.ClassName}" GroupName="MyFormGroup">
                 <TextBox x:Name="TbClassName" Style="{StaticResource EditorTextBoxStyle}" />
             </pu:FormGroup>
+
+            <pu:FormGroup x:Name="FgSort" Style="{StaticResource InputFormGroupStyle}" 
+              Header="{DynamicResource HMI.Menu.Sort}" GroupName="MyFormGroup">
+                <pu:NumberInput x:Name="NiSort" Style="{StaticResource FormNumberInput}" />
+            </pu:FormGroup>
         </StackPanel>
     </Grid>
 </pu:WindowX>
diff --git a/HMI.MenuManage/Modal/MenuModal.xaml.cs b/HMI.MenuManage/Modal/MenuModal.xaml.cs
index a961195..ad3953c 100644
--- a/HMI.MenuManage/Modal/MenuModal.xaml.cs
+++ b/HMI.MenuManage/Modal/MenuModal.xaml.cs
@@ -39,7 +39,6 @@
             set { Set(ref parentSource, value); }
         }
 
-
         protected override void OnClosing(CancelEventArgs e)
         {
             //Press Continue Button
@@ -118,6 +117,7 @@
                 Data.ParentCode = TbParentCode.SelectedValue?.ToString();
                 Data.DllPath = TbDllPath.Text;
                 Data.ClassName = TbClassName.Text;
+                Data.Sort = NiSort.Value;
             }
             return flag;
         }
@@ -154,6 +154,7 @@
             TbParentCode.SelectedValue = Data.ParentCode;
             TbDllPath.Text = Data.DllPath;
             TbClassName.Text = Data.ClassName;
+            NiSort.Value = Data.Sort;
             return this.ShowDialog();
         }
 
@@ -171,10 +172,10 @@
 
         public void InsertMenu(MenuData data)
         {
-            string sql = $@"INSERT INTO menu (code,name,icon,level,type,parentCode,dllPath,className) 
+            string sql = $@"INSERT INTO menu (code,name,icon,level,type,parentCode,dllPath,className,sort) 
                 VALUES
                 ('{data.Code}','{data.Name}','{data.Icon}','{data.Level}','{data.Type}','{data.ParentCode}',
-                '{data.DllPath}','{data.ClassName}');";
+                '{data.DllPath}','{data.ClassName}',{data.Sort});";
             sqliteHelper.ExecuteSql(sql);
         }
 
@@ -182,7 +183,7 @@
         {
             string sql = $@"UPDATE menu SET code='{data.Code}',name='{data.Name}',
 icon='{data.Icon}',level='{data.Level}',type='{data.Type}',parentCode='{data.ParentCode}',
-dllPath='{data.DllPath}',className='{data.ClassName}' WHERE id='{data.Id}';";
+dllPath='{data.DllPath}',className='{data.ClassName}',sort={data.Sort} WHERE id='{data.Id}';";
             sqliteHelper.ExecuteSql(sql);
         }
 
diff --git a/HMI.MenuManage/Model/MenuManageModel.cs b/HMI.MenuManage/Model/MenuManageModel.cs
index 42a57cd..95b9212 100644
--- a/HMI.MenuManage/Model/MenuManageModel.cs
+++ b/HMI.MenuManage/Model/MenuManageModel.cs
@@ -55,6 +55,7 @@
                     parentCode TEXT,
                     dllPath TEXT,
                     className TEXT,
+                    sort REAL,
                     UNIQUE(code)
                     );");
         }
@@ -63,7 +64,7 @@
         {
             ObservableCollection<MenuData> data = new ObservableCollection<MenuData>();
             ObservableCollection<MenuData> treeData = new ObservableCollection<MenuData>();
-            DataTable dt = sqliteHelper.GetDataTable("SELECT m1.*,m2.name AS parentName FROM menu AS m1 LEFT JOIN menu AS m2 ON m1.parentCode = m2.code Order By m1.id;");
+            DataTable dt = sqliteHelper.GetDataTable("SELECT m1.*,m2.name AS parentName FROM menu AS m1 LEFT JOIN menu AS m2 ON m1.parentCode = m2.code Order By m1.sort,m1.id;");
             Dictionary<string, MenuData> cache = new Dictionary<string, MenuData>();
             if (dt != null)
             {
@@ -80,7 +81,8 @@
                         ParentCode = dr["parentCode"].ToString(),
                         ParentName = dr["parentName"].ToString(),
                         DllPath = dr["dllPath"].ToString(),
-                        ClassName = dr["className"].ToString()
+                        ClassName = dr["className"].ToString(),
+                        Sort = dr["sort"] == DBNull.Value ? 0 : double.Parse(dr["sort"].ToString()),
                     };
                     data.Add(menu);
                     if (menu.Level == "1")
diff --git a/HMI.ParameterManage/HMI.ParameterManage.csproj b/HMI.ParameterManage/HMI.ParameterManage.csproj
index de2e7cd..c5043b6 100644
--- a/HMI.ParameterManage/HMI.ParameterManage.csproj
+++ b/HMI.ParameterManage/HMI.ParameterManage.csproj
@@ -78,7 +78,6 @@
     <Reference Include="PresentationFramework" />
   </ItemGroup>
   <ItemGroup>
-    <Compile Include="Entity\TestItemData.cs" />
     <Compile Include="TestItemModal.xaml.cs">
       <DependentUpon>TestItemModal.xaml</DependentUpon>
     </Compile>
@@ -98,8 +97,6 @@
       <DependentUpon>ProductModal.xaml</DependentUpon>
     </Compile>
     <Compile Include="Model\XmlModel.cs" />
-    <Compile Include="Util\TestItemManager.cs" />
-    <Compile Include="Util\ParameterManager.cs" />
     <Page Include="TestItemModal.xaml">
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
@@ -113,8 +110,6 @@
       <SubType>Designer</SubType>
     </Page>
     <Compile Include="Model\SqlModel.cs" />
-    <Compile Include="Entity\ParameterData.cs" />
-    <Compile Include="Entity\ProductData.cs" />
     <Compile Include="ParameterManage.xaml.cs">
       <DependentUpon>ParameterManage.xaml</DependentUpon>
       <SubType>Code</SubType>
@@ -178,6 +173,10 @@
       <Name>HMI.Util</Name>
     </ProjectReference>
   </ItemGroup>
+  <ItemGroup>
+    <Folder Include="Entity\" />
+    <Folder Include="Util\" />
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <PropertyGroup>
     <PostBuildEvent>
diff --git a/HMI.ParameterManage/Model/SqlModel.cs b/HMI.ParameterManage/Model/SqlModel.cs
index 037a083..2718a9b 100644
--- a/HMI.ParameterManage/Model/SqlModel.cs
+++ b/HMI.ParameterManage/Model/SqlModel.cs
@@ -1,9 +1,8 @@
-锘縰sing System.Collections.ObjectModel;
+锘縰sing HMI.Core.Entity;
+using HMI.Core.Util;
+using System.Collections.ObjectModel;
 using System.Data;
 using System.IO;
-using HMI.Core.Entity;
-using HMI.Core.Util;
-using HMI.ParameterManage.Entity;
 
 namespace HMI.ParameterManage.Model
 {
diff --git a/HMI.ParameterManage/Model/XmlModel.cs b/HMI.ParameterManage/Model/XmlModel.cs
index a70a4be..cf541bd 100644
--- a/HMI.ParameterManage/Model/XmlModel.cs
+++ b/HMI.ParameterManage/Model/XmlModel.cs
@@ -1,6 +1,7 @@
 锘縰sing HMI.Core.Entity;
+using HMI.Core.Manager;
 using HMI.Core.Util;
-using HMI.ParameterManage.Entity;
+using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.IO;
 using System.Linq;
@@ -9,17 +10,8 @@
 {
     public class XmlModel : NotifyBase
     {
-        private string ProductSettingFile;
-        private string ParameterSettingFolder;
-
         public XmlModel(string baseFolder)
         {
-            ProductSettingFile = Path.Combine(baseFolder, "Product.setting");
-            ParameterSettingFolder = Path.Combine(baseFolder, "Parameter");
-            if (!Directory.Exists(ParameterSettingFolder))
-            {
-                Directory.CreateDirectory(ParameterSettingFolder);
-            }
         }
 
         private ObservableCollection<ProductData> products;
@@ -91,30 +83,20 @@
 
         public void LoadProducts()
         {
-            ObservableCollection<ProductData> products;
-            if (File.Exists(ProductSettingFile))
-            {
-                products = XmlHelper.DeserializeFromXml<ObservableCollection<ProductData>>(ProductSettingFile);
-            }
-            else
-            {
-                products = new ObservableCollection<ProductData>();
-            }
+            ObservableCollection<ProductData> products = ProductManager.GetProducts<ObservableCollection<ProductData>>();
+            products = products ?? new ObservableCollection<ProductData>();
             Products = products;
         }
 
         public void LoadParameters()
         {
-            ObservableCollection<ParameterData> parameters = new ObservableCollection<ParameterData>();
+            ObservableCollection<ParameterData> parameters = null;
             if (SelectedProduct != null)
             {
-                string fileName = $"Parameter_{SelectedProduct.Name}.setting";
-                string parameterSettingFile = Path.Combine(ParameterSettingFolder, fileName);
-                if (File.Exists(parameterSettingFile))
-                {
-                    parameters = XmlHelper.DeserializeFromXml<ObservableCollection<ParameterData>>(parameterSettingFile);
-                }
+                parameters = ProductManager.GetParametersByProductName<ObservableCollection<ParameterData>>(SelectedProduct.Name);
+
             }
+            parameters = parameters ?? new ObservableCollection<ParameterData>();
             Parameters = parameters;
         }
 
@@ -123,11 +105,11 @@
             int index = Products.IndexOf(data);
             if (index > -1)
             {
-                UpdateData(index, data);
+                UpdateProduct(index, data);
             }
             else
             {
-                InsertData(data);
+                InsertProduct(data);
             }
         }
 
@@ -144,67 +126,119 @@
             }
         }
 
-        public void InsertData(ProductData data)
+        #region 浜у搧鎿嶄綔
+        /// <summary>
+        /// 鏂板浜у搧
+        /// </summary>
+        /// <param name="data"></param>
+        public void InsertProduct(ProductData data)
         {
             var temp = XmlHelper.DeepClone(Products);
             temp.Add(data);
-            XmlHelper.SerializeToXml(ProductSettingFile, temp);
+            SaveProduct(temp);
         }
 
-        public void UpdateData(int index, ProductData data)
+        /// <summary>
+        /// 鏇存柊浜у搧
+        /// </summary>
+        /// <param name="index"></param>
+        /// <param name="data"></param>
+        public void UpdateProduct(int index, ProductData data)
         {
             var temp = XmlHelper.DeepClone(Products);
             temp[index] = data;
-            XmlHelper.SerializeToXml(ProductSettingFile, temp);
+            SaveProduct(temp);
         }
 
-        public void InsertParameter(ParameterData data)
-        {
-            var temp = XmlHelper.DeepClone(Parameters);
-            temp.Add(data);
-            string fileName = Path.Combine(ParameterSettingFolder, $"Parameter_{SelectedProduct.Name}.setting");
-            XmlHelper.SerializeToXml(fileName, temp);
-        }
-
-        public void UpdateParameter(int index, ParameterData data)
-        {
-            var temp = XmlHelper.DeepClone(Parameters);
-            temp[index] = data;
-            string fileName = Path.Combine(ParameterSettingFolder, $"Parameter_{SelectedProduct.Name}.setting");
-            XmlHelper.SerializeToXml(fileName, temp);
-        }
-
+        /// <summary>
+        /// 鍒犻櫎浜у搧
+        /// </summary>
+        /// <param name="data"></param>
         public void DeleteProduct(ProductData data)
         {
             int index = Products.IndexOf(data);
             if (index > -1)
             {
                 var temp = Products.Where(p => p != data).ToList();
-                XmlHelper.SerializeToXml(ProductSettingFile, temp);
+                SaveProduct(temp);
             }
         }
+
+        /// <summary>
+        /// 鎵归噺鍒犻櫎浜у搧
+        /// </summary>
         public void DeleteProducts()
         {
             var temp = Products.Where(p => !p.IsSelected).ToList();
-            XmlHelper.SerializeToXml(ProductSettingFile, temp);
+            SaveProduct(temp);
         }
 
+        /// <summary>
+        /// 鎸佷箙鍖栦骇鍝佹暟鎹�
+        /// </summary>
+        /// <param name="datas"></param>
+        private void SaveProduct(IList<ProductData> datas)
+        {
+            XmlHelper.SerializeToXml(PathHelper.ProductDataFile, datas);
+        }
+        #endregion
+
+        #region 浜у搧鍙傛暟鎿嶄綔
+        /// <summary>
+        /// 鏂板鍙傛暟
+        /// </summary>
+        /// <param name="data"></param>
+        public void InsertParameter(ParameterData data)
+        {
+            var temp = XmlHelper.DeepClone(Parameters);
+            temp.Add(data);
+            SaveParameter(temp);
+        }
+
+        /// <summary>
+        /// 鏇存柊鍙傛暟
+        /// </summary>
+        /// <param name="index"></param>
+        /// <param name="data"></param>
+        public void UpdateParameter(int index, ParameterData data)
+        {
+            var temp = XmlHelper.DeepClone(Parameters);
+            temp[index] = data;
+            SaveParameter(temp);
+        }
+
+        /// <summary>
+        /// 鍒犻櫎鍙傛暟
+        /// </summary>
+        /// <param name="data"></param>
         public void DeleteParameter(ParameterData data)
         {
             int index = Parameters.IndexOf(data);
             if (index > -1)
             {
                 var temp = Parameters.Where(p => p != data).ToList();
-                string fileName = Path.Combine(ParameterSettingFolder, $"Parameter_{SelectedProduct.Name}.setting");
-                XmlHelper.SerializeToXml(fileName, temp);
+                SaveParameter(temp);
             }
         }
 
+        /// <summary>
+        /// 鎵归噺鍒犻櫎鍙傛暟
+        /// </summary>
         public void DeleteParameters()
         {
             var temp = Parameters.Where(p => !p.IsSelected).ToList();
-            string fileName = Path.Combine(ParameterSettingFolder, $"Parameter_{SelectedProduct.Name}.setting");
-            XmlHelper.SerializeToXml(fileName, temp);
+            SaveParameter(temp);
         }
+
+        /// <summary>
+        /// 鎸佷箙鍖栧弬鏁版暟鎹�
+        /// </summary>
+        /// <param name="datas"></param>
+        private void SaveParameter(IList<ParameterData> datas)
+        {
+            string fileName = Path.Combine(PathHelper.ProductDataFolder, "Parameter", $"Parameter_{SelectedProduct.Name}.data");
+            XmlHelper.SerializeToXml(fileName, datas);
+        }
+        #endregion
     }
 }
diff --git a/HMI.ParameterManage/ParameterManage.xaml.cs b/HMI.ParameterManage/ParameterManage.xaml.cs
index 8b6ee33..c844215 100644
--- a/HMI.ParameterManage/ParameterManage.xaml.cs
+++ b/HMI.ParameterManage/ParameterManage.xaml.cs
@@ -1,6 +1,6 @@
 锘縰sing HMI.Base;
+using HMI.Core.Entity;
 using HMI.Core.Util;
-using HMI.ParameterManage.Entity;
 using HMI.ParameterManage.Model;
 using HMI.UI.Util;
 using System.Linq;
@@ -14,7 +14,6 @@
     /// </summary>
     public partial class ParameterManage : PluginControl
     {
-        public static string FileFolder { get; private set; }
         private XmlModel Model;
 
         public ParameterManage()
@@ -25,7 +24,6 @@
         public override void InitFinish()
         {
             Model = new XmlModel(BaseFolder);
-            FileFolder = BaseFolder;
             DataContext = Model;
         }
 
diff --git a/HMI.ParameterManage/ParameterModal.xaml.cs b/HMI.ParameterManage/ParameterModal.xaml.cs
index 3009a76..4c19c35 100644
--- a/HMI.ParameterManage/ParameterModal.xaml.cs
+++ b/HMI.ParameterManage/ParameterModal.xaml.cs
@@ -1,12 +1,10 @@
-锘縰sing HMI.Core.Util;
-using HMI.ParameterManage.Entity;
+锘縰sing HMI.Core.Entity;
 using HMI.ParameterManage.Model;
 using HMI.UI.DpUtil;
 using HMI.UI.Util;
 using Panuon.WPF.UI;
 using System;
 using System.ComponentModel;
-using System.Windows.Interop;
 
 namespace HMI.ParameterManage
 {
diff --git a/HMI.ParameterManage/ParameterSetting.xaml b/HMI.ParameterManage/ParameterSetting.xaml
index c4e41b5..5a097b0 100644
--- a/HMI.ParameterManage/ParameterSetting.xaml
+++ b/HMI.ParameterManage/ParameterSetting.xaml
@@ -10,8 +10,7 @@
              xmlns:pu="https://opensource.panuon.com/wpf-ui"
              xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
              xmlns:cc="clr-namespace:HMI.Core;assembly=HMI.Core"
-             mc:Ignorable="d" 
-             Padding="10"
+             mc:Ignorable="d"
              Loaded="UserControl_Loaded"
              Background="{StaticResource WorkspaceBackground}"
              d:DesignHeight="450" d:DesignWidth="800">
@@ -24,10 +23,7 @@
                 <RowDefinition/>
             </Grid.RowDefinitions>
 
-            <TextBlock FontSize="{StaticResource TitleFontSize}" 
-                       VerticalAlignment="Center" HorizontalAlignment="Left" Text="{DynamicResource Pm.Setting.Title}"/>
-
-            <DockPanel Grid.Row="1" Cursor="">
+            <DockPanel Grid.Row="1">
                 <StackPanel DockPanel.Dock="Right" Style="{StaticResource ButtonGroup}">
                     <Button Style="{StaticResource AddBtn}"
                             x:Name="addBtn" Click="Button_Click"/>
@@ -40,17 +36,18 @@
                     <Button x:Name="importBtn" 
                             Style="{StaticResource ImportBtn}" Click="Button_Click"/>
                 </StackPanel>
+                <TextBlock FontSize="{StaticResource TitleFontSize}" 
+           VerticalAlignment="Center" HorizontalAlignment="Left" Text="{DynamicResource Pm.Setting.Title}"/>
 
-                <StackPanel Style="{StaticResource ButtonGroup}" HorizontalAlignment="Left">
+                <!--<StackPanel Style="{StaticResource ButtonGroup}" HorizontalAlignment="Left">
 
                     <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="{DynamicResource HMI.Line}"/>
                     <ComboBox Style="{StaticResource FormComboxStyle}" Width="160" x:Name="CbLine"/>
 
                     <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="{DynamicResource HMI.Station}"/>
                     <ComboBox Style="{StaticResource FormComboxStyle}" Width="160" x:Name="CbStation"/>
-                </StackPanel>
+                </StackPanel>-->
             </DockPanel>
-
 
             <DataGrid Grid.Row="2" x:Name="table" Style="{StaticResource DefaultDataGrid}"
                       ItemsSource="{Binding Datasource}">
diff --git a/HMI.ParameterManage/ParameterSetting.xaml.cs b/HMI.ParameterManage/ParameterSetting.xaml.cs
index 7db5211..b720d3a 100644
--- a/HMI.ParameterManage/ParameterSetting.xaml.cs
+++ b/HMI.ParameterManage/ParameterSetting.xaml.cs
@@ -1,7 +1,7 @@
 锘縰sing HMI.Base;
+using HMI.Core.Entity;
+using HMI.Core.Manager;
 using HMI.Core.Util;
-using HMI.ParameterManage.Entity;
-using HMI.ParameterManage.Util;
 using HMI.UI.Util;
 using HMI.Util;
 using Microsoft.Win32;
@@ -11,7 +11,6 @@
 using System.Linq;
 using System.Windows;
 using System.Windows.Controls;
-using System.Windows.Markup;
 
 namespace HMI.ParameterManage
 {
diff --git a/HMI.ParameterManage/ProductModal.xaml.cs b/HMI.ParameterManage/ProductModal.xaml.cs
index 29fbb58..bececf3 100644
--- a/HMI.ParameterManage/ProductModal.xaml.cs
+++ b/HMI.ParameterManage/ProductModal.xaml.cs
@@ -1,21 +1,10 @@
-锘縰sing System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.ComponentModel;
-using System.Linq;
-using System.Reflection;
-using System.Windows;
-using Microsoft.Win32;
-using Panuon.WPF.UI;
-using HMI.Core.Annotation;
-using HMI.Core.Entity;
-using HMI.Core.Exceptions;
-using HMI.Core.Util;
-using HMI.ParameterManage.Entity;
-using HMI.UI;
+锘縰sing HMI.Core.Entity;
 using HMI.ParameterManage.Model;
 using HMI.UI.DpUtil;
 using HMI.UI.Util;
+using Panuon.WPF.UI;
+using System;
+using System.ComponentModel;
 
 namespace HMI.ParameterManage
 {
diff --git a/HMI.ParameterManage/SettingModal.xaml b/HMI.ParameterManage/SettingModal.xaml
index 97ead45..ab004fa 100644
--- a/HMI.ParameterManage/SettingModal.xaml
+++ b/HMI.ParameterManage/SettingModal.xaml
@@ -58,23 +58,13 @@
             <pu:FormGroup x:Name="FgGroup" Style="{StaticResource InputFormGroupStyle}"
                           Header="{DynamicResource Pm.Parameter.Group}" 
                           GroupName="MyFormGroup">
-                <ComboBox x:Name="CbGroup" Style="{StaticResource FormComboxStyle}" SelectedValuePath="Content">
-                    <ComboBoxItem>Int</ComboBoxItem>
-                    <ComboBoxItem>Double</ComboBoxItem>
-                    <ComboBoxItem>Bool</ComboBoxItem>
-                    <ComboBoxItem>String</ComboBoxItem>
-                </ComboBox>
+                <TextBox x:Name="TbGroup" Style="{StaticResource EditorTextBoxStyle}"/>
             </pu:FormGroup>
 
             <pu:FormGroup x:Name="FgSource" Style="{StaticResource InputFormGroupStyle}" 
 Header="{DynamicResource Pm.Parameter.Source}" 
 dp:FormGroupHelper.IsRequired="False" GroupName="MyFormGroup">
-                <ComboBox x:Name="CbSource" Style="{StaticResource FormComboxStyle}" SelectedValuePath="Content">
-                    <ComboBoxItem>Int</ComboBoxItem>
-                    <ComboBoxItem>Double</ComboBoxItem>
-                    <ComboBoxItem>Bool</ComboBoxItem>
-                    <ComboBoxItem>String</ComboBoxItem>
-                </ComboBox>
+                <TextBox x:Name="TbSource" Style="{StaticResource EditorTextBoxStyle}"/>
             </pu:FormGroup>
         </StackPanel>
     </Grid>
diff --git a/HMI.ParameterManage/SettingModal.xaml.cs b/HMI.ParameterManage/SettingModal.xaml.cs
index a28c106..edd574b 100644
--- a/HMI.ParameterManage/SettingModal.xaml.cs
+++ b/HMI.ParameterManage/SettingModal.xaml.cs
@@ -1,5 +1,5 @@
-锘縰sing HMI.Core.Util;
-using HMI.ParameterManage.Entity;
+锘縰sing HMI.Core.Entity;
+using HMI.Core.Util;
 using HMI.UI.DpUtil;
 using HMI.UI.Util;
 using Panuon.WPF.UI;
@@ -90,8 +90,8 @@
                 Data.Value = TbValue.Text;
                 Data.Unit = TbUnit.Text;
                 Data.Description = TbDescription.Text;
-                Data.PGroup = CbGroup.Text;
-                Data.Source = CbSource.Text;
+                Data.PGroup = TbGroup.Text;
+                Data.Source = TbSource.Text;
             }
             return flag;
         }
@@ -105,8 +105,8 @@
             TbValue.Text = Data.Value;
             TbUnit.Text = Data.Unit;
             TbDescription.Text = Data.Description;
-            CbGroup.SelectedValue = Data.PGroup;
-            CbSource.SelectedValue = Data.Source;
+            TbGroup.Text = Data.PGroup;
+            TbSource.Text = Data.Source;
             return this.ShowDialog();
         }
     }
diff --git a/HMI.ParameterManage/TestItem.xaml.cs b/HMI.ParameterManage/TestItem.xaml.cs
index d693447..f6c1308 100644
--- a/HMI.ParameterManage/TestItem.xaml.cs
+++ b/HMI.ParameterManage/TestItem.xaml.cs
@@ -1,14 +1,13 @@
 锘縰sing HMI.Base;
+using HMI.Core.Entity;
+using HMI.Core.Manager;
 using HMI.Core.Util;
-using HMI.ParameterManage.Entity;
-using HMI.ParameterManage.Util;
 using HMI.UI.Util;
 using HMI.Util;
 using Microsoft.Win32;
 using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
-using System.IO;
 using System.Linq;
 using System.Windows;
 
@@ -54,14 +53,7 @@
         public static string FileFolder { get; private set; }
         public override void Active()
         {
-            if (!isActivated)
-            {
-                string folder = Path.Combine(ParameterManage.FileFolder, "TestItem");
-                PathHelper.InitFolder(folder);
-                base.SetBaseFolder(folder);
-                FileFolder = folder;
-                isActivated = true;
-            }
+           
         }
 
         private void Button_Click(object sender, RoutedEventArgs e)
@@ -305,7 +297,7 @@
             try
             {
                 ObservableCollection<ProductData> products =
-                ParameterManager.GetProducts<ObservableCollection<ProductData>>()
+                ProductManager.GetProducts<ObservableCollection<ProductData>>()
                 ?? new ObservableCollection<ProductData>();
                 Products = products;
             }
diff --git a/HMI.ParameterManage/TestItemModal.xaml.cs b/HMI.ParameterManage/TestItemModal.xaml.cs
index 735a000..323a585 100644
--- a/HMI.ParameterManage/TestItemModal.xaml.cs
+++ b/HMI.ParameterManage/TestItemModal.xaml.cs
@@ -1,5 +1,5 @@
-锘縰sing HMI.Core.Util;
-using HMI.ParameterManage.Entity;
+锘縰sing HMI.Core.Entity;
+using HMI.Core.Util;
 using HMI.UI.DpUtil;
 using HMI.UI.Util;
 using Panuon.WPF.UI;
diff --git a/HMI.ParameterManage/Util/ParameterManager.cs b/HMI.ParameterManage/Util/ParameterManager.cs
deleted file mode 100644
index 32e3115..0000000
--- a/HMI.ParameterManage/Util/ParameterManager.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-锘縰sing HMI.Core.Util;
-using HMI.ParameterManage.Entity;
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.IO;
-
-namespace HMI.ParameterManage.Util
-{
-    /// <summary>
-    /// TODO 浼樺寲鏁版嵁婧愶紝闇�瑕佹敮鎸乻qlite鍜寈ml
-    /// </summary>
-    public class ParameterManager
-    {
-        /// <summary>
-        /// 鑾峰彇淇濆瓨鐨勪骇鍝�
-        /// </summary>
-        /// <returns></returns>
-        public static T GetProducts<T>() where T : IList<ProductData>
-        {
-            string path = Path.Combine(ParameterManage.FileFolder, "Product.setting");
-            if (!File.Exists(path)) return default(T);
-            T datas = XmlHelper.DeserializeFromXml<T>(path);
-            return datas;
-        }
-
-        /// <summary>
-        /// 鏍规嵁浜у搧鍚嶇О鑾峰彇浜у搧鍙傛暟
-        /// </summary>
-        /// <param name="productName"></param>
-        /// <returns></returns>
-        public static List<ParameterData> GetParametersByProductName(string productName)
-        {
-            string path = Path.Combine(ParameterManage.FileFolder, "Parameter", $"Parameter_{productName}.setting");
-            if (!File.Exists(path)) return new List<ParameterData>();
-            List<ParameterData> datas = XmlHelper.DeserializeFromXml<List<ParameterData>>(path);
-            return datas;
-        }
-
-        /// <summary>
-        /// 鑾峰彇閫氱敤鍙傛暟
-        /// </summary>
-        /// <typeparam name="T"></typeparam>
-        /// <returns></returns>
-        public static T GetCommonParameter<T>() where T : IList<ParameterData>
-        {
-            string path = Path.Combine(ParameterManage.FileFolder, "CommonParameter.setting");
-            if (!File.Exists(path)) return default(T);
-            T datas = XmlHelper.DeserializeFromXml<T>(path);
-            return datas;
-        }
-
-        /// <summary>
-        /// 淇濆瓨閫氱敤鍙傛暟
-        /// </summary>
-        /// <param name="datas"></param>
-        /// <exception cref="Exception"></exception>
-        public static void SaveCommonParameter(IList<ParameterData> datas)
-        {
-            if (datas == null)
-            {
-                throw new Exception("涓嶈兘涓簄ull");
-            }
-            string path = Path.Combine(ParameterManage.FileFolder, "CommonParameter.setting");
-            XmlHelper.SerializeToXml(path, datas);
-        }
-
-    }
-}
diff --git a/HMI.SettingControl/SystemConfig.xaml b/HMI.SettingControl/SystemConfig.xaml
index 9a14cfc..4ad1199 100644
--- a/HMI.SettingControl/SystemConfig.xaml
+++ b/HMI.SettingControl/SystemConfig.xaml
@@ -57,6 +57,11 @@
                     <ComboBox x:Name="CbLang" Style="{StaticResource FormComboxStyle}" 
                               SelectedValue="{Binding CurrentLanguage}" SelectedValuePath="Code" DisplayMemberPath="Name"/>
                 </pu:FormGroup>
+
+                <pu:FormGroup Header="{DynamicResource Setting.LogExpiredDays}" HeaderWidth="Auto" GroupName="MyFormGroup">
+                    <pu:NumberInput Interval="1" x:Name="NiLogExpiredDays" Style="{StaticResource FormNumberInput}" 
+                                    Value="{Binding LogExpiredDays}"/>
+                </pu:FormGroup>
             </StackPanel>
         </GroupBox>
 
diff --git a/HMI.UI/Controls/KeyboradWindow.xaml b/HMI.UI/Controls/KeyboradWindow.xaml
new file mode 100644
index 0000000..a7dc671
--- /dev/null
+++ b/HMI.UI/Controls/KeyboradWindow.xaml
@@ -0,0 +1,234 @@
+锘�<pu:WindowX x:Class="HMI.UI.KeyboardWindow"
+            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.UI"
+            xmlns:pw="https://opensource.panuon.com/wpf"
+            xmlns:pu="https://opensource.panuon.com/wpf-ui"
+            xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
+            xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI"
+            xmlns:dp="clr-namespace:HMI.UI.DpUtil"
+            mc:Ignorable="d"
+            Title="NumberPanel"
+            SizeToContent="Height"
+            FontSize="14"
+            FontWeight="Bold"
+            WindowStartupLocation="CenterOwner"
+            pu:WindowXModalDialog.Buttons="{x:Null}"
+            Style="{StaticResource ModalWindow}">
+    <Window.Resources>
+        <Style TargetType="Button" BasedOn="{StaticResource PrimaryButton}"/>
+    </Window.Resources>
+    
+    <Grid Margin="10" Height="600">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="*"/>
+        </Grid.RowDefinitions>
+
+        <!-- 杈撳叆妗� -->
+        <TextBox x:Name="inputTextBox" 
+                 Style="{StaticResource EditorTextBoxStyle}"
+                 Grid.Row="0"
+                 Margin="0,0,0,10"
+                 VerticalScrollBarVisibility="Auto"
+                 FontSize="16"/>
+
+        <!-- 閿洏甯冨眬 -->
+        <Grid Grid.Row="1">
+            <Grid.RowDefinitions>
+                <RowDefinition Height="*"/>
+                <RowDefinition Height="*"/>
+                <RowDefinition Height="*"/>
+                <RowDefinition Height="*"/>
+                <RowDefinition Height="*"/>
+                <RowDefinition Height="*"/>
+            </Grid.RowDefinitions>
+
+            <Grid Grid.Row="0">
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                </Grid.ColumnDefinitions>
+                <Button Grid.Column="0" Content="(" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="1" Content=")" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="2" Content="-" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="3" Content="+" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="4" Content="{}{" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="5" Content="}" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="6" Content="|" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="7" Content=":" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="8" Content="&quot;" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="9" Content="~" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="10" Content="@" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="11" Content="#" Click="KeyButton_Click" Margin="1"/>
+            </Grid>
+
+            <!-- 绗竴琛岋細鏁板瓧閿拰鐗规畩瀛楃 -->
+            <Grid Grid.Row="1">
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                </Grid.ColumnDefinitions>
+                <Button Grid.Column="0" Content="`" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="1" Content="1" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="2" Content="2" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="3" Content="3" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="4" Content="4" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="5" Content="5" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="6" Content="6" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="7" Content="7" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="8" Content="8" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="9" Content="9" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="10" Content="0" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="11" Content="Backspace" Click="BackspaceButton_Click" Grid.ColumnSpan="2" Margin="1"/>
+            </Grid>
+
+            <!-- 绗簩琛岋細瀛楁瘝閿� -->
+            <Grid Grid.Row="2">
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                </Grid.ColumnDefinitions>
+                <Button Grid.Column="0" Content="Q" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="1" Content="W" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="2" Content="E" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="3" Content="R" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="4" Content="T" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="5" Content="Y" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="6" Content="U" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="7" Content="I" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="8" Content="O" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="9" Content="P" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="10" Content="[" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="11" Content="]" Click="KeyButton_Click" Margin="1"/>
+            </Grid>
+
+            <!-- 绗笁琛岋細瀛楁瘝閿� -->
+            <Grid Grid.Row="3">
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                </Grid.ColumnDefinitions>
+                <Button Grid.Column="0" Content="A" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="1" Content="S" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="2" Content="D" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="3" Content="F" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="4" Content="G" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="5" Content="H" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="6" Content="J" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="7" Content="K" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="8" Content="L" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="9" Content=";" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="10" Content="'" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="11" Content="Enter" Click="EnterButton_Click" Grid.ColumnSpan="2" Margin="1"/>
+            </Grid>
+
+            <!-- 绗洓琛岋細瀛楁瘝閿� -->
+            <Grid Grid.Row="4">
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                </Grid.ColumnDefinitions>
+                <Button Grid.Column="0" Content="Z" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="1" Content="X" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="2" Content="C" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="3" Content="V" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="4" Content="B" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="5" Content="N" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="6" Content="M" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="7" Content="," Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="8" Content="." Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="9" Content="/" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="10" Content="=" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="11" Content="\" Click="KeyButton_Click" Margin="1"/>
+            </Grid>
+
+            <!-- 绗簲琛岋細鍔熻兘閿拰鐗规畩瀛楃 -->
+            <Grid Grid.Row="5">
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                </Grid.ColumnDefinitions>
+                <Button Grid.Column="0" Content="Caps" Click="CapsButton_Click" Grid.ColumnSpan="2" Margin="1">
+                    <Button.Style>
+                        <Style TargetType="Button" BasedOn="{StaticResource PrimaryButton}">
+                            <Style.Triggers>
+                                <DataTrigger Binding="{Binding IsCapsLockOn}" Value="True">
+                                    <Setter Property="Background" Value="{StaticResource GreenVerBar}"/>
+                                </DataTrigger>
+                            </Style.Triggers>
+                        </Style>
+                    </Button.Style>
+                </Button>
+                <Button Grid.Column="2" Content="Space" Click="SpaceButton_Click" Grid.ColumnSpan="4" Margin="1"/>
+                <Button Grid.Column="6" Content="&amp;" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="7" Content="*" Click="KeyButton_Click" Margin="1"/>
+                <Button Grid.Column="8" Content="Clear" Click="ClearButton_Click" Grid.ColumnSpan="2" Margin="1"/>
+                <Button Grid.Column="10" Content="Cancel" Style="{StaticResource DangerButton}" Click="Cancel_Click" Margin="1"/>
+                <Button Grid.Column="11" Content="OK" Style="{StaticResource SuccessButton}" Click="Ok_Click" Margin="1"/>
+            </Grid>
+        </Grid>
+    </Grid>
+</pu:WindowX>
diff --git a/HMI.UI/Controls/KeyboradWindow.xaml.cs b/HMI.UI/Controls/KeyboradWindow.xaml.cs
new file mode 100644
index 0000000..00e929a
--- /dev/null
+++ b/HMI.UI/Controls/KeyboradWindow.xaml.cs
@@ -0,0 +1,110 @@
+锘縰sing Panuon.WPF.UI;
+using System;
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+
+namespace HMI.UI
+{
+    public partial class KeyboardWindow : WindowX
+    {
+        private bool _isCapsLockOn = false;
+
+        public bool IsCapsLockOn
+        {
+            get => _isCapsLockOn;
+            set
+            {
+                Set(ref _isCapsLockOn, value);
+            }
+        }
+
+        public KeyboardWindow()
+        {
+            DataContext = this;
+            InitializeComponent();
+        }
+
+        public bool? Open(string data)
+        {
+            Data = data;
+            inputTextBox.Text = data;
+            return this.ShowDialog();
+        }
+
+        public string Data { get; private set; }
+
+        private void KeyButton_Click(object sender, RoutedEventArgs e)
+        {
+            if (sender is Button button)
+            {
+                string key = button.Content.ToString();
+                if (_isCapsLockOn && IsLetter(key))
+                {
+                    key = key.ToUpper();
+                }
+                else
+                {
+                    key = key.ToLower();
+                }
+                InsertText(key);
+            }
+        }
+
+        private bool IsLetter(string key)
+        {
+            return key.Length == 1 && char.IsLetter(key[0]);
+        }
+
+        private void InsertText(string text)
+        {
+            inputTextBox.Text += text;
+            inputTextBox.Focus();
+        }
+
+        private void BackspaceButton_Click(object sender, RoutedEventArgs e)
+        {
+            if (inputTextBox.Text.Length > 0)
+            {
+                inputTextBox.Text = inputTextBox.Text.Substring(0, inputTextBox.Text.Length - 1);
+            }
+            inputTextBox.Focus();
+        }
+
+        private void ClearButton_Click(object sender, RoutedEventArgs e)
+        {
+            inputTextBox.Clear();
+            inputTextBox.Focus();
+        }
+
+        private void EnterButton_Click(object sender, RoutedEventArgs e)
+        {
+            //InsertText(Environment.NewLine);
+        }
+
+        private void SpaceButton_Click(object sender, RoutedEventArgs e)
+        {
+            InsertText(" ");
+        }
+
+        private void CapsButton_Click(object sender, RoutedEventArgs e)
+        {
+            IsCapsLockOn = !IsCapsLockOn;
+        }
+
+        private void Cancel_Click(object sender, RoutedEventArgs e)
+        {
+            Data = string.Empty;
+            DialogResult = false;
+            this.Close();
+        }
+
+        private void Ok_Click(object sender, RoutedEventArgs e)
+        {
+            Data = inputTextBox.Text;
+            DialogResult = true;
+            this.Close();
+        }
+    }
+}
diff --git a/HMI.UI/Custom/NumberInput.cs b/HMI.UI/Controls/NumberInput.cs
similarity index 100%
rename from HMI.UI/Custom/NumberInput.cs
rename to HMI.UI/Controls/NumberInput.cs
diff --git a/Plugin.ServoManage/NumberPanel.xaml b/HMI.UI/Controls/NumberPanel.xaml
similarity index 92%
rename from Plugin.ServoManage/NumberPanel.xaml
rename to HMI.UI/Controls/NumberPanel.xaml
index f7a6548..3a42b00 100644
--- a/Plugin.ServoManage/NumberPanel.xaml
+++ b/HMI.UI/Controls/NumberPanel.xaml
@@ -1,16 +1,16 @@
-锘�<pu:WindowX x:Class="Plugin.ServoManage.NumberPanel"
+锘�<pu:WindowX x:Class="HMI.UI.NumberPanel"
             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.ServoManage"
+            xmlns:local="clr-namespace:HMI.UI"
             xmlns:pw="https://opensource.panuon.com/wpf"
             xmlns:pu="https://opensource.panuon.com/wpf-ui"
             xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
             xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI"
-            xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
+            xmlns:dp="clr-namespace:HMI.UI.DpUtil"
             mc:Ignorable="d"
-            Title="Form"
+            Title="NumberPanel"
             SizeToContent="Height"
             Width="500"
             FontSize="14"
@@ -40,7 +40,7 @@
             <ColumnDefinition/>
         </Grid.ColumnDefinitions>
 
-        <ui:NumberInput Grid.ColumnSpan="4" BorderThickness="1"
+        <local:NumberInput Grid.ColumnSpan="4" BorderThickness="1"
                         BorderBrush="Black"
                         Style="{StaticResource EditorTextBoxStyle}"
                         x:Name="NiNumber"/>
diff --git a/Plugin.ServoManage/NumberPanel.xaml.cs b/HMI.UI/Controls/NumberPanel.xaml.cs
similarity index 94%
rename from Plugin.ServoManage/NumberPanel.xaml.cs
rename to HMI.UI/Controls/NumberPanel.xaml.cs
index 4391274..abdd3df 100644
--- a/Plugin.ServoManage/NumberPanel.xaml.cs
+++ b/HMI.UI/Controls/NumberPanel.xaml.cs
@@ -1,16 +1,9 @@
-锘縰sing HMI.Core.Util;
-using HMI.UI.DpUtil;
-using HMI.UI.Util;
-using Panuon.WPF.UI;
-using Plugin.ServoManage.Entity;
+锘縰sing Panuon.WPF.UI;
 using System;
-using System.Collections.ObjectModel;
 using System.ComponentModel;
-using System.Linq;
-using System.Reflection;
 using System.Windows.Controls;
 
-namespace Plugin.ServoManage
+namespace HMI.UI
 {
     /// <summary>
     /// ModalWindow.xaml 鐨勪氦浜掗�昏緫
diff --git a/HMI.UI/HMI.UI.csproj b/HMI.UI/HMI.UI.csproj
index 0034e0a..62ae02c 100644
--- a/HMI.UI/HMI.UI.csproj
+++ b/HMI.UI/HMI.UI.csproj
@@ -80,11 +80,17 @@
     <Reference Include="PresentationFramework" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="Controls\KeyboradWindow.xaml.cs">
+      <DependentUpon>KeyboradWindow.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Controls\NumberPanel.xaml.cs">
+      <DependentUpon>NumberPanel.xaml</DependentUpon>
+    </Compile>
     <Compile Include="Converter\VisibilityBoolConverter.cs" />
     <Compile Include="Converter\DataGridRowFlagConverter.cs" />
     <Compile Include="Converter\DataGridRowNumberConverter.cs" />
     <Compile Include="Converter\PathImageConverter.cs" />
-    <Compile Include="Custom\NumberInput.cs" />
+    <Compile Include="Controls\NumberInput.cs" />
     <Compile Include="DpUtil\DataGridHelper.cs" />
     <Compile Include="DpUtil\DataGridTextColumnHelper.cs" />
     <Compile Include="DpUtil\FormGroupHelper.cs" />
@@ -108,6 +114,14 @@
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
     </Resource>
+    <Page Include="Controls\KeyboradWindow.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+    <Page Include="Controls\NumberPanel.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
     <Page Include="MaskWindow.xaml">
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
diff --git a/HMI.UI/Style/FormStyle.xaml b/HMI.UI/Style/FormStyle.xaml
index 3bda03a..1043030 100644
--- a/HMI.UI/Style/FormStyle.xaml
+++ b/HMI.UI/Style/FormStyle.xaml
@@ -67,7 +67,7 @@
         <Setter Property="pu:TextBoxHelper.FocusedWatermarkForeground" Value="#AAAAAA" />
         <Setter Property="Height" Value="35" />
         <Setter Property="Padding" Value="5,0" />
-        <Setter Property="Background" Value="Transparent" />
+        <Setter Property="Background" Value="White" />
         <Setter Property="Foreground" Value="Black" />
         <Setter Property="VerticalContentAlignment" Value="Center"/>
         <Style.Triggers>
@@ -109,14 +109,24 @@
 
             </Style>
         </Style.Resources>
+        <Setter Property="HeaderTemplate">
+            <Setter.Value>
+                <DataTemplate>
+                    <Border Margin="5,0,5,0">
+                        <ContentControl Content="{Binding}" />
+                    </Border>
+                </DataTemplate>
+            </Setter.Value>
+        </Setter>
+
         <Setter Property="HeaderWidth" Value="Auto" />
         <Setter Property="Orientation" Value="Horizontal" />
         <Setter Property="Margin" Value="0,10,0,0" />
     </Style>
 
     <DataTemplate x:Key="RequiredFormHeader">
-        <StackPanel Orientation="Horizontal">
-            <TextBlock Text="*" Foreground="Red" />
+        <StackPanel Orientation="Horizontal" Margin="0,0,5,0">
+            <TextBlock Text="*" Width="5" Foreground="Red" />
             <ContentControl Content="{Binding}" />
         </StackPanel>
     </DataTemplate>
diff --git a/HMI.Util/ExcelHelper.cs b/HMI.Util/ExcelHelper.cs
index 7a6f416..b2ee156 100644
--- a/HMI.Util/ExcelHelper.cs
+++ b/HMI.Util/ExcelHelper.cs
@@ -1,4 +1,6 @@
-锘縰sing NPOI.HSSF.UserModel;
+锘縰sing HMI.Core;
+using HMI.Core.Util;
+using NPOI.HSSF.UserModel;
 using NPOI.SS.UserModel;
 using NPOI.XSSF.UserModel;
 using System;
@@ -33,7 +35,7 @@
             IRow row1 = SheetOne.CreateRow(0);
             for (int i = 0; i < properties.Count; i++)
             {
-                row1.CreateCell(i).SetCellValue((attrs[i]).Name);
+                row1.CreateCell(i).SetCellValue(LangUtil.Get((attrs[i]).Name));
             }
 
             for (int i = 0; i < datas.Count(); i++)
@@ -64,7 +66,7 @@
             Dictionary<string, PropertyInfo> dict = new Dictionary<string, PropertyInfo>();
             for (int i = 0; i < attrs.Count; i++)
             {
-                dict.Add(attrs[i].Name, properties[i]);
+                dict.Add(LangUtil.Get(attrs[i].Name), properties[i]);
             }
             using (FileStream fileStream1 = new FileStream(sourcePath, FileMode.Open, FileAccess.Read))
             {
@@ -105,23 +107,26 @@
                                     value = row.GetCell(j).BooleanCellValue;
 
                                 var prop = dict[title];
-                                if (prop.PropertyType is string)
+                                if (prop.PropertyType == typeof(string))
                                 {
                                     value = Convert.ToString(value);
                                 }
-                                else if (prop.PropertyType is double || prop.PropertyType is double?)
+                                else if (prop.PropertyType == typeof(double) || prop.PropertyType == typeof(double?))
                                 {
                                     value = Convert.ToDouble(value);
                                 }
-                                else if (prop.PropertyType is int || prop.PropertyType is int?)
+                                else if (prop.PropertyType == typeof(int) || prop.PropertyType == typeof(int?))
                                 {
                                     value = Convert.ToInt16(value);
                                 }
-                                else if (prop.PropertyType is bool || prop.PropertyType is bool?)
+                                else if (prop.PropertyType == typeof(bool) || prop.PropertyType == typeof(bool?))
                                 {
                                     value = Convert.ToBoolean(value);
                                 }
-
+                                else if (prop.PropertyType.IsEnum)
+                                {
+                                    value = Enum.Parse(prop.PropertyType, value.ToString());
+                                }
                                 prop.SetValue(obj, value);
                                 isEmpty = false;
                             }
diff --git a/HMI.Util/HMI.Util.csproj b/HMI.Util/HMI.Util.csproj
index 6c81f83..fbad757 100644
--- a/HMI.Util/HMI.Util.csproj
+++ b/HMI.Util/HMI.Util.csproj
@@ -75,7 +75,6 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="ExcelHelper.cs" />
-    <Compile Include="ExcelColumn.cs" />
     <Compile Include="IniFileHelper.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="PropertyHelper.cs" />
@@ -83,5 +82,11 @@
   <ItemGroup>
     <None Include="packages.config" />
   </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\HMI.Core\HMI.Core.csproj">
+      <Project>{3e3bf44a-0b69-45c5-9512-528cb48a7106}</Project>
+      <Name>HMI.Core</Name>
+    </ProjectReference>
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
 </Project>
\ No newline at end of file
diff --git a/Plugin.DOControl/ControlList.xaml b/Plugin.DOControl/ControlList.xaml
new file mode 100644
index 0000000..118b5b7
--- /dev/null
+++ b/Plugin.DOControl/ControlList.xaml
@@ -0,0 +1,120 @@
+锘�<pu:WindowX x:Class="Plugin.DOControl.ControlList"
+            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.DOControl"
+            xmlns:pw="https://opensource.panuon.com/wpf"
+            xmlns:pu="https://opensource.panuon.com/wpf-ui"
+            xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
+            mc:Ignorable="d"
+            Title="Form"
+            Height="800"
+            SizeToContent="Manual"
+            WindowStartupLocation="CenterOwner"
+            Style="{StaticResource ModalWindow}">
+    <Grid Margin="10" dp:GridHelper.Space="10">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition/>
+        </Grid.RowDefinitions>
+        
+        <DockPanel Grid.Row="0">
+            <StackPanel DockPanel.Dock="Right" Style="{StaticResource ButtonGroup}">
+                <Button Style="{StaticResource AddBtn}"
+                            x:Name="addBtn" Click="Button_Click"/>
+                <Button Style="{StaticResource RfhBtn}"
+                            x:Name="refreshBtn" Click="Button_Click"/>
+
+                <Button Style="{StaticResource DelBtn}" 
+                        x:Name="delBatchBtn" Click="Button_Click"/>
+
+                <Button x:Name="exportBtn"
+                            Style="{StaticResource ExportBtn}" Click="Button_Click"/>
+                <Button x:Name="importBtn" Style="{StaticResource ImportBtn}" Click="Button_Click"/>
+            </StackPanel>
+            <TextBlock FontSize="{StaticResource TitleFontSize}" 
+VerticalAlignment="Center" HorizontalAlignment="Left" Text="{DynamicResource doc.list}"/>
+        </DockPanel>
+
+
+        <DataGrid Grid.Row="2" x:Name="table" Style="{StaticResource DefaultDataGrid}" ItemsSource="{Binding Datasource}">
+            <DataGrid.Columns>
+                <DataGridTemplateColumn Header="" Width="Auto" CanUserResize="False">
+                    <DataGridTemplateColumn.CellTemplate>
+                        <DataTemplate>
+                            <CheckBox VerticalAlignment="Center" IsChecked="{Binding IsSelected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
+                        </DataTemplate>
+                    </DataGridTemplateColumn.CellTemplate>
+                </DataGridTemplateColumn>
+                <DataGridTemplateColumn Header="#" 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>
+                <DataGridTextColumn Width="*" Binding="{Binding Group}">
+                    <DataGridTextColumn.Header>
+                        <TextBlock Text="{DynamicResource doc.group}"/>
+                    </DataGridTextColumn.Header>
+                </DataGridTextColumn>
+                <DataGridTextColumn Width="*" Binding="{Binding Name}">
+                    <DataGridTextColumn.Header>
+                        <TextBlock Text="{DynamicResource doc.name}"/>
+                    </DataGridTextColumn.Header>
+                </DataGridTextColumn>
+
+                <DataGridTextColumn Width="*" Binding="{Binding Address}">
+                    <DataGridTextColumn.Header>
+                        <TextBlock Text="{DynamicResource doc.address}"/>
+                    </DataGridTextColumn.Header>
+                </DataGridTextColumn>
+
+                <DataGridTextColumn Width="*" Binding="{Binding ReadOnly}">
+                    <DataGridTextColumn.Header>
+                        <TextBlock Text="{DynamicResource doc.readOnly}"/>
+                    </DataGridTextColumn.Header>
+                </DataGridTextColumn>
+
+                <DataGridTextColumn Width="*" Binding="{Binding PressDownValue}">
+                    <DataGridTextColumn.Header>
+                        <TextBlock Text="{DynamicResource doc.pressDownValue}"/>
+                    </DataGridTextColumn.Header>
+                </DataGridTextColumn>
+
+                <DataGridTextColumn Width="*" Binding="{Binding PressUpValue}">
+                    <DataGridTextColumn.Header>
+                        <TextBlock Text="{DynamicResource doc.pressUpValue}"/>
+                    </DataGridTextColumn.Header>
+                </DataGridTextColumn>
+
+                <DataGridTextColumn Width="*" Binding="{Binding ControlType}">
+                    <DataGridTextColumn.Header>
+                        <TextBlock Text="{DynamicResource doc.controlType}"/>
+                    </DataGridTextColumn.Header>
+                </DataGridTextColumn>
+
+                <DataGridTemplateColumn Width="110" IsReadOnly="True" CanUserResize="False">
+                    <DataGridTemplateColumn.Header>
+                        <TextBlock Text="{DynamicResource App.Opt}"/>
+                    </DataGridTemplateColumn.Header>
+                    <DataGridTemplateColumn.CellTemplate>
+                        <DataTemplate>
+                            <StackPanel Orientation="Horizontal">
+                                <Button x:Name="editBtn" Style="{StaticResource EditLink}"
+    Click="Button_Click" Margin="0,0,10,0"
+    DataContext="{Binding }"/>
+                                <Button x:Name="delBtn" Style="{StaticResource DeleteLink}" 
+    DataContext="{Binding }"
+    Click="Button_Click"/>
+                            </StackPanel>
+                        </DataTemplate>
+                    </DataGridTemplateColumn.CellTemplate>
+                </DataGridTemplateColumn>
+            </DataGrid.Columns>
+        </DataGrid>
+    </Grid>
+</pu:WindowX>
diff --git a/Plugin.DOControl/ControlList.xaml.cs b/Plugin.DOControl/ControlList.xaml.cs
new file mode 100644
index 0000000..1d9c0e0
--- /dev/null
+++ b/Plugin.DOControl/ControlList.xaml.cs
@@ -0,0 +1,224 @@
+锘縰sing HMI.Core.Entity;
+using HMI.Core.Manager;
+using HMI.Core.Util;
+using HMI.UI.DpUtil;
+using HMI.UI.Util;
+using HMI.Util;
+using Microsoft.Win32;
+using Panuon.WPF.UI;
+using Plugin.DOControl.Entity;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Plugin.DOControl
+{
+    /// <summary>
+    /// ModalWindow.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class ControlList : WindowX
+    {
+        public ControlList()
+        {
+            this.DataContext = this;
+            InitializeComponent();
+        }
+
+        private ObservableCollection<DOControlData> datasource;
+
+        public ObservableCollection<DOControlData> Datasource
+        {
+            get { return datasource; }
+            set { Set(ref datasource, value); }
+        }
+
+        private bool dataChanged;
+
+        protected override void OnClosing(CancelEventArgs e)
+        {
+            DialogResult = dataChanged;
+            base.OnClosing(e);
+        }
+
+        private void Button_Click(object sender, RoutedEventArgs e)
+        {
+            Button btn = sender as Button;
+            DOControlData data = btn?.DataContext as DOControlData;
+            switch (btn.Name)
+            {
+                case "addBtn":
+                    OpenDataModal(null);
+                    break;
+                case "editBtn":
+                    OpenDataModal(data);
+                    break;
+                case "delBtn":
+                    TryDeleteData(data);
+                    break;
+                case "delBatchBtn":
+                    TryDeleteBatch();
+                    break;
+                case "refreshBtn":
+                    LoadData();
+                    break;
+                case "exportBtn":
+                    TryExport();
+                    break;
+                case "importBtn":
+                    TryImport();
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        public void LoadData()
+        {
+            ObservableCollection<DOControlData> datas = new ObservableCollection<DOControlData>();
+            try
+            {
+                if (File.Exists(DOControl.FilePath))
+                {
+                    datas = XmlHelper.DeserializeFromXml<ObservableCollection<DOControlData>>(DOControl.FilePath);
+                }
+            }
+            catch (Exception ex)
+            {
+                DOControl.Logger?.Error("Data List Load Data Failed.", ex);
+                ToastHelper.Error($"{this.lang("App.LoadFailed")}: {ex.Message}");
+            }
+            Datasource = datas;
+        }
+
+        private void OpenDataModal(DOControlData data)
+        {
+            try
+            {
+                DataModal modal = new DataModal
+                {
+                    Title = data == null ? this.lang("App.Add") : this.lang("App.Edit"),
+                    Owner = Window.GetWindow(this)
+                };
+                if (modal.Open(data ?? new DOControlData(), Datasource) == true)
+                {
+                    dataChanged = true;
+                    LoadData();
+                }
+            }
+            catch (Exception ex)
+            {
+                DOControl.Logger?.Error($"Data List {(data == null ? "Add" : "Edit")} Data Failed.", ex);
+                ToastHelper.Error($"{(data == null ? this.lang("App.AddFailed") : this.lang("App.EditFailed"))}: {ex.Message}");
+            }
+        }
+
+        private void TryDeleteData(DOControlData data)
+        {
+            try
+            {
+                int index = Datasource.IndexOf(data);
+                if (index < 0)
+                {
+                    ToastHelper.Error("Error");
+                    return;
+                }
+                if (MsgBoxHelper.WarnMessage(this, this.lang("HMI.MakeSureDeleteSingle", data.Name)) == MessageBoxResult.OK)
+                {
+                    var temp = XmlHelper.DeepClone(Datasource);
+                    temp.RemoveAt(index);
+                    XmlHelper.SerializeToXml(DOControl.FilePath, temp);
+                    dataChanged = true;
+                    LoadData();
+                }
+            }
+            catch (Exception ex)
+            {
+                DOControl.Logger?.Error($"Data List Try Delete Data Failed.", ex);
+                ToastHelper.Error($"{this.lang("App.DeleteFailed")}: {ex.Message}");
+            }
+        }
+
+        private void TryDeleteBatch()
+        {
+            try
+            {
+                var count = Datasource.Where(d => d.IsSelected).Count();
+                if (count <= 0)
+                {
+                    ToastHelper.Warning(this, this.lang("HMI.NoDeleteData"));
+                    return;
+                }
+                if (MsgBoxHelper.WarnMessage(this, this.lang("HMI.MakeSureDeletes", count)) == MessageBoxResult.OK)
+                {
+                    var temp = Datasource.Where(d => !d.IsSelected).ToList();
+                    XmlHelper.SerializeToXml(DOControl.FilePath, temp);
+                    dataChanged = true;
+                    LoadData();
+                }
+            }
+            catch (Exception ex)
+            {
+                DOControl.Logger?.Error($"Data List Try Delete Batch Data Failed.", ex);
+                ToastHelper.Error($"{this.lang("App.DeleteFailed")}: {ex.Message}");
+            }
+        }
+
+        public void TryExport()
+        {
+            try
+            {
+                SaveFileDialog sfd = new SaveFileDialog();
+
+                sfd.Filter = "";
+                sfd.DefaultExt = "xls";
+                sfd.FileName = this.lang("doc.list");
+                if (sfd.ShowDialog() == true)
+                {
+                    string targetPath = sfd.FileName;
+                    ExcelHelper.ExportExcel(targetPath, Datasource);
+                    ToastHelper.Success(Window.GetWindow(this), this.lang("App.ExportSuccess"));
+                }
+            }
+            catch (Exception ex)
+            {
+                DOControl.Logger?.Error("Export Data Failed.", ex);
+                MsgBoxHelper.Error(Window.GetWindow(this), $"{this.lang("App.ExportFailed")}: {ex.Message}");
+            }
+        }
+
+        public void TryImport()
+        {
+            try
+            {
+                OpenFileDialog openFileDialog = new OpenFileDialog();
+                openFileDialog.Filter = "execl(*.xlsx;*.xls)|*.xlsx;*.xls";
+                openFileDialog.Title = this.lang("App.SelectFile");
+                openFileDialog.Multiselect = false;
+                if (openFileDialog.ShowDialog() == true)
+                {
+                    List<DOControlData> datas = ExcelHelper.ReadFromExcel<DOControlData>(openFileDialog.FileName);
+                    XmlHelper.SerializeToXml(DOControl.FilePath, datas);
+                    dataChanged = true;
+                    LoadData();
+                }
+            }
+            catch (Exception ex)
+            {
+                DOControl.Logger?.Error("Export Test Item Failed.", ex);
+                MsgBoxHelper.Error(Window.GetWindow(this), $"{this.lang("App.ExportFailed")}: {ex.Message}");
+            }
+        }
+
+
+        public bool? Open(ObservableCollection<DOControlData> datasource)
+        {
+            Datasource = datasource;
+            return this.ShowDialog();
+        }
+    }
+}
diff --git a/Plugin.DOControl/DOControl.xaml b/Plugin.DOControl/DOControl.xaml
index 30bfef1..2df075e 100644
--- a/Plugin.DOControl/DOControl.xaml
+++ b/Plugin.DOControl/DOControl.xaml
@@ -1,10 +1,10 @@
-锘�<hb:PluginControl x:Class="HMI.DOControl.DOControl"
+锘�<hb:PluginControl x:Class="Plugin.DOControl.DOControl"
              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:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
-             xmlns:local="clr-namespace:HMI.DOControl"
+             xmlns:local="clr-namespace:Plugin.DOControl"
              xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
              xmlns:pw="https://opensource.panuon.com/wpf"
              xmlns:pu="https://opensource.panuon.com/wpf-ui"
@@ -13,100 +13,123 @@
              Loaded="UserControl_Loaded"
              Background="{StaticResource WorkspaceBackground}"
              d:DesignHeight="450" d:DesignWidth="800">
+    <UserControl.Resources>
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <pu:StyleDictionary Includes="GroupBox" />
+            </ResourceDictionary.MergedDictionaries>
+
+            <Style TargetType="GroupBox" BasedOn="{StaticResource {x:Static pu:StyleKeys.GroupBoxStyle}}">
+                <Setter Property="pu:GroupBoxHelper.HeaderBackground" Value="{StaticResource ReHoriBar}"/>
+                <Setter Property="pu:GroupBoxHelper.HeaderForeground" Value="{StaticResource PrimaryColorContract}"/>
+                <Setter Property="pu:GroupBoxHelper.HeaderFontWeight" Value="Bold"/>
+            </Style>
+        </ResourceDictionary>        
+    </UserControl.Resources>
+    
     <Border Style="{StaticResource CardBoarder}">
-
-
         <Grid dp:GridHelper.Space="10" Cursor="">
             <Grid.RowDefinitions>
-                <RowDefinition Height="Auto"/>
                 <RowDefinition Height="Auto"/>
                 <RowDefinition/>
             </Grid.RowDefinitions>
 
-            <TextBlock FontSize="{StaticResource TitleFontSize}" 
-VerticalAlignment="Center" HorizontalAlignment="Left" Text="鏃ュ織鏌ヨ"/>
-
-            <DockPanel Grid.Row="1" Cursor="">
+            <DockPanel Grid.Row="0" Cursor="">
                 <StackPanel DockPanel.Dock="Right" Style="{StaticResource ButtonGroup}">
-                    <Button Style="{StaticResource QueryBtn}"
-                            x:Name="queryBtn" Click="Button_Click"/>
+                    <Button Style="{StaticResource SettingBtn}"
+x:Name="settingBtn" Click="Button_Click"/>
+                    <!--<Button Style="{StaticResource AddBtn}"
+                            x:Name="addBtn" Click="Button_Click"/>
+                   
                     <Button Style="{StaticResource RfhBtn}"
                             x:Name="resetBtn" Click="Button_Click"/>
                     <Button x:Name="exportBtn"
-                            Style="{StaticResource ExportBtn}" Click="Button_Click"/>
+                            Style="{StaticResource ExportBtn}" Click="Button_Click"/>-->
                 </StackPanel>
-
-                <StackPanel Style="{StaticResource ButtonGroup}" HorizontalAlignment="Left">
-                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="绾у埆"/>
-                    <ComboBox Style="{StaticResource FormComboxStyle}" x:Name="levelInput" 
-                              Width="180"
-                              SelectedValuePath="Content">
-                        <ComboBoxItem Content="info"/>
-                        <ComboBoxItem Content="warn"/>
-                        <ComboBoxItem Content="error"/>
-                    </ComboBox>
-
-                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="寮�濮嬫椂闂�"/>
-                    <pu:DateTimePicker Style="{StaticResource TimePicker}" x:Name="startTimeInput"/>
-
-                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="缁撴潫鏃堕棿"/>
-                    <pu:DateTimePicker Style="{StaticResource TimePicker}" x:Name="endTimeInput"/>
-
-                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="鏉ユ簮"/>
-                    <!--<ComboBox Style="{StaticResource FormComboxStyle}" x:Name="sourceInput" Width="180"></ComboBox>-->
-                    <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="sourceInput" Width="180"/>
-
-                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="瀹炴椂鍒锋柊"/>
-                    <CheckBox x:Name="autoRfhInput" HorizontalAlignment="Center" VerticalAlignment="Center"
-                              IsChecked="{Binding AutoRfh,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
-                              />
-                </StackPanel>
+                <TextBlock FontSize="{StaticResource TitleFontSize}" 
+VerticalAlignment="Center" HorizontalAlignment="Left" Text="{DynamicResource doc.title}"/>
             </DockPanel>
 
+            <Grid Grid.Row="1" >
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="0*"/>
+                    <ColumnDefinition Width="10*"/>
+                </Grid.ColumnDefinitions>
+                <!--<ListBox ItemsSource="{Binding Groups}" SelectedItem="{Binding SelectedGroup}">
+                    <ListBox.Style>
+                        <Style TargetType="ListBox">
+                            <Setter Property="Padding" Value="-1"/>
+                            <Setter Property="Background" Value="Transparent"/>
+                            <Setter Property="ItemContainerStyle">
+                                <Setter.Value>
+                                    <Style TargetType="ListBoxItem">
+                                        <Setter Property="Margin" Value="0"/>
+                                        <Setter Property="Cursor" Value="Hand"/>
+                                        <Setter Property="FontWeight" Value="Bold"/>
 
-            <DataGrid Grid.Row="2" x:Name="table" Style="{StaticResource DefaultDataGrid}">
-                <DataGrid.RowStyle>
-                    <Style TargetType="DataGridRow" BasedOn="{StaticResource {x:Static pu:StyleKeys.DataGridRowStyle}}">
-                        <Style.Triggers>
-                            <DataTrigger Binding="{Binding type}" Value="warn">
-                                <Setter Property="Background" Value="{StaticResource WarningColor}"/>
-                                <Setter Property="Foreground" Value="Black"/>
-                            </DataTrigger>
-                            <DataTrigger Binding="{Binding type}" Value="error">
-                                <Setter Property="Background" Value="{StaticResource DangerColor}"/>
-                                <Setter Property="Foreground" Value="Black"/>
-                            </DataTrigger>
-                            <Trigger Property="IsSelected" Value="True">
-                                <Setter Property="Background" Value="Transparent"/>
-                            </Trigger>
-                            <Trigger Property="IsMouseOver" Value="True">
-                                <Setter Property="Background" Value="Transparent"/>
-                            </Trigger>
-                        </Style.Triggers>
-                    </Style>
-                </DataGrid.RowStyle>
-                <DataGrid.Columns>
-                    <DataGridTemplateColumn Header="" Width="Auto" CanUserResize="False">
-                        <DataGridTemplateColumn.CellTemplate>
-                            <DataTemplate>
-                                <CheckBox VerticalAlignment="Center" IsChecked="{Binding Selected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
-                            </DataTemplate>
-                        </DataGridTemplateColumn.CellTemplate>
-                    </DataGridTemplateColumn>
-                    <DataGridTemplateColumn Header="#" 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>
-                    <DataGridTextColumn Width="*" Binding="{Binding type}" Header="绾у埆" />
-                    <DataGridTextColumn Width="*" Binding="{Binding time}" Header="鏃堕棿" />
-                    <DataGridTextColumn Width="*" Binding="{Binding source}" Header="鏉ユ簮" />
-                    <DataGridTextColumn Width="*" Binding="{Binding text}" Header="鏂囨湰" />
-                </DataGrid.Columns>
-            </DataGrid>
+                                        <Setter Property="Background" Value="{StaticResource PrimaryColor}"/>
+                                        <Setter Property="Template">
+                                            <Setter.Value>
+                                                <ControlTemplate TargetType="ListBoxItem">
+                                                    <Border x:Name="border" Padding="0,15">
+                                                        <ContentPresenter x:Name="cp" Content="{TemplateBinding Content}" 
+ContentStringFormat="{TemplateBinding ContentStringFormat}" 
+ContentTemplate="{TemplateBinding ContentTemplate}" />
+                                                    </Border>
+                                                    
+
+                                                    <ControlTemplate.Triggers>
+                                                        <Trigger Property="IsSelected" Value="True">
+                                                            <Setter Property="Background" TargetName="border"
+                                                        Value="{StaticResource ReHoriBar}"/>
+                                                            <Setter Property="Foreground" Value="White"/>
+                                                        </Trigger>
+
+                                                        <Trigger Property="IsMouseOver" Value="True">
+                                                            <Setter TargetName="border" Property="Background" Value="#7093d3" />
+                                                            <Setter Property="Foreground" Value="{StaticResource SecondPrimaryColorContract}" />
+                                                        </Trigger>
+                                                    </ControlTemplate.Triggers>
+                                                </ControlTemplate>
+                                            </Setter.Value>
+                                        </Setter>
+                                    </Style>
+                                </Setter.Value>
+                            </Setter>
+                            <Setter Property="ItemsPanel">
+                                <Setter.Value>
+                                    <ItemsPanelTemplate>
+                                        <VirtualizingStackPanel IsItemsHost="True"/>
+                                    </ItemsPanelTemplate>
+                                </Setter.Value>
+                            </Setter>
+                            <Setter Property="ItemTemplate">
+                                <Setter.Value>
+                                    <DataTemplate>
+                                        <Label VerticalContentAlignment="Center" HorizontalContentAlignment="Center" 
+                                               Content="{Binding Mode=OneWay}"/>
+                                    </DataTemplate>
+                                </Setter.Value>
+                            </Setter>
+                        </Style>
+                    </ListBox.Style>
+                </ListBox>-->
+
+                <Border Grid.Column="1" BorderThickness="0" Padding="0" BorderBrush="#FFABADB3">
+                    <Grid>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition/>
+                            <ColumnDefinition/>
+                        </Grid.ColumnDefinitions>
+                        <StackPanel x:Name="panel0" Margin="0,0,10,0">
+
+                        </StackPanel>
+                        <StackPanel Grid.Column="1" x:Name="panel1" >
+
+                        </StackPanel>
+                    </Grid>
+                </Border>
+            </Grid>
         </Grid>
     </Border>
 </hb:PluginControl>
\ No newline at end of file
diff --git a/Plugin.DOControl/DOControl.xaml.cs b/Plugin.DOControl/DOControl.xaml.cs
index e6e4aa3..4ec40cc 100644
--- a/Plugin.DOControl/DOControl.xaml.cs
+++ b/Plugin.DOControl/DOControl.xaml.cs
@@ -1,29 +1,465 @@
 锘縰sing HMI.Base;
+using HMI.Core.Setting;
+using HMI.Core.Util;
+using HMI.UI;
+using HMI.UI.Util;
+using log4net;
+using Panuon.WPF.UI;
+using Plugin.DOControl.Entity;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Linq;
 using System.Windows;
 using System.Windows.Controls;
+using System.Windows.Documents;
+using System.Windows.Media;
 
-namespace HMI.DOControl
+namespace Plugin.DOControl
 {
     /// <summary>
     /// UserControl1.xaml 鐨勪氦浜掗�昏緫
     /// </summary>
     public partial class DOControl : PluginControl
     {
+        /// <summary>
+        /// 瀹炰緥鍖栨湇鍔″櫒鎺ュ彛鍑芥暟
+        /// </summary>
+        private OpcInterface.OpcInterface lTSCADAApi = new OpcInterface.OpcInterface();
+
+        private ObservableCollection<string> groups;
+        public ObservableCollection<string> Groups
+        {
+            get => groups;
+            set
+            {
+                Set(ref groups, value);
+            }
+        }
+
+        private string selectedGroup;
+        public string SelectedGroup
+        {
+            get => selectedGroup;
+            set
+            {
+                Set(ref selectedGroup, value);
+                if (string.IsNullOrEmpty(value))
+                {
+                    selectedControls.Clear();
+                }
+                else if (GroupMap.ContainsKey(value))
+                {
+                    selectedControls = GroupMap[value];
+                }
+                RefreshControl();
+            }
+        }
+
+        private ObservableCollection<DOControlData> datasource;
+        public ObservableCollection<DOControlData> Datasource
+        {
+            get => datasource;
+            set
+            {
+                Set(ref datasource, value);
+            }
+        }
+
+        private Dictionary<string, List<DOControlData>> GroupMap = new Dictionary<string, List<DOControlData>>();
+
+        private List<FrameworkElement> visiableControls = new List<FrameworkElement>();
+        private List<DOControlData> selectedControls = new List<DOControlData>();
+
+        private System.Timers.Timer motorMonitorTimer;
+
         public DOControl()
         {
+            DataContext = this;
+            motorMonitorTimer = new System.Timers.Timer();
+            motorMonitorTimer.Interval = 200;
+            motorMonitorTimer.AutoReset = false;
+            motorMonitorTimer.Elapsed += MotorMonitorTimer_Elapsed;
             InitializeComponent();
+        }
+
+        public override void Active()
+        {
+            motorMonitorTimer.Enabled = true;
+            base.Active();
+        }
+
+        public override void DeActive()
+        {
+            motorMonitorTimer.Enabled = false;
+            base.DeActive();
+        }
+
+        public override void DoClose(object sender, EventArgs e)
+        {
+            motorMonitorTimer.Enabled = false;
+            base.DoClose(sender, e);
+        }
+
+        public static string FilePath { get; private set; }
+        public static ILog Logger { get; private set; }
+
+        public override void SetBaseFolder(string folder)
+        {
+            FilePath = Path.Combine(folder, "DOControlData.data");
+            base.SetBaseFolder(folder);
+        }
+
+        public override void SetLogger(ILog logger)
+        {
+            Logger = logger;
+            base.SetLogger(logger);
         }
 
         public override void InitFinish()
         {
             base.InitFinish();
-            
+        }
+
+        private void LoadData()
+        {
+            try
+            {
+                motorMonitorTimer.Enabled = false;
+                ObservableCollection<DOControlData> datas = new ObservableCollection<DOControlData>();
+                if (File.Exists(FilePath))
+                {
+                    datas = XmlHelper.DeserializeFromXml<ObservableCollection<DOControlData>>(FilePath);
+                }
+                Datasource = datas;
+                GroupMap = datas.GroupBy(x => x.Group)
+                   .ToDictionary(
+                       group => group.Key,
+                       group => group.ToList()
+                   );
+                Groups = new ObservableCollection<string>(GroupMap.Keys);
+                if (string.IsNullOrEmpty(SelectedGroup) && Groups.Count > 0)
+                {
+                    SelectedGroup = Groups[0];
+                }
+                else
+                {
+                    RefreshControl();
+                }
+            }
+            catch (Exception ex)
+            {
+                log.Error("Load Data Failed.", ex);
+                ToastHelper.Error(this, ex.Message);
+            }
+            finally
+            {
+                motorMonitorTimer.Enabled = true;
+            }
+        }
+
+        private void MotorMonitorTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
+        {
+            try
+            {
+                motorMonitorTimer.Enabled = false;
+                for (int i = visiableControls.Count - 1; i >= 0; i--)
+                {
+                    var ui = visiableControls[i];
+                    ui.Dispatcher.Invoke(() =>
+                    {
+                        DOControlData data = ui.Tag as DOControlData;
+                        if (data == null) return;
+                        string value = GetPlcValue(data.Address);
+                        switch (data.ControlType)
+                        {
+                            case ControlType.Button:
+                                var color = Application.Current.FindResource("VerBar") as Brush;
+                                if (value == data.PressDownValue)
+                                {
+                                    color = Application.Current.FindResource("GreenVerBar") as Brush;
+                                }
+                            ((Button)ui).Background = color;
+                                ((Button)ui).Content = value;
+                                break;
+                            case ControlType.Int16:
+                            case ControlType.Int32:
+                            case ControlType.Real:
+                            case ControlType.LReal:
+                            case ControlType.String:
+                            case ControlType.WString:
+                                ((Button)ui).Content = value;
+                                break;
+                            default:
+                                break;
+                        }
+                    });
+                }
+            }
+            finally
+            {
+                motorMonitorTimer.Enabled = true;
+            }
         }
 
 
+
+        private void RefreshControl()
+        {
+            try
+            {
+                motorMonitorTimer.Enabled = false;
+                panel0.Children.Clear();
+                panel1.Children.Clear();
+                visiableControls.Clear();
+                int index = 0;
+                foreach (string group in GroupMap.Keys)
+                {
+                    int num = index % 2;
+
+                    WrapPanel panel = new WrapPanel
+                    {
+                        Orientation = Orientation.Horizontal
+                    };
+                    GroupBox gb = new GroupBox
+                    {
+                        Header = group,
+                        Content = panel,
+                        Margin = new Thickness(0, 0, 0, 10)
+                    };
+                    var datas = GroupMap[group];
+
+                    foreach (var data in datas)
+                    {
+                        FrameworkElement ui = null;
+                        switch (data.ControlType)
+                        {
+                            case ControlType.Button:
+                            case ControlType.ClickButton:
+                                ui = CreateButton(data);
+                                break;
+                            case ControlType.Int16:
+                            case ControlType.Int32:
+                            case ControlType.Real:
+                            case ControlType.LReal:
+                            case ControlType.String:
+                            case ControlType.WString:
+                                ui = CreateNumberInput(data);
+                                break;
+                            default:
+                                break;
+                        }
+                        if (ui != null)
+                        {
+                            FormGroup fg = new FormGroup
+                            {
+                                Header = data.Name,
+                                Content = ui,
+                                Orientation = Orientation.Vertical,
+                                Margin = new Thickness(10, 0, 0, 10)
+                            };
+                            panel.Children.Add(fg);
+                            visiableControls.Add(ui);
+
+                        }
+                    }
+                    if (num == 0)
+                    {
+                        panel0.Children.Add(gb);
+                    }
+                    else
+                    {
+                        panel1.Children.Add(gb);
+                    }
+                    index++;
+                }
+            }
+            catch
+            {
+            }
+            finally
+            {
+                motorMonitorTimer.Enabled = true;
+            }
+        }
+
+        private Button CreateButton(DOControlData data)
+        {
+            Style btnStyle = Application.Current.FindResource("PrimaryButton") as Style;
+            var btn = new Button
+            {
+                Tag = data,
+                Style = btnStyle,
+                MinWidth = 180,
+                MinHeight = 40
+            };
+            if (data.ControlType == ControlType.Button)
+            {
+                btn.Click += ControlButton_Click;
+            }
+            else if (data.ControlType == ControlType.ClickButton)
+            {
+                btn.Content = data.Address;
+                btn.PreviewMouseLeftButtonDown += Btn_MouseLeftButtonDown;
+                btn.PreviewMouseLeftButtonUp += Btn_MouseLeftButtonUp;
+            }
+            return btn;
+        }
+
+        private void Btn_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            Button btn = sender as Button;
+            DOControlData data = btn.Tag as DOControlData;
+            try
+            {
+                var color = Application.Current.FindResource("GreenVerBar") as Brush;
+                btn.Background = color;
+                SetPlcValue(data.Address, data.PressDownValue);
+            }
+            catch (Exception ex)
+            {
+                log.Error("PressDownClick", ex);
+                ToastHelper.Error($"{data.Name}: {ex.Message}");
+            }
+        }
+
+        private void Btn_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            Button btn = sender as Button;
+            DOControlData data = btn.Tag as DOControlData;
+            try
+            {
+                var color = Application.Current.FindResource("VerBar") as Brush;
+                btn.Background = color;
+                SetPlcValue(data.Address, data.PressUpValue);
+            }
+            catch (Exception ex)
+            {
+                log.Error("PressUpClick", ex);
+                ToastHelper.Error($"{data.Name}: {ex.Message}");
+            }
+        }
+
+        private Button CreateNumberInput(DOControlData data)
+        {
+            Style btnStyle = Application.Current.FindResource("PrimaryButton") as Style;
+            var tb = new Button
+            {
+                Tag = data,
+                Style = btnStyle,
+                MinWidth = 180,
+                MinHeight = 40,
+                BorderBrush = Brushes.Black,
+                BorderThickness = new Thickness(1),
+                Background = Brushes.White,
+                Foreground = Brushes.Black
+            };
+            if (data.ReadOnly)
+            {
+                tb.Background = Brushes.LightGray;
+                return tb;
+            }
+
+            if (data.ControlType == ControlType.String || data.ControlType == ControlType.WString)
+            {
+                tb.Click += StringInput_Click;
+            }
+            else
+            {
+                tb.Click += NumberInput_Click;
+            }
+            return tb;
+        }
+
+        /// <summary>
+        /// 璁剧疆plc鍊�
+        /// </summary>
+        /// <param name="address">鍦板潃</param>
+        /// <param name="valueType">绫诲瀷</param>
+        /// <param name="value">鍊�</param>
+        private void SetPlcValue(string key, string value)
+        {
+            if (!lTSCADAApi.DoFctWriteSingle(key, value, out string errMsg))
+            {
+                throw new Exception(errMsg);
+            }
+        }
+
+        private string GetPlcValue(string key)
+        {
+            if (lTSCADAApi.DoFctReadSingle(key, out string Val, out string errMsg))
+            {
+                return Val;
+            }
+            else
+            {
+                return null;
+            }
+        }
+
+        private void ControlButton_Click(object sender, RoutedEventArgs args)
+        {
+            Button btn = sender as Button;
+            DOControlData data = btn.Tag as DOControlData;
+            try
+            {
+                string value = btn.Content?.ToString() == data.PressDownValue ? data.PressUpValue : data.PressDownValue;
+                SetPlcValue(data.Address, value);
+            }
+            catch (Exception ex)
+            {
+                log.Error("ControlButton_Click", ex);
+                ToastHelper.Error($"{data.Name}: {ex.Message}");
+            }
+        }
+
+        private void StringInput_Click(object sender, RoutedEventArgs args)
+        {
+            Button tb = sender as Button;
+            DOControlData data = tb.Tag as DOControlData;
+
+            KeyboardWindow kw = new KeyboardWindow
+            {
+                Title = data.Name,
+                Owner = Window.GetWindow(this)
+            };
+            if (kw.Open(tb.Content?.ToString()) == true)
+            {
+                SetPlcValue(data.Address, kw.Data?.ToString());
+            }
+        }
+
+        private void NumberInput_Click(object sender, RoutedEventArgs args)
+        {
+            Button tb = sender as Button;
+            DOControlData data = tb.Tag as DOControlData;
+            NumberPanel np = new NumberPanel
+            {
+                Title = data.Name,
+                Owner = Window.GetWindow(this)
+            };
+            if (np.Open(tb.Content?.ToString()) == true)
+            {
+                SetPlcValue(data.Address, np.Data?.ToString());
+            }
+        }
+
         private void UserControl_Loaded(object sender, RoutedEventArgs e)
         {
-            
+            LoadData();
+            if (SystemSetting.Instance.PlcEnabled)
+            {
+                if (lTSCADAApi.Connect2("127.0.0.1", 6543, 18888, out string errMsg))
+                {
+                    motorMonitorTimer.Enabled = true;
+                    RefreshControl();
+                }
+                else
+                {
+                    MsgBoxHelper2.Error(errMsg);
+                }
+            }
             Loaded -= UserControl_Loaded;
         }
 
@@ -32,9 +468,14 @@
             Button btn = sender as Button;
             switch (btn.Name)
             {
-                case "queryBtn":
+                case "addBtn":
+                    OpenDataModal(null);
+                    break;
+                case "settingBtn":
+                    OpenDataList();
                     break;
                 case "resetBtn":
+                    LoadData();
                     break;
                 case "exportBtn":
                     break;
@@ -42,5 +483,30 @@
                     break;
             }
         }
+
+        private void OpenDataModal(DOControlData data)
+        {
+            DataModal modal = new DataModal
+            {
+                Title = data == null ? this.lang("App.Add") : this.lang("App.Edit"),
+                Owner = Window.GetWindow(this)
+            };
+            if (modal.Open(data ?? new DOControlData(), Datasource) == true)
+            {
+                LoadData();
+            }
+        }
+
+        private void OpenDataList()
+        {
+            ControlList list = new ControlList
+            {
+                Owner = Window.GetWindow(this)
+            };
+            if (list.Open(Datasource) == true)
+            {
+                LoadData();
+            }
+        }
     }
 }
diff --git a/Plugin.DOControl/DataModal.xaml b/Plugin.DOControl/DataModal.xaml
new file mode 100644
index 0000000..1edace3
--- /dev/null
+++ b/Plugin.DOControl/DataModal.xaml
@@ -0,0 +1,85 @@
+锘�<pu:WindowX x:Class="Plugin.DOControl.DataModal"
+            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.DOControl"
+            xmlns:pw="https://opensource.panuon.com/wpf"
+            xmlns:pu="https://opensource.panuon.com/wpf-ui"
+            xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
+            mc:Ignorable="d"
+            Title="Form"
+            SizeToContent="Height"
+            Width="600"
+            WindowStartupLocation="CenterOwner"
+            Style="{StaticResource ModalWindow}">
+    <Grid Margin="10">
+        <StackPanel x:Name="formPanel">
+            <pu:FormGroup x:Name="FgGroup" 
+                          Header="{DynamicResource doc.group}"
+                          Style="{StaticResource InputFormGroupStyle}"
+                          dp:FormGroupHelper.IsRequired="True"
+                          GroupName="MyFormGroup">
+                <TextBox x:Name="TbGroup" Style="{StaticResource EditorTextBoxStyle}" />
+            </pu:FormGroup>
+
+            <pu:FormGroup x:Name="FgName" 
+              Header="{DynamicResource doc.name}"
+              Style="{StaticResource InputFormGroupStyle}"
+              dp:FormGroupHelper.IsRequired="True"
+              dp:FormGroupHelper.ValidateMethod="ValidateName"
+              GroupName="MyFormGroup">
+                <TextBox x:Name="TbName" Style="{StaticResource EditorTextBoxStyle}" />
+            </pu:FormGroup>
+
+            <pu:FormGroup x:Name="FgAddress" 
+  Header="{DynamicResource doc.address}"
+  Style="{StaticResource InputFormGroupStyle}"
+  dp:FormGroupHelper.IsRequired="True"
+  dp:FormGroupHelper.ValidateMethod="ValidateAddress"
+  GroupName="MyFormGroup">
+                <TextBox x:Name="TbAddress" Style="{StaticResource EditorTextBoxStyle}" />
+            </pu:FormGroup>
+
+            <pu:FormGroup x:Name="FgReadOnly" 
+Header="{DynamicResource doc.readOnly}"
+Style="{StaticResource InputFormGroupStyle}"
+GroupName="MyFormGroup">
+                <CheckBox x:Name="CxReadOnly"/>
+            </pu:FormGroup>
+
+            <pu:FormGroup x:Name="FgPressDownValue" 
+  Header="{DynamicResource doc.pressDownValue}"
+  Style="{StaticResource InputFormGroupStyle}"
+  dp:FormGroupHelper.IsRequired="True"
+  GroupName="MyFormGroup">
+                <TextBox x:Name="TbPressDownValue" Style="{StaticResource EditorTextBoxStyle}" />
+            </pu:FormGroup>
+
+            <pu:FormGroup x:Name="FgPressUpValue" 
+Header="{DynamicResource doc.pressUpValue}"
+Style="{StaticResource InputFormGroupStyle}"
+dp:FormGroupHelper.IsRequired="True"
+GroupName="MyFormGroup">
+                <TextBox x:Name="TbPressUpValue" Style="{StaticResource EditorTextBoxStyle}" />
+            </pu:FormGroup>
+
+            <pu:FormGroup x:Name="FgControlType" 
+Header="{DynamicResource doc.controlType}"
+Style="{StaticResource InputFormGroupStyle}"
+dp:FormGroupHelper.IsRequired="True"
+GroupName="MyFormGroup">
+                <ComboBox x:Name="CbControlType" Style="{StaticResource FormComboxStyle}" SelectedValuePath="Content">
+                    <ComboBoxItem>0</ComboBoxItem>
+                    <ComboBoxItem>1</ComboBoxItem>
+                    <ComboBoxItem>2</ComboBoxItem>
+                    <ComboBoxItem>3</ComboBoxItem>
+                    <ComboBoxItem>4</ComboBoxItem>
+                    <ComboBoxItem>5</ComboBoxItem>
+                    <ComboBoxItem>6</ComboBoxItem>
+                    <ComboBoxItem>7</ComboBoxItem>
+                </ComboBox>
+            </pu:FormGroup>
+        </StackPanel>
+    </Grid>
+</pu:WindowX>
diff --git a/Plugin.DOControl/DataModal.xaml.cs b/Plugin.DOControl/DataModal.xaml.cs
new file mode 100644
index 0000000..573d967
--- /dev/null
+++ b/Plugin.DOControl/DataModal.xaml.cs
@@ -0,0 +1,157 @@
+锘縰sing HMI.Core.Util;
+using HMI.UI.DpUtil;
+using HMI.UI.Util;
+using Panuon.WPF.UI;
+using Plugin.DOControl.Entity;
+using System;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Linq;
+
+namespace Plugin.DOControl
+{
+    /// <summary>
+    /// ModalWindow.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class DataModal : WindowX
+    {
+        public DataModal()
+        {
+            this.DataContext = this;
+            InitializeComponent();
+        }
+
+        public DOControlData Data { get; set; }
+
+        private ObservableCollection<DOControlData> datasource;
+
+        public ObservableCollection<DOControlData> Datasource
+        {
+            get { return datasource; }
+            set { Set(ref datasource, value); }
+        }
+
+        private bool _isProcessing;
+
+        protected override void OnClosing(CancelEventArgs e)
+        {
+            //Press Continue Button
+            if (DialogResult == true)
+            {
+                if (_isProcessing)
+                {
+                    e.Cancel = true;
+                    return;
+                }
+                var okButton = WindowXModalDialog.GetOKButton(this);
+                try
+                {
+                    _isProcessing = true;
+                    ButtonHelper.SetIsPending(okButton, true);
+                    if (!ValidateForm())
+                    {
+                        e.Cancel = true;
+                        return;
+                    }
+
+                    SaveOrUpdateData();
+                }
+                catch (Exception ex)
+                {
+                    ToastHelper.Error(this, $"Error锛亄ex.Message}");
+                    e.Cancel = true;
+                    return;
+                }
+                finally
+                {
+                    _isProcessing = false;
+                    ButtonHelper.SetIsPending(okButton, false);
+                }
+            }
+            base.OnClosing(e);
+        }
+
+        private bool ValidateName(object value, out string err)
+        {
+            err = null;
+            //string group = TbGroup.Text;
+            //var temp = Datasource.FirstOrDefault(d => d.Group == group && d.Name == value.ToString());
+            //if (temp != null && temp != Data)
+            //{
+            //    err = this.lang("App.MustBeUnique", $"{this.lang("doc.group")},{this.lang("doc.name")}");
+            //    return false;
+            //}
+            return true;
+        }
+
+        private bool ValidateAddress(object value, out string err)
+        {
+            err = null;
+            //string group = TbGroup.Text;
+            //var temp = Datasource.FirstOrDefault(d => d.Address == value.ToString());
+            //if (temp != null && temp != Data)
+            //{
+            //    err = this.lang("App.MustBeUnique", this.lang("doc.Address"));
+            //    return false;
+            //}
+            return true;
+        }
+
+        private bool ValidateForm()
+        {
+            bool flag = FormGroupHelper.ValidateForm(formPanel);
+            if (flag)
+            {
+                Data.Group = TbGroup.Text;
+                Data.Name = TbName.Text;
+                Data.Address = TbAddress.Text;
+                Data.ReadOnly = CxReadOnly.IsChecked == true;
+                Data.PressDownValue = TbPressDownValue.Text;
+                Data.PressUpValue = TbPressUpValue.Text;
+                Data.ControlType = (ControlType)int.Parse(CbControlType.SelectedValue.ToString());
+            }
+            return flag;
+        }
+
+        public bool? Open(DOControlData data, ObservableCollection<DOControlData> datasource)
+        {
+            Datasource = datasource;
+            Data = data;
+
+            TbGroup.Text = Data.Group;
+            TbName.Text = Data.Name;
+            TbAddress.Text = Data.Address;
+            CxReadOnly.IsChecked = Data.ReadOnly;
+            TbPressDownValue.Text = Data.PressDownValue;
+            TbPressUpValue.Text = Data.PressUpValue;
+            CbControlType.SelectedValue = (int)Data.ControlType;
+            return this.ShowDialog();
+        }
+
+        private void SaveOrUpdateData()
+        {
+            int index = Datasource.IndexOf(Data);
+            if (index < 0)
+            {
+                InsertData(Data);
+            }
+            else
+            {
+                UpdateData(index, Data);
+            }
+        }
+
+        public void InsertData(DOControlData data)
+        {
+            Datasource.Add(data);
+            XmlHelper.SerializeToXml(DOControl.FilePath, Datasource);
+        }
+
+        public void UpdateData(int index, DOControlData data)
+        {
+            var temp = XmlHelper.DeepClone(Datasource);
+            temp[index] = data;
+            XmlHelper.SerializeToXml(DOControl.FilePath, temp);
+        }
+    }
+}
diff --git a/Plugin.DOControl/Entity/DOControlData.cs b/Plugin.DOControl/Entity/DOControlData.cs
new file mode 100644
index 0000000..a945bb8
--- /dev/null
+++ b/Plugin.DOControl/Entity/DOControlData.cs
@@ -0,0 +1,60 @@
+锘縰sing HMI.Core.Entity;
+using HMI.Core;
+
+namespace Plugin.DOControl.Entity
+{
+    public enum ControlType
+    {
+        Button, Int16, Int32, Real, LReal, String, WString, ClickButton
+    }
+    public class DOControlData : SelectBase
+    {
+        /// <summary>
+        /// 鍒嗙粍鍚嶇О
+        /// </summary>
+        [ExcelColumn("doc.group")]
+        public string Group { get; set; }
+
+        /// <summary>
+        /// 鎺у埗鐐瑰悕绉�
+        /// </summary>
+        [ExcelColumn("doc.name")]
+        public string Name { get; set; }
+
+        /// <summary>
+        /// 鍦板潃
+        /// </summary>
+        [ExcelColumn("doc.address")]
+        public string Address { get; set; }
+        [ExcelColumn("doc.readOnly")]
+        public bool ReadOnly { get; set; }
+
+        /// <summary>
+        /// 鎸変笅鍊�
+        /// </summary>
+        [ExcelColumn("doc.pressDownValue")]
+        public string PressDownValue { get; set; }
+
+        /// <summary>
+        /// 鎶捣鍊�
+        /// </summary>
+        [ExcelColumn("doc.pressUpValue")]
+        public string PressUpValue { get; set; }
+
+        /// <summary>
+        /// 鍊兼牸寮�
+        /// </summary>
+        public string ValueFormat { get; set; }
+
+        /// <summary>
+        /// 鏄惁鍚敤
+        /// </summary>
+        public bool Enable { get; set; }
+
+        /// <summary>
+        /// 鎺у埗绫诲瀷
+        /// </summary>
+        [ExcelColumn("doc.controlType")]
+        public ControlType ControlType { get; set; }
+    }
+}
diff --git a/Plugin.DOControl/Plugin.DOControl.csproj b/Plugin.DOControl/Plugin.DOControl.csproj
index fb506b7..f2dbe82 100644
--- a/Plugin.DOControl/Plugin.DOControl.csproj
+++ b/Plugin.DOControl/Plugin.DOControl.csproj
@@ -59,6 +59,16 @@
     <Reference Include="PresentationFramework" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="ControlList.xaml.cs">
+      <DependentUpon>ControlList.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="DataModal.xaml.cs">
+      <DependentUpon>DataModal.xaml</DependentUpon>
+    </Compile>
+    <Page Include="ControlList.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
     <Page Include="DOControl.xaml">
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
@@ -66,8 +76,13 @@
     <Compile Include="DOControl.xaml.cs">
       <DependentUpon>DOControl.xaml</DependentUpon>
     </Compile>
+    <Page Include="DataModal.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="Entity\DOControlData.cs" />
     <Compile Include="Properties\AssemblyInfo.cs">
       <SubType>Code</SubType>
     </Compile>
@@ -92,6 +107,10 @@
     </None>
   </ItemGroup>
   <ItemGroup>
+    <ProjectReference Include="..\..\HMI.Old\OPCInterface\OpcInterface\OpcInterface.csproj">
+      <Project>{e1464149-2c0a-499d-ab90-0c72e6ed1c53}</Project>
+      <Name>OpcInterface</Name>
+    </ProjectReference>
     <ProjectReference Include="..\HMI.Base\HMI.Base.csproj">
       <Project>{2a6f9720-a890-4036-9d73-41a0dae28341}</Project>
       <Name>HMI.Base</Name>
@@ -109,5 +128,6 @@
       <Name>HMI.Util</Name>
     </ProjectReference>
   </ItemGroup>
+  <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
 </Project>
\ No newline at end of file
diff --git a/Plugin.ServoManage/Plugin.ServoManage.csproj b/Plugin.ServoManage/Plugin.ServoManage.csproj
index e8a5aa1..360b0aa 100644
--- a/Plugin.ServoManage/Plugin.ServoManage.csproj
+++ b/Plugin.ServoManage/Plugin.ServoManage.csproj
@@ -79,9 +79,6 @@
   <ItemGroup>
     <Compile Include="Entity\ServoData.cs" />
     <Compile Include="Entity\ServoProductData.cs" />
-    <Compile Include="NumberPanel.xaml.cs">
-      <DependentUpon>NumberPanel.xaml</DependentUpon>
-    </Compile>
     <Compile Include="ServoProductModal.xaml.cs">
       <DependentUpon>ServoProductModal.xaml</DependentUpon>
     </Compile>
@@ -99,10 +96,6 @@
       <DependentUpon>ServoManage.xaml</DependentUpon>
       <SubType>Code</SubType>
     </Compile>
-    <Page Include="NumberPanel.xaml">
-      <Generator>MSBuild:Compile</Generator>
-      <SubType>Designer</SubType>
-    </Page>
     <Page Include="ServoProductModal.xaml">
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
diff --git a/Plugin.ServoManage/ServoManage.xaml b/Plugin.ServoManage/ServoManage.xaml
index e1976c8..6fac78e 100644
--- a/Plugin.ServoManage/ServoManage.xaml
+++ b/Plugin.ServoManage/ServoManage.xaml
@@ -243,7 +243,7 @@
                             </StackPanel>
                         </DockPanel>
 
-                        <Grid Grid.Row="1" dp:GridHelper.Space="10">
+                        <Grid Grid.Row="1" dp:GridHelper.Space="10" x:Name="panel">
                             <Grid.RowDefinitions>
                                 <RowDefinition Height="Auto"/>
                                 <RowDefinition/>
@@ -301,24 +301,34 @@
                                             </Button.Style>
                                         </Button>
                                         <Button Style="{StaticResource PrimaryButton}"
- Margin="5,0,0,0" Content="{DynamicResource svo.reset}" Tag="Reset" Click="Servo_Click"/>
+ Margin="5,0,0,0" Content="{DynamicResource svo.reset}" Tag="Reset"
+                                                PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown"
+                                                PreviewMouseLeftButtonUp="Button_PreviewMouseLeftButtonUp" />
                                     </UniformGrid>
                                     <Button Grid.Row="3" Style="{StaticResource PrimaryButton}"
                                             Content="{DynamicResource svo.init}"
-                                            Tag="Initialization" Click="Servo_Click"/>
+                                            Tag="Initialization"
+                                            PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown"
+                                            PreviewMouseLeftButtonUp="Button_PreviewMouseLeftButtonUp"/>
                                     <Button Grid.Row="4" Style="{StaticResource PrimaryButton}"
                                             Content="{DynamicResource svo.stop}"
-                                            Tag="Stop" Click="Servo_Click"/>
+                                            Tag="Stop"
+                                            PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown"
+                                            PreviewMouseLeftButtonUp="Button_PreviewMouseLeftButtonUp"/>
                                     <UniformGrid Grid.Row="5" Columns="3">
                                         <Button Style="{StaticResource PrimaryButton}"
                                                 Content="{DynamicResource svo.JOG+}"
-                                                Tag="JoGadd" Click="Servo_Click"/>
+                                                Tag="JoGadd"
+                                            PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown"
+                                            PreviewMouseLeftButtonUp="Button_PreviewMouseLeftButtonUp"/>
                                         <Label Style="{StaticResource LabelText}" MinWidth="90"
 Margin="10,0,10,0"
 BorderThickness="1" MouseLeftButtonUp="Label_MouseLeftButtonUp" Tag="JogVelocity" Content="{Binding JogVelocity}"/>
                                         <Button Style="{StaticResource PrimaryButton}"
                                                 Content="{DynamicResource svo.JOG-}"
-                                                Tag="JOGsub" Click="Servo_Click"/>
+                                                Tag="JOGsub"
+                                            PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown"
+                                            PreviewMouseLeftButtonUp="Button_PreviewMouseLeftButtonUp"/>
                                     </UniformGrid>
                                 </Grid>
                             </GroupBox>
@@ -384,7 +394,8 @@
                                             Grid.ColumnSpan="2" 
                                             Style="{StaticResource PrimaryButton}"
                                             Tag="AbsExecute"
-                                            Click="Servo_Click"
+                                            PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown"
+                                            PreviewMouseLeftButtonUp="Button_PreviewMouseLeftButtonUp"
                                             Content="{DynamicResource svo.absExecute}"/>
                                     </Grid>
                                 </GroupBox>
@@ -394,7 +405,9 @@
                                         <Button DockPanel.Dock="Left" 
                                                 Margin="0,0,10,0"
                                                 Style="{StaticResource PrimaryButton}"
-                                                Tag="Inch" Click="Servo_Click"
+                                                Tag="Inch"
+                                            PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown"
+                                            PreviewMouseLeftButtonUp="Button_PreviewMouseLeftButtonUp"
                                                 Content="{DynamicResource svo.inching}"/>
                                         <Button DockPanel.Dock="Right"
                                                 Tag="Dir" Click="Servo_Click">
diff --git a/Plugin.ServoManage/ServoManage.xaml.cs b/Plugin.ServoManage/ServoManage.xaml.cs
index 76a460e..39c557b 100644
--- a/Plugin.ServoManage/ServoManage.xaml.cs
+++ b/Plugin.ServoManage/ServoManage.xaml.cs
@@ -1,8 +1,9 @@
 锘縰sing HMI.Base;
+using HMI.Core.Entity;
+using HMI.Core.Manager;
 using HMI.Core.Setting;
 using HMI.Core.Util;
-using HMI.ParameterManage.Entity;
-using HMI.ParameterManage.Util;
+using HMI.UI;
 using HMI.UI.Util;
 using Plugin.ServoManage.Entity;
 using System;
@@ -15,6 +16,7 @@
 using System.Timers;
 using System.Windows;
 using System.Windows.Controls;
+using System.Windows.Threading;
 
 namespace Plugin.ServoManage
 {
@@ -93,6 +95,7 @@
         private bool isActived = false;
         private System.Timers.Timer motorMonitorTimer;
 
+        #region ServoData
         private string enable;
         [IsStatus]
         public string Enable
@@ -345,28 +348,20 @@
                 Set(ref posCode, value);
             }
         }
+        #endregion
 
         public ServoManage()
         {
             DataContext = this;
             motorMonitorTimer = new System.Timers.Timer();
             motorMonitorTimer.Elapsed += MotorMonitorTimer_Elapsed;
+            motorMonitorTimer.Interval = 100;
             InitializeComponent();
         }
 
         private void MotorMonitorTimer_Elapsed(object sender, ElapsedEventArgs e)
         {
-            this.motorMonitorTimer.Enabled = false;
-            try
-            {
-                RefreshServoStatus();
-                this.motorMonitorTimer.Enabled = true;
-            }
-            catch (Exception ex)
-            {
-                this.motorMonitorTimer.Enabled = true;
-                return;
-            }
+            RefreshServoStatus();
         }
 
         public override void SetBaseFolder(string folder)
@@ -387,8 +382,7 @@
                     {
                         if (lTSCADAApi.Connect2("127.0.0.1", 6543, 18888, out string errMsg))
                         {
-                            this.motorMonitorTimer.Interval = 100;
-                            this.motorMonitorTimer.Start();
+                            motorMonitorTimer.Enabled = true;
                             isActived = true;
                         }
                         else
@@ -408,6 +402,15 @@
                     MsgBoxHelper2.Error(ex.Message);
                 }
             }
+            else {
+                motorMonitorTimer.Enabled = true;
+            }
+        }
+
+        public override void DeActive()
+        {
+            motorMonitorTimer.Enabled = false;
+            base.DeActive();
         }
 
         private void UserControl_Loaded(object sender, RoutedEventArgs e)
@@ -435,7 +438,6 @@
                 case "rfhServoBtn":
                     LoadData();
                     break;
-
                 case "addProductBtn":
                     OpenProductModal(null);
                     break;
@@ -515,12 +517,12 @@
         public ObservableCollection<ServoProductData> ReadCommonProducts()
         {
             ObservableCollection<ServoProductData> datas = new ObservableCollection<ServoProductData>();
-            List<ProductData> products = ParameterManager.GetProducts<List<ProductData>>();
+            List<ProductData> products = ProductManager.GetProducts<List<ProductData>>();
             if (products != null && products.Count > 0)
             {
                 foreach (var product in products)
                 {
-                    var parameter = ParameterManager.GetParametersByProductName(product.Name)
+                    var parameter = ProductManager.GetParametersByProductName(product.Name)
                          ?.FirstOrDefault(p => p.Name == "ProgramCode");
                     if (parameter != null)
                     {
@@ -580,7 +582,8 @@
 
         private void RefreshServoStatus()
         {
-            Dispatcher.Invoke(() =>
+            this.motorMonitorTimer.Enabled = false;
+            try
             {
                 if (SelectedServo == null) return;
                 PropertyInfo[] properties = this.GetType().GetProperties();
@@ -590,19 +593,29 @@
                     {
                         continue;
                     }
-
-                    if (prop.Name == "SelectedProduct")
+                    Dispatcher.InvokeAsync(new Action(() =>
                     {
-                        var data = GetBitValue($"{SelectedServo.Name}_sRecipeCode");
-                        SelectedProduct = data;
-                    }
-                    else
-                    {
-                        var data = GetBitValue($"{SelectedServo.Name}_{prop.Name}");
-                        prop.SetValue(this, data);
-                    }
+                        if (prop.Name == "SelectedProduct")
+                        {
+                            var data = GetBitValue($"{SelectedServo.Name}_sRecipeCode");
+                            SelectedProduct = data;
+                        }
+                        else
+                        {
+                            var data = GetBitValue($"{SelectedServo.Name}_{prop.Name}");
+                            prop.SetValue(this, data);
+                        }
+                    }), DispatcherPriority.SystemIdle);
                 }
-            });
+            }
+            catch (Exception ex)
+            {
+
+            }
+            finally
+            {
+                this.motorMonitorTimer.Enabled = true;
+            }
         }
 
         /// <summary>
@@ -647,7 +660,6 @@
             Button btn = sender as Button;
             try
             {
-                motorMonitorTimer.Enabled = false;
                 switch (btn.Tag)
                 {
                     case "Enable":
@@ -657,7 +669,7 @@
                         EnableDoClick(btn.Tag.ToString());
                         break;
                     default:
-                        DoSignClick(btn.Tag?.ToString());
+                        //DoSignClick(btn.Tag?.ToString());
                         break;
                 }
             }
@@ -668,7 +680,6 @@
             }
             finally
             {
-                motorMonitorTimer.Enabled = true;
             }
         }
 
@@ -720,5 +731,35 @@
                 SetBitValue($"{SelectedServo?.Name}_{tag}", np.Data?.ToString());
             }
         }
+
+        private void Button_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            Button btn = sender as Button;
+            var tag = btn.Tag.ToString();
+            try
+            {
+                motorMonitorTimer.Enabled = false;
+                SetBitValue($"{SelectedServo?.Name}_{tag}", "true");
+            }
+            finally
+            {
+                motorMonitorTimer.Enabled = true;
+            }
+        }
+
+        private void Button_PreviewMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            Button btn = sender as Button;
+            var tag = btn.Tag.ToString();
+            try
+            {
+                motorMonitorTimer.Enabled = false;
+                SetBitValue($"{SelectedServo?.Name}_{tag}", "false");
+            }
+            finally
+            {
+                motorMonitorTimer.Enabled = true;
+            }
+        }
     }
 }
diff --git a/Plugin.TestResult/DataTable.xaml b/Plugin.TestResult/DataTable.xaml
index 12d9661..5e5d6a8 100644
--- a/Plugin.TestResult/DataTable.xaml
+++ b/Plugin.TestResult/DataTable.xaml
@@ -28,17 +28,37 @@
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
         </Grid.RowDefinitions>
-        <Grid x:Name="infoGrid" Grid.Row="0"
-             Background="LightBlue">
+        <Grid x:Name="infoGrid" Grid.Row="0" Background="LightBlue">
+            <Grid.RowDefinitions>
+                <RowDefinition/>
+                <RowDefinition/>
+            </Grid.RowDefinitions>
+
             <Grid.ColumnDefinitions>
                 <ColumnDefinition Width="Auto"/>
                 <ColumnDefinition/>
             </Grid.ColumnDefinitions>
-            <Label Grid.Row="0" Grid.RowSpan="1" FontSize="20" FontWeight="Bold" 
-                   Padding="0,0,20,0"
-                   BorderThickness="0"
-                   VerticalContentAlignment="Stretch" Background="Transparent" Content="{Binding title}"/>
+            <TextBlock FontSize="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl}, 
+Converter={StaticResource FontSizeConverter}, ConverterParameter='30,940'}"
+                   FontWeight="Bold"
+                   Margin="0"
+                   HorizontalAlignment="Center"
+                   VerticalAlignment="Center" Background="Transparent" Text="{Binding title}"/>
+
+            <ComboBox Grid.Row="1" FontSize="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl}, 
+Converter={StaticResource FontSizeConverter}, ConverterParameter='15,940'}"
+                      Style="{StaticResource FormComboxStyle}"
+                      pu:ComboBoxHelper.ClearButtonVisibility="Collapsed"
+                      BorderBrush="Black"
+                      BorderThickness="0,1,0,0"
+                      pu:ComboBoxHelper.Watermark="{DynamicResource Tr.ProductInfo}"
+                      Width="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl}, 
+Converter={StaticResource FontSizeConverter}, ConverterParameter='150,940'}"
+                      SelectedValuePath="Content"
+                      x:Name="CbProduct" SelectionChanged="CbProduct_SelectionChanged"/>
+
             <local:InfoTable x:Name="infoTable" Grid.Row="0" Grid.Column="1"
+                             Grid.RowSpan="2"
                FontSize="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl}, 
             Converter={StaticResource FontSizeConverter}, ConverterParameter='15,940'}"></local:InfoTable>
         </Grid>
@@ -83,8 +103,16 @@
             </StatusBarItem>
         </StatusBar>
 
-        <Button Grid.Row="4" Click="SetStationGlobal_Click" Style="{StaticResource SuccessButton}"
-                Content="{DynamicResource App.Start}"
-                />
+        <Grid Grid.Row="4" >
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition/>
+                <ColumnDefinition/>
+            </Grid.ColumnDefinitions>
+            <Button Click="ScanWindow_Click" Style="{StaticResource PrimaryButton}"
+        Content="{DynamicResource Tr.Scan}"/>
+            <Button Grid.Column="1" Click="SetStationGlobal_Click" Style="{StaticResource SuccessButton}"
+        Content="{DynamicResource App.Start}"/>
+        </Grid>
+        
     </Grid>
 </UserControl>
\ No newline at end of file
diff --git a/Plugin.TestResult/DataTable.xaml.cs b/Plugin.TestResult/DataTable.xaml.cs
index 39e223c..aa54886 100644
--- a/Plugin.TestResult/DataTable.xaml.cs
+++ b/Plugin.TestResult/DataTable.xaml.cs
@@ -1,8 +1,10 @@
 锘縰sing HMI.Core.Util;
 using HMI.Util;
 using Plugin.TestResult_WPF.Model;
+using Plugin.TestResult_WPF.Util;
 using SuperSocket.SocketBase.Protocol;
 using System;
+using System.CodeDom.Compiler;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.IO;
@@ -64,7 +66,7 @@
             set { RaiseAndSetIfchanged(ref tcpServerInfo, value); }
         }
 
-
+        private static CommonCodeParser codeParser = new CommonCodeParser();
         private readonly int index;
         private readonly int cellCount;
         private readonly string chartTitle;
@@ -202,6 +204,8 @@
             }
         }
 
+        private Scanner scanner;
+
         private void Window_Closed(object sender, EventArgs e)
         {
             window = null;
@@ -212,6 +216,49 @@
             ClearChartData();
             parent.SetStartFlag(this.index, true);
         }
+
+        private void ScanWindow_Click(object sender, RoutedEventArgs e)
+        {
+            if (scanner != null)
+            {
+                scanner.Activate();
+            }
+            else
+            {
+                scanner = new Scanner(this.lang("Tr.ScanTitle", title))
+                {
+                    Tag = title
+                };
+                scanner.FinishBarcode += Scanner_FinishBarcode;
+                scanner.Closed += Scanner_Closed;
+                scanner.Show();
+            }
+        }
+
+        private void Scanner_Closed(object sender, EventArgs e)
+        {
+            scanner = null;
+        }
+
+        private void Scanner_FinishBarcode(object sender, BarcodeEventArg e)
+        {
+            if (parent.SequenceContext == null) return;
+            CellInfo cellInfo = codeParser.GetCellInfoByCode(e.positionCode);
+
+            string paramName = $"StationState.StartScanner_{title}";
+            parent.engine.Globals.SetValBoolean(paramName, 0, true);
+            string positionCode = e.positionCode, snrCode = e.snrCode;
+            //TODO 闇�瑕佸鍔犳潯鐮佹牎楠屽姛鑳�
+
+            //灏嗚鍒扮殑鏉$爜鍐欏叆鍒癝N涓�
+            int dutNo = Convert.ToInt16(positionCode.Split('-')[2]) - 1;
+            string LookupStr = e.name == "Zone1" ? $"StationState.SN[{dutNo}]" : $"StationState.SN[{(dutNo + 54)}]";
+
+            parent.engine.Globals.SetValString(LookupStr, 0, snrCode);
+            string code = parent.engine.Globals.GetValString(LookupStr, 0);
+            parent.nameTables[cellInfo.OvenName].GetSingleCell(cellInfo.Index).SetStatus("hadCode");
+        }
+
 
         /// <summary>
         /// 娓呯┖涓や釜鍥捐〃鏁版嵁
@@ -426,5 +473,28 @@
                 Console.WriteLine(ex.ToString());
             }
         }
+
+        public void SetProductSource(List<string> productSource)
+        {
+            CbProduct.ItemsSource = productSource;
+        }
+
+        public void SetCurrentProduct(string currentProduct)
+        {
+            CbProduct.SelectedItem = currentProduct;
+        }
+
+        private void CbProduct_SelectionChanged(object sender, SelectionChangedEventArgs e)
+        {
+            if (e.AddedItems.Count > 0)
+            {
+                var str = e.AddedItems[0];
+                parent.SetOvenProduct(index, str.ToString());
+            }
+            else
+            {
+                parent.SetOvenProduct(index, "");
+            }
+        }
     }
 }
diff --git a/Plugin.TestResult/InfoTable.xaml b/Plugin.TestResult/InfoTable.xaml
index b817d9d..990aa4e 100644
--- a/Plugin.TestResult/InfoTable.xaml
+++ b/Plugin.TestResult/InfoTable.xaml
@@ -31,11 +31,11 @@
             <RowDefinition/>
         </Grid.RowDefinitions>
         <Grid.ColumnDefinitions>
-            <ColumnDefinition Width="1*"/>
-            <ColumnDefinition Width="2*"/>
+            <ColumnDefinition Width="Auto"/>
             <ColumnDefinition Width="1.5*"/>
-            <ColumnDefinition Width="2*"/>
+            <ColumnDefinition Width="Auto"/>
             <ColumnDefinition Width="1.5*"/>
+            <ColumnDefinition Width="Auto"/>
             <ColumnDefinition Width="2*"/>
         </Grid.ColumnDefinitions>
         <Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Tr.Temperature}"/>
diff --git a/Plugin.TestResult/Scanner.xaml b/Plugin.TestResult/Scanner.xaml
index 02780f3..d9daae5 100644
--- a/Plugin.TestResult/Scanner.xaml
+++ b/Plugin.TestResult/Scanner.xaml
@@ -16,9 +16,10 @@
              xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
              xmlns:cc="clr-namespace:HMI.Core;assembly=HMI.Core"
             InteropOwnersMask="False"
+            SizeToContent="Height"
             
             pu:WindowXModalDialog.Buttons="{x:Null}"
-        Title="{Binding title}" Height="300" Width="600">
+        Title="{Binding title}" Width="600">
     <Window.Resources>
         <local:FontSizeConverter x:Key="FontSizeConverter"/>
         <Style TargetType="Label">
@@ -32,42 +33,47 @@
         </Style>
         <Style TargetType="Button" BasedOn="{StaticResource PrimaryButton}">
             <Setter Property="FontSize" Value="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=Window}, 
-Converter={StaticResource FontSizeConverter}, ConverterParameter='100,1920'}" />
+Converter={StaticResource FontSizeConverter}, ConverterParameter='20,1920'}" />
         </Style>
     </Window.Resources>
 
-    <Grid Margin="20">
+    <Grid Margin="10">
         <Grid.RowDefinitions>
             <RowDefinition Height="Auto"/>
-            <RowDefinition />
+            <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
         </Grid.RowDefinitions>
 
-        <Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" HorizontalAlignment="Stretch" Padding="0"
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition/>
+            <ColumnDefinition/>
+        </Grid.ColumnDefinitions>
+
+        <!--<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" HorizontalAlignment="Stretch" Padding="0"
                Content="{DynamicResource Tr.PleaseScanSnr}"
                Margin="0,0,0,20"
-               />
-        <StackPanel Grid.Row="1">
-            <pu:FormGroup 
-                dp:FormGroupHelper.IsRequired="True"
-                Header="{DynamicResource Tr.PositionSnr}">
-                <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="positionText" TextWrapping="NoWrap"/>
-            </pu:FormGroup>
+               />-->
+        <GroupBox Grid.Row="1" Header="{DynamicResource Tr.PleaseScanSnr}" Padding="0,0,0,10" Grid.ColumnSpan="2" >
+            <StackPanel>
+                <pu:FormGroup 
+         Header="{DynamicResource Tr.PositionSnr}"
+         dp:FormGroupHelper.IsRequired="True"
+         Style="{StaticResource InputFormGroupStyle}">
+                    <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="positionText" TextWrapping="NoWrap"/>
+                </pu:FormGroup>
 
-            <pu:FormGroup 
-                dp:FormGroupHelper.IsRequired="True"
-                Header="{DynamicResource Tr.ProductSnr}">
-                <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="snrText" TextWrapping="NoWrap"/>
-            </pu:FormGroup>
-        </StackPanel>
+                <pu:FormGroup 
+         Header="{DynamicResource Tr.ProductSnr}"
+         dp:FormGroupHelper.IsRequired="True" Style="{StaticResource InputFormGroupStyle}">
+                    <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="snrText" TextWrapping="NoWrap"/>
+                </pu:FormGroup>
+            </StackPanel>
 
-        <StackPanel Style="{StaticResource ButtonGroup}" 
-                    dp:StackPanelHelper.ChildSpace="50"
-                    HorizontalAlignment="Center" Grid.Row="2">
-            <Button Click="Con_Click" 
-                    Content="{DynamicResource Tr.ContinueScan}"/>
-            <Button Click="Ok_Click" 
-                    Content="{DynamicResource Tr.FinishScan}"/>
-        </StackPanel>
+        </GroupBox>
+
+        <Button Grid.Row="2" Click="Con_Click" Style="{StaticResource PrimaryButton}"
+        Content="{DynamicResource Tr.ContinueScan}"/>
+        <Button Grid.Row="2" Grid.Column="1" Click="Ok_Click" Style="{StaticResource SuccessButton}"
+        Content="{DynamicResource Tr.FinishScan}"/>
     </Grid>
 </pu:WindowX>
diff --git a/Plugin.TestResult/SingleCell.xaml b/Plugin.TestResult/SingleCell.xaml
index 8db4b23..d944288 100644
--- a/Plugin.TestResult/SingleCell.xaml
+++ b/Plugin.TestResult/SingleCell.xaml
@@ -51,20 +51,22 @@
         </Style>
     </UserControl.Style>
     <Grid>
-        <Grid.RowDefinitions>
-            <RowDefinition Height="Auto"/>
-            <RowDefinition/>
-        </Grid.RowDefinitions>
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition/>
+            <ColumnDefinition/>
+        </Grid.ColumnDefinitions>
         <!--<TextBox Grid.Row="0" IsReadOnly="True" TextAlignment="Center" Margin="0,0,0,3" FontSize="15" 
                  Background="Transparent" BorderThickness="0" Text="{Binding title}"/>-->
-        <Label Grid.Row="0" HorizontalContentAlignment="Left" VerticalContentAlignment="Center" Padding="0" Margin="0,0,0,3" 
+        <Label Grid.Column="0" HorizontalContentAlignment="Left" VerticalContentAlignment="Top" Padding="0" Margin="0" 
                FontSize="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl}, 
             Converter={StaticResource FontSizeConverter}, ConverterParameter='15,103'}" 
                FontWeight="Bold"
          Background="Transparent" BorderThickness="0" Content="{Binding title}"/>
-        <Image Grid.Row="1">
+        <Image Grid.Column="1">
             <Image.Style>
                 <Style TargetType="Image">
+                    <Setter Property="HorizontalAlignment" Value="Center"/>
+                    <Setter Property="VerticalAlignment" Value="Bottom"/>
                     <Style.Triggers>
                         <DataTrigger Binding="{Binding status}" Value="ok">
                             <Setter Property="Source" Value="Resource/ok2.png"/>
diff --git a/Plugin.TestResult/TestResultView.xaml.cs b/Plugin.TestResult/TestResultView.xaml.cs
index 8cc204f..5d452eb 100644
--- a/Plugin.TestResult/TestResultView.xaml.cs
+++ b/Plugin.TestResult/TestResultView.xaml.cs
@@ -35,7 +35,7 @@
         /// <summary>
         /// 娴嬭瘯搴忓垪鎿嶄綔鍙ユ焺
         /// </summary>
-        private SequenceContext SequenceContext = null;
+        public SequenceContext SequenceContext { get; private set; } = null;
         /// <summary>
         /// 娴嬭瘯妯″瀷搴忓垪
         /// </summary>
@@ -430,5 +430,25 @@
                 table.ClearNumber();
             }
         }
+
+        public void SetProductSource(List<string> productSource)
+        {
+            foreach (var table in tables)
+            {
+                table.SetProductSource(productSource);
+            }
+        }
+
+        public void SetCurrentProduct(string currentProduct)
+        {
+            foreach (var table in tables)
+            {
+                table.SetCurrentProduct(currentProduct);
+            }
+        }
+
+        public void SetOvenProduct(int index, string product) { 
+            
+        }
     }
 }
diff --git a/Plugin.TestResult/ViewControl.xaml b/Plugin.TestResult/ViewControl.xaml
index 7778d89..05f971c 100644
--- a/Plugin.TestResult/ViewControl.xaml
+++ b/Plugin.TestResult/ViewControl.xaml
@@ -41,6 +41,7 @@
         <StackPanel Grid.Column="1" Background="#FFF">
             <GroupBox Header="{DynamicResource Tr.ProductInfo}">
                 <ComboBox x:Name="modelSelect" Style="{StaticResource FormComboxStyle}"
+                          pu:ComboBoxHelper.ClearButtonVisibility="Collapsed"
                           SelectionChanged="modelSelect_SelectionChanged"></ComboBox>
             </GroupBox>
             <GroupBox Header="{DynamicResource Tr.Status}">
@@ -103,11 +104,11 @@
                     <Button x:Name="btnClear" Click="BtnClear_Click" Style="{StaticResource PrimaryButton}"
                             Content="{DynamicResource Ts.BtnClearNum}"/>
 
-                    <Button x:Name="btnScan1" Click="Scan_Click_1" Style="{StaticResource PrimaryButton}"
+                    <!--<Button x:Name="btnScan1" Click="Scan_Click_1" Style="{StaticResource PrimaryButton}"
                             Content="{DynamicResource Tr.Zone1Scan}"/>
 
                     <Button x:Name="btnScan2" Click="Scan_Click_2" Style="{StaticResource PrimaryButton}"
-                            Content="{DynamicResource Tr.Zone2Scan}"/>
+                            Content="{DynamicResource Tr.Zone2Scan}"/>-->
                 </StackPanel>
             </GroupBox>
         </StackPanel>
diff --git a/Plugin.TestResult/ViewControl.xaml.cs b/Plugin.TestResult/ViewControl.xaml.cs
index ef1b83e..3e4794a 100644
--- a/Plugin.TestResult/ViewControl.xaml.cs
+++ b/Plugin.TestResult/ViewControl.xaml.cs
@@ -1,7 +1,7 @@
 锘縰sing HMI.Base;
+using HMI.Core.Entity;
+using HMI.Core.Manager;
 using HMI.Core.Util;
-using HMI.ParameterManage.Entity;
-using HMI.ParameterManage.Util;
 using HMI.UI;
 using HMI.UI.Util;
 using log4net;
@@ -11,7 +11,6 @@
 using Plugin.TestResult_WPF.Util;
 using System;
 using System.Collections.Generic;
-using System.ComponentModel;
 using System.IO;
 using System.Linq;
 using System.Windows;
@@ -157,16 +156,21 @@
 
         public void LoadProduct()
         {
-            List<ProductData> products = ParameterManager.GetProducts<List<ProductData>>()
+            List<ProductData> products = ProductManager.GetProducts<List<ProductData>>()
                 ?? new List<ProductData>();
             this.modelSelect.Items.Clear();
+            List<string> productSource = new List<string>();
             foreach (var prd in products)
             {
                 this.modelSelect.Items.Add(prd.Name);
+                productSource.Add(prd.Name);
             }
+            testResultView.SetProductSource(productSource);
+
             if (products.Count > 0)
             {
                 this.modelSelect.SelectedIndex = 0;
+                testResultView.SetCurrentProduct(products[0].Name);
             }
         }
 
@@ -174,7 +178,7 @@
         {
             try
             {
-                List<ParameterData> parameters = ParameterManager.GetParametersByProductName(this.modelSelect.SelectedItem.ToString());
+                List<ParameterData> parameters = ProductManager.GetParametersByProductName(this.modelSelect.SelectedItem.ToString());
                 //MessageBox.Show("ParaFile:" + ParaFile + "ParaCount:" + this.parameterManager.Parameters.Count.ToString());
                 //this.TestTemperature = Convert.ToDouble(this.parameterManager.Find("TestTemperature").ParmValue);
                 //this.TestTime = Convert.ToInt32(this.parameterManager.Find("TestTime").ParmValue);
diff --git a/Plugin.TsExecution/TsExecution.xaml.cs b/Plugin.TsExecution/TsExecution.xaml.cs
index a674983..5239693 100644
--- a/Plugin.TsExecution/TsExecution.xaml.cs
+++ b/Plugin.TsExecution/TsExecution.xaml.cs
@@ -1,8 +1,8 @@
 锘縰sing HMI.Base;
 using HMI.Core;
+using HMI.Core.Entity;
+using HMI.Core.Manager;
 using HMI.Core.Util;
-using HMI.ParameterManage.Entity;
-using HMI.ParameterManage.Util;
 using HMI.UI;
 using HMI.UI.Util;
 using HMI.Util;
@@ -10,13 +10,11 @@
 using NationalInstruments.TestStand.Interop.UI.Ax;
 using Panuon.WPF.UI;
 using ScottPlot;
-using ScottPlot.AxisLimitManagers;
 using ScottPlot.Plottables;
 using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.ComponentModel;
-using System.Drawing;
 using System.IO;
 using System.Linq;
 using System.Timers;
@@ -173,7 +171,7 @@
 
         private void InitProducts()
         {
-            Products = ParameterManager.GetProducts<ObservableCollection<ProductData>>();
+            Products = ProductManager.GetProducts<ObservableCollection<ProductData>>();
             if (Products != null && Products.Count > 0)
             {
                 List<ParameterData> parameters =
@@ -641,7 +639,7 @@
         private void SetTestSeqFile(ProductData product)
         {
             string name = product.Name;
-            List<ParameterData> parameters = ParameterManager.GetParametersByProductName(name);
+            List<ParameterData> parameters = ProductManager.GetParametersByProductName(name);
             testSeqFile = parameters.FirstOrDefault(p => p.Name == "TestSequence")?.Value?.ToString();
         }
 
diff --git a/WPFProject/App.config b/WPFProject/App.config
index 24552f6..7b2d8dc 100644
--- a/WPFProject/App.config
+++ b/WPFProject/App.config
@@ -1,5 +1,9 @@
 锘�<?xml version="1.0" encoding="utf-8"?>
 <configuration>
+	<configSections>
+		<section name="TestResultConfig" type="System.Configuration.SingleTagSectionHandler" />
+	</configSections>
+	<TestResultConfig Row="1" Column="2" PortStart="6660" />
 	<startup>
 		<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
 	</startup>
diff --git a/WPFProject/App.xaml.cs b/WPFProject/App.xaml.cs
index 545b7cc..214e876 100644
--- a/WPFProject/App.xaml.cs
+++ b/WPFProject/App.xaml.cs
@@ -18,6 +18,7 @@
     public partial class App : Application
     {
         private ILog log;
+        private LogCleanner logCleanner;
         public App()
         {
             log = LogManager.GetLogger("APP");
@@ -28,7 +29,7 @@
         {
             var ex = e.ExceptionObject as Exception;
             log.Error("UnhandledException.", ex);
-            MsgBoxHelper.ErrorMessage(Current.MainWindow, this.lang("App.AppUnhandledException",ex.Message),
+            MsgBoxHelper.ErrorMessage(Current.MainWindow, this.lang("App.AppUnhandledException", ex.Message),
                     this.lang("App.Error"), MessageBoxButton.OK,
                     MessageBoxIcon.Error, DefaultButton.YesOK, 5);
         }
@@ -39,8 +40,9 @@
             mutex = new Mutex(true, "TestPlatform_OnlyRun_CRNS");
             if (mutex.WaitOne(0, false))
             {
+                logCleanner = new LogCleanner();
                 AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
-
+                LoggerHelper.Register(new CommonLogger());
                 LangUtil.Load();
                 SystemSetting.SystemSettingChanged += SystemSetting_SystemSettingChanged;
                 ToastHelper.Register(new MainToastHandler());
diff --git a/WPFProject/Controls/MainTabControl.xaml.cs b/WPFProject/Controls/MainTabControl.xaml.cs
index 1e57271..41e8557 100644
--- a/WPFProject/Controls/MainTabControl.xaml.cs
+++ b/WPFProject/Controls/MainTabControl.xaml.cs
@@ -2,6 +2,7 @@
 using HMI.Core.Api;
 using Microsoft.DwayneNeed.Interop;
 using Panuon.WPF.UI;
+using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.Runtime.CompilerServices;
@@ -94,6 +95,7 @@
             }
         }
 
+        private Dictionary<PluginInfo, PluginInfo> selectedCache = new Dictionary<PluginInfo, PluginInfo>();
 
         private ObservableCollection<PluginInfo> _SubMenuItems;
 
@@ -138,20 +140,27 @@
             {
                 var data = (PluginInfo)e.AddedItems[0];
                 SubMenuItems = data?.Children;
+                if (selectedCache.ContainsKey(data))
+                {
+                    SelectedSubMenu = selectedCache[data];
+                }
+                else if (SubMenuItems.Count > 0)
+                {
+                    SelectedSubMenu = SubMenuItems[0];
+                }
             }
         }
-
-        private void SwithMenu(PluginInfo menu)
-        {
-
-        }
-
 
         private void SubMenu_SelectionChanged(object sender, SelectionChangedEventArgs e)
         {
             if (e.AddedItems?.Count > 0)
             {
                 var data = (PluginInfo)e.AddedItems[0];
+                if (SelectedMenu != null)
+                {
+                    selectedCache[SelectedMenu] = data;
+                }
+
                 var content = data.Content;
                 var last = page.Child;
                 PluginPackage.Get(last)?.DeActive();
@@ -186,6 +195,11 @@
                 UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
             };
             winFormHolder.SetBinding(WinFormHolder.MaskVisiableProperty, binding);
+            if (MenuItems != null && MenuItems.Count > 0)
+            {
+                SelectedMenu = MenuItems[0];
+            }
+            Loaded -= UserControl_Loaded;
         }
     }
 }
diff --git a/WPFProject/Lang/en-US/DOControl.json b/WPFProject/Lang/en-US/DOControl.json
new file mode 100644
index 0000000..0d57ccf
--- /dev/null
+++ b/WPFProject/Lang/en-US/DOControl.json
@@ -0,0 +1,11 @@
+{
+  "doc.title": "DO Control",
+  "doc.list": "Data List",
+  "doc.group": "Group",
+  "doc.name": "Name",
+  "doc.address": "Address",
+  "doc.readOnly": "Read Only",
+  "doc.pressDownValue": "Press Down Value",
+  "doc.pressUpValue": "Press Up Value",
+  "doc.controlType": "Control Type"
+}
\ No newline at end of file
diff --git a/WPFProject/Lang/en-US/LogViewer.json b/WPFProject/Lang/en-US/LogViewer.json
new file mode 100644
index 0000000..4bb5fcf
--- /dev/null
+++ b/WPFProject/Lang/en-US/LogViewer.json
@@ -0,0 +1,10 @@
+{
+  "log.title": "Log Viewer",
+  "log.level": "Level",
+  "log.time": "Time",
+  "log.source": "Source",
+  "log.text": "Content",
+  "log.autoRefresh": "Auto Refresh",
+  "log.timeCantBeNull": "Please select start time or end time",
+  "log.timeRangeError": "End time must after start time",
+}
\ No newline at end of file
diff --git a/WPFProject/Lang/en-US/TestResult.json b/WPFProject/Lang/en-US/TestResult.json
index 65e4717..96391b0 100644
--- a/WPFProject/Lang/en-US/TestResult.json
+++ b/WPFProject/Lang/en-US/TestResult.json
@@ -21,9 +21,11 @@
   "Tr.GetGunData": "Receive scanner failed",
   "Tr.PleaseInputSnrs": "Please scan serials",
 
-  "Tr.ProductInfo": "Product info",
+  "Tr.ProductInfo": "Select Product",
   "Tr.MES_CLOSE": "MES is closed",
   "Tr.MES_OPEN": "MES is open",
+  "Tr.Scan": "Scan SNR",
+  "Tr.ScanTitle": "Zone {0} Scan SNR",
   "Tr.Zone1Scan": "Scan to zone1",
   "Tr.Zone2Scan": "Scan to zone2"
 }
\ No newline at end of file
diff --git a/WPFProject/Lang/en-US/app.json b/WPFProject/Lang/en-US/app.json
index 4d1da20..960e0d5 100644
--- a/WPFProject/Lang/en-US/app.json
+++ b/WPFProject/Lang/en-US/app.json
@@ -123,6 +123,7 @@
   "HMI.Menu.ParentName": "Previous menu",
   "HMI.Menu.DllPath": "Dll path",
   "HMI.Menu.ClassName": "Class name",
+  "HMI.Menu.Sort": "Sort Num",
 
   "HMI.User": "User",
   "HMI.User.Title": "User list",
diff --git a/WPFProject/Lang/en-US/setting.json b/WPFProject/Lang/en-US/setting.json
index 65562a1..bf5663d 100644
--- a/WPFProject/Lang/en-US/setting.json
+++ b/WPFProject/Lang/en-US/setting.json
@@ -2,6 +2,7 @@
   "Setting.Logo": "Logo",
   "Setting.Title": "Title",
   "Setting.Lang": "Language",
+  "Setting.LogExpiredDays": "Log Expired",
   "Setting.PlcIp": "PLC IP",
   "Setting.PlcEnabled": "Enabled",
   "Setting.BasicSetting": "Basic setting",
diff --git a/WPFProject/Lang/zh-CN/DOControl.json b/WPFProject/Lang/zh-CN/DOControl.json
new file mode 100644
index 0000000..2d37d79
--- /dev/null
+++ b/WPFProject/Lang/zh-CN/DOControl.json
@@ -0,0 +1,11 @@
+{
+  "doc.title": "DO鎺у埗",
+  "doc.list": "鏁版嵁鍒楄〃",
+  "doc.group": "缁�",
+  "doc.name": "鍚嶇О",
+  "doc.address": "鍦板潃",
+  "doc.readOnly": "鍙",
+  "doc.pressDownValue": "鎸変笅鍊�",
+  "doc.pressUpValue": "寮硅捣鍊�",
+  "doc.controlType": "鎺т欢绫诲瀷"
+}
\ No newline at end of file
diff --git a/WPFProject/Lang/zh-CN/LogViewer.json b/WPFProject/Lang/zh-CN/LogViewer.json
new file mode 100644
index 0000000..161fe97
--- /dev/null
+++ b/WPFProject/Lang/zh-CN/LogViewer.json
@@ -0,0 +1,10 @@
+{
+  "log.title": "鏃ュ織鏌ヨ",
+  "log.level": "绾у埆",
+  "log.time": "鏃堕棿",
+  "log.source": "鏉ユ簮",
+  "log.text": "鍐呭",
+  "log.autoRefresh": "鑷姩鍒锋柊",
+  "log.timeCantBeNull": "璇烽�夋嫨寮�濮嬫椂闂存垨缁撴潫鏃堕棿",
+  "log.timeRangeError": "缁撴潫鏃堕棿涓嶈兘灏忎簬寮�濮嬫椂闂�",
+}
\ No newline at end of file
diff --git a/WPFProject/Lang/zh-CN/TestResult.json b/WPFProject/Lang/zh-CN/TestResult.json
index a74def8..7a66b2f 100644
--- a/WPFProject/Lang/zh-CN/TestResult.json
+++ b/WPFProject/Lang/zh-CN/TestResult.json
@@ -24,6 +24,8 @@
   "Tr.ProductInfo": "浜у搧淇℃伅",
   "Tr.MES_CLOSE": "MES鍏�",
   "Tr.MES_OPEN": "MES寮�",
+  "Tr.Scan": "鎵爜",
+  "Tr.ScanTitle": "{0}鍖烘壂鐮�",
   "Tr.Zone1Scan": "涓�鍖烘壂鐮�",
   "Tr.Zone2Scan": "浜屽尯鎵爜"
 }
\ No newline at end of file
diff --git a/WPFProject/Lang/zh-CN/app.json b/WPFProject/Lang/zh-CN/app.json
index deacc3e..036fb63 100644
--- a/WPFProject/Lang/zh-CN/app.json
+++ b/WPFProject/Lang/zh-CN/app.json
@@ -123,6 +123,7 @@
   "HMI.Menu.ParentName": "涓婄骇鑿滃崟",
   "HMI.Menu.DllPath": "Dll璺緞",
   "HMI.Menu.ClassName": "绫诲悕",
+  "HMI.Menu.Sort": "鎺掑簭",
 
   "HMI.User": "鐢ㄦ埛",
   "HMI.User.Title": "鐢ㄦ埛鍒楄〃",
diff --git a/WPFProject/Lang/zh-CN/setting.json b/WPFProject/Lang/zh-CN/setting.json
index 9acdef6..92e8ceb 100644
--- a/WPFProject/Lang/zh-CN/setting.json
+++ b/WPFProject/Lang/zh-CN/setting.json
@@ -2,6 +2,7 @@
   "Setting.Logo": "Logo",
   "Setting.Title": "鏍囬",
   "Setting.Lang": "璇█",
+  "Setting.LogExpiredDays": "鏃ュ織淇濆瓨鏃堕棿",
   "Setting.PlcIp": "PLC IP",
   "Setting.PlcEnabled": "鏄惁鍚敤",
   "Setting.BasicSetting": "鍩虹璁剧疆",
diff --git a/WPFProject/Models/Program.cs b/WPFProject/Models/Program.cs
index e3b126c..e448dfd 100644
--- a/WPFProject/Models/Program.cs
+++ b/WPFProject/Models/Program.cs
@@ -7,7 +7,7 @@
 
 namespace WPFProject.Models
 {
-    public class Program : NotifyBase, IProgram
+    public class Program : NotifyBase
     {
         /// <summary>
         /// 绋嬪簭鍚�
@@ -53,9 +53,9 @@
         /// <summary>
         /// 璁$畻涓存椂鍙傛暟
         /// </summary>
-        private Dictionary<string, Param> _TempVars = new Dictionary<string, Param>();
+        private Dictionary<string, object> _TempVars = new Dictionary<string, object>();
         [XmlIgnore]
-        public Dictionary<string, Param> TempVars
+        public Dictionary<string, object> TempVars
         {
             get { return _TempVars; }
             set { _TempVars = value; }
diff --git a/WPFProject/Models/Step.cs b/WPFProject/Models/Step.cs
index 4e26b1d..e07959b 100644
--- a/WPFProject/Models/Step.cs
+++ b/WPFProject/Models/Step.cs
@@ -8,7 +8,7 @@
 {
     public class Step : NotifyBase
     {
-        private static readonly Type InterfaceType = typeof(IStepRunner);
+        //private static readonly Type InterfaceType = typeof(IStepRunner);
         private static readonly Dictionary<string, Assembly> Cache = new Dictionary<string, Assembly>();
         #region 瀛楁
         [XmlIgnore]
@@ -63,13 +63,13 @@
 
         public void RunStep()
         {
-            Assembly assembly = Assembly.LoadFrom(DllPath);
-            Type type = assembly.GetType(ClassName);
-            if (type != null && InterfaceType.IsAssignableFrom(type))
-            {
-                IStepRunner stepRunner = (IStepRunner)assembly.CreateInstance(ClassName);
-                stepRunner.RunStep(Sequence.Program.TempVars);
-            }
+            //Assembly assembly = Assembly.LoadFrom(DllPath);
+            //Type type = assembly.GetType(ClassName);
+            //if (type != null && InterfaceType.IsAssignableFrom(type))
+            //{
+            //    IStepRunner stepRunner = (IStepRunner)assembly.CreateInstance(ClassName);
+            //    stepRunner.RunStep(Sequence.Program.TempVars);
+            //}
         }
     }
 }
diff --git a/WPFProject/PanuonWindow.xaml b/WPFProject/PanuonWindow.xaml
index a9ffe87..ef1eee4 100644
--- a/WPFProject/PanuonWindow.xaml
+++ b/WPFProject/PanuonWindow.xaml
@@ -111,7 +111,7 @@
     <Grid>
         <Grid.RowDefinitions>
             <RowDefinition/>
-            <RowDefinition Height="30"/>
+            <RowDefinition Height="Auto"/>
         </Grid.RowDefinitions>
 
         <ctl:MainTabControl Grid.Row="0" x:Name="menuTab">
@@ -121,6 +121,6 @@
         <!--<ctl:MenuTabControl Grid.Row="1" x:Name="menuTab" OnTabChange="menuTab_OnTabChange"> </ctl:MenuTabControl>-->
 
 
-        <StatusBar Grid.Row="1" Background="{StaticResource HoriBar}"></StatusBar>
+        <!--<StatusBar Grid.Row="1" Background="{StaticResource HoriBar}"></StatusBar>-->
     </Grid>
 </pu:WindowX>
diff --git a/WPFProject/TextFile1.txt b/WPFProject/TextFile1.txt
index 228ea1f..a1ea64f 100644
--- a/WPFProject/TextFile1.txt
+++ b/WPFProject/TextFile1.txt
@@ -12,6 +12,7 @@
 move /y "$(TargeDir)Plugin.TestResult.dll" $(TargeDir)Plugin\Plugin.TestResult.dll"
 move /y "$(TargeDir)Plugin.TsExecution.dll" $(TargeDir)Plugin\Plugin.TsExecution.dll"
 move /y "$(TargeDir)Plugin.ServoManage.dll" $(TargeDir)Plugin\Plugin.ServoManage.dll"
+move /y "$(TargeDir)Plugin.DOControl.dll" $(TargeDir)Plugin\Plugin.DOControl.dll"
 
 setlocal enabledelayedexpansion
 
diff --git a/WPFProject/Utils/CommonLogger.cs b/WPFProject/Utils/CommonLogger.cs
new file mode 100644
index 0000000..fb0b770
--- /dev/null
+++ b/WPFProject/Utils/CommonLogger.cs
@@ -0,0 +1,103 @@
+锘縰sing HMI.Core.Api;
+using HMI.Core.Util;
+using log4net;
+using System;
+using System.IO;
+
+namespace WPFProject.Utils
+{
+    class CommonLogger : ILogWriter
+    {
+        private static ILog log = LogManager.GetLogger("CommonLogger");
+        private SqliteHelper sqliteHelper;
+        private DateTime currentDate;
+
+        public CommonLogger()
+        {
+            RefreshConnection();
+        }
+
+        private SqliteHelper InitHelper(DateTime date)
+        {
+            string file = Path.Combine(PathHelper.LogViewerDir, $"log_{date.ToString("yyyy-MM-dd")}.db");
+            if (!File.Exists(file))
+            {
+                SqliteHelper.CreateDatabase(file);
+            }
+            var sqliteHelper = new SqliteHelper(file);
+            sqliteHelper.ExecuteSql(@"CREATE TABLE IF NOT EXISTS log (
+                    id INTEGER PRIMARY KEY AUTOINCREMENT,
+                    level TEXT NOT NULL,
+                    text TEXT NOT NULL,
+                    source TEXT,
+                    time TIME
+                    );");
+            return sqliteHelper;
+        }
+
+        private void RefreshConnection()
+        {
+            if (sqliteHelper == null)
+            {
+                currentDate = DateTime.Today;
+                sqliteHelper = InitHelper(currentDate);
+                return;
+            }
+            else
+            {
+                lock (this)
+                {
+                    try
+                    {
+                        var current = DateTime.Today;
+                        if (current == currentDate) return;
+                        sqliteHelper?.Close();
+                        sqliteHelper = null;
+                        currentDate = current;
+                        sqliteHelper = InitHelper(currentDate);
+                    }
+                    catch (Exception ex)
+                    {
+                        log.Error("RefreshConnection Failed.", ex);
+                        sqliteHelper = null;
+                    }
+                }
+            }
+        }
+
+        public void Debug(string source, string content)
+        {
+            DoLog("Debug", source, content);
+        }
+
+        public void Info(string source, string content)
+        {
+            DoLog("Info", source, content);
+        }
+        public void Warn(string source, string content)
+        {
+            DoLog("Warn", source, content);
+        }
+
+        public void Error(string source, string content)
+        {
+            DoLog("Error", source, content);
+        }
+
+        private void DoLog(string level, string source, string content)
+        {
+            if (currentDate != DateTime.Today || sqliteHelper == null)
+            {
+                RefreshConnection();
+            }
+            if (sqliteHelper == null)
+            {
+                log.Warn($"Level:{level} Source:{source} {content}");
+            }
+            else
+            {
+                sqliteHelper.ExecuteSql($"INSERT INTO log (level, text, source, time) VALUES ('{level}', '{content}', '{source}', '{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}');");
+            }
+        }
+    }
+}
diff --git a/WPFProject/Utils/LogCleanner.cs b/WPFProject/Utils/LogCleanner.cs
new file mode 100644
index 0000000..d088a1a
--- /dev/null
+++ b/WPFProject/Utils/LogCleanner.cs
@@ -0,0 +1,109 @@
+锘縰sing HMI.Core.Setting;
+using HMI.Core.Util;
+using log4net;
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Timers;
+
+namespace WPFProject.Utils
+{
+    public class LogCleanner
+    {
+
+        private ILog log = LogManager.GetLogger("LogCleanner");
+
+        private long ms = 24 * 60 * 60 * 1000;
+        private string[] _fileExtensions = new string[] { ".log", ".db" };
+        private string _filePrefix = "log";
+        private Timer timer;
+        public LogCleanner()
+        {
+            timer = new Timer();
+            timer.Interval = ms;
+            timer.Elapsed += Timer_Elapsed;
+            timer.Enabled = true;
+            Task.Run(() => {
+                Clean();
+            });
+        }
+
+        private void Clean()
+        {
+            int days = SystemSetting.Instance.LogExpiredDays;
+            if (days == null || days <= 0) return;
+
+            // 璁$畻鎴鏃ユ湡
+            DateTime cutoffDate = DateTime.Now.AddDays(-days);
+
+            string folder1 = PathHelper.LogViewerDir;
+            // 妫�鏌ョ洰褰曟槸鍚﹀瓨鍦�
+            if (Directory.Exists(folder1))
+            {
+                // 鑾峰彇鎵�鏈夌鍚堟潯浠剁殑鏂囦欢
+                var files = Directory.GetFiles(folder1)
+                    .Where(file =>
+                        Path.GetFileName(file).StartsWith(_filePrefix, StringComparison.OrdinalIgnoreCase) &&
+                        _fileExtensions.Any(ext => file.EndsWith(ext, StringComparison.OrdinalIgnoreCase)) &&
+                        File.GetLastWriteTime(file) < cutoffDate)
+                    .ToList();
+                foreach (var file in files)
+                {
+                    try
+                    {
+                        File.Delete(file);
+                    }
+                    catch (Exception ex)
+                    {
+                        log.Warn($"LogFile:{file} Delete Failed.", ex);
+                    }
+                }
+            }
+
+            folder1 = Path.Combine(PathHelper.AppFolder, "Log");
+            // 妫�鏌ョ洰褰曟槸鍚﹀瓨鍦�
+            if (Directory.Exists(folder1))
+            {
+                // 鑾峰彇鎵�鏈夌鍚堟潯浠剁殑鏂囦欢
+                var files = Directory.GetFiles(folder1)
+                    .Where(file =>
+                        Path.GetFileName(file).StartsWith(_filePrefix, StringComparison.OrdinalIgnoreCase) &&
+                        _fileExtensions.Any(ext => file.EndsWith(ext, StringComparison.OrdinalIgnoreCase)) &&
+                        File.GetLastWriteTime(file) < cutoffDate)
+                    .ToList();
+                foreach (var file in files)
+                {
+                    try
+                    {
+                        File.Delete(file);
+                    }
+                    catch (Exception ex)
+                    {
+                        log.Warn($"LogFile:{file} Delete Failed.", ex);
+                    }
+                }
+            }
+        }
+
+
+
+        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
+        {
+            try
+            {
+                timer.Enabled = false;
+                Clean();
+                log.Info("Clean Log File Success.");
+            }
+            catch (System.Exception ex)
+            {
+                log.Error("Clean Log File Failed.", ex);
+            }
+            finally
+            {
+                timer.Enabled = true;
+            }
+        }
+    }
+}
diff --git a/WPFProject/WPFProject.csproj b/WPFProject/WPFProject.csproj
index 49262a9..51dae9e 100644
--- a/WPFProject/WPFProject.csproj
+++ b/WPFProject/WPFProject.csproj
@@ -273,9 +273,11 @@
     </Compile>
     <Compile Include="Utils\BinUtil.cs" />
     <Compile Include="Utils\CanvasHelper.cs" />
+    <Compile Include="Utils\CommonLogger.cs" />
     <Compile Include="Utils\DdDragHandler.cs" />
     <Compile Include="Utils\DdDropHandler.cs" />
     <Compile Include="Utils\Encryptor.cs" />
+    <Compile Include="Utils\LogCleanner.cs" />
     <Compile Include="Utils\MainToastHandler.cs" />
     <Compile Include="Utils\Resize.cs" />
     <Compile Include="Utils\Win32Helper.cs" />
@@ -497,6 +499,18 @@
     <Content Include="Lang\en-US\ServoManage.json">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Content>
+    <Content Include="Lang\zh-CN\DOControl.json">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="Lang\zh-CN\LogViewer.json">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="Lang\en-US\DOControl.json">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="Lang\en-US\LogViewer.json">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
     <None Include="packages.config" />
     <None Include="Properties\app.manifest" />
     <None Include="Properties\Settings.settings">
@@ -548,6 +562,10 @@
       <Project>{de4205c6-5923-46fd-a4b9-65169bf61c75}</Project>
       <Name>HMI.MenuManage</Name>
     </ProjectReference>
+    <ProjectReference Include="..\Plugin.DOControl\Plugin.DOControl.csproj">
+      <Project>{306a83dc-a7db-4057-baa7-477d8756f5d9}</Project>
+      <Name>Plugin.DOControl</Name>
+    </ProjectReference>
     <ProjectReference Include="..\Plugin.ServoManage\Plugin.ServoManage.csproj">
       <Project>{1853e00f-6865-4bc6-974b-6a5e1c00bafe}</Project>
       <Name>Plugin.ServoManage</Name>
@@ -594,6 +612,7 @@
 move /y "$(TargeDir)Plugin.TestResult.dll" $(TargeDir)Plugin\Plugin.TestResult.dll"
 move /y "$(TargeDir)Plugin.TsExecution.dll" $(TargeDir)Plugin\Plugin.TsExecution.dll"
 move /y "$(TargeDir)Plugin.ServoManage.dll" $(TargeDir)Plugin\Plugin.ServoManage.dll"
+move /y "$(TargeDir)Plugin.DOControl.dll" $(TargeDir)Plugin\Plugin.DOControl.dll"
 
 setlocal enabledelayedexpansion
 

--
Gitblit v1.9.1