From 4c3eb7f334abb8129b57e86f2935d75d0b3070b4 Mon Sep 17 00:00:00 2001
From: alielie <alielie@git.com>
Date: 星期一, 03 三月 2025 18:55:31 +0800
Subject: [PATCH] 完善UI;新增TestResult;

---
 HMI.ParameterManage/ProductModal.xaml.cs            |   91 
 HMI.SettingControl/SettingControl.xaml.cs           |   72 
 HMI.ParameterManage/ParameterManage.xaml.cs         |  113 
 HMI.IOMonitor/IPManageModal.xaml.cs                 |    1 
 Plugin.TestResult/Model/CellInfo.cs                 |    9 
 Plugin.TestResult/Resource/ready.png                |    0 
 Plugin.TestResult/SingleCell.xaml                   |  101 +
 HMI.LogViewer/LogViewer.xaml                        |  126 +
 Plugin.TestResult/DataTable.xaml                    |   84 
 HMI.DataViewer/DataViewer.xaml                      |   31 
 Plugin.TestResult/Resource/checkInFailed2.png       |    0 
 Plugin.TestResult/TestResultView.xaml.cs            |  383 ++++
 HMI.ParameterManage/Entity/ParameterData.cs         |    8 
 Plugin.TestResult/Model/TcpServer.cs                |   41 
 HMI.ParameterManage/Model/SqlModel.cs               |   32 
 Plugin.TestResult/Properties/Resources.Designer.cs  |   62 
 HMI.ParameterManage/HMI.ParameterManage.csproj      |    3 
 Plugin.TestResult/Chart.xaml.cs                     |  314 +++
 HMI.IOMonitor/IOMonitor.xaml                        |   12 
 WPFProject/LoadingWindow.xaml.cs                    |    7 
 Plugin.TestResult/InfoTable.xaml.cs                 |  153 +
 HMI.ParameterManage/ParameterModal.xaml             |    9 
 Plugin.TestResult/Chart.xaml                        |   14 
 HMI.LogViewer/Properties/Resources.Designer.cs      |   62 
 Plugin.TestResult/Properties/Settings.settings      |    7 
 HMI.ParameterManage/ParameterManage.xaml            |   82 
 Plugin.TestResult/Model/DataTableModel.cs           |    7 
 HMI.Core/Api/IConfigManager.cs                      |   17 
 Plugin.TestResult/Util/CommonCodeParser.cs          |   27 
 Plugin.TestResult/ViewControl.xaml.cs               |  288 +++
 HMI.LogViewer/HMI.LogViewer.csproj                  |  132 +
 HMI.DataViewer/DataViewer.xaml.cs                   |   59 
 Plugin.TestResult/TestResultView.xaml               |   16 
 HMI.DataViewer/DbSettingModal.xaml.cs               |    2 
 Plugin.TestResult/Model/Info.cs                     |   60 
 Plugin.TestResult/Resource/testing2.png             |    0 
 HMI.LogViewer/Properties/AssemblyInfo.cs            |   52 
 Plugin.TestResult/OpenTK.dll.config                 |   25 
 HMI.UI/Util/ToastHelper.cs                          |    5 
 HMI.UI/DpUtil/DataGridHelper.cs                     |   20 
 Plugin.TestResult/Properties/Settings.Designer.cs   |   30 
 Plugin.TestResult/Scanner.xaml                      |   54 
 HMI.ParameterManage/Model/XmlModel.cs               |  210 ++
 Plugin.TestResult/Scanner.xaml.cs                   |  216 ++
 HMI.AlermInfoManage/AlarmInfoManage_Setting.xaml    |    2 
 HMI.LogViewer/Properties/Resources.resx             |  117 +
 HMI.LogViewer/packages.config                       |    6 
 HMI.ParameterManage/ProductModal.xaml               |    8 
 Plugin.TestResult/Model/BaseConfig.cs               |  157 +
 Plugin.TestResult/ChartWindow.xaml                  |   13 
 HMI.Core/HMI.Core.csproj                            |    1 
 HMI.UI/Style/FormStyle.xaml                         |    4 
 Plugin.TestResult/InfoTable.xaml                    |   81 
 WPFProject/WPFProject.csproj                        |    8 
 HMI.UI/Style/PanuonResource.xaml                    |   23 
 Plugin.TestResult/Model/BaseModel.cs                |   21 
 HMI.DataViewer/DbSettingModal.xaml                  |    6 
 Plugin.TestResult/Properties/Resources.resx         |  117 +
 WPFProject/WPFProject.sln                           |   20 
 Plugin.TestResult/Resource/ng2.png                  |    0 
 HMI.LogViewer/Properties/Settings.settings          |    7 
 Plugin.TestResult/Resource/checkIn.png              |    0 
 Plugin.TestResult/Util/FontSizeConverter.cs         |   33 
 Plugin.TestResult/Plugin.TestResult.csproj          |  313 +++
 Plugin.TestResult/Resource/waiting2.png             |    0 
 HMI.ParameterManage/ParameterModal.xaml.cs          |   66 
 Plugin.TestResult/InfoWindow.xaml                   |   25 
 Plugin.TestResult/Resource/alert.png                |    0 
 HMI.UI/DpUtil/FormGroupHelper.cs                    |    7 
 HMI.LogViewer/Properties/Settings.Designer.cs       |   30 
 HMI.SettingControl/SystemConfig.xaml.cs             |   26 
 Plugin.TestResult/Util/DpUtil.cs                    |  232 ++
 HMI.LogViewer/LogViewer.xaml.cs                     |  210 ++
 HMI.IOMonitor/IPManageModal.xaml                    |    5 
 Plugin.TestResult/Properties/AssemblyInfo.cs        |   53 
 HMI.AlermInfoManage/Modal/AlarmSettingModal.xaml.cs |    2 
 Plugin.TestResult/Resource/ok2.png                  |    0 
 HMI.AlermInfoManage/AlarmInfoManage_Setting.xaml.cs |   16 
 HMI.AlermInfoManage/Modal/AlarmSettingModal.xaml    |    9 
 HMI.ParameterManage/Entity/ProductData.cs           |    6 
 Plugin.TestResult/Resource/barcode.png              |    0 
 Plugin.TestResult/Constant/CommonConstant.cs        |   70 
 Plugin.TestResult/packages.config                   |   25 
 HMI.IOMonitor/IOMonitor.xaml.cs                     |   10 
 HMI.SettingControl/SystemConfig.xaml                |    4 
 HMI.SettingControl/SettingControl.xaml              |   38 
 Plugin.TestResult/InfoWindow.xaml.cs                |   58 
 Plugin.TestResult/SingleCell.xaml.cs                |  195 ++
 Plugin.TestResult/ViewControl.xaml                  |  161 +
 Plugin.TestResult/ChartWindow.xaml.cs               |   45 
 Plugin.TestResult/DataTable.xaml.cs                 |  285 +++
 HMI.UI/HMI.UI.csproj                                |    1 
 92 files changed, 5,253 insertions(+), 300 deletions(-)

diff --git a/HMI.AlermInfoManage/AlarmInfoManage_Setting.xaml b/HMI.AlermInfoManage/AlarmInfoManage_Setting.xaml
index 611a9c1..b6c507b 100644
--- a/HMI.AlermInfoManage/AlarmInfoManage_Setting.xaml
+++ b/HMI.AlermInfoManage/AlarmInfoManage_Setting.xaml
@@ -22,8 +22,6 @@
                     </pu:FormGroup>
                 </StackPanel>
             </GroupBox>
-
-            <Button Click="Button_Click">淇濆瓨</Button>
         </StackPanel>
     </Grid>
 </UserControl>
diff --git a/HMI.AlermInfoManage/AlarmInfoManage_Setting.xaml.cs b/HMI.AlermInfoManage/AlarmInfoManage_Setting.xaml.cs
index 0baf7dd..1e0296a 100644
--- a/HMI.AlermInfoManage/AlarmInfoManage_Setting.xaml.cs
+++ b/HMI.AlermInfoManage/AlarmInfoManage_Setting.xaml.cs
@@ -1,4 +1,5 @@
 锘縰sing HMI.AlarmInfoManage.Entity;
+using HMI.Core.Api;
 using HMI.Core.Util;
 using System.Windows;
 using System.Windows.Controls;
@@ -8,7 +9,7 @@
     /// <summary>
     /// AlarmInfoManage_Setting.xaml 鐨勪氦浜掗�昏緫
     /// </summary>
-    public partial class AlarmInfoManage_Setting : UserControl
+    public partial class AlarmInfoManage_Setting : UserControl, IConfigManager
     {
         public AlarmInfoManage_Setting()
         {
@@ -17,17 +18,26 @@
 
         private void UserControl_Loaded(object sender, RoutedEventArgs e)
         {
-            DataContext = AimSetting.Instance.GetModel();
+            ResetConfig();
         }
 
-        private void Button_Click(object sender, RoutedEventArgs e)
+        public object GetConfig()
+        {
+            return AimSetting.Instance;
+        }
+
+        public void SaveConfig()
         {
             AimSettingModel model = (AimSettingModel)DataContext;
 
             AimSetting newSetting = AimSetting.FromModel(model);
 
             XmlHelper.SerializeToXml(PathHelper.ConfigFolder + "\\AimSetting.xml", model);
+        }
 
+        public void ResetConfig()
+        {
+            DataContext = AimSetting.Instance.GetModel();
         }
     }
 }
diff --git a/HMI.AlermInfoManage/Modal/AlarmSettingModal.xaml b/HMI.AlermInfoManage/Modal/AlarmSettingModal.xaml
index 36941a7..6edad5f 100644
--- a/HMI.AlermInfoManage/Modal/AlarmSettingModal.xaml
+++ b/HMI.AlermInfoManage/Modal/AlarmSettingModal.xaml
@@ -8,6 +8,7 @@
             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"
             mc:Ignorable="d"
             Title="Form"
             SizeToContent="Height"
@@ -26,19 +27,19 @@
             pu:WindowXModalDialog.ButtonPanelMargin="0,30,10,10">
     <Grid Margin="10">
         <StackPanel x:Name="formPanel">
-            <pu:FormGroup ui:FormGroupHelper.IsRequired="True"
-                                ui:FormGroupHelper.ValidateMethod="ValidateDBAddress"
+            <pu:FormGroup dp:FormGroupHelper.IsRequired="True"
+                                dp:FormGroupHelper.ValidateMethod="ValidateDBAddress"
                           Header="DB鍦板潃" GroupName="MyFormGroup">
                 <pu:NumberInput Style="{StaticResource FormNumberInput}" 
                                 x:Name="dbAddress" Interval="1"/>
             </pu:FormGroup>
 
-            <pu:FormGroup ui:FormGroupHelper.IsRequired="True" 
+            <pu:FormGroup dp:FormGroupHelper.IsRequired="True" 
               Header="璧峰鍦板潃" GroupName="MyFormGroup">
                 <pu:NumberInput Style="{StaticResource FormNumberInput}" x:Name="startAddress" Interval="1"/>
             </pu:FormGroup>
 
-            <pu:FormGroup ui:FormGroupHelper.IsRequired="True" 
+            <pu:FormGroup dp:FormGroupHelper.IsRequired="True" 
               Header="闀垮害" GroupName="MyFormGroup">
                 <pu:NumberInput Style="{StaticResource FormNumberInput}" x:Name="length" Interval="1"/>
             </pu:FormGroup>
diff --git a/HMI.AlermInfoManage/Modal/AlarmSettingModal.xaml.cs b/HMI.AlermInfoManage/Modal/AlarmSettingModal.xaml.cs
index 57ac994..f3f7f92 100644
--- a/HMI.AlermInfoManage/Modal/AlarmSettingModal.xaml.cs
+++ b/HMI.AlermInfoManage/Modal/AlarmSettingModal.xaml.cs
@@ -1,6 +1,6 @@
 锘縰sing HMI.AlarmInfoManage.Entity;
 using HMI.Core.Util;
-using HMI.UI;
+using HMI.UI.DpUtil;
 using HMI.UI.Util;
 using log4net;
 using Panuon.WPF.UI;
diff --git a/HMI.Core/Api/IConfigManager.cs b/HMI.Core/Api/IConfigManager.cs
new file mode 100644
index 0000000..4415b4c
--- /dev/null
+++ b/HMI.Core/Api/IConfigManager.cs
@@ -0,0 +1,17 @@
+锘縰sing System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace HMI.Core.Api
+{
+    public interface IConfigManager
+    {
+        object GetConfig();
+
+        void SaveConfig();
+
+        void ResetConfig();
+    }
+}
diff --git a/HMI.Core/HMI.Core.csproj b/HMI.Core/HMI.Core.csproj
index 3f23aae..caa4430 100644
--- a/HMI.Core/HMI.Core.csproj
+++ b/HMI.Core/HMI.Core.csproj
@@ -80,6 +80,7 @@
     <Reference Include="System.Xml" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="Api\IConfigManager.cs" />
     <Compile Include="Api\IConfigRefresh.cs" />
     <Compile Include="Api\IFolderOwner.cs" />
     <Compile Include="Api\IPlugin.cs" />
diff --git a/HMI.DataViewer/DataViewer.xaml b/HMI.DataViewer/DataViewer.xaml
index 6e97d35..1b37283 100644
--- a/HMI.DataViewer/DataViewer.xaml
+++ b/HMI.DataViewer/DataViewer.xaml
@@ -55,10 +55,10 @@
             </Grid>-->
             <StackPanel Grid.Row="1" Grid.ColumnSpan="2" HorizontalAlignment="Left" Style="{StaticResource ButtonGroup}">
 
-                <TextBlock Style="{StaticResource TitleBox}" Text="寮�濮嬫椂闂�"/>
-                <pu:DateTimePicker Width="190" Style="{StaticResource TimePicker}" SelectedDateTime="{x:Null}" x:Name="startTimeInput"/>
-                <TextBlock Style="{StaticResource TitleBox}" Text="缁撴潫鏃堕棿"/>
-                <pu:DateTimePicker Width="190" Style="{StaticResource TimePicker}" SelectedDateTime="{x:Null}" x:Name="endTimeInput"/>
+                <TextBlock Style="{StaticResource TitleBox}" FontSize="{StaticResource ContentFontSize2}" Text="寮�濮嬫椂闂�"/>
+                <pu:DateTimePicker Style="{StaticResource TimePicker}" SelectedDateTime="{x:Null}" x:Name="startTimeInput"/>
+                <TextBlock Style="{StaticResource TitleBox}" FontSize="{StaticResource ContentFontSize2}" Text="缁撴潫鏃堕棿"/>
+                <pu:DateTimePicker Style="{StaticResource TimePicker}" SelectedDateTime="{x:Null}" x:Name="endTimeInput"/>
 
                 <Button Style="{StaticResource PrimaryButton}" pu:ButtonHelper.Icon="&#xe752;" 
                         x:Name="srhBtn" Click="Button_Click">鏌ヨ</Button>
@@ -66,9 +66,9 @@
                 <Button Style="{StaticResource PrimaryButton}" pu:ButtonHelper.Icon="&#xe752;" 
                         x:Name="resetBtn" Click="Button_Click">閲嶇疆</Button>
 
-                <Menu x:Name="anyBtn" >
+                <Menu x:Name="anyBtn" Style="{StaticResource CommonMenu}">
                     <MenuItem Header="鍒嗘瀽">
-                        <MenuItem Header="CPK" />
+                        <MenuItem Header="CPK" Click="MenuItem_Click"/>
                         <MenuItem Header="RandR" />
                     </MenuItem>
                 </Menu>
@@ -77,8 +77,9 @@
 
             <DockPanel Grid.Row="2" Grid.ColumnSpan="2" Margin="0,10,0,0">
                 <pu:Drawer DockPanel.Dock="Left" x:Name="DrwMenu" MinWidth="34" MaxWidth="400"
+                           Padding="0"
                            ShadowColor="{x:Null}"
-                           AnimationDuration="0" BorderThickness="2,2,0,2" BorderBrush="{StaticResource CommonBoarder}">
+                           AnimationDuration="0" BorderThickness="2,0,0,2" BorderBrush="{StaticResource CommonBoarder}">
                     <Grid>
                         <Grid.RowDefinitions>
                             <RowDefinition Height="Auto"/>
@@ -90,7 +91,7 @@
                         </Grid.ColumnDefinitions>
 
                         <DockPanel Grid.ColumnSpan="2" Background="{StaticResource CommonBoarder}">
-                            <ToggleButton Grid.Column="1" x:Name="drawerBtn" BorderThickness="0"
+                            <ToggleButton x:Name="drawerBtn" BorderThickness="0"
                                           DockPanel.Dock="Right"
                                           Background="Transparent"
                                           Width="34" Height="34" HorizontalAlignment="Right"
@@ -98,7 +99,7 @@
                                           FontFamily="{StaticResource iconfont}"
                                           Content="&#xe60e;" IsChecked="{Binding IsOpen, ElementName=DrwMenu, Mode=TwoWay}">
                                 <ToggleButton.Style>
-                                    <Style TargetType="ToggleButton">
+                                    <Style TargetType="ToggleButton" BasedOn="{StaticResource CommonToggle}">
                                         <Style.Triggers>
                                             <DataTrigger Binding="{Binding IsOpen, ElementName=DrwMenu}" Value="False">
                                                 <Setter Property="RenderTransform">
@@ -122,7 +123,7 @@
                                     </Style>
                                 </ToggleButton.Style>
                             </ToggleButton>
-                            
+
                             <TextBlock Text="楂樼骇鏌ヨ" FontSize="16" VerticalAlignment="Center" HorizontalAlignment="Center"/>
                         </DockPanel>
 
@@ -140,9 +141,10 @@
                                 <Label Content="閫夋嫨鏌ヨ椤圭洰"/>
                                 <ComboBox Grid.Column="1" x:Name="programInput" SelectionChanged="programInput_SelectionChanged"/>
                             </DockPanel>
-                           
+
                             <DataGrid Margin="10" Grid.Row="1" Grid.ColumnSpan="2" Style="{StaticResource DefaultDataGrid}"
                                       IsReadOnly="False"
+                                      SelectionMode="Single"
                                       BorderThickness="0"
                                       ItemsSource="{Binding Datas}">
                                 <DataGrid.Columns>
@@ -216,7 +218,12 @@
                     </Grid>
                 </pu:Drawer>
 
-                <DataGrid BorderThickness="2" BorderBrush="{StaticResource CommonBoarder}" x:Name="dataTable" Style="{StaticResource DefaultDataGrid}">
+                <DataGrid BorderThickness="2" BorderBrush="{StaticResource CommonBoarder}" x:Name="dataTable" 
+                          Style="{StaticResource DefaultDataGrid}"
+                          SelectionUnit="CellOrRowHeader"
+                          SelectionMode="Extended"
+                          pu:DataGridHelper.CellSelectedBackground="{StaticResource PrimaryColor}"
+                          PreviewMouseLeftButtonDown="dataTable_PreviewMouseLeftButtonDown">
                     <DataGrid.Columns>
                         <DataGridTemplateColumn Header="#" IsReadOnly="True" CanUserResize="False">
                             <DataGridTemplateColumn.CellTemplate>
diff --git a/HMI.DataViewer/DataViewer.xaml.cs b/HMI.DataViewer/DataViewer.xaml.cs
index 0b7ca90..6c3ac20 100644
--- a/HMI.DataViewer/DataViewer.xaml.cs
+++ b/HMI.DataViewer/DataViewer.xaml.cs
@@ -2,6 +2,7 @@
 using HMI.Core.Util;
 using HMI.DataViewer.Entity;
 using HMI.UI.Util;
+using Panuon.WPF.UI;
 using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
@@ -10,7 +11,9 @@
 using System.Linq;
 using System.Windows;
 using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
 using System.Windows.Data;
+using System.Windows.Media;
 
 namespace HMI.DataViewer
 {
@@ -97,7 +100,7 @@
             }
 
             DataBase_Setting setting = Map[program];
-            
+
 
             string fields = string.Join(",", Datas?.Select(d => $"[{d.Key}]").ToList());
 
@@ -116,7 +119,7 @@
                 conditon += $" AND {timeCol} < '{endTimeInput.Text}'";
             }
             conditon = string.IsNullOrEmpty(conditon) ? string.Empty : $" WHERE {conditon}";
-            
+
             SqliteHelper helper = null;
             string sql = string.Empty;
             try
@@ -124,14 +127,13 @@
                 helper = new SqliteHelper(setting.DBFilePath);
                 sql = $"SELECT {fields} FROM {setting.TableName} {conditon}";
                 DataTable dt = helper.GetDataTable(sql);
-
+                HMI.UI.DpUtil.DataGridHelper.SetSelectedColumn(dataTable, null);
                 dataTable.ItemsSource = dt.DefaultView;
             }
             catch (Exception ex)
             {
                 log.Error($"Select Data Failed. SQL: {sql}", ex);
                 ToastHelper.Error(Window.GetWindow(this), $"鏌ヨ澶辫触锛亄ex.Message}");
-
             }
             finally
             {
@@ -191,6 +193,7 @@
                 setting?.Condition?.Select(d => new QueryData { Key = d.Key, Name = d.Desc, Condition = "=" }).ToList());
 
             var temp = dataTable.Columns[0];
+            HMI.UI.DpUtil.DataGridHelper.SetSelectedColumn(dataTable, null);
             dataTable.Columns.Clear();
             dataTable.Columns.Add(temp);
             foreach (var data in Datas)
@@ -204,11 +207,55 @@
             DataGridTextColumn textColumn = new DataGridTextColumn
             {
                 Header = header,
-                Binding = new Binding(bindingPath)
-                ,
+                Binding = new Binding(bindingPath),
                 Width = new DataGridLength(1, DataGridLengthUnitType.Star)
             };
             dataTable.Columns.Add(textColumn);
         }
+
+        private void dataTable_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            // 鑾峰彇鐐瑰嚮鐨勫厓绱�
+            DependencyObject dep = (DependencyObject)e.OriginalSource;
+
+            // 閬嶅巻鎵惧埌 DataGridColumnHeader
+            while (dep != null && !(dep is DataGridColumnHeader) && !(dep is DataGridRow))
+            {
+                dep = VisualTreeHelper.GetParent(dep);
+            }
+            HMI.UI.DpUtil.DataGridHelper.SetSelectedColumn(dataTable, null);
+
+            if (dep is DataGridColumnHeader columnHeader)
+            {
+                // 鑾峰彇鍒楃储寮�
+                int columnIndex = columnHeader.Column.DisplayIndex;
+
+                // 娓呴櫎宸叉湁閫夋嫨
+                dataTable.SelectedCells.Clear();
+
+                // 閬嶅巻鎵�鏈夎锛岄�変腑璇ュ垪鐨勬墍鏈夊崟鍏冩牸
+                foreach (var item in dataTable.Items)
+                {
+                    DataGridCellInfo cellInfo = new DataGridCellInfo(item, columnHeader.Column);
+                    dataTable.SelectedCells.Add(cellInfo);
+                }
+                HMI.UI.DpUtil.DataGridHelper.SetSelectedColumn(dataTable, columnHeader.Column);
+
+                e.Handled = true; // 闃叉榛樿琛屼负
+            }
+            else if (dep is DataGridRow row)
+            {
+                // 娓呴櫎宸叉湁閫夋嫨
+                dataTable.SelectedCells.Clear();
+                dataTable.SelectedItem = row.Item;
+                e.Handled = true;
+            }
+        }
+
+        private void MenuItem_Click(object sender, RoutedEventArgs e)
+        {
+            DataGridColumn column = HMI.UI.DpUtil.DataGridHelper.GetSelectedColumn(dataTable);
+
+        }
     }
 }
diff --git a/HMI.DataViewer/DbSettingModal.xaml b/HMI.DataViewer/DbSettingModal.xaml
index b91d95f..ded1c50 100644
--- a/HMI.DataViewer/DbSettingModal.xaml
+++ b/HMI.DataViewer/DbSettingModal.xaml
@@ -27,11 +27,11 @@
             pu:WindowXModalDialog.ButtonPanelMargin="0,30,10,10">
     <Grid Margin="10">
         <StackPanel x:Name="formPanel">
-            <pu:FormGroup ui:FormGroupHelper.IsRequired="True" Header="鍚嶇О" GroupName="MyFormGroup">
+            <pu:FormGroup dp:FormGroupHelper.IsRequired="True" Header="鍚嶇О" GroupName="MyFormGroup">
                 <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="programInput"/>
             </pu:FormGroup>
 
-            <pu:FormGroup ui:FormGroupHelper.IsRequired="True" Header="鏁版嵁鏂囦欢" GroupName="MyFormGroup">
+            <pu:FormGroup dp:FormGroupHelper.IsRequired="True" Header="鏁版嵁鏂囦欢" GroupName="MyFormGroup">
                 <Grid>
                     <Grid.ColumnDefinitions>
                         <ColumnDefinition/>
@@ -42,7 +42,7 @@
                 </Grid>
             </pu:FormGroup>
 
-            <pu:FormGroup ui:FormGroupHelper.IsRequired="True" Header="鏁版嵁琛�" GroupName="MyFormGroup">
+            <pu:FormGroup dp:FormGroupHelper.IsRequired="True" Header="鏁版嵁琛�" GroupName="MyFormGroup">
                 <ComboBox Style="{StaticResource FormComboxStyle}" SelectedValuePath="Content" 
                           x:Name="tableNameInput" SelectionChanged="ComboxSelectionChanged"/>
             </pu:FormGroup>
diff --git a/HMI.DataViewer/DbSettingModal.xaml.cs b/HMI.DataViewer/DbSettingModal.xaml.cs
index 31bf8f0..2902aad 100644
--- a/HMI.DataViewer/DbSettingModal.xaml.cs
+++ b/HMI.DataViewer/DbSettingModal.xaml.cs
@@ -1,6 +1,6 @@
 锘縰sing HMI.Core.Util;
 using HMI.DataViewer.Entity;
-using HMI.UI;
+using HMI.UI.DpUtil;
 using HMI.UI.Util;
 using log4net;
 using Microsoft.Win32;
diff --git a/HMI.IOMonitor/IOMonitor.xaml b/HMI.IOMonitor/IOMonitor.xaml
index 3cd6837..8e1cf0f 100644
--- a/HMI.IOMonitor/IOMonitor.xaml
+++ b/HMI.IOMonitor/IOMonitor.xaml
@@ -62,7 +62,7 @@
                     </Grid.RowDefinitions>
 
                     <Label Grid.Row="0" Grid.Column="0" Content="DI鍚嶇О"
-                       FontSize="{StaticResource TitleFontSize}"/>
+                       FontSize="{StaticResource ContentFontSize}"/>
                     <ComboBox Grid.Row="0" Grid.Column="1" x:Name="diName" 
                           IsEditable="True" Style="{StaticResource FormComboxStyle}"/>
 
@@ -73,13 +73,13 @@
                         x:Name="diReset" Click="Button_Click">DI閲嶇疆</Button>
 
                     <Label Grid.Row="1" Grid.Column="0" Content="DI缂栧彿"
-       FontSize="{StaticResource TitleFontSize}"/>
+       FontSize="{StaticResource ContentFontSize}"/>
                     <ComboBox Grid.Row="1" Grid.Column="1" x:Name="diCode" 
                           IsEditable="True"
                           Style="{StaticResource FormComboxStyle}"/>
 
                     <Label Grid.Row="2" Grid.Column="0" Content="DI鍒嗙粍"
-       FontSize="{StaticResource TitleFontSize}"/>
+       FontSize="{StaticResource ContentFontSize}"/>
                     <ComboBox Grid.Row="2" Grid.Column="1" x:Name="diGroup" 
                           IsEditable="True" Style="{StaticResource FormComboxStyle}"/>
 
@@ -154,7 +154,7 @@
                     </Grid.RowDefinitions>
 
                     <Label Grid.Row="0" Grid.Column="0" Content="DO鍚嶇О"
-                   FontSize="{StaticResource TitleFontSize}"/>
+                   FontSize="{StaticResource ContentFontSize}"/>
                     <ComboBox Grid.Row="0" Grid.Column="1" x:Name="doName"
                           IsEditable="True" Style="{StaticResource FormComboxStyle}"/>
 
@@ -165,12 +165,12 @@
                         >DO閲嶇疆</Button>
 
                     <Label Grid.Row="1" Grid.Column="0" Content="DO缂栧彿"
-   FontSize="{StaticResource TitleFontSize}"/>
+   FontSize="{StaticResource ContentFontSize}"/>
                     <ComboBox Grid.Row="1" Grid.Column="1" x:Name="doCode"
                           IsEditable="True" Style="{StaticResource FormComboxStyle}"/>
 
                     <Label Grid.Row="2" Grid.Column="0" Content="DO鍒嗙粍"
-   FontSize="{StaticResource TitleFontSize}"/>
+   FontSize="{StaticResource ContentFontSize}"/>
                     <ComboBox Grid.Row="2" Grid.Column="1" x:Name="doGroup"
                           IsEditable="True" Style="{StaticResource FormComboxStyle}"/>
 
diff --git a/HMI.IOMonitor/IOMonitor.xaml.cs b/HMI.IOMonitor/IOMonitor.xaml.cs
index 99394ff..da9de42 100644
--- a/HMI.IOMonitor/IOMonitor.xaml.cs
+++ b/HMI.IOMonitor/IOMonitor.xaml.cs
@@ -48,20 +48,14 @@
         /// <summary>The dis
         /// DI鍙e璞�
         /// </summary>
-        private ObservableCollection<DIO> _dis = new ObservableCollection<DIO>() {
-            new DIO(null,null,null,false),
-            new DIO(null,null,null,true),
-        };
+        private ObservableCollection<DIO> _dis = new ObservableCollection<DIO>();
         public ObservableCollection<DIO> Dis
         {
             get => _dis;
             set { Set(ref _dis, value); }
         }
 
-        private ObservableCollection<DIO> _dos = new ObservableCollection<DIO>() {
-            new DIO(null,null,null,false),
-            new DIO(null,null,null,true),
-        };
+        private ObservableCollection<DIO> _dos = new ObservableCollection<DIO>();
         public ObservableCollection<DIO> Dos
         {
             get => _dos;
diff --git a/HMI.IOMonitor/IPManageModal.xaml b/HMI.IOMonitor/IPManageModal.xaml
index 963a325..1b98673 100644
--- a/HMI.IOMonitor/IPManageModal.xaml
+++ b/HMI.IOMonitor/IPManageModal.xaml
@@ -8,6 +8,7 @@
             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"
             mc:Ignorable="d"
             Title="Form"
             SizeToContent="Height"
@@ -26,12 +27,12 @@
             pu:WindowXModalDialog.ButtonPanelMargin="0,30,10,10">
     <Grid Margin="10">
         <StackPanel x:Name="formPanel">
-            <pu:FormGroup ui:FormGroupHelper.IsRequired="True" 
+            <pu:FormGroup dp:FormGroupHelper.IsRequired="True" 
   Header="I鐐硅鍙栨爣绛�" GroupName="MyFormGroup">
                 <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="iTag"/>
             </pu:FormGroup>
 
-            <pu:FormGroup ui:FormGroupHelper.IsRequired="True" 
+            <pu:FormGroup dp:FormGroupHelper.IsRequired="True" 
   Header="Q鐐硅鍙栨爣绛�" GroupName="MyFormGroup">
                 <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="qTag"/>
             </pu:FormGroup>
diff --git a/HMI.IOMonitor/IPManageModal.xaml.cs b/HMI.IOMonitor/IPManageModal.xaml.cs
index fbec583..eb443e9 100644
--- a/HMI.IOMonitor/IPManageModal.xaml.cs
+++ b/HMI.IOMonitor/IPManageModal.xaml.cs
@@ -1,6 +1,7 @@
 锘縰sing HMI.Core.Util;
 using HMI.IOMonitor.Entity;
 using HMI.UI;
+using HMI.UI.DpUtil;
 using HMI.UI.Util;
 using log4net;
 using Panuon.WPF.UI;
diff --git a/HMI.LogViewer/HMI.LogViewer.csproj b/HMI.LogViewer/HMI.LogViewer.csproj
new file mode 100644
index 0000000..09b77b7
--- /dev/null
+++ b/HMI.LogViewer/HMI.LogViewer.csproj
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{20C23602-B029-4E21-8C11-B9E5F2223237}</ProjectGuid>
+    <OutputType>library</OutputType>
+    <RootNamespace>HMI.LogViewer</RootNamespace>
+    <AssemblyName>HMI.LogViewer</AssemblyName>
+    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <WarningLevel>4</WarningLevel>
+    <Deterministic>true</Deterministic>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>bin\x64\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <DebugType>full</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <LangVersion>7.3</LangVersion>
+    <ErrorReport>prompt</ErrorReport>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+    <OutputPath>bin\x64\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <LangVersion>7.3</LangVersion>
+    <ErrorReport>prompt</ErrorReport>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="log4net, Version=3.0.3.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\log4net.3.0.3\lib\net462\log4net.dll</HintPath>
+    </Reference>
+    <Reference Include="Panuon.WPF, Version=1.1.2.2, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\Panuon.WPF.1.1.2.2\lib\net472\Panuon.WPF.dll</HintPath>
+    </Reference>
+    <Reference Include="Panuon.WPF.UI, Version=1.2.4.8, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\Panuon.WPF.UI.1.2.4.9\lib\net472\Panuon.WPF.UI.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Configuration" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Web" />
+    <Reference Include="System.Xml" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Xaml">
+      <RequiredTargetFramework>4.0</RequiredTargetFramework>
+    </Reference>
+    <Reference Include="WindowsBase" />
+    <Reference Include="PresentationCore" />
+    <Reference Include="PresentationFramework" />
+  </ItemGroup>
+  <ItemGroup>
+    <Page Include="LogViewer.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+    <Compile Include="LogViewer.xaml.cs">
+      <DependentUpon>LogViewer.xaml</DependentUpon>
+      <SubType>Code</SubType>
+    </Compile>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Properties\AssemblyInfo.cs">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="Properties\Resources.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DesignTime>True</DesignTime>
+      <DependentUpon>Resources.resx</DependentUpon>
+    </Compile>
+    <Compile Include="Properties\Settings.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Settings.settings</DependentUpon>
+      <DesignTimeSharedInput>True</DesignTimeSharedInput>
+    </Compile>
+    <EmbeddedResource Include="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+    </EmbeddedResource>
+    <None Include="packages.config" />
+    <None Include="Properties\Settings.settings">
+      <Generator>SettingsSingleFileGenerator</Generator>
+      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+    </None>
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\HMI.Base\HMI.Base.csproj">
+      <Project>{2a6f9720-a890-4036-9d73-41a0dae28341}</Project>
+      <Name>HMI.Base</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\HMI.Core\HMI.Core.csproj">
+      <Project>{3e3bf44a-0b69-45c5-9512-528cb48a7106}</Project>
+      <Name>HMI.Core</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\HMI.UI\HMI.UI.csproj">
+      <Project>{f17de6b5-af1d-48c6-94a0-6b763a6546a6}</Project>
+      <Name>HMI.UI</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <PropertyGroup>
+    <PostBuildEvent>copy /Y "$(TargetPath)" "$(SolutionDir)bin\$(PlatformName)\$(ConfigurationName)\Plugin\$(TargetFileName)" 
+copy /Y "$(TargetDir)$(TargetName).pdb" "$(SolutionDir)bin\$(PlatformName)\$(ConfigurationName)\Plugin\$(TargetName).pdb"</PostBuildEvent>
+  </PropertyGroup>
+</Project>
\ No newline at end of file
diff --git a/HMI.LogViewer/LogViewer.xaml b/HMI.LogViewer/LogViewer.xaml
new file mode 100644
index 0000000..8fc848c
--- /dev/null
+++ b/HMI.LogViewer/LogViewer.xaml
@@ -0,0 +1,126 @@
+锘�<hb:PluginControl x:Class="HMI.LogViewer.LogViewer"
+             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.LogViewer"
+             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"
+             xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
+             mc:Ignorable="d"
+             Loaded="UserControl_Loaded"
+             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"/>
+                <RowDefinition Height="Auto"/>
+                <RowDefinition/>
+            </Grid.RowDefinitions>
+
+            <TextBlock FontSize="{StaticResource TitleFontSize}" 
+VerticalAlignment="Center" HorizontalAlignment="Left" Text="鏃ュ織鏌ヨ"/>
+
+            <DockPanel Grid.Row="1" Cursor="">
+                <StackPanel DockPanel.Dock="Right" Style="{StaticResource ButtonGroup}">
+                    <Button Style="{StaticResource PrimaryButton}" pu:ButtonHelper.Icon="&#xe631;" 
+                            x:Name="queryBtn" Click="Button_Click">鏌ヨ</Button>
+                    <Button Style="{StaticResource PrimaryButton}" pu:ButtonHelper.Icon="&#xe631;" 
+                            x:Name="resetBtn" Click="Button_Click" Cursor="Hand">閲嶇疆</Button>
+                    <Button x:Name="exportBtn"
+                            Style="{StaticResource PrimaryButton}" Click="Button_Click">瀵煎嚭</Button>
+                </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>
+            </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">
+                                <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>
+        </Grid>
+    </Border>
+</hb:PluginControl>
\ No newline at end of file
diff --git a/HMI.LogViewer/LogViewer.xaml.cs b/HMI.LogViewer/LogViewer.xaml.cs
new file mode 100644
index 0000000..272d886
--- /dev/null
+++ b/HMI.LogViewer/LogViewer.xaml.cs
@@ -0,0 +1,210 @@
+锘縰sing HMI.Base;
+using HMI.Core.Util;
+using HMI.UI.Util;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.IO;
+using System.Timers;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace HMI.LogViewer
+{
+    /// <summary>
+    /// UserControl1.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class LogViewer : PluginControl
+    {
+        private string logDir;
+        private string todayLogFile;
+
+        private bool autoRfh;
+
+        public bool AutoRfh
+        {
+            get { return autoRfh; }
+            set
+            {
+                Set(ref autoRfh, value);
+                if (autoRfhTimer != null) autoRfhTimer.Enabled = value;
+            }
+        }
+
+        private Timer autoRfhTimer;
+
+
+        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");
+
+            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);
+            }
+        }
+
+
+        private void UserControl_Loaded(object sender, RoutedEventArgs e)
+        {
+            autoRfhTimer = new Timer
+            {
+                Enabled = false,
+                Interval = 1000
+            };
+            autoRfhTimer.Elapsed += AutoRfhTimer_Elapsed;
+            Loaded -= UserControl_Loaded;
+        }
+
+        private void AutoRfhTimer_Elapsed(object sender, ElapsedEventArgs e)
+        {
+            Refresh100items();
+        }
+
+        private void Button_Click(object sender, RoutedEventArgs e)
+        {
+            Button btn = sender as Button;
+            switch (btn.Name)
+            {
+                case "queryBtn":
+                    QueryLog();
+                    break;
+                case "resetBtn":
+                    QueryReset();
+                    break;
+                case "exportBtn":
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        private void QueryLog()
+        {
+            try
+            {
+                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}");
+            }
+        }
+
+        private void QueryReset()
+        {
+            levelInput.Text = null;
+            startTimeInput.SelectedDateTime = null;
+            endTimeInput.SelectedDateTime = null;
+            startTimeInput.Text = null;
+            table.ItemsSource = null;
+        }
+
+        private void Filtrate()
+        {
+            string str = LoadSql();
+            DataTable dt = new DataTable();
+            var files = Directory.GetFiles(logDir, "*.db");
+            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;
+        }
+
+        /// <summary>
+        /// 鍔犺浇鐣岄潰鍒ゆ柇鏁版嵁搴撹鍙�
+        /// </summary>
+        /// <returns> str鏁版嵁搴撶殑璇彞 </returns>
+        private string LoadSql()
+        {
+            string sql = " select type,time,source,text from log";
+
+            List<string> datas = new List<string>();
+            if (!string.IsNullOrEmpty(levelInput.Text))
+            {
+                datas.Add($"type='{levelInput.Text}'");
+            }
+            if (!string.IsNullOrEmpty(startTimeInput.Text))
+            {
+                datas.Add($"time>='{startTimeInput.Text}'");
+            }
+            if (!string.IsNullOrEmpty(endTimeInput.Text))
+            {
+                datas.Add($"time<='{endTimeInput.Text}'");
+            }
+            if (!string.IsNullOrEmpty(sourceInput.Text))
+            {
+                datas.Add($"source='{sourceInput.Text}'");
+            }
+
+            string condition = string.Join(" AND ", datas);
+            condition = string.IsNullOrEmpty(condition) ? string.Empty : $" WHERE {condition}";
+
+            sql = $"{sql}{condition} order by time desc";
+            return sql;
+        }
+
+        /// <summary>
+        /// 鍒锋柊100鏉℃暟鎹�
+        /// </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}");
+            }
+        }
+    }
+}
diff --git a/HMI.LogViewer/Properties/AssemblyInfo.cs b/HMI.LogViewer/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..8b227ca
--- /dev/null
+++ b/HMI.LogViewer/Properties/AssemblyInfo.cs
@@ -0,0 +1,52 @@
+锘縰sing System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+// 鏈夊叧绋嬪簭闆嗙殑涓�鑸俊鎭敱浠ヤ笅
+// 鎺у埗銆傛洿鏀硅繖浜涚壒鎬у�煎彲淇敼
+// 涓庣▼搴忛泦鍏宠仈鐨勪俊鎭��
+[assembly: AssemblyTitle("HMI.LogViewer")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("HMI.LogViewer")]
+[assembly: AssemblyCopyright("Copyright 漏  2025")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 灏� ComVisible 璁剧疆涓� false 浼氫娇姝ょ▼搴忛泦涓殑绫诲瀷
+//瀵� COM 缁勪欢涓嶅彲瑙併�傚鏋滈渶瑕佷粠 COM 璁块棶姝ょ▼搴忛泦涓殑绫诲瀷
+//璇峰皢姝ょ被鍨嬬殑 ComVisible 鐗规�ц缃负 true銆�
+[assembly: ComVisible(false)]
+
+//鑻ヨ寮�濮嬬敓鎴愬彲鏈湴鍖栫殑搴旂敤绋嬪簭锛岃璁剧疆
+//.csproj 鏂囦欢涓殑 <UICulture>CultureYouAreCodingWith</UICulture>
+//鍦� <PropertyGroup> 涓�備緥濡傦紝濡傛灉浣犱娇鐢ㄧ殑鏄編鍥借嫳璇��
+//浣跨敤鐨勬槸缇庡浗鑻辫锛岃灏� <UICulture> 璁剧疆涓� en-US銆�  鐒跺悗鍙栨秷
+//瀵逛互涓� NeutralResourceLanguage 鐗规�х殑娉ㄩ噴銆�  鏇存柊
+//浠ヤ笅琛屼腑鐨勨�渆n-US鈥濅互鍖归厤椤圭洰鏂囦欢涓殑 UICulture 璁剧疆銆�
+
+//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
+
+
+[assembly:ThemeInfo(
+    ResourceDictionaryLocation.None, //涓婚鐗瑰畾璧勬簮璇嶅吀鎵�澶勪綅缃�
+                             //(鏈湪椤甸潰涓壘鍒拌祫婧愭椂浣跨敤锛�
+                             //鎴栧簲鐢ㄧ▼搴忚祫婧愬瓧鍏镐腑鎵惧埌鏃朵娇鐢�)
+    ResourceDictionaryLocation.SourceAssembly //甯歌璧勬簮璇嶅吀鎵�澶勪綅缃�
+                                      //(鏈湪椤甸潰涓壘鍒拌祫婧愭椂浣跨敤锛�
+                                      //銆佸簲鐢ㄧ▼搴忔垨浠讳綍涓婚涓撶敤璧勬簮瀛楀吀涓壘鍒版椂浣跨敤)
+)]
+
+
+// 绋嬪簭闆嗙殑鐗堟湰淇℃伅鐢变笅鍒楀洓涓�肩粍鎴�: 
+//
+//      涓荤増鏈�
+//      娆$増鏈�
+//      鐢熸垚鍙�
+//      淇鍙�
+//
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/HMI.LogViewer/Properties/Resources.Designer.cs b/HMI.LogViewer/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..186ad9f
--- /dev/null
+++ b/HMI.LogViewer/Properties/Resources.Designer.cs
@@ -0,0 +1,62 @@
+锘�//------------------------------------------------------------------------------
+// <auto-generated>
+//     姝や唬鐮佺敱宸ュ叿鐢熸垚銆�
+//     杩愯鏃剁増鏈�: 4.0.30319.42000
+//
+//     瀵规鏂囦欢鐨勬洿鏀瑰彲鑳藉鑷翠笉姝g‘鐨勮涓猴紝濡傛灉
+//     閲嶆柊鐢熸垚浠g爜锛屽垯鎵�鍋氭洿鏀瑰皢涓㈠け銆�
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace HMI.LogViewer.Properties {
+    
+    
+    /// <summary>
+    ///   寮虹被鍨嬭祫婧愮被锛岀敤浜庢煡鎵炬湰鍦板寲瀛楃涓茬瓑銆�
+    /// </summary>
+    // 姝ょ被鏄敱 StronglyTypedResourceBuilder
+    // 绫婚�氳繃绫讳技浜� ResGen 鎴� Visual Studio 鐨勫伐鍏疯嚜鍔ㄧ敓鎴愮殑銆�
+    // 鑻ヨ娣诲姞鎴栫Щ闄ゆ垚鍛橈紝璇风紪杈� .ResX 鏂囦欢锛岀劧鍚庨噸鏂拌繍琛� ResGen
+    // (浠� /str 浣滀负鍛戒护閫夐」)锛屾垨閲嶆柊鐢熸垚 VS 椤圭洰銆�
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources {
+        
+        private static global::System.Resources.ResourceManager resourceMan;
+        
+        private static global::System.Globalization.CultureInfo resourceCulture;
+        
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources() {
+        }
+        
+        /// <summary>
+        ///   杩斿洖姝ょ被浣跨敤鐨勭紦瀛� ResourceManager 瀹炰緥銆�
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager {
+            get {
+                if ((resourceMan == null)) {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("HMI.LogViewer.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+        
+        /// <summary>
+        ///   閲嶅啓褰撳墠绾跨▼鐨� CurrentUICulture 灞炴�э紝瀵�
+        ///   浣跨敤姝ゅ己绫诲瀷璧勬簮绫荤殑鎵�鏈夎祫婧愭煡鎵炬墽琛岄噸鍐欍��
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture {
+            get {
+                return resourceCulture;
+            }
+            set {
+                resourceCulture = value;
+            }
+        }
+    }
+}
diff --git a/HMI.LogViewer/Properties/Resources.resx b/HMI.LogViewer/Properties/Resources.resx
new file mode 100644
index 0000000..af7dbeb
--- /dev/null
+++ b/HMI.LogViewer/Properties/Resources.resx
@@ -0,0 +1,117 @@
+锘�<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>
\ No newline at end of file
diff --git a/HMI.LogViewer/Properties/Settings.Designer.cs b/HMI.LogViewer/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..0d5e3b2
--- /dev/null
+++ b/HMI.LogViewer/Properties/Settings.Designer.cs
@@ -0,0 +1,30 @@
+锘�//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.42000
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace HMI.LogViewer.Properties
+{
+    
+    
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+    {
+        
+        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+        
+        public static Settings Default
+        {
+            get
+            {
+                return defaultInstance;
+            }
+        }
+    }
+}
diff --git a/HMI.LogViewer/Properties/Settings.settings b/HMI.LogViewer/Properties/Settings.settings
new file mode 100644
index 0000000..033d7a5
--- /dev/null
+++ b/HMI.LogViewer/Properties/Settings.settings
@@ -0,0 +1,7 @@
+锘�<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
+  <Profiles>
+    <Profile Name="(Default)" />
+  </Profiles>
+  <Settings />
+</SettingsFile>
\ No newline at end of file
diff --git a/HMI.LogViewer/packages.config b/HMI.LogViewer/packages.config
new file mode 100644
index 0000000..63cdc53
--- /dev/null
+++ b/HMI.LogViewer/packages.config
@@ -0,0 +1,6 @@
+锘�<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="log4net" version="3.0.3" targetFramework="net472" />
+  <package id="Panuon.WPF" version="1.1.2.2" targetFramework="net472" />
+  <package id="Panuon.WPF.UI" version="1.2.4.9" targetFramework="net472" />
+</packages>
\ No newline at end of file
diff --git a/HMI.ParameterManage/Entity/ParameterData.cs b/HMI.ParameterManage/Entity/ParameterData.cs
index 144ff47..c0d5e2b 100644
--- a/HMI.ParameterManage/Entity/ParameterData.cs
+++ b/HMI.ParameterManage/Entity/ParameterData.cs
@@ -1,12 +1,8 @@
-锘縰sing System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+锘縰sing HMI.Core.Entity;
 
 namespace HMI.ParameterManage.Entity
 {
-    public class ParameterData
+    public class ParameterData : SelectBase
     {
         public string Id { get; set; }
         public string Name { get; set; }
diff --git a/HMI.ParameterManage/Entity/ProductData.cs b/HMI.ParameterManage/Entity/ProductData.cs
index ada68f8..e5a5b93 100644
--- a/HMI.ParameterManage/Entity/ProductData.cs
+++ b/HMI.ParameterManage/Entity/ProductData.cs
@@ -1,8 +1,12 @@
 锘�
+using HMI.Core.Entity;
+using System.Xml.Serialization;
+
 namespace HMI.ParameterManage.Entity
 {
-    public class ProductData
+    public class ProductData:SelectBase
     {
+        [XmlIgnore]
         public string Id { get; set; }
         public string Name { get; set; }
         public bool IsMaster { get; set; }
diff --git a/HMI.ParameterManage/HMI.ParameterManage.csproj b/HMI.ParameterManage/HMI.ParameterManage.csproj
index 5a8f5c7..f854a4d 100644
--- a/HMI.ParameterManage/HMI.ParameterManage.csproj
+++ b/HMI.ParameterManage/HMI.ParameterManage.csproj
@@ -81,11 +81,12 @@
     <Compile Include="ProductModal.xaml.cs">
       <DependentUpon>ProductModal.xaml</DependentUpon>
     </Compile>
+    <Compile Include="Model\XmlModel.cs" />
     <Page Include="ParameterManage.xaml">
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
     </Page>
-    <Compile Include="ControlModel.cs" />
+    <Compile Include="Model\SqlModel.cs" />
     <Compile Include="Entity\ParameterData.cs" />
     <Compile Include="Entity\ProductData.cs" />
     <Compile Include="ParameterManage.xaml.cs">
diff --git a/HMI.ParameterManage/ControlModel.cs b/HMI.ParameterManage/Model/SqlModel.cs
similarity index 72%
rename from HMI.ParameterManage/ControlModel.cs
rename to HMI.ParameterManage/Model/SqlModel.cs
index 67d0035..037a083 100644
--- a/HMI.ParameterManage/ControlModel.cs
+++ b/HMI.ParameterManage/Model/SqlModel.cs
@@ -1,16 +1,40 @@
 锘縰sing System.Collections.ObjectModel;
 using System.Data;
+using System.IO;
 using HMI.Core.Entity;
 using HMI.Core.Util;
 using HMI.ParameterManage.Entity;
 
-namespace HMI.ParameterManage
+namespace HMI.ParameterManage.Model
 {
-    public class ControlModel : NotifyBase
+
+    public class SqlModel : NotifyBase
     {
-        public ControlModel(SqliteHelper sqliteHelper)
+        public SqlModel(string baseFolder)
         {
-            SqliteHelper = sqliteHelper;
+            string dbpath = Path.Combine(baseFolder, "ParameterManage.db");
+            if (!File.Exists(dbpath))
+            {
+                SqliteHelper.CreateDatabase(dbpath);
+            }
+            SqliteHelper = new SqliteHelper(dbpath);
+            SqliteHelper.ExecuteSql(@"CREATE TABLE IF NOT EXISTS product (
+                    id INTEGER PRIMARY KEY AUTOINCREMENT,
+                    name TEXT NOT NULL,
+                    isMaster INTEGER,
+                    parentId INTEGER,
+                    UNIQUE(name)
+                    );
+CREATE TABLE IF NOT EXISTS parameter (
+                    id INTEGER PRIMARY KEY AUTOINCREMENT,
+                    name TEXT NOT NULL,
+                    value TEXT,
+                    unit TEXT,
+                    description TEXT,
+                    pGroup TEXT,
+                    productName TEXT,
+                    UNIQUE(productName,name)
+                    );");
         }
 
         public SqliteHelper SqliteHelper { get; private set; }
diff --git a/HMI.ParameterManage/Model/XmlModel.cs b/HMI.ParameterManage/Model/XmlModel.cs
new file mode 100644
index 0000000..287bcac
--- /dev/null
+++ b/HMI.ParameterManage/Model/XmlModel.cs
@@ -0,0 +1,210 @@
+锘縰sing HMI.Core.Entity;
+using HMI.Core.Util;
+using HMI.ParameterManage.Entity;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Linq;
+
+namespace HMI.ParameterManage.Model
+{
+    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;
+
+        public ObservableCollection<ProductData> Products
+        {
+            get { return products; }
+            set { Set(ref products, value); }
+        }
+
+        private ObservableCollection<ParameterData> parameters;
+
+        public ObservableCollection<ParameterData> Parameters
+        {
+            get { return parameters; }
+            set { Set(ref parameters, value); }
+        }
+
+        private ProductData selectedProduct;
+
+        public ProductData SelectedProduct
+        {
+            get { return selectedProduct; }
+            set
+            {
+                Set(ref selectedProduct, value);
+                LoadParameters();
+            }
+        }
+
+        private ParameterData selectedParameter;
+
+        public ParameterData SelectedParameter
+        {
+            get { return selectedParameter; }
+            set
+            {
+                Set(ref selectedParameter, value);
+            }
+        }
+
+        public bool ValidateProductName(object value, out string msg)
+        {
+            msg = null;
+            string name = value.ToString();
+            if (Products == null) return true;
+            var temp = Products.FirstOrDefault(p => p.Name == name);
+            if (temp != null && temp != SelectedProduct)
+            {
+                msg = "浜у搧鍚嶇О涓嶈兘閲嶅";
+                return false;
+            }
+            return true;
+        }
+
+        public bool ValidateParameterName(object value, out string msg)
+        {
+            msg = null;
+            string name = value.ToString();
+            if (Products == null) return true;
+            var temp = Parameters.FirstOrDefault(p => p.Name == name);
+            if (temp != null && temp != SelectedParameter)
+            {
+                msg = "鍙傛暟鍚嶇О涓嶈兘閲嶅";
+                return false;
+            }
+            return true;
+        }
+
+        public void LoadProducts()
+        {
+            ObservableCollection<ProductData> products;
+            if (File.Exists(ProductSettingFile))
+            {
+                products = XmlHelper.DeserializeFromXml<ObservableCollection<ProductData>>(ProductSettingFile);
+            }
+            else
+            {
+                products = new ObservableCollection<ProductData>();
+            }
+            Products = products;
+        }
+
+        public void LoadParameters()
+        {
+            ObservableCollection<ParameterData> parameters = new ObservableCollection<ParameterData>();
+            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 = parameters;
+        }
+
+        public void SaveOrUpdateData(ProductData data)
+        {
+            int index = Products.IndexOf(data);
+            if (index > -1)
+            {
+                UpdateData(index, data);
+            }
+            else
+            {
+                InsertData(data);
+            }
+        }
+
+        public void SaveOrUpdateParameter(ParameterData data)
+        {
+            int index = Parameters.IndexOf(data);
+            if (index > -1)
+            {
+                UpdateParameter(index, data);
+            }
+            else
+            {
+                InsertParameter(data);
+            }
+        }
+
+        public void InsertData(ProductData data)
+        {
+            var temp = XmlHelper.DeepClone(Products);
+            temp.Add(data);
+            XmlHelper.SerializeToXml(ProductSettingFile, temp);
+        }
+
+        public void UpdateData(int index, ProductData data)
+        {
+            var temp = XmlHelper.DeepClone(Products);
+            temp[index] = data;
+            XmlHelper.SerializeToXml(ProductSettingFile, 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);
+        }
+
+        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);
+            }
+        }
+        public void DeleteProducts()
+        {
+            var temp = Products.Where(p => !p.IsSelected).ToList();
+            XmlHelper.SerializeToXml(ProductSettingFile, temp);
+        }
+
+        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);
+            }
+        }
+
+        public void DeleteParameters()
+        {
+            var temp = Parameters.Where(p => !p.IsSelected).ToList();
+            string fileName = Path.Combine(ParameterSettingFolder, $"Parameter_{SelectedProduct.Name}.setting");
+            XmlHelper.SerializeToXml(fileName, temp);
+        }
+    }
+}
diff --git a/HMI.ParameterManage/ParameterManage.xaml b/HMI.ParameterManage/ParameterManage.xaml
index bad6c5f..ac5b69b 100644
--- a/HMI.ParameterManage/ParameterManage.xaml
+++ b/HMI.ParameterManage/ParameterManage.xaml
@@ -17,29 +17,38 @@
              d:DesignHeight="450" d:DesignWidth="800">
     <Grid>
         <Grid.ColumnDefinitions>
-            <ColumnDefinition Width="2*"/>
-            <ColumnDefinition Width="8*"/>
+            <ColumnDefinition Width="3*"/>
+            <ColumnDefinition Width="7*"/>
         </Grid.ColumnDefinitions>
         <Border Style="{StaticResource CardBoarder}" Margin="0,0,2,0">
-            <StackPanel>
-                <DockPanel Margin="0,0,0,10" Cursor="">
-                    <Button DockPanel.Dock="Right" x:Name="delBatchBtn" pu:ButtonHelper.Icon="&#xe60f;" Style="{StaticResource DangerButton}">鍒犻櫎</Button>
-                    <Button DockPanel.Dock="Right" Style="{StaticResource PrimaryButton}" 
-                        pu:ButtonHelper.Icon="&#xe631;" Margin="0,0,10,0" x:Name="refreshBtn" Click="Button_Click">鍒锋柊</Button>
-                    <Button DockPanel.Dock="Right" Style="{StaticResource PrimaryButton}"
-                        pu:ButtonHelper.Icon="&#xe61a;" Margin="0,0,10,0" x:Name="addBtn" Click="Button_Click">鏂板</Button>
+            <Grid dp:GridHelper.Space="10">
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition />
+                </Grid.RowDefinitions>
+                <DockPanel>
+                    <StackPanel DockPanel.Dock="Right" Style="{StaticResource ButtonGroup}">
+                        <Button Style="{StaticResource PrimaryButton}"
+        pu:ButtonHelper.Icon="&#xe61a;" x:Name="addBtn" Click="Button_Click">鏂板</Button>
+                        <Button Style="{StaticResource PrimaryButton}" 
+pu:ButtonHelper.Icon="&#xe631;" x:Name="refreshBtn" Click="Button_Click">鍒锋柊</Button>
+                        <Button x:Name="delBatchBtn" 
+                            pu:ButtonHelper.Icon="&#xe60f;" Style="{StaticResource DangerButton}"
+                                Click="Button_Click">鍒犻櫎</Button>
+                    </StackPanel>
                     <TextBlock DockPanel.Dock="Left" FontSize="{StaticResource TitleFontSize}" 
-                       VerticalAlignment="Center" HorizontalAlignment="Left" Text="浜у搧鍒楄〃"/>
+VerticalAlignment="Center" HorizontalAlignment="Left" Text="浜у搧鍒楄〃"/>
                 </DockPanel>
 
-                <DataGrid Grid.Column="1" x:Name="table" Style="{StaticResource DefaultDataGrid}" 
+
+                <DataGrid Grid.Row="1" x:Name="productTable" Style="{StaticResource DefaultDataGrid}" 
                       SelectedItem="{Binding SelectedProduct}" ItemsSource="{Binding Products}">
                     <DataGrid.Columns>
                         <DataGridTemplateColumn Width="Auto" CanUserResize="False">
                             <DataGridTemplateColumn.CellTemplate>
                                 <DataTemplate>
                                     <CheckBox VerticalAlignment="Center"
-                      IsChecked="{Binding Selected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
+                      IsChecked="{Binding IsSelected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
                                 </DataTemplate>
                             </DataGridTemplateColumn.CellTemplate>
                         </DataGridTemplateColumn>
@@ -67,38 +76,45 @@
                         </DataGridTemplateColumn>
                     </DataGrid.Columns>
                 </DataGrid>
-            </StackPanel>
+            </Grid>
         </Border>
 
         <Border Grid.Column="1" Style="{StaticResource CardBoarder}" Margin="2,0,0,0">
-            <StackPanel>
-                <DockPanel Margin="0,0,0,10" >
-                    <Button DockPanel.Dock="Right" pu:ButtonHelper.Icon="&#xe60f;" Style="{StaticResource DangerButton}">鍒犻櫎</Button>
-                    <Button DockPanel.Dock="Right" Style="{StaticResource PrimaryButton}" 
-    pu:ButtonHelper.Icon="&#xe631;" Margin="0,0,10,0" x:Name="refreshParameterBtn" Click="Button_Click">鍒锋柊</Button>
-                    <Button DockPanel.Dock="Right" Style="{StaticResource PrimaryButton}"
-    pu:ButtonHelper.Icon="&#xe61a;" Margin="0,0,10,0" x:Name="addParameterBtn" Click="Button_Click">鏂板</Button>
+            <Grid dp:GridHelper.Space="10">
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition />
+                </Grid.RowDefinitions>
+                <DockPanel>
+                    <StackPanel DockPanel.Dock="Right" Style="{StaticResource ButtonGroup}">
+                        <Button Style="{StaticResource PrimaryButton}"
+pu:ButtonHelper.Icon="&#xe61a;" x:Name="addParameterBtn" Click="Button_Click">鏂板</Button>
+                        <Button Style="{StaticResource PrimaryButton}" 
+pu:ButtonHelper.Icon="&#xe631;" x:Name="refreshParameterBtn" Click="Button_Click">鍒锋柊</Button>
+                        <Button pu:ButtonHelper.Icon="&#xe60f;" x:Name="deleteBatchParameterBtn" 
+                                Style="{StaticResource DangerButton}" Click="Button_Click">鍒犻櫎</Button>
+                    </StackPanel>
                     <TextBlock DockPanel.Dock="Left" FontSize="{StaticResource TitleFontSize}" 
-   VerticalAlignment="Center" HorizontalAlignment="Left" Text="浜у搧鍒楄〃"/>
-                    <TextBlock DockPanel.Dock="Left" FontSize="{StaticResource TitleFontSize}" 
-                       VerticalAlignment="Center" HorizontalAlignment="Left" Text="浜у搧鍙傛暟"/>
+VerticalAlignment="Center" HorizontalAlignment="Left" Text="浜у搧鍙傛暟"/>
                 </DockPanel>
 
-                <DataGrid x:Name="roleTable" Style="{StaticResource DefaultDataGrid}" SelectionMode="Single" 
-                      ItemsSource="{Binding Parameters}">
+
+                <DataGrid Grid.Row="1" x:Name="parameterTable" Style="{StaticResource DefaultDataGrid}" SelectionMode="Single" 
+                          SelectedItem="{Binding SelectedParameter}"
+                ItemsSource="{Binding Parameters}">
                     <DataGrid.Columns>
                         <DataGridTemplateColumn Header="" Width="Auto" CanUserResize="False">
                             <DataGridTemplateColumn.CellTemplate>
                                 <DataTemplate>
                                     <CheckBox VerticalAlignment="Center" 
-                HorizontalAlignment="Center" IsChecked="{Binding Selected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
+          HorizontalAlignment="Center" IsChecked="{Binding IsSelected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
                                 </DataTemplate>
                             </DataGridTemplateColumn.CellTemplate>
                         </DataGridTemplateColumn>
 
                         <DataGridTextColumn Header="#" Binding="{Binding Converter={StaticResource rowNumber},
-      RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGridRow},AncestorLevel=1}}"
-              CanUserResize="False"/>
+RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGridRow},AncestorLevel=1}}"
+        CanUserResize="False"/>
                         <DataGridTextColumn Width="*" Binding="{Binding Name}" Header="鍚嶇О" />
                         <DataGridTextColumn Width="*" Binding="{Binding Value}" Header="鍊�" />
                         <DataGridTextColumn Width="*" Binding="{Binding Unit}" Header="鍗曚綅" />
@@ -109,18 +125,18 @@
                                 <DataTemplate>
                                     <StackPanel Orientation="Horizontal">
                                         <Button x:Name="editParameterBtn" Style="{StaticResource RowLink}" Margin="0,0,10,0"
-                DataContext="{Binding }"
-                Click="Button_Click">缂栬緫</Button>
+          DataContext="{Binding }"
+          Click="Button_Click">缂栬緫</Button>
                                         <Button x:Name="delParameterBtn" Style="{StaticResource DangerLinkButton}"
-                DataContext="{Binding }"
-                Click="Button_Click">鍒犻櫎</Button>
+          DataContext="{Binding }"
+          Click="Button_Click">鍒犻櫎</Button>
                                     </StackPanel>
                                 </DataTemplate>
                             </DataGridTemplateColumn.CellTemplate>
                         </DataGridTemplateColumn>
                     </DataGrid.Columns>
                 </DataGrid>
-            </StackPanel>
+            </Grid>
         </Border>
     </Grid>
 </hb:PluginControl>
\ No newline at end of file
diff --git a/HMI.ParameterManage/ParameterManage.xaml.cs b/HMI.ParameterManage/ParameterManage.xaml.cs
index 9a9bc3a..8da173a 100644
--- a/HMI.ParameterManage/ParameterManage.xaml.cs
+++ b/HMI.ParameterManage/ParameterManage.xaml.cs
@@ -1,9 +1,12 @@
 锘縰sing System.IO;
+using System.Linq;
 using System.Windows;
 using System.Windows.Controls;
+using System.Windows.Markup;
 using HMI.Base;
 using HMI.Core.Util;
 using HMI.ParameterManage.Entity;
+using HMI.ParameterManage.Model;
 using HMI.UI.Util;
 
 namespace HMI.ParameterManage
@@ -13,8 +16,7 @@
     /// </summary>
     public partial class ParameterManage : PluginControl
     {
-        private ControlModel Model;
-        private SqliteHelper SqliteHelper;
+        private XmlModel Model;
 
         public ParameterManage()
         {
@@ -23,31 +25,7 @@
 
         public override void InitFinish()
         {
-            string dbpath = Path.Combine(BaseFolder, "ParameterManage.db");
-            if (!File.Exists(dbpath))
-            {
-                SqliteHelper.CreateDatabase(dbpath);
-            }
-            SqliteHelper = new SqliteHelper(dbpath);
-            SqliteHelper.ExecuteSql(@"CREATE TABLE IF NOT EXISTS product (
-                    id INTEGER PRIMARY KEY AUTOINCREMENT,
-                    name TEXT NOT NULL,
-                    isMaster INTEGER,
-                    parentId INTEGER,
-                    UNIQUE(name)
-                    );
-CREATE TABLE IF NOT EXISTS parameter (
-                    id INTEGER PRIMARY KEY AUTOINCREMENT,
-                    name TEXT NOT NULL,
-                    value TEXT,
-                    unit TEXT,
-                    description TEXT,
-                    pGroup TEXT,
-                    productName TEXT,
-                    UNIQUE(productName,name)
-                    );
-");
-            Model = new ControlModel(SqliteHelper);
+            Model = new XmlModel(BaseFolder);
             DataContext = Model;
         }
 
@@ -63,7 +41,16 @@
             switch (btn.Name)
             {
                 case "addBtn":
-                    OpenAddDialog();
+                    OpenProductDialog(null);
+                    break;
+                case "editProductBtn":
+                    OpenProductDialog((ProductData)btn.DataContext);
+                    break;
+                case "delProductBtn":
+                    TryDeleteProduct((ProductData)btn.DataContext);
+                    break;
+                case "delBatchBtn":
+                    TryDeleteProducts();
                     break;
                 case "refrshBtn":
                     Model.LoadProducts();
@@ -77,20 +64,24 @@
                 case "editParameterBtn":
                     OpenAddParameterDialog((ParameterData)btn.DataContext);
                     break;
+                case "delParameterBtn":
+                    TryDeleteParameter((ParameterData)btn.DataContext);
+                    break;
+                case "deleteBatchParameterBtn":
+                    TryDeleteParameters();
+                    break;
                 default:
                     break;
             }
         }
-
-        private void OpenAddDialog()
+        private void OpenProductDialog(ProductData data)
         {
-            var win = new ProductModal
+            var win = new ProductModal(Model)
             {
-                Title = "鏂板浜у搧",
+                Title = data == null ? "鏂板浜у搧" : "缂栬緫浜у搧",
                 Owner = Window.GetWindow(this),
-                sqliteHelper = SqliteHelper
             };
-            if (win.Open(new ProductData()) == true)
+            if (win.Open(data ?? new ProductData()) == true)
             {
                 Model.LoadProducts();
             }
@@ -103,11 +94,10 @@
                 ToastHelper.Warning(Window.GetWindow(this), "璇峰厛閫夋嫨涓�椤逛骇鍝�");
                 return;
             }
-            var win = new ParameterModal
+            var win = new ParameterModal(Model)
             {
                 Title = data == null ? "鏂板鍙傛暟" : "缂栬緫鍙傛暟",
                 Owner = Window.GetWindow(this),
-                sqliteHelper = SqliteHelper
             };
             if (win.Open(data ?? new ParameterData { ProductName = Model.SelectedProduct.Name }) == true)
             {
@@ -115,5 +105,56 @@
             }
         }
 
+        private void TryDeleteProduct(ProductData data)
+        {
+            if (MsgBoxHelper.WarnMessage(Window.GetWindow(this), $"纭畾瑕佸垹闄や骇鍝乕{data.Name}]鍚楋紵璇ユ搷浣滄棤娉曟挙閿�")
+                == MessageBoxResult.OK)
+            {
+                Model.DeleteProduct(data);
+                Model.LoadProducts();
+            }
+        }
+
+        private void TryDeleteProducts()
+        {
+            var temp = Model.Products.Where(p => p.IsSelected).Count();
+            if (temp <= 0)
+            {
+                this.Warning("璇烽�夋嫨瑕佸垹闄ょ殑浜у搧");
+                return;
+            }
+            if (MsgBoxHelper.WarnMessage(Window.GetWindow(this), $"纭畾瑕佸垹闄ら�変腑鐨刐{temp}]鏉′骇鍝佸悧锛熻鎿嶄綔鏃犳硶鎾ら攢")
+                == MessageBoxResult.OK)
+            {
+                Model.DeleteProducts();
+                Model.LoadProducts();
+            }
+        }
+
+        private void TryDeleteParameter(ParameterData data)
+        {
+            if (MsgBoxHelper.WarnMessage(Window.GetWindow(this), $"纭畾瑕佸垹闄ゅ弬鏁癧{data.Name}]鍚楋紵璇ユ搷浣滄棤娉曟挙閿�") == MessageBoxResult.OK)
+            {
+                Model.DeleteParameter(data);
+                Model.LoadParameters();
+            }
+        }
+
+        private void TryDeleteParameters()
+        {
+            var temp = Model.Parameters.Where(p => p.IsSelected).Count();
+            if (temp <= 0)
+            {
+                this.Warning("璇烽�夋嫨瑕佸垹闄ょ殑鍙傛暟");
+                return;
+            }
+            if (MsgBoxHelper.WarnMessage(Window.GetWindow(this), $"纭畾瑕佸垹闄ら�変腑鐨刐{temp}]鏉″弬鏁板悧锛熻鎿嶄綔鏃犳硶鎾ら攢")
+                == MessageBoxResult.OK)
+            {
+                Model.DeleteParameters();
+                Model.LoadParameters();
+            }
+        }
+
     }
 }
diff --git a/HMI.ParameterManage/ParameterModal.xaml b/HMI.ParameterManage/ParameterModal.xaml
index 08bc442..29a4921 100644
--- a/HMI.ParameterManage/ParameterModal.xaml
+++ b/HMI.ParameterManage/ParameterModal.xaml
@@ -8,6 +8,7 @@
             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"
             mc:Ignorable="d"
             Title="Form"
             SizeToContent="Height"
@@ -26,19 +27,19 @@
             pu:WindowXModalDialog.ButtonPanelMargin="0,30,10,10">
     <Grid Margin="10">
         <StackPanel x:Name="formPanel">
-            <pu:FormGroup x:Name="FgProductName" ui:FormGroupHelper.IsRequired="True" 
+            <pu:FormGroup x:Name="FgProductName" dp:FormGroupHelper.IsRequired="True" 
                           Header="浜у搧鍚嶇О" GroupName="MyFormGroup">
                 <TextBox x:Name="TbProductName" IsReadOnly="True" Style="{StaticResource EditorTextBoxStyle}" />
             </pu:FormGroup>
 
-            <pu:FormGroup x:Name="FgName" ui:FormGroupHelper.IsRequired="True"
-                          ui:FormGroupHelper.ValidateMethod="ValidateName"
+            <pu:FormGroup x:Name="FgName" dp:FormGroupHelper.IsRequired="True"
+                          dp:FormGroupHelper.ValidateMethod="ValidateName"
                               Header="鍙傛暟鍚嶇О" GroupName="MyFormGroup">
                 <TextBox x:Name="TbName" Style="{StaticResource EditorTextBoxStyle}" />
             </pu:FormGroup>
 
             <pu:FormGroup x:Name="FgValue" Style="{StaticResource RequiredInputFormGroupStyle}" 
-                          ui:FormGroupHelper.IsRequired="True"
+                          dp:FormGroupHelper.IsRequired="True"
                   Header="鍙傛暟鍊�" GroupName="MyFormGroup">
                 <TextBox x:Name="TbValue" Style="{StaticResource EditorTextBoxStyle}" />
             </pu:FormGroup>
diff --git a/HMI.ParameterManage/ParameterModal.xaml.cs b/HMI.ParameterManage/ParameterModal.xaml.cs
index 6824e14..d30c52d 100644
--- a/HMI.ParameterManage/ParameterModal.xaml.cs
+++ b/HMI.ParameterManage/ParameterModal.xaml.cs
@@ -1,18 +1,11 @@
-锘縰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;
+锘縰sing HMI.Core.Util;
 using HMI.ParameterManage.Entity;
-using HMI.UI;
+using HMI.ParameterManage.Model;
+using HMI.UI.DpUtil;
+using Panuon.WPF.UI;
+using System;
+using System.ComponentModel;
+using System.Windows.Interop;
 
 namespace HMI.ParameterManage
 {
@@ -21,15 +14,15 @@
     /// </summary>
     public partial class ParameterModal : WindowX
     {
-        public ParameterModal()
+        private XmlModel DataModel;
+        public ParameterModal(XmlModel model)
         {
+            DataModel = model;
             this.DataContext = this;
             InitializeComponent();
         }
 
         public ParameterData Data { get; set; }
-
-        public SqliteHelper sqliteHelper { get; set; }
 
         private bool _isProcessing;
 
@@ -54,7 +47,7 @@
                         return;
                     }
 
-                    SaveOrUpdateData();
+                    DataModel.SaveOrUpdateParameter(Data);
                 }
                 catch (Exception ex)
                 {
@@ -73,15 +66,7 @@
 
         public bool ValidateName(object value, out string err)
         {
-            err = null;
-            string segment = string.IsNullOrEmpty(Data.Id) ? "" : $" AND id!='{Data.Id}'";
-            string sql = $"SELECT 1 FROM parameter WHERE name='{TbName.Text}' AND productName='{Data.ProductName}'{segment};";
-            if (sqliteHelper.HasData(sql))
-            {
-                err = "鍙傛暟鍚嶇О涓嶈兘閲嶅";
-                return false;
-            }
-            return true;
+            return DataModel.ValidateParameterName(value, out err);
         }
 
         private bool ValidateForm()
@@ -109,33 +94,6 @@
             TbDescription.Text = Data.Description;
             TbPGroup.Text = Data.PGroup;
             return this.ShowDialog();
-        }
-
-        private void SaveOrUpdateData()
-        {
-            if (string.IsNullOrEmpty(Data.Id))
-            {
-                InsertData(Data);
-            }
-            else
-            {
-                UpdateData(Data);
-            }
-        }
-
-        public void InsertData(ParameterData data)
-        {
-            string sql = $@"INSERT INTO parameter (name,value,unit,description,productName,pGroup) 
-                VALUES
-                ('{data.Name}','{data.Value}','{data.Unit}','{data.Description}','{data.ProductName}','{data.PGroup}');";
-            sqliteHelper.ExecuteSql(sql);
-        }
-
-        public void UpdateData(ParameterData data)
-        {
-            string sql = $@"UPDATE parameter SET name='{data.Name}',value='{data.Value}',unit='{data.Unit}',
-description='{data.Description}',productName='{data.ProductName}',pGroup='{data.PGroup}' WHERE id='{data.Id}';";
-            sqliteHelper.ExecuteSql(sql);
         }
     }
 }
diff --git a/HMI.ParameterManage/ProductModal.xaml b/HMI.ParameterManage/ProductModal.xaml
index dd216e1..1b9c033 100644
--- a/HMI.ParameterManage/ProductModal.xaml
+++ b/HMI.ParameterManage/ProductModal.xaml
@@ -7,6 +7,7 @@
             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:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
             mc:Ignorable="d"
             Title="Form"
             SizeToContent="Height"
@@ -37,9 +38,12 @@
     </Window.Resources>
 
     <Grid Margin="10">
-        <StackPanel>
+        <StackPanel x:Name="formPanel">
             <pu:FormGroup x:Name="FgName" Style="{StaticResource RequiredInputFormGroupStyle}" 
-                              Header="浜у搧鍚嶇О" GroupName="MyFormGroup">
+                              Header="浜у搧鍚嶇О" GroupName="MyFormGroup"
+                          dp:FormGroupHelper.IsRequired="True"
+                          dp:FormGroupHelper.ValidateMethod="ValidateName"
+                          >
                 <TextBox x:Name="TbName" Style="{StaticResource EditorTextBoxStyle}" />
             </pu:FormGroup>
 
diff --git a/HMI.ParameterManage/ProductModal.xaml.cs b/HMI.ParameterManage/ProductModal.xaml.cs
index d67793d..53eea7b 100644
--- a/HMI.ParameterManage/ProductModal.xaml.cs
+++ b/HMI.ParameterManage/ProductModal.xaml.cs
@@ -13,6 +13,8 @@
 using HMI.Core.Util;
 using HMI.ParameterManage.Entity;
 using HMI.UI;
+using HMI.ParameterManage.Model;
+using HMI.UI.DpUtil;
 
 namespace HMI.ParameterManage
 {
@@ -21,15 +23,17 @@
     /// </summary>
     public partial class ProductModal : WindowX
     {
-        public ProductModal()
+
+        private XmlModel DataModel;
+
+        public ProductModal(XmlModel dataModel)
         {
+            DataModel = dataModel;
             this.DataContext = this;
             InitializeComponent();
         }
 
         public ProductData Data { get; set; }
-
-        public SqliteHelper sqliteHelper { get; set; }
 
         private bool _isProcessing;
 
@@ -54,7 +58,7 @@
                         return;
                     }
 
-                    SaveOrUpdateData();
+                    DataModel.SaveOrUpdateData(Data);
                 }
                 catch (Exception ex)
                 {
@@ -71,50 +75,20 @@
             base.OnClosing(e);
         }
 
+        private bool ValidateName(object value, out string msg)
+        {
+            return DataModel.ValidateProductName(value, out msg);
+        }
+
         private bool ValidateForm()
         {
-            try
+            bool flag = FormGroupHelper.ValidateForm(formPanel);
+            if (flag)
             {
-               
-                //
-
-                FormValidateException ex = new FormValidateException();
-                string segment = string.IsNullOrEmpty(Data.Id) ? "" : $" AND id!='{Data.Id}'";
-                string sql = $"SELECT 1 FROM product WHERE name='{TbName.Text}'{segment};";
-                if (string.IsNullOrEmpty(TbName.Text))
-                {
-                    ex.Result.Add(new FormGroupValidateResult("璇疯緭鍏ヤ骇鍝佸悕绉�", FgName));
-                }
-                else if (sqliteHelper.HasData(sql))
-                {
-                    ex.Result.Add(new FormGroupValidateResult("浜у搧鍚嶇О涓嶈兘閲嶅", FgName));
-                }
-                else
-                {
-                    FgName.ValidateResult = ValidateResult.None;
-                    FgName.Message = null;
-                }
-
-                if (!ex.Success)
-                {
-                    throw ex;
-                }
+                Data.Name = TbName.Text;
+                Data.IsMaster = TbIsMaster.IsChecked == true;
             }
-            catch (FormValidateException ex)
-            {
-                foreach (var data in ex.Result)
-                {
-                    var fg = (FormGroup)data.Sender;
-                    fg.ValidateResult = ValidateResult.Error;
-                    fg.Message = data.Message;
-                }
-                return false;
-            }
-
-            Data.Name = TbName.Text;
-            Data.IsMaster = TbIsMaster.IsChecked == true;
-            Data.ParentId = TbParentId.Text;
-            return true;
+            return flag;
         }
 
         public bool? Open(ProductData data)
@@ -125,35 +99,6 @@
             TbParentId.Text = Data.ParentId;
 
             return this.ShowDialog();
-        }
-
-        private void SaveOrUpdateData()
-        {
-            if (string.IsNullOrEmpty(Data.Id))
-            {
-                InsertData(Data);
-            }
-            else
-            {
-                UpdateData(Data);
-            }
-        }
-
-        public void InsertData(ProductData data)
-        {
-            string sql = $@"INSERT INTO product (name,isMaster,parentId) 
-                VALUES
-                ('{data.Name}',{(data.IsMaster ? 1 : 0)} ,{data.ParentId} );";
-            sqliteHelper.ExecuteSql(sql);
-        }
-
-
-
-        public void UpdateData(ProductData data)
-        {
-            string sql = $@"UPDATE product SET name='{data.Name}',isMaster='{(data.IsMaster ? 1 : 0)}',parentId = {data.ParentId} 
-WHERE id='{data.Id}';";
-            sqliteHelper.ExecuteSql(sql);
         }
     }
 }
diff --git a/HMI.SettingControl/SettingControl.xaml b/HMI.SettingControl/SettingControl.xaml
index 70b3828..8a2e249 100644
--- a/HMI.SettingControl/SettingControl.xaml
+++ b/HMI.SettingControl/SettingControl.xaml
@@ -9,7 +9,7 @@
              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"
-            xmlns:dp="clr-namespace:HMI.UI;assembly=HMI.UI"
+            xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
              Loaded="UserControl_Loaded"
              Background="{StaticResource WorkspaceBackground}"
              d:DesignHeight="450" d:DesignWidth="800">
@@ -28,33 +28,35 @@
 
     <Grid>
         <Border Style="{StaticResource CardBoarder}">
-            <Grid>
+            <Grid dp:GridHelper.Space="10">
                 <Grid.RowDefinitions>
                     <RowDefinition Height="Auto"/>
                     <RowDefinition />
+                    <RowDefinition Height="Auto"/>
                 </Grid.RowDefinitions>
-                <TextBlock VerticalAlignment="Center" Text="绯荤粺璁剧疆" Margin="0,0,0,10"
-                           FontSize="{StaticResource TitleFontSize}" />
-                <TabControl Grid.Row="1"
-
+                <TextBlock VerticalAlignment="Center" Text="绯荤粺璁剧疆" FontSize="{StaticResource TitleFontSize}" />
+                <TabControl Grid.Row="1" x:Name="tabControl"
         BorderBrush="{StaticResource CommonBoarder}"
         ItemsSource="{Binding TabItems}">
 
-                <TabControl.Resources>
-                    <Style TargetType="TabControl" BasedOn="{StaticResource {x:Static pu:StyleKeys.TabControlStyle}}">
-                        <Setter Property="Template">
-                            <Setter.Value>
-                                <ControlTemplate TargetType="TabControl">
-                                    <Border BorderBrush="Black" BorderThickness="1">
+                    <TabControl.Resources>
+                        <Style TargetType="TabControl" BasedOn="{StaticResource {x:Static pu:StyleKeys.TabControlStyle}}">
+                            <Setter Property="Template">
+                                <Setter.Value>
+                                    <ControlTemplate TargetType="TabControl">
+                                        <Border BorderBrush="Black" BorderThickness="1">
                                             <ContentPresenter/>
                                         </Border>
-                                </ControlTemplate>
-                            </Setter.Value>
-                        </Setter>
-                    </Style>
-                </TabControl.Resources>
-
+                                    </ControlTemplate>
+                                </Setter.Value>
+                            </Setter>
+                        </Style>
+                    </TabControl.Resources>
                 </TabControl>
+                <StackPanel Grid.Row="2" Style="{StaticResource ButtonGroup}">
+                    <Button Style="{StaticResource PrimaryButton}" x:Name="resetBtn" Click="Button_Click">閲嶇疆</Button>
+                    <Button Style="{StaticResource PrimaryButton}" x:Name="saveBtn" Click="Button_Click">淇濆瓨</Button>
+                </StackPanel>
             </Grid>
         </Border>
     </Grid>
diff --git a/HMI.SettingControl/SettingControl.xaml.cs b/HMI.SettingControl/SettingControl.xaml.cs
index 9ee7841..fceb973 100644
--- a/HMI.SettingControl/SettingControl.xaml.cs
+++ b/HMI.SettingControl/SettingControl.xaml.cs
@@ -1,10 +1,12 @@
-锘縰sing System.Collections.ObjectModel;
+锘縰sing System;
+using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.Runtime.CompilerServices;
 using System.Windows;
 using System.Windows.Controls;
 using HMI.Base;
 using HMI.Core.Api;
+using HMI.UI.Util;
 using log4net;
 
 namespace HMI.SettingControl
@@ -51,5 +53,73 @@
             }
             Loaded -= UserControl_Loaded;
         }
+
+        private void Button_Click(object sender, RoutedEventArgs e)
+        {
+            Button btn = sender as Button;
+            switch (btn.Name)
+            {
+                case "resetBtn":
+                    ResetCurrentConfig();
+                    break;
+                case "saveBtn":
+                    SaveAllConfig();
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        private void SaveAllConfig()
+        {
+            foreach (var d in tabControl.ItemsSource)
+            {
+                if (!(d is TabItem tabItem)) continue;
+                if (tabItem.Content is IConfigManager manager)
+                {
+                    try
+                    {
+                        manager.SaveConfig();
+                    }
+                    catch (Exception ex)
+                    {
+                        MsgBoxHelper.Error(this, $"淇濆瓨{tabItem.Header}閰嶇疆澶辫触锛亄ex.Message}");
+                    }
+                }
+            }
+            this.Success("淇濆瓨鎴愬姛");
+        }
+
+        private void SaveCurrentConfig()
+        {
+            var content = tabControl.SelectedItem as TabItem;
+            if (content is IConfigManager manager)
+            {
+                try
+                {
+                    manager.SaveConfig();
+                }
+                catch (Exception ex)
+                {
+                    MsgBoxHelper.Error(this, $"淇濆瓨閰嶇疆澶辫触锛亄ex.Message}");
+                }
+            }
+        }
+
+        private void ResetCurrentConfig()
+        {
+            var tabItem = tabControl.SelectedItem as TabItem;
+            if (tabItem.Content is IConfigManager manager)
+            {
+                try
+                {
+                    manager.ResetConfig();
+                }
+                catch (Exception ex)
+                {
+                    MsgBoxHelper.Error(this, $"淇濆瓨閰嶇疆澶辫触锛亄ex.Message}");
+                }
+            }
+        }
     }
 }
diff --git a/HMI.SettingControl/SystemConfig.xaml b/HMI.SettingControl/SystemConfig.xaml
index cc84c83..28cc0e5 100644
--- a/HMI.SettingControl/SystemConfig.xaml
+++ b/HMI.SettingControl/SystemConfig.xaml
@@ -53,9 +53,5 @@
                 </pu:FormGroup>
             </StackPanel>
         </GroupBox>
-
-        <DockPanel Grid.Row="3" LastChildFill="False">
-            <Button DockPanel.Dock="Right" Style="{StaticResource PrimaryButton}" Click="Button_Click">淇濆瓨</Button>
-        </DockPanel>
     </Grid>
 </UserControl>
diff --git a/HMI.SettingControl/SystemConfig.xaml.cs b/HMI.SettingControl/SystemConfig.xaml.cs
index 1e75505..53e5e26 100644
--- a/HMI.SettingControl/SystemConfig.xaml.cs
+++ b/HMI.SettingControl/SystemConfig.xaml.cs
@@ -1,5 +1,6 @@
 锘縰sing System;
 using System.Windows.Controls;
+using HMI.Core.Api;
 using HMI.Core.Setting;
 using HMI.Core.Util;
 using HMI.UI.Util;
@@ -9,7 +10,7 @@
     /// <summary>
     /// SystemConfig.xaml 鐨勪氦浜掗�昏緫
     /// </summary>
-    public partial class SystemConfig : UserControl
+    public partial class SystemConfig : UserControl, IConfigManager
     {
         public SystemConfig()
         {
@@ -19,14 +20,29 @@
             InitializeComponent();
         }
 
+        public object GetConfig()
+        {
+            return SystemSetting.Instance;
+        }
+
+        public void ResetConfig()
+        {
+            var model = new SystemSettingModel();
+            BeanUtil.Copy(SystemSetting.Instance, model);
+            DataContext = model;
+        }
+
+        public void SaveConfig()
+        {
+            SystemSettingModel settingModel = (SystemSettingModel)DataContext;
+            XmlHelper.SerializeToXml(PathHelper.ConfigFolder + "\\SystemConfig.xml", settingModel);
+            SystemSetting.ReLoad();
+        }
+
         private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
         {
             try
             {
-                SystemSettingModel settingModel = (SystemSettingModel)DataContext;
-                XmlHelper.SerializeToXml(PathHelper.ConfigFolder + "\\SystemConfig.xml", settingModel);
-                SystemSetting.ReLoad();
-                this.Success("淇濆瓨鎴愬姛");
             }
             catch (Exception ex)
             {
diff --git a/HMI.UI/DpUtil/DataGridHelper.cs b/HMI.UI/DpUtil/DataGridHelper.cs
new file mode 100644
index 0000000..b9fce70
--- /dev/null
+++ b/HMI.UI/DpUtil/DataGridHelper.cs
@@ -0,0 +1,20 @@
+锘縰sing System.Windows;
+using System.Windows.Controls;
+
+namespace HMI.UI.DpUtil
+{
+    public class DataGridHelper
+    {
+        public static DataGridColumn GetSelectedColumn(DataGrid obj)
+        {
+            return (DataGridColumn)obj.GetValue(SelectedColumnProperty);
+        }
+        public static void SetSelectedColumn(DataGrid obj, DataGridColumn value)
+        {
+            obj.SetValue(SelectedColumnProperty, value);
+        }
+        public static readonly DependencyProperty SelectedColumnProperty =
+            DependencyProperty.RegisterAttached("SelectedColumn", typeof(DataGridColumn), typeof(Grid),
+                new PropertyMetadata(null));
+    }
+}
diff --git a/HMI.UI/DpUtil/FormGroupHelper.cs b/HMI.UI/DpUtil/FormGroupHelper.cs
index 3447885..21c314e 100644
--- a/HMI.UI/DpUtil/FormGroupHelper.cs
+++ b/HMI.UI/DpUtil/FormGroupHelper.cs
@@ -1,11 +1,8 @@
-锘縰sing System;
-using System.ComponentModel;
+锘縰sing Panuon.WPF.UI;
 using System.Windows;
 using System.Windows.Controls;
-using Panuon.WPF.UI;
-using Panuon.WPF.UI.Configurations;
 
-namespace HMI.UI
+namespace HMI.UI.DpUtil
 {
     public class FormGroupHelper
     {
diff --git a/HMI.UI/HMI.UI.csproj b/HMI.UI/HMI.UI.csproj
index 9c5efce..d2bcfdc 100644
--- a/HMI.UI/HMI.UI.csproj
+++ b/HMI.UI/HMI.UI.csproj
@@ -82,6 +82,7 @@
   <ItemGroup>
     <Compile Include="Converter\DataGridRowNumberConverter.cs" />
     <Compile Include="Converter\PathImageConverter.cs" />
+    <Compile Include="DpUtil\DataGridHelper.cs" />
     <Compile Include="DpUtil\FormGroupHelper.cs" />
     <Compile Include="DpUtil\GridHelper.cs" />
     <Compile Include="DpUtil\StackPanelHelper.cs" />
diff --git a/HMI.UI/Style/FormStyle.xaml b/HMI.UI/Style/FormStyle.xaml
index 42eb600..b77cbf6 100644
--- a/HMI.UI/Style/FormStyle.xaml
+++ b/HMI.UI/Style/FormStyle.xaml
@@ -90,7 +90,7 @@
         <Setter Property="VerticalContentAlignment" Value="Center"/>
         <Style.Triggers>
             <Trigger Property="IsReadOnly" Value="True">
-                <Setter Property="Background" Value="LightGray"/>
+                <Setter Property="Background" Value="#e8e8e8"/>
             </Trigger>
         </Style.Triggers>
     </Style>
@@ -172,7 +172,9 @@
         <Setter Property="ItemsHoverBackground" Value="{StaticResource SecondPrimaryColor}"/>
         <Setter Property="ItemsHoverForeground" Value="{StaticResource SecondPrimaryColorContract}"/>
         <Setter Property="TextStringFormat" Value="yyyy-MM-dd HH:mm:ss"/>
+        <Setter Property="Width" Value="180"/>
         <Setter Property="Mode" Value="DateTime"/>
+        <Setter Property="SelectedDateTime" Value="{x:Null}"/>
     </Style>
 
 </ResourceDictionary>
\ No newline at end of file
diff --git a/HMI.UI/Style/PanuonResource.xaml b/HMI.UI/Style/PanuonResource.xaml
index 792c26e..f97f70a 100644
--- a/HMI.UI/Style/PanuonResource.xaml
+++ b/HMI.UI/Style/PanuonResource.xaml
@@ -13,6 +13,7 @@
 
     <sys:Double x:Key="TitleFontSize">20</sys:Double>
     <sys:Double x:Key="ContentFontSize">16</sys:Double>
+    <sys:Double x:Key="ContentFontSize2">14</sys:Double>
 
     <Style x:Key="PrimaryButton" TargetType="Button" BasedOn="{StaticResource {x:Static pu:StyleKeys.ButtonStyle}}">
         <Setter Property="Background" Value="{StaticResource PrimaryColor}"/>
@@ -65,6 +66,22 @@
 
     <Style x:Key="DangerLinkButton" TargetType="Button" BasedOn="{StaticResource LinkButton}">
         <Setter Property="Foreground" Value="{StaticResource DangerColor}"/>
+    </Style>
+
+    <Style x:Key="CommonToggle" TargetType="ToggleButton" BasedOn="{StaticResource {x:Static pu:StyleKeys.ToggleButtonStyle}}">
+        <Setter Property="Background" Value="#FFF"/>
+        <Setter Property="Foreground" Value="Black"/>
+        <Setter Property="BorderBrush" Value="LightGray"/>
+        <Setter Property="Cursor" Value="Hand"/>
+    </Style>
+
+    <Style x:Key="CommonMenu" TargetType="Menu" BasedOn="{StaticResource {x:Static pu:StyleKeys.MenuStyle}}">
+        <Setter Property="Background" Value="{StaticResource PrimaryColor}"/>
+        <Setter Property="Foreground" Value="#FFF"/>
+        <Setter Property="pu:IconHelper.FontFamily" Value="{StaticResource iconfont}"/>
+        <Setter Property="pu:IconHelper.Margin" Value="0,0,10,0"/>
+        <Setter Property="BorderThickness" Value="0"/>
+        <Setter Property="Cursor" Value="Hand"/>
     </Style>
 
     <Style x:Key="CardBoarder" TargetType="Border">
@@ -171,4 +188,10 @@
         <Setter Property="VerticalAlignment" Value="Center"/>
         <Setter Property="FontSize" Value="{StaticResource TitleFontSize}"/>
     </Style>
+
+    <Style x:Key="FormHeader" TargetType="TextBlock">
+        <Setter Property="VerticalAlignment" Value="Center"/>
+        <Setter Property="HorizontalAlignment" Value="Center"/>
+        <Setter Property="FontSize" Value="{StaticResource ContentFontSize2}"/>
+    </Style>
 </ResourceDictionary>
\ No newline at end of file
diff --git a/HMI.UI/Util/ToastHelper.cs b/HMI.UI/Util/ToastHelper.cs
index 6410963..9f4421f 100644
--- a/HMI.UI/Util/ToastHelper.cs
+++ b/HMI.UI/Util/ToastHelper.cs
@@ -141,7 +141,10 @@
             ToastPosition position = ToastPosition.Top, double offset = 0, int durationMs = 3000,
             ToastWindow targetWindow = ToastWindow.ActiveWindow)
         {
-            ToastHelper.Error(Window.GetWindow(control), message, icon, position, offset, durationMs, targetWindow);
+            Application.Current.Dispatcher.Invoke(() =>
+            {
+                ToastHelper.Error(Window.GetWindow(control), message, icon, position, offset, durationMs, targetWindow);
+            });
         }
 
         public static void Info(this UserControl control,
diff --git a/Plugin.TestResult/Chart.xaml b/Plugin.TestResult/Chart.xaml
new file mode 100644
index 0000000..dc41e62
--- /dev/null
+++ b/Plugin.TestResult/Chart.xaml
@@ -0,0 +1,14 @@
+锘�<UserControl x:Class="Plugin.TestResult_WPF.Chart"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:Plugin.TestResult_WPF"
+             xmlns:ScottPlot="clr-namespace:ScottPlot.WPF;assembly=ScottPlot.WPF"
+             mc:Ignorable="d" 
+             Loaded="UserControl_Loaded"
+             Unloaded="UserControl_Unloaded"
+             SizeChanged="UserControl_SizeChanged"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <ScottPlot:WpfPlot x:Name="chart" />
+</UserControl>
diff --git a/Plugin.TestResult/Chart.xaml.cs b/Plugin.TestResult/Chart.xaml.cs
new file mode 100644
index 0000000..16257d5
--- /dev/null
+++ b/Plugin.TestResult/Chart.xaml.cs
@@ -0,0 +1,314 @@
+锘縰sing ScottPlot;
+using ScottPlot.Plottables;
+using ScottPlot.TickGenerators;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Timers;
+using System.Windows.Controls;
+
+namespace Plugin.TestResult_WPF
+{
+    /// <summary>
+    /// Chart.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class Chart : UserControl
+    {
+        private readonly int maxSize;
+
+        private readonly Queue<long> xData;
+        private readonly Queue<double> yData;
+
+        private readonly Dictionary<string, Queue<long>> xValues;
+        private readonly Dictionary<string, Queue<double>> yValues;
+
+        readonly DataLogger singleLogger;
+
+        private readonly string title;
+        private readonly bool multi;
+        private readonly int refreshInterval;
+
+        private double maxYValue = -1;
+        private double minYValue = double.MaxValue;
+
+        private Timer timer;
+
+        private readonly Dictionary<string, DataLogger> loggers = new Dictionary<string, DataLogger>();
+
+        private DataLogger InitLogger()
+        {
+            DataLogger logger = chart.Plot.Add.DataLogger();
+            //logger.ViewSlide(maxSize * 5);
+            logger.ViewFull();
+            //logger.Add(new Coordinates(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), 0));
+            return logger;
+        }
+
+        public Chart(string title, bool multi, int maxSize, int refreshInterval)
+        {
+            this.title = title;
+            this.maxSize = maxSize;
+            this.multi = multi;
+            this.refreshInterval = refreshInterval;
+
+
+            InitializeComponent();
+            if (this.multi)
+            {
+                xValues = new Dictionary<string, Queue<long>>();
+                yValues = new Dictionary<string, Queue<double>>();
+                //chart.Plot.ShowLegend(Alignment.UpperLeft, Orientation.Vertical);
+                var legend = chart.Plot.ShowLegend(Edge.Top);
+                legend.Alignment = Alignment.UpperLeft;
+                legend.Padding = new PixelPadding(0);
+
+
+                chart.Plot.Axes.Left.TickLabelStyle.FontSize = 12;
+                chart.Plot.Axes.Bottom.TickLabelStyle.FontSize = 12;
+                chart.Plot.Axes.Left.Label.FontSize = 15;
+            }
+            else
+            {
+                singleLogger = InitLogger();
+                singleLogger.Color = Colors.Red;
+                xData = new Queue<long>(maxSize);
+                yData = new Queue<double>(maxSize);
+                chart.Plot.Axes.Left.TickLabelStyle.FontSize = (float)FontSizeConverter.CountFontSize(12, ActualHeight, 92);
+                chart.Plot.Axes.Bottom.TickLabelStyle.FontSize = (float)FontSizeConverter.CountFontSize(12, ActualHeight, 92);
+                chart.Plot.Axes.Left.Label.FontSize = (float)FontSizeConverter.CountFontSize(15, ActualHeight, 92);
+            }
+
+            /// 绂佺敤榧犳爣
+            chart.UserInputProcessor.Disable();
+            //chart.Plot.Layout.Frameless();
+
+
+            //Streamer1 = chart.Plot.Add.DataStreamer(maxSize);
+            //Streamer1.ViewScrollLeft();
+            //Streamer1.Color = Colors.Green;
+
+            //chart.Plot.Add.Scatter(xData.ToArray(), yData.ToArray());
+
+
+
+
+            //string CustomFormatter(DateTime dt)
+            //{
+            //    return dt.ToString("HH:mm:ss.fff");
+            //}
+            //var axis = chart.Plot.Axes.DateTimeTicksBottom();
+            //var tickGen = (DateTimeAutomatic)axis.TickGenerator;
+            //tickGen.LabelFormatter = CustomFormatter;
+
+            //chart.Plot.Axes.Bottom.TickGenerator = new DateTimeAutomatic();
+            //chart.Plot.Axes.DateTimeTicksBottom();
+            //chart.Plot.RenderManager.RenderStarting += (s, e) =>
+            //{
+            //    try
+            //    {
+            //        Tick[] ticks = chart.Plot.Axes.Bottom.TickGenerator.Ticks;
+            //        for (int i = 0; i < ticks.Length; i++)
+            //        {
+            //            long ms = ticks[i].Position < 0 ? 0 : (long)ticks[i].Position;
+            //            DateTime dt = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1)).AddMilliseconds(ms);
+            //            string label = $"{dt:hh:mm:ss.fff}";
+            //            ticks[i] = new Tick(ticks[i].Position, label);
+            //        }
+            //    }
+            //    catch (Exception ex)
+            //    {
+
+            //    }
+            //};
+
+            string CustomFormatter(double position)
+            {
+                if (position > 0)
+                    return TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1)).AddMilliseconds(position).ToString("HH:mm:ss");
+                return "";
+            }
+            EvenlySpacedMinorTickGenerator minorTickGen = new EvenlySpacedMinorTickGenerator(10);
+            NumericAutomatic myTickGenerator = new NumericAutomatic()
+            {
+                LabelFormatter = CustomFormatter,
+                MinorTickGenerator = minorTickGen
+            };
+            chart.Plot.Axes.Bottom.TickGenerator = myTickGenerator;
+            if (!string.IsNullOrEmpty(this.title))
+            {
+                chart.Plot.Axes.Left.Label.Text = this.title;
+                chart.Plot.Axes.Left.Label.Bold = false;
+                chart.Plot.Font.Automatic();
+                //RefreshData();
+            }
+
+            /// 璁剧疆鍒锋柊瀹氭椂鍣�
+            //refreshTimer = new System.Timers.Timer() { Interval = this.refreshInterval, AutoReset = true };
+            //refreshTimer.Elapsed += RefreshTimer_Elapsed;
+
+            //chart.Plot.DataBackground.Color = new Color(0, 0, 0);
+            //chart.Plot.Grid.MajorLineColor = Colors.Green.WithOpacity(.6);
+            //chart.Plot.Grid.MajorLineWidth = 1;
+            //chart.Plot.Grid.MinorLineColor = Colors.Gray.WithOpacity(.3);
+            //chart.Plot.Grid.MinorLineWidth = 1;
+
+            //chart.Plot.Axes.Bottom.TickLabelStyle.IsVisible = false;
+            //chart.Plot.Axes.Bottom.MajorTickStyle.Length = 0;
+            //chart.Plot.Axes.Left.TickLabelStyle.IsVisible = false;
+            //chart.Plot.Axes.Left.MajorTickStyle.Length = 0;
+
+            //chart.Plot.Axes.Bottom.FrameLineStyle.Width = 0;
+            //chart.Plot.Axes.Left.FrameLineStyle.Width = 0;
+            //chart.Plot.Axes.Top.FrameLineStyle.Width = 0;
+            //chart.Plot.Axes.Right.FrameLineStyle.Width = 0;
+
+            timer = new Timer();
+            timer.Interval = this.refreshInterval;
+            timer.Elapsed += Timer_Elapsed;
+        }
+
+        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
+        {
+            if (maxYValue > 0)
+            {
+                RefreshData();
+            }
+        }
+
+        public void RefreshData()
+        {
+            /// 闄愬埗y杞�
+            if (maxYValue > -1)
+            {
+                chart.Plot.Axes.SetLimitsY(minYValue > 5 ? minYValue - 5 : 0, maxYValue + 5);
+            }
+            else
+            {
+                chart.Plot.Axes.SetLimitsY(0, 100);
+            }
+            chart.Refresh();
+        }
+
+        public void Clear()
+        {
+            this.maxYValue = -1;
+            this.minYValue = double.MaxValue;
+            if (this.multi)
+            {
+                foreach (var name in loggers.Keys)
+                {
+                    loggers[name].Clear();
+                    xValues[name].Clear();
+                    yValues[name].Clear();
+                }
+                chart.Refresh();
+                return;
+            }
+            singleLogger.Clear();
+            xData.Clear();
+            yData.Clear();
+            chart.Refresh();
+        }
+
+        public void AddData(double y)
+        {
+            if (this.multi)
+            {
+                return;
+            }
+
+            this.maxYValue = y > maxYValue ? y : maxYValue;
+            this.minYValue = y < minYValue ? y : minYValue;
+
+            var nowTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+            InsertData(xData, yData, nowTime, y);
+
+            singleLogger.Add(new Coordinates(nowTime, y));
+            //Streamer1.Add(new Coordinates(DateTimeOffset.Now.ToUnixTimeSeconds(), y));
+
+            chart.Plot.GetPlottables<Marker>()
+                .ToList()
+                .ForEach(m => m.X -= 1);
+            chart.Plot.GetPlottables<Marker>()
+                .Where(m => m.X < 0)
+                .ToList()
+                .ForEach(m => chart.Plot.Remove(m));
+            //RefreshData();
+        }
+
+        private void InsertData(Queue<long> xAxis, Queue<double> yAxis, long x, double y)
+        {
+            if (xAxis.Count >= maxSize)
+            {
+                xAxis.Dequeue();
+                yAxis.Dequeue();
+            }
+            xAxis.Enqueue(x);
+            yAxis.Enqueue(y);
+        }
+
+        public void AddData(string name, double y)
+        {
+            if (!this.multi)
+            {
+                return;
+            }
+
+            this.maxYValue = y > maxYValue ? y : maxYValue;
+            this.minYValue = y < minYValue ? y : minYValue;
+
+            if (loggers.ContainsKey(name))
+            {
+                var logger = loggers[name];
+                var x = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+                logger.Add(new Coordinates(x, y));
+                InsertData(xValues[name], yValues[name], x, y);
+            }
+            else
+            {
+                var logger = InitLogger();
+                logger.LegendText = name;
+                loggers.Add(name, logger);
+                var xAxis = new Queue<long>(maxSize);
+                var yAxis = new Queue<double>(maxSize);
+
+                xValues.Add(name, xAxis);
+                yValues.Add(name, yAxis);
+
+                var x = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+                logger.Add(new Coordinates(x, y));
+                InsertData(xAxis, yAxis, x, y);
+            }
+
+            //Streamer1.Add(new Coordinates(DateTimeOffset.Now.ToUnixTimeSeconds(), y));
+
+            chart.Plot.GetPlottables<Marker>()
+                .ToList()
+                .ForEach(m => m.X -= 1);
+            chart.Plot.GetPlottables<Marker>()
+                .Where(m => m.X < 0)
+                .ToList()
+                .ForEach(m => chart.Plot.Remove(m));
+            //RefreshData();
+        }
+
+        private void UserControl_Loaded(object sender, System.Windows.RoutedEventArgs e)
+        {
+            //refreshTimer.Start();
+            timer?.Start();
+        }
+
+        private void UserControl_Unloaded(object sender, System.Windows.RoutedEventArgs e)
+        {
+            timer?.Stop();
+        }
+
+        private void UserControl_SizeChanged(object sender, System.Windows.SizeChangedEventArgs e)
+        {
+            if (this.multi) return;
+            chart.Plot.Axes.Left.Label.FontSize = (float)FontSizeConverter.CountFontSize(15, ActualHeight, 92);
+            chart.Plot.Axes.Left.TickLabelStyle.FontSize = (float)FontSizeConverter.CountFontSize(10, ActualHeight, 92);
+            chart.Plot.Axes.Bottom.TickLabelStyle.FontSize = (float)FontSizeConverter.CountFontSize(10, ActualHeight, 92);
+        }
+    }
+}
diff --git a/Plugin.TestResult/ChartWindow.xaml b/Plugin.TestResult/ChartWindow.xaml
new file mode 100644
index 0000000..e9e70fa
--- /dev/null
+++ b/Plugin.TestResult/ChartWindow.xaml
@@ -0,0 +1,13 @@
+锘�<Window x:Class="Plugin.TestResult_WPF.ChartWindow"
+        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.TestResult_WPF"
+        mc:Ignorable="d"
+        Unloaded="Window_Unloaded"
+        Title="{Binding title}" Height="450" Width="800">
+    <UniformGrid x:Name="mainGrid" Columns="1">
+
+    </UniformGrid>
+</Window>
diff --git a/Plugin.TestResult/ChartWindow.xaml.cs b/Plugin.TestResult/ChartWindow.xaml.cs
new file mode 100644
index 0000000..a49535f
--- /dev/null
+++ b/Plugin.TestResult/ChartWindow.xaml.cs
@@ -0,0 +1,45 @@
+锘縰sing System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Windows;
+
+namespace Plugin.TestResult_WPF
+{
+    /// <summary>
+    /// ChartWindow.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class ChartWindow : Window, INotifyPropertyChanged
+    {
+        #region ...
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        public virtual void RaisePropertyChanged(string name)
+        {
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+        }
+
+        public void RaiseAndSetIfchanged<T>(ref T old, T @new, [CallerMemberName] string propertyName = null)
+        {
+            old = @new;
+            if (propertyName != null) RaisePropertyChanged(propertyName);
+        }
+        #endregion
+        private string _title;
+        public string title
+        {
+            get => _title;
+            set => RaiseAndSetIfchanged(ref _title, value);
+        }
+
+        public ChartWindow(string title)
+        {
+            this.title = $"{title}璇︾粏鏇茬嚎";
+            this.DataContext = this;
+            InitializeComponent();
+        }
+
+        private void Window_Unloaded(object sender, RoutedEventArgs e)
+        {
+            this.mainGrid.Children.Clear();
+        }
+    }
+}
diff --git a/Plugin.TestResult/Constant/CommonConstant.cs b/Plugin.TestResult/Constant/CommonConstant.cs
new file mode 100644
index 0000000..cb338e3
--- /dev/null
+++ b/Plugin.TestResult/Constant/CommonConstant.cs
@@ -0,0 +1,70 @@
+锘縰sing System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace Plugin.TestResult_WPF.Constant
+{
+    public class CommonStatus
+    {
+        public static readonly string OK = "ok";
+        public static readonly string NG = "ng";
+        public static readonly string WAITING = "waiting";
+        public static readonly string CHECKINFAILED = "checkInFailed";
+        public static readonly string CHECK_IN = "checkIn";
+        public static readonly string TESTING = "testing";
+        public static readonly string ALERT = "alert";
+        public static readonly string READY = "ready";
+
+        private static string[] cache = new string[] { "ok", "ng", "waiting", "checkInFailed", "checkIn", "testing", "alert", "ready" };
+
+        public static readonly Dictionary<string, string> STATUS_MAP = new Dictionary<string, string>() {
+            { "ok","姝e父"},
+            { "ng","寮傚父"},
+            { "waiting","绛夊緟涓�"},
+            { "checkInFailed","CheckIn澶辫触"},
+            { "checkIn","CheckIn"},
+            { "testing","娴嬭瘯涓�"},
+            { "alert","鎶ヨ"},
+            { "ready","鍑嗗灏辩华"},
+        };
+
+        public static readonly Dictionary<string, SolidBrush> COLOR_MAP = new Dictionary<string, SolidBrush>() {
+            { "ok",new SolidBrush(Color.FromArgb(0,0,0,0))},
+            { "ng",new SolidBrush(Color.FromArgb(0,0,0,0))},
+            { "waiting",new SolidBrush(Color.FromArgb(0,0,0,0))},
+            { "testing",new SolidBrush(Color.FromArgb(0,0,0,0))},
+            { "checkInFailed",new SolidBrush(Color.FromArgb(0,0,0,0))},
+            { "checkIn",new SolidBrush(Color.FromArgb(0,0,0,0))},
+        };
+
+        public static string GetRandomStatus()
+        {
+            return cache[new Random().Next(cache.Length)];
+        }
+
+        public static string GetStatusDescription(string status)
+        {
+            if (STATUS_MAP.ContainsKey(status)) return STATUS_MAP[status];
+            return "";
+        }
+    }
+
+    public class StatusColorConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value is string status)
+            {
+
+            }
+            return null;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}
diff --git a/Plugin.TestResult/DataTable.xaml b/Plugin.TestResult/DataTable.xaml
new file mode 100644
index 0000000..3c260c0
--- /dev/null
+++ b/Plugin.TestResult/DataTable.xaml
@@ -0,0 +1,84 @@
+锘�<UserControl x:Class="Plugin.TestResult_WPF.DataTable"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:Plugin.TestResult_WPF"
+             mc:Ignorable="d"
+             Background="#FFF"
+             BorderBrush="Black"
+             BorderThickness="1"
+             x:Name="control"
+             Loaded="UserControl_Loaded"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <UserControl.Resources>
+        <local:FontSizeConverter x:Key="FontSizeConverter"/>
+    </UserControl.Resources>
+    <Grid x:Name="grid">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="8.5*"/>
+            <RowDefinition Height="1.5*"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+        </Grid.RowDefinitions>
+        <Grid x:Name="infoGrid" Grid.Row="0"
+             Background="LightBlue">
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition Width="Auto"/>
+                <ColumnDefinition/>
+            </Grid.ColumnDefinitions>
+            <Label Grid.Row="0" Grid.Column="0" Grid.RowSpan="1" FontSize="20" FontWeight="Bold" 
+                   Padding="0,0,20,0"
+                   BorderThickness="0,0,0,1"
+                   BorderBrush="Black"
+                   VerticalContentAlignment="Center" Background="Transparent" Content="{Binding title}"/>
+            <local:InfoTable x:Name="infoTable" Grid.Row="0" Grid.Column="1"
+               FontSize="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl}, 
+            Converter={StaticResource FontSizeConverter}, ConverterParameter='15,940'}" 
+                             ></local:InfoTable>
+        </Grid>
+
+        <UniformGrid x:Name="mainGrid" Grid.Row="1" Columns="6" local:GridProperties.ShowBorder="True"
+                     local:GridProperties.InnerBorderIgnore="All">
+        </UniformGrid>
+
+        <!--<local:Chart Grid.Row="2" x:Name="chart" BorderThickness="0,1,0,1" BorderBrush="Black" 
+                      MouseDoubleClick="chart_MouseDoubleClick"/>-->
+
+        <StatusBar Grid.Row="3">
+            <StatusBar.ItemsPanel>
+                <ItemsPanelTemplate>
+                    <DockPanel/>
+                </ItemsPanelTemplate>
+            </StatusBar.ItemsPanel>
+            <StatusBarItem DockPanel.Dock="Right">
+                <StackPanel Orientation="Horizontal">
+                    <Ellipse Width="10" Height="10">
+                        <Ellipse.Style>
+                            <Style TargetType="Ellipse">
+                                <Setter Property="Fill" Value="Gray"/>
+                                <Style.Triggers>
+                                    <DataTrigger Binding="{Binding TcpServerStatus,ElementName=control}" Value="ERROR">
+                                        <Setter Property="Fill" Value="Red"/>
+                                    </DataTrigger>
+                                    <DataTrigger Binding="{Binding TcpServerStatus,ElementName=control}" Value="RUNNING">
+                                        <Setter Property="Fill" Value="Green"/>
+                                    </DataTrigger>
+                                </Style.Triggers>
+                            </Style>
+                        </Ellipse.Style>
+                    </Ellipse>
+                    <TextBlock Text="{Binding TcpServerInfo}"/>
+                    <Button Padding="0" Margin="0" BorderThickness="0" Click="Button_Click">馃攧</Button>
+                </StackPanel>
+            </StatusBarItem>
+
+            <StatusBarItem >
+                <TextBox IsReadOnly="True" Background="Transparent" BorderThickness="0" Text="{Binding statusText}"/>
+            </StatusBarItem>
+        </StatusBar>
+
+        <Button Grid.Row="4" Margin="0" Click="SetStationGlobal_Click" BorderThickness="0,1,0,0">鍚姩</Button>
+    </Grid>
+</UserControl>
\ No newline at end of file
diff --git a/Plugin.TestResult/DataTable.xaml.cs b/Plugin.TestResult/DataTable.xaml.cs
new file mode 100644
index 0000000..e43375f
--- /dev/null
+++ b/Plugin.TestResult/DataTable.xaml.cs
@@ -0,0 +1,285 @@
+锘縰sing Plugin.TestResult_WPF.Model;
+using SuperSocket.SocketBase.Protocol;
+using System;
+using System.ComponentModel;
+using System.Net;
+using System.Net.Sockets;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Plugin.TestResult_WPF
+{
+    /// <summary>
+    /// DataTable.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class DataTable : UserControl, INotifyPropertyChanged
+    {
+        #region ...
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        public virtual void RaisePropertyChanged(string name)
+        {
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+        }
+
+        public void RaiseAndSetIfchanged<T>(ref T old, T @new, [CallerMemberName] string propertyName = null)
+        {
+            old = @new;
+            if (propertyName != null) RaisePropertyChanged(propertyName);
+        }
+        #endregion
+
+        private string _title;
+
+        public string title
+        {
+            get { return _title; }
+            set => RaiseAndSetIfchanged(ref _title, value);
+        }
+
+        private string _statusText;
+
+        public string statusText
+        {
+            get { return _statusText; }
+            set => RaiseAndSetIfchanged(ref _statusText, value);
+        }
+
+        private string tcpServerStatus;
+
+        public string TcpServerStatus
+        {
+            get { return tcpServerStatus; }
+            set { RaiseAndSetIfchanged(ref tcpServerStatus, value); }
+        }
+
+        private string tcpServerInfo = "鏈惎鍔�";
+
+        public string TcpServerInfo
+        {
+            get { return tcpServerInfo; }
+            set { RaiseAndSetIfchanged(ref tcpServerInfo, value); }
+        }
+
+
+        private readonly int index;
+        private readonly int cellCount;
+        private readonly string chartTitle;
+        private readonly TestResultView parent;
+
+        private BaseConfig config;
+        private Chart chart;
+        private Chart multiChart;
+        private SingleCell[] cells;
+
+        private TcpServer tcpServer;
+
+        public DataTable(TestResultView parent,int index, string title, BaseConfig config)
+        {
+            this.config = config;
+            this.index = index;
+            this.parent = parent;
+            this.title = title;
+            this.cellCount = config.PositionCount;
+            this.cells = new SingleCell[cellCount];
+            this.chartTitle = config.MainChartTitle;
+            DataContext = this;
+            InitializeComponent();
+            this.chart = new Chart(this.chartTitle, false, config.MaxSize, config.RefreshInterval);
+            this.multiChart = new Chart(config.DetailChartTitle, true, config.MaxSize, config.RefreshInterval);
+            this.grid.Children.Add(this.chart);
+            Grid.SetRow(this.chart, 2);
+            this.chart.MouseDoubleClick += chart_MouseDoubleClick;
+
+            for (int i = 0; i < this.cellCount; i++)
+            {
+                var cell = new SingleCell(title, i, config.CellListMaxSize);
+                mainGrid.Children.Add(cell);
+                cells[i] = cell;
+            }
+        }
+
+        public int GetSize()
+        {
+            return this.cellCount;
+        }
+
+        public SingleCell GetSingleCell(int index)
+        {
+            return cells[index];
+        }
+
+        public void SetStatusText(string statusText)
+        {
+            this.statusText = statusText;
+        }
+
+        public void SetTemperature(double t)
+        {
+            infoTable.temperature = t;
+            chart.AddData(t);
+        }
+
+        public void SetTemperature(string name, double t)
+        {
+            multiChart.AddData(name, t);
+        }
+
+        public void SetStatus(string status)
+        {
+            infoTable.status = status;
+        }
+
+        public void SetOkCount(int count)
+        {
+            infoTable.okCount = count;
+        }
+
+        public void SetNgCount(int count)
+        {
+            infoTable.ngCount = count;
+        }
+
+        public void SetStartTime(string startTime)
+        {
+            infoTable.startTimeStr = startTime;
+        }
+
+        public void SetEndTime(string endTime)
+        {
+            infoTable.endTimeStr = endTime;
+        }
+
+        public void SetConsumeTime(double consumeTime)
+        {
+            infoTable.consumeTime = consumeTime;
+        }
+
+        private ChartWindow window;
+
+        private void chart_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            /// 鎵撳紑澶氫釜娓╁害鏇茬嚎
+            if (window == null)
+            {
+                window = new ChartWindow(title);
+                window.Owner = Window.GetWindow(this);
+                window.Closed += Window_Closed;
+                window.mainGrid.Children.Add(multiChart);
+                window.Show();
+            }
+            else
+            {
+                window.Activate();
+            }
+        }
+
+        private void Window_Closed(object sender, EventArgs e)
+        {
+            window = null;
+        }
+
+        private void SetStationGlobal_Click(object sender, RoutedEventArgs e)
+        {
+            ClearChartData();
+            parent.SetStartFlag(this.index, true);
+        }
+
+        /// <summary>
+        /// 娓呯┖涓や釜鍥捐〃鏁版嵁
+        /// </summary>
+        public void ClearChartData()
+        {
+            chart.Clear();
+            multiChart.Clear();
+        }
+
+        private void StartTcpServer()
+        {
+            try
+            {
+                if (tcpServer == null)
+                {
+                    tcpServer = new TcpServer();
+                    tcpServer.NewRequestReceived += TcpServer_NewRequestReceived;
+                    if (!tcpServer.Setup(config.PortStart + index))
+                    {
+                        ChangeTcpServerStatus("ERROR", "鏈嶅姟璁剧疆寮傚父");
+                        return;
+                    }
+                    if (!tcpServer.Start())
+                    {
+                        ChangeTcpServerStatus("ERROR", "鏈嶅姟鍚姩寮傚父");
+                        return;
+                    }
+                    ChangeTcpServerStatus("RUNNING", "鏈嶅姟杩愯涓�");
+                }
+            }
+            catch (Exception ex)
+            {
+                ChangeTcpServerStatus("ERROR", $"鏈嶅姟寮傚父锛歿ex.Message}");
+                tcpServer?.Stop();
+                tcpServer = null;
+            }
+        }
+
+        private void StopTcpServer()
+        {
+            if (tcpServer == null) return;
+            try
+            {
+                tcpServer.Stop();
+                tcpServer = null;
+            }
+            catch (Exception ex)
+            {
+
+            }
+        }
+
+        private void ChangeTcpServerStatus(string status, string info)
+        {
+            Dispatcher.Invoke(() =>
+            {
+                TcpServerStatus = status;
+                TcpServerInfo = info;
+            });
+        }
+
+        private void TcpServer_NewRequestReceived(TcpSession session, StringRequestInfo requestInfo)
+        {
+            string msg = requestInfo.Body;
+            string[] datas_10015 = msg.Split(';');
+            int cellIndex = int.Parse(datas_10015[1]);
+
+            string strs = datas_10015[2];
+            string[] values = strs.Split(',');
+
+            //string step = datas_10015[2];
+            //string result = datas_10015[3];
+            GetSingleCell(cellIndex).SetInfos(new Info
+            {
+                step = values[0],
+                value = values[1],
+                result = values[2],
+                range = values[3],
+                condition = values[4],
+                updateTime = values[5]
+            });
+            session.Send("1");
+        }
+
+        private void UserControl_Loaded(object sender, RoutedEventArgs e)
+        {
+            StartTcpServer();
+            Loaded -= UserControl_Loaded;
+        }
+
+        private void Button_Click(object sender, RoutedEventArgs e)
+        {
+            StartTcpServer();
+        }
+    }
+}
diff --git a/Plugin.TestResult/InfoTable.xaml b/Plugin.TestResult/InfoTable.xaml
new file mode 100644
index 0000000..bb03d5f
--- /dev/null
+++ b/Plugin.TestResult/InfoTable.xaml
@@ -0,0 +1,81 @@
+锘�<UserControl x:Class="Plugin.TestResult_WPF.InfoTable"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:Plugin.TestResult_WPF"
+             mc:Ignorable="d"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <UserControl.Resources>
+        <Style TargetType="Label">
+            <Setter Property="VerticalContentAlignment" Value="Center"/>
+            <Setter Property="FontWeight" Value="Bold"/>
+        </Style>
+        <Style TargetType="TextBox">
+            <Setter Property="VerticalContentAlignment" Value="Center"/>
+            <Setter Property="FontWeight" Value="Bold"/>
+            <Setter Property="IsReadOnly" Value="True"/>
+            <Setter Property="BorderThickness" Value="0"/>
+        </Style>
+    </UserControl.Resources>
+    <Grid x:Name="infoGrid" Grid.Row="0" local:GridProperties.ShowBorder="True" local:GridProperties.InnerBorderIgnore="Top,Right">
+        <Grid.RowDefinitions>
+            <RowDefinition/>
+            <RowDefinition/>
+        </Grid.RowDefinitions>
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="1*"/>
+            <ColumnDefinition Width="2*"/>
+            <ColumnDefinition Width="1.5*"/>
+            <ColumnDefinition Width="2*"/>
+            <ColumnDefinition Width="1.5*"/>
+            <ColumnDefinition Width="2*"/>
+        </Grid.ColumnDefinitions>
+        <Label Grid.Row="0" Grid.Column="0">娓╁害</Label>
+        <Label Grid.Row="0" Grid.Column="1" Background="Yellow" Content="{Binding temperatureStr}"/>
+        <Label Grid.Row="0" Grid.Column="2">鐘舵��</Label>
+        <Label Grid.Row="0" Grid.Column="3" Content="{Binding statusStr}">
+            <Label.Style>
+                <Style TargetType="Label">
+                    <Setter Property="FontWeight" Value="Bold"/>
+                    <Setter Property="VerticalContentAlignment" Value="Center"/>
+                    <Style.Triggers>
+                        <DataTrigger Binding="{Binding status}" Value="ok">
+                            <Setter Property="Background" Value="#FF0F8853"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="ng">
+                            <Setter Property="Background" Value="#fd6b6d"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="checkInFailed">
+                            <Setter Property="Background" Value="#ffac00"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="checkIn">
+                            <Setter Property="Background" Value="#FF00E2FF"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="testing">
+                            <Setter Property="Background" Value="#1296db"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="waiting">
+                            <Setter Property="Background" Value="#6e6e6e"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="alert">
+                            <Setter Property="Background" Value="#e04733"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="ready">
+                            <Setter Property="Background" Value="#FF08FF00"/>
+                        </DataTrigger>
+                    </Style.Triggers>
+                </Style>
+            </Label.Style>
+        </Label>
+        <Label Grid.Row="0" Grid.Column="4">鑰佸寲鏁伴噺</Label>
+        <Label Grid.Row="0" Grid.Column="5" Background="Yellow" Content="{Binding countStr}"/>
+
+        <Label Grid.Row="1" Grid.Column="0">寮�濮嬫椂闂�</Label>
+        <Label Grid.Row="1" Grid.Column="1" Background="Yellow" Content="{Binding startTimeStr}"/>
+        <Label Grid.Row="1" Grid.Column="2">宸茶�佸寲鏃堕棿</Label>
+        <Label Grid.Row="1" Grid.Column="3" Background="Yellow" Content="{Binding consumeTimeStr}"/>
+        <Label Grid.Row="1" Grid.Column="4">棰勮缁撴潫鏃堕棿</Label>
+        <Label Grid.Row="1" Grid.Column="5" Background="Yellow" Content="{Binding endTimeStr}"/>
+    </Grid>
+</UserControl>
diff --git a/Plugin.TestResult/InfoTable.xaml.cs b/Plugin.TestResult/InfoTable.xaml.cs
new file mode 100644
index 0000000..4bff3b1
--- /dev/null
+++ b/Plugin.TestResult/InfoTable.xaml.cs
@@ -0,0 +1,153 @@
+锘縰sing Plugin.TestResult_WPF.Constant;
+using System;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Windows.Controls;
+
+namespace Plugin.TestResult_WPF
+{
+    /// <summary>
+    /// InfoTable.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class InfoTable : UserControl, INotifyPropertyChanged
+    {
+
+        #region ...
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        public virtual void RaisePropertyChanged(string name)
+        {
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+        }
+
+        public void RaiseAndSetIfchanged<T>(ref T old, T @new, [CallerMemberName] string propertyName = null)
+        {
+            old = @new;
+            if (propertyName != null) RaisePropertyChanged(propertyName);
+        }
+        #endregion
+
+        private double _temperature;
+        public double temperature
+        {
+            get => _temperature;
+            set
+            {
+                RaiseAndSetIfchanged(ref _temperature, value);
+                RaisePropertyChanged("temperatureStr");
+            }
+        }
+
+        public string temperatureStr
+        {
+            get => $"{_temperature.ToString("f2")}鈩�";
+        }
+
+        private string _status;
+        public string status
+        {
+            get => _status;
+            set
+            {
+                RaiseAndSetIfchanged(ref _status, value);
+                RaisePropertyChanged("statusStr");
+            }
+        }
+
+        public string statusStr
+        {
+            get => status != null && CommonStatus.STATUS_MAP.ContainsKey(status) ? CommonStatus.STATUS_MAP[status] : "";
+        }
+
+        private int _okCount;
+        public int okCount
+        {
+            get => _okCount;
+            set
+            {
+                RaiseAndSetIfchanged(ref _okCount, value);
+                RefreshCount();
+            }
+        }
+
+        private int _ngCount;
+
+        public int ngCount
+        {
+            get => _ngCount;
+            set
+            {
+                RaiseAndSetIfchanged(ref _ngCount, value);
+                RefreshCount();
+            }
+        }
+
+        private int _allCount;
+
+        public int allCount
+        {
+            get => _allCount;
+            set
+            {
+                RaiseAndSetIfchanged(ref _allCount, value);
+            }
+        }
+
+        public string countStr
+        {
+            get => $"OK:{okCount},NG:{ngCount},ALL:{allCount}";
+        }
+
+        private string _startTimeStr;
+        public string startTimeStr
+        {
+            get => _startTimeStr;
+            set
+            {
+                RaiseAndSetIfchanged(ref _startTimeStr, value);
+            }
+        }
+
+        private double _consumeTime;
+        public double consumeTime
+        {
+            get => _consumeTime;
+            set
+            {
+                RaiseAndSetIfchanged(ref _consumeTime, value);
+                RaisePropertyChanged("consumeTimeStr");
+            }
+        }
+
+        public string consumeTimeStr
+        {
+            get
+            {
+                if (consumeTime < 60) return $"{consumeTime.ToString("f2")}s";
+                else if (consumeTime < 3600) return $"{Math.Floor(consumeTime / 60)}m{(consumeTime % 60).ToString("f2")}s";
+                return $"{Math.Floor(consumeTime / 3600)}h{Math.Floor((consumeTime % 3600) / 60)}m{(consumeTime % 60).ToString("f2")}s";
+            }
+        }
+
+        private string _endTimeStr;
+
+        public string endTimeStr
+        {
+            get => _endTimeStr;
+            set => RaiseAndSetIfchanged(ref _endTimeStr, value);
+        }
+
+
+        private void RefreshCount()
+        {
+            this.allCount = this.okCount + this.ngCount;
+            RaisePropertyChanged("countStr");
+        }
+
+        public InfoTable()
+        {
+            this.DataContext = this;
+            InitializeComponent();
+        }
+    }
+}
diff --git a/Plugin.TestResult/InfoWindow.xaml b/Plugin.TestResult/InfoWindow.xaml
new file mode 100644
index 0000000..e80ad29
--- /dev/null
+++ b/Plugin.TestResult/InfoWindow.xaml
@@ -0,0 +1,25 @@
+锘�<Window x:Class="Plugin.TestResult_WPF.InfoWindow"
+        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.TestResult_WPF"
+        mc:Ignorable="d"
+        Title="{Binding title}" Height="500" Width="800">
+    <DataGrid ItemsSource="{Binding dataSource}" AutoGenerateColumns="False"
+              CanUserAddRows="False"
+              CanUserDeleteRows="False"
+              IsReadOnly="True"
+              ColumnWidth="*"
+              FontSize="16"
+              >
+        <DataGrid.Columns>
+            <DataGridTextColumn Header="娴嬭瘯椤瑰悕绉�" Binding="{Binding step}"/>
+            <DataGridTextColumn Header="娴嬭瘯鍊�" Binding="{Binding value}"/>
+            <DataGridTextColumn Header="娴嬭瘯缁撴灉" Binding="{Binding result}"/>
+            <DataGridTextColumn Header="娴嬭瘯鑼冨洿" Binding="{Binding range}"/>
+            <DataGridTextColumn Header="娴嬭瘯鏉′欢" Binding="{Binding condition}"/>
+            <DataGridTextColumn Header="鏇存柊鏃堕棿" Binding="{Binding updateTime}"/>
+        </DataGrid.Columns>
+    </DataGrid>
+</Window>
diff --git a/Plugin.TestResult/InfoWindow.xaml.cs b/Plugin.TestResult/InfoWindow.xaml.cs
new file mode 100644
index 0000000..bb085f8
--- /dev/null
+++ b/Plugin.TestResult/InfoWindow.xaml.cs
@@ -0,0 +1,58 @@
+锘縰sing Plugin.TestResult_WPF.Model;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Windows;
+
+namespace Plugin.TestResult_WPF
+{
+    /// <summary>
+    /// InfoWindow.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class InfoWindow : Window, INotifyPropertyChanged
+    {
+
+        #region ...
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        public virtual void RaisePropertyChanged(string name)
+        {
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+        }
+
+        public void RaiseAndSetIfchanged<T>(ref T old, T @new, [CallerMemberName] string propertyName = null)
+        {
+            old = @new;
+            if (propertyName != null) RaisePropertyChanged(propertyName);
+        }
+        #endregion
+
+        private string _title;
+        public string title
+        {
+            get => _title;
+            set => RaiseAndSetIfchanged(ref _title, value);
+        }
+
+        private ObservableCollection<Info> _dataSource;
+
+        public ObservableCollection<Info> dataSource
+        {
+            get => _dataSource == null ? new ObservableCollection<Info>() : _dataSource;
+            set => RaiseAndSetIfchanged(ref _dataSource, value);
+        }
+
+        public InfoWindow(string title, ObservableCollection<Info> infos)
+        {
+            this.title = title;
+            this.dataSource = infos;
+            this.DataContext = this;
+            InitializeComponent();
+        }
+
+        public void UpdateSource(ObservableCollection<Info> infos)
+        {
+            this.dataSource = infos;
+        }
+    }
+}
diff --git a/Plugin.TestResult/Model/BaseConfig.cs b/Plugin.TestResult/Model/BaseConfig.cs
new file mode 100644
index 0000000..4e0d5e0
--- /dev/null
+++ b/Plugin.TestResult/Model/BaseConfig.cs
@@ -0,0 +1,157 @@
+锘縰sing System.Collections;
+using System.Configuration;
+using System.Runtime.CompilerServices;
+
+namespace Plugin.TestResult_WPF.Model
+{
+    public class BaseConfig
+    {
+        private static Hashtable customConfig = (Hashtable)ConfigurationManager.GetSection("TestResultConfig");
+        /// <summary>
+        /// 棣栭〉甯冨眬琛屾暟
+        /// </summary>
+        private int row = 1;
+        public int Row
+        {
+            get
+            {
+                var data = GetValue();
+                return data == null ? row : int.Parse(data);
+            }
+        }
+
+        private string GetValue([CallerMemberName] string propertyName = null)
+        {
+            return (customConfig != null && customConfig.ContainsKey(propertyName)) ? customConfig[propertyName].ToString() : null;
+        }
+
+        /// <summary>
+        /// 棣栭〉甯冨眬鍒楁暟
+        /// </summary>
+        private int column = 2;
+        public int Column
+        {
+            get
+            {
+                var data = GetValue();
+                return data == null ? column : int.Parse(data);
+            }
+        }
+
+        /// <summary>
+        /// 鐐夋暟 -1 灏辨槸row*column
+        /// </summary>
+        private int cellCount = -1;
+        public int CellCount
+        {
+            get
+            {
+                var data = GetValue();
+                return data == null ? cellCount : int.Parse(data);
+            }
+        }
+
+        /// <summary>
+        /// 姣忎釜鐐夊瓙鐨勭偣浣嶆暟
+        /// </summary>
+        private int positionCount = 54;
+        public int PositionCount
+        {
+            get
+            {
+                var data = GetValue();
+                return data == null ? positionCount : int.Parse(data);
+            }
+        }
+
+        /// <summary>
+        /// 鏇茬嚎鏈�澶氫繚瀛樺灏戞暟鎹�
+        /// </summary>
+        private int maxSize = 10000;
+        public int MaxSize
+        {
+            get
+            {
+                var data = GetValue();
+                return data == null ? maxSize : int.Parse(data);
+            }
+        }
+
+        /// <summary>
+        /// 鍥捐〃鍒锋柊棰戠巼 ms
+        /// </summary>
+        private int refreshInterval = 500;
+        public int RefreshInterval
+        {
+            get
+            {
+                var data = GetValue();
+                return data == null ? refreshInterval : int.Parse(data);
+            }
+        }
+
+        /// <summary>
+        /// 鐐夊瓙鍚嶇О
+        /// </summary>
+        private string nameStr = "A,B,C,D,E,F,G";
+        public string NameStr
+        {
+            get
+            {
+                var data = GetValue();
+                return data == null ? nameStr : data;
+            }
+        }
+
+        /// <summary>
+        /// 榛樿灞曠ず鐨勫浘琛ㄦ爣棰�
+        /// </summary>
+        private string mainChartTitle = "骞冲潎娓╁害";
+        public string MainChartTitle
+        {
+            get
+            {
+                var data = GetValue();
+                return data == null ? mainChartTitle : data;
+            }
+        }
+
+        /// <summary>
+        /// 鍙屽嚮鎵撳紑鐨勮缁嗗浘琛ㄦ爣棰�
+        /// </summary>
+        private string detailChartTitle = "璇︾粏娓╁害";
+        public string DetailChartTitle
+        {
+            get
+            {
+                var data = GetValue();
+                return data == null ? detailChartTitle : data;
+            }
+        }
+
+        /// <summary>
+        /// 绌翠綅Step鍒楄〃鏈�澶у閲�
+        /// </summary>
+        private int cellListMaxSize = 10000;
+        public int CellListMaxSize
+        {
+            get
+            {
+                var data = GetValue();
+                return data == null ? cellListMaxSize : int.Parse(data);
+            }
+        }
+
+        private int portStart = 6660;
+        public int PortStart
+        {
+            get
+            {
+                var data = GetValue();
+                return data == null ? portStart : int.Parse(data);
+            }
+        }
+
+        public bool testing = false;
+    }
+}
diff --git a/Plugin.TestResult/Model/BaseModel.cs b/Plugin.TestResult/Model/BaseModel.cs
new file mode 100644
index 0000000..eae935b
--- /dev/null
+++ b/Plugin.TestResult/Model/BaseModel.cs
@@ -0,0 +1,21 @@
+锘縰sing System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace Plugin.TestResult_WPF.Model
+{
+    public class BaseModel : INotifyPropertyChanged
+    {
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        public virtual void RaisePropertyChanged(string name)
+        {
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+        }
+
+        public void RaiseAndSetIfchanged<T>(ref T old, T @new, [CallerMemberName] string propertyName = null)
+        {
+            old = @new;
+            if (propertyName != null) RaisePropertyChanged(propertyName);
+        }
+    }
+}
diff --git a/Plugin.TestResult/Model/CellInfo.cs b/Plugin.TestResult/Model/CellInfo.cs
new file mode 100644
index 0000000..a463bc9
--- /dev/null
+++ b/Plugin.TestResult/Model/CellInfo.cs
@@ -0,0 +1,9 @@
+锘縩amespace Plugin.TestResult_WPF.Model
+{
+    public class CellInfo
+    {
+        public string MachineName { get; set; }
+        public string OvenName { get; set; }
+        public int Index { get; set; }
+    }
+}
diff --git a/Plugin.TestResult/Model/DataTableModel.cs b/Plugin.TestResult/Model/DataTableModel.cs
new file mode 100644
index 0000000..3e37ba9
--- /dev/null
+++ b/Plugin.TestResult/Model/DataTableModel.cs
@@ -0,0 +1,7 @@
+锘縩amespace Plugin.TestResult_WPF.Model
+{
+    public class DataTableModel : BaseModel
+    {
+
+    }
+}
diff --git a/Plugin.TestResult/Model/Info.cs b/Plugin.TestResult/Model/Info.cs
new file mode 100644
index 0000000..5874718
--- /dev/null
+++ b/Plugin.TestResult/Model/Info.cs
@@ -0,0 +1,60 @@
+锘縩amespace Plugin.TestResult_WPF.Model
+{
+    public class Info : BaseModel
+    {
+        private string _step;
+
+        public string step
+        {
+            get => _step;
+
+            set => RaiseAndSetIfchanged(ref _step, value);
+        }
+
+        private string _value;
+
+        public string value
+        {
+            get => _value;
+
+            set => RaiseAndSetIfchanged(ref _value, value);
+        }
+
+        private string _result;
+
+        public string result
+        {
+            get => _result;
+
+            set => RaiseAndSetIfchanged(ref _result, value);
+        }
+
+        private string _range;
+
+        public string range
+        {
+            get => _range;
+
+            set => RaiseAndSetIfchanged(ref _range, value);
+        }
+
+        private string _condition;
+
+        public string condition
+        {
+            get => _condition;
+
+            set => RaiseAndSetIfchanged(ref _condition, value);
+        }
+
+        private string _updateTime;
+
+        public string updateTime
+        {
+            get => _updateTime;
+
+            set => RaiseAndSetIfchanged(ref _updateTime, value);
+        }
+
+    }
+}
diff --git a/Plugin.TestResult/Model/TcpServer.cs b/Plugin.TestResult/Model/TcpServer.cs
new file mode 100644
index 0000000..c1c0911
--- /dev/null
+++ b/Plugin.TestResult/Model/TcpServer.cs
@@ -0,0 +1,41 @@
+锘縰sing ScottPlot.Colormaps;
+using SuperSocket.SocketBase;
+using SuperSocket.SocketBase.Protocol;
+using System;
+using System.Text;
+
+namespace Plugin.TestResult_WPF.Model
+{
+    public class DTReceiveFilter : ReceiveFilterBase<StringRequestInfo>
+    {
+        public override StringRequestInfo Filter(byte[] readBuffer, int offset, int length, bool toBeCopied, out int rest)
+        {
+            rest = 0;
+            StringBuilder sb = new StringBuilder();
+            int i = offset;
+            while (i < readBuffer.Length)
+            {
+                byte b = readBuffer[i++];
+                if (b == '\n')
+                {
+                    break;
+                }
+                sb.Append((char)b);
+            }
+            return new StringRequestInfo(DateTime.Now + "", sb.ToString(), null);
+        }
+    }
+
+    public class TcpServer : AppServer<TcpSession>
+    {
+        public TcpServer() : base(new DefaultReceiveFilterFactory<DTReceiveFilter, StringRequestInfo>())
+        {
+
+        }
+    }
+
+    public class TcpSession : AppSession<TcpSession, StringRequestInfo>
+    {
+
+    }
+}
diff --git a/Plugin.TestResult/OpenTK.dll.config b/Plugin.TestResult/OpenTK.dll.config
new file mode 100644
index 0000000..7098d39
--- /dev/null
+++ b/Plugin.TestResult/OpenTK.dll.config
@@ -0,0 +1,25 @@
+<configuration>
+  <dllmap os="linux" dll="opengl32.dll" target="libGL.so.1"/>
+  <dllmap os="linux" dll="glu32.dll" target="libGLU.so.1"/>
+  <dllmap os="linux" dll="openal32.dll" target="libopenal.so.1"/>
+  <dllmap os="linux" dll="alut.dll" target="libalut.so.0"/>
+  <dllmap os="linux" dll="opencl.dll" target="libOpenCL.so"/>
+  <dllmap os="linux" dll="libX11" target="libX11.so.6"/>
+  <dllmap os="linux" dll="libXi" target="libXi.so.6"/>
+  <dllmap os="linux" dll="SDL2.dll" target="libSDL2-2.0.so.0"/>
+  <dllmap os="osx" dll="opengl32.dll" target="/System/Library/Frameworks/OpenGL.framework/OpenGL"/>
+  <dllmap os="osx" dll="openal32.dll" target="/System/Library/Frameworks/OpenAL.framework/OpenAL" />
+  <dllmap os="osx" dll="alut.dll" target="/System/Library/Frameworks/OpenAL.framework/OpenAL" />
+  <dllmap os="osx" dll="libGLES.dll" target="/System/Library/Frameworks/OpenGLES.framework/OpenGLES" />
+  <dllmap os="osx" dll="libGLESv1_CM.dll" target="/System/Library/Frameworks/OpenGLES.framework/OpenGLES" />
+  <dllmap os="osx" dll="libGLESv2.dll" target="/System/Library/Frameworks/OpenGLES.framework/OpenGLES" />
+  <dllmap os="osx" dll="opencl.dll" target="/System/Library/Frameworks/OpenCL.framework/OpenCL"/>
+  <dllmap os="osx" dll="SDL2.dll" target="libSDL2.dylib"/>
+  <!-- XQuartz compatibility (X11 on Mac) -->
+  <dllmap os="osx" dll="libGL.so.1" target="/usr/X11/lib/libGL.dylib"/>
+  <dllmap os="osx" dll="libX11" target="/usr/X11/lib/libX11.dylib"/>
+  <dllmap os="osx" dll="libXcursor.so.1" target="/usr/X11/lib/libXcursor.dylib"/>
+  <dllmap os="osx" dll="libXi" target="/usr/X11/lib/libXi.dylib"/>
+  <dllmap os="osx" dll="libXinerama" target="/usr/X11/lib/libXinerama.dylib"/>
+  <dllmap os="osx" dll="libXrandr.so.2" target="/usr/X11/lib/libXrandr.dylib"/>
+</configuration>
diff --git a/Plugin.TestResult/Plugin.TestResult.csproj b/Plugin.TestResult/Plugin.TestResult.csproj
new file mode 100644
index 0000000..d66425d
--- /dev/null
+++ b/Plugin.TestResult/Plugin.TestResult.csproj
@@ -0,0 +1,313 @@
+锘�<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{7375B74C-B6BF-4814-9838-E1CAACAD1897}</ProjectGuid>
+    <OutputType>library</OutputType>
+    <RootNamespace>Plugin.TestResult</RootNamespace>
+    <AssemblyName>Plugin.TestResult</AssemblyName>
+    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <WarningLevel>4</WarningLevel>
+    <Deterministic>true</Deterministic>
+    <NuGetPackageImportStamp>
+    </NuGetPackageImportStamp>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>bin\x64\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <DebugType>full</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <LangVersion>7.3</LangVersion>
+    <ErrorReport>prompt</ErrorReport>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+    <OutputPath>bin\x64\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <LangVersion>7.3</LangVersion>
+    <ErrorReport>prompt</ErrorReport>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="CommonLibs">
+      <HintPath>..\Libs\CommonLibs.dll</HintPath>
+    </Reference>
+    <Reference Include="GLWpfControl, Version=3.3.0.0, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\OpenTK.GLWpfControl.3.3.0\lib\net452\GLWpfControl.dll</HintPath>
+    </Reference>
+    <Reference Include="HarfBuzzSharp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\HarfBuzzSharp.7.3.0.3\lib\net462\HarfBuzzSharp.dll</HintPath>
+    </Reference>
+    <Reference Include="NationalInstruments.TestStand.Interop.AdapterAPI, Version=20.0.0.49158, Culture=neutral, PublicKeyToken=ad9244eb3b825cd8, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <EmbedInteropTypes>True</EmbedInteropTypes>
+      <HintPath>..\Libs\NationalInstruments.TestStand.Interop.AdapterAPI.dll</HintPath>
+    </Reference>
+    <Reference Include="NationalInstruments.TestStand.Interop.API, Version=20.0.0.49158, Culture=neutral, PublicKeyToken=ad9244eb3b825cd8, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <EmbedInteropTypes>True</EmbedInteropTypes>
+      <HintPath>..\Libs\NationalInstruments.TestStand.Interop.API.dll</HintPath>
+    </Reference>
+    <Reference Include="NationalInstruments.TestStand.Interop.SequenceAnalyzer, Version=20.0.0.49158, Culture=neutral, PublicKeyToken=ad9244eb3b825cd8, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <EmbedInteropTypes>True</EmbedInteropTypes>
+      <HintPath>..\Libs\NationalInstruments.TestStand.Interop.SequenceAnalyzer.dll</HintPath>
+    </Reference>
+    <Reference Include="NationalInstruments.TestStand.Interop.UI, Version=20.0.0.49158, Culture=neutral, PublicKeyToken=ad9244eb3b825cd8, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <EmbedInteropTypes>True</EmbedInteropTypes>
+      <HintPath>..\Libs\NationalInstruments.TestStand.Interop.UI.dll</HintPath>
+    </Reference>
+    <Reference Include="NationalInstruments.TestStand.Interop.UI.AxControls, Version=20.0.0.49158, Culture=neutral, PublicKeyToken=ad9244eb3b825cd8, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\Libs\NationalInstruments.TestStand.Interop.UI.AxControls.dll</HintPath>
+    </Reference>
+    <Reference Include="NationalInstruments.TestStand.Interop.UI.Support, Version=20.0.0.49158, Culture=neutral, PublicKeyToken=ad9244eb3b825cd8, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <EmbedInteropTypes>True</EmbedInteropTypes>
+      <HintPath>..\Libs\NationalInstruments.TestStand.Interop.UI.Support.dll</HintPath>
+    </Reference>
+    <Reference Include="NationalInstruments.TestStand.Utility">
+      <HintPath>..\Libs\NationalInstruments.TestStand.Utility.dll</HintPath>
+    </Reference>
+    <Reference Include="OpenTK, Version=3.3.1.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\OpenTK.3.3.1\lib\net20\OpenTK.dll</HintPath>
+    </Reference>
+    <Reference Include="ParameterHelper">
+      <HintPath>..\Libs\ParameterHelper.dll</HintPath>
+    </Reference>
+    <Reference Include="ScottPlot, Version=5.0.47.0, Culture=neutral, PublicKeyToken=86698dc10387c39e, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\ScottPlot.5.0.47\lib\net462\ScottPlot.dll</HintPath>
+    </Reference>
+    <Reference Include="ScottPlot.WPF, Version=5.0.47.0, Culture=neutral, PublicKeyToken=86698dc10387c39e, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\ScottPlot.WPF.5.0.47\lib\net462\ScottPlot.WPF.dll</HintPath>
+    </Reference>
+    <Reference Include="SkiaSharp, Version=2.88.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\SkiaSharp.2.88.9\lib\net462\SkiaSharp.dll</HintPath>
+    </Reference>
+    <Reference Include="SkiaSharp.HarfBuzz, Version=2.88.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\SkiaSharp.HarfBuzz.2.88.9\lib\net462\SkiaSharp.HarfBuzz.dll</HintPath>
+    </Reference>
+    <Reference Include="SkiaSharp.Views.Desktop.Common, Version=2.88.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\SkiaSharp.Views.Desktop.Common.2.88.9\lib\net462\SkiaSharp.Views.Desktop.Common.dll</HintPath>
+    </Reference>
+    <Reference Include="SkiaSharp.Views.WPF, Version=2.88.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\SkiaSharp.Views.WPF.2.88.9\lib\net462\SkiaSharp.Views.WPF.dll</HintPath>
+    </Reference>
+    <Reference Include="SuperSocket.Facility">
+      <HintPath>..\Libs\SuperSocket.Facility.dll</HintPath>
+    </Reference>
+    <Reference Include="SuperSocket.SocketBase">
+      <HintPath>..\Libs\SuperSocket.SocketBase.dll</HintPath>
+    </Reference>
+    <Reference Include="SuperSocket.SocketEngine">
+      <HintPath>..\Libs\SuperSocket.SocketEngine.dll</HintPath>
+    </Reference>
+    <Reference Include="SupersocketCommon">
+      <HintPath>..\Libs\SupersocketCommon.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Configuration" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Drawing.Common, Version=4.0.0.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\System.Drawing.Common.4.7.3\lib\net461\System.Drawing.Common.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Memory, Version=4.0.1.2, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll</HintPath>
+      <SpecificVersion>False</SpecificVersion>
+    </Reference>
+    <Reference Include="System.Numerics" />
+    <Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\System.Runtime.CompilerServices.Unsafe.4.5.3\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
+    </Reference>
+    <Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Xaml">
+      <RequiredTargetFramework>4.0</RequiredTargetFramework>
+    </Reference>
+    <Reference Include="WindowsBase" />
+    <Reference Include="PresentationCore" />
+    <Reference Include="PresentationFramework" />
+    <Reference Include="WindowsFormsIntegration" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="ChartWindow.xaml.cs">
+      <DependentUpon>ChartWindow.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Constant\CommonConstant.cs" />
+    <Compile Include="Model\BaseConfig.cs" />
+    <Compile Include="Model\CellInfo.cs" />
+    <Compile Include="Model\Info.cs" />
+    <Compile Include="Model\TcpServer.cs" />
+    <Compile Include="Scanner.xaml.cs">
+      <DependentUpon>Scanner.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Util\CommonCodeParser.cs" />
+    <Compile Include="Util\FontSizeConverter.cs" />
+    <Compile Include="ViewControl.xaml.cs">
+      <DependentUpon>ViewControl.xaml</DependentUpon>
+    </Compile>
+    <Page Include="Chart.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="ChartWindow.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="DataTable.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="InfoTable.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="InfoWindow.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="Scanner.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="SingleCell.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="TestResultView.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+    <Compile Include="Chart.xaml.cs">
+      <DependentUpon>Chart.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="DataTable.xaml.cs">
+      <DependentUpon>DataTable.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Util\DpUtil.cs" />
+    <Compile Include="InfoTable.xaml.cs">
+      <DependentUpon>InfoTable.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="InfoWindow.xaml.cs">
+      <DependentUpon>InfoWindow.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Model\BaseModel.cs" />
+    <Compile Include="Model\DataTableModel.cs" />
+    <Compile Include="SingleCell.xaml.cs">
+      <DependentUpon>SingleCell.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="TestResultView.xaml.cs">
+      <DependentUpon>TestResultView.xaml</DependentUpon>
+      <SubType>Code</SubType>
+    </Compile>
+    <Page Include="ViewControl.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Properties\AssemblyInfo.cs">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="Properties\Resources.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DesignTime>True</DesignTime>
+      <DependentUpon>Resources.resx</DependentUpon>
+    </Compile>
+    <Compile Include="Properties\Settings.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Settings.settings</DependentUpon>
+      <DesignTimeSharedInput>True</DesignTimeSharedInput>
+    </Compile>
+    <EmbeddedResource Include="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+    </EmbeddedResource>
+    <None Include="OpenTK.dll.config" />
+    <None Include="packages.config" />
+    <None Include="Properties\Settings.settings">
+      <Generator>SettingsSingleFileGenerator</Generator>
+      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+    </None>
+  </ItemGroup>
+  <ItemGroup>
+    <Resource Include="Resource\checkInFailed2.png" />
+    <Resource Include="Resource\ng2.png" />
+    <Resource Include="Resource\ok2.png" />
+    <Resource Include="Resource\testing2.png" />
+    <Resource Include="Resource\waiting2.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <Resource Include="Resource\alert.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <Resource Include="Resource\checkIn.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <Resource Include="Resource\ready.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <Resource Include="Resource\barcode.png" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Import Project="..\WPFProject\packages\HarfBuzzSharp.NativeAssets.macOS.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.macOS.targets" Condition="Exists('..\WPFProject\packages\HarfBuzzSharp.NativeAssets.macOS.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.macOS.targets')" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>杩欏彴璁$畻鏈轰笂缂哄皯姝ら」鐩紩鐢ㄧ殑 NuGet 绋嬪簭鍖呫�備娇鐢ㄢ�淣uGet 绋嬪簭鍖呰繕鍘熲�濆彲涓嬭浇杩欎簺绋嬪簭鍖呫�傛湁鍏虫洿澶氫俊鎭紝璇峰弬瑙� http://go.microsoft.com/fwlink/?LinkID=322105銆傜己灏戠殑鏂囦欢鏄� {0}銆�</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('..\WPFProject\packages\HarfBuzzSharp.NativeAssets.macOS.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.macOS.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\WPFProject\packages\HarfBuzzSharp.NativeAssets.macOS.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.macOS.targets'))" />
+    <Error Condition="!Exists('..\WPFProject\packages\HarfBuzzSharp.NativeAssets.Win32.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Win32.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\WPFProject\packages\HarfBuzzSharp.NativeAssets.Win32.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Win32.targets'))" />
+    <Error Condition="!Exists('..\WPFProject\packages\SkiaSharp.NativeAssets.macOS.2.88.9\build\net462\SkiaSharp.NativeAssets.macOS.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\WPFProject\packages\SkiaSharp.NativeAssets.macOS.2.88.9\build\net462\SkiaSharp.NativeAssets.macOS.targets'))" />
+    <Error Condition="!Exists('..\WPFProject\packages\SkiaSharp.NativeAssets.Win32.2.88.9\build\net462\SkiaSharp.NativeAssets.Win32.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\WPFProject\packages\SkiaSharp.NativeAssets.Win32.2.88.9\build\net462\SkiaSharp.NativeAssets.Win32.targets'))" />
+    <Error Condition="!Exists('..\WPFProject\packages\HarfBuzzSharp.NativeAssets.Linux.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Linux.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\WPFProject\packages\HarfBuzzSharp.NativeAssets.Linux.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Linux.targets'))" />
+    <Error Condition="!Exists('..\WPFProject\packages\SkiaSharp.NativeAssets.Linux.NoDependencies.2.88.9\build\net462\SkiaSharp.NativeAssets.Linux.NoDependencies.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\WPFProject\packages\SkiaSharp.NativeAssets.Linux.NoDependencies.2.88.9\build\net462\SkiaSharp.NativeAssets.Linux.NoDependencies.targets'))" />
+  </Target>
+  <Import Project="..\WPFProject\packages\HarfBuzzSharp.NativeAssets.Win32.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Win32.targets" Condition="Exists('..\WPFProject\packages\HarfBuzzSharp.NativeAssets.Win32.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Win32.targets')" />
+  <Import Project="..\WPFProject\packages\SkiaSharp.NativeAssets.macOS.2.88.9\build\net462\SkiaSharp.NativeAssets.macOS.targets" Condition="Exists('..\WPFProject\packages\SkiaSharp.NativeAssets.macOS.2.88.9\build\net462\SkiaSharp.NativeAssets.macOS.targets')" />
+  <Import Project="..\WPFProject\packages\SkiaSharp.NativeAssets.Win32.2.88.9\build\net462\SkiaSharp.NativeAssets.Win32.targets" Condition="Exists('..\WPFProject\packages\SkiaSharp.NativeAssets.Win32.2.88.9\build\net462\SkiaSharp.NativeAssets.Win32.targets')" />
+  <Import Project="..\WPFProject\packages\HarfBuzzSharp.NativeAssets.Linux.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Linux.targets" Condition="Exists('..\WPFProject\packages\HarfBuzzSharp.NativeAssets.Linux.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Linux.targets')" />
+  <Import Project="..\WPFProject\packages\SkiaSharp.NativeAssets.Linux.NoDependencies.2.88.9\build\net462\SkiaSharp.NativeAssets.Linux.NoDependencies.targets" Condition="Exists('..\WPFProject\packages\SkiaSharp.NativeAssets.Linux.NoDependencies.2.88.9\build\net462\SkiaSharp.NativeAssets.Linux.NoDependencies.targets')" />
+  <PropertyGroup>
+    <PostBuildEvent>copy /Y "$(TargetPath)" "$(SolutionDir)bin\$(PlatformName)\$(ConfigurationName)\Plugin\$(TargetFileName)" 
+copy /Y "$(TargetDir)$(TargetName).pdb" "$(SolutionDir)bin\$(PlatformName)\$(ConfigurationName)\Plugin\$(TargetName).pdb"</PostBuildEvent>
+  </PropertyGroup>
+</Project>
\ No newline at end of file
diff --git a/Plugin.TestResult/Properties/AssemblyInfo.cs b/Plugin.TestResult/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..9948624
--- /dev/null
+++ b/Plugin.TestResult/Properties/AssemblyInfo.cs
@@ -0,0 +1,53 @@
+锘縰sing System.Reflection;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+// 鏈夊叧绋嬪簭闆嗙殑涓�鑸俊鎭敱浠ヤ笅
+// 鎺у埗銆傛洿鏀硅繖浜涚壒鎬у�煎彲淇敼
+// 涓庣▼搴忛泦鍏宠仈鐨勪俊鎭��
+[assembly: AssemblyTitle("Plugin.TestResult_WPF")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Plugin.TestResult_WPF")]
+[assembly: AssemblyCopyright("Copyright 漏  2024")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 灏� ComVisible 璁剧疆涓� false 浼氫娇姝ょ▼搴忛泦涓殑绫诲瀷
+//瀵� COM 缁勪欢涓嶅彲瑙併�傚鏋滈渶瑕佷粠 COM 璁块棶姝ょ▼搴忛泦涓殑绫诲瀷
+//璇峰皢姝ょ被鍨嬬殑 ComVisible 鐗规�ц缃负 true銆�
+[assembly: ComVisible(false)]
+
+//鑻ヨ寮�濮嬬敓鎴愬彲鏈湴鍖栫殑搴旂敤绋嬪簭锛岃璁剧疆
+//.csproj 鏂囦欢涓殑 <UICulture>CultureYouAreCodingWith</UICulture>
+//鍦� <PropertyGroup> 涓�備緥濡傦紝濡傛灉浣犱娇鐢ㄧ殑鏄編鍥借嫳璇��
+//浣跨敤鐨勬槸缇庡浗鑻辫锛岃灏� <UICulture> 璁剧疆涓� en-US銆�  鐒跺悗鍙栨秷
+//瀵逛互涓� NeutralResourceLanguage 鐗规�х殑娉ㄩ噴銆�  鏇存柊
+//浠ヤ笅琛屼腑鐨勨�渆n-US鈥濅互鍖归厤椤圭洰鏂囦欢涓殑 UICulture 璁剧疆銆�
+
+//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
+
+
+[assembly: ThemeInfo(
+    ResourceDictionaryLocation.None, //涓婚鐗瑰畾璧勬簮璇嶅吀鎵�澶勪綅缃�
+                                     //(鏈湪椤甸潰涓壘鍒拌祫婧愭椂浣跨敤锛�
+                                     //鎴栧簲鐢ㄧ▼搴忚祫婧愬瓧鍏镐腑鎵惧埌鏃朵娇鐢�)
+    ResourceDictionaryLocation.SourceAssembly //甯歌璧勬簮璇嶅吀鎵�澶勪綅缃�
+                                              //(鏈湪椤甸潰涓壘鍒拌祫婧愭椂浣跨敤锛�
+                                              //銆佸簲鐢ㄧ▼搴忔垨浠讳綍涓婚涓撶敤璧勬簮瀛楀吀涓壘鍒版椂浣跨敤)
+)]
+
+
+// 绋嬪簭闆嗙殑鐗堟湰淇℃伅鐢变笅鍒楀洓涓�肩粍鎴�: 
+//
+//      涓荤増鏈�
+//      娆$増鏈�
+//      鐢熸垚鍙�
+//      淇鍙�
+//
+//鍙互鎸囧畾鎵�鏈夎繖浜涘�硷紝涔熷彲浠ヤ娇鐢ㄢ�滅敓鎴愬彿鈥濆拰鈥滀慨璁㈠彿鈥濈殑榛樿鍊�
+//閫氳繃浣跨敤 "*"锛屽涓嬫墍绀�:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Plugin.TestResult/Properties/Resources.Designer.cs b/Plugin.TestResult/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..e6971ee
--- /dev/null
+++ b/Plugin.TestResult/Properties/Resources.Designer.cs
@@ -0,0 +1,62 @@
+锘�//------------------------------------------------------------------------------
+// <auto-generated>
+//     姝や唬鐮佺敱宸ュ叿鐢熸垚銆�
+//     杩愯鏃剁増鏈�: 4.0.30319.42000
+//
+//     瀵规鏂囦欢鐨勬洿鏀瑰彲鑳藉鑷翠笉姝g‘鐨勮涓猴紝濡傛灉
+//     閲嶆柊鐢熸垚浠g爜锛屽垯鎵�鍋氭洿鏀瑰皢涓㈠け銆�
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Plugin.TestResult_WPF.Properties {
+    
+    
+    /// <summary>
+    ///   寮虹被鍨嬭祫婧愮被锛岀敤浜庢煡鎵炬湰鍦板寲瀛楃涓茬瓑銆�
+    /// </summary>
+    // 姝ょ被鏄敱 StronglyTypedResourceBuilder
+    // 绫婚�氳繃绫讳技浜� ResGen 鎴� Visual Studio 鐨勫伐鍏疯嚜鍔ㄧ敓鎴愮殑銆�
+    // 鑻ヨ娣诲姞鎴栫Щ闄ゆ垚鍛橈紝璇风紪杈� .ResX 鏂囦欢锛岀劧鍚庨噸鏂拌繍琛� ResGen
+    // (浠� /str 浣滀负鍛戒护閫夐」)锛屾垨閲嶆柊鐢熸垚 VS 椤圭洰銆�
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources {
+        
+        private static global::System.Resources.ResourceManager resourceMan;
+        
+        private static global::System.Globalization.CultureInfo resourceCulture;
+        
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources() {
+        }
+        
+        /// <summary>
+        ///   杩斿洖姝ょ被浣跨敤鐨勭紦瀛� ResourceManager 瀹炰緥銆�
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager {
+            get {
+                if ((resourceMan == null)) {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Plugin.TestResult_WPF.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+        
+        /// <summary>
+        ///   閲嶅啓褰撳墠绾跨▼鐨� CurrentUICulture 灞炴�э紝瀵�
+        ///   浣跨敤姝ゅ己绫诲瀷璧勬簮绫荤殑鎵�鏈夎祫婧愭煡鎵炬墽琛岄噸鍐欍��
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture {
+            get {
+                return resourceCulture;
+            }
+            set {
+                resourceCulture = value;
+            }
+        }
+    }
+}
diff --git a/Plugin.TestResult/Properties/Resources.resx b/Plugin.TestResult/Properties/Resources.resx
new file mode 100644
index 0000000..af7dbeb
--- /dev/null
+++ b/Plugin.TestResult/Properties/Resources.resx
@@ -0,0 +1,117 @@
+锘�<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>
\ No newline at end of file
diff --git a/Plugin.TestResult/Properties/Settings.Designer.cs b/Plugin.TestResult/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..45f5698
--- /dev/null
+++ b/Plugin.TestResult/Properties/Settings.Designer.cs
@@ -0,0 +1,30 @@
+锘�//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.42000
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Plugin.TestResult_WPF.Properties
+{
+    
+    
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+    {
+        
+        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+        
+        public static Settings Default
+        {
+            get
+            {
+                return defaultInstance;
+            }
+        }
+    }
+}
diff --git a/Plugin.TestResult/Properties/Settings.settings b/Plugin.TestResult/Properties/Settings.settings
new file mode 100644
index 0000000..033d7a5
--- /dev/null
+++ b/Plugin.TestResult/Properties/Settings.settings
@@ -0,0 +1,7 @@
+锘�<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
+  <Profiles>
+    <Profile Name="(Default)" />
+  </Profiles>
+  <Settings />
+</SettingsFile>
\ No newline at end of file
diff --git a/Plugin.TestResult/Resource/alert.png b/Plugin.TestResult/Resource/alert.png
new file mode 100644
index 0000000..6a9e531
--- /dev/null
+++ b/Plugin.TestResult/Resource/alert.png
Binary files differ
diff --git a/Plugin.TestResult/Resource/barcode.png b/Plugin.TestResult/Resource/barcode.png
new file mode 100644
index 0000000..b68a9d6
--- /dev/null
+++ b/Plugin.TestResult/Resource/barcode.png
Binary files differ
diff --git a/Plugin.TestResult/Resource/checkIn.png b/Plugin.TestResult/Resource/checkIn.png
new file mode 100644
index 0000000..eb4f984
--- /dev/null
+++ b/Plugin.TestResult/Resource/checkIn.png
Binary files differ
diff --git a/Plugin.TestResult/Resource/checkInFailed2.png b/Plugin.TestResult/Resource/checkInFailed2.png
new file mode 100644
index 0000000..b4e6cfd
--- /dev/null
+++ b/Plugin.TestResult/Resource/checkInFailed2.png
Binary files differ
diff --git a/Plugin.TestResult/Resource/ng2.png b/Plugin.TestResult/Resource/ng2.png
new file mode 100644
index 0000000..b1da88a
--- /dev/null
+++ b/Plugin.TestResult/Resource/ng2.png
Binary files differ
diff --git a/Plugin.TestResult/Resource/ok2.png b/Plugin.TestResult/Resource/ok2.png
new file mode 100644
index 0000000..637da7a
--- /dev/null
+++ b/Plugin.TestResult/Resource/ok2.png
Binary files differ
diff --git a/Plugin.TestResult/Resource/ready.png b/Plugin.TestResult/Resource/ready.png
new file mode 100644
index 0000000..df8dfd3
--- /dev/null
+++ b/Plugin.TestResult/Resource/ready.png
Binary files differ
diff --git a/Plugin.TestResult/Resource/testing2.png b/Plugin.TestResult/Resource/testing2.png
new file mode 100644
index 0000000..531bff9
--- /dev/null
+++ b/Plugin.TestResult/Resource/testing2.png
Binary files differ
diff --git a/Plugin.TestResult/Resource/waiting2.png b/Plugin.TestResult/Resource/waiting2.png
new file mode 100644
index 0000000..2fd9c21
--- /dev/null
+++ b/Plugin.TestResult/Resource/waiting2.png
Binary files differ
diff --git a/Plugin.TestResult/Scanner.xaml b/Plugin.TestResult/Scanner.xaml
new file mode 100644
index 0000000..3a7c2ad
--- /dev/null
+++ b/Plugin.TestResult/Scanner.xaml
@@ -0,0 +1,54 @@
+锘�<Window x:Class="Plugin.TestResult_WPF.Scanner"
+        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.TestResult_WPF"
+        WindowStartupLocation="CenterScreen"
+        Closing="Window_Closing"
+        mc:Ignorable="d"
+        Title="{Binding title}" Height="450" Width="800">
+    <Window.Resources>
+        <local:FontSizeConverter x:Key="FontSizeConverter"/>
+        <Style TargetType="Label">
+            <Setter Property="HorizontalAlignment" Value="Right"/>
+            <Setter Property="FontSize" Value="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=Window}, 
+Converter={StaticResource FontSizeConverter}, ConverterParameter='100,1920'}" />
+        </Style>
+        <Style TargetType="TextBox">
+            <Setter Property="FontSize" Value="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=Window}, 
+Converter={StaticResource FontSizeConverter}, ConverterParameter='100,1920'}" />
+        </Style>
+        <Style TargetType="Button">
+            <Setter Property="FontSize" Value="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=Window}, 
+Converter={StaticResource FontSizeConverter}, ConverterParameter='100,1920'}" />
+        </Style>
+    </Window.Resources>
+
+    <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition />
+            <!--<RowDefinition />-->
+            <RowDefinition />
+            <RowDefinition Height="Auto"/>
+        </Grid.RowDefinitions>
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="Auto"/>
+            <ColumnDefinition />
+        </Grid.ColumnDefinitions>
+        <Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" HorizontalContentAlignment="Center" HorizontalAlignment="Stretch">璇蜂緷娆℃壂涓嬪垪鏉$爜</Label>
+        <Label Grid.Row="1" Grid.Column="0">鍌ㄤ綅鏉$爜</Label>
+        <!--<Label Grid.Row="2" Grid.Column="0">澶瑰叿鏉$爜</Label>-->
+        <Label Grid.Row="2" Grid.Column="0">浜у搧鏉$爜</Label>
+
+        <TextBox Grid.Row="1" Grid.Column="1" x:Name="positionText" TextWrapping="NoWrap"/>
+        <!--<TextBox Grid.Row="2" Grid.Column="1"/>-->
+        <TextBox Grid.Row="2" Grid.Column="1" x:Name="snrText"/>
+
+        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2">
+            <Button Margin="20" Padding="15" Click="Con_Click">缁х画鎵弿</Button>
+            <Button Margin="20" Padding="15" Click="Ok_Click">鎵弿瀹屾垚</Button>
+        </StackPanel>
+    </Grid>
+</Window>
diff --git a/Plugin.TestResult/Scanner.xaml.cs b/Plugin.TestResult/Scanner.xaml.cs
new file mode 100644
index 0000000..7e80db7
--- /dev/null
+++ b/Plugin.TestResult/Scanner.xaml.cs
@@ -0,0 +1,216 @@
+锘縰sing System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO.Ports;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+
+namespace Plugin.TestResult_WPF
+{
+    public class BarcodeEventArg
+    {
+        public string name { get; private set; }
+        public string positionCode { get; private set; }
+        public string snrCode { get; private set; }
+
+        public BarcodeEventArg(string name, string positionCode, string snrCode)
+        {
+            this.name = name;
+            this.positionCode = positionCode;
+            this.snrCode = snrCode;
+        }
+    }
+
+    /// <summary>
+    /// Scanner.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class Scanner : Window, INotifyPropertyChanged
+    {
+        #region ...
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        public virtual void RaisePropertyChanged(string name)
+        {
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+        }
+
+        public void RaiseAndSetIfchanged<T>(ref T old, T @new, [CallerMemberName] string propertyName = null)
+        {
+            old = @new;
+            if (propertyName != null) RaisePropertyChanged(propertyName);
+        }
+        #endregion
+
+        private string _title;
+
+        public string title
+        {
+            get { return _title; }
+            set => RaiseAndSetIfchanged(ref _title, value);
+        }
+
+        public event EventHandler<BarcodeEventArg> FinishBarcode;
+
+        private DataTable dataTable;
+        SerialPort serialPort;
+        public Scanner(string title)
+        {
+            this.title = title;
+            this.DataContext = this;
+            InitializeComponent();
+            InitScanner();
+        }
+
+        /// <summary>
+        /// 璁剧疆鎵爜鏋�
+        /// </summary>
+        private void InitScanner()
+        {
+            string comStr = "";
+            if (serialPort != null) return;
+            if (this.title == "涓�鍖烘壂鐮�")
+            {
+                comStr = "COM1";
+            }
+            else
+            {
+                comStr = "COM2";
+            }
+
+            // 閰嶇疆涓插彛
+            serialPort = new SerialPort
+            {
+                PortName = comStr,
+                BaudRate = 115200,
+                Parity = Parity.None,
+                DataBits = 8,
+                StopBits = StopBits.One,
+                Handshake = Handshake.None,
+                DtrEnable = true,
+                RtsEnable = true
+                //ReadTimeout = 500,
+                //WriteTimeout = 500,
+                //ReciveBufferSize=4096
+            };
+
+            try
+            {
+                // 鎵撳紑涓插彛
+                serialPort.Open();
+                // 娉ㄥ唽鎺ユ敹鏁版嵁浜嬩欢
+                serialPort.DataReceived += SerialPort_DataReceived;
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"鍒濆鍖栨壂鐮佹灙澶辫触锛亄ex.StackTrace}");
+            }
+        }
+
+        private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
+        {
+            try
+            {
+                SerialPort sp = (SerialPort)sender;
+                string barcode = ReadDataFrom(sp);
+
+                //string barcode = 
+                Dispatcher.Invoke(() =>
+                {
+                    if (snrText.IsFocused)
+                    {
+                        snrText.Text = barcode;
+                        Con_Click(null, null);
+                    }
+                    else
+                    {
+                        positionText.Text = barcode;
+                        //positionText.Focus();
+                        //snrText.Text = "";
+                        snrText.Focus();
+                    }
+                });
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"鑾峰彇鎵爜鏋暟鎹け璐ワ紒{ex.StackTrace}");
+            }
+        }
+
+        private void Con_Click(object sender, RoutedEventArgs e)
+        {
+            Dispatcher.Invoke(() =>
+            {
+                string position = positionText.Text, snr = snrText.Text;
+                if (string.IsNullOrEmpty(position) || string.IsNullOrEmpty(snr))
+                {
+                    MessageBox.Show("璇疯緭鍏ュ偍浣嶆潯鐮佸拰浜у搧鏉$爜");
+                    return;
+                }
+                try
+                {
+                    FinishBarcode.Invoke(this, new BarcodeEventArg(title, position, snr));
+                    //positionText.Text = "";
+                    //snrText.Text = "";
+                    positionText.Focus();
+                }
+                catch (Exception ex)
+                {
+                    MessageBox.Show(ex.Message);
+                }
+            });
+        }
+
+        private void Ok_Click(object sender, RoutedEventArgs e)
+        {
+            string position = positionText.Text, snr = snrText.Text;
+            if (string.IsNullOrEmpty(position) || string.IsNullOrEmpty(snr))
+            {
+                MessageBox.Show("璇疯緭鍏ュ偍浣嶆潯鐮佸拰浜у搧鏉$爜");
+                return;
+            }
+            try
+            {
+                FinishBarcode.Invoke(this, new BarcodeEventArg(title, position, snr));
+                this.Close();
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show(ex.Message);
+            }
+        }
+
+        private string ReadDataFrom(SerialPort sp)
+        {
+            string barcode = "";
+            while (sp.BytesToRead > 0)
+            {
+                byte[] buffer = new byte[sp.BytesToRead];
+                sp.Read(buffer, 0, buffer.Length);
+                barcode += System.Text.Encoding.UTF8.GetString(buffer);
+                System.Threading.Thread.Sleep(50);
+            }
+            return barcode;
+        }
+
+        private void Window_Closing(object sender, CancelEventArgs e)
+        {
+            try
+            {
+                serialPort.Close();
+            }
+            catch (Exception ex)
+            {
+            }
+        }
+    }
+}
diff --git a/Plugin.TestResult/SingleCell.xaml b/Plugin.TestResult/SingleCell.xaml
new file mode 100644
index 0000000..35448dc
--- /dev/null
+++ b/Plugin.TestResult/SingleCell.xaml
@@ -0,0 +1,101 @@
+锘�<UserControl x:Class="Plugin.TestResult_WPF.SingleCell"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:Plugin.TestResult_WPF"
+             mc:Ignorable="d" 
+             x:Name="uc"
+             Padding="5"
+             MouseDoubleClick="UserControl_MouseDoubleClick"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <UserControl.Resources>
+        <local:FontSizeConverter x:Key="FontSizeConverter"/>
+    </UserControl.Resources>
+
+    <UserControl.ToolTip>
+        <ToolTip Padding="5" BorderThickness="1" BorderBrush="Black" Background="Yellow" Content="{Binding tooltipContent}"/>
+    </UserControl.ToolTip>
+
+    <UserControl.Style>
+        <Style TargetType="UserControl">
+            <Style.Triggers>
+                <DataTrigger Binding="{Binding status}" Value="ok">
+                    <Setter Property="Background" Value="#FF0F8853"/>
+                </DataTrigger>
+                <DataTrigger Binding="{Binding status}" Value="ng">
+                    <Setter Property="Background" Value="#fd6b6d"/>
+                </DataTrigger>
+                <DataTrigger Binding="{Binding status}" Value="checkInFailed">
+                    <Setter Property="Background" Value="#ffac00"/>
+                </DataTrigger>
+                <DataTrigger Binding="{Binding status}" Value="checkIn">
+                    <Setter Property="Background" Value="#FF00E2FF"/>
+                </DataTrigger>
+                <DataTrigger Binding="{Binding status}" Value="testing">
+                    <Setter Property="Background" Value="#1296db"/>
+                </DataTrigger>
+                <DataTrigger Binding="{Binding status}" Value="waiting">
+                    <Setter Property="Background" Value="#6e6e6e"/>
+                </DataTrigger>
+                <DataTrigger Binding="{Binding status}" Value="alert">
+                    <Setter Property="Background" Value="#e04733"/>
+                </DataTrigger>
+                <DataTrigger Binding="{Binding status}" Value="ready">
+                    <Setter Property="Background" Value="#FF08FF00"/>
+                </DataTrigger>
+                <DataTrigger Binding="{Binding status}" Value="hadCode">
+                    <Setter Property="Background" Value="#FFF"/>
+                </DataTrigger>
+            </Style.Triggers>
+        </Style>
+    </UserControl.Style>
+    <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition/>
+        </Grid.RowDefinitions>
+        <!--<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" 
+               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.Style>
+                <Style TargetType="Image">
+                    <Style.Triggers>
+                        <DataTrigger Binding="{Binding status}" Value="ok">
+                            <Setter Property="Source" Value="Resource/ok2.png"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="ng">
+                            <Setter Property="Source" Value="Resource/ng2.png"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="checkInFailed">
+                            <Setter Property="Source" Value="Resource/checkInFailed2.png"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="checkIn">
+                            <Setter Property="Source" Value="Resource/checkIn.png"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="testing">
+                            <Setter Property="Source" Value="Resource/testing2.png"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="waiting">
+                            <Setter Property="Source" Value="Resource/waiting2.png"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="alert">
+                            <Setter Property="Source" Value="Resource/alert.png"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="ready">
+                            <Setter Property="Source" Value="Resource/ready.png"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="hadCode">
+                            <Setter Property="Source" Value="Resource/barcode.png"/>
+                        </DataTrigger>
+                    </Style.Triggers>
+                </Style>
+            </Image.Style>
+        </Image>
+    </Grid>
+</UserControl>
diff --git a/Plugin.TestResult/SingleCell.xaml.cs b/Plugin.TestResult/SingleCell.xaml.cs
new file mode 100644
index 0000000..b218e98
--- /dev/null
+++ b/Plugin.TestResult/SingleCell.xaml.cs
@@ -0,0 +1,195 @@
+锘縰sing Plugin.TestResult_WPF.Constant;
+using Plugin.TestResult_WPF.Model;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+
+namespace Plugin.TestResult_WPF
+{
+    /// <summary>
+    /// SingleCell.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class SingleCell : UserControl, INotifyPropertyChanged
+    {
+
+        #region ...
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        public virtual void RaisePropertyChanged(string name)
+        {
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+        }
+
+        public void RaiseAndSetIfchanged<T>(ref T old, T @new, [CallerMemberName] string propertyName = null)
+        {
+            old = @new;
+            if (propertyName != null) RaisePropertyChanged(propertyName);
+        }
+        #endregion
+
+        private InfoWindow window;
+        private int maxSize;
+
+        public SingleCell()
+        {
+            DataContext = this;
+            InitializeComponent();
+        }
+
+        public SingleCell(string parentName, int index,int maxSize)
+        {
+            this.parentName = parentName;
+            this.index = index;
+            this.maxSize = maxSize;
+            DataContext = this;
+            InitializeComponent();
+        }
+
+        private string _parentName;
+        public string parentName
+        {
+            get { return _parentName; }
+            set
+            {
+                RaiseAndSetIfchanged(ref _parentName, value);
+                RaisePropertyChanged("title");
+            }
+        }
+
+        private int _index;
+
+        public int index
+        {
+            get { return _index; }
+            set
+            {
+                RaiseAndSetIfchanged(ref _index, value);
+                RaisePropertyChanged("title");
+            }
+        }
+
+        public string title => $"{parentName}{index + 1}";
+
+        private string _status = CommonStatus.WAITING;
+
+        public string tooltipContent
+        {
+            get
+            {
+                string dataStr = "";
+                if (infos != null && infos.Count > 0)
+                {
+                    foreach (var info in infos)
+                    {
+                        dataStr += $"\r\nStep:{info.step},Result:{info.result}";
+                    }
+                }
+                return $"{title}\r\n鐘舵��:{CommonStatus.GetStatusDescription(status)}{dataStr}";
+            }
+        }
+
+        public string status
+        {
+            get { return _status; }
+            set => RaiseAndSetIfchanged(ref _status, value);
+        }
+
+        private ConcurrentQueue<Info> queue = new ConcurrentQueue<Info>();
+
+        private ObservableCollection<Info> _infos;
+
+        public ObservableCollection<Info> infos
+        {
+            get => _infos;
+            set
+            {
+                RaiseAndSetIfchanged(ref _infos, value);
+                RaisePropertyChanged("tooltipContent");
+            }
+        }
+
+        private void UserControl_MouseDoubleClick(object sender, MouseButtonEventArgs e)
+        {
+            if (window == null)
+            {
+                window = new InfoWindow(title, infos);
+                window.Closed += Window_Closed;
+                window.Owner = Window.GetWindow(this);
+                window.Show();
+            }
+            else
+            {
+                window.Activate();
+            }
+        }
+
+        private void Window_Closed(object sender, EventArgs e)
+        {
+            this.window = null;
+        }
+
+        public void SetStatus(string status)
+        {
+            this.status = status;
+        }
+
+        public void SetInfos(List<Info> infos)
+        {
+            this.infos = new ObservableCollection<Info>(infos);
+            if (window != null) window.UpdateSource(this.infos);
+        }
+
+        public void SetInfos(string[,] infos)
+        {
+            var data = new ObservableCollection<Info>();
+            for (int i = 0; i < infos.GetLength(0); i++)
+            {
+                data.Add(new Info
+                {
+                    step = infos[i, 0],
+                    result = infos[i, 1]
+                });
+            }
+            this.infos = data;
+            if (window != null) window.UpdateSource(this.infos);
+        }
+
+        public void SetInfos(Info info)
+        {
+            if (queue.Count >= maxSize)
+            {
+                queue.TryDequeue(out var first);
+            }
+            queue.Enqueue(info);
+            var list = new ObservableCollection<Info>(queue);
+            this.infos = list;
+            if (window != null) window.UpdateSource(this.infos);
+        }
+
+        public void ClearInfo()
+        {
+            queue = new ConcurrentQueue<Info>();
+            var list = new ObservableCollection<Info>(queue);
+            this.infos = list;
+            if (window != null) window.UpdateSource(this.infos);
+        }
+
+        public void SetInfos(string step, string result)
+        {
+            if (queue.Count >= maxSize)
+            {
+                queue.TryDequeue(out var info);
+            }
+            queue.Enqueue(new Info() { step = step, result = result });
+            var list = new ObservableCollection<Info>(queue);
+            this.infos = list;
+            if (window != null) window.UpdateSource(this.infos);
+        }
+    }
+}
diff --git a/Plugin.TestResult/TestResultView.xaml b/Plugin.TestResult/TestResultView.xaml
new file mode 100644
index 0000000..3429e54
--- /dev/null
+++ b/Plugin.TestResult/TestResultView.xaml
@@ -0,0 +1,16 @@
+锘�<UserControl x:Class="Plugin.TestResult_WPF.TestResultView"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:Plugin.TestResult_WPF"
+             mc:Ignorable="d" 
+             FontFamily="Microsoft YaHei"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <Grid x:Name="panel">
+        <WindowsFormsHost x:Name="host" Visibility="Collapsed"></WindowsFormsHost>
+        <Grid x:Name="mainGrid" local:GridProperties.BorderSpace="10">
+
+        </Grid>
+    </Grid>
+</UserControl>
diff --git a/Plugin.TestResult/TestResultView.xaml.cs b/Plugin.TestResult/TestResultView.xaml.cs
new file mode 100644
index 0000000..b5877c6
--- /dev/null
+++ b/Plugin.TestResult/TestResultView.xaml.cs
@@ -0,0 +1,383 @@
+锘縰sing NationalInstruments.TestStand.Interop.API;
+using NationalInstruments.TestStand.Interop.UI.Ax;
+using NationalInstruments.TestStand.Utility;
+using Plugin.TestResult_WPF.Constant;
+using Plugin.TestResult_WPF.Model;
+using System;
+using System.Collections.Generic;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Plugin.TestResult_WPF
+{
+    /// <summary>
+    /// TestResultView.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class TestResultView : UserControl
+    {
+        private int rowCount;
+        private int colCount;
+        private int cellCount;
+        private DataTable[] tables;
+        public Dictionary<string, DataTable> nameTables { get; private set; }
+        private readonly BaseConfig config;
+
+        public AxApplicationMgr testApp;
+        public string testSeqFile;
+        /// <summary>
+        /// 娴嬭瘯搴忓垪
+        /// </summary>
+        private SequenceFile testSeq = null;
+        public Engine engine;
+        private Execution execution = null;
+        /// <summary>
+        /// 娴嬭瘯搴忓垪鎿嶄綔鍙ユ焺
+        /// </summary>
+        private SequenceContext SequenceContext = null;
+        /// <summary>
+        /// 娴嬭瘯妯″瀷搴忓垪
+        /// </summary>
+        private SequenceFile modeSeq;
+
+        public TestResultView() : this(new BaseConfig { testing = false })
+        {
+
+        }
+
+        public TestResultView(BaseConfig config)
+        {
+            this.rowCount = config.Row;
+            this.colCount = config.Column;
+            this.config = config;
+            if (config.CellCount < 0)
+            {
+                this.cellCount = rowCount * colCount;
+            }
+            else
+            {
+                this.cellCount = config.CellCount;
+            }
+            tables = new DataTable[this.cellCount];
+
+
+            InitializeComponent();
+            testApp = new AxApplicationMgr();
+            testApp.ExitApplication += new EventHandler(this.axApplicationMgr_ExitApplication);
+            testApp.UIMessageEvent += new _ApplicationMgrEvents_UIMessageEventEventHandler(this.axApplicationMgr_UIMessageEvent);
+            testApp.ExecutionClosed += new _ApplicationMgrEvents_ExecutionClosedEventHandler(this.axApplicationMgr_ExecutionClosed);
+
+
+            ((System.ComponentModel.ISupportInitialize)(this.testApp)).BeginInit();//myClient涓虹涓夋柟鎺т欢鍚�
+            host.Child = testApp;
+            ((System.ComponentModel.ISupportInitialize)(this.testApp)).EndInit();
+            InitMainGrid();
+            //InitTestStandEngine();
+            //StartTestSequence();
+        }
+
+        private void Refresh()
+        {
+            for (int i = 0; i < tables.Length; i++)
+            {
+                var sb = new System.Timers.Timer()
+                {
+                    Interval = 1000,
+                    AutoReset = true,
+                };
+                var table = tables[i];
+                Random random = new Random(i * 13);
+                sb.Elapsed += (s, e) =>
+                {
+
+                    table.SetTemperature(random.Next(100));
+                    for (int j = 0; j < 3; j++)
+                    {
+                        table.SetTemperature(j + "89", random.Next(100));
+                    }
+                    table.SetOkCount(random.Next(100));
+                    table.SetNgCount(random.Next(100));
+                    table.SetStatus(CommonStatus.GetRandomStatus());
+                    table.SetStartTime(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
+                    table.SetEndTime(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
+                    table.SetConsumeTime(random.Next(10000));
+
+
+                    var cell = table.GetSingleCell(random.Next(table.GetSize()));
+                    cell.SetStatus(CommonStatus.GetRandomStatus());
+                    cell.SetInfos(random.Next(1000) + "", random.Next(1000) + "");
+                    table.SetStatusText(DateTime.Now + "");
+                };
+                sb.Start();
+            }
+        }
+
+        public void InitMainGrid()
+        {
+            nameTables = new Dictionary<string, DataTable>();
+            for (int r = 0; r < rowCount; r++)
+            {
+                RowDefinition row = new RowDefinition();
+                mainGrid.RowDefinitions.Add(row);
+            }
+
+            for (int c = 0; c < colCount; c++)
+            {
+                ColumnDefinition col = new ColumnDefinition();
+                mainGrid.ColumnDefinitions.Add(col);
+            }
+            string[] names = config.NameStr.Split(',');
+            for (int n = 0; n < cellCount; n++)
+            {
+                string name = n < names.Length ? names[n] : "";
+                DataTable child = new DataTable(this, n, name, config);
+                mainGrid.Children.Add(child);
+                tables[n] = child;
+                nameTables[name] = child;
+                int row = n / colCount;
+                int col = n % colCount;
+                Grid.SetRow(child, row);
+                Grid.SetColumn(child, col);
+            }
+            if (config.testing) Refresh();
+        }
+
+        public DataTable GetDataTable(int index)
+        {
+            if (index > 0 && index < tables.Length) return tables[index];
+            return null;
+        }
+
+        /// <summary>
+        /// 鍚姩娴嬭瘯搴忓垪
+        /// </summary>
+        public void InitTestStandEngine()
+        {
+            try
+            {
+                this.engine = this.testApp.GetEngine();
+                this.engine.PersistBreakpoints = false;
+                this.testApp.IsEditor = true;
+                this.testApp.LoginOnStart = false;
+                this.testApp.Start();
+                this.modeSeq = this.engine.GetSequenceFileEx(@"C:\Program Files\National Instruments\TestStand 2020\Components\Models\TestStandModels\ParallelModel.seq");
+
+            }
+            catch (Exception ex)
+            {
+                throw new Exception("鍒濆鍖朤S寮曟搸澶辫触:" + ex.Message);
+            }
+        }
+
+        public void StartTestSequence()
+        {
+            try
+            {
+                this.modeSeq = this.engine.GetSequenceFileEx(@"C:\Program Files\National Instruments\TestStand 2020\Components\Models\TestStandModels\ParallelModel.seq");
+                this.testSeq = this.engine.GetSequenceFileEx(this.testSeqFile, 0, TypeConflictHandlerTypes.ConflictHandler_UseGlobalType);
+                this.execution = this.engine.NewExecution(this.testSeq, "Test UUTs", this.modeSeq, false, 0, null, null, null);
+                this.engine.Globals.SetValBoolean("StationState.StopTest", 0, false);
+            }
+            catch (Exception ex)
+            {
+                throw new Exception("鍚姩娴嬭瘯搴忓垪澶辫触:" + ex.Message);
+            }
+        }
+
+        public void StopTest()
+        {
+            try
+            {
+                if (this.SequenceContext == null)
+                    return;
+                bool val = this.SequenceContext.StationGlobals.Exists("StationState.StopTest", 0);
+                if (val)
+                {
+                    this.SequenceContext.StationGlobals.SetValBoolean("StationState.StopTest", 0, true);
+                    bool isStop = this.execution.WaitForEndEx(2000, true, null, null);
+                }
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message);
+            }
+        }
+
+        private void axApplicationMgr_ExitApplication(object sender, EventArgs e)
+        {
+            Environment.ExitCode = testApp.ExitCode;
+            TSHelper.DoSynchronousGCForCOMObjectDestruction();
+        }
+
+        private void axApplicationMgr_ExecutionClosed(object sender, _ApplicationMgrEvents_ExecutionClosedEvent e)
+        {
+            try
+            {
+                //this.ReleaseSequenceFile();
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show("閲婃斁娴嬭瘯搴忓垪澶辫触:" + ex.Message);
+            }
+        }
+
+        private void axApplicationMgr_UIMessageEvent(object sender, _ApplicationMgrEvents_UIMessageEventEvent e)
+        {
+            try
+            {
+                int EventCase = Convert.ToInt32(e.uiMsg.Event);
+                int MethodCode = Convert.ToInt16(e.uiMsg.NumericData);
+                string strData;
+
+                if (EventCase != 10014) return;
+
+                if (MethodCode == 0)
+                {
+                    /// 璁剧疆鍙ユ焺
+                    this.SequenceContext = (SequenceContext)e.uiMsg.ActiveXData;
+                    return;
+                }
+
+                if (MethodCode == 1)
+                {
+                    /// 鏇存柊鐐夊瓙鐘舵��
+                    strData = e.uiMsg.StringData;
+                    string[] datas_10014 = strData.Split(';');
+                    int ovenIndex = int.Parse(datas_10014[0]);
+
+                    string status = datas_10014[1];
+                    tables[ovenIndex].SetStatus(status);
+                    return;
+                }
+                if (MethodCode == 2)
+                {
+                    /// 鏇存柊鐐夊瓙鏁伴噺淇℃伅
+                    strData = e.uiMsg.StringData;
+                    string[] datas_10014 = strData.Split(';');
+                    int ovenIndex = int.Parse(datas_10014[0]);
+
+                    string okCount = datas_10014[1];
+                    string ngCount = datas_10014[2];
+                    tables[ovenIndex].SetOkCount(int.Parse(okCount));
+                    tables[ovenIndex].SetNgCount(int.Parse(ngCount));
+                    return;
+                }
+                if (MethodCode == 3)
+                {
+                    /// 鏇存柊鐐夊瓙鏃堕棿淇℃伅
+                    strData = e.uiMsg.StringData;
+                    string[] datas_10014 = strData.Split(';');
+                    int ovenIndex = int.Parse(datas_10014[0]);
+
+                    string startTime = datas_10014[1];
+                    string consumeTime = datas_10014[2];
+                    string planEndTime = datas_10014[3];
+                    tables[ovenIndex].SetStartTime(startTime);
+                    tables[ovenIndex].SetConsumeTime(double.Parse(consumeTime));
+                    tables[ovenIndex].SetEndTime(planEndTime);
+                    return;
+                }
+                if (MethodCode == 4)
+                {
+                    /// 鏇存柊鑰佸寲鐐夊簳閮ㄧ姸鎬佹爮
+                    strData = e.uiMsg.StringData;
+                    string[] datas_10014 = strData.Split(';');
+                    int ovenIndex = int.Parse(datas_10014[0]);
+
+                    string statusBar = datas_10014[1];
+                    tables[ovenIndex].SetStatusText(statusBar);
+                    return;
+                }
+                if (MethodCode == 5)
+                {
+                    /// 閲囬泦鐐夋俯搴�
+                    strData = e.uiMsg.StringData;
+                    string[] datas_10016 = strData.Split(';');
+                    int ovenIndex = int.Parse(datas_10016[0]);
+
+                    string[] names = datas_10016[1].Split(',');
+                    string[] values = datas_10016[2].Split(',');
+
+                    for (int x = 0; x < values.Length; x++)
+                    {
+                        if (x == 0) tables[ovenIndex].SetTemperature(double.Parse(values[x]));
+                        else tables[ovenIndex].SetTemperature(names[x - 1], double.Parse(values[x]));
+                    }
+                    return;
+                }
+                if (MethodCode == 6)
+                {
+                    /// 鏇存柊绌翠綅鐘舵��
+                    strData = e.uiMsg.StringData;
+                    string[] datas_10015 = strData.Split(';');
+                    int ovenIndex = int.Parse(datas_10015[0]);
+                    int cellIndex = int.Parse(datas_10015[1]);
+                    string status_10015 = datas_10015[2];
+                    tables[ovenIndex].GetSingleCell(cellIndex).SetStatus(status_10015);
+                    return;
+                }
+                if (MethodCode == 7)
+                {
+                    /// 鏇存柊绌翠綅鍙傛暟
+                    strData = e.uiMsg.StringData;
+                    string[] datas_10015 = strData.Split(';');
+                    int ovenIndex = int.Parse(datas_10015[0]);
+                    int cellIndex = int.Parse(datas_10015[1]);
+
+                    string strs = datas_10015[2];
+                    string[] values = strs.Split(',');
+
+                    //string step = datas_10015[2];
+                    //string result = datas_10015[3];
+                    tables[ovenIndex].GetSingleCell(cellIndex).SetInfos(new Info
+                    {
+                        step = values[0],
+                        value = values[1],
+                        result = values[2],
+                        range = values[3],
+                        condition = values[4],
+                        updateTime = values[5]
+                    });
+                    return;
+                }
+                if (MethodCode == 10)
+                {
+                    /// 娓呯┖鐐夊瓙鐨勬俯搴﹀浘琛�
+                    strData = e.uiMsg.StringData;
+                    int ovenIndex = int.Parse(strData);
+                    tables[ovenIndex].ClearChartData();
+                    return;
+                }
+                if (MethodCode == 11)
+                {
+                    /// 娓呴櫎绌翠綅鍙傛暟
+                    strData = e.uiMsg.StringData;
+                    string[] datas_10015 = strData.Split(';');
+                    int ovenIndex = int.Parse(datas_10015[0]);
+                    int cellIndex = int.Parse(datas_10015[1]);
+                    tables[ovenIndex].GetSingleCell(cellIndex).ClearInfo();
+                    return;
+                }
+            }
+            catch (Exception ex)
+            {
+
+            }
+        }
+
+        public void SetStartFlag(int index, bool data)
+        {
+            if (this.SequenceContext == null) return;
+            char name = (char)(index + 65);
+            string key = $"StationState.Start_{name}";
+            this.engine.Globals.SetValBoolean(key, 0, data);
+            bool val = this.engine.Globals.GetValBoolean(key, 0);
+            //this.SequenceContext.StationGlobals.SetValBoolean(key, 0, data);
+            //bool val = this.SequenceContext.StationGlobals.GetValBoolean(key, 0);
+        }
+
+        public void SetSnrToSeqContext(int index, string snr)
+        {
+
+        }
+    }
+}
diff --git a/Plugin.TestResult/Util/CommonCodeParser.cs b/Plugin.TestResult/Util/CommonCodeParser.cs
new file mode 100644
index 0000000..605ea69
--- /dev/null
+++ b/Plugin.TestResult/Util/CommonCodeParser.cs
@@ -0,0 +1,27 @@
+锘縰sing System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Plugin.TestResult_WPF.Model;
+
+namespace Plugin.TestResult_WPF.Util
+{
+    public class CommonCodeParser
+    {
+        public CellInfo GetCellInfoByCode(string code)
+        {
+            string[] arr = code.Split('-');
+            if (arr == null || arr.Length != 3)
+            {
+                throw new Exception("鎵爜鏍煎紡寮傚父");
+            }
+            return new CellInfo()
+            {
+                MachineName = arr[0],
+                OvenName = arr[1],
+                Index = int.Parse(arr[2]) - 1,
+            };
+        }
+    }
+}
diff --git a/Plugin.TestResult/Util/DpUtil.cs b/Plugin.TestResult/Util/DpUtil.cs
new file mode 100644
index 0000000..cc3c52c
--- /dev/null
+++ b/Plugin.TestResult/Util/DpUtil.cs
@@ -0,0 +1,232 @@
+锘縰sing System.Collections.Generic;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Media;
+
+namespace Plugin.TestResult_WPF
+{
+    public class GridProperties
+    {
+        public static bool GetShowBorder(DependencyObject obj)
+        {
+            return (bool)obj.GetValue(ShowBorderProperty);
+        }
+        public static void SetShowBorder(DependencyObject obj, bool value)
+        {
+            obj.SetValue(ShowBorderProperty, value);
+        }
+        public static readonly DependencyProperty ShowBorderProperty =
+            DependencyProperty.RegisterAttached("ShowBorder", typeof(bool), typeof(Grid), new PropertyMetadata(false, OnShowBorderChanged));
+
+        public static int GetBorderSpace(DependencyObject obj)
+        {
+            return (int)obj.GetValue(BorderSpaceProperty);
+        }
+        public static void SetBorderSpace(DependencyObject obj, int value)
+        {
+            obj.SetValue(BorderSpaceProperty, value);
+        }
+        public static readonly DependencyProperty BorderSpaceProperty =
+            DependencyProperty.RegisterAttached("BorderSpace", typeof(int), typeof(Grid), new PropertyMetadata(0, OnShowBorderChanged));
+
+        public static string GetInnerBorderIgnore(DependencyObject obj)
+        {
+            return (string)obj.GetValue(InnerBorderIgnoreProperty);
+        }
+        public static void SetInnerBorderIgnore(DependencyObject obj, string value)
+        {
+            obj.SetValue(InnerBorderIgnoreProperty, value);
+        }
+        public static readonly DependencyProperty InnerBorderIgnoreProperty =
+            DependencyProperty.RegisterAttached("InnerBorderIgnore", typeof(string),
+                typeof(Grid), new PropertyMetadata("None", OnShowBorderChanged));
+
+
+        private static void OnShowBorderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            if (d is Grid grid)
+            {
+                if (e.Property == ShowBorderProperty)
+                {
+                    grid.Loaded += GridInnerBorderLoaded;
+                    return;
+                }
+                else if (e.Property == BorderSpaceProperty)
+                {
+                    grid.Loaded += GridSpaceOnLoaded;
+                    return;
+                }
+            }
+            else if (d is UniformGrid uniformGrid)
+            {
+                uniformGrid.Loaded += UniformGridOnLoad;
+                return;
+            }
+        }
+
+        private static void GridInnerBorderLoaded(object sender, RoutedEventArgs e)
+        {
+            if (!(sender is Grid grid)) return;
+            var rowCount = grid.RowDefinitions.Count;
+            var columnCount = grid.ColumnDefinitions.Count;
+            int totalCount = rowCount * columnCount;
+
+            int width = 1;
+
+            string baseData = GetInnerBorderIgnore(grid);
+            HashSet<string> data = new HashSet<string>(baseData.Split(','));
+            bool ignoreTop = data.Contains("All") || data.Contains("Top"),
+                ignoreBottom = data.Contains("All") || data.Contains("Bottom"),
+                ignoreLeft = data.Contains("All") || data.Contains("Left"),
+                ignoreRight = data.Contains("All") || data.Contains("Right");
+
+            foreach (var child in grid.Children)
+            {
+                int row = Grid.GetRow((UIElement)child);
+                int col = Grid.GetColumn((UIElement)child);
+
+                var bt = new Thickness(col == 0 ? (ignoreLeft ? 0 : width) : 0,
+                    row == 0 ? (ignoreTop ? 0 : width) : 0,
+                    (col == columnCount - 1 && ignoreRight) ? 0 : width,
+                    width);
+
+                if (child is UserControl control)
+                {
+                    control.BorderBrush = Brushes.Black;
+                    control.BorderThickness = bt;
+                }
+
+                if (child is Label label)
+                {
+                    label.BorderBrush = Brushes.Black;
+                    label.BorderThickness = bt;
+                }
+
+                if (child is TextBox box)
+                {
+                    box.BorderBrush = Brushes.Black;
+                    box.BorderThickness = bt;
+                }
+            }
+
+            /// 寮冪敤
+            //for (int i = 0; i < totalCount; i++)
+            //{
+            //    int row = i / columnCount;
+            //    int col = i % columnCount;
+
+            //    if (i > grid.Children.Count - 1) {
+            //        continue;
+            //    }
+            //    var cell = grid.Children[i];
+            //    var bt = new Thickness(col == 0 ? (ignoreLeft ? 0 : width) : 0,
+            //        row == 0 ? (ignoreTop ? 0 : width) : 0,
+            //        (col == columnCount - 1 && ignoreRight) ? 0 : width,
+            //        width);
+
+            //    if (cell is UserControl child)
+            //    {
+            //        child.BorderBrush = Brushes.Black;
+            //        child.BorderThickness = bt;
+            //    }
+
+            //    if (cell is Label label)
+            //    {
+            //        label.BorderBrush = Brushes.Black;
+            //        label.BorderThickness = bt;
+            //    }
+
+            //    if (cell is TextBox box)
+            //    {
+            //        box.BorderBrush = Brushes.Black;
+            //        box.BorderThickness = bt;
+            //    }
+            //}
+            grid.Loaded -= GridInnerBorderLoaded;
+        }
+
+        private static void GridSpaceOnLoaded(object sender, RoutedEventArgs e)
+        {
+            if (!(sender is Grid grid)) return;
+            int space = GetBorderSpace(grid);
+            if (space <= 0)
+            {
+                return;
+            }
+
+            var rowCount = grid.RowDefinitions.Count;
+            var columnCount = grid.ColumnDefinitions.Count;
+            int totalCount = rowCount * columnCount;
+
+            for (int i = 0; i < totalCount; i++)
+            {
+                int row = i / columnCount;
+                int col = i % columnCount;
+
+                if (i > grid.Children.Count - 1)
+                {
+                    continue;
+                }
+                var cell = grid.Children[i];
+
+                var bt = new Thickness(col == 0 ? space : 0, row == 0 ? space : 0, space, space);
+
+                if (cell is UserControl child)
+                {
+                    child.Margin = bt;
+                }
+            }
+            grid.Loaded -= GridSpaceOnLoaded;
+        }
+
+        private static void UniformGridOnLoad(object sender, RoutedEventArgs e)
+        {
+            if (!(sender is UniformGrid grid)) return;
+            //var rowCount = grid.Rows;
+            int columnCount = grid.Columns;
+            int totalCount = grid.Children.Count;
+            int rowCount = totalCount / columnCount;
+            int width = 1;
+
+            for (int i = 0; i < grid.Children.Count; i++)
+            {
+                int row = i / columnCount;
+                int col = i % columnCount;
+                var child = grid.Children[i];
+
+                string baseData = GetInnerBorderIgnore(grid);
+                HashSet<string> data = new HashSet<string>(baseData.Split(','));
+                bool ignoreTop = data.Contains("All") || data.Contains("Top"),
+                    ignoreBottom = data.Contains("All") || data.Contains("Bottom"),
+                    ignoreLeft = data.Contains("All") || data.Contains("Left"),
+                    ignoreRight = data.Contains("All") || data.Contains("Right");
+
+                var bt = new Thickness(col == 0 ? (ignoreLeft ? 0 : width) : 0,
+                    row == 0 ? (ignoreTop ? 0 : width) : 0,
+                    (col == columnCount - 1 && ignoreRight) ? 0 : width,
+                    (row == rowCount && ignoreBottom) ? 0 : width);
+
+                if (child is UserControl control)
+                {
+                    control.BorderBrush = Brushes.Black;
+                    control.BorderThickness = bt;
+                }
+
+                if (child is Label label)
+                {
+                    label.BorderBrush = Brushes.Black;
+                    label.BorderThickness = bt;
+                }
+
+                if (child is TextBox box)
+                {
+                    box.BorderBrush = Brushes.Black;
+                    box.BorderThickness = bt;
+                }
+            }
+            grid.Loaded -= UniformGridOnLoad;
+        }
+    }
+
+}
diff --git a/Plugin.TestResult/Util/FontSizeConverter.cs b/Plugin.TestResult/Util/FontSizeConverter.cs
new file mode 100644
index 0000000..7d1d61e
--- /dev/null
+++ b/Plugin.TestResult/Util/FontSizeConverter.cs
@@ -0,0 +1,33 @@
+锘縰sing System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace Plugin.TestResult_WPF
+{
+    public class FontSizeConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (parameter == null) return 15;
+
+            string[] ps = parameter.ToString().Split(',');
+            int size = int.Parse(ps[0]);
+            int baseSize = int.Parse(ps[1]);
+            if (value is double width)
+            {
+                return CountFontSize(size, width, baseSize);
+            }
+            return 15;
+        }
+
+        public static double CountFontSize(double size, double limit, double baseSize)
+        {
+            return size * limit / baseSize;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}
diff --git a/Plugin.TestResult/ViewControl.xaml b/Plugin.TestResult/ViewControl.xaml
new file mode 100644
index 0000000..1d320b9
--- /dev/null
+++ b/Plugin.TestResult/ViewControl.xaml
@@ -0,0 +1,161 @@
+锘�<UserControl x:Class="Plugin.TestResult_WPF.ViewControl"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:Plugin.TestResult_WPF"
+             mc:Ignorable="d" 
+             Loaded="UserControl_Loaded"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <UserControl.Resources>
+        <local:FontSizeConverter x:Key="FontSizeConverter"/>
+        <Style TargetType="GroupBox">
+            <Setter Property="FontSize" Value="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl}, 
+Converter={StaticResource FontSizeConverter}, ConverterParameter='30,1920'}" />
+        </Style>
+
+        <Style TargetType="Button">
+            <Setter Property="Margin" Value="0,10"/>
+            <Setter Property="Padding" Value="5"/>
+        </Style>
+
+        <Style TargetType="Label">
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="LightGray"/>
+            <Setter Property="Margin" Value="0,10"/>
+            <Setter Property="Padding" Value="5"/>
+        </Style>
+    </UserControl.Resources>
+    <Grid>
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="7.5*"/>
+            <ColumnDefinition Width="*"/>
+        </Grid.ColumnDefinitions>
+        <local:TestResultView x:Name="testResultView" Grid.Column="0" Loaded="testResultView_Loaded" />
+        <StackPanel Grid.Column="1" Background="#FFF">
+            <GroupBox Header="浜у搧淇℃伅">
+                <ComboBox x:Name="modelSelect" SelectionChanged="modelSelect_SelectionChanged"></ComboBox>
+            </GroupBox>
+            <GroupBox Header="鐢熶骇缁熻">
+                <StackPanel>
+                    <Label HorizontalContentAlignment="Center" HorizontalAlignment="Stretch">
+                        <Label.Style>
+                            <Style TargetType="Label">
+                                <Setter Property="HorizontalContentAlignment"  Value="Center"/>
+                                <Setter Property="HorizontalAlignment"  Value="Stretch"/>
+                                <Setter Property="Margin" Value="0,10"/>
+                                <Style.Triggers>
+                                    <DataTrigger Binding="{Binding mesStatus}" Value="true">
+                                        <Setter Property="Background" Value="Green"/>
+                                        <Setter Property="Content" Value="MES寮�"/>
+                                        <Setter Property="Foreground" Value="White"/>
+                                    </DataTrigger>
+                                    <DataTrigger Binding="{Binding mesStatus}" Value="false">
+                                        <Setter Property="Background" Value="Red"/>
+                                        <Setter Property="Content" Value="MES鍏�"/>
+                                        <Setter Property="Foreground" Value="White"/>
+                                    </DataTrigger>
+                                </Style.Triggers>
+                            </Style>
+                        </Label.Style>
+                    </Label>
+                    <Label>
+                        <Label.Style>
+                            <Style TargetType="Label">
+                                <Setter Property="HorizontalContentAlignment"  Value="Center"/>
+                                <Setter Property="HorizontalAlignment"  Value="Stretch"/>
+                                <Setter Property="Content" Value="{Binding produceStatus}"/>
+                                <Setter Property="Margin" Value="0,10"/>
+                                <Style.Triggers>
+                                    <DataTrigger Binding="{Binding produceStatus}" Value="INIT">
+                                        <Setter Property="Background" Value="Gray"/>
+                                        <Setter Property="Foreground" Value="White"/>
+                                    </DataTrigger>
+                                    <DataTrigger Binding="{Binding produceStatus}" Value="STOP">
+                                        <Setter Property="Background" Value="Red"/>
+                                        <Setter Property="Foreground" Value="White"/>
+                                    </DataTrigger>
+                                    <DataTrigger Binding="{Binding produceStatus}" Value="RUNNING">
+                                        <Setter Property="Background" Value="Green"/>
+                                        <Setter Property="Foreground" Value="White"/>
+                                    </DataTrigger>
+                                </Style.Triggers>
+                            </Style>
+                        </Label.Style>
+                    </Label>
+                </StackPanel>
+            </GroupBox>
+            <GroupBox Header="鎿嶄綔">
+                <StackPanel>
+                    <Button x:Name="btnStart" Click="Start_Click">
+                        <Button.Content>鍚姩</Button.Content>
+                        <Button.Style>
+                            <Style TargetType="Button">
+                                <Setter Property="Background" Value="Green"/>
+                                <Setter Property="Foreground" Value="White"/>
+                                <Setter Property="Margin" Value="0,10"/>
+                                <Setter Property="Padding" Value="5"/>
+                                <Style.Triggers>
+                                    <Trigger Property="IsEnabled" Value="False">
+                                        <Setter Property="Foreground" Value="Gray"/>
+                                    </Trigger>
+                                </Style.Triggers>
+                            </Style>
+                        </Button.Style>
+                    </Button>
+
+                    <Button x:Name="btnStop" Click="Stop_Click">
+                        <Button.Content>鍋滄</Button.Content>
+                        <Button.Style>
+                            <Style TargetType="Button">
+                                <Setter Property="Background" Value="Red"/>
+                                <Setter Property="Foreground" Value="White"/>
+                                <Setter Property="Margin" Value="0,10"/>
+                                <Setter Property="Padding" Value="5"/>
+                                <Style.Triggers>
+                                    <Trigger Property="IsEnabled" Value="False">
+                                        <Setter Property="Foreground" Value="Gray"/>
+                                    </Trigger>
+                                </Style.Triggers>
+                            </Style>
+                        </Button.Style>
+                    </Button>
+
+                    <Button x:Name="btnScan1" Click="Scan_Click_1">
+                        <Button.Content>涓�鍖烘壂鐮�</Button.Content>
+                        <Button.Style>
+                            <Style TargetType="Button">
+                                <Setter Property="Margin" Value="0,10"/>
+                                <Setter Property="Padding" Value="5"/>
+                                <Style.Triggers>
+                                    <Trigger Property="IsEnabled" Value="False">
+                                        <Setter Property="Foreground" Value="Gray"/>
+                                    </Trigger>
+                                </Style.Triggers>
+                            </Style>
+                        </Button.Style>
+                    </Button>
+
+                    <Button x:Name="btnScan2" Click="Scan_Click_2">
+                        <Button.Content>浜屽尯鎵爜</Button.Content>
+                        <Button.Style>
+                            <Style TargetType="Button">
+                                <Setter Property="Margin" Value="0,10"/>
+                                <Setter Property="Padding" Value="5"/>
+                                <Style.Triggers>
+                                    <Trigger Property="IsEnabled" Value="False">
+                                        <Setter Property="Foreground" Value="Gray"/>
+                                    </Trigger>
+                                </Style.Triggers>
+                            </Style>
+                        </Button.Style>
+                    </Button>
+                    <!-- <Button Content="璁℃暟娓呴浂"/>
+                    <Button Content="鎵爜"/>
+                    <Button Content="涓�鍖烘墜鍔�"/>
+                    <Button Content="浜屽尯鎵嬪姩"/>-->
+                </StackPanel>
+            </GroupBox>
+        </StackPanel>
+    </Grid>
+</UserControl>
diff --git a/Plugin.TestResult/ViewControl.xaml.cs b/Plugin.TestResult/ViewControl.xaml.cs
new file mode 100644
index 0000000..fc91920
--- /dev/null
+++ b/Plugin.TestResult/ViewControl.xaml.cs
@@ -0,0 +1,288 @@
+锘縰sing NationalInstruments.TestStand.Interop.API;
+using NationalInstruments.TestStand.Interop.UI.Ax;
+using NationalInstruments.TestStand.Utility;
+using ParameterHelper;
+using Plugin.TestResult_WPF.Model;
+using Plugin.TestResult_WPF.Util;
+using 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;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Markup;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace Plugin.TestResult_WPF
+{
+    /// <summary>
+    /// ViewControl.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class ViewControl : UserControl, INotifyPropertyChanged
+    {
+        public event PropertyChangedEventHandler PropertyChanged;
+        public void RaisePropertyChanged(string name)
+        {
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+        }
+        public void RaiseAndSetIfChanged<T>(ref T old, T @new, [CallerMemberName] string propertyName = null)
+        {
+            old = @new;
+            if (propertyName != null)
+            {
+                RaisePropertyChanged(propertyName);
+            }
+        }
+
+        private ParameterManager parameterManager;
+        private CommonCodeParser codeParser = new CommonCodeParser();
+        private double TestTemperature = 60;
+        private int TestTime = 3600;
+        private double SaftyTemperature = 40;
+        private double TestVoltage = 12;
+        private double Time_Heating_Low = 300;
+        private double Time_Heating_High = 2400;
+        private string testSeqFile;
+        private Scanner scannerWindow1;
+        private Scanner scannerWindow2;
+        /// <summary>
+        /// 娴嬭瘯搴忓垪鎿嶄綔鍙ユ焺
+        /// </summary>
+        private SequenceContext SequenceContext = null;
+
+
+        private bool _mesStatus;
+
+        public bool mesStatus
+        {
+            get => _mesStatus;
+            set => RaiseAndSetIfChanged(ref _mesStatus, value);
+        }
+
+        private string _produceStatus = "INIT";
+
+        public string produceStatus
+        {
+            get => _produceStatus;
+            set => RaiseAndSetIfChanged(ref _produceStatus, value);
+        }
+
+
+
+        public ViewControl()
+        {
+            DataContext = this;
+            InitializeComponent();
+            testResultView.testApp.ExitApplication += new EventHandler(this.axApplicationMgr_ExitApplication);
+            testResultView.testApp.UIMessageEvent += new _ApplicationMgrEvents_UIMessageEventEventHandler(this.axApplicationMgr_UIMessageEvent);
+            testResultView.testApp.ExecutionClosed += new _ApplicationMgrEvents_ExecutionClosedEventHandler(this.axApplicationMgr_ExecutionClosed);
+        }
+
+        private void axApplicationMgr_ExitApplication(object sender, EventArgs e)
+        {
+        }
+
+        private void axApplicationMgr_ExecutionClosed(object sender, _ApplicationMgrEvents_ExecutionClosedEvent e)
+        {
+           
+        }
+
+        private void axApplicationMgr_UIMessageEvent(object sender, _ApplicationMgrEvents_UIMessageEventEvent e)
+        {
+            try
+            {
+                int EventCase = Convert.ToInt32(e.uiMsg.Event);
+                int MethodCode = Convert.ToInt16(e.uiMsg.NumericData);
+                string strData;
+
+                if (EventCase != 10014) return;
+
+                if (MethodCode == 0)
+                {
+                    /// 璁剧疆鍙ユ焺
+                    this.SequenceContext = (SequenceContext)e.uiMsg.ActiveXData;
+                    return;
+                }
+
+
+                if (MethodCode == 8)
+                {
+                    /// 鏇存柊MES鐘舵��
+                    strData = e.uiMsg.StringData;
+                    mesStatus = strData == "1";
+                    return;
+                }
+
+                if (MethodCode == 9)
+                {
+                    /// 鏇存柊鐢熶骇鐘舵��
+                    strData = e.uiMsg.StringData;
+                    produceStatus = strData;
+                    return;
+                }
+            }
+            catch
+            { }
+        }
+
+        private void UserControl_Loaded(object sender, RoutedEventArgs e)
+        {
+            LoadProduct();
+            testResultView.InitTestStandEngine();
+            Start_Click(null, null);
+        }
+
+        public void LoadProduct()
+        {
+            ProductManager prdManager = new ProductManager();
+            prdManager.LoadProducts();
+            this.modelSelect.Items.Clear();
+            foreach (Product prd in prdManager.Products)
+            {
+                this.modelSelect.Items.Add(prd.Name);
+            }
+            if (prdManager.Products.Count > 0)
+            {
+                this.modelSelect.SelectedIndex = 0;
+            }
+        }
+
+        private void modelSelect_SelectionChanged(object sender, SelectionChangedEventArgs e)
+        {
+            try
+            {
+                string ParaFile = @".\Config\ProductionSetting\Parameter\Parameter_" + this.modelSelect.SelectedItem + ".setting";
+
+                this.parameterManager = new ParameterManager(ParaFile);
+                this.parameterManager.LoadParameters();
+                //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);
+                //this.TestVoltage = Convert.ToDouble(this.parameterManager.Find("TestVoltage").ParmValue);
+                //this.SaftyTemperature = Convert.ToDouble(this.parameterManager.Find("SaftyTemperature").ParmValue);
+                //this.Time_Heating_Low = Convert.ToDouble(this.parameterManager.Find("Time_Heat_Low").ParmValue);
+                //this.Time_Heating_High = Convert.ToDouble(this.parameterManager.Find("Time_Heat_High").ParmValue);
+                testResultView.testSeqFile = this.parameterManager.Find("TestSequence").ParmValue.ToString(); // @"D:\BurnIn\TestDemo.seq"
+
+
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show("鍒囨崲浜у搧鍨嬪彿澶辫触!" + ex.Message + ex.StackTrace);
+            }
+        }
+
+        private void Start_Click(object sender, RoutedEventArgs e)
+        {
+            try
+            {
+                testResultView.StartTestSequence();
+                
+                this.modelSelect.IsEnabled = false;
+                this.btnStart.IsEnabled = false;
+                this.btnStop.IsEnabled = true;
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show("鍚姩娴嬭瘯澶辫触锛�" + ex.Message);
+            }
+        }
+
+        private void Stop_Click(object sender, RoutedEventArgs e)
+        {
+            try
+            {
+                testResultView.StopTest();
+                this.modelSelect.IsEnabled = true;
+                this.btnStart.IsEnabled = true;
+                this.btnStop.IsEnabled = false;
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show("鍋滄娴嬭瘯澶辫触!" + ex.Message);
+            }
+        }
+
+        private void Scan_Click_1(object sender, RoutedEventArgs e)
+        {
+            if (scannerWindow1 == null)
+            {
+                scannerWindow1 = new Scanner("涓�鍖烘壂鐮�");
+                scannerWindow1.FinishBarcode += ScannerWindow_FinishBarcode;
+                scannerWindow1.Owner = Window.GetWindow(this);
+                scannerWindow1.Closed += ScannerWindow1_Closed;
+                scannerWindow1.Show();
+            }
+            else
+            {
+                scannerWindow1.Activate();
+            }
+        }
+
+        private void Scan_Click_2(object sender, RoutedEventArgs e)
+        {
+            if (scannerWindow2 == null)
+            {
+                scannerWindow2 = new Scanner("浜屽尯鎵爜");
+                scannerWindow2.FinishBarcode += ScannerWindow_FinishBarcode;
+                scannerWindow2.Owner = Window.GetWindow(this);
+                scannerWindow2.Closed += ScannerWindow2_Closed;
+                scannerWindow2.Show();
+            }
+            else
+            {
+                scannerWindow2.Activate();
+            }
+        }
+
+        /// <summary>
+        /// 鎵爜瀹屾垚閫昏緫
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        private void ScannerWindow_FinishBarcode(object sender, BarcodeEventArg e)
+        {
+            if (this.SequenceContext == null) return;
+            CellInfo cellInfo = codeParser.GetCellInfoByCode(e.positionCode);
+
+            string paramName = e.name == "涓�鍖烘壂鐮�" ? "StationState.StartScanner_A" : "StationState.StartScanner_B";
+            testResultView.engine.Globals.SetValBoolean(paramName, 0, true);
+            string positionCode = e.positionCode, snrCode = e.snrCode;
+            //闇�瑕佸鍔犳潯鐮佹牎楠屽姛鑳�
+
+            //灏嗚鍒扮殑鏉$爜鍐欏叆鍒癝N涓�
+            int dutNo = Convert.ToInt16(positionCode.Split('-')[2]) - 1;
+            string LookupStr = e.name == "涓�鍖烘壂鐮�" ? $"StationState.SN[{dutNo}]" : $"StationState.SN[{(dutNo + 54)}]";
+            //this.SequenceContext.StationGlobals.SetValString(LookupStr, 0, snrCode);
+            //string code = this.SequenceContext.StationGlobals.GetValString(LookupStr, 0);
+
+            testResultView.engine.Globals.SetValString(LookupStr, 0, snrCode);
+            string code = testResultView.engine.Globals.GetValString(LookupStr, 0);
+            testResultView.nameTables[cellInfo.OvenName].GetSingleCell(cellInfo.Index).SetStatus("hadCode");
+        }
+
+        private void ScannerWindow1_Closed(object sender, EventArgs e)
+        {
+            scannerWindow1 = null;
+        }
+
+        private void ScannerWindow2_Closed(object sender, EventArgs e)
+        {
+            scannerWindow2 = null;
+        }
+
+        private void testResultView_Loaded(object sender, RoutedEventArgs e)
+        {
+
+        }
+    }
+}
diff --git a/Plugin.TestResult/packages.config b/Plugin.TestResult/packages.config
new file mode 100644
index 0000000..6693fcf
--- /dev/null
+++ b/Plugin.TestResult/packages.config
@@ -0,0 +1,25 @@
+锘�<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="HarfBuzzSharp" version="7.3.0.3" targetFramework="net472" />
+  <package id="HarfBuzzSharp.NativeAssets.Linux" version="7.3.0.3" targetFramework="net472" />
+  <package id="HarfBuzzSharp.NativeAssets.macOS" version="7.3.0.3" targetFramework="net472" />
+  <package id="HarfBuzzSharp.NativeAssets.Win32" version="7.3.0.3" targetFramework="net472" />
+  <package id="OpenTK" version="3.3.1" targetFramework="net472" />
+  <package id="OpenTK.GLWpfControl" version="3.3.0" targetFramework="net472" />
+  <package id="ScottPlot" version="5.0.47" targetFramework="net472" />
+  <package id="ScottPlot.WPF" version="5.0.47" targetFramework="net472" />
+  <package id="SkiaSharp" version="2.88.9" targetFramework="net472" />
+  <package id="SkiaSharp.HarfBuzz" version="2.88.9" targetFramework="net472" />
+  <package id="SkiaSharp.NativeAssets.Linux.NoDependencies" version="2.88.9" targetFramework="net472" />
+  <package id="SkiaSharp.NativeAssets.macOS" version="2.88.9" targetFramework="net472" />
+  <package id="SkiaSharp.NativeAssets.Win32" version="2.88.9" targetFramework="net472" />
+  <package id="SkiaSharp.Views.Desktop.Common" version="2.88.9" targetFramework="net472" />
+  <package id="SkiaSharp.Views.WPF" version="2.88.9" targetFramework="net472" />
+  <package id="System.Buffers" version="4.5.1" targetFramework="net472" />
+  <package id="System.Drawing.Common" version="4.7.3" targetFramework="net472" />
+  <package id="System.Memory" version="4.5.5" targetFramework="net472" />
+  <package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net472" />
+  <package id="System.Runtime.CompilerServices.Unsafe" version="4.5.3" targetFramework="net472" />
+  <package id="System.Runtime.Loader" version="4.3.0" targetFramework="net472" />
+  <package id="System.ValueTuple" version="4.5.0" targetFramework="net472" />
+</packages>
\ No newline at end of file
diff --git a/WPFProject/LoadingWindow.xaml.cs b/WPFProject/LoadingWindow.xaml.cs
index 6a857bb..49b44ce 100644
--- a/WPFProject/LoadingWindow.xaml.cs
+++ b/WPFProject/LoadingWindow.xaml.cs
@@ -1,4 +1,5 @@
-锘縰sing System.Threading.Tasks;
+锘縰sing System;
+using System.Threading.Tasks;
 using System.Windows;
 using HMI.Base;
 
@@ -24,9 +25,9 @@
 
         private void Loader_LoadMessageAppend(object sender, string e)
         {
-            Dispatcher.BeginInvoke(() =>
+            this.Dispatcher.BeginInvoke((Action)delegate ()
             {
-                Console.Text = Console.Text+$"{e}\r\n";
+                Console.Text = Console.Text + $"{e}\r\n";
             });
         }
 
diff --git a/WPFProject/WPFProject.csproj b/WPFProject/WPFProject.csproj
index b5336b6..564f32e 100644
--- a/WPFProject/WPFProject.csproj
+++ b/WPFProject/WPFProject.csproj
@@ -369,6 +369,10 @@
       <Project>{f7663c79-7047-42f3-b129-73a2575ee7c6}</Project>
       <Name>HMI.IOMonitor</Name>
     </ProjectReference>
+    <ProjectReference Include="..\HMI.LogViewer\HMI.LogViewer.csproj">
+      <Project>{20c23602-b029-4e21-8c11-b9e5f2223237}</Project>
+      <Name>HMI.LogViewer</Name>
+    </ProjectReference>
     <ProjectReference Include="..\HMI.ParameterManage\HMI.ParameterManage.csproj">
       <Project>{736cd77d-b81e-4866-bb00-84b0b57110f0}</Project>
       <Name>HMI.ParameterManage</Name>
@@ -389,6 +393,10 @@
       <Project>{de4205c6-5923-46fd-a4b9-65169bf61c75}</Project>
       <Name>HMI.MenuManage</Name>
     </ProjectReference>
+    <ProjectReference Include="..\Plugin.TestResult\Plugin.TestResult.csproj">
+      <Project>{7375b74c-b6bf-4814-9838-e1caacad1897}</Project>
+      <Name>Plugin.TestResult</Name>
+    </ProjectReference>
   </ItemGroup>
   <ItemGroup>
     <Content Include="Icon\xinzeng_1.png">
diff --git a/WPFProject/WPFProject.sln b/WPFProject/WPFProject.sln
index 92be340..80ca826 100644
--- a/WPFProject/WPFProject.sln
+++ b/WPFProject/WPFProject.sln
@@ -33,6 +33,10 @@
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommonLibs", "..\..\HMI.Old\Framework\CommonLibs\CommonLibs\CommonLibs.csproj", "{0212E104-4E85-4F9F-AA70-FC4FAC25C3E5}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HMI.LogViewer", "..\HMI.LogViewer\HMI.LogViewer.csproj", "{20C23602-B029-4E21-8C11-B9E5F2223237}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin.TestResult", "..\Plugin.TestResult\Plugin.TestResult.csproj", "{7375B74C-B6BF-4814-9838-E1CAACAD1897}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -161,6 +165,22 @@
 		{0212E104-4E85-4F9F-AA70-FC4FAC25C3E5}.Release|Any CPU.Build.0 = Release|Any CPU
 		{0212E104-4E85-4F9F-AA70-FC4FAC25C3E5}.Release|x64.ActiveCfg = Release|x64
 		{0212E104-4E85-4F9F-AA70-FC4FAC25C3E5}.Release|x64.Build.0 = Release|x64
+		{20C23602-B029-4E21-8C11-B9E5F2223237}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{20C23602-B029-4E21-8C11-B9E5F2223237}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{20C23602-B029-4E21-8C11-B9E5F2223237}.Debug|x64.ActiveCfg = Debug|x64
+		{20C23602-B029-4E21-8C11-B9E5F2223237}.Debug|x64.Build.0 = Debug|x64
+		{20C23602-B029-4E21-8C11-B9E5F2223237}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{20C23602-B029-4E21-8C11-B9E5F2223237}.Release|Any CPU.Build.0 = Release|Any CPU
+		{20C23602-B029-4E21-8C11-B9E5F2223237}.Release|x64.ActiveCfg = Release|Any CPU
+		{20C23602-B029-4E21-8C11-B9E5F2223237}.Release|x64.Build.0 = Release|Any CPU
+		{7375B74C-B6BF-4814-9838-E1CAACAD1897}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{7375B74C-B6BF-4814-9838-E1CAACAD1897}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{7375B74C-B6BF-4814-9838-E1CAACAD1897}.Debug|x64.ActiveCfg = Debug|x64
+		{7375B74C-B6BF-4814-9838-E1CAACAD1897}.Debug|x64.Build.0 = Debug|x64
+		{7375B74C-B6BF-4814-9838-E1CAACAD1897}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{7375B74C-B6BF-4814-9838-E1CAACAD1897}.Release|Any CPU.Build.0 = Release|Any CPU
+		{7375B74C-B6BF-4814-9838-E1CAACAD1897}.Release|x64.ActiveCfg = Release|x64
+		{7375B74C-B6BF-4814-9838-E1CAACAD1897}.Release|x64.Build.0 = Release|x64
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

--
Gitblit v1.9.1