From c4a3f8db90530a727ffab77c3bb5bb7d782792bf Mon Sep 17 00:00:00 2001
From: alielie <alielie@git.com>
Date: 星期一, 25 八月 2025 15:50:27 +0800
Subject: [PATCH] 上传代码;

---
 Plugin.ModelManage/Entity/ModelData.cs                 |   19 
 Plugin.SeqEditor/SettingPanel/LoopPanel.xaml.cs        |   60 
 WPFProject/BuildCmd.bat                                |   12 
 WPFProject/Controls/MainTabControl.xaml                |   41 
 HMI.LogViewer/LogViewer.xaml                           |    7 
 HMI.UI/Style/ScrollBarResource.xaml                    |  106 
 Plugin.TestResult/DataTable.xaml                       |   40 
 HMI.UI/Style/PanuonListView.xaml                       |   77 
 Plugin.ModelManage/AssemblyInfo.cs                     |   10 
 HMI.Core/Util/MSMQHelper.cs                            |    2 
 Plugin.TestBuilder/UI/InnerWindow.xaml.cs              |    2 
 Plugin.TestResult/TestResultView.xaml.cs               |   19 
 Plugin.TsExecution/Plugin.TsExecution.csproj           |    5 
 HMI.UI/DpUtil/CanvasHelper.cs                          |   16 
 WPFProject/Lang/zh-CN/DOControl.json                   |    4 
 HMI.Core/DpUtil/PermissionHelper.cs                    |   47 
 Plugin.SeqEditor/Entity/EditablePO/StringValTestEPO.cs |    9 
 HMI.AlermInfoManage/AlarmSettingList.xaml.cs           |  117 
 WPFProject/PanuonWindow.xaml.cs                        |   75 
 WPFProject/Lang/en-US/AlarmInfoManage.json             |    1 
 HMI.ParameterManage/ParameterManage.xaml               |    4 
 HMI.ParameterManage/TestItem.xaml                      |    4 
 Plugin.DODriverControl/Entity/DIOData.cs               |   89 
 Plugin.SeqEditor/ViewModel/SeqEditorViewModel.cs       |   11 
 Plugin.TestResult/ViewControl.xaml.cs                  |   90 
 HMI.AlermInfoManage/SimpleAlarm.xaml.cs                |  100 
 WPFProject/Lang/zh-CN/ChangeModel.json                 |   25 
 HMI.UI/MDI/Workspace.xaml.cs                           |  366 +
 Plugin.ChangeModel/ChangeModel.xaml.cs                 |  430 +
 HMI.MenuManage/Modal/MenuModal.xaml.cs                 |    3 
 Plugin.DODriverControl/DOControl.xaml.cs               |  141 
 HMI.UI/MDI/BottomBar.xaml.cs                           |   29 
 Plugin.TsExecution/TsExecution.xaml.cs                 |  533 +
 HMI.Base/MaskControl.xaml                              |   32 
 Plugin.SeqEditor/SettingPanel/ActionPanel.xaml         |    1 
 WPFProject/App.xaml                                    |    2 
 HMI.MenuManage/Model/MenuManageModel.cs                |  105 
 Plugin.TsSupport/Properties/AssemblyInfo.cs            |   33 
 SimpleUtil/HMI.MQUtil/HMI.MQUtil.csproj                |   49 
 WPFProject/Properties/app.manifest                     |    2 
 HMI.UI/Util/MsgBoxHelper.cs                            |    1 
 SimpleUtil/HMI.MQUtil/Properties/AssemblyInfo.cs       |   33 
 WPFProject/Lang/en-US/ChangeModel.json                 |   25 
 HMI.Core/Entity/UserInfo.cs                            |   13 
 Plugin.SeqEditor/SeqEditor.xaml.cs                     |    9 
 SimpleUtil/HMI.SerialData.Util/SerialDataHelper.cs     |   10 
 Plugin.ChangeModel/Entity/WorkOrderData.cs             |    7 
 Plugin.DataBoard/DataBoard.xaml.cs                     |    2 
 Plugin.SeqEditor/Entity/EditablePO/NumValTestEPO.cs    |   43 
 HMI.ParameterManage/ParameterSetting.xaml              |    4 
 HMI.UI/Style/FormStyle.xaml                            |   10 
 Plugin.ModelManage/DataModal.xaml                      |   77 
 HMI.UI/RequirePasswordWindow.xaml.cs                   |   49 
 HMI.UI/MDI/InnerWindow.xaml.cs                         |  267 +
 Plugin.TestBuilder/UI/Resize.cs                        |    5 
 Plugin.ChangeModel/AssemblyInfo.cs                     |   10 
 SimpleUtil/HMI.Simple.Util/ProductModelHelper.cs       |  106 
 Plugin.ModelManage/ModelManage.xaml                    |  169 
 HMI.UI/Style/PanuonResource.xaml                       |   45 
 WPFProject/PanuonWindow.xaml                           |  274 
 Plugin.TsExecution/Entity/TestStep.cs                  |    1 
 Plugin.DODriverControl/ConfigModal.xaml                |   54 
 Plugin.TsExecution/SingleControl.xaml                  |  168 
 WPFProject/WPFProject.sln                              |   81 
 HMI.Core/Api/IAlarmShow.cs                             |   13 
 HMI.UI/Controls/NumberInput.cs                         |    8 
 HMI.UI/MDI/BottomBar.xaml                              |   15 
 Plugin.DODriverControl/Plugin.DODriverControl.csproj   |   28 
 HMI.Util/ExcelHelper.cs                                |  140 
 HMI.MenuManage/Modal/MenuModal.xaml                    |    3 
 HMI.LogViewer/LogViewer.xaml.cs                        |    2 
 HMI.UI/Converter/DataGridRowNumberConverter.cs         |   31 
 HMI.AlermInfoManage/Modal/AlarmSettingModal.xaml.cs    |   17 
 Plugin.ChangeModel/ChangeModel.xaml                    |  144 
 HMI.UI/TableComboBox.cs                                |  249 +
 Plugin.ChangeModel/Plugin.ChangeModel.csproj           |   27 
 HMI.MenuManage/MenuManage.xaml                         |    6 
 HMI.UI/EditBox.cs                                      |  194 
 Plugin.DODriverControl/Api/ModBusTcpController.cs      |   55 
 HMI.MenuManage/Modal/RoleModal.xaml.cs                 |    3 
 HMI.UI/Style/WindowResources.xaml                      |    1 
 HMI.UI/Resources/Icon/dot2.png                         |    0 
 HMI.UI/MaskControl.xaml                                |   58 
 Plugin.ChangeModel/Entity/ModelData.cs                 |   13 
 HMI.Base/PluginMaskControl.cs                          |   89 
 Plugin.TestBuilder/UI/InnerWindow.xaml                 |    6 
 HMI.UI/MaskControl.xaml.cs                             |   63 
 Plugin.TestResult/ViewControl.xaml                     |    7 
 Plugin.TestResult/DataTable.xaml.cs                    |   24 
 HMI.UI/HMI.UI.csproj                                   |   75 
 Plugin.DODriverControl/ConfigModal.xaml.cs             |  108 
 HMI.DataViewer/DataViewer.xaml                         |    1 
 Plugin.SeqEditor/SettingPanel/StringValTestPanel.xaml  |    3 
 Plugin.ModelManage/DataModal.xaml.cs                   |  142 
 Plugin.ModelManage/ModelManage.xaml.cs                 |  300 +
 HMI.Base/HMI.Base.csproj                               |   17 
 Plugin.ChangeModel/Entity/CmLogData.cs                 |   13 
 Plugin.DODriverControl/Api/PCIController.cs            |   49 
 Plugin.MaterialOpt/MaterialOpt.xaml                    |  251 +
 Plugin.DOControl/DOControl.xaml                        |    4 
 WPFProject/Lang/zh-CN/app.json                         |    1 
 HMI.MenuManage/MenuManage.xaml.cs                      |  140 
 HMI.MenuManage/RoleManage.xaml                         |    5 
 WPFProject/Utils/MainToastHandler.cs                   |   22 
 HMI.Core/Entity/PermissionData.cs                      |   24 
 HMI.UI/MDI/Workspace.xaml                              |   34 
 Plugin.MaterialOpt/Plugin.MaterialOpt.csproj           |   27 
 SimpleUtil/HMI.MQUtil/MQConnector.cs                   |  110 
 Plugin.MaterialOpt/AssemblyInfo.cs                     |   10 
 Plugin.SeqEditor/Entity/StepUI.cs                      |  269 +
 HMI.UI/Controls/VisualHost.cs                          |   30 
 HMI.Base/SimpleControl.cs                              |   59 
 HMI.UI/MDI/InnerWindow.xaml                            |   67 
 Plugin.TsSupport/Plugin.TsSupport.csproj               |   58 
 HMI.UI/Util/ToastHelper.cs                             |   78 
 HMI.UnitTest/MainWindow.xaml.cs                        |   51 
 HMI.AlermInfoManage/Entity/AlarmText.cs                |   14 
 Plugin.SeqEditor/Util/ItemData.cs                      |   57 
 HMI.UI/Controls/LightBall.cs                           |   43 
 Plugin.TsExecution/TsExecution.xaml                    |  305 
 Plugin.DODriverControl/DOControl.xaml                  |   22 
 HMI.UI/Style/MsgBoxStyle.xaml                          |    3 
 HMI.AlermInfoManage/AlarmSettingList.xaml              |  236 +
 HMI.UI/Converter/ReverseBoolConverter.cs               |   27 
 Plugin.DODriverControl/Api/Dask.cs                     |  801 +++
 Plugin.SeqEditor/SettingPanel/LoopPanel.xaml           |  104 
 HMI.UI/RequirePasswordWindow.xaml                      |   33 
 HMI.UI/Resources/Icon/dot.svg                          |   12 
 Plugin.DODriverControl/DataModal.xaml.cs               |  177 
 Plugin.TestBuilder/Plugin.TestBuilder.csproj           |    1 
 HMI.UI/MDI/MinInnerWindow.xaml.cs                      |   42 
 Plugin.ModelManage/Plugin.ModelManage.csproj           |   27 
 Plugin.DODriverControl/DataModal.xaml                  |  120 
 SimpleUtil/HMI.Simple.Util/HMI.Simple.Util.csproj      |    5 
 HMI.AlermInfoManage/AlarmInfoManage.xaml               |  261 
 HMI.UI/Themes/Generic.xaml                             |  145 
 HMI.AlermInfoManage/SimpleAlarm.xaml                   |   96 
 HMI.Core/HMI.Core.csproj                               |    5 
 HMI.UI/DpUtil/Resize.cs                                |  640 ++
 HMI.Base/PluginLoader.cs                               |   76 
 WPFProject/WPFProject.csproj                           |   46 
 WPFProject/Login.xaml.cs                               |  125 
 HMI.IOMonitor/HMI.IOMonitor.csproj                     |    1 
 HMI.UI/MDI/MinInnerWindow.xaml                         |   24 
 HMI.AlermInfoManage/HMI.AlarmInfoManage.csproj         |   19 
 Plugin.DOControl/Plugin.DOControl.csproj               |    4 
 Plugin.DODriverControl/SingleControl.xaml              |  365 +
 HMI.AlermInfoManage/AlarmInfoManage.xaml.cs            |  584 +
 Plugin.JarUI/JarUI.xaml.cs                             |    3 
 HMI.AlermInfoManage/Model/XmlModel.cs                  |   19 
 Plugin.DODriverControl/Api/DaskHelper.cs               |  132 
 Plugin.TestResult/Plugin.TestResult.csproj             |    5 
 Plugin.TsExecution/SingleControl.xaml.cs               |   58 
 HMI.Core/Api/AlarmShowHelper.cs                        |   24 
 HMI.UI/DpUtil/FormGroupHelper.cs                       |   24 
 Plugin.DODriverControl/AssemblyInfo.cs                 |   10 
 HMI.UI/Style/PanuonDataGrid.xaml                       |   31 
 Plugin.DODriverControl/Api/IDIOController.cs           |   15 
 Plugin.DODriverControl/Entity/ConfigData.cs            |   17 
 WPFProject/安装MSMQ.bat                                  |    5 
 WPFProject/Login.xaml                                  |   11 
 SimpleUtil/HMI.Simple.Util/SqliteHelper.cs             |  238 +
 WPFProject/Lang/zh-CN/AlarmInfoManage.json             |    1 
 HMI.AlermInfoManage/Modal/AlarmSettingModal.xaml       |   12 
 HMI.Base/SimpleMaskControl.cs                          |   79 
 HMI.MenuManage/UserManage.xaml                         |    5 
 Plugin.DOControl/DOControl.xaml.cs                     |   75 
 WPFProject/Controls/MainTabControl.xaml.cs             |   23 
 /dev/null                                              |    1 
 HMI.UI/Resources/Font/iconfont.ttf                     |    0 
 HMI.UI/Resources/Icon/dot.png                          |    0 
 Plugin.JarUI/JarUI.xaml                                |    2 
 SimpleUtil/HMI.Simple.Util/Parameter.cs                |    2 
 Plugin.MaterialOpt/MaterialOpt.xaml.cs                 |  429 +
 HMI.Base/Themes/Generic.xaml                           |   32 
 HMI.AlermInfoManage/Entity/AlarmSetting.cs             |   13 
 Plugin.SeqEditor/SeqEditor.xaml                        |  107 
 HMI.SettingControl/SystemConfig.xaml                   |    1 
 HMI.SettingControl/SettingControl.xaml                 |    1 
 HMI.UnitTest/HMI.UnitTest.csproj                       |    8 
 WPFProject/Api/SQLite.Interop.dll                      |    0 
 Plugin.DODriverControl/SingleControl.xaml.cs           |  568 ++
 WPFProject/Lang/en-US/app.json                         |    1 
 Plugin.TsSupport/TestAppManager.cs                     |   17 
 184 files changed, 12,499 insertions(+), 1,215 deletions(-)

diff --git a/HMI.AlermInfoManage/AlarmInfoManage.xaml b/HMI.AlermInfoManage/AlarmInfoManage.xaml
index 74e1f8f..2d892d9 100644
--- a/HMI.AlermInfoManage/AlarmInfoManage.xaml
+++ b/HMI.AlermInfoManage/AlarmInfoManage.xaml
@@ -13,10 +13,20 @@
              Background="{StaticResource WorkspaceBackground}"
              Loaded="UserControl_Loaded"
              d:DesignHeight="450" d:DesignWidth="800">
-
+    <UserControl.Resources>
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonListView.xaml"/>
+            </ResourceDictionary.MergedDictionaries>
+        </ResourceDictionary>
+    </UserControl.Resources>
     <Border Style="{StaticResource CardBoarder}">
-        <StackPanel>
-            <DockPanel Margin="0,0,0,10">
+        <Grid>
+            <Grid.RowDefinitions>
+                <RowDefinition Height="Auto"/>
+                <RowDefinition/>
+            </Grid.RowDefinitions>
+            <DockPanel Grid.Row="0" Margin="0,0,0,10">
                 <StackPanel DockPanel.Dock="Right" Style="{StaticResource ButtonGroup}">
                     <Button Style="{StaticResource SettingBtn}" x:Name="settingBtn" Click="Button_Click"/>
                     <Button Style="{StaticResource RfhBtn}" x:Name="refreshBtn" Click="Button_Click"/>
@@ -27,63 +37,192 @@
                            VerticalAlignment="Center" HorizontalAlignment="Left" Text="{DynamicResource Alm.Title}"/>
             </DockPanel>
 
-            <DataGrid x:Name="table" Style="{StaticResource DefaultDataGrid}" ItemsSource="{Binding Datasource}">
-                <DataGrid.Columns>
-                    <DataGridTemplateColumn Header="" Width="Auto" CanUserResize="False">
-                        <DataGridTemplateColumn.CellTemplate>
-                            <DataTemplate>
-                                <CheckBox VerticalAlignment="Center" IsChecked="{Binding 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 Source}">
-                        <DataGridTextColumn.Header>
-                            <TextBlock Text="{DynamicResource Alm.Source}"/>
-                        </DataGridTextColumn.Header>
-                    </DataGridTextColumn>
-                    <DataGridTextColumn Width="*" Binding="{Binding Code}">
-                        <DataGridTextColumn.Header>
-                            <TextBlock Text="{DynamicResource Alm.Code}"/>
-                        </DataGridTextColumn.Header>
-                    </DataGridTextColumn>
-                    <DataGridTextColumn Width="*" Binding="{Binding Info}">
-                        <DataGridTextColumn.Header>
-                            <TextBlock Text="{DynamicResource Alm.Info}"/>
-                        </DataGridTextColumn.Header>
-                    </DataGridTextColumn>
-                    <DataGridTextColumn Width="*" Binding="{Binding Time}">
-                        <DataGridTextColumn.Header>
-                            <TextBlock Text="{DynamicResource Alm.Time}"/>
-                        </DataGridTextColumn.Header>
-                    </DataGridTextColumn>
-                    <DataGridTemplateColumn Width="100" IsReadOnly="True" CanUserResize="False">
-                        <DataGridTemplateColumn.Header>
-                            <TextBlock Text="{DynamicResource App.Opt}"/>
-                        </DataGridTemplateColumn.Header>
-                        <DataGridTemplateColumn.CellTemplate>
-                            <DataTemplate>
-                                <StackPanel Orientation="Horizontal">
-                                    <Button x:Name="editBtn" Style="{StaticResource EditLink}" 
-                            Margin="0,0,10,0" 
-                            Click="Button_Click"
-                            DataContext="{Binding }"/>
-                                    <Button x:Name="delBtn" Style="{StaticResource DeleteLink}" 
-                            DataContext="{Binding }"
-                            Click="Button_Click"/>
-                                </StackPanel>
-                            </DataTemplate>
-                        </DataGridTemplateColumn.CellTemplate>
-                    </DataGridTemplateColumn>
-                </DataGrid.Columns>
-            </DataGrid>
-        </StackPanel>
+            <TabControl Grid.Row="1" Style="{StaticResource HeaderTabControl}">
+                <TabItem Header="鎶ヨ璁板綍">
+                    <DockPanel>
+                        <StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right">
+                            <pu:Pagination 
+                                Height="36" Margin="10"
+    HorizontalAlignment="Left"
+    Foreground="Black"
+    ItemsBackground="Transparent"
+    ItemsCornerRadius="12"
+    ItemsSelectedBackground="{DynamicResource SecondPrimaryColor}"
+                                x:Name="page"
+                                CurrentPage="{Binding CurrentPage}" 
+                                            MaxPage="{Binding MaxPage}"/>
+                        </StackPanel>
+                        <ListView x:Name="table" Style="{StaticResource DefaultListTable}" 
+                                  VirtualizingPanel.VirtualizationMode="Standard"
+                                  ItemsSource="{Binding Datasource}">
+                            <ListView.View>
+                                <GridView>
+                                    <GridViewColumn Width="50">
+                                        <GridViewColumn.Header>
+                                            <TextBlock Text="#"/>
+                                        </GridViewColumn.Header>
+                                        <GridViewColumn.CellTemplate>
+                                            <DataTemplate>
+                                                <TextBlock Text="{Binding Converter={StaticResource lvRowNumber},
+RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ListViewItem,AncestorLevel=1}}"/>
+                                            </DataTemplate>
+                                        </GridViewColumn.CellTemplate>
+                                    </GridViewColumn>
+
+                                    <GridViewColumn Width="300">
+                                        <GridViewColumn.Header>
+                                            <TextBlock Text="{DynamicResource Alm.Source}"/>
+                                        </GridViewColumn.Header>
+                                        <GridViewColumn.CellTemplate>
+                                            <DataTemplate>
+                                                <Border Style="{StaticResource CellBorder}">
+                                                    <TextBlock VerticalAlignment="Center" Text="{Binding Source}"/>
+                                                </Border>
+                                            </DataTemplate>
+                                        </GridViewColumn.CellTemplate>
+                                    </GridViewColumn>
+
+                                    <GridViewColumn Width="500">
+                                        <GridViewColumn.Header>
+                                            <TextBlock Text="{DynamicResource Alm.Code}"/>
+                                        </GridViewColumn.Header>
+                                        <GridViewColumn.CellTemplate>
+                                            <DataTemplate>
+                                                <Border Style="{StaticResource CellBorder}">
+                                                    <TextBlock VerticalAlignment="Center" Text="{Binding AlarmCode}"/>
+                                                </Border>
+                                            </DataTemplate>
+                                        </GridViewColumn.CellTemplate>
+                                    </GridViewColumn>
+
+                                    <GridViewColumn Width="800">
+                                        <GridViewColumn.Header>
+                                            <TextBlock Text="{DynamicResource Alm.Info}"/>
+                                        </GridViewColumn.Header>
+                                        <GridViewColumn.CellTemplate>
+                                            <DataTemplate>
+                                                <Border Style="{StaticResource CellBorder}">
+                                                    <TextBlock VerticalAlignment="Center" Text="{Binding AlarmInfo}"/>
+                                                </Border>
+                                            </DataTemplate>
+                                        </GridViewColumn.CellTemplate>
+                                    </GridViewColumn>
+                                    <GridViewColumn Width="200">
+                                        <GridViewColumn.Header>
+                                            <TextBlock Text="{DynamicResource Alm.Time}"/>
+                                        </GridViewColumn.Header>
+                                        <GridViewColumn.CellTemplate>
+                                            <DataTemplate>
+                                                <Border Style="{StaticResource CellBorder}">
+                                                    <TextBlock VerticalAlignment="Center" Text="{Binding Time}"/>
+                                                </Border>
+                                            </DataTemplate>
+                                        </GridViewColumn.CellTemplate>
+                                    </GridViewColumn>
+                                </GridView>
+                            </ListView.View>
+                        </ListView>
+                        <!--<DataGrid x:Name="table" Style="{StaticResource DefaultDataGrid}" ItemsSource="{Binding Datasource}">
+                            <DataGrid.Columns>
+                                <DataGridTemplateColumn Header="" Width="Auto" CanUserResize="False">
+                                    <DataGridTemplateColumn.CellTemplate>
+                                        <DataTemplate>
+                                            <CheckBox VerticalAlignment="Center" IsChecked="{Binding 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 Source}">
+                                    <DataGridTextColumn.Header>
+                                        <TextBlock Text="{DynamicResource Alm.Source}"/>
+                                    </DataGridTextColumn.Header>
+                                </DataGridTextColumn>
+                                <DataGridTextColumn Width="*" Binding="{Binding AlarmCode}">
+                                    <DataGridTextColumn.Header>
+                                        <TextBlock Text="{DynamicResource Alm.Code}"/>
+                                    </DataGridTextColumn.Header>
+                                </DataGridTextColumn>
+                                <DataGridTextColumn Width="*" Binding="{Binding AlarmInfo}">
+                                    <DataGridTextColumn.Header>
+                                        <TextBlock Text="{DynamicResource Alm.Info}"/>
+                                    </DataGridTextColumn.Header>
+                                </DataGridTextColumn>
+                                <DataGridTextColumn Width="*" Binding="{Binding Time}">
+                                    <DataGridTextColumn.Header>
+                                        <TextBlock Text="{DynamicResource Alm.Time}"/>
+                                    </DataGridTextColumn.Header>
+                                </DataGridTextColumn>
+                            </DataGrid.Columns>
+                        </DataGrid>-->
+                    </DockPanel>
+                </TabItem>
+                <TabItem Header="鎶ヨ淇℃伅">
+                    <Grid>
+                        <Grid.RowDefinitions>
+                            <RowDefinition Height="Auto"/>
+                            <RowDefinition/>
+                        </Grid.RowDefinitions>
+
+                        <DataGrid Grid.Row="1" x:Name="alarmTextTable" Visibility="Hidden" Style="{x:Null}">
+                            <DataGrid.Columns>
+                                <DataGridTemplateColumn Header="" Width="50"/>
+                                <DataGridTextColumn x:Name="sb1" Width="*" Binding="{Binding AlarmCode}"/>
+                                <DataGridTextColumn x:Name="sb2" Width="*" Binding="{Binding MAlarmText_CN}"/>
+                            </DataGrid.Columns>
+                        </DataGrid>
+                        <ListView Grid.Row="1" Style="{StaticResource DefaultListTable}" ItemsSource="{Binding AlarmTexts}">
+                            <ListView.View>
+                                <GridView>
+                                    <GridViewColumn Width="50">
+                                        <GridViewColumn.Header>
+                                            <TextBlock Text="#"/>
+                                        </GridViewColumn.Header>
+                                        <GridViewColumn.CellTemplate>
+                                            <DataTemplate>
+                                                <TextBlock Text="{Binding Converter={StaticResource lvRowNumber},
+RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ListViewItem,AncestorLevel=1}}"/>
+                                            </DataTemplate>
+                                        </GridViewColumn.CellTemplate>
+                                    </GridViewColumn>
+
+                                    <GridViewColumn Width="{Binding ActualWidth,ElementName=sb1}">
+                                        <GridViewColumn.Header>
+                                            <TextBlock Text="{DynamicResource Alm.Code}"/>
+                                        </GridViewColumn.Header>
+                                        <GridViewColumn.CellTemplate>
+                                            <DataTemplate>
+                                                <Border Style="{StaticResource CellBorder}">
+                                                    <TextBlock VerticalAlignment="Center" Text="{Binding AlarmCode}"/>
+                                                </Border>
+                                            </DataTemplate>
+                                        </GridViewColumn.CellTemplate>
+                                    </GridViewColumn>
+
+                                    <GridViewColumn Width="{Binding ActualWidth,Converter={StaticResource sbC},ConverterParameter=50,ElementName=sb2}">
+                                        <GridViewColumn.Header>
+                                            <TextBlock Text="{DynamicResource Alm.Info}"/>
+                                        </GridViewColumn.Header>
+                                        <GridViewColumn.CellTemplate>
+                                            <DataTemplate>
+                                                <Border Style="{StaticResource CellBorder}">
+                                                    <TextBlock VerticalAlignment="Center" Text="{Binding MAlarmText_CN}"/>
+                                                </Border>
+                                            </DataTemplate>
+                                        </GridViewColumn.CellTemplate>
+                                    </GridViewColumn>
+                                </GridView>
+                            </ListView.View>
+                        </ListView>
+                    </Grid>
+                </TabItem>
+            </TabControl>
+        </Grid>
     </Border>
 </hb:PluginControl>
diff --git a/HMI.AlermInfoManage/AlarmInfoManage.xaml.cs b/HMI.AlermInfoManage/AlarmInfoManage.xaml.cs
index f0feadd..c7891c0 100644
--- a/HMI.AlermInfoManage/AlarmInfoManage.xaml.cs
+++ b/HMI.AlermInfoManage/AlarmInfoManage.xaml.cs
@@ -1,15 +1,19 @@
-锘縰sing HMI.AlarmInfoManage.Entity;
-using HMI.AlarmInfoManage.Model;
+锘縰sing ConnectionUtil;
+using HMI.AlarmInfoManage.Entity;
 using HMI.Base;
+using HMI.Core.Api;
 using HMI.Core.Setting;
 using HMI.Core.Util;
 using HMI.UI.Util;
+using HMI.Util;
 using Microsoft.Win32;
-using NPOI.XSSF.UserModel;
+using OPCConnection;
 using Sharp7;
 using SimensDriver;
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Data;
 using System.IO;
 using System.Linq;
 using System.Timers;
@@ -25,9 +29,16 @@
     {
         private string blockPath;
         private string markPath;
-        private string textSettingPath;
+        private string alarmTextPath;
+
+        private string opcIP = "127.0.0.1";
+        private int opcPort = 6543;
+        private int localPort = 18888;
+        private volatile bool isClosing = false;
+
+
+        private string dbPath;
         private Simens plc = new Simens();
-        private XmlUIModel Model;
 
         private bool isHaveAlarm;
         private bool isNewAlarm;
@@ -50,7 +61,7 @@
         /// <summary>
         /// 鎺ユ敹鎶ヨ鐨勯槦鍒�
         /// </summary>
-        private MSMQHelper alarmQueue = new MSMQHelper("LTHMI_Alarm");
+        private MSMQHelper alarmQueue = new MSMQHelper("HMI_Alarm");
         /// <summary>
         /// 鏃ュ織鐢熸垚鍣�
         /// </summary>
@@ -59,6 +70,8 @@
         /// 鍒锋柊鎶ヨ鐨勯槦鍒�
         /// </summary>
         private MSMQHelper alarmRefershToMain = new MSMQHelper("LTHMI_Alarm_Rrfresh");
+
+        private SCADAApi lTSCADAApi = new OPCClient();
 
         /// <summary>
         /// 鎶ヨ
@@ -80,15 +93,12 @@
         /// <summary>
         /// 浠g爜绠$悊
         /// </summary>
-        private List<AlarmSetting> alarmSettings = new List<AlarmSetting>();
+        private ObservableCollection<AlarmSetting> alarmSettings = new ObservableCollection<AlarmSetting>();
         // <summary>
         /// 鏍囧織浣嶆姤璀︾鐞�
         /// </summary>
-        private List<AlarmSetting> signAlarmSetting = new List<AlarmSetting>();
-        /// <summary>
-        /// 鎶ヨ鏂囨湰鍒楄〃
-        /// </summary>
-        private List<AlarmText> alarmTexts = new List<AlarmText>();
+        private ObservableCollection<AlarmSetting> markAlarmSetting = new ObservableCollection<AlarmSetting>();
+
         /// <summary>
         /// 浼犺嚦涓荤獥浣撶殑鎶ヨ淇℃伅
         /// </summary>
@@ -97,17 +107,114 @@
         private AlarmText alarm;
         private SystemSetting systemSetting;
 
+        private ObservableCollection<AlarmText> datasource = new ObservableCollection<AlarmText>();
+
+        public ObservableCollection<AlarmText> Datasource
+        {
+            get { return datasource; }
+            set { Set(ref datasource, value); }
+        }
+
+        /// <summary>
+        /// 鎶ヨ鏂囨湰鍒楄〃
+        /// </summary>
+        private ObservableCollection<AlarmText> alarmTexts = new ObservableCollection<AlarmText>();
+        public ObservableCollection<AlarmText> AlarmTexts
+        {
+            get { return alarmTexts; }
+            set { Set(ref alarmTexts, value); }
+        }
+
+        public Dictionary<string, AlarmText> textMap = new Dictionary<string, AlarmText>();
+
+        private double size = 30;
+
+        private int currentPage = 0;
+        public int CurrentPage
+        {
+            get => currentPage;
+            set
+            {
+                Set(ref currentPage, value);
+                RefreshData();
+            }
+        }
+
+        private int maxPage = 0;
+        public int MaxPage
+        {
+            get => maxPage;
+            set
+            {
+                Set(ref maxPage, value);
+            }
+        }
 
         public AlarmInfoManage()
         {
+            DataContext = this;
             systemSetting = SystemSetting.Instance;
             InitializeComponent();
         }
 
+        public override void SetBaseFolder(string folder)
+        {
+            base.SetBaseFolder(folder);
+            dbPath = Path.Combine(BaseFolder, "alarm.db");
+            alarmTextPath = Path.Combine(BaseFolder, "AlarmText.data");
+
+            blockPath = Path.Combine(BaseFolder, "BlockSetting.xml");
+            markPath = Path.Combine(BaseFolder, "MarkSetting.xml");
+        }
+
         public override void InitFinish()
         {
-            Model = new XmlUIModel(BaseFolder);
-            DataContext = Model;
+            if (!File.Exists(dbPath))
+            {
+                SqliteHelper.CreateDatabase(dbPath);
+            }
+            sqliteHelper = new SqliteHelper(dbPath);
+            sqliteHelper.ExecuteSql(@"CREATE TABLE IF NOT EXISTS Alarm (
+                    id INTEGER PRIMARY KEY AUTOINCREMENT,
+                    source TEXT NOT NULL,
+                    alarmcode TEXT,
+                    alarminfo TEXT,
+                    time DATETIME);");
+
+            LoadAlarmSetting();
+            LoadAlarmText();
+
+            MonitorTimer = new Timer
+            {
+                Interval = 100,
+            };
+            MonitorTimer.Elapsed += MonitorTimer_Elapsed;
+            if (!lTSCADAApi.Connect2(opcIP, opcPort, localPort, out string errMsg))
+            {
+                //MsgBoxHelper.Error(this, errMsg);
+            }
+            else
+            {
+                this.MonitorTimer.Start();
+            }
+
+            PcTimer = new Timer
+            {
+                Interval = 100,
+                AutoReset = false
+            };
+            PcTimer.Elapsed += PcTimer_Elapsed;
+            PcTimer.Enabled = true;
+        }
+
+        public override void DoClose(object sender, EventArgs e)
+        {
+            isClosing = true;
+            PcTimer.Enabled = false;
+            PcTimer?.Dispose();
+
+            MonitorTimer.Enabled = false;
+            MonitorTimer?.Dispose();
         }
 
         private void Button_Click(object sender, RoutedEventArgs e)
@@ -119,7 +226,8 @@
                     OpenSettingWindow();
                     break;
                 case "refreshBtn":
-                    //Model.
+                    LoadAlarmText();
+                    RefreshPage(CurrentPage);
                     break;
                 case "importBtn":
                     ImportText();
@@ -131,11 +239,58 @@
 
         private void OpenSettingWindow()
         {
-            AlarmSettingList listModal = new AlarmSettingList(blockPath, markPath, log)
+            AlarmSettingList listModal = new AlarmSettingList(blockPath, markPath,alarmSettings,markAlarmSetting, log)
             {
                 Owner = Window.GetWindow(this)
             };
             listModal.ShowDialog();
+            alarmSettings = listModal.Datasource;
+            markAlarmSetting = listModal.MarkDatasource;
+        }
+
+        private void RefreshPage(int? page = null)
+        {
+            var sql = "SELECT COUNT (*) as num FROM Alarm;";
+            var dt = sqliteHelper.GetDataTable(sql);
+            if (dt.Rows.Count <= 0)
+            {
+                CurrentPage = 1;
+                MaxPage = 1;
+                return;
+            }
+            int count = int.Parse(dt.Rows[0]["num"].ToString());
+            MaxPage = (int)Math.Ceiling(count / size);
+
+            if (page != null)
+            {
+                CurrentPage = page.Value;
+            }
+            else if (CurrentPage > MaxPage)
+            {
+                CurrentPage = MaxPage;
+            }
+        }
+
+        private void RefreshData()
+        {
+            var list = new ObservableCollection<AlarmText>();
+            var sql = $"SELECT source, alarmcode, alarminfo, time FROM Alarm ORDER BY time DESC LIMIT ({CurrentPage - 1} * {size}), {size};";
+            var dt = sqliteHelper.GetDataTable(sql);
+            if (dt.Rows.Count > 0)
+            {
+                foreach (DataRow dr in dt.Rows)
+                {
+                    var at = new AlarmText
+                    {
+                        Source = dr["source"].ToString(),
+                        AlarmCode = dr["alarmcode"].ToString(),
+                        AlarmInfo = dr["alarminfo"].ToString(),
+                        Time = dr["time"].ToString(),
+                    };
+                    list.Add(at);
+                }
+            }
+            Datasource = list;
         }
 
         private void ImportText()
@@ -160,6 +315,60 @@
         /// 瀵煎叆鎶ヨ浠g爜鏂囨湰
         /// </summary>
         /// <param name="excelFile">鐢靛瓙璁捐琛ㄦ牸鏂囦欢</param>
+        //private void ImportExcelAlarmText(string excelFile)
+        //{
+        //    int index = 0;
+
+        //    try
+        //    {
+        //        if (excelFile.IndexOf(".xlsm") < 0)
+        //        {
+        //            this.Warning(this.lang("Alm.OnlySupportXlsm"));
+        //            return;
+        //        }
+
+        //        this.alarmTexts.Clear();
+        //        XSSFWorkbook workbook = new XSSFWorkbook(excelFile);
+        //        XSSFSheet sheet = (XSSFSheet)workbook.GetSheet("sheet1");
+        //        XSSFRow row = null;
+
+        //        for (int i = 2; i < (sheet.LastRowNum + 1); i++)
+        //        {
+        //            index = i;
+        //            row = (XSSFRow)sheet.GetRow(i);
+        //            if (row != null)
+        //            {
+        //                AlarmText alarm = new AlarmText();
+        //                alarm.AlarmCode = row.Cells[0].StringCellValue;
+        //                alarm.OpNo = row.Cells[1].StringCellValue;
+        //                alarm.ModuleNo = row.Cells[2].StringCellValue;
+        //                alarm.ModuleName_CN = row.Cells[3].StringCellValue;
+        //                alarm.ModuleName_EN = row.Cells[4].StringCellValue;
+        //                alarm.MAlarmCode = row.Cells[5].StringCellValue;
+        //                alarm.MAlarmText_CN = row.Cells[6].StringCellValue;
+        //                alarm.MAlarmText_EN = row.Cells[7].StringCellValue;
+        //                this.alarmTexts.Add(alarm);
+        //            }
+        //        }
+
+        //        if (sheet.LastRowNum > 0)
+        //        {
+        //            XmlHelper.SerializeToXml(textSettingPath, this.alarmTexts);
+        //        }
+
+        //        this.Success(this.lang("App.ImportSuccess"));
+        //    }
+        //    catch (Exception ex)
+        //    {
+        //        log.Error($"Row {index} AlarmText Import Failed.", ex);
+        //        this.Error($"{this.lang("Alm.RowImportFailed", index)}: {ex.Message}");
+        //    }
+        //}
+
+        /// <summary>
+        /// 瀵煎叆鎶ヨ浠g爜鏂囨湰
+        /// </summary>
+        /// <param name="excelFile">鐢靛瓙璁捐琛ㄦ牸鏂囦欢</param>
         private void ImportExcelAlarmText(string excelFile)
         {
             int index = 0;
@@ -172,35 +381,14 @@
                     return;
                 }
 
-                this.alarmTexts.Clear();
-                XSSFWorkbook workbook = new XSSFWorkbook(excelFile);
-                XSSFSheet sheet = (XSSFSheet)workbook.GetSheet("sheet1");
-                XSSFRow row = null;
+                this.alarmTexts = new ObservableCollection<AlarmText>
+                    (ExcelHelper.ReadFromExcel<AlarmText>(excelFile));
 
-                for (int i = 2; i < (sheet.LastRowNum + 1); i++)
+                if (alarmTexts.Count > 0)
                 {
-                    index = i;
-                    row = (XSSFRow)sheet.GetRow(i);
-                    if (row != null)
-                    {
-                        AlarmText alarm = new AlarmText();
-                        alarm.AlarmCode = row.Cells[0].StringCellValue;
-                        alarm.OpNo = row.Cells[1].StringCellValue;
-                        alarm.ModuleNo = row.Cells[2].StringCellValue;
-                        alarm.ModuleName_CN = row.Cells[3].StringCellValue;
-                        alarm.ModuleName_EN = row.Cells[4].StringCellValue;
-                        alarm.MAlarmCode = row.Cells[5].StringCellValue;
-                        alarm.MAlarmText_CN = row.Cells[6].StringCellValue;
-                        alarm.MAlarmText_EN = row.Cells[7].StringCellValue;
-                        this.alarmTexts.Add(alarm);
-                    }
+                    XmlHelper.SerializeToXml(alarmTextPath, this.alarmTexts);
                 }
-
-                if (sheet.LastRowNum > 0)
-                {
-                    XmlHelper.SerializeToXml(textSettingPath, this.alarmTexts);
-                }
-
+                LoadAlarmText();
                 this.Success(this.lang("App.ImportSuccess"));
             }
             catch (Exception ex)
@@ -210,27 +398,57 @@
             }
         }
 
-        private void UserControl_Loaded(object sender, RoutedEventArgs e)
+        private void LoadAlarmText()
         {
-            if (!string.IsNullOrEmpty(systemSetting.PlcIp))
+            try
             {
-                if (this.plc.isConnection)
+                textMap.Clear();
+                AlarmTexts = XmlHelper.DeserializeFromXml<ObservableCollection<AlarmText>>(alarmTextPath);
+                foreach (var text in AlarmTexts)
                 {
-                    this.plc.Simens_S7_ConnectPLC(systemSetting.PlcIp, 102, 0, 1, string.Empty);
+                    textMap[text.AlarmCode] = text;
                 }
             }
-            MonitorTimer = new Timer
+            catch (Exception ex)
             {
-                Interval = 100,
-            };
-            MonitorTimer.Elapsed += MonitorTimer_Elapsed;
+                log.Error("LoadAlarmText Error.", ex);
+                AlarmTexts = new ObservableCollection<AlarmText>();
+            }
+        }
 
-            PcTimer = new Timer
+        private void LoadAlarmSetting()
+        {
+            try
             {
-                Interval = 100,
-            };
-            PcTimer.Elapsed += PcTimer_Elapsed; ;
+                alarmSettings = XmlHelper.DeserializeFromXml<ObservableCollection<AlarmSetting>>(blockPath);
+            }
+            catch (Exception ex)
+            {
+                log.Error("LoadAlarmSetting Error.", ex);
+                alarmSettings = new ObservableCollection<AlarmSetting>();
+            }
 
+            try
+            {
+                markAlarmSetting = XmlHelper.DeserializeFromXml<ObservableCollection<AlarmSetting>>(markPath);
+            }
+            catch (Exception ex)
+            {
+                log.Error("LoadAlarmText Error.", ex);
+                markAlarmSetting = new ObservableCollection<AlarmSetting>();
+            }
+        }
+
+        private void UserControl_Loaded(object sender, RoutedEventArgs e)
+        {
+            //if (!string.IsNullOrEmpty(systemSetting.PlcIp))
+            //{
+            //    if (this.plc.isConnection)
+            //    {
+            //        this.plc.Simens_S7_ConnectPLC(systemSetting.PlcIp, 102, 0, 1, string.Empty);
+            //    }
+            //}
+            RefreshPage(1);
             Loaded -= UserControl_Loaded;
         }
 
@@ -242,25 +460,56 @@
                 this.alarmQueue.Receive(100, out string MessageID, out string MsgStr);
                 if (MessageID != string.Empty)
                 {
-                    log.Info(MsgStr);
                     switch (MessageID)
                     {
                         case "Clear":
-                            ////this.currentAlarmCachePc.RemoveAll(alarm => alarm.Source == "PC");
                             this.currentAlarmCachePc.Clear();
                             this.RefershAlarm();
                             break;
-                        default:
-                            AlarmText aalarm = this.alarmTexts.Find(alarm => alarm.AlarmCode == MsgStr);
-                            aalarm.Source = "PC";
-                            aalarm.Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
-                            if ((aalarm != null) && (this.currentAlarmCachePc.Find(alarm => alarm.AlarmCode == aalarm.AlarmCode) == null))
+                        case "AlarmCode":
+                            AlarmText aalarm = AlarmTexts.FirstOrDefault(alarm => alarm.AlarmCode == MsgStr);
+                            if (aalarm == null) break;
+                            var data = new AlarmText {
+                                Source = "PC",
+                                AlarmCode = aalarm.AlarmCode,
+                                AlarmInfo = aalarm.MAlarmText_CN,
+                                Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
+                            };
+                            //aalarm.Source = "PC";
+                            //aalarm.Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
+                            if (this.currentAlarmCachePc.Find(alarm => alarm.AlarmCode == aalarm.AlarmCode) == null)
                             {
-                                this.currentAlarmCachePc.Add(aalarm);
+                                this.currentAlarmCachePc.Add(data);
                                 this.RefershAlarm();
                                 this.isPcAlarmnumber = this.currentAlarmCachePc.Count;
                             }
-
+                            break;
+                        default:
+                            string[] arr = MsgStr.Split(',');
+                            string source = "PC";
+                            string info = string.Empty;
+                            if (arr.Length > 1)
+                            {
+                                source = arr[0];
+                                info = arr[1];
+                            }
+                            else
+                            {
+                                info = MsgStr;
+                            }
+                            AlarmText newAlarm = new AlarmText
+                            {
+                                Source = source,
+                                AlarmInfo = info,
+                                Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
+                            };
+                            if (this.currentAlarmCachePc
+                                .Find(alarm => alarm.Source == newAlarm.Source && alarm.AlarmInfo == newAlarm.AlarmInfo) == null)
+                            {
+                                this.currentAlarmCachePc.Add(newAlarm);
+                                this.RefershAlarm();
+                                this.isPcAlarmnumber = this.currentAlarmCachePc.Count;
+                            }
                             break;
                     }
                 }
@@ -271,79 +520,158 @@
             }
             finally
             {
-                PcTimer.Enabled = true;
+                if (!isClosing) PcTimer.Enabled = true;
             }
         }
+
+        //private void MonitorTimer_Elapsed_bak(object sender, ElapsedEventArgs e)
+        //{
+        //    try
+        //    {
+        //        this.MonitorTimer.Enabled = false;
+        //        isHaveAlarm = false;
+        //        foreach (var item in signAlarmSetting)
+        //        {
+        //            //isHaveAlarm = isHaveAlarm ||
+        //            //    this.plc.Simens_S7_ReadBool_DB(item.DBAddress, item.StartAddress, item.Length);
+        //        }
+
+        //        ////鏈夋姤璀�
+        //        if (isHaveAlarm)
+        //        {
+        //            this.isNewAlarm = false;
+        //            this.currentAlarmCachePlc.Clear();
+        //            RefreshDBSetting();
+        //            for (int i = 0; i < ((int)alarmCodeBytes.Length / 4); i++)
+        //            {
+        //                int code = S7.GetDIntAt(alarmCodeBytes, 4 * i);
+        //                ////鏈夋姤璀�
+        //                if (code != 0)
+        //                {
+        //                    string acode = string.Format("{0:d10}", code);
+        //                    alarm = AlarmTexts.FirstOrDefault(item => item.AlarmCode == acode);
+        //                    if (alarm != null)
+        //                    {
+        //                        alarm.Source = "PLC";
+        //                        alarm.Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+        //                        this.currentAlarmCachePlc.Add(alarm);
+        //                        ////鏈夋柊鎶ヨ
+        //                        if (this.preAlarmCachePlc.Find(alarm => alarm.AlarmCode == acode) == null)
+        //                        {
+        //                            this.isNewAlarm = true;
+        //                            this.SendLogMsg(alarm.ModuleName_CN + alarm.MAlarmText_CN);
+        //                        }
+        //                    }
+        //                }
+        //            }
+
+        //            ////鏈夋柊鎶ヨ鍜屾姤璀﹀噺灏戞椂鍒锋柊鎶ヨ琛�
+        //            if (this.isNewAlarm || (this.currentAlarmCachePlc.Count != this.preAlarmCachePlc.Count))
+        //            {
+        //                this.currentAlarmCachePlcToMain = XmlHelper.DeepClone(this.currentAlarmCachePlc);
+        //                this.RefershAlarm();
+        //            }
+
+        //            this.preAlarmCachePlc.Clear();
+        //            this.preAlarmCachePlc = XmlHelper.DeepClone(this.currentAlarmCachePlc);
+        //        }
+        //        else
+        //        {
+        //            if (this.preAlarmCachePlc.Count != 0 || this.currentAlarmCachePlcToMain.Count != 0 || this.currentAlarmCachePlc.Count != 0)
+        //            {
+        //                this.preAlarmCachePlc.Clear();
+        //                this.currentAlarmCachePlcToMain.Clear();
+        //                this.currentAlarmCachePlc.Clear();
+        //                this.RefershAlarm();
+        //            }
+        //        }
+        //    }
+        //    catch (Exception ex)
+        //    {
+        //        log.Error("sdadsa", ex);
+        //    }
+        //    finally
+        //    {
+        //        this.MonitorTimer.Enabled = true;
+        //    }
+        //}
 
         private void MonitorTimer_Elapsed(object sender, ElapsedEventArgs e)
         {
             try
             {
                 this.MonitorTimer.Enabled = false;
-                isHaveAlarm = false;
-                foreach (var item in signAlarmSetting)
+
+                if (!lTSCADAApi.IsConnected(out string errMsg))
                 {
-                    isHaveAlarm = isHaveAlarm ||
-                        this.plc.Simens_S7_ReadBool_DB(item.DBAddress, item.StartAddress, item.Length);
+                    log.Warn($"OPCServer Connection Error. {errMsg}");
+                    if (!lTSCADAApi.Connect2(opcIP, opcPort, localPort, out errMsg))
+                    {
+                        log.Warn($"OPCServer Try ReConnection Error. {errMsg}");
+                        return;
+                    }
                 }
 
-                ////鏈夋姤璀�
-                if (isHaveAlarm)
+                isHaveAlarm = false;
+                List<AlarmText> cache = new List<AlarmText>();
+                foreach (var item in alarmSettings)
                 {
-                    this.isNewAlarm = false;
-                    this.currentAlarmCachePlc.Clear();
-                    RefreshDBSetting();
-                    for (int i = 0; i < ((int)alarmCodeBytes.Length / 4); i++)
+                    var name = $"{item.DBAddress}.{item.StartAddress}";
+                    if (lTSCADAApi.DoFctReadSingle(name, out string alarmCode, out errMsg) && alarmCode != "0")
                     {
-                        int code = S7.GetDIntAt(alarmCodeBytes, 4 * i);
-                        ////鏈夋姤璀�
-                        if (code != 0)
+                        isHaveAlarm = true;
+                        if (!textMap.TryGetValue(alarmCode, out alarm)) {
+                            continue;
+                        }
+                        var newAlarm = new AlarmText
                         {
-                            string acode = string.Format("{0:d10}", code);
-                            alarm = this.alarmTexts.Find(item => item.AlarmCode == acode);
-                            if (alarm != null)
-                            {
-                                alarm.Source = "PLC";
-                                alarm.Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
-                                this.currentAlarmCachePlc.Add(alarm);
-                                ////鏈夋柊鎶ヨ
-                                if (this.preAlarmCachePlc.Find(alarm => alarm.AlarmCode == acode) == null)
-                                {
-                                    this.isNewAlarm = true;
-                                    this.SendLogMsg(alarm.ModuleName_CN + alarm.MAlarmText_CN);
-                                }
-                            }
+                            Source = "PLC",
+                            AlarmCode = alarm.AlarmCode,
+                            ModuleName_CN = alarm.ModuleName_CN,
+                            MAlarmText_CN = alarm.MAlarmText_CN,
+                            Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
+                            AlarmInfo = $"{alarm.ModuleName_CN} {alarm.MAlarmText_CN}"
+                        };
+                        if (this.currentAlarmCachePlc
+                                .Find(alarm => alarm.AlarmCode == newAlarm.AlarmCode) == null)
+                        {
+                            this.currentAlarmCachePlc.Add(newAlarm);
                         }
                     }
-
-                    ////鏈夋柊鎶ヨ鍜屾姤璀﹀噺灏戞椂鍒锋柊鎶ヨ琛�
-                    if (this.isNewAlarm || (this.currentAlarmCachePlc.Count != this.preAlarmCachePlc.Count))
-                    {
-                        this.currentAlarmCachePlcToMain = XmlHelper.DeepClone(this.currentAlarmCachePlc);
-                        this.RefershAlarm();
-                    }
-
-                    this.preAlarmCachePlc.Clear();
-                    this.preAlarmCachePlc = XmlHelper.DeepClone(this.currentAlarmCachePlc);
                 }
-                else
+
+                foreach (var item in markAlarmSetting)
                 {
-                    if (this.preAlarmCachePlc.Count != 0 || this.currentAlarmCachePlcToMain.Count != 0 || this.currentAlarmCachePlc.Count != 0)
+                    var name = $"{item.DBAddress}.{item.StartAddress}";
+                    if (lTSCADAApi.DoFctReadSingle(name, out string alarmCode, out errMsg) && alarmCode != "0")
                     {
-                        this.preAlarmCachePlc.Clear();
-                        this.currentAlarmCachePlcToMain.Clear();
-                        this.currentAlarmCachePlc.Clear();
-                        this.RefershAlarm();
+                        isHaveAlarm = true;
+                        var newAlarm = new AlarmText
+                        {
+                            Source = "PLC",
+                            AlarmCode = alarmCode,
+                            Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
+                            AlarmInfo = item.Description
+                        };
+                        if (this.currentAlarmCachePlc
+                                .Find(d => d.AlarmInfo == newAlarm.AlarmInfo) == null)
+                        {
+                            this.currentAlarmCachePlc.Add(newAlarm);
+                        }
                     }
                 }
+                this.RefershAlarm();
+                this.isPcAlarmnumber = this.currentAlarmCachePc.Count;
+
+                if (!isHaveAlarm) currentAlarmCachePlc.Clear();
             }
             catch (Exception ex)
             {
-                log.Error("sdadsa", ex);
+                log.Error("Read OPCServer Data Error.", ex);
             }
             finally
             {
-                this.MonitorTimer.Enabled = true;
+                if(!isClosing) this.MonitorTimer.Enabled = true;
             }
         }
 
@@ -355,11 +683,13 @@
             List<byte> bytes = new List<byte>();
             foreach (var item in alarmSettings)
             {
-                byte[] itemalarmCodeBytes = this.plc.Simens_S7_ReadBytes_DB("DB", item.DBAddress, item.StartAddress, item.Length);
-                bytes.AddRange(itemalarmCodeBytes.ToList());
+                //byte[] itemalarmCodeBytes = this.plc.Simens_S7_ReadBytes_DB("DB", item.DBAddress, item.StartAddress, item.Length);
+                //bytes.AddRange(itemalarmCodeBytes.ToList());
             }
             alarmCodeBytes = bytes.ToArray();
         }
+
+
 
         /// <summary>
         /// 鍒锋柊鎶ヨ淇℃伅
@@ -370,20 +700,26 @@
 
             foreach (AlarmText alarm in this.currentAlarmCachePlc)
             {
-                this.currentInfo = alarm.ModuleName_CN + alarm.MAlarmText_CN;
-                Model.Datasource.Add(alarm);
-                string sqladd = string.Format("INSERT INTO Alarm (source, alarmcode, alarminfo, time) VALUES( '{0}','{1}','{2}','{3}');",
-                    alarm.Source, alarm.AlarmCode, this.currentInfo, alarm.Time);
-                sqliteHelper.ExecuteSql(sqladd);
+                var currentInfo = alarm.AlarmInfo;
+                string sqladd = string.Format("INSERT INTO Alarm (source, alarmcode, alarminfo, time) VALUES('{0}','{1}','{2}','{3}');",
+                    alarm.Source, alarm.AlarmCode, currentInfo, alarm.Time);
+                if (AlarmShowHelper.ShowAlarm(currentInfo))
+                {
+                    sqliteHelper.ExecuteSql(sqladd);
+                }
             }
+            //this.currentAlarmCachePlc.Clear();
 
             foreach (AlarmText alarm in this.currentAlarmCachePc)
             {
-
-                Model.Datasource.Add(alarm);
-                string sqladd2 = string.Format("INSERT INTO Alarm (source, alarmcode, alarminfo, time) VALUES( '{0}','{1}','{2}','{3}');", alarm.Source, alarm.AlarmCode, this.currentInfo, alarm.Time);
-                this.sqliteHelper.ExecuteSql(sqladd2);
+                string sqladd2 = string.Format("INSERT INTO Alarm (source, alarmcode, alarminfo, time) VALUES('{0}','{1}','{2}','{3}');", alarm.Source, alarm.AlarmCode, alarm.AlarmInfo, alarm.Time);
+                sqliteHelper.ExecuteSql(sqladd2);
+                //Dispatcher.BeginInvoke(new Action(() =>
+                //{
+                //    Datasource.Add(alarm);
+                //}));
             }
+            this.currentAlarmCachePc.Clear();
         }
 
         /// <summary>
@@ -394,14 +730,6 @@
         {
             string LogMsg = "{\"Type\":\"Alarm\",\"Source\":\"" + alarm.Source + "\",\"Msg\":\"" + Msg + "\",\"Time\":\"" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "\"}";
             this.loggeralarm.Send("Log", LogMsg);
-        }
-
-        public override void SetBaseFolder(string folder)
-        {
-            base.SetBaseFolder(folder);
-            blockPath = Path.Combine(BaseFolder, "BlockSetting.xml");
-            markPath = Path.Combine(BaseFolder, "MarkSetting.xml");
-            textSettingPath = Path.Combine(BaseFolder, "textSetting.xml");
         }
     }
 }
diff --git a/HMI.AlermInfoManage/AlarmSettingList.xaml b/HMI.AlermInfoManage/AlarmSettingList.xaml
index 75fc864..573554d 100644
--- a/HMI.AlermInfoManage/AlarmSettingList.xaml
+++ b/HMI.AlermInfoManage/AlarmSettingList.xaml
@@ -10,15 +10,239 @@
             xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
             mc:Ignorable="d"
             Title="{Binding SelectedValue,Mode=OneWay}"
-            SizeToContent="Height"
             pu:WindowXModalDialog.Buttons="OK"
             pu:WindowXModalDialog.OKButtonContent="{DynamicResource App.Close}"
-            Width="800"
+            Width="1000"
             Height="600"
+            Loaded="WindowX_Loaded"
+            WindowStartupLocation="CenterOwner"
             Style="{StaticResource ModalWindow}">
+    <pu:WindowX.Resources>
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonListView.xaml"/>
+            </ResourceDictionary.MergedDictionaries>
+
+            <Style x:Key="EditLink" TargetType="Button" BasedOn="{StaticResource RowLink}">
+                <Setter Property="Content" Value="{DynamicResource App.Edit}"/>
+                <Setter Property="Foreground" Value="{DynamicResource PrimaryColor}"/>
+                <Style.Triggers>
+                    <DataTrigger Binding="{Binding Path=IsMouseOver,
+                RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ListViewItem,AncestorLevel=1}}" 
+         Value="True">
+                        <Setter Property="Foreground" Value="#FFF"/>
+                    </DataTrigger>
+                    <DataTrigger Binding="{Binding Path=IsSelected,
+       RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ListViewItem,AncestorLevel=1}}" 
+Value="True">
+                        <Setter Property="Foreground" Value="#FFF"/>
+                    </DataTrigger>
+                </Style.Triggers>
+            </Style>
+        </ResourceDictionary>
+    </pu:WindowX.Resources>
     <Grid Margin="10">
-        <Border BorderThickness="1" BorderBrush="Gray">
-            <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition />
+        </Grid.RowDefinitions>
+
+        <StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Right" 
+            Margin="0,0,0,10"
+                    dp:StackPanelHelper.ChildSpace="10">
+            <Button Style="{StaticResource AddBtn}" x:Name="addBtn" Click="Button_Click"/>
+            <Button Style="{StaticResource RfhBtn}" x:Name="refreshBtn" Click="Button_Click"/>
+            <Button Style="{StaticResource ExportBtn}" x:Name="exportBtn" Click="Button_Click"/>
+            <Button Style="{StaticResource ImportBtn}" x:Name="importBtn" Click="Button_Click"/>
+            <Button Style="{StaticResource DelBtn}" x:Name="delBatchBtn" Click="Button_Click"/>
+        </StackPanel>
+
+        <Border Grid.Row="1" BorderThickness="0" BorderBrush="Gray">
+
+            <TabControl Style="{StaticResource HeaderTabControl}" SelectedIndex="{Binding ItemIndex}" x:Name="tab">
+                <TabItem Header="DB鍧楁姤璀�">
+                    <ListView Style="{StaticResource DefaultListTable}" ItemsSource="{Binding Datasource}">
+                        <ListView.View>
+                            <GridView AllowsColumnReorder="False">
+
+                                <GridViewColumn Width="30">
+                                    <GridViewColumn.CellTemplate>
+                                        <DataTemplate>
+                                            <CheckBox VerticalAlignment="Center"
+IsChecked="{Binding IsSelected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
+                                        </DataTemplate>
+                                    </GridViewColumn.CellTemplate>
+                                </GridViewColumn>
+
+                                <GridViewColumn Width="60" Header="#">
+                                    <GridViewColumn.CellTemplate>
+                                        <DataTemplate>
+                                            <Border Style="{StaticResource CellBorder}">
+                                                <TextBlock Style="{StaticResource ListTableTextBlock}"  Text="{Binding Converter={StaticResource lvRowNumber},
+RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ListViewItem,AncestorLevel=1}}"/>
+                                            </Border>
+                                        </DataTemplate>
+                                    </GridViewColumn.CellTemplate>
+                                </GridViewColumn>
+
+                                <GridViewColumn Width="300">
+                                    <GridViewColumn.Header>
+                                        <TextBlock Text="{DynamicResource Alm.DbAddress}"/>
+                                    </GridViewColumn.Header>
+                                    <GridViewColumn.CellTemplate>
+                                        <DataTemplate>
+                                            <Border Style="{StaticResource CellBorder}">
+                                                <TextBlock Style="{StaticResource ListTableTextBlock}" 
+                                                           Text="{Binding DBAddress}"/>
+                                            </Border>
+                                        </DataTemplate>
+                                    </GridViewColumn.CellTemplate>
+                                </GridViewColumn>
+                                <GridViewColumn Width="200">
+                                    <GridViewColumn.Header>
+                                        <TextBlock Text="{DynamicResource Alm.StartAddress}"/>
+                                    </GridViewColumn.Header>
+                                    <GridViewColumn.CellTemplate>
+                                        <DataTemplate>
+                                            <Border Style="{StaticResource CellBorder}">
+                                                <TextBlock Style="{StaticResource ListTableTextBlock}" 
+   Text="{Binding StartAddress}"/>
+                                            </Border>
+                                        </DataTemplate>
+                                    </GridViewColumn.CellTemplate>
+                                </GridViewColumn>
+                                <GridViewColumn Width="200">
+                                    <GridViewColumn.Header>
+                                        <TextBlock Text="{DynamicResource Alm.DbLength}"/>
+                                    </GridViewColumn.Header>
+                                    <GridViewColumn.CellTemplate>
+                                        <DataTemplate>
+                                            <Border Style="{StaticResource CellBorder}">
+                                                <TextBlock Style="{StaticResource ListTableTextBlock}" 
+                                                   Text="{Binding Length}"/>
+                                            </Border>
+                                        </DataTemplate>
+                                    </GridViewColumn.CellTemplate>
+                                </GridViewColumn>
+                                <GridViewColumn Width="120">
+                                    <GridViewColumn.Header>
+                                        <TextBlock Text="{DynamicResource App.Opt}"/>
+                                    </GridViewColumn.Header>
+                                    <GridViewColumn.CellTemplate>
+                                        <DataTemplate>
+                                            <StackPanel Orientation="Horizontal" dp:StackPanelHelper.ChildSpace="10">
+                                                <Button x:Name="editBtn" Style="{StaticResource EditLink}" 
+                Click="Button_Click" Tag="{Binding }"/>
+
+                                                <Button x:Name="delBtn" Style="{StaticResource DeleteLink}" 
+                Click="Button_Click" Tag="{Binding }"/>
+                                            </StackPanel>
+                                        </DataTemplate>
+                                    </GridViewColumn.CellTemplate>
+                                </GridViewColumn>
+                            </GridView>
+                        </ListView.View>
+                    </ListView>
+                </TabItem>
+                <TabItem Header="鏍囧織浣嶆姤璀�">
+                    <ListView Style="{StaticResource DefaultListTable}" ItemsSource="{Binding MarkDatasource}">
+                        <ListView.View>
+                            <GridView AllowsColumnReorder="False">
+
+                                <GridViewColumn Width="30">
+                                    <GridViewColumn.CellTemplate>
+                                        <DataTemplate>
+                                            <CheckBox VerticalAlignment="Center"
+IsChecked="{Binding IsSelected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
+                                        </DataTemplate>
+                                    </GridViewColumn.CellTemplate>
+                                </GridViewColumn>
+
+                                <GridViewColumn Width="60" Header="#">
+                                    <GridViewColumn.CellTemplate>
+                                        <DataTemplate>
+                                            <Border Style="{StaticResource CellBorder}">
+                                                <TextBlock Style="{StaticResource ListTableTextBlock}"  Text="{Binding Converter={StaticResource lvRowNumber},
+RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ListViewItem,AncestorLevel=1}}"/>
+                                            </Border>
+                                        </DataTemplate>
+                                    </GridViewColumn.CellTemplate>
+                                </GridViewColumn>
+
+                                <GridViewColumn Width="220">
+                                    <GridViewColumn.Header>
+                                        <TextBlock Text="{DynamicResource Alm.DbAddress}"/>
+                                    </GridViewColumn.Header>
+                                    <GridViewColumn.CellTemplate>
+                                        <DataTemplate>
+                                            <Border Style="{StaticResource CellBorder}">
+                                                <TextBlock Style="{StaticResource ListTableTextBlock}" 
+                                                           Text="{Binding DBAddress}"/>
+                                            </Border>
+                                        </DataTemplate>
+                                    </GridViewColumn.CellTemplate>
+                                </GridViewColumn>
+                                <GridViewColumn Width="180">
+                                    <GridViewColumn.Header>
+                                        <TextBlock Text="{DynamicResource Alm.StartAddress}"/>
+                                    </GridViewColumn.Header>
+                                    <GridViewColumn.CellTemplate>
+                                        <DataTemplate>
+                                            <Border Style="{StaticResource CellBorder}">
+                                                <TextBlock Style="{StaticResource ListTableTextBlock}" 
+   Text="{Binding StartAddress}"/>
+                                            </Border>
+                                        </DataTemplate>
+                                    </GridViewColumn.CellTemplate>
+                                </GridViewColumn>
+                                <GridViewColumn Width="150">
+                                    <GridViewColumn.Header>
+                                        <TextBlock Text="{DynamicResource Alm.DbLength}"/>
+                                    </GridViewColumn.Header>
+                                    <GridViewColumn.CellTemplate>
+                                        <DataTemplate>
+                                            <Border Style="{StaticResource CellBorder}">
+                                                <TextBlock Style="{StaticResource ListTableTextBlock}" 
+                                                   Text="{Binding Length}"/>
+                                            </Border>
+                                        </DataTemplate>
+                                    </GridViewColumn.CellTemplate>
+                                </GridViewColumn>
+                                <GridViewColumn Width="200">
+                                    <GridViewColumn.Header>
+                                        <TextBlock Text="{DynamicResource Alm.Description}"/>
+                                    </GridViewColumn.Header>
+                                    <GridViewColumn.CellTemplate>
+                                        <DataTemplate>
+                                            <Border Style="{StaticResource CellBorder}">
+                                                <TextBlock Style="{StaticResource ListTableTextBlock}" 
+                   Text="{Binding Description}"/>
+                                            </Border>
+                                        </DataTemplate>
+                                    </GridViewColumn.CellTemplate>
+                                </GridViewColumn>
+                                <GridViewColumn Width="120">
+                                    <GridViewColumn.Header>
+                                        <TextBlock Text="{DynamicResource App.Opt}"/>
+                                    </GridViewColumn.Header>
+                                    <GridViewColumn.CellTemplate>
+                                        <DataTemplate>
+                                            <StackPanel Orientation="Horizontal" dp:StackPanelHelper.ChildSpace="10">
+                                                <Button x:Name="editBtn" Style="{StaticResource EditLink}" 
+                Click="Button_Click" Tag="{Binding }"/>
+
+                                                <Button x:Name="delBtn" Style="{StaticResource DeleteLink}" 
+                Click="Button_Click" Tag="{Binding }"/>
+                                            </StackPanel>
+                                        </DataTemplate>
+                                    </GridViewColumn.CellTemplate>
+                                </GridViewColumn>
+                            </GridView>
+                        </ListView.View>
+                    </ListView>
+                </TabItem>
+            </TabControl>
+            <!--<Grid>
                 <Grid.RowDefinitions>
                     <RowDefinition Height="Auto"/>
                     <RowDefinition Height="Auto"/>
@@ -40,7 +264,7 @@
                                     <ControlTemplate TargetType="ListBoxItem">
                                         <Border x:Name="border" Background="LightGray" 
 Padding="10" Margin="0" BorderThickness="0">
-                                            <!-- 鍐呭鍛堢幇鍣� -->
+                                            --><!-- 鍐呭鍛堢幇鍣� --><!--
                                             <StackPanel>
                                                 <ContentPresenter  Content="{TemplateBinding Content}" 
                    ContentStringFormat="{TemplateBinding ContentStringFormat}" 
@@ -124,7 +348,7 @@
                         </DataGridTemplateColumn>
                     </DataGrid.Columns>
                 </DataGrid>
-            </Grid>
+            </Grid>-->
         </Border>
     </Grid>
 </pu:WindowX>
diff --git a/HMI.AlermInfoManage/AlarmSettingList.xaml.cs b/HMI.AlermInfoManage/AlarmSettingList.xaml.cs
index a641650..ba8ac96 100644
--- a/HMI.AlermInfoManage/AlarmSettingList.xaml.cs
+++ b/HMI.AlermInfoManage/AlarmSettingList.xaml.cs
@@ -1,7 +1,10 @@
 锘縰sing HMI.AlarmInfoManage.Entity;
 using HMI.Core.Util;
 using HMI.UI.Util;
+using HMI.Util;
 using log4net;
+using Microsoft.Win32;
+using NPOI.SS.Formula.Functions;
 using Panuon.WPF.UI;
 using System;
 using System.Collections.ObjectModel;
@@ -18,10 +21,14 @@
     public partial class AlarmSettingList : WindowX
     {
         private ILog log;
-        public AlarmSettingList(string blockFile, string markFile, ILog log)
+        public AlarmSettingList(string blockFile, string markFile, ObservableCollection<AlarmSetting> datasource,
+            ObservableCollection<AlarmSetting> markDatasource,
+            ILog log)
         {
             this.blockFile = blockFile;
             this.markFile = markFile;
+            Datasource = datasource;
+            MarkDatasource = markDatasource;
             this.log = log;
             DataContext = this;
             InitializeComponent();
@@ -69,7 +76,7 @@
         private void Button_Click(object sender, RoutedEventArgs e)
         {
             Button btn = sender as Button;
-            DataGridRow row = btn?.Tag as DataGridRow;
+            AlarmSetting data = btn?.Tag as AlarmSetting;
             switch (btn.Name)
             {
                 case "refreshBtn":
@@ -82,36 +89,57 @@
                     DeleteBatchData();
                     break;
                 case "editBtn":
-                    EditData(row.GetIndex(), btn.DataContext as AlarmSetting);
+                    EditData(ItemIndex == 0 ? Datasource.IndexOf(data) : MarkDatasource.IndexOf(data), data);
+                    break;
+                case "exportBtn":
+                    ExportData();
+                    break;
+                case "importBtn":
+                    ImportData();
                     break;
                 case "delBtn":
-                    DeleteData(row.GetIndex());
+                    DeleteData(ItemIndex == 0 ? Datasource.IndexOf(data) : MarkDatasource.IndexOf(data));
                     break;
                 default:
                     break;
             }
-
         }
 
         private void LoadData()
         {
             try
             {
-                ObservableCollection<AlarmSetting> datasource = new ObservableCollection<AlarmSetting>();
-                if (ItemIndex == 0 && File.Exists(blockFile))
+                if (File.Exists(blockFile))
                 {
-                    datasource =
+                    Datasource =
                         XmlHelper.DeserializeFromXml<ObservableCollection<AlarmSetting>>(blockFile);
                 }
-                else if (ItemIndex == 1 && File.Exists(markFile))
+                else
                 {
-                    datasource =
-                        XmlHelper.DeserializeFromXml<ObservableCollection<AlarmSetting>>(markFile);
+                    Datasource = new ObservableCollection<AlarmSetting>();
                 }
-                Datasource = datasource;
             }
             catch (Exception ex)
             {
+                Datasource = new ObservableCollection<AlarmSetting>();
+                ToastHelper.Error(this, $"{this.lang("App.LoadFailed")}: {ex.Message}");
+            }
+
+            try
+            {
+                if (File.Exists(markFile))
+                {
+                    MarkDatasource =
+                        XmlHelper.DeserializeFromXml<ObservableCollection<AlarmSetting>>(markFile);
+                }
+                else
+                {
+                    MarkDatasource = new ObservableCollection<AlarmSetting>();
+                }
+            }
+            catch (Exception ex)
+            {
+                MarkDatasource = new ObservableCollection<AlarmSetting>();
                 ToastHelper.Error(this, $"{this.lang("App.LoadFailed")}: {ex.Message}");
             }
         }
@@ -127,7 +155,7 @@
                     Title = $"{this.lang("App.Add")}{temp}",
                     Owner = Window.GetWindow(this),
                 };
-                if (modal.Open(-1, data, Datasource, file) == true)
+                if (modal.Open(-1, data, ItemIndex == 0 ? Datasource : MarkDatasource, file, ItemIndex != 0) == true)
                 {
                     LoadData();
                 }
@@ -150,7 +178,7 @@
                     Title = $"{this.lang("App.Edit")}{temp}",
                     Owner = Window.GetWindow(this),
                 };
-                if (modal.Open(index, data, Datasource, file) == true)
+                if (modal.Open(index, data, ItemIndex == 0 ? Datasource : MarkDatasource, file, ItemIndex != 0) == true)
                 {
                     LoadData();
                 }
@@ -161,6 +189,61 @@
                 ToastHelper.Error(this, $"{this.lang("App.EditFailed")}: {ex.Message}");
             }
         }
+
+        private void ExportData()
+        {
+            try
+            {
+                SaveFileDialog sfd = new SaveFileDialog
+                {
+                    FileName = ItemIndex == 0 ? "DB鍧楁姤璀�.xls" : "鏍囧織浣嶆姤璀�.xls",
+                    Filter = "Excel|*.xls"
+                };
+                if (sfd.ShowDialog() == true)
+                {
+                    ExcelHelper.ExportExcel(sfd.FileName, ItemIndex == 0 ? Datasource : MarkDatasource);
+                }
+            }
+            catch (Exception ex)
+            {
+                log.Error("Export Failed.", ex);
+                ToastHelper.Error(this, $"{this.lang("App.ExportFailed")}: {ex.Message}");
+            }
+        }
+
+        private void ImportData()
+        {
+            try
+            {
+                OpenFileDialog ofd = new OpenFileDialog
+                {
+                    Filter = "Excel|*.xls"
+                };
+                if (ofd.ShowDialog() == true)
+                {
+                    var datasource = new ObservableCollection<AlarmSetting>(
+                        ExcelHelper.ReadFromExcel<AlarmSetting>(ofd.FileName));
+                    if (ItemIndex == 0)
+                    {
+                        Datasource = datasource;
+                    }
+                    else
+                    {
+                        MarkDatasource = datasource;
+                    }
+                    var file = ItemIndex == 0 ? blockFile : markFile;
+                    var temp = ItemIndex == 0 ? Datasource : MarkDatasource;
+                    XmlHelper.SerializeToXml(file, temp);
+                    LoadData();
+                }
+            }
+            catch (Exception ex)
+            {
+                log.Error("Import Failed.", ex);
+                ToastHelper.Error(this, $"{this.lang("App.ImportFailed")}: {ex.Message}");
+            }
+        }
+
         private void DeleteData(int index)
         {
             try
@@ -168,7 +251,7 @@
                 if (MsgBoxHelper.WarnMessage(Window.GetWindow(this), this.lang("HMI.MakeSureDeleteThis")) == MessageBoxResult.OK)
                 {
                     var file = ItemIndex == 0 ? blockFile : markFile;
-                    var temp = XmlHelper.DeepClone(Datasource);
+                    var temp = XmlHelper.DeepClone(ItemIndex == 0 ? Datasource : MarkDatasource);
 
                     temp.RemoveAt(index);
                     XmlHelper.SerializeToXml(file, temp);
@@ -185,7 +268,7 @@
         {
             try
             {
-                var temp = Datasource.Where(e => e.IsSelected).ToList();
+                var temp = (ItemIndex == 0 ? Datasource : MarkDatasource).Where(e => e.IsSelected).ToList();
                 if (temp == null || temp.Count <= 0)
                 {
                     ToastHelper.Warning(Window.GetWindow(this), this.lang("HMI.NoDeleteData"));
@@ -196,7 +279,7 @@
                     == MessageBoxResult.OK)
                 {
                     var file = ItemIndex == 0 ? blockFile : markFile;
-                    temp = Datasource.Where(e => !e.IsSelected).ToList();
+                    temp = (ItemIndex == 0 ? Datasource : MarkDatasource).Where(e => !e.IsSelected).ToList();
                     XmlHelper.SerializeToXml(file, temp);
                     LoadData();
                 }
diff --git a/HMI.AlermInfoManage/Entity/AlarmSetting.cs b/HMI.AlermInfoManage/Entity/AlarmSetting.cs
index d612d6a..1ca7090 100644
--- a/HMI.AlermInfoManage/Entity/AlarmSetting.cs
+++ b/HMI.AlermInfoManage/Entity/AlarmSetting.cs
@@ -1,4 +1,5 @@
-锘縰sing HMI.Core.Entity;
+锘縰sing HMI.Core;
+using HMI.Core.Entity;
 
 namespace HMI.AlarmInfoManage.Entity
 {
@@ -9,16 +10,22 @@
         /// <summary>
         /// 寮�濮婦B鍦板潃
         /// </summary>
-        public int DBAddress { get; set; }
+        [ExcelColumn("DB鍦板潃")]
+        public string DBAddress { get; set; }
 
         /// <summary>
         /// 璧峰Byte浣�
         /// </summary>
-        public int StartAddress { get; set; }
+        [ExcelColumn("璧峰浣�")]
+        public string StartAddress { get; set; }
 
         /// <summary>
         /// 闀垮害
         /// </summary>
+        [ExcelColumn("闀垮害")]
         public int Length { get; set; }
+
+        [ExcelColumn("鎶ヨ鎻忚堪")]
+        public string Description { get; set; }
     }
 }
diff --git a/HMI.AlermInfoManage/Entity/AlarmText.cs b/HMI.AlermInfoManage/Entity/AlarmText.cs
index e431b70..ea746d4 100644
--- a/HMI.AlermInfoManage/Entity/AlarmText.cs
+++ b/HMI.AlermInfoManage/Entity/AlarmText.cs
@@ -1,4 +1,5 @@
-锘縰sing System.Xml.Serialization;
+锘縰sing HMI.Core;
+using System.Xml.Serialization;
 
 namespace HMI.AlarmInfoManage.Entity
 {
@@ -7,42 +8,50 @@
         /// <summary>
         /// 鎶ヨ浠g爜
         /// </summary>
+        [ExcelColumn("缁勫悎鎶ヨ浠g爜")]
         public string AlarmCode { get; set; }
 
         /// <summary>
         /// 宸ヤ綅缂栧彿
         /// </summary>
+        [ExcelColumn("璁惧鍙�")]
         public string OpNo { get; set; }
 
         /// <summary>
         /// 妯″潡缂栧彿
         /// </summary>
+        [ExcelColumn("妯″潡鍙�")]
         public string ModuleNo { get; set; }
 
         /// <summary>
         /// 妯″潡鍚嶇О(CN)
         /// </summary>
+        [ExcelColumn("妯″潡鍚嶇О-CN")]
         public string ModuleName_CN { get; set; }
 
         /// <summary>
         /// 妯″潡鍚嶇О(EN)
         /// </summary>
+        [ExcelColumn("妯″潡鍚嶇О-US")]
         public string ModuleName_EN { get; set; }
 
 
         /// <summary>
         /// 妯″潡鎶ヨ浠g爜
         /// </summary>
+        [ExcelColumn("鎶ヨ浠g爜")]
         public string MAlarmCode { get; set; }
 
         /// <summary>
         /// 妯″潡鎶ヨ鏂囨湰(CN)
         /// </summary>
+        [ExcelColumn("鎶ヨ淇℃伅鏂囨湰-CN")]
         public string MAlarmText_CN { get; set; }
 
         /// <summary>
         /// 妯″潡鎶ヨ鏂囨湰(EN)
         /// </summary>
+        [ExcelColumn("鎶ヨ淇℃伅鏂囨湰-US")]
         public string MAlarmText_EN { get; set; }
 
         /// <summary>
@@ -50,6 +59,9 @@
         /// </summary>
         public string Time { get; set; }
 
+        [XmlIgnore]
+        public string AlarmInfo { get; set; }
+
         /// <summary>
         /// 鎶ヨ鏉ユ簮
         /// </summary> 
diff --git a/HMI.AlermInfoManage/HMI.AlarmInfoManage.csproj b/HMI.AlermInfoManage/HMI.AlarmInfoManage.csproj
index 452376a..c888b53 100644
--- a/HMI.AlermInfoManage/HMI.AlarmInfoManage.csproj
+++ b/HMI.AlermInfoManage/HMI.AlarmInfoManage.csproj
@@ -95,10 +95,17 @@
     <Reference Include="PresentationFramework" />
   </ItemGroup>
   <ItemGroup>
+    <Page Include="SimpleAlarm.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
     <Page Include="AlarmInfoManage.xaml">
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
     </Page>
+    <Compile Include="SimpleAlarm.xaml.cs">
+      <DependentUpon>SimpleAlarm.xaml</DependentUpon>
+    </Compile>
     <Compile Include="AlarmInfoManage.xaml.cs">
       <DependentUpon>AlarmInfoManage.xaml</DependentUpon>
       <SubType>Code</SubType>
@@ -155,6 +162,10 @@
     </None>
   </ItemGroup>
   <ItemGroup>
+    <ProjectReference Include="..\..\ConnectionUtil\ConnectionUtil\ConnectionUtil.csproj">
+      <Project>{D0806FA6-3F6D-4304-81B8-EAD9DA7CB5B2}</Project>
+      <Name>ConnectionUtil</Name>
+    </ProjectReference>
     <ProjectReference Include="..\HMI.Base\HMI.Base.csproj">
       <Project>{2a6f9720-a890-4036-9d73-41a0dae28341}</Project>
       <Name>HMI.Base</Name>
@@ -167,6 +178,14 @@
       <Project>{f17de6b5-af1d-48c6-94a0-6b763a6546a6}</Project>
       <Name>HMI.UI</Name>
     </ProjectReference>
+    <ProjectReference Include="..\HMI.Util\HMI.Util.csproj">
+      <Project>{AFEA100A-9223-448D-AB10-3015D93DD98C}</Project>
+      <Name>HMI.Util</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\OPCClient\OPCClient.csproj">
+      <Project>{E5740BB5-9FC6-4A20-B7C2-CC90C281ED05}</Project>
+      <Name>OPCClient</Name>
+    </ProjectReference>
   </ItemGroup>
   <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
diff --git a/HMI.AlermInfoManage/Modal/AlarmSettingModal.xaml b/HMI.AlermInfoManage/Modal/AlarmSettingModal.xaml
index da381d3..6dca9df 100644
--- a/HMI.AlermInfoManage/Modal/AlarmSettingModal.xaml
+++ b/HMI.AlermInfoManage/Modal/AlarmSettingModal.xaml
@@ -24,19 +24,25 @@
                 dp:FormGroupHelper.IsRequired="True"
                                 dp:FormGroupHelper.ValidateMethod="ValidateDBAddress"
                            GroupName="MyFormGroup">
-                <pu:NumberInput Style="{StaticResource FormNumberInput}" 
-                                x:Name="dbAddress" Interval="1"/>
+                <TextBox Style="{StaticResource EditorTextBoxStyle}" 
+                                x:Name="dbAddress"/>
             </pu:FormGroup>
 
             <pu:FormGroup Header="{DynamicResource Alm.StartAddress}" dp:FormGroupHelper.IsRequired="True"
                           GroupName="MyFormGroup">
-                <pu:NumberInput Style="{StaticResource FormNumberInput}" x:Name="startAddress" Interval="1"/>
+                <TextBox Style="{StaticResource EditorTextBoxStyle}" 
+                         x:Name="startAddress"/>
             </pu:FormGroup>
 
             <pu:FormGroup Header="{DynamicResource Alm.DbLength}" dp:FormGroupHelper.IsRequired="True"
                           GroupName="MyFormGroup">
                 <pu:NumberInput Style="{StaticResource FormNumberInput}" x:Name="length" Interval="1"/>
             </pu:FormGroup>
+
+            <pu:FormGroup x:Name="FgDesc" Header="{DynamicResource Alm.Description}" dp:FormGroupHelper.IsRequired="True"
+              GroupName="MyFormGroup">
+                <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="TbDesc"/>
+            </pu:FormGroup>
         </StackPanel>
     </Grid>
 </pu:WindowX>
diff --git a/HMI.AlermInfoManage/Modal/AlarmSettingModal.xaml.cs b/HMI.AlermInfoManage/Modal/AlarmSettingModal.xaml.cs
index a1ab9e5..08f0f70 100644
--- a/HMI.AlermInfoManage/Modal/AlarmSettingModal.xaml.cs
+++ b/HMI.AlermInfoManage/Modal/AlarmSettingModal.xaml.cs
@@ -8,6 +8,7 @@
 using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.Linq;
+using System.Windows;
 
 namespace HMI.AlarmInfoManage
 {
@@ -72,8 +73,7 @@
         public bool ValidateDBAddress(object value, out string err)
         {
             err = null;
-            int dbAddress = Convert.ToInt32(value);
-            if (Datasource.FirstOrDefault(e => e.DBAddress == dbAddress) != null)
+            if (Datasource?.FirstOrDefault(e => e.DBAddress == value) != null)
             {
                 err = this.lang("App.MustBeUnique", this.lang("Alm.DbAddress"));
                 return false;
@@ -86,24 +86,27 @@
             bool flag = FormGroupHelper.ValidateForm(formPanel);
             if (flag)
             {
-                Data.DBAddress = Convert.ToInt32(dbAddress.Value);
-                Data.StartAddress = Convert.ToInt32(startAddress.Value);
+                Data.DBAddress = dbAddress.Text;
+                Data.StartAddress = startAddress.Text;
                 Data.Length = Convert.ToInt32(length.Value);
+                Data.Description = TbDesc.Text;
             }
             return flag;
         }
 
         public bool? Open(int index, AlarmSetting data,
             ObservableCollection<AlarmSetting> dataSource,
-            string filePath)
+            string filePath, bool showDescription)
         {
+            FgDesc.Visibility = showDescription ? Visibility.Visible : Visibility.Collapsed;
             FilePath = filePath;
             DataIndex = index;
             Data = data;
             Datasource = dataSource;
-            dbAddress.Value = Data.DBAddress;
-            startAddress.Value = Data.StartAddress;
+            dbAddress.Text = Data.DBAddress;
+            startAddress.Text = Data.StartAddress;
             length.Value = Data.Length;
+            TbDesc.Text = Data.Description;
             return this.ShowDialog();
         }
 
diff --git a/HMI.AlermInfoManage/Model/XmlModel.cs b/HMI.AlermInfoManage/Model/XmlModel.cs
index f1eb4b1..eea1f87 100644
--- a/HMI.AlermInfoManage/Model/XmlModel.cs
+++ b/HMI.AlermInfoManage/Model/XmlModel.cs
@@ -1,14 +1,5 @@
-锘縰sing HMI.AlarmInfoManage.Entity;
-using HMI.Core.Entity;
-using HMI.Core.Util;
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Data;
+锘縰sing HMI.Core.Entity;
 using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
 
 namespace HMI.AlarmInfoManage.Model
 {
@@ -18,13 +9,7 @@
         public string AlermTextPath { get; private set; }
         public string AlermSettingPath { get; private set; }
 
-        private ObservableCollection<AlarmText> datasource = new ObservableCollection<AlarmText>();
-
-        public ObservableCollection<AlarmText> Datasource
-        {
-            get { return datasource; }
-            set { Set(ref datasource, value); }
-        }
+        
 
         public XmlUIModel(string FileFolder)
         {
diff --git a/HMI.AlermInfoManage/SimpleAlarm.xaml b/HMI.AlermInfoManage/SimpleAlarm.xaml
new file mode 100644
index 0000000..93b4d9e
--- /dev/null
+++ b/HMI.AlermInfoManage/SimpleAlarm.xaml
@@ -0,0 +1,96 @@
+锘�<hb:PluginControl x:Class="HMI.AlarmInfoManage.SimpleAlarm"
+             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:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
+             xmlns:local="clr-namespace:HMI.AlarmInfoManage"
+             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"
+             mc:Ignorable="d"
+             Background="{StaticResource WorkspaceBackground}"
+             Loaded="UserControl_Loaded"
+             d:DesignHeight="450" d:DesignWidth="800">
+
+    <Border Style="{StaticResource CardBoarder}">
+        <DockPanel>
+            <DockPanel Margin="0,0,0,10" DockPanel.Dock="Top">
+                <StackPanel DockPanel.Dock="Right" Style="{StaticResource ButtonGroup}">
+                    <Button Style="{StaticResource RfhBtn}" x:Name="refreshBtn" Click="Button_Click"/>
+                    <Button Style="{StaticResource ExportBtn}" x:Name="exportBtn" Click="Button_Click"/>
+                </StackPanel>
+
+                <TextBlock DockPanel.Dock="Left" FontSize="{StaticResource TitleFontSize}" 
+                           VerticalAlignment="Center" HorizontalAlignment="Left" Text="{DynamicResource Alm.Title}"/>
+            </DockPanel>
+
+            <pu:Pagination DockPanel.Dock="Bottom" Margin="0,10,0,0" Height="30"
+                       FontSize="14"
+HorizontalAlignment="Left"
+Foreground="Black"
+ItemsBackground="Transparent"
+ItemsCornerRadius="12"
+ItemsSelectedBackground="#36AAF7" CurrentPage="1"  MaxPage="10"/>
+            <DataGrid x:Name="table" Style="{StaticResource DefaultDataGrid}" ItemsSource="{Binding Datasource}">
+                <DataGrid.Columns>
+                    <DataGridTemplateColumn Header="" Width="Auto" CanUserResize="False">
+                        <DataGridTemplateColumn.CellTemplate>
+                            <DataTemplate>
+                                <CheckBox VerticalAlignment="Center" IsChecked="{Binding 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 Source}">
+                        <DataGridTextColumn.Header>
+                            <TextBlock Text="{DynamicResource Alm.Source}"/>
+                        </DataGridTextColumn.Header>
+                    </DataGridTextColumn>
+                    <DataGridTextColumn Width="*" Binding="{Binding Code}">
+                        <DataGridTextColumn.Header>
+                            <TextBlock Text="{DynamicResource Alm.Code}"/>
+                        </DataGridTextColumn.Header>
+                    </DataGridTextColumn>
+                    <DataGridTextColumn Width="*" Binding="{Binding Info}">
+                        <DataGridTextColumn.Header>
+                            <TextBlock Text="{DynamicResource Alm.Info}"/>
+                        </DataGridTextColumn.Header>
+                    </DataGridTextColumn>
+                    <DataGridTextColumn Width="*" Binding="{Binding Time}">
+                        <DataGridTextColumn.Header>
+                            <TextBlock Text="{DynamicResource Alm.Time}"/>
+                        </DataGridTextColumn.Header>
+                    </DataGridTextColumn>
+                    <!--<DataGridTemplateColumn Width="100" IsReadOnly="True" CanUserResize="False">
+                        <DataGridTemplateColumn.Header>
+                            <TextBlock Text="{DynamicResource App.Opt}"/>
+                        </DataGridTemplateColumn.Header>
+                        <DataGridTemplateColumn.CellTemplate>
+                            <DataTemplate>
+                                <StackPanel Orientation="Horizontal">
+                                    <Button x:Name="editBtn" Style="{StaticResource EditLink}" 
+                            Margin="0,0,10,0" 
+                            Click="Button_Click"
+                            DataContext="{Binding }"/>
+                                    <Button x:Name="delBtn" Style="{StaticResource DeleteLink}" 
+                            DataContext="{Binding }"
+                            Click="Button_Click"/>
+                                </StackPanel>
+                            </DataTemplate>
+                        </DataGridTemplateColumn.CellTemplate>
+                    </DataGridTemplateColumn>-->
+                </DataGrid.Columns>
+            </DataGrid>
+            
+        </DockPanel>
+    </Border>
+</hb:PluginControl>
diff --git a/HMI.AlermInfoManage/SimpleAlarm.xaml.cs b/HMI.AlermInfoManage/SimpleAlarm.xaml.cs
new file mode 100644
index 0000000..f323f1d
--- /dev/null
+++ b/HMI.AlermInfoManage/SimpleAlarm.xaml.cs
@@ -0,0 +1,100 @@
+锘縰sing HMI.Base;
+using HMI.Core.Util;
+using System;
+using System.IO;
+using System.Timers;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace HMI.AlarmInfoManage
+{
+    /// <summary>
+    /// UserControl1.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class SimpleAlarm : PluginControl
+    {
+        private string dbPath;
+        private SqliteHelper sqliteHelper;
+        private Timer MonitorTimer;
+
+        public SimpleAlarm()
+        {
+            DataContext = this;
+            InitializeComponent();
+        }
+
+        public override void InitFinish()
+        {
+            dbPath = Path.Combine(BaseFolder, "alarm.db");
+            if (!File.Exists(dbPath))
+            {
+                SqliteHelper.CreateDatabase(dbPath);
+                sqliteHelper = new SqliteHelper(dbPath);
+                sqliteHelper.ExecuteSql(@"CREATE TABLE IF NOT EXISTS Alarm (
+                    id INTEGER PRIMARY KEY AUTOINCREMENT,
+                    source TEXT NOT NULL,
+                    alarmcode TEXT,
+                    alarminfo TEXT,
+                    time DATETIME);");
+            }
+        }
+
+        private void Button_Click(object sender, RoutedEventArgs e)
+        {
+            Button btn = sender as Button;
+            switch (btn.Name)
+            {
+                case "refreshBtn":
+                    //Model.
+                    break;
+                case "exportBtn":
+                    //ImportText();
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        private void UserControl_Loaded(object sender, RoutedEventArgs e)
+        {
+            MonitorTimer = new Timer
+            {
+                Interval = 100,
+            };
+            MonitorTimer.Elapsed += MonitorTimer_Elapsed;
+
+            Loaded -= UserControl_Loaded;
+        }
+
+
+        private void MonitorTimer_Elapsed(object sender, ElapsedEventArgs e)
+        {
+            try
+            {
+                this.MonitorTimer.Enabled = false;
+                this.RefershAlarm();
+            }
+            catch (Exception ex)
+            {
+                log.Error("sdadsa", ex);
+            }
+            finally
+            {
+                this.MonitorTimer.Enabled = true;
+            }
+        }
+
+        /// <summary>
+        /// 鍒锋柊鎶ヨ淇℃伅
+        /// </summary>
+        private void RefershAlarm()
+        {
+            
+        }
+
+        public override void SetBaseFolder(string folder)
+        {
+            base.SetBaseFolder(folder);
+        }
+    }
+}
diff --git a/HMI.Base/HMI.Base.csproj b/HMI.Base/HMI.Base.csproj
index 5203494..676e63f 100644
--- a/HMI.Base/HMI.Base.csproj
+++ b/HMI.Base/HMI.Base.csproj
@@ -72,6 +72,8 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="DtConverter.cs" />
+    <Compile Include="PluginMaskControl.cs" />
+    <Compile Include="SimpleControl.cs" />
     <Compile Include="PluginControl.cs" />
     <Compile Include="PluginInfo.cs" />
     <Compile Include="PluginLoader.cs" />
@@ -88,6 +90,7 @@
       <DependentUpon>Settings.settings</DependentUpon>
       <DesignTimeSharedInput>True</DesignTimeSharedInput>
     </Compile>
+    <Compile Include="SimpleMaskControl.cs" />
     <EmbeddedResource Include="Properties\Resources.resx">
       <Generator>ResXFileCodeGenerator</Generator>
       <LastGenOutput>Resources.Designer.cs</LastGenOutput>
@@ -103,6 +106,20 @@
       <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>
+  <ItemGroup>
+    <Page Include="MaskControl.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="Themes\Generic.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
   </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
 </Project>
\ No newline at end of file
diff --git a/HMI.Base/MaskControl.xaml b/HMI.Base/MaskControl.xaml
new file mode 100644
index 0000000..7e58c72
--- /dev/null
+++ b/HMI.Base/MaskControl.xaml
@@ -0,0 +1,32 @@
+锘�<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI"
+             xmlns:local="clr-namespace:HMI.Base">
+    <Style x:Key="simpleMaskControl" TargetType="local:SimpleMaskControl">
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate TargetType="local:SimpleMaskControl">
+                    <Grid>
+                        <ContentPresenter Content="{TemplateBinding Content}"/>
+
+                        <ui:MaskControl x:Name="mask"/>
+                    </Grid>
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
+
+    <Style x:Key="pluginMaskControl" TargetType="local:PluginMaskControl">
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate TargetType="local:PluginMaskControl">
+                    <Grid>
+                        <ContentPresenter Content="{TemplateBinding Content}"/>
+
+                        <ui:MaskControl x:Name="mask"/>
+                    </Grid>
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
+</ResourceDictionary>
\ No newline at end of file
diff --git a/HMI.Base/PluginLoader.cs b/HMI.Base/PluginLoader.cs
index e63503a..f295e9d 100644
--- a/HMI.Base/PluginLoader.cs
+++ b/HMI.Base/PluginLoader.cs
@@ -4,7 +4,6 @@
 using log4net;
 using System;
 using System.Collections.Generic;
-using System.Data;
 using System.IO;
 using System.Reflection;
 using System.Threading;
@@ -31,7 +30,6 @@
         private static ILog log = LogManager.GetLogger("PluginLoader");
         private static string PluginFolder = PathHelper.PluginFolder();
 
-        public bool Completed { get; private set; } = false;
         public SqliteHelper sqliteHelper;
         public List<PluginInfo> Menus { get; private set; }
         public List<PluginInfo> Plugins { get; private set; }
@@ -50,6 +48,7 @@
             {
                 Instance = null;
             }
+            GC.Collect();
         }
 
         public static PluginLoader GetLoader()
@@ -76,6 +75,12 @@
                     LoadMessageAppend?.Invoke(this, new LoadMsg { Type = LoadMsgType.FINISHED });
                 });
             }
+            pluginContainer.DoCloseEvent += PluginContainer_DoCloseEvent;
+        }
+
+        private void PluginContainer_DoCloseEvent(object sender, EventArgs e)
+        {
+            Reset();
         }
 
         private void LoadChildrenMenu()
@@ -83,7 +88,7 @@
             foreach (var menu in Plugins)
             {
                 CountdownEvent c = new CountdownEvent(1);
-                Thread.Sleep(80);
+                Thread.Sleep(50);
                 Application.Current.Dispatcher.Invoke(new Action(() =>
                 {
                     try
@@ -158,21 +163,39 @@
             }
         }
 
+        private PluginInfo ConvertPermission2PluginInfo(PermissionData data)
+        {
+            if (data.Type == "鏉冮檺") return null;
+            PluginInfo info = new PluginInfo
+            {
+                Code = data.Code,
+                Name = data.Name,
+                Type = data.Type,
+                ParentCode = data.ParentCode,
+                DllPath = data.DllPath,
+                ClassName = data.ClassName,
+                IconFont = data.IconFont
+                //Icon = new BitmapImage(new Uri(Path.Combine(PathHelper.IconFolder, dr["iconPath"].ToString())))
+            };
+            if (data.Children != null && data.Children.Count > 0)
+            {
+                foreach (var child in data.Children)
+                {
+                    var childInfo = ConvertPermission2PluginInfo(child);
+                    if (childInfo != null) info.Children.Add(childInfo);
+                }
+            }
+            return info;
+        }
+
         public List<PluginInfo> LoadTreeMenu(Dispatcher dispatcher)
         {
-            //LoadMessageAppend?.Invoke(this, "寮�濮嬪姞杞借彍鍗�...");
-            List<PluginInfo> data = new List<PluginInfo>();
+            List<PluginInfo> menus = new List<PluginInfo>();
             Dictionary<string, PluginInfo> cache = new Dictionary<string, PluginInfo>();
             try
             {
-                UserInfo user = UserInfo.User;
-                string sql = $@"SELECT DISTINCT m.* FROM menu AS m INNER JOIN role_menu_rel AS rrel ON m.code = rrel.menuCode
-INNER JOIN role AS r ON rrel.roleCode = r.code
-INNER JOIN user_role_rel AS urel ON r.code = urel.roleCode
-AND urel.username = '{user.Username}' ORDER BY m.level,m.sort, m.id;";
-
-                DataTable dt = sqliteHelper.GetDataTable(sql);
-                if (dt != null)
+                List<PermissionData> allPermissions = UserInfo.User?.AllPermissions;
+                if (allPermissions != null)
                 {
                     Application.Current.Dispatcher.Invoke(() =>
                     {
@@ -180,28 +203,27 @@
                           new LoadMsg
                           {
                               Type = LoadMsgType.INIT,
-                              NumData = dt.Rows.Count,
-                              Message = this.lang("App.MenuCount", dt.Rows.Count),
+                              NumData = allPermissions.Count,
+                              Message = this.lang("App.MenuCount", allPermissions.Count),
                           });
 
-                        foreach (DataRow dr in dt.Rows)
+                        foreach (PermissionData data in allPermissions)
                         {
-                            string path = Path.Combine(PathHelper.IconFolder, dr["iconPath"].ToString());
+                            if (data.Type == "Permission") continue;
                             var menu = new PluginInfo
                             {
-                                Code = dr["code"].ToString(),
-                                Name = dr["name"].ToString(),
-                                Type = dr["type"].ToString(),
-                                ParentCode = dr["parentCode"].ToString(),
-                                DllPath = dr["dllPath"].ToString(),
-                                ClassName = dr["className"].ToString(),
-                                IconFont = dr["icon"].ToString()
-                                //Icon = new BitmapImage(new Uri(Path.Combine(PathHelper.IconFolder, dr["iconPath"].ToString())))
+                                Code = data.Code,
+                                Name = data.Name,
+                                Type = data.Type,
+                                ParentCode = data.ParentCode,
+                                DllPath = data.DllPath,
+                                ClassName = data.ClassName,
+                                IconFont = data.IconFont
                             };
                             cache[menu.Code] = menu;
                             if (!cache.ContainsKey(menu.ParentCode))
                             {
-                                data.Add(menu);
+                                menus.Add(menu);
                             }
                             else
                             {
@@ -217,7 +239,7 @@
             {
                 //LoadMessageAppend?.Invoke(this, ex.Message);
             }
-            return data;
+            return menus;
         }
     }
 }
diff --git a/HMI.Base/PluginMaskControl.cs b/HMI.Base/PluginMaskControl.cs
new file mode 100644
index 0000000..21be635
--- /dev/null
+++ b/HMI.Base/PluginMaskControl.cs
@@ -0,0 +1,89 @@
+锘縰sing System;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Windows.Controls;
+using HMI.Core.Api;
+using HMI.UI;
+using log4net;
+
+namespace HMI.Base
+{
+    public class PluginMaskControl : UserControl, IPlugin, INotifyPropertyChanged
+    {
+        #region ...
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        protected void Set([CallerMemberName] string name = null)
+        {
+
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+        }
+
+        protected void Set<T>(ref T old, T @new, [CallerMemberName] string propertyName = null)
+        {
+            old = @new;
+            if (propertyName != null)
+            {
+                Set(propertyName);
+            }
+        }
+        #endregion
+
+        protected ILog log;
+        protected string BaseFolder;
+
+        public virtual void Active()
+        {
+
+        }
+
+        public virtual void DeActive()
+        {
+
+        }
+
+        public virtual void Init()
+        {
+
+        }
+
+        public virtual void OnTabChange(object sender, object args)
+        {
+
+        }
+
+        public virtual void SetBaseFolder(string folder)
+        {
+            BaseFolder = folder;
+        }
+
+        public virtual void SetLogger(ILog logger)
+        {
+            log = logger;
+        }
+
+        public virtual void InitFinish()
+        {
+
+        }
+
+        public virtual void OnClose(object sender, CancelEventArgs e)
+        {
+
+        }
+
+        public virtual void DoClose(object sender, EventArgs e)
+        {
+
+        }
+
+        public void ShowMask(Action action, Action<Exception> reject = null, string title = null, string content = null)
+        {
+            MaskControl mask = this.Template.FindName("mask", this) as MaskControl;
+            if (mask != null)
+            {
+                mask.ShowMask(this, action, reject, title, content);
+            }
+        }
+    }
+}
diff --git a/HMI.Base/SimpleControl.cs b/HMI.Base/SimpleControl.cs
new file mode 100644
index 0000000..c5f5414
--- /dev/null
+++ b/HMI.Base/SimpleControl.cs
@@ -0,0 +1,59 @@
+锘縰sing System;
+using System.ComponentModel;
+using System.Windows.Controls;
+using HMI.Core.Api;
+using log4net;
+
+namespace HMI.Base
+{
+    public class SimpleControl : UserControl, IPlugin
+    {
+        protected ILog log;
+        protected string BaseFolder;
+
+        public virtual void Active()
+        {
+
+        }
+
+        public virtual void DeActive()
+        {
+
+        }
+
+        public virtual void Init()
+        {
+
+        }
+
+        public virtual void OnTabChange(object sender, object args)
+        {
+
+        }
+
+        public virtual void SetBaseFolder(string folder)
+        {
+            BaseFolder = folder;
+        }
+
+        public virtual void SetLogger(ILog logger)
+        {
+            log = logger;
+        }
+
+        public virtual void InitFinish()
+        {
+
+        }
+
+        public virtual void OnClose(object sender, CancelEventArgs e)
+        {
+
+        }
+
+        public virtual void DoClose(object sender, EventArgs e)
+        {
+
+        }
+    }
+}
diff --git a/HMI.Base/SimpleMaskControl.cs b/HMI.Base/SimpleMaskControl.cs
new file mode 100644
index 0000000..c0a3d3f
--- /dev/null
+++ b/HMI.Base/SimpleMaskControl.cs
@@ -0,0 +1,79 @@
+锘縰sing HMI.Core.Api;
+using HMI.UI;
+using log4net;
+using System;
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace HMI.Base
+{
+    /// <summary>
+    /// PluginMaskControl.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class SimpleMaskControl : UserControl, IPlugin
+    {
+        static SimpleMaskControl()
+        {
+            DefaultStyleKeyProperty.OverrideMetadata(typeof(SimpleMaskControl),
+                new FrameworkPropertyMetadata(typeof(SimpleMaskControl)));
+        }
+
+        protected ILog log;
+        protected string BaseFolder;
+
+        public virtual void Active()
+        {
+
+        }
+
+        public virtual void DeActive()
+        {
+
+        }
+
+        public virtual void Init()
+        {
+
+        }
+
+        public virtual void OnTabChange(object sender, object args)
+        {
+
+        }
+
+        public virtual void SetBaseFolder(string folder)
+        {
+            BaseFolder = folder;
+        }
+
+        public virtual void SetLogger(ILog logger)
+        {
+            log = logger;
+        }
+
+        public virtual void InitFinish()
+        {
+
+        }
+
+        public virtual void OnClose(object sender, CancelEventArgs e)
+        {
+
+        }
+
+        public virtual void DoClose(object sender, EventArgs e)
+        {
+
+        }
+
+        public void ShowMask(Action action, Action<Exception> reject = null, string title = null, string content = null)
+        {
+            MaskControl mask = this.Template.FindName("mask", this) as MaskControl;
+            if (mask != null)
+            {
+                mask.ShowMask(this, action, reject, title, content);
+            }
+        }
+    }
+}
diff --git a/HMI.Base/Themes/Generic.xaml b/HMI.Base/Themes/Generic.xaml
new file mode 100644
index 0000000..d220045
--- /dev/null
+++ b/HMI.Base/Themes/Generic.xaml
@@ -0,0 +1,32 @@
+锘�<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+                    xmlns:local="clr-namespace:HMI.Base"
+                    xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI">
+    <Style TargetType="local:SimpleMaskControl">
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate TargetType="local:SimpleMaskControl">
+                    <Grid Background="{TemplateBinding Background}">
+                        <ContentPresenter Content="{TemplateBinding Content}"/>
+
+                        <ui:MaskControl x:Name="mask"/>
+                    </Grid>
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
+
+    <Style TargetType="local:PluginMaskControl">
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate TargetType="local:PluginMaskControl">
+                    <Grid>
+                        <ContentPresenter Content="{TemplateBinding Content}"/>
+
+                        <ui:MaskControl x:Name="mask"/>
+                    </Grid>
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
+</ResourceDictionary>
\ No newline at end of file
diff --git a/HMI.Core/Api/AlarmShowHelper.cs b/HMI.Core/Api/AlarmShowHelper.cs
new file mode 100644
index 0000000..172a9ef
--- /dev/null
+++ b/HMI.Core/Api/AlarmShowHelper.cs
@@ -0,0 +1,24 @@
+锘縩amespace HMI.Core.Api
+{
+    public class AlarmShowHelper
+    {
+        private static IAlarmShow shower;
+
+        public static void Register(IAlarmShow shower)
+        {
+            if (AlarmShowHelper.shower == null)
+            {
+                AlarmShowHelper.shower = shower;
+            }
+        }
+
+        public static bool ShowAlarm(string message)
+        {
+            if (shower != null)
+            {
+                return shower.ShowAlarm(message);
+            }
+            return false;
+        }
+    }
+}
diff --git a/HMI.Core/Api/IAlarmShow.cs b/HMI.Core/Api/IAlarmShow.cs
new file mode 100644
index 0000000..65c2243
--- /dev/null
+++ b/HMI.Core/Api/IAlarmShow.cs
@@ -0,0 +1,13 @@
+锘縰sing System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace HMI.Core.Api
+{
+    public interface IAlarmShow
+    {
+        bool ShowAlarm(string message);
+    }
+}
diff --git a/HMI.Core/DpUtil/PermissionHelper.cs b/HMI.Core/DpUtil/PermissionHelper.cs
new file mode 100644
index 0000000..cfcaec3
--- /dev/null
+++ b/HMI.Core/DpUtil/PermissionHelper.cs
@@ -0,0 +1,47 @@
+锘縰sing HMI.Core.Entity;
+using System.Linq;
+using System.Windows;
+
+namespace HMI.Core.DpUtil
+{
+    public class PermissionHelper
+    {
+        public static string GetRequiredPermission(UIElement element)
+        {
+            return (string)element.GetValue(RequiredPermissionProperty);
+        }
+
+        public static void SetRequiredPermission(UIElement element, string requiredPermission)
+        {
+            element.SetValue(RequiredPermissionProperty, requiredPermission);
+        }
+
+        public static readonly DependencyProperty RequiredPermissionProperty =
+            DependencyProperty.RegisterAttached("RequiredPermission", typeof(string), typeof(UIElement),
+            new PropertyMetadata(string.Empty, OnRequiredPermissionChanged));
+
+        private static void OnRequiredPermissionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            if (d is UIElement element)
+            {
+                if (e.Property == RequiredPermissionProperty)
+                {
+                    var requiredPermission = (string)e.NewValue;
+                    if (string.IsNullOrEmpty(requiredPermission)) return;
+                    if (UserInfo.User == null)
+                    {
+                        element.Visibility = Visibility.Collapsed; 
+                        return;
+                    }
+                    if (UserInfo.User?.AllPermissions == null)
+                    {
+                        element.Visibility = Visibility.Collapsed;
+                        return;
+                    }
+                    if (UserInfo.User.AllPermissions.FirstOrDefault(p => p.Code == requiredPermission) != null) return;
+                    element.Visibility = Visibility.Collapsed;
+                }
+            }
+        }
+    }
+}
diff --git a/HMI.Core/Entity/PermissionData.cs b/HMI.Core/Entity/PermissionData.cs
new file mode 100644
index 0000000..905a8a7
--- /dev/null
+++ b/HMI.Core/Entity/PermissionData.cs
@@ -0,0 +1,24 @@
+锘縰sing System.Collections.Generic;
+
+namespace HMI.Core.Entity
+{
+    public class PermissionData
+    {
+        public string Id { get; set; }
+        public string Code { get; set; }
+        public string Name { get; set; }
+        public string IconPath { get; set; }
+        public string Icon { get; set; }
+        public string Level { get; set; }
+        public string Type { get; set; }
+        public string ParentCode { get; set; }
+        public string ParentName { get; set; }
+        public string DllPath { get; set; }
+        public string ClassName { get; set; }
+        public string IconFont { get; set; }
+
+        public double? Sort { get; set; }
+
+        public List<PermissionData> Children { get; set; } = new List<PermissionData>();
+    }
+}
diff --git a/HMI.Core/Entity/UserInfo.cs b/HMI.Core/Entity/UserInfo.cs
index c9b644d..49d8d31 100644
--- a/HMI.Core/Entity/UserInfo.cs
+++ b/HMI.Core/Entity/UserInfo.cs
@@ -1,15 +1,22 @@
-锘縩amespace HMI.Core.Entity
+锘縰sing System.Collections.Generic;
+
+namespace HMI.Core.Entity
 {
     public class UserInfo
     {
         public static UserInfo User { get; private set; }
-        public UserInfo(string id, string code, string username, string name, string password = null)
+        public UserInfo(string id, string code, string username, string name,
+            List<PermissionData> permissions,
+            List<PermissionData> allPermissions,
+            string password = null)
         {
             Id = id;
             Code = code;
             Username = username;
             Name = name;
             Password = password;
+            Permissions = permissions;
+            AllPermissions = allPermissions;
         }
 
         public string Id { get; private set; }
@@ -17,6 +24,8 @@
         public string Username { get; private set; }
         public string Name { get; private set; }
         public string Password { get; private set; }
+        public List<PermissionData> Permissions { get; private set; }
+        public List<PermissionData> AllPermissions { get; private set; }
 
         public static void Login(UserInfo info)
         {
diff --git a/HMI.Core/HMI.Core.csproj b/HMI.Core/HMI.Core.csproj
index 600e655..43134dc 100644
--- a/HMI.Core/HMI.Core.csproj
+++ b/HMI.Core/HMI.Core.csproj
@@ -87,6 +87,8 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="Annotation\ExcelColumn.cs" />
+    <Compile Include="Api\AlarmShowHelper.cs" />
+    <Compile Include="Api\IAlarmShow.cs" />
     <Compile Include="Api\IConfigManager.cs" />
     <Compile Include="Api\IConfigRefresh.cs" />
     <Compile Include="Api\IFolderOwner.cs" />
@@ -95,9 +97,11 @@
     <Compile Include="Api\IPluginContainer.cs" />
     <Compile Include="Annotation\InitAttribute.cs" />
     <Compile Include="Api\PluginPackage.cs" />
+    <Compile Include="DpUtil\PermissionHelper.cs" />
     <Compile Include="Entity\LangInfo.cs" />
     <Compile Include="Entity\NotifyBase.cs" />
     <Compile Include="Entity\ParameterData.cs" />
+    <Compile Include="Entity\PermissionData.cs" />
     <Compile Include="Entity\ProductData.cs" />
     <Compile Include="Entity\SelectBase.cs" />
     <Compile Include="Entity\TestItemData.cs" />
@@ -125,7 +129,6 @@
     <Compile Include="Util\XmlHelper.cs" />
   </ItemGroup>
   <ItemGroup>
-    <None Include="app.config" />
     <None Include="packages.config" />
   </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
diff --git a/HMI.Core/Util/MSMQHelper.cs b/HMI.Core/Util/MSMQHelper.cs
index 3e331c6..41dcf48 100644
--- a/HMI.Core/Util/MSMQHelper.cs
+++ b/HMI.Core/Util/MSMQHelper.cs
@@ -87,7 +87,7 @@
             {
                 MessageID = string.Empty;
                 MsgString = string.Empty;
-                TimeSpan Time = new TimeSpan(TimeOut*10000);
+                TimeSpan Time = new TimeSpan(TimeOut);
                 Message MsgReceived = null;
                 try
                 {
diff --git a/HMI.Core/app.config b/HMI.Core/app.config
deleted file mode 100644
index d93dea3..0000000
--- a/HMI.Core/app.config
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<configuration>
-  <runtime>
-    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
-      <dependentAssembly>
-        <assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
-        <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
-      </dependentAssembly>
-      <dependentAssembly>
-        <assemblyIdentity name="System.ComponentModel.Annotations" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
-        <bindingRedirect oldVersion="0.0.0.0-4.2.1.0" newVersion="4.2.1.0"/>
-      </dependentAssembly>
-      <dependentAssembly>
-        <assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
-        <bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0"/>
-      </dependentAssembly>
-    </assemblyBinding>
-  </runtime>
-<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/></startup></configuration>
diff --git a/HMI.DataViewer/DataViewer.xaml b/HMI.DataViewer/DataViewer.xaml
index f5ef14b..ae17303 100644
--- a/HMI.DataViewer/DataViewer.xaml
+++ b/HMI.DataViewer/DataViewer.xaml
@@ -16,7 +16,6 @@
     <UserControl.Resources>
         <ResourceDictionary>
             <ResourceDictionary.MergedDictionaries>
-                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonDataGrid.xaml"/>
             </ResourceDictionary.MergedDictionaries>
         </ResourceDictionary>
     </UserControl.Resources>
diff --git a/HMI.IOMonitor/HMI.IOMonitor.csproj b/HMI.IOMonitor/HMI.IOMonitor.csproj
index cc932b2..9600002 100644
--- a/HMI.IOMonitor/HMI.IOMonitor.csproj
+++ b/HMI.IOMonitor/HMI.IOMonitor.csproj
@@ -131,7 +131,6 @@
       <Generator>ResXFileCodeGenerator</Generator>
       <LastGenOutput>Resources.Designer.cs</LastGenOutput>
     </EmbeddedResource>
-    <None Include="app.config" />
     <None Include="packages.config" />
     <None Include="Properties\Settings.settings">
       <Generator>SettingsSingleFileGenerator</Generator>
diff --git a/HMI.IOMonitor/app.config b/HMI.IOMonitor/app.config
deleted file mode 100644
index a0cefc2..0000000
--- a/HMI.IOMonitor/app.config
+++ /dev/null
@@ -1,11 +0,0 @@
-锘�<?xml version="1.0" encoding="utf-8"?>
-<configuration>
-  <runtime>
-    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
-      <dependentAssembly>
-        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
-        <bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" />
-      </dependentAssembly>
-    </assemblyBinding>
-  </runtime>
-</configuration>
\ No newline at end of file
diff --git a/HMI.LogViewer/LogViewer.xaml b/HMI.LogViewer/LogViewer.xaml
index 86632eb..52a68ba 100644
--- a/HMI.LogViewer/LogViewer.xaml
+++ b/HMI.LogViewer/LogViewer.xaml
@@ -2,21 +2,20 @@
              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:d="http://schemas.microsoft.com/expression/blend/2008"
              xmlns:local="clr-namespace:HMI.LogViewer"
+             mc:Ignorable="d" 
+             xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
              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/PanuonDataGrid.xaml"/>
             </ResourceDictionary.MergedDictionaries>
         </ResourceDictionary>
     </UserControl.Resources>
diff --git a/HMI.LogViewer/LogViewer.xaml.cs b/HMI.LogViewer/LogViewer.xaml.cs
index bc54659..da25941 100644
--- a/HMI.LogViewer/LogViewer.xaml.cs
+++ b/HMI.LogViewer/LogViewer.xaml.cs
@@ -1,7 +1,6 @@
 锘縰sing HMI.Base;
 using HMI.Core.Util;
 using HMI.UI.Util;
-using log4net.Core;
 using System;
 using System.Collections.Generic;
 using System.Data;
@@ -10,7 +9,6 @@
 using System.Timers;
 using System.Windows;
 using System.Windows.Controls;
-using static System.Net.Mime.MediaTypeNames;
 
 namespace HMI.LogViewer
 {
diff --git a/HMI.MenuManage/MenuManage.xaml b/HMI.MenuManage/MenuManage.xaml
index 269e70e..e5a5b7b 100644
--- a/HMI.MenuManage/MenuManage.xaml
+++ b/HMI.MenuManage/MenuManage.xaml
@@ -16,11 +16,7 @@
              Background="{StaticResource WorkspaceBackground}"
              d:DesignHeight="450" d:DesignWidth="800">
     <UserControl.Resources>
-        <ResourceDictionary>
-            <ResourceDictionary.MergedDictionaries>
-                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonDataGrid.xaml"/>
-            </ResourceDictionary.MergedDictionaries>
-        </ResourceDictionary>
+        
     </UserControl.Resources>
     <Grid>
         <Grid.ColumnDefinitions>
diff --git a/HMI.MenuManage/MenuManage.xaml.cs b/HMI.MenuManage/MenuManage.xaml.cs
index fa38a3a..b812ce4 100644
--- a/HMI.MenuManage/MenuManage.xaml.cs
+++ b/HMI.MenuManage/MenuManage.xaml.cs
@@ -3,9 +3,13 @@
 using HMI.MenuManage.Entity;
 using HMI.MenuManage.Model;
 using HMI.UI.Util;
+using log4net;
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.ComponentModel;
+using System.Data;
+using System.IO;
 using System.Linq;
 using System.Windows;
 using System.Windows.Controls;
@@ -18,14 +22,120 @@
     public partial class MenuManage : PluginControl
     {
         #region 灞炴��
-        private MenuManageModel Model;
+        public SqliteHelper sqliteHelper;
+
+        public int DataSourceCount { get => DataSource?.Count ?? 0; }
+
+        private ObservableCollection<MenuData> _DataSource;
+
+        public ObservableCollection<MenuData> DataSource
+        {
+            get => _DataSource;
+            set
+            {
+                Set(ref _DataSource, value);
+                Set(nameof(DataSourceCount));
+            }
+        }
+
+        private ObservableCollection<MenuData> _TreeDataSource;
+
+        public ObservableCollection<MenuData> TreeDataSource
+        {
+            get => _TreeDataSource;
+            set { Set(ref _TreeDataSource, value); }
+        }
         #endregion
+
+        public static ILog PluginLog { get; private set; }
 
         public MenuManage()
         {
-            Model = new MenuManageModel();
-            this.DataContext = Model;
+            this.DataContext = this;
             InitializeComponent();
+            if (!File.Exists(PathHelper.DatabasePath))
+            {
+                SqliteHelper.CreateDatabase(PathHelper.DatabasePath);
+            }
+            sqliteHelper = new SqliteHelper(PathHelper.DatabasePath);
+            sqliteHelper.ExecuteSql(@"CREATE TABLE IF NOT EXISTS menu (
+                    id INTEGER PRIMARY KEY AUTOINCREMENT,
+                    code TEXT NOT NULL,
+                    name TEXT NOT NULL,
+                    icon TEXT,
+                    iconPath TEXT,
+                    level TEXT,
+                    type TEXT,
+                    parentCode TEXT,
+                    dllPath TEXT,
+                    className TEXT,
+                    sort REAL,
+                    UNIQUE(code)
+                    );");
+        }
+
+        public override void SetLogger(ILog logger)
+        {
+            base.SetLogger(logger);
+            PluginLog = logger;
+        }
+
+        public void LoadData()
+        {
+            ObservableCollection<MenuData> data = new ObservableCollection<MenuData>();
+            ObservableCollection<MenuData> treeData = new ObservableCollection<MenuData>();
+            DataTable dt = sqliteHelper.GetDataTable("SELECT m1.*,m2.name AS parentName FROM menu AS m1 LEFT JOIN menu AS m2 ON m1.parentCode = m2.code Order By m1.sort,m1.id;");
+            Dictionary<string, MenuData> cache = new Dictionary<string, MenuData>();
+            if (dt != null)
+            {
+                foreach (DataRow dr in dt.Rows)
+                {
+                    var menu = new MenuData
+                    {
+                        Id = dr["id"].ToString(),
+                        Code = dr["code"].ToString(),
+                        Name = dr["name"].ToString(),
+                        Icon = dr["icon"].ToString(),
+                        Level = dr["level"].ToString(),
+                        Type = dr["type"].ToString(),
+                        ParentCode = dr["parentCode"].ToString(),
+                        ParentName = dr["parentName"].ToString(),
+                        DllPath = dr["dllPath"].ToString(),
+                        ClassName = dr["className"].ToString(),
+                        Sort = dr["sort"] == DBNull.Value ? 0 : double.Parse(dr["sort"].ToString()),
+                    };
+                    data.Add(menu);
+                    if (menu.Level == "1")
+                    {
+                        treeData.Add(menu);
+                    }
+                    cache.Add(menu.Code, menu);
+                }
+
+                foreach (var menu in data)
+                {
+                    if (!string.IsNullOrEmpty(menu.ParentCode) && menu.ParentCode != menu.Code)
+                    {
+                        cache[menu.ParentCode].Children.Add(menu);
+                    }
+                }
+            }
+
+            TreeDataSource = treeData;
+            DataSource = data;
+        }
+
+        public void DeleteMenuById(string id)
+        {
+            string sql = $"DELETE FROM menu WHERE id = {id};";
+            sqliteHelper.ExecuteSql(sql);
+        }
+
+        public void DeleteMenuByIds(IEnumerable<string> ids)
+        {
+            List<string> sqls = ids.Select(id => $"DELETE FROM menu WHERE id = {id};")
+                .ToList();
+            sqliteHelper.ExecuteBatchSql(sqls);
         }
 
         private void Button_Click(object sender, RoutedEventArgs e)
@@ -38,13 +148,13 @@
                     MenuModal window = new MenuModal
                     {
                         Title = $"{LangUtil.Get("HMI.Menu.Add")}",
-                        sqliteHelper = Model.sqliteHelper,
+                        sqliteHelper = sqliteHelper,
                         Owner = Window.GetWindow(this)
                     };
                     if (true == window.Open(new MenuData()))
                     {
                         MenuData data = window.Data;
-                        Model.LoadData();
+                        LoadData();
                     }
                 }
                 catch (Exception ex)
@@ -57,7 +167,7 @@
             {
                 try
                 {
-                    Model.LoadData();
+                    LoadData();
                 }
                 catch (Exception ex)
                 {
@@ -69,7 +179,7 @@
             {
                 try
                 {
-                    List<string> ids = Model.DataSource.Where(x => x.Selected).Select(x => x.Id).ToList();
+                    List<string> ids = DataSource.Where(x => x.Selected).Select(x => x.Id).ToList();
                     if (ids == null || ids.Count <= 0)
                     {
                         ToastHelper.Warning(Window.GetWindow(this), LangUtil.Get("HMI.NoDeleteData"));
@@ -80,8 +190,8 @@
                         string.Format(LangUtil.Get("HMI.MakeSureDeletes"), ids.Count));
                     if (res == MessageBoxResult.OK)
                     {
-                        Model.DeleteMenuByIds(ids);
-                        Model.LoadData();
+                        DeleteMenuByIds(ids);
+                        LoadData();
                     }
                 }
                 catch (Exception ex)
@@ -98,13 +208,13 @@
                     MenuModal window = new MenuModal
                     {
                         Title = $"{LangUtil.Get("HMI.Menu.Edit")}",
-                        sqliteHelper = Model.sqliteHelper,
+                        sqliteHelper = sqliteHelper,
                         Owner = Window.GetWindow(this)
                     };
                     if (true == window.Open(data))
                     {
                         var newData = window.Data;
-                        Model.LoadData();
+                        LoadData();
                     }
                 }
                 catch (Exception ex)
@@ -122,8 +232,8 @@
                         string.Format(LangUtil.Get("HMI.MakeSureDeleteSingle"), data.Name));
                     if (res == MessageBoxResult.OK)
                     {
-                        Model.DeleteMenuById(data.Id);
-                        Model.LoadData();
+                        DeleteMenuById(data.Id);
+                        LoadData();
                     }
                 }
                 catch (Exception ex)
@@ -138,7 +248,7 @@
         {
             try
             {
-                Model?.sqliteHelper?.Close();
+                sqliteHelper?.Close();
             }
             catch (Exception ex)
             {
@@ -148,7 +258,7 @@
 
         private void DataGrid_Loaded(object sender, RoutedEventArgs e)
         {
-            Model.LoadData();
+            LoadData();
             Loaded -= DataGrid_Loaded;
         }
 
diff --git a/HMI.MenuManage/Modal/MenuModal.xaml b/HMI.MenuManage/Modal/MenuModal.xaml
index ce33c4e..a919f39 100644
--- a/HMI.MenuManage/Modal/MenuModal.xaml
+++ b/HMI.MenuManage/Modal/MenuModal.xaml
@@ -55,8 +55,9 @@
             </pu:FormGroup>
 
             <pu:FormGroup x:Name="FgType"
-                          Style="{StaticResource InputFormGroupStyle}" Header="{DynamicResource HMI.Menu.Type}" GroupName="MyFormGroup">
+  Style="{StaticResource InputFormGroupStyle}" Header="{DynamicResource HMI.Menu.Type}" GroupName="MyFormGroup">
                 <ComboBox x:Name="TbType" Style="{StaticResource FormComboxStyle}">
+                    <ComboBoxItem>Permission</ComboBoxItem>
                     <ComboBoxItem>WPF</ComboBoxItem>
                     <ComboBoxItem>WinForm</ComboBoxItem>
                     <ComboBoxItem>LV</ComboBoxItem>
diff --git a/HMI.MenuManage/Modal/MenuModal.xaml.cs b/HMI.MenuManage/Modal/MenuModal.xaml.cs
index ad3953c..35d2ffb 100644
--- a/HMI.MenuManage/Modal/MenuModal.xaml.cs
+++ b/HMI.MenuManage/Modal/MenuModal.xaml.cs
@@ -64,7 +64,8 @@
                 }
                 catch (Exception ex)
                 {
-                    ToastHelper.Error(this, "Error锛�");
+                    MenuManage.PluginLog.Error("Modal Commit Error.", ex);
+                    ToastHelper.Error(this, ex.Message);
                     e.Cancel = true;
                     return;
                 }
diff --git a/HMI.MenuManage/Modal/RoleModal.xaml.cs b/HMI.MenuManage/Modal/RoleModal.xaml.cs
index 91699e9..0b8f823 100644
--- a/HMI.MenuManage/Modal/RoleModal.xaml.cs
+++ b/HMI.MenuManage/Modal/RoleModal.xaml.cs
@@ -1,5 +1,4 @@
-锘縰sing HMI.Core.Exceptions;
-using HMI.Core.Util;
+锘縰sing HMI.Core.Util;
 using HMI.MenuManage.Entity;
 using HMI.MenuManage.Util;
 using HMI.UI.DpUtil;
diff --git a/HMI.MenuManage/Model/MenuManageModel.cs b/HMI.MenuManage/Model/MenuManageModel.cs
index 95b9212..1baebfd 100644
--- a/HMI.MenuManage/Model/MenuManageModel.cs
+++ b/HMI.MenuManage/Model/MenuManageModel.cs
@@ -12,110 +12,5 @@
 {
     public class MenuManageModel : NotifyBase
     {
-        public SqliteHelper sqliteHelper;
-
-        private ObservableCollection<MenuData> _DataSource;
-
-        public ObservableCollection<MenuData> DataSource
-        {
-            get => _DataSource;
-            set
-            {
-                Set(ref _DataSource, value);
-                Set(nameof(DataSourceCount));
-            }
-        }
-
-        public int DataSourceCount { get => DataSource?.Count ?? 0; }
-
-
-        private ObservableCollection<MenuData> _TreeDataSource;
-
-        public ObservableCollection<MenuData> TreeDataSource
-        {
-            get => _TreeDataSource;
-            set { Set(ref _TreeDataSource, value); }
-        }
-
-        public MenuManageModel()
-        {
-            if (!File.Exists(PathHelper.DatabasePath))
-            {
-                SqliteHelper.CreateDatabase(PathHelper.DatabasePath);
-            }
-            sqliteHelper = new SqliteHelper(PathHelper.DatabasePath);
-            sqliteHelper.ExecuteSql(@"CREATE TABLE IF NOT EXISTS menu (
-                    id INTEGER PRIMARY KEY AUTOINCREMENT,
-                    code TEXT NOT NULL,
-                    name TEXT NOT NULL,
-                    icon TEXT,
-                    iconPath TEXT,
-                    level TEXT,
-                    type TEXT,
-                    parentCode TEXT,
-                    dllPath TEXT,
-                    className TEXT,
-                    sort REAL,
-                    UNIQUE(code)
-                    );");
-        }
-
-        public void LoadData()
-        {
-            ObservableCollection<MenuData> data = new ObservableCollection<MenuData>();
-            ObservableCollection<MenuData> treeData = new ObservableCollection<MenuData>();
-            DataTable dt = sqliteHelper.GetDataTable("SELECT m1.*,m2.name AS parentName FROM menu AS m1 LEFT JOIN menu AS m2 ON m1.parentCode = m2.code Order By m1.sort,m1.id;");
-            Dictionary<string, MenuData> cache = new Dictionary<string, MenuData>();
-            if (dt != null)
-            {
-                foreach (DataRow dr in dt.Rows)
-                {
-                    var menu = new MenuData
-                    {
-                        Id = dr["id"].ToString(),
-                        Code = dr["code"].ToString(),
-                        Name = dr["name"].ToString(),
-                        Icon = dr["icon"].ToString(),
-                        Level = dr["level"].ToString(),
-                        Type = dr["type"].ToString(),
-                        ParentCode = dr["parentCode"].ToString(),
-                        ParentName = dr["parentName"].ToString(),
-                        DllPath = dr["dllPath"].ToString(),
-                        ClassName = dr["className"].ToString(),
-                        Sort = dr["sort"] == DBNull.Value ? 0 : double.Parse(dr["sort"].ToString()),
-                    };
-                    data.Add(menu);
-                    if (menu.Level == "1")
-                    {
-                        treeData.Add(menu);
-                    }
-                    cache.Add(menu.Code, menu);
-                }
-
-                foreach (var menu in data)
-                {
-                    if (!string.IsNullOrEmpty(menu.ParentCode) && menu.ParentCode != menu.Code)
-                    {
-                        cache[menu.ParentCode].Children.Add(menu);
-                    }
-                }
-            }
-
-            TreeDataSource = treeData;
-            DataSource = data;
-        }
-
-        public void DeleteMenuById(string id)
-        {
-            string sql = $"DELETE FROM menu WHERE id = {id};";
-            sqliteHelper.ExecuteSql(sql);
-        }
-
-        public void DeleteMenuByIds(IEnumerable<string> ids)
-        {
-            List<string> sqls = ids.Select(id => $"DELETE FROM menu WHERE id = {id};")
-                .ToList();
-            sqliteHelper.ExecuteBatchSql(sqls);
-        }
     }
 }
diff --git a/HMI.MenuManage/RoleManage.xaml b/HMI.MenuManage/RoleManage.xaml
index 0f033c1..4b2acbd 100644
--- a/HMI.MenuManage/RoleManage.xaml
+++ b/HMI.MenuManage/RoleManage.xaml
@@ -14,11 +14,6 @@
              Background="#f0f2f5"
              d:DesignHeight="450" d:DesignWidth="800">
     <UserControl.Resources>
-        <ResourceDictionary>
-            <ResourceDictionary.MergedDictionaries>
-                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonDataGrid.xaml"/>
-            </ResourceDictionary.MergedDictionaries>
-        </ResourceDictionary>
     </UserControl.Resources>
     <Grid>
         <Grid.ColumnDefinitions>
diff --git a/HMI.MenuManage/UserManage.xaml b/HMI.MenuManage/UserManage.xaml
index d964dd7..c9059ee 100644
--- a/HMI.MenuManage/UserManage.xaml
+++ b/HMI.MenuManage/UserManage.xaml
@@ -13,11 +13,6 @@
              Padding="10"
              d:DesignHeight="450" d:DesignWidth="800">
     <UserControl.Resources>
-        <ResourceDictionary>
-            <ResourceDictionary.MergedDictionaries>
-                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonDataGrid.xaml"/>
-            </ResourceDictionary.MergedDictionaries>
-        </ResourceDictionary>
     </UserControl.Resources>
     <Grid>
         <Grid.ColumnDefinitions>
diff --git a/HMI.ParameterManage/ParameterManage.xaml b/HMI.ParameterManage/ParameterManage.xaml
index 838287d..61be0e0 100644
--- a/HMI.ParameterManage/ParameterManage.xaml
+++ b/HMI.ParameterManage/ParameterManage.xaml
@@ -16,11 +16,11 @@
              Background="{StaticResource WorkspaceBackground}"
              d:DesignHeight="450" d:DesignWidth="800">
     <UserControl.Resources>
-        <ResourceDictionary>
+        <!--<ResourceDictionary>
             <ResourceDictionary.MergedDictionaries>
                 <ResourceDictionary Source="/HMI.UI;component/Style/PanuonDataGrid.xaml"/>
             </ResourceDictionary.MergedDictionaries>
-        </ResourceDictionary>
+        </ResourceDictionary>-->
     </UserControl.Resources>
     <Grid>
         <Grid.ColumnDefinitions>
diff --git a/HMI.ParameterManage/ParameterSetting.xaml b/HMI.ParameterManage/ParameterSetting.xaml
index cc093dc..96eeac9 100644
--- a/HMI.ParameterManage/ParameterSetting.xaml
+++ b/HMI.ParameterManage/ParameterSetting.xaml
@@ -15,11 +15,11 @@
              Background="{StaticResource WorkspaceBackground}"
              d:DesignHeight="450" d:DesignWidth="800">
     <UserControl.Resources>
-        <ResourceDictionary>
+        <!--<ResourceDictionary>
             <ResourceDictionary.MergedDictionaries>
                 <ResourceDictionary Source="/HMI.UI;component/Style/PanuonDataGrid.xaml"/>
             </ResourceDictionary.MergedDictionaries>
-        </ResourceDictionary>
+        </ResourceDictionary>-->
     </UserControl.Resources>
     <Border Style="{StaticResource CardBoarder}">
         <Grid dp:GridHelper.Space="10" Cursor="">
diff --git a/HMI.ParameterManage/TestItem.xaml b/HMI.ParameterManage/TestItem.xaml
index a26e373..00b9d03 100644
--- a/HMI.ParameterManage/TestItem.xaml
+++ b/HMI.ParameterManage/TestItem.xaml
@@ -14,11 +14,11 @@
              Loaded="PluginControl_Loaded"
              d:DesignHeight="450" d:DesignWidth="800">
     <UserControl.Resources>
-        <ResourceDictionary>
+        <!--<ResourceDictionary>
             <ResourceDictionary.MergedDictionaries>
                 <ResourceDictionary Source="/HMI.UI;component/Style/PanuonDataGrid.xaml"/>
             </ResourceDictionary.MergedDictionaries>
-        </ResourceDictionary>
+        </ResourceDictionary>-->
     </UserControl.Resources>
     <Border Style="{StaticResource CardBoarder}">
         <Grid dp:GridHelper.Space="10">
diff --git a/HMI.SettingControl/SettingControl.xaml b/HMI.SettingControl/SettingControl.xaml
index facee9b..ccf7602 100644
--- a/HMI.SettingControl/SettingControl.xaml
+++ b/HMI.SettingControl/SettingControl.xaml
@@ -17,7 +17,6 @@
         <ResourceDictionary>
             <ResourceDictionary.MergedDictionaries>
                 <pu:StyleDictionary Includes="TabControl" />
-                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonResource.xaml"/>
                 <ResourceDictionary Source="/HMI.UI;component/Resources/Font/Font.xaml"/>
             </ResourceDictionary.MergedDictionaries>
 
diff --git a/HMI.SettingControl/SystemConfig.xaml b/HMI.SettingControl/SystemConfig.xaml
index 4ad1199..9027c4d 100644
--- a/HMI.SettingControl/SystemConfig.xaml
+++ b/HMI.SettingControl/SystemConfig.xaml
@@ -15,7 +15,6 @@
         <ResourceDictionary>
             <ResourceDictionary.MergedDictionaries>
                 <pu:StyleDictionary Includes="TabControl" />
-                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonResource.xaml"/>
                 <ResourceDictionary Source="/HMI.UI;component/Resources/Font/Font.xaml"/>
             </ResourceDictionary.MergedDictionaries>
 
diff --git a/HMI.UI/Controls/LightBall.cs b/HMI.UI/Controls/LightBall.cs
new file mode 100644
index 0000000..d9ffd5c
--- /dev/null
+++ b/HMI.UI/Controls/LightBall.cs
@@ -0,0 +1,43 @@
+锘縰sing System.Windows;
+using System.Windows.Controls;
+
+namespace HMI.UI
+{
+    public class LightBall : ContentControl
+    {
+        static LightBall()
+        {
+            DefaultStyleKeyProperty.OverrideMetadata(typeof(LightBall), new FrameworkPropertyMetadata(typeof(LightBall)));
+        }
+
+        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
+                            "Value",
+                            typeof(bool),
+                            typeof(LightBall),
+                            new FrameworkPropertyMetadata(false));
+
+        public static readonly DependencyProperty SizeProperty = DependencyProperty.Register(
+                            "Size",
+                            typeof(double),
+                            typeof(LightBall),
+                            new FrameworkPropertyMetadata(12d));
+
+        public double Size
+        {
+            get => (double)GetValue(SizeProperty);
+            set
+            {
+                SetValue(SizeProperty, value);
+            }
+        }
+
+        public bool Value
+        {
+            get => (bool)GetValue(ValueProperty);
+            set
+            {
+                SetValue(ValueProperty, value);
+            }
+        }
+    }
+}
diff --git a/HMI.UI/Controls/NumberInput.cs b/HMI.UI/Controls/NumberInput.cs
index d91c656..01d4234 100644
--- a/HMI.UI/Controls/NumberInput.cs
+++ b/HMI.UI/Controls/NumberInput.cs
@@ -37,7 +37,8 @@
         {
             // 鍏佽杈撳叆鏁板瓧銆佽礋鍙峰拰灏忔暟鐐�
             Regex regex = new Regex(@"^-?\d*\.?\d*$");
-            string newText = this.Text.Insert(this.CaretIndex, e.Text);
+            string segment = e.Text == "." ? ".0" : e.Text;
+            string newText = this.Text.Insert(this.CaretIndex, segment);
 
             // 妫�鏌ユ槸鍚﹀凡缁忔湁璐熷彿锛屽鏋滄湁鍒欎笉鍏佽鍐嶈緭鍏ヨ礋鍙�
             if (e.Text == "-" && this.Text.Contains("-"))
@@ -52,7 +53,10 @@
                 e.Handled = true;
                 return;
             }
-
+            //else if (e.Text == ".") {
+            //    e.Text = ".0";
+            //}
+            //regex = new Regex(@"^-?\d*\.?\d*$|^-?\.\d*$|^-?\d+\.$");
             // 楠岃瘉瀹屾暣鐨勬柊鏂囨湰鏄惁绗﹀悎鏁板瓧鏍煎紡
             if (!regex.IsMatch(newText))
             {
diff --git a/HMI.UI/Controls/VisualHost.cs b/HMI.UI/Controls/VisualHost.cs
new file mode 100644
index 0000000..92adf88
--- /dev/null
+++ b/HMI.UI/Controls/VisualHost.cs
@@ -0,0 +1,30 @@
+锘縰sing System;
+using System.Windows;
+using System.Windows.Media;
+
+namespace HMI.UI.Controls
+{
+    public class VisualHost : FrameworkElement
+    {
+        Visual child;
+
+        public VisualHost(Visual child)
+        {
+            if (child == null)
+                throw new ArgumentException("child");
+
+            this.child = child;
+            AddVisualChild(child);
+        }
+
+        protected override Visual GetVisualChild(int index)
+        {
+            return (index == 0) ? child : null;
+        }
+
+        protected override int VisualChildrenCount
+        {
+            get { return 1; }
+        }
+    }
+}
diff --git a/HMI.UI/Converter/DataGridRowNumberConverter.cs b/HMI.UI/Converter/DataGridRowNumberConverter.cs
index a9fca5c..5ebb98c 100644
--- a/HMI.UI/Converter/DataGridRowNumberConverter.cs
+++ b/HMI.UI/Converter/DataGridRowNumberConverter.cs
@@ -22,6 +22,37 @@
         }
     }
 
+    public class ListViewRowNumberConverter : IValueConverter
+    {
+        public object Convert(object value, Type TargetType, object parameter, CultureInfo culture)
+        {
+            ListViewItem item = (ListViewItem)value;
+            ListView listView = ItemsControl.ItemsControlFromItemContainer(item) as ListView;
+            return listView.ItemContainerGenerator.IndexFromContainer(item) + 1;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return value;
+        }
+    }
+
+    public class ListViewRowHeightConverter : IValueConverter
+    {
+        public object Convert(object value, Type TargetType, object parameter, CultureInfo culture)
+        {
+            double num = double.Parse(parameter.ToString());
+            double? res = value as double?;
+            if (res == null) return value;
+            return res - num;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return value;
+        }
+    }
+
     //public class ListBoxItemIndexConverter : IValueConverter
     //{
     //    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
diff --git a/HMI.UI/Converter/ReverseBoolConverter.cs b/HMI.UI/Converter/ReverseBoolConverter.cs
new file mode 100644
index 0000000..48966c4
--- /dev/null
+++ b/HMI.UI/Converter/ReverseBoolConverter.cs
@@ -0,0 +1,27 @@
+锘縰sing System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace HMI.UI.Converter
+{
+    public class ReverseBoolConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value is bool flag)
+            {
+                return !flag;
+            }
+            return value;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value is bool flag)
+            {
+                return !flag;
+            }
+            return value;
+        }
+    }
+}
diff --git a/Plugin.TestBuilder/UI/CanvasHelper.cs b/HMI.UI/DpUtil/CanvasHelper.cs
similarity index 69%
rename from Plugin.TestBuilder/UI/CanvasHelper.cs
rename to HMI.UI/DpUtil/CanvasHelper.cs
index 1778663..8973ac7 100644
--- a/Plugin.TestBuilder/UI/CanvasHelper.cs
+++ b/HMI.UI/DpUtil/CanvasHelper.cs
@@ -1,9 +1,21 @@
-锘縰sing System;
+锘縰sing HMI.UI.MDI;
+using System;
 using System.Windows;
 using System.Windows.Media;
 
-namespace Plugin.TestBuilder.UI
+namespace HMI.UI.DpUtil
 {
+    public class CanvasPosition
+    {
+        public double Top { get; set; }
+        public double Left { get; set; }
+        public double Width { get; set; }
+        public double Height { get; set; }
+
+        public double LimitWidth { get; set; }
+        public double LimitHeight { get; set; }
+    }
+
     public class CanvasHelper
     {
         public static void SetTopZIndex(InnerWindow element, DependencyObject parent, Type targetClass)
diff --git a/HMI.UI/DpUtil/FormGroupHelper.cs b/HMI.UI/DpUtil/FormGroupHelper.cs
index 01f6223..bdfd5bd 100644
--- a/HMI.UI/DpUtil/FormGroupHelper.cs
+++ b/HMI.UI/DpUtil/FormGroupHelper.cs
@@ -57,6 +57,11 @@
             {
                 if (child is FormGroup fg)
                 {
+                    if (fg.Visibility != Visibility.Visible)
+                    {
+                        ClearValue(fg);
+                        continue;
+                    }
                     fg.ValidateResult = ValidateResult.None;
                     fg.Message = null;
                     object content = fg.Content;
@@ -96,5 +101,24 @@
             if (content is PasswordBox passwordInput) return passwordInput.Password;
             return null;
         }
+
+        private static void ClearValue(object content)
+        {
+            if (content == null) return;
+            if (content is UIElementCollection elementCollection && elementCollection.Count > 0)
+                ClearValue(elementCollection[0]);
+            if (content is TextBox tb) tb.Text = null;
+            if (content is CheckBox cb) cb.IsChecked = null;
+            if (content is ComboBox cBox)
+            {
+                cBox.SelectedItem = null;
+                cBox.SelectedValue = null;
+                cBox.Text = null;
+            }
+
+            if (content is Panel panel) ClearValue(panel.Children);
+            if (content is Panuon.WPF.UI.NumberInput numberInput) numberInput.Value = null;
+            if (content is PasswordBox passwordInput) passwordInput.Password = null;
+        }
     }
 }
diff --git a/HMI.UI/DpUtil/Resize.cs b/HMI.UI/DpUtil/Resize.cs
new file mode 100644
index 0000000..d21fd4d
--- /dev/null
+++ b/HMI.UI/DpUtil/Resize.cs
@@ -0,0 +1,640 @@
+锘縰sing HMI.UI.MDI;
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Shapes;
+
+namespace HMI.UI.DpUtil
+{
+    public class Resize
+    {
+        private static readonly double MinWidth = 60;
+        private static readonly double MinHeight = 60;
+
+        public static double GetReMaxWidth(DependencyObject obj)
+        {
+            return (double)obj.GetValue(ReMaxWithProperty);
+        }
+        public static void SetReMaxWidth(DependencyObject obj, double value)
+        {
+            obj.SetValue(ReMaxWithProperty, value);
+        }
+        public static readonly DependencyProperty ReMaxWithProperty =
+            DependencyProperty.RegisterAttached("ReMaxWith", typeof(double), typeof(Resize), new PropertyMetadata(0.0));
+
+
+        // 鎷栨嫿妯℃澘
+        public static ControlTemplate GetResizeTemplate(DependencyObject obj)
+        {
+            return (ControlTemplate)obj.GetValue(ResizeTemplateProperty);
+        }
+
+        public static void SetResizeTemplate(DependencyObject obj, ControlTemplate value)
+        {
+            obj.SetValue(ResizeTemplateProperty, value);
+        }
+
+        public static readonly DependencyProperty ResizeTemplateProperty =
+            DependencyProperty.RegisterAttached("ResizeTemplate", typeof(ControlTemplate), typeof(Resize), new PropertyMetadata(null, (d, e) =>
+            {
+                FrameworkElement element = d as FrameworkElement;
+                if (element == null) return;
+                Resizeable resize = GetResizeable(element);
+                if (resize == null) return;
+                resize.Detach();
+                resize.Attach();
+            }));
+
+        // 鏄惁鍙互鎷栨嫿鏀瑰彉澶у皬
+        public static bool GetCanMove(DependencyObject obj)
+        {
+            return (bool)obj.GetValue(CanMoveProperty);
+        }
+        public static void SetCanMove(DependencyObject obj, bool value)
+        {
+            obj.SetValue(CanMoveProperty, value);
+        }
+        public static readonly DependencyProperty CanMoveProperty =
+            DependencyProperty.RegisterAttached("CanMove", typeof(bool), typeof(Resize),
+                new PropertyMetadata(true));
+
+        // 鏄惁鍙互鎷栨嫿鏀瑰彉澶у皬
+        public static bool GetIsResizeable(DependencyObject obj)
+        {
+            return (bool)obj.GetValue(IsResizeableProperty);
+        }
+        public static void SetIsResizeable(DependencyObject obj, bool value)
+        {
+            obj.SetValue(IsResizeableProperty, value);
+        }
+
+        public static readonly DependencyProperty IsResizeableProperty =
+            DependencyProperty.RegisterAttached("IsResizeable", typeof(bool), typeof(Resize), new PropertyMetadata(true, (d, e) =>
+            {
+                FrameworkElement element = d as FrameworkElement;
+                if (element == null) return;
+                // 鍏堝彇娑堜箣鍓嶇殑鎷栨嫿瑁呴グ
+                Resizeable resize = GetResizeable(element);
+                if (resize != null) resize.Detach();
+                // 瀹炵幇閫昏緫
+                // 鍒ゆ柇灞炴�ц缃殑鏄笉鏄痶rue
+                if (!(bool)e.NewValue)
+                {
+                    SetResizeable(element, null);
+                    return;
+                }
+
+                if (!element.IsLoaded)
+                {
+                    RoutedEventHandler Loaded_Resize = null;
+                    Loaded_Resize = (sender, e1) =>
+                    {
+                        FrameworkElement senderElement = sender as FrameworkElement;
+                        Resizeable r = new Resizeable(senderElement);
+                        r.Attach();
+                        SetResizeable(senderElement, r);
+                        senderElement.Loaded -= Loaded_Resize;
+                    };
+                    element.Loaded += Loaded_Resize;
+                }
+                else
+                {
+                    resize = new Resizeable(element);
+                    resize.Attach();
+                    SetResizeable(element, resize);
+                }
+            }));
+
+        public static Resizeable GetResizeable(DependencyObject obj)
+        {
+            return (Resizeable)obj.GetValue(ResizeableProperty);
+        }
+
+        public static void SetResizeable(DependencyObject obj, Resizeable value)
+        {
+            obj.SetValue(ResizeableProperty, value);
+        }
+
+        // Using a DependencyProperty as the backing store for Resizeable.  This enables animation, styling, binding, etc...
+        static readonly DependencyProperty ResizeableProperty =
+           DependencyProperty.RegisterAttached("Resizeable", typeof(Resizeable), typeof(Resize), new PropertyMetadata(null));
+
+        static int i = 0;
+        static SolidColorBrush[] colors = new SolidColorBrush[] {
+            new SolidColorBrush(Colors.White),
+            new SolidColorBrush(Colors.Red),
+            new SolidColorBrush(Colors.Blue),
+            new SolidColorBrush(Colors.Green),
+            new SolidColorBrush(Colors.LightBlue),
+            new SolidColorBrush(Colors.Orange),
+            new SolidColorBrush(Colors.Yellow),
+            new SolidColorBrush(Colors.Black),
+        };
+
+        public class Resizeable
+        {
+            ResizeType _resizeType;
+            enum ResizeType
+            {
+                None,
+                Grid,
+                Canvas,
+                Window,
+                Others,
+            }
+            FrameworkElement _resizeTarget;
+
+            FrameworkElement _thumbsParent = null;
+            public Resizeable(FrameworkElement resizeTarget)
+            {
+                _resizeTarget = resizeTarget;
+            }
+
+            public void Attach()
+            {
+                FrameworkElement thumbsRoot = null;
+                ControlTemplate template = GetResizeTemplate(_resizeTarget);
+                // 鐪嬩笉鎳備簡锛屽ぇ姒傛槸鎷栨嫿妯℃澘
+                if (template != null)
+                {
+                    Control control = new Control();
+                    control.Template = template;
+                    control.ApplyTemplate();
+                    thumbsRoot = control;
+                    _thumbsParent = VisualTreeHelper.GetChild(thumbsRoot, 0) as FrameworkElement;
+                }
+
+                if (thumbsRoot != null)
+                {
+                    UIElement element = _resizeTarget is Window _windowTarget ? _windowTarget.Content as UIElement : _resizeTarget;
+                    if (element != null)
+                    {
+                        if (element is InnerWindow _inner)
+                        {
+                            AdornerLayer layer = _inner.ResizeAdornerDecorator.AdornerLayer;
+                            if (layer == null)
+                                throw new Exception("鑾峰彇鎺т欢瑁呴グ灞傚け璐ワ紝鎺т欢鍙兘娌℃湁瑁呴グ灞傦紒");
+                            layer.Add(new ResizeAdorner(layer, thumbsRoot));
+                        }
+                        else
+                        {
+                            AdornerLayer layer = AdornerLayer.GetAdornerLayer(element);
+                            if (layer == null)
+                                throw new Exception("鑾峰彇鎺т欢瑁呴グ灞傚け璐ワ紝鎺т欢鍙兘娌℃湁瑁呴グ灞傦紒");
+                            layer.Add(new ResizeAdorner(element, thumbsRoot));
+                        }
+                    }
+                }
+
+                // 浣跨敤缂虹渷鏍峰紡
+                if (thumbsRoot == null)
+                {
+                    Thumb leftThumb = null, topThumb = null, rightThumb = null, bottomThumb = null,
+                        lefTopThumb = null, rightTopThumb = null, rightBottomThumb = null, leftbottomThumb = null;
+                    int thumnSize = 5;
+                    // 鍒濆鍖杢humb
+                    leftThumb = new Thumb();
+                    leftThumb.Template = new ControlTemplate(typeof(Thumb))
+                    {
+                        VisualTree = GetFactory(new SolidColorBrush(Colors.Transparent))
+                    };
+                    leftThumb.HorizontalAlignment = HorizontalAlignment.Left;
+                    leftThumb.VerticalAlignment = VerticalAlignment.Stretch;
+                    leftThumb.Cursor = Cursors.SizeWE;
+                    leftThumb.Width = thumnSize;
+                    leftThumb.Height = double.NaN;
+                    leftThumb.Margin = new Thickness(0, thumnSize, 0, thumnSize);
+
+                    topThumb = new Thumb();
+                    topThumb.Template = new ControlTemplate(typeof(Thumb))
+                    {
+                        VisualTree = GetFactory(new SolidColorBrush(Colors.Transparent))
+                    };
+                    topThumb.HorizontalAlignment = HorizontalAlignment.Stretch;
+                    topThumb.VerticalAlignment = VerticalAlignment.Top;
+                    topThumb.Cursor = Cursors.SizeNS;
+                    topThumb.Width = double.NaN;
+                    topThumb.Height = thumnSize;
+                    topThumb.Margin = new Thickness(thumnSize, 0, thumnSize, 0);
+
+                    rightThumb = new Thumb();
+                    rightThumb.Template = new ControlTemplate(typeof(Thumb))
+                    {
+                        VisualTree = GetFactory(new SolidColorBrush(Colors.Transparent))
+                    };
+                    rightThumb.HorizontalAlignment = HorizontalAlignment.Right;
+                    rightThumb.VerticalAlignment = VerticalAlignment.Stretch;
+                    rightThumb.Cursor = Cursors.SizeWE;
+                    rightThumb.Width = thumnSize;
+                    rightThumb.Height = double.NaN;
+                    rightThumb.Margin = new Thickness(0, thumnSize, 0, thumnSize);
+
+                    bottomThumb = new Thumb();
+                    bottomThumb.Template = new ControlTemplate(typeof(Thumb))
+                    {
+                        VisualTree = GetFactory(new SolidColorBrush(Colors.Transparent))
+                    };
+                    bottomThumb.HorizontalAlignment = HorizontalAlignment.Stretch;
+                    bottomThumb.VerticalAlignment = VerticalAlignment.Bottom;
+                    bottomThumb.Cursor = Cursors.SizeNS;
+                    bottomThumb.Width = double.NaN;
+                    bottomThumb.Height = thumnSize;
+                    bottomThumb.Margin = new Thickness(thumnSize, 0, thumnSize, 0);
+
+                    lefTopThumb = new Thumb();
+                    lefTopThumb.Template = new ControlTemplate(typeof(Thumb))
+                    {
+                        VisualTree = GetFactory(new SolidColorBrush(Colors.Transparent))
+                    };
+                    lefTopThumb.HorizontalAlignment = HorizontalAlignment.Left;
+                    lefTopThumb.VerticalAlignment = VerticalAlignment.Top;
+                    lefTopThumb.Cursor = Cursors.SizeNWSE;
+                    lefTopThumb.Width = thumnSize;
+                    lefTopThumb.Height = thumnSize;
+
+                    rightTopThumb = new Thumb();
+                    rightTopThumb.Template = new ControlTemplate(typeof(Thumb))
+                    {
+                        VisualTree = GetFactory(new SolidColorBrush(Colors.Transparent))
+                    };
+                    rightTopThumb.HorizontalAlignment = HorizontalAlignment.Right;
+                    rightTopThumb.VerticalAlignment = VerticalAlignment.Top;
+                    rightTopThumb.Cursor = Cursors.SizeNESW;
+                    rightTopThumb.Width = thumnSize;
+                    rightTopThumb.Height = thumnSize;
+
+                    rightBottomThumb = new Thumb();
+                    rightBottomThumb.Template = new ControlTemplate(typeof(Thumb))
+                    {
+                        VisualTree = GetFactory(new SolidColorBrush(Colors.Transparent))
+                    };
+                    rightBottomThumb.HorizontalAlignment = HorizontalAlignment.Right;
+                    rightBottomThumb.VerticalAlignment = VerticalAlignment.Bottom;
+                    rightBottomThumb.Cursor = Cursors.SizeNWSE;
+                    rightBottomThumb.Width = thumnSize;
+                    rightBottomThumb.Height = thumnSize;
+
+                    leftbottomThumb = new Thumb();
+                    leftbottomThumb.Template = new ControlTemplate(typeof(Thumb))
+                    {
+                        VisualTree = GetFactory(new SolidColorBrush(Colors.Transparent))
+                    };
+                    leftbottomThumb.HorizontalAlignment = HorizontalAlignment.Left;
+                    leftbottomThumb.VerticalAlignment = VerticalAlignment.Bottom;
+                    leftbottomThumb.Cursor = Cursors.SizeNESW;
+                    leftbottomThumb.Width = thumnSize;
+                    leftbottomThumb.Height = thumnSize;
+
+
+                    Grid grid = new Grid();
+                    grid.Children.Add(leftThumb);
+                    grid.Children.Add(topThumb);
+                    grid.Children.Add(rightThumb);
+                    grid.Children.Add(bottomThumb);
+                    grid.Children.Add(lefTopThumb);
+                    grid.Children.Add(rightTopThumb);
+                    grid.Children.Add(rightBottomThumb);
+                    grid.Children.Add(leftbottomThumb);
+                    UIElement element;
+                    if (_resizeTarget is Window _resizeTargetWindow)
+                    {
+                        element = _resizeTargetWindow.Content as UIElement;
+                    }
+                    else
+                    {
+                        element = _resizeTarget;
+                    }
+                    if (element != null)
+                    {
+                        if (element is InnerWindow _inner)
+                        {
+                            AdornerLayer layer = _inner.ResizeAdornerDecorator?.AdornerLayer;
+                            if (layer == null)
+                                throw new Exception("鑾峰彇鎺т欢瑁呴グ灞傚け璐ワ紝鎺т欢鍙兘娌℃湁瑁呴グ灞傦紒");
+                            layer.Add(new ResizeAdorner(layer, grid));
+                        }
+                        else
+                        {
+                            AdornerLayer layer = AdornerLayer.GetAdornerLayer(element);
+                            if (layer == null)
+                                throw new Exception("鑾峰彇鎺т欢瑁呴グ灞傚け璐ワ紝鎺т欢鍙兘娌℃湁瑁呴グ灞傦紒");
+                            layer.Add(new ResizeAdorner(element, grid));
+                        }
+                    }
+                    thumbsRoot = grid;
+                    _thumbsParent = thumbsRoot;
+                }
+
+                //娉ㄥ唽浜嬩欢
+                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(_thumbsParent); i++)
+                {
+                    Thumb thumb = VisualTreeHelper.GetChild(_thumbsParent, i) as Thumb;
+                    if (thumb != null)
+                    {
+                        thumb.DragStarted += Thumb_DragStarted;
+                        thumb.DragDelta += Thumb_DragDelta;
+                    }
+                }
+            }
+            public void Detach()
+            {
+                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(_thumbsParent); i++)
+                {
+                    Thumb thumb = VisualTreeHelper.GetChild(_thumbsParent, i) as Thumb;
+                    if (thumb != null)
+                    {
+                        thumb.DragStarted -= Thumb_DragStarted;
+                        thumb.DragDelta -= Thumb_DragDelta;
+                    }
+                    thumb.Cursor = Cursors.Arrow;
+                }
+                _thumbsParent = null;
+            }
+
+            private void Thumb_DragStarted(object sender, DragStartedEventArgs e)
+            {
+                _resizeType = GetResizeType();
+                if (_resizeType == ResizeType.Canvas && _resizeTarget is InnerWindow _inner)
+                {
+                    Canvas parent = GetCanvas(_inner);
+                    // 濡傛灉鏄釜InnerWindow锛屽氨鎶奍nnerWindowZIndex缃《
+                    // TODO 鍙互鏀规垚鎺ュ彛鎴栬�匘ependencyPrope鏉ュ仛
+                    CanvasHelper.SetTopZIndex(_inner, parent, _inner.GetType());
+                }
+            }
+
+            private void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
+            {
+                switch (_resizeType)
+                {
+                    case ResizeType.None:
+                        break;
+                    case ResizeType.Grid:
+                        {
+                            FrameworkElement element = _resizeTarget;
+                            FrameworkElement thumb = sender as FrameworkElement;
+                            double left, top, right, bottom, width, height;
+                            if (thumb.HorizontalAlignment == HorizontalAlignment.Left)
+                            {
+                                right = element.Margin.Right;
+                                left = element.Margin.Left + e.HorizontalChange;
+                                width = (double.IsNaN(element.Width) ? element.ActualWidth : element.Width) - e.HorizontalChange;
+                            }
+                            else
+                            {
+                                left = element.Margin.Left;
+                                right = element.Margin.Right - e.HorizontalChange;
+                                width = (double.IsNaN(element.Width) ? element.ActualWidth : element.Width) + e.HorizontalChange;
+                            }
+
+                            if (thumb.VerticalAlignment == VerticalAlignment.Top)
+                            {
+                                bottom = element.Margin.Bottom;
+                                top = element.Margin.Top + e.VerticalChange;
+                                height = (double.IsNaN(element.Height) ? element.ActualHeight : element.Height) - e.VerticalChange;
+                            }
+                            else
+                            {
+                                top = element.Margin.Top;
+                                bottom = element.Margin.Bottom - e.VerticalChange;
+                                height = (double.IsNaN(element.Height) ? element.ActualHeight : element.Height) + e.VerticalChange;
+                            }
+                            if (thumb.HorizontalAlignment != HorizontalAlignment.Center && thumb.HorizontalAlignment != HorizontalAlignment.Stretch)
+                            {
+                                if (width >= 0)
+                                {
+                                    element.Margin = new Thickness(left, element.Margin.Top, right, element.Margin.Bottom);
+                                    element.Width = width;
+                                }
+                            }
+                            if (thumb.VerticalAlignment != VerticalAlignment.Center && thumb.VerticalAlignment != VerticalAlignment.Stretch)
+                            {
+                                if (height >= 0)
+                                {
+                                    element.Margin = new Thickness(element.Margin.Left, top, element.Margin.Right, bottom);
+                                    element.Height = height;
+                                }
+                            }
+                        }
+                        break;
+                    case ResizeType.Canvas:
+                        {
+                            FrameworkElement target = _resizeTarget;
+                            Canvas parent = GetCanvas(target);
+                            FrameworkElement thumb = sender as FrameworkElement;
+                            if (parent == null)
+                            {
+                                // 涓�鑸笉浼氬嚭鐜拌繖绉嶆儏鍐碉紝淇濋櫓鍒ゆ柇涓�涓�
+                                return;
+                            }
+
+                            double oldLeft = double.IsNaN(Canvas.GetLeft(target)) ? 0 : Canvas.GetLeft(target),
+                                oldTop = double.IsNaN(Canvas.GetTop(target)) ? 0 : Canvas.GetTop(target),
+                                oldWidth = double.IsNaN(target.ActualWidth) ? 0 : target.ActualWidth,
+                                oldHeight = double.IsNaN(target.ActualHeight) ? 0 : target.ActualHeight;
+
+                            double left, top, width, height;
+                            double x = e.HorizontalChange, y = e.VerticalChange;
+                            bool toLeft = x < 0, toTop = y < 0;
+                            bool isLeft = thumb.HorizontalAlignment == HorizontalAlignment.Left,
+                                isTop = thumb.VerticalAlignment == VerticalAlignment.Top;
+                            if (isLeft)
+                            {
+                                // 宸︿晶鐨勬嫋鎷�
+                                left = oldLeft + x;
+                                width = oldWidth - x;
+                                if (left < 0) { left = 0; width = oldWidth - (left - oldLeft); }
+                                width = width < MinWidth ? MinWidth : width;
+                            }
+                            else
+                            {
+                                // 鍙充晶鐨勬嫋鎷�
+                                left = oldLeft >= 0 ? oldLeft : 0;
+                                width = oldWidth + x;
+                                width = width < MinWidth ? MinWidth : width;
+                                if (left + width > parent.ActualWidth) width = parent.ActualWidth - left;
+                            }
+
+                            if (isTop)
+                            {
+                                // 涓婅竟鐨勬嫋鎷�
+                                top = oldTop + y;
+                                height = oldHeight - y;
+                                if (top < 0) { top = 0; height = oldHeight - (top - oldTop); }
+                                height = height < MinHeight ? MinHeight : height;
+                            }
+                            else
+                            {
+                                // 涓嬭竟鐨勬嫋鎷�
+                                top = oldTop > 0 ? oldTop : 0;
+                                height = oldHeight + y;
+                                height = height < MinHeight ? MinHeight : height;
+                                if (top + height > parent.ActualHeight) height = parent.ActualHeight - top;
+                            }
+
+                            if (thumb.HorizontalAlignment != HorizontalAlignment.Center &&
+                                thumb.HorizontalAlignment != HorizontalAlignment.Stretch)
+                            {
+                                // 姘村钩鏂瑰悜鏈�缁堝垽鏂�
+                                Canvas.SetLeft(target, left);
+                                target.Width = width;
+                                //if ((isLeft && toLeft && left >= 0) ||
+                                //    (isLeft && !toLeft && width >= MinWidth) ||
+                                //    (!isLeft && toLeft && width >= MinWidth) ||
+                                //    (!isLeft && !toLeft && (left + width) <= parent.ActualWidth))
+                                //{
+                                //    Canvas.SetLeft(target, left);
+                                //    target.Width = width;
+                                //}
+                            }
+
+                            if (thumb.VerticalAlignment != VerticalAlignment.Center &&
+                                thumb.VerticalAlignment != VerticalAlignment.Stretch)
+                            {
+                                // 鍨傜洿鏂瑰悜鏈�缁堝垽鏂�
+                                Canvas.SetTop(target, top);
+                                target.Height = height;
+                                //if ((isTop && toTop && top >= 0) ||
+                                //    (isTop && !toTop && height >= MinHeight) ||
+                                //    (!isTop && toTop && height >= MinHeight) ||
+                                //    (!isTop && !toTop && (top + height) <= parent.ActualHeight))
+                                //{
+                                //    Canvas.SetTop(target, top);
+                                //    target.Height = height;
+                                //}
+                            }
+                        }
+                        break;
+                    case ResizeType.Window:
+                        {
+                            var c = _resizeTarget as Window;
+                            var thumb = sender as FrameworkElement;
+                            double left, top, width, height;
+                            if (thumb.HorizontalAlignment == HorizontalAlignment.Left)
+                            {
+                                left = c.Left + e.HorizontalChange;
+                                width = c.Width - e.HorizontalChange;
+                            }
+                            else
+                            {
+                                left = c.Left;
+                                width = c.Width + e.HorizontalChange;
+                            }
+
+                            if (thumb.VerticalAlignment == VerticalAlignment.Top)
+                            {
+
+                                top = c.Top + e.VerticalChange;
+                                height = c.Height - e.VerticalChange;
+                            }
+                            else
+                            {
+                                top = c.Top;
+                                height = c.Height + e.VerticalChange;
+                            }
+
+                            if (thumb.HorizontalAlignment != HorizontalAlignment.Center && thumb.HorizontalAlignment != HorizontalAlignment.Stretch)
+                            {
+                                if (width > 63)
+                                {
+                                    c.Width = width;
+                                    c.Left = left;
+                                }
+                            }
+                            if (thumb.VerticalAlignment != VerticalAlignment.Center && thumb.VerticalAlignment != VerticalAlignment.Stretch)
+                            {
+                                if (height > 63)
+                                {
+                                    c.Top = top;
+                                    c.Height = height;
+                                }
+                            }
+                        }
+                        break;
+                    case ResizeType.Others:
+
+                        break;
+                }
+            }
+            ResizeType GetResizeType()
+            {
+                ResizeType rt;
+                DependencyObject parent = VisualTreeHelper.GetParent(_resizeTarget);
+                if (parent is Grid)
+                {
+                    rt = ResizeType.Grid;
+                }
+                else if (parent is Canvas)
+                {
+                    rt = ResizeType.Canvas;
+                }
+                else if (_resizeTarget is Window)
+                {
+                    rt = ResizeType.Window;
+                }
+                else
+                {
+                    rt = ResizeType.Grid;
+                }
+                return rt;
+            }
+
+            // 鏂瑰舰宸ュ巶      
+            FrameworkElementFactory GetFactory(Brush back)
+            {
+                FrameworkElementFactory fac = new FrameworkElementFactory(typeof(Rectangle));
+                fac.SetValue(Rectangle.FillProperty, back);
+                return fac;
+            }
+
+            // 瑁呴グ鍣�
+            public class ResizeAdorner : Adorner
+            {
+                // 甯冨眬瀹瑰櫒锛屽鏋滀笉浣跨敤甯冨眬瀹瑰櫒锛屽垯闇�瑕佺粰涓婅堪8涓帶浠跺竷灞�锛屽疄鐜板拰Grid甯冨眬瀹氫綅鏄竴鏍风殑锛屼細姣旇緝绻佺悙涓旀剰涔変笉澶с��
+                UIElement _child;
+                public ResizeAdorner(UIElement adornedElement, UIElement child) : base(adornedElement)
+                {
+                    _child = child;
+                    AddVisualChild(child);
+                }
+
+                protected override Visual GetVisualChild(int index)
+                {
+                    return _child;
+                }
+                protected override int VisualChildrenCount { get; } = 1;
+                protected override Size ArrangeOverride(Size finalSize)
+                {
+                    // 鐩存帴缁檊rid甯冨眬锛実rid鍐呴儴鐨則humb浼氳嚜鍔ㄥ竷灞�銆�
+                    _child.Arrange(new Rect(new Point(0, 0), finalSize));
+                    return finalSize;
+                }
+                public void ttt(int index)
+                {
+                    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(_child); i++)
+                    {
+                        DependencyObject child = VisualTreeHelper.GetChild(_child, i);
+                        Canvas.SetZIndex(child as UIElement, index);
+
+                    }
+
+                }
+
+            }
+        }
+
+        static Canvas GetCanvas(DependencyObject element)
+        {
+            DependencyObject e = element;
+            while (e != null && !(e is Canvas))
+            {
+                e = VisualTreeHelper.GetParent(e);
+            }
+            return e == null ? null : e as Canvas;
+        }
+    }
+}
diff --git a/HMI.UI/EditBox.cs b/HMI.UI/EditBox.cs
new file mode 100644
index 0000000..9d7b8bd
--- /dev/null
+++ b/HMI.UI/EditBox.cs
@@ -0,0 +1,194 @@
+锘縰sing System;
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
+
+namespace HMI.UI
+{
+    public class EditBox : Control
+    {
+        static EditBox()
+        {
+            DefaultStyleKeyProperty.OverrideMetadata(typeof(EditBox), new FrameworkPropertyMetadata(typeof(EditBox)));
+        }
+
+        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
+                            "Value",
+                            typeof(object),
+                            typeof(EditBox),
+                            new FrameworkPropertyMetadata());
+
+        public static readonly DependencyProperty CanEditProperty = DependencyProperty.Register(
+                            "CanEdit",
+                            typeof(bool),
+                            typeof(EditBox),
+                            new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnCanEditPropertyChanged)));
+
+        public static DependencyProperty IsEditingProperty = DependencyProperty.Register(
+                            "IsEditing",
+                            typeof(bool),
+                            typeof(EditBox),
+                            new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsEditingPropertyChanged)));
+
+        public static DependencyProperty PreEditingProperty = DependencyProperty.Register(
+                            "PreEditing",
+                            typeof(Func<bool?>),
+                            typeof(EditBox),
+                            new FrameworkPropertyMetadata());
+
+        public static DependencyProperty ShowEmptyProperty = DependencyProperty.Register(
+                            "ShowEmpty",
+                            typeof(bool),
+                            typeof(EditBox),
+                            new FrameworkPropertyMetadata(false));
+
+        private static void OnCanEditPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
+        {
+            if (obj is EditBox eb && e.Property == CanEditProperty)
+            {
+                bool value = (bool)e.NewValue;
+                if (value)
+                {
+                    eb.MouseDoubleClick += eb.EditBox_MouseDoubleClick;
+                }
+                else
+                {
+                    eb.MouseDoubleClick -= eb.EditBox_MouseDoubleClick;
+                }
+            }
+        }
+
+        private static void OnIsEditingPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
+        {
+            if (obj is EditBox eb && e.Property == IsEditingProperty)
+            {
+                bool value = (bool)e.NewValue;
+                eb.editing.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
+                if (value)
+                {
+                    eb.editing.Focus();
+                }
+            }
+        }
+
+        private TextBox editing;
+        public EditBox()
+        {
+            this.Loaded += EditBox_Loaded;
+        }
+
+        private void EditBox_Loaded(object sender, RoutedEventArgs e)
+        {
+            editing = this.Template.FindName("PART_TextBoxPart", this) as TextBox;
+        }
+
+        private void EditBox_MouseDoubleClick(object sender, MouseButtonEventArgs e)
+        {
+            if (IsEditing)
+            {
+                return;
+            }
+
+            Func<bool?> pre = GetValue(PreEditingProperty) as Func<bool?>;
+            if (pre != null && pre.Invoke() != true)
+            {
+                return;
+            }
+            var win = Window.GetWindow(this);
+
+            win.MouseDown += EditBox_LostFocus;
+            editing.LostFocus += EditBox_LostFocus;
+            IsEditing = true;
+            e.Handled = true;
+        }
+
+        public static bool GetElementUnderMouse(UIElement relativeTo, UIElement target)
+        {
+            Point mousePosition = Mouse.GetPosition(relativeTo);
+            bool res = false;
+            HitTestResultCallback resultCallback = (result) =>
+            {
+                // 鑾峰彇鐩镐氦鐨刄I鍏冪礌
+                var element = result.VisualHit as FrameworkElement;
+
+                if (element != null && element == target)
+                {
+                    res = true;
+                    return HitTestResultBehavior.Stop; // 鎵惧埌鍚庡仠姝㈤亶鍘�
+                }
+                return HitTestResultBehavior.Continue;
+            };
+
+            VisualTreeHelper.HitTest(relativeTo, null,
+                resultCallback, new PointHitTestParameters(mousePosition));
+            return res;
+        }
+
+        private void EditBox_LostFocus(object sender, RoutedEventArgs e)
+        {
+            if (sender == editing)
+            {
+                IsEditing = false;
+                editing.LostFocus -= EditBox_LostFocus;
+                Window.GetWindow(this).MouseDown -= EditBox_LostFocus;
+            }
+            else
+            {
+                var win = sender as Window;
+                if (!GetElementUnderMouse(win, this))
+                {
+                    //editing.Focu
+                    IsEditing = false;
+                    win.MouseDown -= EditBox_LostFocus;
+                    Window.GetWindow(this).MouseDown -= EditBox_LostFocus;
+                }
+            }
+        }
+
+        [Bindable(true)]
+        [Category("Appearance")]
+        public object Value
+        {
+            get { return GetValue(ValueProperty); }
+            set { SetValue(ValueProperty, value); }
+        }
+
+        public bool CanEdit
+        {
+            get => (bool)GetValue(CanEditProperty);
+            set
+            {
+                SetValue(CanEditProperty, value);
+            }
+        }
+
+        public bool IsEditing
+        {
+            get => (bool)GetValue(IsEditingProperty);
+            set
+            {
+                SetValue(IsEditingProperty, value);
+            }
+        }
+
+        public Func<bool?> PreEditing
+        {
+            get => (Func<bool?>)GetValue(PreEditingProperty);
+            set
+            {
+                SetValue(PreEditingProperty, value);
+            }
+        }
+
+        public bool ShowEmpty
+        {
+            get => (bool)GetValue(ShowEmptyProperty);
+            set
+            {
+                SetValue(ShowEmptyProperty, value);
+            }
+        }
+    }
+}
diff --git a/HMI.UI/HMI.UI.csproj b/HMI.UI/HMI.UI.csproj
index a36b6f6..8415c58 100644
--- a/HMI.UI/HMI.UI.csproj
+++ b/HMI.UI/HMI.UI.csproj
@@ -83,6 +83,7 @@
     <Compile Include="Controls\KeyboradWindow.xaml.cs">
       <DependentUpon>KeyboradWindow.xaml</DependentUpon>
     </Compile>
+    <Compile Include="Controls\LightBall.cs" />
     <Compile Include="Controls\MultiCombox.xaml.cs">
       <DependentUpon>MultiCombox.xaml</DependentUpon>
     </Compile>
@@ -96,22 +97,45 @@
     <Compile Include="Controls\treegrid\Controls\TreeListView.cs" />
     <Compile Include="Controls\treegrid\TreeItemConverter.cs" />
     <Compile Include="Controls\treegrid\TreeNodeExtend.cs" />
+    <Compile Include="Controls\VisualHost.cs" />
+    <Compile Include="Converter\ReverseBoolConverter.cs" />
     <Compile Include="Converter\IndentConverter.cs" />
     <Compile Include="Converter\VisibilityBoolConverter.cs" />
     <Compile Include="Converter\DataGridRowFlagConverter.cs" />
     <Compile Include="Converter\DataGridRowNumberConverter.cs" />
     <Compile Include="Converter\PathImageConverter.cs" />
     <Compile Include="Controls\NumberInput.cs" />
+    <Compile Include="DpUtil\CanvasHelper.cs" />
     <Compile Include="DpUtil\DataGridHelper.cs" />
     <Compile Include="DpUtil\DataGridTextColumnHelper.cs" />
     <Compile Include="DpUtil\FormGroupHelper.cs" />
     <Compile Include="DpUtil\GridHelper.cs" />
+    <Compile Include="DpUtil\Resize.cs" />
     <Compile Include="DpUtil\StackPanelHelper.cs" />
     <Compile Include="DpUtil\TreeViewHelper.cs" />
     <Compile Include="DpUtil\UniformGridHelper.cs" />
+    <Compile Include="EditBox.cs" />
     <Compile Include="Entity\ScannerConfig.cs" />
+    <Compile Include="MaskControl.xaml.cs">
+      <DependentUpon>MaskControl.xaml</DependentUpon>
+    </Compile>
     <Compile Include="MaskWindow.xaml.cs">
       <DependentUpon>MaskWindow.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="MDI\BottomBar.xaml.cs">
+      <DependentUpon>BottomBar.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="MDI\InnerWindow.xaml.cs">
+      <DependentUpon>InnerWindow.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="MDI\MinInnerWindow.xaml.cs">
+      <DependentUpon>MinInnerWindow.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="MDI\Workspace.xaml.cs">
+      <DependentUpon>Workspace.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="RequirePasswordWindow.xaml.cs">
+      <DependentUpon>RequirePasswordWindow.xaml</DependentUpon>
     </Compile>
     <Compile Include="ScannerSetting.xaml.cs">
       <DependentUpon>ScannerSetting.xaml</DependentUpon>
@@ -120,11 +144,28 @@
       <DependentUpon>BaseWindow.xaml</DependentUpon>
     </Compile>
     <Compile Include="DpUtil\DpPlugin.cs" />
+    <Compile Include="TableComboBox.cs" />
     <Compile Include="Util\IValueValidator.cs" />
     <Compile Include="Util\MaskHolder.cs" />
     <Compile Include="Util\MsgBoxHelper.cs" />
     <Compile Include="Util\MsgBoxHelper2.cs" />
     <Compile Include="Util\ToastHelper.cs" />
+    <Page Include="RequirePasswordWindow.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+    <Page Include="Style\PanuonListView.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+    <Page Include="Style\ScrollBarResource.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+    <Page Include="Themes\Generic.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
     <Resource Include="Resources\Font\Font.xaml">
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
@@ -153,7 +194,27 @@
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
     </Page>
+    <Page Include="MaskControl.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
     <Page Include="MaskWindow.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="MDI\BottomBar.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+    <Page Include="MDI\InnerWindow.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+    <Page Include="MDI\MinInnerWindow.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+    <Page Include="MDI\Workspace.xaml">
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>
@@ -172,7 +233,6 @@
     <Page Include="Style\PanuonResource.xaml">
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
-      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Page>
     <Page Include="Style\BaseWindow.xaml">
       <SubType>Designer</SubType>
@@ -181,17 +241,14 @@
     <Page Include="Style\Colors.xaml">
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
-      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Page>
     <Page Include="Style\FormStyle.xaml">
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
-      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Page>
     <Page Include="Style\WindowResources.xaml">
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
-      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Page>
   </ItemGroup>
   <ItemGroup>
@@ -233,6 +290,14 @@
       <Name>HMI.Util</Name>
     </ProjectReference>
   </ItemGroup>
-  <ItemGroup />
+  <ItemGroup>
+    <Resource Include="Resources\Icon\dot.svg" />
+  </ItemGroup>
+  <ItemGroup>
+    <Resource Include="Resources\Icon\dot.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <Resource Include="Resources\Icon\dot2.png" />
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
 </Project>
\ No newline at end of file
diff --git a/HMI.UI/MDI/BottomBar.xaml b/HMI.UI/MDI/BottomBar.xaml
new file mode 100644
index 0000000..5928ee7
--- /dev/null
+++ b/HMI.UI/MDI/BottomBar.xaml
@@ -0,0 +1,15 @@
+锘�<UserControl x:Class="HMI.UI.MDI.BottomBar"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:HMI.UI.MDI"
+             mc:Ignorable="d" 
+             d:DesignHeight="30" d:DesignWidth="800">
+    <ScrollViewer HorizontalScrollBarVisibility="Auto" Background="Transparent" VerticalScrollBarVisibility="Hidden">
+        <StackPanel x:Name="stackPanel" Orientation="Horizontal" HorizontalAlignment="Stretch">
+
+        </StackPanel>
+    </ScrollViewer>
+    
+</UserControl>
diff --git a/HMI.UI/MDI/BottomBar.xaml.cs b/HMI.UI/MDI/BottomBar.xaml.cs
new file mode 100644
index 0000000..2808597
--- /dev/null
+++ b/HMI.UI/MDI/BottomBar.xaml.cs
@@ -0,0 +1,29 @@
+锘縰sing System.Windows;
+using System.Windows.Controls;
+
+namespace HMI.UI.MDI
+{
+    /// <summary>
+    /// BottomBar.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class BottomBar : UserControl
+    {
+        public BottomBar()
+        {
+            Visibility = Visibility.Collapsed;
+            InitializeComponent();
+        }
+
+        public void AddMinWindow(MinInnerWindow min)
+        {
+            stackPanel.Children.Add(min);
+            Visibility = Visibility.Visible;
+        }
+
+        public void RemoveMinWindow(MinInnerWindow min)
+        {
+            stackPanel.Children.Remove(min);
+            Visibility = stackPanel.Children.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
+        }
+    }
+}
diff --git a/HMI.UI/MDI/InnerWindow.xaml b/HMI.UI/MDI/InnerWindow.xaml
new file mode 100644
index 0000000..856e1e7
--- /dev/null
+++ b/HMI.UI/MDI/InnerWindow.xaml
@@ -0,0 +1,67 @@
+锘�<UserControl x:Class="HMI.UI.MDI.InnerWindow"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:HMI.UI.MDI"
+             mc:Ignorable="d" 
+             HorizontalAlignment="Stretch"
+             HorizontalContentAlignment="Stretch"
+             MouseLeftButtonDown="Grid_MouseLeftButtonDown"
+             SizeChanged="UserControl_SizeChanged"
+             Background="#FFF"
+             Focusable="True">
+    <UserControl.Resources>
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <ResourceDictionary Source="/HMI.UI;component/Style/colors.xaml"/>
+                <ResourceDictionary Source="/HMI.UI;component/Style/WindowResources.xaml"/>
+            </ResourceDictionary.MergedDictionaries>
+
+            <Style x:Key="SliderThumbStyle" TargetType="{x:Type Thumb}">
+                <Setter Property="Template">
+                    <Setter.Value>
+                        <ControlTemplate>
+                            <Label VerticalContentAlignment="Center"
+                                   Foreground="{Binding HeaderFontColor}" Content="{Binding Title}"/>
+                        </ControlTemplate>
+                    </Setter.Value>
+                </Setter>
+            </Style>
+        </ResourceDictionary>
+    </UserControl.Resources>
+
+    <UserControl.Template>
+        <ControlTemplate TargetType="local:InnerWindow">
+            <AdornerDecorator x:Name="PART_Adorner" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
+                <Grid GotFocus="Grid_GotFocus">
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="30"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+
+                    <Grid Background="{StaticResource ReVerBar}">
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="*"/>
+                            <ColumnDefinition Width="Auto"/>
+                        </Grid.ColumnDefinitions>
+
+                        <Thumb Style="{StaticResource SliderThumbStyle}" Grid.Column="0" x:Name="thumb1" DragDelta="DragDelta" 
+                           DragStarted="DragStarted" DragCompleted="DragCompleted"/>
+
+                        <StackPanel Grid.Column="1" Orientation="Horizontal">
+                            <Button Style="{StaticResource ChildWindowButton}" Background="Transparent" Click="Lock_Click">&#xe881;</Button>
+                            <Button Style="{StaticResource ChildWindowButton}" Background="Transparent" Click="Min_Click">&#xe650;</Button>
+                            <Button Style="{StaticResource ChildWindowButton}" Background="Transparent" Click="Max_Click" >&#xe602;</Button>
+                            <!--<Button Style="{StaticResource ChildWindowCloseButton}" Background="Transparent" Click="CLose_Click">&#xe609;</Button>-->
+                        </StackPanel>
+                    </Grid>
+
+                    <Border Grid.Row="1" Background="LightGray" VerticalAlignment="Stretch" BorderThickness="2,0,2,2" BorderBrush="{Binding HeaderBackground}">
+                        <ContentPresenter HorizontalAlignment="Stretch" VerticalAlignment="Stretch"></ContentPresenter>
+                    </Border>
+                </Grid>
+            </AdornerDecorator>
+        </ControlTemplate>
+    </UserControl.Template>
+</UserControl>
\ No newline at end of file
diff --git a/HMI.UI/MDI/InnerWindow.xaml.cs b/HMI.UI/MDI/InnerWindow.xaml.cs
new file mode 100644
index 0000000..ad0a0f4
--- /dev/null
+++ b/HMI.UI/MDI/InnerWindow.xaml.cs
@@ -0,0 +1,267 @@
+锘縰sing HMI.UI.DpUtil;
+using System;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+
+namespace HMI.UI.MDI
+{
+    /// <summary>
+    /// InnerWindow.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class InnerWindow : UserControl, INotifyPropertyChanged
+    {
+        #region INotifyPropertyChanged
+        public virtual event PropertyChangedEventHandler PropertyChanged;
+
+        protected virtual void RaisePropertyChanged(string name)
+        {
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+        }
+
+        protected void RaiseAndSetIfChanged<T>(ref T old, T @new, [CallerMemberName] string propertyName = null)
+        {
+            old = @new;
+            if (propertyName != null)
+            {
+                RaisePropertyChanged(propertyName);
+            }
+        }
+        #endregion
+
+        #region 灞炴��
+
+        #region Common
+        public DragType DragType { get; set; } = DragType.Canvas;
+        public CanvasPosition canvasPosition;
+        private Canvas canvas;
+        public WindowState WindowState { get; set; } = WindowState.Normal;
+        public AdornerDecorator ResizeAdornerDecorator
+        {
+            get
+            {
+                return this.Template.FindName("PART_Adorner", this) as AdornerDecorator;
+            }
+        }
+
+        private string _Title;
+        public string Title
+        {
+            get { return _Title; }
+            set => RaiseAndSetIfChanged(ref _Title, value);
+        }
+
+        public int Zindex
+        {
+            get
+            {
+                return Canvas.GetZIndex(this);
+            }
+            set
+            {
+                Canvas.SetZIndex(this, value);
+            }
+        }
+        #endregion
+
+        #region 浜嬩欢
+        public event EventHandler MinEvent;
+        public event EventHandler MaxEvent;
+        public event EventHandler LockEvent;
+        public event EventHandler CloseEvent;
+        public event EventHandler ZIndexEvent;
+        #endregion
+
+        #region DependencyProperty
+        public Brush HeaderBackground
+        {
+            get { return (Brush)GetValue(HeaderBackgroundProperty); }
+            set { SetValue(HeaderBackgroundProperty, value); }
+        }
+
+        public static readonly DependencyProperty HeaderBackgroundProperty =
+            DependencyProperty.Register("HeaderBackground", typeof(Brush), typeof(InnerWindow),
+                new PropertyMetadata(new SolidColorBrush(Color.FromRgb(54, 146, 225))));
+
+        public Brush HeaderFontColor
+        {
+            get { return (Brush)GetValue(HeaderFontColorProperty); }
+            set { SetValue(HeaderFontColorProperty, value); }
+        }
+
+        public static readonly DependencyProperty HeaderFontColorProperty =
+            DependencyProperty.Register("HeaderFontColor", typeof(Brush), typeof(InnerWindow),
+                new PropertyMetadata(new SolidColorBrush(Color.FromRgb(255, 255, 225))));
+        #endregion
+
+        #endregion
+
+        public InnerWindow()
+        {
+            DataContext = this;
+            InitializeComponent();
+            Loaded += InnerWindow_Loaded;
+        }
+
+        public static readonly DependencyProperty CanResizeProperty = DependencyProperty.Register(
+                            "CanResize",
+                            typeof(bool),
+                            typeof(InnerWindow),
+                            new FrameworkPropertyMetadata(true, new PropertyChangedCallback(OnCanResizeChanged)));
+
+        private static void OnCanResizeChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
+        {
+            bool val = (bool)e.NewValue;
+            Resize.SetIsResizeable(dp, val);
+            Resize.SetCanMove(dp, val);
+        }
+
+        public bool CanResize
+        {
+            get => (bool)GetValue(CanResizeProperty);
+            set => SetValue(CanResizeProperty, value);
+        }
+
+        private void InnerWindow_Loaded(object sender, RoutedEventArgs e)
+        {
+            Loaded -= InnerWindow_Loaded;
+        }
+
+        #region 鎷栨嫿绉诲姩鐩稿叧
+        private void DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
+        {
+            if (!Resize.GetCanMove(this)) return;
+            switch (DragType)
+            {
+                case DragType.Canvas:
+                    {
+                        double offsetX = e.HorizontalChange;
+                        double offsetY = e.VerticalChange;
+
+                        Width = ActualWidth > canvas.ActualWidth ? canvas.ActualWidth : Width;
+                        Height = ActualHeight > canvas.ActualHeight ? canvas.ActualHeight : Height;
+
+                        double newLeft = Canvas.GetLeft(this) + e.HorizontalChange;
+                        newLeft = newLeft < 0 ? 0 : newLeft;
+
+
+                        double newTop = Canvas.GetTop(this) + e.VerticalChange;
+                        newTop = newTop < 0 ? 0 : newTop;
+
+                        double fixLeft = canvasPosition == null || canvasPosition.LimitWidth <= 0 ? ActualWidth : canvasPosition.LimitWidth;
+                        double fixTop = canvasPosition == null || canvasPosition.LimitHeight <= 0 ? ActualHeight : canvasPosition.LimitHeight;
+
+                        if (canvas != null)
+                        {
+                            newLeft = offsetX > 0 && newLeft > canvas.ActualWidth - fixLeft ? canvas.ActualWidth - fixLeft : newLeft;
+                            newTop = offsetY > 0 && newTop > canvas.ActualHeight - fixTop ? canvas.ActualHeight - fixTop : newTop;
+                        }
+
+                        Canvas.SetLeft(this, newLeft);
+                        Canvas.SetTop(this, newTop);
+                        break;
+                    }
+            }
+        }
+
+        private void DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
+        {
+            ZIndexEvent?.Invoke(this, null);
+            // thumb1.Background = Brushes.White;
+        }
+
+        private void DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
+        {
+            canvasPosition.Top = Canvas.GetTop(this);
+            canvasPosition.Left = Canvas.GetLeft(this);
+        }
+        #endregion
+
+        /// <summary>
+        /// 娣诲姞鍒扮敾甯冧笂
+        /// </summary>
+        /// <param name="canvas"></param>
+        public void AttachToCanvas(Canvas canvas)
+        {
+            canvas.Children.Add(this);
+            this.canvas = canvas;
+            if (ActualWidth < MinWidth)
+            {
+                Width = MinWidth;
+            }
+            if (ActualHeight < MinHeight)
+            {
+                Height = MinHeight;
+            }
+            Canvas.SetLeft(this, canvasPosition == null ? 0 : canvasPosition.Left);
+            Canvas.SetTop(this, canvasPosition == null ? 0 : canvasPosition.Top);
+            canvas.SizeChanged += Canvas_SizeChanged;
+        }
+
+        private void Canvas_SizeChanged(object sender, SizeChangedEventArgs e)
+        {
+            Size newSize = e.NewSize;
+            Size previousSize = e.PreviousSize;
+
+            double xRate = newSize.Width / previousSize.Width;
+            double yRate = newSize.Height / previousSize.Height;
+
+            Canvas.SetLeft(this, Canvas.GetLeft(this) * xRate);
+            Canvas.SetTop(this, Canvas.GetTop(this) * yRate);
+            this.Width = this.Width * xRate;
+            this.Height = this.Height * yRate;
+        }
+
+        private void Lock_Click(object sender, RoutedEventArgs e)
+        {
+            LockEvent?.Invoke(this, null);
+        }
+
+        private void Min_Click(object sender, RoutedEventArgs e)
+        {
+            MinEvent?.Invoke(this, null);
+        }
+
+        private void Max_Click(object sender, RoutedEventArgs e)
+        {
+            MaxEvent?.Invoke(this, null);
+        }
+
+        private void CLose_Click(object sender, RoutedEventArgs e)
+        {
+
+        }
+
+        private void Grid_GotFocus(object sender, RoutedEventArgs e)
+        {
+
+        }
+
+        private void Grid_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            this.Focus();
+        }
+
+        private void UserControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+        {
+
+        }
+
+        private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
+        {
+
+        }
+    }
+
+    public enum DragType
+    {
+        Canvas = 0,
+        Dock = 1
+    }
+
+}
+
diff --git a/HMI.UI/MDI/MinInnerWindow.xaml b/HMI.UI/MDI/MinInnerWindow.xaml
new file mode 100644
index 0000000..c0804f0
--- /dev/null
+++ b/HMI.UI/MDI/MinInnerWindow.xaml
@@ -0,0 +1,24 @@
+锘�<UserControl x:Class="HMI.UI.MDI.MinInnerWindow"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:HMI.UI.MDI"
+             mc:Ignorable="d" 
+             Height="30"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <UserControl.Resources>
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <ResourceDictionary Source="/HMI.UI;component/Style/colors.xaml"/>
+                <ResourceDictionary Source="/HMI.UI;component/Style/WindowResources.xaml"/>
+            </ResourceDictionary.MergedDictionaries>
+        </ResourceDictionary>
+    </UserControl.Resources>
+
+    <StackPanel Background="{StaticResource VerBar}" Orientation="Horizontal">
+        <Label BorderBrush="{StaticResource PrimaryColor}" Foreground="#FFF" BorderThickness="2,0,0,0" VerticalContentAlignment="Center" ToolTip="{Binding Title}" Content="{Binding Title}"/>
+        <Button Style="{StaticResource ChildWindowButton}" Background="Transparent" Click="Max_Click" >&#xe602;</Button>
+        <!--<Button Style="{StaticResource ChildWindowCloseButton}" Click="CLose_Click">&#xe609;</Button>-->
+    </StackPanel>
+</UserControl>
diff --git a/HMI.UI/MDI/MinInnerWindow.xaml.cs b/HMI.UI/MDI/MinInnerWindow.xaml.cs
new file mode 100644
index 0000000..91a94c8
--- /dev/null
+++ b/HMI.UI/MDI/MinInnerWindow.xaml.cs
@@ -0,0 +1,42 @@
+锘縰sing System;
+using System.Windows.Controls;
+
+namespace HMI.UI.MDI
+{
+    /// <summary>
+    /// MinInnerWindow.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class MinInnerWindow : UserControl
+    {
+        public event EventHandler MaxEvent;
+        public event EventHandler CloseEvent;
+        public InnerWindow Inner { get; set; }
+
+        public string Title
+        {
+            get => Inner == null ? null : Inner.Title;
+        }
+
+        public MinInnerWindow() : this(null)
+        {
+            
+        }
+
+        public MinInnerWindow(InnerWindow inner)
+        {
+            DataContext = this;
+            Inner = inner;
+            InitializeComponent();
+        }
+
+        private void Max_Click(object sender, System.Windows.RoutedEventArgs e)
+        {
+            MaxEvent?.Invoke(this, null);
+        }
+
+        private void CLose_Click(object sender, System.Windows.RoutedEventArgs e)
+        {
+            CloseEvent?.Invoke(this, null);
+        }
+    }
+}
diff --git a/HMI.UI/MDI/Workspace.xaml b/HMI.UI/MDI/Workspace.xaml
new file mode 100644
index 0000000..d926fbe
--- /dev/null
+++ b/HMI.UI/MDI/Workspace.xaml
@@ -0,0 +1,34 @@
+锘�<UserControl x:Class="HMI.UI.MDI.Workspace"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:HMI.UI.MDI"
+             mc:Ignorable="d"
+             x:Name="workspace"
+             xmlns:hcu="clr-namespace:HMI.Core.Util;assembly=HMI.Core"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <UserControl.Resources>
+        <hcu:BindingProxy x:Key="BindingProxy" Data="{Binding ElementName=workspace}"/>
+    </UserControl.Resources>
+    <Grid>
+        <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
+            <Canvas x:Name="WorkArea" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
+                <Canvas.ContextMenu>
+                    <ContextMenu DataContext="{Binding Data,Source={StaticResource BindingProxy}}">
+                        <MenuItem Header="淇濆瓨甯冨眬" Click="MenuItem_Click"></MenuItem>
+                        <MenuItem Header="閿佸畾甯冨眬"
+                  IsCheckable="True"
+                  IsChecked="{Binding IsLocked,Mode=OneWay}" 
+                  Click="MenuItem_Checked"></MenuItem>
+                    </ContextMenu>
+                </Canvas.ContextMenu>
+                <Image Canvas.Left="0" Canvas.Top="0" Canvas.Right="0" Canvas.Bottom="0"
+Stretch="None" Panel.ZIndex="-999" Source="/HMI.UI;component/Resources/Icon/dot2.png">
+
+                </Image>
+                <local:BottomBar x:Name="BottomBar" Canvas.Bottom="0" Panel.ZIndex="9999999"/>
+            </Canvas>
+        </ScrollViewer>
+    </Grid>
+</UserControl>
diff --git a/HMI.UI/MDI/Workspace.xaml.cs b/HMI.UI/MDI/Workspace.xaml.cs
new file mode 100644
index 0000000..01ae0a9
--- /dev/null
+++ b/HMI.UI/MDI/Workspace.xaml.cs
@@ -0,0 +1,366 @@
+锘縰sing HMI.Core.Util;
+using HMI.UI.Converter;
+using HMI.UI.DpUtil;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+
+namespace HMI.UI.MDI
+{
+    public class WorkspaceSetting
+    {
+        public bool IsLocked { get; set; }
+        public List<Position> positions { get; set; } = new List<Position>();
+    }
+    public class Position
+    {
+        public string Key { get; set; }
+        public double Left { get; set; }
+        public double Top { get; set; }
+        public double Width { get; set; }
+        public double Height { get; set; }
+    }
+    /// <summary>
+    /// Workspace.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class Workspace : UserControl
+    {
+
+        public Workspace()
+        {
+            InitializeComponent();
+        }
+
+        private WorkspaceSetting setting;
+
+        private Dictionary<string, InnerWindow> nameMap
+            = new Dictionary<string, InnerWindow>();
+
+        private Dictionary<string, Position> positionMap
+            = new Dictionary<string, Position>();
+
+        public static readonly DependencyProperty SettingPathProperty = DependencyProperty.Register(
+                            "SettingPath",
+                            typeof(string),
+                            typeof(Workspace),
+                            new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnSettingChanged)));
+
+        private static void OnSettingChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
+        {
+            if (dp is Workspace ws && e.Property == SettingPathProperty)
+            {
+                try
+                {
+                    if (!File.Exists(e.NewValue.ToString()))
+                    {
+                        throw new Exception("Empty Workspace Setting.");
+                    }
+                    ws.setting = XmlHelper.DeserializeFromXml<WorkspaceSetting>(e.NewValue.ToString());
+                    if (ws?.setting != null)
+                    {
+                        ws.IsLocked = ws.setting.IsLocked;
+                        ws.positionMap = ws.setting.positions.ToDictionary(p => p.Key, p => p);
+                    }
+                    else
+                    {
+                        ws.positionMap.Clear();
+                    }
+                }
+                catch
+                {
+                    ws.setting = new WorkspaceSetting();
+                    ws.positionMap.Clear();
+                }
+            }
+        }
+
+        private CanvasPosition GetPositionByIndex(int index)
+        {
+            double step = 15;
+            return new CanvasPosition
+            {
+                Left = (index + 1) * step,
+                Top = (index + 1) * step,
+                Width = 0.75 * ActualWidth,
+                Height = 0.75 * ActualHeight
+            };
+        }
+
+        public string SettingPath
+        {
+            get => (string)GetValue(SettingPathProperty);
+            set => SetValue(SettingPathProperty, value);
+        }
+
+        private int InnerWindowCount = 0;
+
+        public void AddInnerWindow(InnerWindow innerWindow)
+        {
+            AddInnerWindow(innerWindow, GetPositionByIndex(WorkArea.Children.Count));
+        }
+
+        public void AddInnerWindow(InnerWindow innerWindow, CanvasPosition canvasPosition)
+        {
+            HandleInnerWindow(innerWindow);
+            UpdatePosition(innerWindow, canvasPosition);
+        }
+
+        public void AddInnerWindow(string key, InnerWindow innerWindow)
+        {
+            if (nameMap.ContainsKey(key))
+            {
+                throw new Exception("Key Exists");
+            }
+            if (positionMap.ContainsKey(key))
+            {
+                var position = positionMap[key];
+                AddInnerWindow(innerWindow, new CanvasPosition
+                {
+                    Left = position.Left * WorkArea.ActualWidth,
+                    Top = position.Top * WorkArea.ActualHeight,
+                    Width = position.Width * WorkArea.ActualWidth,
+                    Height = position.Height * WorkArea.ActualHeight
+                });
+            }
+            else
+            {
+                AddInnerWindow(innerWindow);
+            }
+            nameMap.Add(key, innerWindow);
+        }
+
+        public void UpdatePosition(InnerWindow inner, CanvasPosition position)
+        {
+            inner.canvasPosition = position;
+            inner.Width = position.Width;
+            inner.Height = position.Height;
+            Canvas.SetLeft(inner, position.Left);
+            Canvas.SetTop(inner, position.Top);
+        }
+
+        /// <summary>
+        /// 璁剧疆InnerWindow
+        /// </summary>
+        /// <param name="inner"></param>
+        private void HandleInnerWindow(InnerWindow inner)
+        {
+            inner.MinEvent += InnerWindowMin;
+            inner.MaxEvent += InnerWindowMax;
+            inner.LockEvent += Inner_LockEvent;
+            inner.ZIndexEvent += Inner_ZIndexEvent;
+            inner.GotFocus += Inner_GotFocus;
+            inner.AttachToCanvas(WorkArea);
+            inner.CanResize = !IsLoaded;
+            Binding binding = new Binding("IsLocked")
+            {
+                Source = this,
+                Mode = BindingMode.OneWay,
+                Converter = new ReverseBoolConverter(),
+                UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
+            };
+            inner.SetBinding(InnerWindow.CanResizeProperty, binding);
+            inner.Zindex = InnerWindowCount++;
+        }
+
+        private void Inner_LockEvent(object sender, EventArgs e)
+        {
+            var inner = sender as InnerWindow;
+            var left = Canvas.GetLeft(inner) / WorkArea.ActualWidth;
+            var top = Canvas.GetTop(inner) / WorkArea.ActualHeight;
+            var width = inner.ActualWidth / WorkArea.ActualWidth;
+            var height = inner.ActualHeight / WorkArea.ActualHeight;
+
+            positionMap[inner.Title] = new Position
+            {
+                Left = left,
+                Top = top,
+                Width = width,
+                Height = height
+            };
+
+            List<Position> ps = new List<Position>();
+            foreach (var key in positionMap.Keys)
+            {
+                positionMap[key].Key = key;
+                ps.Add(positionMap[key]);
+            }
+
+            XmlHelper.SerializeToXml(SettingPath, ps);
+        }
+
+        /// <summary>
+        /// 搴曢儴MinInnerWindow澶勭悊
+        /// </summary>
+        /// <param name="inner"></param>
+        private void HandleMinInnerWindow(InnerWindow inner)
+        {
+            MinInnerWindow min = new MinInnerWindow(inner);
+            min.MaxEvent += MinInnerWindowMax;
+            min.CloseEvent += MinInnerWindowClose;
+            BottomBar.AddMinWindow(min);
+        }
+
+        /// <summary>
+        /// InnerWindow鐒︾偣浜嬩欢
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        private void Inner_GotFocus(object sender, RoutedEventArgs e)
+        {
+            CanvasHelper.SetTopZIndex(sender as InnerWindow, WorkArea, typeof(InnerWindow));
+        }
+
+        /// <summary>
+        /// InnerWindow ZIndex鍒囨崲浜嬩欢
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        private void Inner_ZIndexEvent(object sender, EventArgs e)
+        {
+            CanvasHelper.SetTopZIndex(sender as InnerWindow, WorkArea, typeof(InnerWindow));
+        }
+
+        /// <summary>
+        /// InnerWindow鏈�灏忓寲浜嬩欢
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        private void InnerWindowMin(object sender, System.EventArgs e)
+        {
+            InnerWindow inner = sender as InnerWindow;
+            inner.Visibility = Visibility.Collapsed;
+            HandleMinInnerWindow(inner);
+        }
+
+        /// <summary>
+        /// InnerWindow鏈�澶у寲浜嬩欢
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        private void InnerWindowMax(object sender, System.EventArgs e)
+        {
+            InnerWindow inner = sender as InnerWindow;
+            if (inner.WindowState == WindowState.Maximized)
+            {
+                Canvas.SetLeft(inner, inner.canvasPosition.Left);
+                Canvas.SetTop(inner, inner.canvasPosition.Top);
+
+                inner.Width = inner.canvasPosition.Width;
+                inner.Height = inner.canvasPosition.Height;
+                inner.WindowState = WindowState.Normal;
+            }
+            else
+            {
+                inner.canvasPosition.Left = Canvas.GetLeft(inner);
+                inner.canvasPosition.Top = Canvas.GetTop(inner);
+                inner.canvasPosition.Width = inner.ActualWidth;
+                inner.canvasPosition.Height = inner.ActualHeight;
+                Canvas.SetLeft(inner, 0);
+                Canvas.SetTop(inner, 0);
+                inner.Width = WorkArea.ActualWidth;
+                inner.Height = WorkArea.ActualHeight;
+                inner.WindowState = WindowState.Maximized;
+            }
+        }
+
+        /// <summary>
+        /// 搴曢儴MinInnerWindow灞曞紑
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        private void MinInnerWindowMax(object sender, System.EventArgs e)
+        {
+            MinInnerWindow min = sender as MinInnerWindow;
+            min.Inner.Visibility = Visibility.Visible;
+            CanvasHelper.SetTopZIndex(min.Inner, WorkArea, typeof(InnerWindow));
+            BottomBar.RemoveMinWindow(min);
+        }
+
+        /// <summary>
+        /// 搴曢儴MinInnerWin鍏抽棴
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        private void MinInnerWindowClose(object sender, System.EventArgs e)
+        {
+            MinInnerWindow min = sender as MinInnerWindow;
+            BottomBar.RemoveMinWindow(min);
+        }
+
+        private void MenuItem_Click(object sender, RoutedEventArgs e)
+        {
+            foreach (var inner in nameMap.Values)
+            {
+                var left = Canvas.GetLeft(inner) / WorkArea.ActualWidth;
+                var top = Canvas.GetTop(inner) / WorkArea.ActualHeight;
+                var width = inner.ActualWidth / WorkArea.ActualWidth;
+                var height = inner.ActualHeight / WorkArea.ActualHeight;
+                positionMap[inner.Title] = new Position
+                {
+                    Left = left,
+                    Top = top,
+                    Width = width,
+                    Height = height
+                };
+            }
+            List<Position> ps = new List<Position>();
+            foreach (var key in positionMap.Keys)
+            {
+                positionMap[key].Key = key;
+                ps.Add(positionMap[key]);
+            }
+            setting.positions = ps;
+            XmlHelper.SerializeToXml(SettingPath, setting);
+        }
+
+        public static readonly DependencyProperty IsLockedProperty = DependencyProperty.Register(
+                            "IsLocked",
+                            typeof(bool),
+                            typeof(Workspace),
+                            new FrameworkPropertyMetadata(false));
+
+        public bool IsLocked
+        {
+            get => (bool)GetValue(IsLockedProperty);
+            private set
+            {
+                if (setting != null)
+                {
+                    setting.IsLocked = value;
+                }
+                SetValue(IsLockedProperty, value);
+            }
+        }
+
+        private void MenuItem_Checked(object sender, RoutedEventArgs e)
+        {
+            IsLocked = !IsLocked;
+            foreach (var inner in nameMap.Values)
+            {
+                var left = Canvas.GetLeft(inner) / WorkArea.ActualWidth;
+                var top = Canvas.GetTop(inner) / WorkArea.ActualHeight;
+                var width = inner.ActualWidth / WorkArea.ActualWidth;
+                var height = inner.ActualHeight / WorkArea.ActualHeight;
+                positionMap[inner.Title] = new Position
+                {
+                    Left = left,
+                    Top = top,
+                    Width = width,
+                    Height = height
+                };
+            }
+            List<Position> ps = new List<Position>();
+            foreach (var key in positionMap.Keys)
+            {
+                positionMap[key].Key = key;
+                ps.Add(positionMap[key]);
+            }
+            setting.positions = ps;
+            XmlHelper.SerializeToXml(SettingPath, setting);
+        }
+    }
+}
diff --git a/HMI.UI/MaskControl.xaml b/HMI.UI/MaskControl.xaml
new file mode 100644
index 0000000..4c7fb02
--- /dev/null
+++ b/HMI.UI/MaskControl.xaml
@@ -0,0 +1,58 @@
+锘�<UserControl x:Class="HMI.UI.MaskControl"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:HMI.UI"
+             FontSize="14"
+             mc:Ignorable="d">
+    <Grid>
+        <Canvas Grid.Row="0" Grid.RowSpan="5" Visibility="Collapsed" Background="#AAFFFFFF" x:Name="pausePanel" 
+                SizeChanged="pausePanel_Loaded">
+            <Border MinWidth="220" MaxWidth="300" BorderBrush="#000" Background="#FFF" BorderThickness="0.5" CornerRadius="5" x:Name="visualModal">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="Auto"/>
+                    </Grid.RowDefinitions>
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition />
+                    </Grid.ColumnDefinitions>
+                    <Border Padding="10,5,0,5" Background="{StaticResource PrimaryColor}"
+                   CornerRadius="5,5,0,0">
+                        <Label Padding="2" Margin="0" Foreground="#FFF" x:Name="titleLabel" Content="璇风◢鍚�"/>
+                    </Border>
+                    <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center"
+                                VerticalAlignment="Center"
+                                Margin="20">
+
+                        <TextBlock FontSize="45" 
+           FontFamily="{StaticResource iconfont}" Foreground="{StaticResource PrimaryColor}" 
+           RenderTransformOrigin="0.5,0.5"
+           VerticalAlignment="Center" Text="&#xe891;">
+                            <TextBlock.RenderTransform>
+                                <RotateTransform x:Name="MyAnimatedTransform" Angle="0" />
+                            </TextBlock.RenderTransform>
+                            <TextBlock.Triggers>
+                                <EventTrigger RoutedEvent="TextBlock.Loaded">
+                                    <BeginStoryboard>
+                                        <Storyboard>
+                                            <DoubleAnimation 
+                    Storyboard.TargetName="MyAnimatedTransform"
+                    Storyboard.TargetProperty="(RotateTransform.Angle)"
+                            From="0" To="360" Duration="0:0:3"
+                            RepeatBehavior="Forever"/>
+                                        </Storyboard>
+                                    </BeginStoryboard>
+                                </EventTrigger>
+                            </TextBlock.Triggers>
+                        </TextBlock>
+                        <TextBlock Grid.Row="1" Margin="20,0,0,0" Grid.Column="1" x:Name="contentLabel" 
+                                   VerticalAlignment="Center" TextWrapping="Wrap"/>
+                    </StackPanel>                    
+                </Grid>
+            </Border>
+        </Canvas>
+    </Grid>
+</UserControl>
diff --git a/HMI.UI/MaskControl.xaml.cs b/HMI.UI/MaskControl.xaml.cs
new file mode 100644
index 0000000..25e649c
--- /dev/null
+++ b/HMI.UI/MaskControl.xaml.cs
@@ -0,0 +1,63 @@
+锘縰sing System;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace HMI.UI
+{
+    /// <summary>
+    /// MaskControl.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class MaskControl : UserControl
+    {
+        public static MaskControl Instance;
+
+        public static MaskControl GetInstance()
+        {
+            if (Instance == null)
+            {
+                Instance = new MaskControl();
+            }
+            return Instance;
+        }
+
+        public MaskControl()
+        {
+            InitializeComponent();
+        }
+
+        private void pausePanel_Loaded(object sender, RoutedEventArgs e)
+        {
+            var can = pausePanel;
+            var modal = visualModal;
+            Canvas.SetLeft(modal, (pausePanel.ActualWidth - visualModal.ActualWidth) / 2);
+            Canvas.SetTop(modal, (pausePanel.ActualHeight - visualModal.ActualHeight) / 2.5);
+        }
+
+        public void ShowMask(UserControl control, Action action, Action<Exception> reject = null, string title = null, string content = null)
+        {
+            this.titleLabel.Content = title;
+            this.contentLabel.Text = content;
+            pausePanel.Visibility = Visibility.Visible;
+            Task.Run(() =>
+            {
+                try
+                {
+                    action();
+                }
+                catch (Exception ex)
+                {
+                    if (reject != null) reject(ex);
+                }
+                finally
+                {
+                    Dispatcher.BeginInvoke(new Action(() =>
+                    {
+                        pausePanel.Visibility = Visibility.Collapsed;
+                    }));
+                }
+            });
+        }
+
+    }
+}
diff --git a/HMI.UI/RequirePasswordWindow.xaml b/HMI.UI/RequirePasswordWindow.xaml
new file mode 100644
index 0000000..92c0bce
--- /dev/null
+++ b/HMI.UI/RequirePasswordWindow.xaml
@@ -0,0 +1,33 @@
+锘�<pu:WindowX x:Class="HMI.Base.RequirePasswordWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        xmlns:local="clr-namespace:HMI.Base"
+        mc:Ignorable="d"
+        Style="{StaticResource ModalWindow}"
+        WindowStartupLocation="CenterOwner"
+        xmlns:pu="https://opensource.panuon.com/wpf-ui"
+        xmlns:dp="clr-namespace:HMI.UI.DpUtil"
+        Loaded="WindowX_Loaded"
+        SizeToContent="Height"
+        FontSize="14"
+        pu:WindowXModalDialog.OKButtonContent="纭畾"
+        Title="璇疯緭鍏ュ瘑鐮�" Width="500">
+    <Grid dp:GridHelper.Space="10">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+        </Grid.RowDefinitions>
+        <pu:FormGroup Header="褰撳墠鐢ㄦ埛" Orientation="Horizontal" Style="{StaticResource InputFormGroupStyle}">
+            <TextBlock Text="{Binding Username}"/>
+        </pu:FormGroup>
+        <pu:FormGroup Grid.Row="1" Header="瀵嗙爜" Orientation="Horizontal" 
+                      Style="{StaticResource RequiredInputFormGroupStyle}">
+            <PasswordBox x:Name="PwdPassword" VerticalContentAlignment="Center"
+             Style="{StaticResource SignInPasswordBoxStyle}" 
+             pu:PasswordBoxHelper.Watermark="{DynamicResource App.Password}"/>
+        </pu:FormGroup>
+    </Grid>
+</pu:WindowX>
diff --git a/HMI.UI/RequirePasswordWindow.xaml.cs b/HMI.UI/RequirePasswordWindow.xaml.cs
new file mode 100644
index 0000000..b3472d6
--- /dev/null
+++ b/HMI.UI/RequirePasswordWindow.xaml.cs
@@ -0,0 +1,49 @@
+锘縰sing HMI.Core.Entity;
+using HMI.UI.Util;
+using Panuon.WPF.UI;
+using System.ComponentModel;
+
+namespace HMI.Base
+{
+    /// <summary>
+    /// RequirePasswordWindow.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class RequirePasswordWindow : WindowX
+    {
+        public RequirePasswordWindow()
+        {
+            this.DataContext = this;
+            InitializeComponent();
+        }
+
+        private string username;
+        public string Username
+        {
+            get => username;
+            set
+            {
+                Set(ref username, value);
+            }
+        }
+
+        protected override void OnClosing(CancelEventArgs e)
+        {
+            //Press Continue Button
+            if (DialogResult == true)
+            {
+                if (!string.Equals(PwdPassword.Password, UserInfo.User.Password))
+                {
+                    ToastHelper.Error(this, "瀵嗙爜閿欒");
+                    e.Cancel = true;
+                }
+            }
+            base.OnClosing(e);
+        }
+
+        private void WindowX_Loaded(object sender, System.Windows.RoutedEventArgs e)
+        {
+            Username = UserInfo.User.Username;
+            PwdPassword.Password = string.Empty;
+        }
+    }
+}
diff --git a/HMI.UI/Resources/Font/iconfont.ttf b/HMI.UI/Resources/Font/iconfont.ttf
index e7b61af..5ab6b13 100644
--- a/HMI.UI/Resources/Font/iconfont.ttf
+++ b/HMI.UI/Resources/Font/iconfont.ttf
Binary files differ
diff --git a/HMI.UI/Resources/Icon/dot.png b/HMI.UI/Resources/Icon/dot.png
new file mode 100644
index 0000000..21021e6
--- /dev/null
+++ b/HMI.UI/Resources/Icon/dot.png
Binary files differ
diff --git a/HMI.UI/Resources/Icon/dot.svg b/HMI.UI/Resources/Icon/dot.svg
new file mode 100644
index 0000000..7864800
--- /dev/null
+++ b/HMI.UI/Resources/Icon/dot.svg
@@ -0,0 +1,12 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="1920mm" height="1080mm" viewBox="0 0 1920 1080">
+  <defs>
+    <!-- 鍒涘缓鐐归樀鍥炬 -->
+    <pattern id="dotPattern" patternUnits="userSpaceOnUse" width="5" height="5">
+      <!-- 姣忎釜鍗曞厓涓績缁樺埗鐩村緞0.5mm鐨勯粦鐐� -->
+      <circle cx="2.5" cy="2.5" r="0.25" fill="black"/>
+    </pattern>
+  </defs>
+  
+  <!-- 鐢ㄧ偣闃靛浘妗堝~鍏呮暣涓尯鍩� -->
+  <rect width="100%" height="100%" fill="url(#dotPattern)" fill-opacity="1"/>
+</svg>
\ No newline at end of file
diff --git a/HMI.UI/Resources/Icon/dot2.png b/HMI.UI/Resources/Icon/dot2.png
new file mode 100644
index 0000000..e2d568e
--- /dev/null
+++ b/HMI.UI/Resources/Icon/dot2.png
Binary files differ
diff --git a/HMI.UI/Style/FormStyle.xaml b/HMI.UI/Style/FormStyle.xaml
index b9a908f..24958cc 100644
--- a/HMI.UI/Style/FormStyle.xaml
+++ b/HMI.UI/Style/FormStyle.xaml
@@ -175,4 +175,14 @@
         <Setter Property="SelectedDateTime" Value="{x:Null}"/>
     </Style>
 
+    <Style x:Key="SignInPasswordBoxStyle" TargetType="PasswordBox" BasedOn="{StaticResource {x:Type PasswordBox}}">
+        <Setter Property="pu:PasswordBoxHelper.FocusedBorderBrush" Value="#6CBCEA" />
+        <Setter Property="pu:PasswordBoxHelper.FocusedShadowColor" Value="#6CBCEA" />
+        <Setter Property="pu:PasswordBoxHelper.CornerRadius" Value="2" />
+        <Setter Property="pu:PasswordBoxHelper.WatermarkForeground" Value="#4A4A4A" />
+        <Setter Property="pu:PasswordBoxHelper.FocusedWatermarkForeground" Value="#AAAAAA" />
+        <Setter Property="pu:PasswordBoxHelper.ClearButtonVisibility" Value="VisibleOnHoverAndNonnull" />
+        <Setter Property="Height" Value="35" />
+        <Setter Property="Padding" Value="10,0" />
+    </Style>
 </ResourceDictionary>
\ No newline at end of file
diff --git a/HMI.UI/Style/MsgBoxStyle.xaml b/HMI.UI/Style/MsgBoxStyle.xaml
index 1858f41..984e2cc 100644
--- a/HMI.UI/Style/MsgBoxStyle.xaml
+++ b/HMI.UI/Style/MsgBoxStyle.xaml
@@ -124,6 +124,7 @@
     <pu:ToastSetting x:Key="toastSuccess" Spacing="25">
         <pu:ToastSetting.LabelStyle>
             <Style TargetType="Label" BasedOn="{StaticResource {x:Static pu:Toast.LabelStyleKey}}">
+                <Setter Property="FontSize" Value="18" />
                 <Setter Property="Background" Value="{StaticResource SuccessColor}" />
                 <Setter Property="Foreground" Value="{StaticResource SuccessColorContrast}" />
             </Style>
@@ -133,6 +134,7 @@
     <pu:ToastSetting x:Key="toastWarning" Spacing="25">
         <pu:ToastSetting.LabelStyle>
             <Style TargetType="Label" BasedOn="{StaticResource {x:Static pu:Toast.LabelStyleKey}}">
+                <Setter Property="FontSize" Value="18" />
                 <Setter Property="Background" Value="{StaticResource WarningColor}" />
                 <Setter Property="Foreground" Value="{StaticResource SuccessColorContrast}" />
             </Style>
@@ -142,6 +144,7 @@
     <pu:ToastSetting x:Key="toastError" Spacing="25">
         <pu:ToastSetting.LabelStyle>
             <Style TargetType="Label" BasedOn="{StaticResource {x:Static pu:Toast.LabelStyleKey}}">
+                <Setter Property="FontSize" Value="18" />
                 <Setter Property="Background" Value="{StaticResource DangerColor}" />
                 <Setter Property="Foreground" Value="{StaticResource DangerColorContrast}" />
             </Style>
diff --git a/HMI.UI/Style/PanuonDataGrid.xaml b/HMI.UI/Style/PanuonDataGrid.xaml
index 4260135..c2d9930 100644
--- a/HMI.UI/Style/PanuonDataGrid.xaml
+++ b/HMI.UI/Style/PanuonDataGrid.xaml
@@ -18,7 +18,15 @@
 
     <Style x:Key="DefaultDataGridRow" TargetType="DataGridRow" BasedOn="{StaticResource {x:Static pu:StyleKeys.DataGridRowStyle}}">
         <Setter Property="Background" Value="White"/>
-        <Setter Property="DetailsVisibility" Value="Collapsed"/>
+        <Setter Property="HeaderStyle">
+            <Setter.Value>
+                <Style TargetType="DataGridColumnHeader" 
+       BasedOn="{StaticResource {x:Static pu:StyleKeys.DataGridColumnHeaderStyle}}">
+                    <Setter Property="Padding" Value="10"/>
+                    <Setter Property="BorderThickness" Value="0"/>
+                </Style>
+            </Setter.Value>
+        </Setter>
         <Style.Triggers>
             <Trigger Property="AlternationIndex" Value="1">
                 <Setter Property="Background" Value="#eeeeee"/>
@@ -26,22 +34,15 @@
         </Style.Triggers>
     </Style>
 
-
-    <Style TargetType="DataGridCell" 
-       BasedOn="{StaticResource {x:Static pu:StyleKeys.DataGridCellStyle}}">
-        <Setter Property="Background" Value="Transparent"/>
-        <Setter Property="Padding" Value="5"/>
-        <Setter Property="Margin" Value="0"/>
-        <Setter Property="BorderThickness" Value="0"/>
-        <Setter Property="VerticalAlignment" Value="Stretch"/>
-    </Style>
-
     <Style x:Key="DefaultDataGrid" TargetType="DataGrid" BasedOn="{StaticResource {x:Static pu:StyleKeys.DataGridStyle}}">
         <Style.Resources>
-            <Style TargetType="DataGridColumnHeader" 
-       BasedOn="{StaticResource {x:Static pu:StyleKeys.DataGridColumnHeaderStyle}}">
-                <Setter Property="Padding" Value="10"/>
+            <Style TargetType="DataGridCell" 
+                   BasedOn="{StaticResource {x:Static pu:StyleKeys.DataGridCellStyle}}">
+                <Setter Property="Background" Value="Transparent"/>
+                <Setter Property="Padding" Value="5"/>
+                <Setter Property="Margin" Value="0"/>
                 <Setter Property="BorderThickness" Value="0"/>
+                <Setter Property="VerticalAlignment" Value="Stretch"/>
             </Style>
         </Style.Resources>
 
@@ -51,7 +52,7 @@
         <Setter Property="pu:DataGridHelper.ColumnHeaderPadding" Value="0"/>
         <Setter Property="ForceCursor" Value="True"/>
         <Setter Property="SelectionMode" Value="Single"/>
-        <Setter Property="RowStyle" Value="{StaticResource DefaultDataGridRow}"/>
+        <!--<Setter Property="RowStyle" Value="{StaticResource DefaultDataGridRow}"/>-->
         <Setter Property="AlternationCount" Value="2"></Setter>
         <Setter Property="Foreground" Value="#3a3a3a"/>
         <Setter Property="HorizontalGridLinesBrush" Value="{StaticResource TableBoarder}"/>
diff --git a/HMI.UI/Style/PanuonListView.xaml b/HMI.UI/Style/PanuonListView.xaml
new file mode 100644
index 0000000..23eff26
--- /dev/null
+++ b/HMI.UI/Style/PanuonListView.xaml
@@ -0,0 +1,77 @@
+锘�<ResourceDictionary 
+     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+                    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+                    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+                    mc:Ignorable="d"
+                    xmlns:ui="clr-namespace:HMI.UI"
+                    xmlns:sys="clr-namespace:System;assembly=mscorlib"
+                    xmlns:pw="https://opensource.panuon.com/wpf"
+                    xmlns:pu="https://opensource.panuon.com/wpf-ui"
+             xmlns:uic="clr-namespace:HMI.UI.Converter"
+             xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI">
+    <ResourceDictionary.MergedDictionaries>
+        <pu:StyleDictionary Includes="ListView" />
+        <ResourceDictionary Source="/HMI.UI;component/Style/Colors.xaml"/>
+        <ResourceDictionary Source="/HMI.UI;component/Resources/Font/Font.xaml"/>
+        <ResourceDictionary Source="/HMI.UI;component/Style/ScrollBarResource.xaml"/>
+    </ResourceDictionary.MergedDictionaries>
+
+    <uic:ListViewRowNumberConverter x:Key="lvRowNumber"/>
+
+    <uic:ListViewRowHeightConverter x:Key="sbC"/>
+
+    <Style x:Key="CellBorder" TargetType="Border">
+        <Setter Property="BorderBrush" Value="{DynamicResource CommonBoarder}"/>
+        <Setter Property="BorderThickness" Value="1,0,0,0"/>
+        <Setter Property="Margin" Value="-5,0"/>
+        <Setter Property="Padding" Value="5,0,0,0"/>
+        <Setter Property="Height" Value="{Binding ActualHeight,
+            Mode=OneWay,
+            RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=GridViewRowPresenter,AncestorLevel=1}}"/>
+        <!--<Setter Property="Width" Value="{Binding ActualWidth,
+            Converter={StaticResource sbC},ConverterParameter=2.0,
+            RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContentPresenter,AncestorLevel=1}}"/>-->
+    </Style>
+
+    <Style x:Key="ListTableTextBlock" TargetType="TextBlock">
+        <Setter Property="Padding" Value="0,5"/>
+        <Setter Property="Margin" Value="0"/>
+        <Setter Property="VerticalAlignment" Value="Center"/>
+        <Setter Property="TextWrapping" Value="Wrap"/>
+    </Style>
+
+    <Style x:Key="DefaultListTableItemStyle" TargetType="ListViewItem" BasedOn="{StaticResource {x:Static pu:StyleKeys.ListViewItemStyle}}">
+        <Setter Property="Background" Value="#FFF"/>
+        <Style.Triggers>
+            <Trigger Property="ItemsControl.AlternationIndex" Value="1">
+                <Setter Property="Background" Value="#eeeeee"/>
+            </Trigger>
+        </Style.Triggers>
+    </Style>
+
+    <Style x:Key="DefaultListTable" TargetType="ListView" BasedOn="{StaticResource {x:Static pu:StyleKeys.ListViewStyle}}">
+        <Style.Resources>
+            <Style TargetType="ScrollBar" BasedOn="{StaticResource ScrollStyle_Mobile}"/>
+        </Style.Resources>
+
+        <Setter Property="BorderThickness" Value="0"/>
+        <Setter Property="VirtualizingPanel.IsVirtualizing" Value="True"/>
+        <Setter Property="AlternationCount" Value="2"/>
+        <Setter Property="pu:ListViewHelper.ColumnHeaderHeight" Value="36"/>
+        <Setter Property="pu:ListViewHelper.ColumnHeaderBackground" Value="{DynamicResource VerBar}"/>
+        <Setter Property="pu:ListViewHelper.ColumnHeaderHoverBackground" Value="{DynamicResource VerBar}"/>
+        <Setter Property="pu:ListViewHelper.ColumnHeaderForeground" Value="#FFF"/>
+        <Setter Property="pu:ListViewHelper.ItemsHoverBackground" Value="{DynamicResource SecondPrimaryColor}"/>
+        <Setter Property="pu:ListViewHelper.ItemsHoverForeground" Value="{DynamicResource SecondPrimaryColorContract}"/>
+        <Setter Property="pu:ListViewHelper.ItemsSelectedBackground" Value="{DynamicResource PrimaryColor}"/>
+        <Setter Property="pu:ListViewHelper.ItemsSelectedForeground" Value="{DynamicResource PrimaryColorContract}"/>
+        <Setter Property="pu:ListViewHelper.ItemsSeparatorBrush" Value="{DynamicResource CommonBoarder}"/>
+        <Setter Property="pu:ListViewHelper.ItemsSeparatorThickness" Value="1"/>
+        <Setter Property="pu:ListViewHelper.ItemsPadding" Value="0"/>
+        <Setter Property="pu:ListViewHelper.ItemsHeight" Value="30"/>
+        <Setter Property="pu:ListViewHelper.ItemsSeparatorVisibility" Value="Visible"/>
+        <Setter Property="ItemContainerStyle" Value="{StaticResource DefaultListTableItemStyle}"/>
+    </Style>
+
+</ResourceDictionary>
\ No newline at end of file
diff --git a/HMI.UI/Style/PanuonResource.xaml b/HMI.UI/Style/PanuonResource.xaml
index 01cd250..23a2dcb 100644
--- a/HMI.UI/Style/PanuonResource.xaml
+++ b/HMI.UI/Style/PanuonResource.xaml
@@ -11,8 +11,10 @@
              xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
              xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI">
     <ResourceDictionary.MergedDictionaries>
-        <pu:StyleDictionary Includes="PasswordBox,TextBox,ComboBox,Button,TreeView,Menu,ListBox,ContextMenu" />
+        <pu:StyleDictionary Includes="PasswordBox,TextBox,ComboBox,Button,TreeView,Menu,ListBox,ContextMenu,TabControl" />
         <ResourceDictionary Source="/HMI.UI;component/Style/Colors.xaml"/>
+        <ResourceDictionary Source="/HMI.UI;component/Style/PanuonDataGrid.xaml"/>
+        <ResourceDictionary Source="/HMI.UI;component/Style/ScrollBarResource.xaml"/>
         <ResourceDictionary Source="/HMI.UI;component/Resources/Font/Font.xaml"/>
     </ResourceDictionary.MergedDictionaries>
     <sys:Double x:Key="TitleFontSize">20</sys:Double>
@@ -234,4 +236,45 @@
         <Setter Property="Background" Value="Transparent"/>
         <Setter Property="BorderThickness" Value="0"/>
     </Style>
+
+    <Style x:Key="HeaderTabControl" TargetType="TabControl">
+        <Setter Property="pu:TabControlHelper.HeaderPanelAlignment" Value="Stretch"/>
+        <Setter Property="pu:TabControlHelper.ItemsBackground" Value="{DynamicResource VerBar}"/>
+        <Setter Property="pu:TabControlHelper.ItemsForeground" Value="#FFF"/>
+        <Setter Property="pu:TabControlHelper.ItemsSelectedForeground" Value="{DynamicResource PrimaryColor}"/>
+        <Setter Property="pu:TabControlHelper.ItemsFontSize" Value="14"/>
+        <Setter Property="pu:TabControlHelper.ItemsHeight" Value="30"/>
+        <Setter Property="pu:TabControlHelper.ItemsHorizontalHeaderAlignment" Value="Left"/>
+        <Setter Property="pu:TabControlHelper.ItemsHoverBackground" Value="{DynamicResource SecondPrimaryColor}"/>
+        <Setter Property="pu:TabControlHelper.ItemsHoverForeground" Value="#FFF"/>
+        <Setter Property="pu:TabControlHelper.ItemsPadding" Value="20,5,0,0"/>
+        <Setter Property="pu:TabControlHelper.ItemsRibbonLineCornerRadius" Value="4"/>
+        <Setter Property="pu:TabControlHelper.ItemsRibbonLineMargin" Value="0,5,0,5"/>
+        <Setter Property="pu:TabControlHelper.ItemsRibbonLineThickness" Value="4"/>
+        <Setter Property="pu:TabControlHelper.ItemsRibbonLineVisibility" Value="Visible"/>
+        <Setter Property="pu:TabControlHelper.ItemsSelectedBackground" Value="#FFF"/>
+        <Setter Property="pu:TabControlHelper.ItemsSelectedRibbonLineBrush" Value="#FFF"/>
+        <Setter Property="pu:TabControlHelper.ItemsSelectedRibbonLineThickness" Value="2"/>
+        <Setter Property="pu:TabControlHelper.ItemsWidth" Value="100"/>
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate TargetType="{x:Type TabControl}">
+                    <Grid>
+                        <Grid.RowDefinitions>
+                            <RowDefinition Height="Auto"/>
+                            <RowDefinition Height="*"/>
+                        </Grid.RowDefinitions>
+                        <Border BorderThickness="1,1,1,0" BorderBrush="#acacac" Background="{DynamicResource VerBar}" Padding="2,2,10,0">
+                            <TabPanel x:Name="HeaderPanel" Grid.Row="0" Panel.ZIndex="1" IsItemsHost="True"/>
+                        </Border>
+
+                        <Border Grid.Row="1" BorderBrush="{TemplateBinding BorderBrush}" 
+        BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
+                            <ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent"/>
+                        </Border>
+                    </Grid>
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
 </ResourceDictionary>
\ No newline at end of file
diff --git a/HMI.UI/Style/ScrollBarResource.xaml b/HMI.UI/Style/ScrollBarResource.xaml
new file mode 100644
index 0000000..c7d7f6e
--- /dev/null
+++ b/HMI.UI/Style/ScrollBarResource.xaml
@@ -0,0 +1,106 @@
+锘�<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+                    xmlns:ui="clr-namespace:HMI.UI"
+                    xmlns:sys="clr-namespace:System;assembly=mscorlib">
+    
+    <ControlTemplate x:Key="VerticalScrollBar" TargetType="RangeBase">
+        <Border Width="{TemplateBinding Width}" CornerRadius="2" 
+                Background="{DynamicResource CommonBoarder}">
+            <Track Name="PART_Track" IsDirectionReversed="true">
+                <Track.DecreaseRepeatButton>
+                    <RepeatButton Command="ScrollBar.PageUpCommand" Focusable="False">
+                        <RepeatButton.Template>
+                            <ControlTemplate>
+                                <Border Background="Transparent"></Border>
+                            </ControlTemplate>
+                        </RepeatButton.Template>
+                    </RepeatButton>
+                </Track.DecreaseRepeatButton>
+                <Track.Thumb>
+                    <Thumb Width="{TemplateBinding Width}"  Focusable="False">
+                        <Thumb.Template>
+                            <ControlTemplate>
+                                <Border Background="Gray"
+                                        CornerRadius="2" />
+                            </ControlTemplate>
+                        </Thumb.Template>
+                    </Thumb>
+                </Track.Thumb>
+                <Track.IncreaseRepeatButton>
+                    <RepeatButton Command="ScrollBar.PageDownCommand" Focusable="False">
+                        <RepeatButton.Template>
+                            <ControlTemplate>
+                                <Border Background="Transparent"></Border>
+                            </ControlTemplate>
+                        </RepeatButton.Template>
+                    </RepeatButton>
+                </Track.IncreaseRepeatButton>
+            </Track>
+        </Border>
+    </ControlTemplate>
+
+    <ControlTemplate x:Key="HorizontalScrollBar" TargetType="{x:Type ScrollBar}">
+        <Grid Background="{DynamicResource CommonBoarder}">
+            <Track
+                Name="PART_Track"
+                Grid.Column="1"
+                IsDirectionReversed="False">
+                <Track.DecreaseRepeatButton>
+                    <RepeatButton Command="ScrollBar.PageLeftCommand" Focusable="False">
+                        <RepeatButton.Template>
+                            <ControlTemplate>
+                                <Border Background="Transparent"></Border>
+                            </ControlTemplate>
+                        </RepeatButton.Template>
+                    </RepeatButton>
+                </Track.DecreaseRepeatButton>
+                <Track.Thumb>
+                    <Thumb Height="{TemplateBinding Height}" >
+                        <Thumb.Template>
+                            <ControlTemplate>
+                                <Border Background="Gray"
+                                        CornerRadius="2" />
+                            </ControlTemplate>
+                        </Thumb.Template>
+                    </Thumb>
+                </Track.Thumb>
+                <Track.IncreaseRepeatButton>
+                    <RepeatButton Command="ScrollBar.PageRightCommand" Focusable="False">
+                        <RepeatButton.Template>
+                            <ControlTemplate>
+                                <Border Background="Transparent"></Border>
+                            </ControlTemplate>
+                        </RepeatButton.Template>
+                    </RepeatButton>
+                </Track.IncreaseRepeatButton>
+            </Track>
+        </Grid>
+    </ControlTemplate>
+
+    <Style x:Key="ScrollStyle_Mobile" TargetType="ScrollBar">
+        <Style.Resources>
+            <sys:Double x:Key="{x:Static SystemParameters.VerticalScrollBarWidthKey}">6</sys:Double>
+            <sys:Double x:Key="{x:Static SystemParameters.HorizontalScrollBarHeightKey}">6</sys:Double>
+        </Style.Resources>
+
+        <Style.Triggers>
+            <Trigger Property="Orientation" Value="Vertical">
+                <Setter Property="Width" Value="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}" />
+                <Setter Property="Height" Value="Auto" />
+                <Setter Property="Template" Value="{DynamicResource VerticalScrollBar}" />
+            </Trigger>
+            <Trigger Property="Orientation" Value="Horizontal">
+                <Setter Property="Width" Value="Auto" />
+                <Setter Property="Height" Value="{DynamicResource {x:Static SystemParameters.HorizontalScrollBarHeightKey}}" />
+                <Setter Property="Template" Value="{DynamicResource HorizontalScrollBar}" />
+            </Trigger>
+        </Style.Triggers>
+    </Style>
+
+    <Style x:Key="DefaultScrollViewer" TargetType="ScrollViewer">
+        <Style.Resources>
+            <Style TargetType="ScrollBar" BasedOn="{StaticResource ScrollStyle_Mobile}"/>
+        </Style.Resources>
+    </Style>
+
+</ResourceDictionary>
\ No newline at end of file
diff --git a/HMI.UI/Style/WindowResources.xaml b/HMI.UI/Style/WindowResources.xaml
index 2cf9e7a..d12c27e 100644
--- a/HMI.UI/Style/WindowResources.xaml
+++ b/HMI.UI/Style/WindowResources.xaml
@@ -1,6 +1,7 @@
 锘�<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                     xmlns:ui="clr-namespace:HMI.UI"
+                    xmlns:sys="clr-namespace:System;assembly=mscorlib"
                     >
     <ResourceDictionary.MergedDictionaries>
         <ResourceDictionary Source="/HMI.UI;component/Style/Colors.xaml"/>
diff --git a/HMI.UI/TableComboBox.cs b/HMI.UI/TableComboBox.cs
new file mode 100644
index 0000000..f76d7b5
--- /dev/null
+++ b/HMI.UI/TableComboBox.cs
@@ -0,0 +1,249 @@
+锘縰sing HMI.UI.Converter;
+using HMI.UI.MDI;
+using System;
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Input;
+using System.Windows.Media;
+
+namespace HMI.UI
+{
+    public class TableComboBox : ItemsControl
+    {
+        static TableComboBox()
+        {
+            DefaultStyleKeyProperty.OverrideMetadata(typeof(TableComboBox), new FrameworkPropertyMetadata(typeof(TableComboBox)));
+        }
+
+        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
+                            "Value",
+                            typeof(object),
+                            typeof(TableComboBox),
+                            new FrameworkPropertyMetadata());
+
+        public static readonly DependencyProperty CanEditProperty = DependencyProperty.Register(
+                            "CanEdit",
+                            typeof(bool),
+                            typeof(TableComboBox),
+                            new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnCanEditPropertyChanged)));
+
+        public static DependencyProperty IsEditingProperty = DependencyProperty.Register(
+                            "IsEditing",
+                            typeof(bool),
+                            typeof(TableComboBox),
+                            new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsEditingPropertyChanged)));
+
+        public static DependencyProperty PreEditingProperty = DependencyProperty.Register(
+                            "PreEditing",
+                            typeof(Func<bool?>),
+                            typeof(TableComboBox),
+                            new FrameworkPropertyMetadata());
+
+        public static DependencyProperty ShowEmptyProperty = DependencyProperty.Register(
+                            "ShowEmpty",
+                            typeof(bool),
+                            typeof(TableComboBox),
+                            new FrameworkPropertyMetadata(false));
+
+        public static DependencyProperty IsDropDownOpenProperty = DependencyProperty.Register(
+                            "IsDropDownOpen",
+                            typeof(bool),
+                            typeof(TableComboBox),
+                            new FrameworkPropertyMetadata(false));
+
+
+        private static void OnCanEditPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
+        {
+            if (obj is TableComboBox eb && e.Property == CanEditProperty)
+            {
+                bool value = (bool)e.NewValue;
+                if (value)
+                {
+                    eb.MouseDoubleClick += eb.EditBox_MouseDoubleClick;
+                }
+                else
+                {
+                    eb.MouseDoubleClick -= eb.EditBox_MouseDoubleClick;
+                }
+            }
+        }
+
+        private static void OnIsEditingPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
+        {
+            if (obj is TableComboBox eb && e.Property == IsEditingProperty)
+            {
+                bool value = (bool)e.NewValue;
+                eb.editor.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
+                if (value)
+                {
+                    eb.editor.Focus();
+                }
+            }
+        }
+
+        private ComboBox editor;
+        public TableComboBox()
+        {
+            this.Loaded += EditBox_Loaded;
+        }
+
+        public bool IsDropDownOpen
+        {
+            get => (bool)GetValue(IsDropDownOpenProperty);
+            set
+            {
+                SetValue(IsDropDownOpenProperty, value);
+            }
+        }
+
+
+        private void EditBox_Loaded(object sender, RoutedEventArgs e)
+        {
+            editor = this.Template.FindName("PART_ComboBoxPart", this) as ComboBox;
+            editor.DisplayMemberPath = this.DisplayMemberPath;
+            editor.SelectedValuePath = this.SelectedValuePath;
+            editor.ItemsSource = this.ItemsSource;
+            editor.SelectedValue = Value;
+            editor.SelectionChanged += Editor_SelectionChanged;
+
+            Binding binding = new Binding("IsDropDownOpen")
+            {
+                Source = this,
+                Mode = BindingMode.TwoWay,
+                UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
+            };
+            editor.SetBinding(ComboBox.IsDropDownOpenProperty, binding);
+        }
+
+        private void Editor_SelectionChanged(object sender, SelectionChangedEventArgs e)
+        {
+            ComboBox box = sender as ComboBox;
+            if (IsEditing)
+            {
+                box.Focus();
+            }
+        }
+
+        private void EditBox_MouseDoubleClick(object sender, MouseButtonEventArgs e)
+        {
+            if (IsEditing)
+            {
+                return;
+            }
+
+            Func<bool?> pre = GetValue(PreEditingProperty) as Func<bool?>;
+            if (pre != null && pre.Invoke() != true)
+            {
+                return;
+            }
+            //var win = Window.GetWindow(this);
+
+            //win.MouseDown += EditBox_LostFocus;
+            editor.LostFocus += EditBox_LostFocus;
+            IsEditing = true;
+            e.Handled = true;
+        }
+
+        public static bool GetElementUnderMouse(UIElement relativeTo, UIElement target)
+        {
+            Point mousePosition = Mouse.GetPosition(relativeTo);
+            bool res = false;
+            HitTestResultCallback resultCallback = (result) =>
+            {
+                // 鑾峰彇鐩镐氦鐨刄I鍏冪礌
+                var element = result.VisualHit as FrameworkElement;
+
+                if (element != null && element == target)
+                {
+                    res = true;
+                    return HitTestResultBehavior.Stop; // 鎵惧埌鍚庡仠姝㈤亶鍘�
+                }
+                return HitTestResultBehavior.Continue;
+            };
+
+            VisualTreeHelper.HitTest(relativeTo, null,
+                resultCallback, new PointHitTestParameters(mousePosition));
+            return res;
+        }
+
+        private void EditBox_LostFocus(object sender, RoutedEventArgs e)
+        {
+            if (IsDropDownOpen)
+            {
+                return;
+            }
+            if (sender == editor)
+            {
+                IsEditing = false;
+                editor.LostFocus -= EditBox_LostFocus;
+                Window.GetWindow(this).MouseDown -= EditBox_LostFocus;
+            }
+            else
+            {
+                var win = sender as Window;
+                if (!GetElementUnderMouse(win, this))
+                {
+                    //editing.Focu
+                    IsEditing = false;
+                    win.MouseDown -= EditBox_LostFocus;
+                    Window.GetWindow(this).MouseDown -= EditBox_LostFocus;
+                }
+            }
+        }
+
+        public object Value
+        {
+            get
+            {
+                return GetValue(ValueProperty);
+            }
+            set
+            {
+                SetValue(ValueProperty, value);
+            }
+        }
+
+        public bool CanEdit
+        {
+            get => (bool)GetValue(CanEditProperty);
+            set
+            {
+                SetValue(CanEditProperty, value);
+            }
+        }
+
+        public string SelectedValuePath
+        {
+            get; set;
+        }
+
+        public bool IsEditing
+        {
+            get => (bool)GetValue(IsEditingProperty);
+            set
+            {
+                SetValue(IsEditingProperty, value);
+            }
+        }
+
+        public Func<bool?> PreEditing
+        {
+            get => (Func<bool?>)GetValue(PreEditingProperty);
+            set
+            {
+                SetValue(PreEditingProperty, value);
+            }
+        }
+
+        public bool ShowEmpty
+        {
+            get => (bool)GetValue(ShowEmptyProperty);
+            set
+            {
+                SetValue(ShowEmptyProperty, value);
+            }
+        }
+    }
+}
diff --git a/HMI.UI/Themes/Generic.xaml b/HMI.UI/Themes/Generic.xaml
new file mode 100644
index 0000000..e77a351
--- /dev/null
+++ b/HMI.UI/Themes/Generic.xaml
@@ -0,0 +1,145 @@
+锘�<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+                    xmlns:local="clr-namespace:HMI.UI">
+    <RadialGradientBrush x:Key="RedLight" GradientOrigin="0.3,0.3" 
+                     RadiusX="0.7" 
+                     RadiusY="0.7">
+        <RadialGradientBrush.GradientStops>
+            <GradientStop Color="#fd583e" Offset="0"/>
+            <GradientStop Color="#d5341f" Offset="0.5"/>
+            <GradientStop Color="#aa0e00" Offset="1"/>
+        </RadialGradientBrush.GradientStops>
+    </RadialGradientBrush>
+
+    <RadialGradientBrush x:Key="GreenLight" GradientOrigin="0.3,0.3" 
+                         RadiusX="0.7" 
+                         RadiusY="0.7">
+        <RadialGradientBrush.GradientStops>
+            <GradientStop Color="#1b9e14" Offset="1"/>
+            <GradientStop Color="#21c418" Offset="0.5"/>
+            <GradientStop Color="#28ea1d" Offset="0"/>
+        </RadialGradientBrush.GradientStops>
+    </RadialGradientBrush>
+
+    <Style TargetType="{x:Type local:LightBall}">
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate TargetType="{x:Type local:LightBall}">
+                    <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
+                        <Ellipse Height="{TemplateBinding Size}" Width="{TemplateBinding Size}" VerticalAlignment="Center">
+                            <Ellipse.Style>
+                                <Style TargetType="Ellipse">
+                                    <Setter Property="Fill" Value="{StaticResource RedLight}"/>
+                                    <Style.Triggers>
+                                        <DataTrigger Binding="{Binding Value,RelativeSource={RelativeSource Mode=TemplatedParent}}" Value="True">
+                                            <Setter Property="Fill" Value="{StaticResource GreenLight}"/>
+                                        </DataTrigger>
+                                    </Style.Triggers>
+                                </Style>
+                            </Ellipse.Style>
+                        </Ellipse>
+                        <ContentPresenter Margin="5,0,0,0" VerticalAlignment="Center"/>
+                    </StackPanel>
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
+
+    <Style TargetType="{x:Type local:EditBox}">
+        <Setter Property="HorizontalAlignment" Value="Stretch" />
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate TargetType="{x:Type local:EditBox}">
+                    <Grid Cursor="Hand" MinWidth="{TemplateBinding MinWidth}" Background="{TemplateBinding Background}">
+                        <Border x:Name="sb" VerticalAlignment="Center" Padding="0" Margin="0">
+                            <Border.Style>
+                                <Style TargetType="Border">
+                                    <Setter Property="BorderThickness" Value="0"/>
+                                    <Setter Property="BorderBrush" Value="Transparent"/>
+                                    <Style.Triggers>
+                                        <MultiDataTrigger>
+                                            <MultiDataTrigger.Conditions>
+                                                <Condition Binding="{Binding Path=Value, RelativeSource={RelativeSource TemplatedParent}}" Value=""/>
+                                                <Condition Binding="{Binding Path=ShowEmpty, RelativeSource={RelativeSource TemplatedParent}}" Value="True"/>
+                                            </MultiDataTrigger.Conditions>
+                                            <MultiDataTrigger.Setters>
+                                                <Setter Property="BorderBrush" Value="Black"/>
+                                                <Setter Property="BorderThickness" Value="0,0,0,0.6"/>
+                                            </MultiDataTrigger.Setters>
+                                        </MultiDataTrigger>
+                                        <MultiDataTrigger>
+                                            <MultiDataTrigger.Conditions>
+                                                <Condition Binding="{Binding Path=Value, RelativeSource={RelativeSource TemplatedParent}}" Value="{x:Null}"/>
+                                                <Condition Binding="{Binding Path=ShowEmpty, RelativeSource={RelativeSource TemplatedParent}}" Value="True"/>
+                                            </MultiDataTrigger.Conditions>
+                                            <MultiDataTrigger.Setters>
+                                                <Setter Property="BorderBrush" Value="Black"/>
+                                                <Setter Property="BorderThickness" Value="0,0,0,0.6"/>
+                                            </MultiDataTrigger.Setters>
+                                        </MultiDataTrigger>
+                                    </Style.Triggers>
+                                </Style>
+                            </Border.Style>
+                            <TextBlock x:Name="PART_TextBlockPart" 
+                                       FontSize="{TemplateBinding FontSize}"
+                                       TextWrapping="NoWrap" Padding="0" VerticalAlignment="Center"
+                               Text="{Binding Path=Value, RelativeSource={RelativeSource TemplatedParent}}"/>
+                        </Border>
+                        <TextBox x:Name="PART_TextBoxPart" BorderThickness="0" Padding="0" 
+                                       FontSize="{TemplateBinding FontSize}" Visibility="Collapsed"
+                               Text="{Binding Path=Value, RelativeSource={RelativeSource TemplatedParent}}"/>
+                    </Grid>
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
+
+    <Style TargetType="{x:Type local:TableComboBox}">
+        <Setter Property="HorizontalAlignment" Value="Stretch" />
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate TargetType="{x:Type local:TableComboBox}">
+                    <Grid Cursor="Hand" MinWidth="{TemplateBinding MinWidth}" Background="{TemplateBinding Background}">
+                        <Border x:Name="sb" VerticalAlignment="Center" Padding="0" Margin="0">
+                            <Border.Style>
+                                <Style TargetType="Border">
+                                    <Setter Property="BorderThickness" Value="0"/>
+                                    <Setter Property="BorderBrush" Value="Transparent"/>
+                                    <Style.Triggers>
+                                        <MultiDataTrigger>
+                                            <MultiDataTrigger.Conditions>
+                                                <Condition Binding="{Binding Path=Value, RelativeSource={RelativeSource TemplatedParent}}" Value=""/>
+                                                <Condition Binding="{Binding Path=ShowEmpty, RelativeSource={RelativeSource TemplatedParent}}" Value="True"/>
+                                            </MultiDataTrigger.Conditions>
+                                            <MultiDataTrigger.Setters>
+                                                <Setter Property="BorderBrush" Value="Black"/>
+                                                <Setter Property="BorderThickness" Value="0,0,0,0.6"/>
+                                            </MultiDataTrigger.Setters>
+                                        </MultiDataTrigger>
+                                        <MultiDataTrigger>
+                                            <MultiDataTrigger.Conditions>
+                                                <Condition Binding="{Binding Path=Value, RelativeSource={RelativeSource TemplatedParent}}" Value="{x:Null}"/>
+                                                <Condition Binding="{Binding Path=ShowEmpty, RelativeSource={RelativeSource TemplatedParent}}" Value="True"/>
+                                            </MultiDataTrigger.Conditions>
+                                            <MultiDataTrigger.Setters>
+                                                <Setter Property="BorderBrush" Value="Black"/>
+                                                <Setter Property="BorderThickness" Value="0,0,0,0.6"/>
+                                            </MultiDataTrigger.Setters>
+                                        </MultiDataTrigger>
+                                    </Style.Triggers>
+                                </Style>
+                            </Border.Style>
+                            <TextBlock x:Name="PART_TextBlockPart" 
+                                   FontSize="{TemplateBinding FontSize}"
+                                   TextWrapping="NoWrap" Padding="0" VerticalAlignment="Center"
+                           Text="{Binding Path=Value, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"/>
+                        </Border>
+                        <ComboBox x:Name="PART_ComboBoxPart" BorderThickness="0" Padding="0" 
+                                   FontSize="{TemplateBinding FontSize}" Visibility="Collapsed"
+                           Text="{Binding Path=Value, RelativeSource={RelativeSource TemplatedParent}}"/>
+                    </Grid>
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
+</ResourceDictionary>
\ No newline at end of file
diff --git a/HMI.UI/Util/MsgBoxHelper.cs b/HMI.UI/Util/MsgBoxHelper.cs
index 18f2a30..2c81478 100644
--- a/HMI.UI/Util/MsgBoxHelper.cs
+++ b/HMI.UI/Util/MsgBoxHelper.cs
@@ -2,6 +2,7 @@
 using System.Windows;
 using Panuon.WPF.UI.Configurations;
 using HMI.Core.Util;
+using System;
 
 namespace HMI.UI.Util
 {
diff --git a/HMI.UI/Util/ToastHelper.cs b/HMI.UI/Util/ToastHelper.cs
index 90a409b..b578a76 100644
--- a/HMI.UI/Util/ToastHelper.cs
+++ b/HMI.UI/Util/ToastHelper.cs
@@ -1,6 +1,6 @@
 锘縰sing HMI.Core.Util;
 using Panuon.WPF.UI;
-using System.Runtime.CompilerServices;
+using System;
 using System.Windows;
 using System.Windows.Controls;
 
@@ -47,15 +47,18 @@
             ToastPosition position = ToastPosition.Top, double offset = 0, int durationMs = 3000,
             ToastWindow targetWindow = ToastWindow.ActiveWindow)
         {
-            var window = Application.Current.MainWindow;
-            if (window is WindowX)
+            Application.Current.Dispatcher.BeginInvoke(new Action(() =>
             {
-                Instance.Success(window, message, icon, position, offset, durationMs, targetWindow);
-            }
-            else
-            {
-                MessageBox.Show(message, LangUtil.Get("App.Success"));
-            }
+                var window = Application.Current.MainWindow;
+                if (window is WindowX)
+                {
+                    Instance.Success(window, message, icon, position, offset, durationMs, targetWindow);
+                }
+                else
+                {
+                    MessageBox.Show(message, LangUtil.Get("App.Success"));
+                }
+            }));
         }
 
         public static void Warning(string message,
@@ -63,15 +66,18 @@
             ToastPosition position = ToastPosition.Top, double offset = 0, int durationMs = 3000,
             ToastWindow targetWindow = ToastWindow.ActiveWindow)
         {
-            var window = Application.Current.MainWindow;
-            if (window is WindowX)
+            Application.Current.Dispatcher.BeginInvoke(new Action(() =>
             {
-                Instance.Warning(window, message, icon, position, offset, durationMs, targetWindow);
-            }
-            else
-            {
-                MessageBox.Show(message, LangUtil.Get("App.Warn"));
-            }
+                var window = Application.Current.MainWindow;
+                if (window is WindowX)
+                {
+                    Instance.Warning(window, message, icon, position, offset, durationMs, targetWindow);
+                }
+                else
+                {
+                    MessageBox.Show(message, LangUtil.Get("App.Warn"));
+                }
+            }));
         }
 
         public static void Error(string message,
@@ -79,16 +85,19 @@
             ToastPosition position = ToastPosition.Top, double offset = 0, int durationMs = 3000,
             ToastWindow targetWindow = ToastWindow.ActiveWindow)
         {
-            var window = Application.Current.MainWindow;
-            if (window is WindowX)
+            Application.Current.Dispatcher.BeginInvoke(new Action(() =>
             {
+                var window = Application.Current.MainWindow;
+                if (window is WindowX)
+                {
 
-                Instance.Error(window, message, icon, position, offset, durationMs, targetWindow);
-            }
-            else
-            {
-                MessageBox.Show(message, LangUtil.Get("App.Error"));
-            }
+                    Instance.Error(window, message, icon, position, offset, durationMs, targetWindow);
+                }
+                else
+                {
+                    MessageBox.Show(message, LangUtil.Get("App.Error"));
+                }
+            }));
         }
 
         public static void Info(string message,
@@ -96,16 +105,19 @@
             ToastPosition position = ToastPosition.Top, double offset = 0, int durationMs = 3000,
             ToastWindow targetWindow = ToastWindow.ActiveWindow)
         {
-            var window = Application.Current.MainWindow;
-            if (window is WindowX)
+            Application.Current.Dispatcher.BeginInvoke(new Action(() =>
             {
+                var window = Application.Current.MainWindow;
+                if (window is WindowX)
+                {
 
-                Instance.Info(window, message, icon, position, offset, durationMs, targetWindow);
-            }
-            else
-            {
-                MessageBox.Show(message, LangUtil.Get("App.Info"));
-            }
+                    Instance.Info(window, message, icon, position, offset, durationMs, targetWindow);
+                }
+                else
+                {
+                    MessageBox.Show(message, LangUtil.Get("App.Info"));
+                }
+            }));
         }
 
 
diff --git a/HMI.UnitTest/HMI.UnitTest.csproj b/HMI.UnitTest/HMI.UnitTest.csproj
index ba07951..73dc3ae 100644
--- a/HMI.UnitTest/HMI.UnitTest.csproj
+++ b/HMI.UnitTest/HMI.UnitTest.csproj
@@ -155,10 +155,6 @@
       <Project>{be42ab26-3270-43e1-84b8-6929d862ff8d}</Project>
       <Name>Plugin.DataBoard</Name>
     </ProjectReference>
-    <ProjectReference Include="..\Plugin.TestBuilder\Plugin.TestBuilder.csproj">
-      <Project>{30201ec1-9a82-48db-94b9-bd5c1190228b}</Project>
-      <Name>Plugin.TestBuilder</Name>
-    </ProjectReference>
     <ProjectReference Include="..\Plugin.TestResult\Plugin.TestResult.csproj">
       <Project>{7375b74c-b6bf-4814-9838-e1caacad1897}</Project>
       <Name>Plugin.TestResult</Name>
@@ -167,6 +163,10 @@
       <Project>{202d9a7b-96b3-4ad4-9977-fc4263207a02}</Project>
       <Name>Plugin.TsExecution</Name>
     </ProjectReference>
+    <ProjectReference Include="..\SimpleUtil\HMI.Simple.Util\HMI.Simple.Util.csproj">
+      <Project>{cf5bebe9-2803-49c3-b773-73d082e0984a}</Project>
+      <Name>HMI.Simple.Util</Name>
+    </ProjectReference>
   </ItemGroup>
   <ItemGroup>
     <Folder Include="Entity\" />
diff --git a/HMI.UnitTest/MainWindow.xaml.cs b/HMI.UnitTest/MainWindow.xaml.cs
index d367685..a267a28 100644
--- a/HMI.UnitTest/MainWindow.xaml.cs
+++ b/HMI.UnitTest/MainWindow.xaml.cs
@@ -1,10 +1,10 @@
 锘縰sing ConnectionUtil;
 using HMI.Core.Api;
 using HMI.Core.Util;
+using HMI.Simple.Util;
 using log4net;
 using Panuon.WPF.UI;
 using Plugin.DataBoard;
-using Plugin.TestBuilder;
 using Plugin.TestResult_WPF;
 using Plugin.TsExecution;
 using System;
@@ -22,6 +22,7 @@
         private ILog log = LogManager.GetLogger("UnitTest");
         public MainWindow()
         {
+            ProductModelHelper.GetModelBySNR("cc123", out string model, out string msg);
             LangUtil.Load();
             InitializeComponent();
             //page.Children.Add(new UserControl1());
@@ -33,13 +34,58 @@
 
         private void Button_Click(object sender, RoutedEventArgs e)
         {
+            Task.Run(() =>
+            {
+                for (int j = 0; j < 1000; j++)
+                {
+                    string status = (j % 2 == 0) ? "Passed" : "Failed";
+                    string d = (j % 2 == 0) ? "9" : "9999999999999999999999999999999999999999999999999999999999999999995";
+                    tsExecution.AddTestData(0, $"ConnectivityCANRec,2,3,4,{d},6,{status}");
+                    Thread.Sleep(5);
+                }
+            });
+
+            Task.Run(() =>
+            {
+                for (int j = 0; j < 1000; j++)
+                {
+                    string status = (j % 2 == 0) ? "Passed" : "Failed";
+                    string d = (j % 2 == 0) ? "9" : "9999999999999999999999999999999999999999999999999999999999999999995";
+                    tsExecution.AddTestData(1, $"ConnectivityCANRec,2,3,4,{d},6,{status}");
+                    Thread.Sleep(5);
+                }
+            });
+
+            Task.Run(() =>
+            {
+                for (int j = 0; j < 1000; j++)
+                {
+                    string status = (j % 2 == 0) ? "Passed" : "Failed";
+                    string d = (j % 2 == 0) ? "9" : "9999999999999999999999999999999999999999999999999999999999999999995";
+                    tsExecution.AddTestData(2, $"ConnectivityCANRec,2,3,4,{d},6,{status}");
+                    Thread.Sleep(5);
+                }
+            });
+
             //Task.Run(() =>
             //{
             //    for (int j = 0; j < 1000; j++)
             //    {
             //        string status = (j % 2 == 0) ? "Passed" : "Failed";
             //        string d = (j % 2 == 0) ? "9" : "9999999999999999999999999999999999999999999999999999999999999999995";
-            //        tsExecution.AddTestData(0, $"ConnectivityCANRec,2,3,4,{d},6,{status}");
+            //        tsExecution.AddTestData(3, $"ConnectivityCANRec,2,3,4,{d},6,{status}");
+            //        Thread.Sleep(5);
+            //    }
+            //});
+
+            //Task.Run(() =>
+            //{
+            //    for (int j = 0; j < 1000; j++)
+            //    {
+            //        string status = (j % 2 == 0) ? "Passed" : "Failed";
+            //        string d = (j % 2 == 0) ? "9" : "9999999999999999999999999999999999999999999999999999999999999999995";
+            //        tsExecution.AddTestData(1, $"ConnectivityCANRec,2,3,4,{d},6,{status}");
+            //        Thread.Sleep(5);
             //    }
             //});
 
@@ -160,7 +206,6 @@
 
         private TsExecution tsExecution;
 
-        private TestBuilder testBuilder;
         private DataBoard dataBoard;
 
         private void Window_Loaded(object sender, RoutedEventArgs e)
diff --git a/HMI.Util/ExcelHelper.cs b/HMI.Util/ExcelHelper.cs
index ee8f3fc..9e795dc 100644
--- a/HMI.Util/ExcelHelper.cs
+++ b/HMI.Util/ExcelHelper.cs
@@ -45,7 +45,7 @@
                 {
                     var attr = attrs[j];
                     var value = attr.IgnoreExport ? "" : properties[j].GetValue(datas[i]);
-                    rowTemp.CreateCell(j).SetCellValue(value.ToString());
+                    rowTemp.CreateCell(j).SetCellValue(value?.ToString());
                 }
             }
 
@@ -72,74 +72,86 @@
             using (FileStream fileStream1 = new FileStream(sourcePath, FileMode.Open, FileAccess.Read))
             {
                 IWorkbook workbook = null;
-                if (sourcePath.EndsWith(".xlsx"))
-                {
-                    workbook = new XSSFWorkbook(fileStream1);
-                }
-                else if (sourcePath.EndsWith(".xls"))
+                if (sourcePath.EndsWith(".xls"))
                 {
                     workbook = new HSSFWorkbook(fileStream1);
                 }
-
-                ISheet sheet = workbook.GetSheetAt(0);
-                IRow titleRow = sheet.GetRow(0);
-
-                List<T> result = new List<T>();
-                IRow row;
-                for (int i = 1; i < (sheet.LastRowNum + 1); i++)
+                else
                 {
-                    row = sheet.GetRow(i);
-                    if (row != null)
-                    {
-                        T obj = (T)Activator.CreateInstance(type);
-                        bool isEmpty = true;
-                        for (int j = 0; j < row.Count(); j++)
-                        {
-                            string title = titleRow.GetCell(j).StringCellValue;
-                            if (dict.ContainsKey(title))
-                            {
-                                var cell = row.GetCell(j);
-                                if (cell == null || cell.CellType == CellType.Blank)
-                                    continue;
-                                CellType ct = cell.CellType;
-                                object value = null;
-                                if (ct == CellType.String)
-                                    value = row.GetCell(j).StringCellValue;
-                                else if (ct == CellType.Numeric)
-                                    value = row.GetCell(j).NumericCellValue;
-                                else if (ct == CellType.Boolean)
-                                    value = row.GetCell(j).BooleanCellValue;
-
-                                var prop = dict[title];
-                                if (prop.PropertyType == typeof(string))
-                                {
-                                    value = Convert.ToString(value);
-                                }
-                                else if (prop.PropertyType == typeof(double) || prop.PropertyType == typeof(double?))
-                                {
-                                    value = Convert.ToDouble(value);
-                                }
-                                else if (prop.PropertyType == typeof(int) || prop.PropertyType == typeof(int?))
-                                {
-                                    value = Convert.ToInt16(value);
-                                }
-                                else if (prop.PropertyType == typeof(bool) || prop.PropertyType == typeof(bool?))
-                                {
-                                    value = Convert.ToBoolean(value);
-                                }
-                                else if (prop.PropertyType.IsEnum)
-                                {
-                                    value = Enum.Parse(prop.PropertyType, value.ToString());
-                                }
-                                prop.SetValue(obj, value);
-                                isEmpty = false;
-                            }
-                        }
-                        if (!isEmpty) result.Add(obj);
-                    }
+                    workbook = new XSSFWorkbook(fileStream1);
                 }
-                workbook.Close();
-                return result;
+                int currentRow = 1;
+                try
+                {
+                    ISheet sheet = workbook.GetSheetAt(0);
+                    IRow titleRow = sheet.GetRow(0);
+
+                    List<T> result = new List<T>();
+                    IRow row;
+                    for (currentRow = 1; currentRow < (sheet.LastRowNum + 1); currentRow++)
+                    {
+                        row = sheet.GetRow(currentRow);
+                        if (row != null)
+                        {
+                            T obj = (T)Activator.CreateInstance(type);
+                            bool isEmpty = true;
+                            for (int j = 0; j < row.Count(); j++)
+                            {
+                                string title = titleRow.Cells[j].StringCellValue;
+                                if (dict.ContainsKey(title))
+                                {
+                                    var cell = row.GetCell(j);
+                                    if (cell == null || cell.CellType == CellType.Blank)
+                                        continue;
+                                    CellType ct = cell.CellType;
+                                    object value = null;
+                                    if (ct == CellType.String)
+                                        value = row.GetCell(j).StringCellValue;
+                                    else if (ct == CellType.Numeric)
+                                        value = row.GetCell(j).NumericCellValue;
+                                    else if (ct == CellType.Boolean)
+                                        value = row.GetCell(j).BooleanCellValue;
+                                    else
+                                        value = row.GetCell(j).StringCellValue;
+
+                                    var prop = dict[title];
+                                    if (prop.PropertyType == typeof(string))
+                                    {
+                                        value = Convert.ToString(value);
+                                    }
+                                    else if (prop.PropertyType == typeof(double) || prop.PropertyType == typeof(double?))
+                                    {
+                                        value = Convert.ToDouble(value);
+                                    }
+                                    else if (prop.PropertyType == typeof(int) || prop.PropertyType == typeof(int?))
+                                    {
+                                        value = Convert.ToInt16(value);
+                                    }
+                                    else if (prop.PropertyType == typeof(bool) || prop.PropertyType == typeof(bool?))
+                                    {
+                                        value = Convert.ToBoolean(value);
+                                    }
+                                    else if (prop.PropertyType.IsEnum)
+                                    {
+                                        value = Enum.Parse(prop.PropertyType, value.ToString());
+                                    }
+                                    prop.SetValue(obj, value);
+                                    isEmpty = false;
+                                }
+                            }
+                            if (!isEmpty) result.Add(obj);
+                        }
+                    }
+                    return result;
+                }
+                catch (Exception ex)
+                {
+                    throw new Exception($"绗瑊currentRow}琛岃В鏋愰敊璇細{ex.Message}", ex);
+                }
+                finally
+                {
+                    workbook.Close();
+                }
             }
         }
     }
diff --git a/Plugin.ChangeModel/AssemblyInfo.cs b/Plugin.ChangeModel/AssemblyInfo.cs
new file mode 100644
index 0000000..cc29e7f
--- /dev/null
+++ b/Plugin.ChangeModel/AssemblyInfo.cs
@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly:ThemeInfo(
+    ResourceDictionaryLocation.None,            //where theme specific resource dictionaries are located
+                                                //(used if a resource is not found in the page,
+                                                // or application resource dictionaries)
+    ResourceDictionaryLocation.SourceAssembly   //where the generic resource dictionary is located
+                                                //(used if a resource is not found in the page,
+                                                // app, or any theme specific resource dictionaries)
+)]
diff --git a/Plugin.ChangeModel/ChangeModel.xaml b/Plugin.ChangeModel/ChangeModel.xaml
new file mode 100644
index 0000000..db63d92
--- /dev/null
+++ b/Plugin.ChangeModel/ChangeModel.xaml
@@ -0,0 +1,144 @@
+锘�<hb:SimpleMaskControl x:Class="Plugin.ChangeModel.ChangeModel"
+             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.ChangeModel"
+             mc:Ignorable="d" 
+             xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
+             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"
+             Loaded="SimpleControl_Loaded"
+                      Style="{StaticResource simpleMaskControl}"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <hb:SimpleMaskControl.Resources>
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonDataGrid.xaml"/>
+            </ResourceDictionary.MergedDictionaries>
+        </ResourceDictionary>
+    </hb:SimpleMaskControl.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="{DynamicResource cm.title}"/>
+
+            <DockPanel Grid.Row="1" Cursor="">
+                <StackPanel DockPanel.Dock="Right" Style="{StaticResource ButtonGroup}">
+                   
+                    <Button Style="{StaticResource RfhBtn}"
+                            Content="{DynamicResource cm.rfhLog}"
+                            x:Name="refreshLogBtn" Click="Button_Click"/>
+
+                    <Button Style="{StaticResource DelBtn}" Content="{DynamicResource cm.clrLog}" 
+                            x:Name="deleteLogBtn" Click="Button_Click"/>
+
+                </StackPanel>
+
+                <StackPanel Style="{StaticResource ButtonGroup}" HorizontalAlignment="Left" dp:StackPanelHelper.ChildSpace="10">
+                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="{DynamicResource cm.lineName}"/>
+                    <ComboBox Style="{StaticResource FormComboxStyle}" Width="180"
+                              ItemsSource="{Binding LineNameSource}"
+                              SelectedItem="{Binding CurrentLineName}"
+                              SelectedValuePath="Content">
+                    </ComboBox>
+
+                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Visibility="{Binding WorkOrderVisiable}" 
+                               Text="{DynamicResource cm.workorder}"/>
+                    <ComboBox Style="{StaticResource FormComboxStyle}" Width="180"
+                              Visibility="{Binding WorkOrderVisiable}"
+                              ItemsSource="{Binding WorkorderSource}"
+                              SelectedItem="{Binding CurrentWorkorder}" SelectedValuePath="Content">
+                    </ComboBox>
+
+                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="{DynamicResource cm.productModel}"/>
+                    <ComboBox Style="{StaticResource FormComboxStyle}" Width="180"
+                              ItemsSource="{Binding ProductModelSource}"
+                              SelectedItem="{Binding CurrentProductModel}" SelectedValuePath="Content">
+                    </ComboBox>
+
+                    <Button Style="{StaticResource EditBtn}" x:Name="changeModelBtn" Content="{DynamicResource cm.cm}" 
+                            Click="Button_Click"/>
+                    <Button Style="{StaticResource RfhBtn}" Content="{DynamicResource cm.rfhInfo}" x:Name="refreshInfoBtn" 
+                            Click="Button_Click"/>
+                </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 level}" Value="Warn">
+                                <Setter Property="Background" Value="{StaticResource WarningColor}"/>
+                                <Setter Property="Foreground" Value="Black"/>
+                            </DataTrigger>
+                            <DataTrigger Binding="{Binding level}" 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="#" 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 LineName}">
+                        <DataGridTextColumn.Header>
+                            <TextBlock Text="{DynamicResource cm.lineName}"/>
+                        </DataGridTextColumn.Header>
+                    </DataGridTextColumn>
+                    <DataGridTextColumn Width="*" Binding="{Binding ProductModel}">
+                        <DataGridTextColumn.Header>
+                            <TextBlock Text="{DynamicResource cm.productModel}"/>
+                        </DataGridTextColumn.Header>
+                    </DataGridTextColumn>
+                    <DataGridTextColumn Width="*" Binding="{Binding CustomProperty}">
+                        <DataGridTextColumn.Header>
+                            <TextBlock Text="{DynamicResource cm.customProperty}"/>
+                        </DataGridTextColumn.Header>
+                    </DataGridTextColumn>
+                    <!--<DataGridTextColumn Width="*" Binding="{Binding Workstation}">
+                        <DataGridTextColumn.Header>
+                            <TextBlock Text="{DynamicResource cm.workstation}"/>
+                        </DataGridTextColumn.Header>
+                    </DataGridTextColumn>-->
+                    <DataGridTextColumn Width="*" Binding="{Binding ChangeTime}">
+                        <DataGridTextColumn.Header>
+                            <TextBlock Text="{DynamicResource cm.changeTime}"/>
+                        </DataGridTextColumn.Header>
+                    </DataGridTextColumn>
+                    <DataGridTextColumn Width="*" Binding="{Binding ChangeResult}">
+                        <DataGridTextColumn.Header>
+                            <TextBlock Text="{DynamicResource cm.changeResult}"/>
+                        </DataGridTextColumn.Header>
+                    </DataGridTextColumn>
+                    <DataGridTextColumn Width="*" Binding="{Binding Info}">
+                        <DataGridTextColumn.Header>
+                            <TextBlock Text="{DynamicResource cm.info}"/>
+                        </DataGridTextColumn.Header>
+                    </DataGridTextColumn>
+                </DataGrid.Columns>
+            </DataGrid>
+        </Grid>
+    </Border>
+</hb:SimpleMaskControl>
diff --git a/Plugin.ChangeModel/ChangeModel.xaml.cs b/Plugin.ChangeModel/ChangeModel.xaml.cs
new file mode 100644
index 0000000..9bab845
--- /dev/null
+++ b/Plugin.ChangeModel/ChangeModel.xaml.cs
@@ -0,0 +1,430 @@
+锘縰sing CommunityToolkit.Mvvm.ComponentModel;
+using HMI.Base;
+using HMI.Core.Entity;
+using HMI.Core.Manager;
+using HMI.Core.Util;
+using HMI.UI.Util;
+using LSMSInterface;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Plugin.ChangeModel.Entity;
+using System.Collections.ObjectModel;
+using System.Data;
+using System.IO;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Plugin.ChangeModel
+{
+    /// <summary>
+    /// ChangeModel.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    [ObservableObject]
+    public partial class ChangeModel : SimpleMaskControl
+    {
+        public ChangeModel()
+        {
+            api = new LSMSApi();
+            DataContext = this;
+            InitializeComponent();
+        }
+
+        [ObservableProperty]
+        private ObservableCollection<CmLogData> datasource;
+
+        [ObservableProperty]
+        private ObservableCollection<string> lineNameSource;
+
+        [ObservableProperty]
+        private ObservableCollection<string> workorderSource;
+
+        [ObservableProperty]
+        private ObservableCollection<string> productModelSource;
+
+        [ObservableProperty]
+        private string currentLineName;
+        [ObservableProperty]
+        private string currentWorkorder;
+        [ObservableProperty]
+        private string currentProductModel;
+        [ObservableProperty]
+        private Visibility workOrderVisiable = Visibility.Collapsed;
+
+        private string configLineName;
+        private string mesIp;
+        private int mesPort;
+        private LSMSApi api;
+        private bool isMesConnected = false;
+        private SqliteHelper sqliteHelper;
+
+        public void ConnectMes()
+        {
+            string msg = string.Empty;
+            try
+            {
+                if (api.Connect(this.mesIp, this.mesPort, ref msg))
+                {
+                    isMesConnected = true;
+                }
+                else
+                {
+                    throw new Exception(msg);
+                }
+            }
+            catch (Exception ex)
+            {
+                isMesConnected = false;
+                log.Error("ConnectMes Failed.", ex);
+                MsgBoxHelper.Error(Window.GetWindow(this), this.lang("cm.mesConnectFail", ex.Message));
+            }
+        }
+
+        public override void InitFinish()
+        {
+            base.InitFinish();
+            ParameterData parameter;
+            try
+            {
+                parameter = ParameterManager.GetCommonParameterByName("MesIp");
+                mesIp = parameter.Value;
+            }
+            catch
+            {
+                mesIp = "127.0.0.1";
+            }
+            try
+            {
+                parameter = ParameterManager.GetCommonParameterByName("MesPort");
+                mesPort = int.Parse(parameter.Value);
+            }
+            catch
+            {
+                mesPort = 9909;
+            }
+
+            try
+            {
+                parameter = ParameterManager.GetCommonParameterByName("ChangeModelByWorkOrder");
+                var res = bool.Parse(parameter.Value);
+                if (res) WorkOrderVisiable = Visibility.Visible;
+            }
+            catch
+            {
+                WorkOrderVisiable = Visibility.Collapsed;
+            }
+
+            try
+            {
+                var dbPath = Path.Combine(BaseFolder, "ChangeModel.db");
+                if (!File.Exists(dbPath))
+                {
+                    SqliteHelper.CreateDatabase(dbPath);
+                }
+                sqliteHelper = new SqliteHelper(dbPath);
+                sqliteHelper.ExecuteSql(@"CREATE TABLE IF NOT EXISTS change_model_record (
+                    id INTEGER PRIMARY KEY AUTOINCREMENT,
+                    lineName TEXT NOT NULL,
+                    workOrder TEXT,
+                    productModel TEXT NOT NULL,
+                    customProperty TEXT,
+                    changeTime TIME,
+                    changeResult TEXT,
+                    info TEXT);");
+            }
+            catch (Exception ex)
+            {
+                log.Error("Init ChangeModel DB Failed.", ex);
+            }
+        }
+
+        public override void Active()
+        {
+            base.Active();
+            if (!isMesConnected)
+            {
+                ConnectMes();
+                RefreshLineNameSource();
+            }
+        }
+
+        public override void DoClose(object sender, EventArgs e)
+        {
+            try
+            {
+                sqliteHelper?.Dispose();
+                string errMsg = string.Empty;
+                api?.DisConnect(ref errMsg);
+                base.DoClose(sender, e);
+            }
+            catch { 
+            }
+        }
+
+        private void RefreshLineNameSource()
+        {
+            try
+            {
+                if (PingMes(out string errMsg))
+                {
+                    string lineStr = api.GetAllLine();
+                    JObject job = (JObject)JsonConvert.DeserializeObject(lineStr);
+                    LineNameSource = new ObservableCollection<string>(job["ProductLines"].ToString().Split(','));
+                    try
+                    {
+                        var parameter = ParameterManager.GetCommonParameterByName("LineName");
+                        configLineName = parameter.Value;
+                        CurrentLineName = parameter.Value;
+                    }
+                    catch
+                    {
+
+                    }
+                }
+                else
+                {
+                    throw new Exception(errMsg);
+                }
+            }
+            catch (Exception ex)
+            {
+                log.Error("RefreshLineNameSource Failed.", ex);
+                MsgBoxHelper.ErrorMessage(Window.GetWindow(this), this.lang("cm.rfhLineNameFail", ex.Message));
+            }
+        }
+
+        private void RefreshWorkOrderByLineName(string lineName)
+        {
+            try
+            {
+                if (PingMes(out string errMsg))
+                {
+                    string workOrderStr = api.GetWorkOrderByLineName(lineName, out errMsg);
+                    if (string.IsNullOrEmpty(errMsg))
+                    {
+                        JObject res = (JObject)JsonConvert.DeserializeObject(workOrderStr);
+                        JArray workOrders = (JArray)res["Data"];
+                        WorkorderSource = new ObservableCollection<string>(workOrders.Select(j => j["Code"].ToString()).ToArray());
+                    }
+
+                    string infoStr = api.GetProductInfoByLineName(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.FFF"), lineName, out errMsg);
+                    if (string.IsNullOrEmpty(errMsg))
+                    {
+                        JObject job = JsonConvert.DeserializeObject(infoStr) as JObject;
+                        CurrentWorkorder = job["Order"]?.ToString();
+                    }
+                }
+                else
+                {
+                    throw new Exception(errMsg);
+                }
+            }
+            catch (Exception ex)
+            {
+                log.Error("RefreshWorkOrderByLineName Failed.", ex);
+                MsgBoxHelper.ErrorMessage(Window.GetWindow(this), this.lang("cm.rfhWorkOrderFail", ex.Message));
+            }
+        }
+
+        private void RefreshProductByLineName(string lineName)
+        {
+            try
+            {
+                if (PingMes(out string errMsg))
+                {
+                    string workOrderStr = api.GetProductsByLineName(lineName, out string ErrMsg);
+                    if (string.IsNullOrEmpty(errMsg))
+                    {
+                        JObject res = (JObject)JsonConvert.DeserializeObject(workOrderStr);
+                        JArray arr = res["Products"] as JArray;
+                        ProductModelSource = new ObservableCollection<string>(arr.Select(j => j["model"].ToString()).ToArray());
+                    }
+
+                    string lineStr = api.GetProductInfoByLineName(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.FFF"), lineName, out errMsg);
+                    if (string.IsNullOrEmpty(errMsg))
+                    {
+                        JObject job = (JObject)JsonConvert.DeserializeObject(lineStr);
+                        CurrentProductModel = job["Model"]?.ToString();
+                    }
+                }
+                else
+                {
+                    throw new Exception(errMsg);
+                }
+            }
+            catch (Exception ex)
+            {
+                log.Error("RefreshProductByLineName Failed.", ex);
+                MsgBoxHelper.ErrorMessage(Window.GetWindow(this), this.lang("cm.rfhModelFail", ex.Message));
+            }
+        }
+
+        private void MesChangeModel()
+        {
+            ShowMask(() =>
+            {
+                if (PingMes(out string errMsg))
+                {
+                    var info = new Dictionary<string, object>() {
+                        { "Model",CurrentProductModel}
+                    };
+                    if (WorkOrderVisiable == Visibility.Visible)
+                    {
+                        info["Order"] = CurrentWorkorder;
+                    }
+                    if (!api.ChangeModel2(CurrentLineName, info, out errMsg))
+                    {
+                        throw new Exception(errMsg);
+                    }
+                    sqliteHelper.ExecuteSql($@"INSERT INTO change_model_record (lineName, workOrder,productModel, customProperty, 
+changeTime,changeResult,info) Values 
+('{CurrentLineName}','{CurrentWorkorder}','{CurrentProductModel}',NULL,'{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.FFF")}', 'OK', NULL)");
+                    RefreshLog();
+                    if (!api.RefreshCache(out errMsg))
+                    {
+                        throw new Exception($"Refresh MES Cache Failed.: {errMsg}");
+                    }
+                    ToastHelper.Success("鎹㈠瀷鎴愬姛");
+                }
+                else
+                {
+                    throw new Exception(errMsg);
+                }
+            },
+            (ex) =>
+            {
+                log.Error("MesChangeModel Failed.", ex);
+                MsgBoxHelper.ErrorMessage(Window.GetWindow(this), this.lang("cm.changeFail", ex.Message));
+            }, "璇风◢鍚�", "鎹㈠瀷涓�...");
+        }
+
+
+
+        private bool PingMes(out string errMsg)
+        {
+            errMsg = string.Empty;
+            if (!isMesConnected)
+            {
+                errMsg = "MES鏈繛鎺ワ紝璇峰厛杩炴帴MES";
+                return false;
+            }
+            try
+            {
+                if (!api.IsConnect(ref errMsg))
+                {
+                    isMesConnected = false;
+                    return false;
+                }
+                return true;
+            }
+            catch (Exception ex)
+            {
+                errMsg = ex.Message;
+                isMesConnected = false;
+                return false;
+            }
+        }
+
+        private void RefreshLog()
+        {
+            try
+            {
+                DataTable dt = sqliteHelper.GetDataTable(@"SELECT lineName AS LineName
+,productModel AS ProductModel
+,customProperty AS CustomProperty
+,changeTime AS ChangeTime
+,changeResult AS ChangeResult
+,info AS Info
+FROM change_model_record order by changeTime desc limit 100;");
+                this.Dispatcher.BeginInvoke((Action)delegate ()
+                {
+                    table.ItemsSource = dt.DefaultView;
+                });
+            }
+            catch (Exception ex)
+            {
+                log.Error("Refresh ChangeModel Log Failed.", ex);
+                ToastHelper.Error(this.lang("cm.rfhLogFail", ex.Message));
+            }
+        }
+
+        partial void OnCurrentLineNameChanged(string lineName)
+        {
+            RefreshWorkOrderByLineName(lineName);
+            RefreshProductByLineName(lineName);
+        }
+
+
+        private void Button_Click(object sender, RoutedEventArgs e)
+        {
+            switch ((sender as Button).Name)
+            {
+                case "changeModelBtn":
+                    DoChangeModel();
+                    break;
+                case "refreshLogBtn":
+                    ShowMask(RefreshLog, null, "璇风◢鍚�", "姝e湪鍒锋柊...");
+                    break;
+                case "refreshInfoBtn":
+                    RefreshInfo();
+                    break;
+                case "deleteLogBtn":
+                    DeleteLog();
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        private void DeleteLog()
+        {
+            if (MsgBoxHelper.WarnMessage(this, this.lang("cm.makeSureDelLog")) != MessageBoxResult.OK)
+            {
+                return;
+            }
+            ShowMask(() =>
+            {
+                try
+                {
+                    sqliteHelper.ExecuteSql($@"DELETE FROM change_model_record WHERE ID NOT IN
+(SELECT id FROM change_model_record Order by changeTime DESC limit 10);");
+                    RefreshLog();
+                }
+                catch (Exception ex)
+                {
+                    log.Error("DeleteLog Failed.", ex);
+                    ToastHelper.Error(this.lang("cm.delLogFail", ex.Message));
+                }
+            }, null, "璇风◢鍚�", "姝e湪娓呯悊...");
+        }
+
+        private void RefreshInfo()
+        {
+            try
+            {
+                RefreshLineNameSource();
+                RefreshWorkOrderByLineName(CurrentLineName);
+                RefreshProductByLineName(CurrentLineName);
+            }
+            catch (Exception ex)
+            {
+                log.Error("Refresh Info Failed.", ex);
+                ToastHelper.Error(this.lang("cm.rfhInfoErr", ex.Message));
+            }
+        }
+
+        private void DoChangeModel()
+        {
+            if (string.IsNullOrEmpty(CurrentLineName) || string.IsNullOrEmpty(CurrentProductModel)) return;
+            if (MsgBoxHelper.WarnMessage(this, this.lang("cm.makeSureChangeModel")) != MessageBoxResult.OK)
+            {
+                return;
+            }
+            MesChangeModel();
+        }
+
+        private void SimpleControl_Loaded(object sender, RoutedEventArgs e)
+        {
+            RefreshLog();
+        }
+    }
+}
diff --git a/Plugin.ChangeModel/Entity/CmLogData.cs b/Plugin.ChangeModel/Entity/CmLogData.cs
new file mode 100644
index 0000000..2e03437
--- /dev/null
+++ b/Plugin.ChangeModel/Entity/CmLogData.cs
@@ -0,0 +1,13 @@
+锘縩amespace Plugin.ChangeModel.Entity
+{
+    public class CmLogData
+    {
+        public string LineName { get; set; }
+        public string ProductModel { get; set; }
+        public string CustomProperty { get; set; }
+        public string Workstation { get; set; }
+        public DateTime ChangeTime { get; set; }
+        public string ChangeResult { get; set; }
+        public string Info { get; set; }
+    }
+}
diff --git a/Plugin.ChangeModel/Entity/ModelData.cs b/Plugin.ChangeModel/Entity/ModelData.cs
new file mode 100644
index 0000000..2bfe432
--- /dev/null
+++ b/Plugin.ChangeModel/Entity/ModelData.cs
@@ -0,0 +1,13 @@
+锘縰sing System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Plugin.ChangeModel.Entity
+{
+    public class ModelData
+    {
+        public string Model { get; set; }
+    }
+}
diff --git a/Plugin.ChangeModel/Entity/WorkOrderData.cs b/Plugin.ChangeModel/Entity/WorkOrderData.cs
new file mode 100644
index 0000000..3384765
--- /dev/null
+++ b/Plugin.ChangeModel/Entity/WorkOrderData.cs
@@ -0,0 +1,7 @@
+锘縩amespace Plugin.ChangeModel.Entity
+{
+    public class WorkOrderData
+    {
+        public string Code { get; set; }
+    }
+}
diff --git a/Plugin.ChangeModel/Plugin.ChangeModel.csproj b/Plugin.ChangeModel/Plugin.ChangeModel.csproj
new file mode 100644
index 0000000..c85ac8f
--- /dev/null
+++ b/Plugin.ChangeModel/Plugin.ChangeModel.csproj
@@ -0,0 +1,27 @@
+锘�<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Library</OutputType>
+    <TargetFramework>net472</TargetFramework>
+    <Nullable>enable</Nullable>
+    <LangVersion>10.0</LangVersion>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <UseWPF>true</UseWPF>
+    <Platforms>AnyCPU;x64</Platforms>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
+    <PackageReference Include="log4net" Version="3.0.4" />
+    <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
+    <PackageReference Include="Panuon.WPF.UI" Version="1.3.0.2" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\MesApi\lmes.csproj" />
+    <ProjectReference Include="..\HMI.Base\HMI.Base.csproj" />
+    <ProjectReference Include="..\HMI.Core\HMI.Core.csproj" />
+    <ProjectReference Include="..\HMI.UI\HMI.UI.csproj" />
+  </ItemGroup>
+
+</Project>
diff --git a/Plugin.DOControl/DOControl.xaml b/Plugin.DOControl/DOControl.xaml
index 2df075e..db18129 100644
--- a/Plugin.DOControl/DOControl.xaml
+++ b/Plugin.DOControl/DOControl.xaml
@@ -3,13 +3,13 @@
              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:Plugin.DOControl"
+             mc:Ignorable="d"
+             xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
              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">
diff --git a/Plugin.DOControl/DOControl.xaml.cs b/Plugin.DOControl/DOControl.xaml.cs
index b5e1924..405c4ff 100644
--- a/Plugin.DOControl/DOControl.xaml.cs
+++ b/Plugin.DOControl/DOControl.xaml.cs
@@ -5,6 +5,7 @@
 using HMI.UI;
 using HMI.UI.Util;
 using log4net;
+using OPCConnection;
 using Panuon.WPF.UI;
 using Plugin.DOControl.Entity;
 using System;
@@ -26,8 +27,8 @@
         /// <summary>
         /// 瀹炰緥鍖栨湇鍔″櫒鎺ュ彛鍑芥暟
         /// </summary>
-        //private OpcInterface.OpcInterface lTSCADAApi = new OpcInterface.OpcInterface();
-        private PipeClient pipeClient = new PipeClient("DAService");
+        private SCADAApi lTSCADAApi = new OPCClient();
+        //private PipeClient pipeClient = new PipeClient("DAService");
 
         private ObservableCollection<string> groups;
         public ObservableCollection<string> Groups
@@ -99,8 +100,14 @@
 
         public override void DoClose(object sender, EventArgs e)
         {
-            motorMonitorTimer.Enabled = false;
-            base.DoClose(sender, e);
+            try
+            {
+
+                motorMonitorTimer.Enabled = false;
+                base.DoClose(sender, e);
+            }
+            catch { 
+            }
         }
 
         public static string FilePath { get; private set; }
@@ -376,24 +383,24 @@
         /// <param name="value">鍊�</param>
         private void SetPlcValue(string key, string value)
         {
-            //if (!lTSCADAApi.DoFctWriteSingle(key, value, out string errMsg))
-            //{
-            //    ToastHelper.Error(this, errMsg);
-            //}
+            if (!lTSCADAApi.DoFctWriteSingle(key, value, out string errMsg))
+            {
+                ToastHelper.Error(this, errMsg);
+            }
 
-            pipeClient.SendMessage("asdsadasda", out _);
+            //lTSCADAApi.SendMessage("asdsadasda", out _);
         }
 
         private string GetPlcValue(string key)
         {
-            //if (lTSCADAApi.DoFctReadSingle(key, out string Val, out string errMsg))
-            //{
-            //    return Val;
-            //}
-            //else
-            //{
-            //    return null;
-            //}
+            if (lTSCADAApi.DoFctReadSingle(key, out string Val, out string errMsg))
+            {
+                return Val;
+            }
+            else
+            {
+                return null;
+            }
             return null;
         }
 
@@ -450,25 +457,25 @@
             LoadData();
             if (SystemSetting.Instance.PlcEnabled)
             {
-                //if (lTSCADAApi.Connect2("127.0.0.1", 6543, 18888, out string errMsg))
-                //{
-                //    motorMonitorTimer.Enabled = true;
-                //    RefreshControl();
-                //}
-                //else
-                //{
-                //    MsgBoxHelper2.Error(errMsg);
-                //}
-                try
+                if (lTSCADAApi.Connect2("127.0.0.1", 6543, 28888, out string errMsg))
                 {
-                    pipeClient.OnConnected += PipeClient_OnConnected;
+                    motorMonitorTimer.Enabled = true;
+                    RefreshControl();
+                }
+                else
+                {
+                    MsgBoxHelper2.Error(errMsg);
+                }
+                //try
+                //{
+                //    pipeClient.OnConnected += PipeClient_OnConnected;
 
-                    pipeClient.Connect();
-                }
-                catch (Exception ex)
-                {
-                    MsgBoxHelper.Error(this, ex.Message);
-                }
+                //    pipeClient.Connect();
+                //}
+                //catch (Exception ex)
+                //{
+                //    MsgBoxHelper.Error(this, ex.Message);
+                //}
             }
             Loaded -= UserControl_Loaded;
         }
diff --git a/Plugin.DOControl/Plugin.DOControl.csproj b/Plugin.DOControl/Plugin.DOControl.csproj
index c380687..0d99dca 100644
--- a/Plugin.DOControl/Plugin.DOControl.csproj
+++ b/Plugin.DOControl/Plugin.DOControl.csproj
@@ -145,6 +145,10 @@
       <Project>{afea100a-9223-448d-ab10-3015d93dd98c}</Project>
       <Name>HMI.Util</Name>
     </ProjectReference>
+    <ProjectReference Include="..\OPCClient\OPCClient.csproj">
+      <Project>{E5740BB5-9FC6-4A20-B7C2-CC90C281ED05}</Project>
+      <Name>OPCClient</Name>
+    </ProjectReference>
   </ItemGroup>
   <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
diff --git a/Plugin.DODriverControl/Api/Dask.cs b/Plugin.DODriverControl/Api/Dask.cs
new file mode 100644
index 0000000..a77f6bf
--- /dev/null
+++ b/Plugin.DODriverControl/Api/Dask.cs
@@ -0,0 +1,801 @@
+using System.Runtime.InteropServices;
+
+namespace Plugin.DODriverControl.Api;
+
+public delegate void CallbackDelegate();  
+
+public class DASK
+{
+	//ADLink PCI Card Type
+	public const ushort PCI_6208V		=1;
+	public const ushort PCI_6208A		=2;
+	public const ushort PCI_6308V       =3;
+	public const ushort PCI_6308A       =4;
+	public const ushort PCI_7200        =5;
+	public const ushort PCI_7230        =6;
+	public const ushort PCI_7233        =7;
+	public const ushort PCI_7234        =8;
+	public const ushort PCI_7248        =9;
+	public const ushort PCI_7249        =10;
+	public const ushort PCI_7250        =11;
+	public const ushort PCI_7252        =12;
+	public const ushort PCI_7296        =13;
+	public const ushort PCI_7300A_RevA  =14;
+	public const ushort PCI_7300A_RevB  =15;
+	public const ushort PCI_7432        =16;
+	public const ushort PCI_7433        =17;
+	public const ushort PCI_7434        =18;
+	public const ushort PCI_8554        =19;
+	public const ushort PCI_9111DG      =20;
+	public const ushort PCI_9111HR      =21;
+	public const ushort PCI_9112        =22;
+	public const ushort PCI_9113        =23;
+	public const ushort PCI_9114DG      =24;
+	public const ushort PCI_9114HG      =25;
+	public const ushort PCI_9118DG      =26;
+	public const ushort PCI_9118HG      =27;
+	public const ushort PCI_9118HR      =28;
+	public const ushort PCI_9810        =29;
+	public const ushort PCI_9812        =30;
+	public const ushort PCI_7396        =31;
+	public const ushort PCI_9116        =32;
+	public const ushort PCI_7256        =33;
+	public const ushort PCI_7258        =34;
+    public const ushort PCI_7260        =35;
+    public const ushort PCI_7452        =36;
+    public const ushort PCI_7442        =37;
+
+	public const ushort MAX_CARD        =32;
+
+//Error Number
+	public const short NoError						=0;
+	public const short ErrorUnknownCardType         =-1;
+	public const short ErrorInvalidCardNumber       =-2;
+	public const short ErrorTooManyCardRegistered   =-3;
+	public const short ErrorCardNotRegistered       =-4;
+	public const short ErrorFuncNotSupport          =-5;
+	public const short ErrorInvalidIoChannel        =-6;
+	public const short ErrorInvalidAdRange          =-7;
+	public const short ErrorContIoNotAllowed        =-8;
+	public const short ErrorDiffRangeNotSupport     =-9;
+	public const short ErrorLastChannelNotZero      =-10;
+	public const short ErrorChannelNotDescending    =-11;
+	public const short ErrorChannelNotAscending     =-12;
+	public const short ErrorOpenDriverFailed        =-13;
+	public const short ErrorOpenEventFailed         =-14;
+	public const short ErrorTransferCountTooLarge   =-15;
+	public const short ErrorNotDoubleBufferMode     =-16;
+	public const short ErrorInvalidSampleRate       =-17;
+	public const short ErrorInvalidCounterMode      =-18;
+	public const short ErrorInvalidCounter          =-19;
+	public const short ErrorInvalidCounterState     =-20;
+	public const short ErrorInvalidBinBcdParam      =-21;
+	public const short ErrorBadCardType             =-22;
+	public const short ErrorInvalidDaRefVoltage     =-23;
+	public const short ErrorAdTimeOut               =-24;
+	public const short ErrorNoAsyncAI               =-25;
+	public const short ErrorNoAsyncAO               =-26;
+	public const short ErrorNoAsyncDI               =-27;
+	public const short ErrorNoAsyncDO               =-28;
+	public const short ErrorNotInputPort            =-29;
+	public const short ErrorNotOutputPort           =-30;
+	public const short ErrorInvalidDioPort          =-31;
+	public const short ErrorInvalidDioLine          =-32;
+	public const short ErrorContIoActive            =-33;
+	public const short ErrorDblBufModeNotAllowed    =-34;
+	public const short ErrorConfigFailed            =-35;
+	public const short ErrorInvalidPortDirection    =-36;
+	public const short ErrorBeginThreadError        =-37;
+	public const short ErrorInvalidPortWidth        =-38;
+	public const short ErrorInvalidCtrSource        =-39;
+	public const short ErrorOpenFile                =-40;
+	public const short ErrorAllocateMemory          =-41;
+	public const short ErrorDaVoltageOutOfRange     =-42;
+	public const short ErrorDaExtRefNotAllowed      =-43;
+	public const short ErrorDIODataWidthError       =-44;
+	public const short ErrorTaskCodeError           =-45;
+	public const short ErrortriggercountError       =-46;
+	public const short ErrorInvalidTriggerMode      =-47;
+	public const short ErrorInvalidTriggerType	    =-48;
+	public const short ErrorInvalidCounterValue		=-50;
+	public const short ErrorInvalidEventHandle      =-60;
+	public const short ErrorNoMessageAvailable      =-61;
+	public const short ErrorEventMessgaeNotAdded    =-62;
+//Error number for driver API 
+	public const short ErrorConfigIoctl				=-201;
+	public const short ErrorAsyncSetIoctl			=-202;
+	public const short ErrorDBSetIoctl				=-203;
+	public const short ErrorDBHalfReadyIoctl		=-204;
+	public const short ErrorContOPIoctl				=-205;
+	public const short ErrorContStatusIoctl			=-206;
+	public const short ErrorPIOIoctl				=-207;
+	public const short ErrorDIntSetIoctl			=-208;
+	public const short ErrorWaitEvtIoctl			=-209;
+	public const short ErrorOpenEvtIoctl			=-210;
+	public const short ErrorCOSIntSetIoctl			=-211;
+	public const short ErrorMemMapIoctl				=-212;
+	public const short ErrorMemUMapSetIoctl			=-213;
+	public const short ErrorCTRIoctl			    =-214;
+	public const short ErrorGetResIoctl				=-215;
+
+//Synchronous Mode
+	public const ushort SYNCH_OP        =1;
+	public const ushort ASYNCH_OP       =2;
+
+//AD Range
+	public const ushort AD_B_10_V		=1;
+	public const ushort AD_B_5_V        =2;
+	public const ushort AD_B_2_5_V      =3;
+	public const ushort AD_B_1_25_V     =4;
+	public const ushort AD_B_0_625_V    =5;
+	public const ushort AD_B_0_3125_V   =6;
+	public const ushort AD_B_0_5_V      =7;
+	public const ushort AD_B_0_05_V     =8;
+	public const ushort AD_B_0_005_V    =9;
+	public const ushort AD_B_1_V		=10;
+	public const ushort AD_B_0_1_V		=11;
+	public const ushort AD_B_0_01_V		=12;
+	public const ushort AD_B_0_001_V	=13;
+	public const ushort AD_U_20_V		=14;
+	public const ushort AD_U_10_V		=15;
+	public const ushort AD_U_5_V		=16;
+	public const ushort AD_U_2_5_V		=17;
+	public const ushort AD_U_1_25_V		=18;
+	public const ushort AD_U_1_V		=19;
+	public const ushort AD_U_0_1_V		=20;
+	public const ushort AD_U_0_01_V		=21;
+	public const ushort AD_U_0_001_V	=22;
+
+//Clock Mode
+	public const ushort TRIG_SOFTWARE           =0;
+	public const ushort TRIG_INT_PACER          =1;
+	public const ushort TRIG_EXT_STROBE         =2;
+	public const ushort TRIG_HANDSHAKE          =3;
+	public const ushort TRIG_CLK_10MHZ          =4;  //PCI-7300A
+	public const ushort TRIG_CLK_20MHZ          =5;  //PCI-7300A
+	public const ushort TRIG_DO_CLK_TIMER_ACK   =6;  //PCI-7300A Rev. B
+	public const ushort TRIG_DO_CLK_10M_ACK     =7;  //PCI-7300A Rev. B
+	public const ushort TRIG_DO_CLK_20M_ACK     =8;  //PCI-7300A Rev. B
+//Virtual Sampling Rate for using external clock as the clock source
+	public const double CLKSRC_EXT_SampRate        =10000;
+
+//-------- Constants for PCI-6208A/PCI-6308A/PCI-6308V -------------------
+//Output Mode
+	public const ushort P6208_CURRENT_0_20MA    =0;
+	public const ushort P6208_CURRENT_4_20MA    =3;
+	public const ushort P6208_CURRENT_5_25MA    =1;
+	public const ushort P6308_CURRENT_0_20MA    =0;
+	public const ushort P6308_CURRENT_4_20MA    =3;
+	public const ushort P6308_CURRENT_5_25MA    =1;
+//AO Setting
+	public const ushort P6308V_AO_CH0_3         =0;
+	public const ushort P6308V_AO_CH4_7         =1;
+	public const ushort P6308V_AO_UNIPOLAR      =0;
+	public const ushort P6308V_AO_BIPOLAR       =1;
+//-------- Constants for PCI-7200 --------------------
+//InputMode
+	public const ushort DI_WAITING              =0x02;
+	public const ushort DI_NOWAITING            =0x00;
+
+	public const ushort DI_TRIG_RISING          =0x04;
+	public const ushort DI_TRIG_FALLING         =0x00;
+
+	public const ushort IREQ_RISING             =0x08;
+	public const ushort IREQ_FALLING            =0x00;
+
+//Output Mode
+	public const ushort OREQ_ENABLE             =0x10;
+	public const ushort OREQ_DISABLE            =0x00;
+
+	public const ushort OTRIG_HIGH              =0x20;
+	public const ushort OTRIG_LOW               =0x00;
+
+//-------- Constants for PCI-7248/7296/7396/7442 ---------------
+//DIO Port Direction
+    public const ushort INPUT_PORT = 1;
+    public const ushort OUTPUT_PORT = 2;
+//DIO Line Direction
+    public const ushort INPUT_LINE = 1;
+    public const ushort OUTPUT_LINE = 2;
+
+//Channel & Port
+	public const ushort Channel_P1A             =0;
+	public const ushort Channel_P1B             =1;
+	public const ushort Channel_P1C             =2;
+	public const ushort Channel_P1CL            =3;
+	public const ushort Channel_P1CH            =4;
+	public const ushort Channel_P1AE            =10;
+	public const ushort Channel_P1BE            =11;
+	public const ushort Channel_P1CE            =12;
+	public const ushort Channel_P2A             =5;
+	public const ushort Channel_P2B             =6;
+	public const ushort Channel_P2C             =7;
+	public const ushort Channel_P2CL            =8;
+	public const ushort Channel_P2CH            =9;
+	public const ushort Channel_P2AE            =15;
+	public const ushort Channel_P2BE            =16;
+	public const ushort Channel_P2CE            =17;
+	public const ushort Channel_P3A             =10;
+	public const ushort Channel_P3B             =11;
+	public const ushort Channel_P3C             =12;
+	public const ushort Channel_P3CL            =13;
+	public const ushort Channel_P3CH            =14;
+	public const ushort Channel_P4A             =15;
+	public const ushort Channel_P4B             =16;
+	public const ushort Channel_P4C             =17;
+	public const ushort Channel_P4CL            =18;
+	public const ushort Channel_P4CH            =19;
+	public const ushort Channel_P5A             =20;
+	public const ushort Channel_P5B             =21;
+	public const ushort Channel_P5C             =22;
+	public const ushort Channel_P5CL            =23;
+	public const ushort Channel_P5CH            =24;
+	public const ushort Channel_P6A             =25;
+	public const ushort Channel_P6B             =26;
+	public const ushort Channel_P6C             =27;
+	public const ushort Channel_P6CL            =28;
+	public const ushort Channel_P6CH            =29;
+//the following are used for PCI7396
+	public const ushort Channel_P1              =30;
+	public const ushort Channel_P2              =31;
+	public const ushort Channel_P3              =32;
+	public const ushort Channel_P4              =33;
+	public const ushort Channel_P1E             =34; //only used by DIO_PortConfig function
+	public const ushort Channel_P2E             =35; //only used by DIO_PortConfig function
+	public const ushort Channel_P3E             =36; //only used by DIO_PortConfig function
+	public const ushort Channel_P4E             =37; //only used by DIO_PortConfig function
+//7442
+    public const ushort P7442_CH0 = 0;
+    public const ushort P7442_CH1 = 1;
+    public const ushort P7442_TTL0 = 2;
+    public const ushort P7442_TTL1 = 3;
+//-------- Constants for PCI-7300A -------------------
+//Wait Status
+	public const ushort P7300_WAIT_NO           =0;
+	public const ushort P7300_WAIT_TRG          =1;
+	public const ushort P7300_WAIT_FIFO         =2;
+	public const ushort P7300_WAIT_BOTH         =3;
+
+//Terminator control
+	public const ushort P7300_TERM_OFF          =0;
+	public const ushort P7300_TERM_ON           =1;
+
+//DI control signals polarity for PCI-7300A Rev. B
+	public const ushort P7300_DIREQ_POS         =0x00000000;
+	public const ushort P7300_DIREQ_NEG         =0x00000001;
+	public const ushort P7300_DIACK_POS         =0x00000000;
+	public const ushort P7300_DIACK_NEG         =0x00000002;
+	public const ushort P7300_DITRIG_POS        =0x00000000;
+	public const ushort P7300_DITRIG_NEG        =0x00000004;
+
+//DO control signals polarity for PCI-7300A Rev. B
+	public const ushort P7300_DOREQ_POS         =0x00000000;
+	public const ushort P7300_DOREQ_NEG         =0x00000008;
+	public const ushort P7300_DOACK_POS         =0x00000000;
+	public const ushort P7300_DOACK_NEG         =0x00000010;
+	public const ushort P7300_DOTRIG_POS        =0x00000000;
+	public const ushort P7300_DOTRIG_NEG        =0x00000020;
+//-------- Constants for PCI-7432/7433/7434 ---------------
+	public const ushort PORT_DI_LOW             =0;
+	public const ushort PORT_DI_HIGH            =1;
+	public const ushort PORT_DO_LOW             =0;
+	public const ushort PORT_DO_HIGH            =1;
+	public const ushort P7432R_DO_LED           =1;
+	public const ushort P7433R_DO_LED           =0;
+	public const ushort P7434R_DO_LED           =2;
+	public const ushort P7432R_DI_SLOT          =1;
+	public const ushort P7433R_DI_SLOT          =2;
+	public const ushort P7434R_DI_SLOT          =0;
+//-- Dual-Interrupt Source control for PCI-7248/96 & 7432/33 & 7230 & 8554 & 7396 &7256 &7260 ---
+	public const short INT1_DISABLE             =-1;    //INT1 Disabled
+	public const short INT1_COS                 =0;    //INT1 COS : only available for PCI-7396, PCI-7256, PCI-7260
+	public const short INT1_FP1C0               =1;    //INT1 by Falling edge of P1C0 : only available for PCI7248/96/7396
+	public const short INT1_RP1C0_FP1C3         =2;    //INT1 by P1C0 Rising or P1C3 Falling : only available for PCI7248/96/7396
+	public const short INT1_EVENT_COUNTER       =3;    //INT1 by Event Counter down to zero : only available for PCI7248/96/7396
+	public const short INT1_EXT_SIGNAL          =1;    //INT1 by external signal : only available for PCI7432/7433/7230/8554
+	public const short INT1_COUT12              =1;    //INT1 COUT12 : only available for PCI8554
+	public const short INT1_CH0				    =1;    //INT1 CH0 : only available for PCI7256, PCI7260
+    public const short INT1_COS0                =1;    //INT1 COS0 : only available for PCI-7452/PCI-7442
+    public const short INT1_COS1                =2;    //INT1 COS1 : only available for PCI-7452/PCI-7442
+    public const short INT1_COS2                =4;    //INT1 COS2 : only available for PCI-7452/PCI-7442
+    public const short INT1_COS3                =8;    //INT1 COS3 : only available for PCI-7452/PCI-7442
+	public const short INT2_DISABLE            =-1;    //INT2 Disabled
+	public const short INT2_COS                 =0;    //INT2 COS : only available for PCI-7396
+	public const short INT2_FP2C0               =1;    //INT2 by Falling edge of P2C0 : only available for PCI7248/96/7396
+	public const short INT2_RP2C0_FP2C3         =2;    //INT2 by P2C0 Rising or P2C3 Falling : only available for PCI7248/96/7396
+	public const short INT2_TIMER_COUNTER       =3;    //INT2 by Timer Counter down to zero : only available for PCI7248/96/7396
+	public const short INT2_EXT_SIGNAL          =1;    //INT2 by external signal : only available for PCI7432/7433/7230/8554
+	public const short INT2_CH1				    =2;	  //INT2 CH1 : only available for PCI7256, PCI7260
+	public const short INT2_WDT				    =4;	  //INT2 by WDT
+
+	public const ushort ManualResetIEvt		    =0x4000;//interrupt event is manually reset by user
+	public const ushort WDT_OVRFLOW_SAFETYOUT	=0x8000;// enable safteyout while WDT overflow
+//-------- Constants for PCI-8554 --------------------
+//Clock Source of Cunter N
+	public const ushort ECKN            =0;
+	public const ushort COUTN_1         =1;
+	public const ushort CK1             =2;
+	public const ushort COUT10          =3;
+
+//Clock Source of CK1
+	public const ushort CK1_C8M         =0;
+	public const ushort CK1_COUT11      =1;
+
+//Debounce Clock
+	public const ushort DBCLK_COUT11    =0;
+	public const ushort DBCLK_2MHZ      =1;
+
+//-------- Constants for PCI-9111 --------------------
+//Dual Interrupt Mode
+	public const ushort P9111_INT1_EOC            =0;       //Ending of AD conversion
+	public const ushort P9111_INT1_FIFO_HF        =1;       //FIFO Half Full
+	public const ushort P9111_INT2_PACER          =0;       //Every Timer tick
+	public const ushort P9111_INT2_EXT_TRG        =1;       //ExtTrig High->Low
+
+//Channel Count
+	public const ushort P9111_CHANNEL_DO          =0;
+	public const ushort P9111_CHANNEL_EDO         =1;
+	public const ushort P9111_CHANNEL_DI          =0;
+	public const ushort P9111_CHANNEL_EDI         =1;
+
+//EDO function
+	public const ushort P9111_EDO_INPUT           =1;   //EDO port set as Input port
+	public const ushort P9111_EDO_OUT_EDO         =2;   //EDO port set as Output port
+	public const ushort P9111_EDO_OUT_CHN         =3;   //EDO port set as channel number ouput port
+
+//Trigger Mode
+	public const ushort P9111_TRGMOD_SOFT         =0x00;   //Software Trigger Mode
+	public const ushort P9111_TRGMOD_PRE          =0x01;   //Pre-Trigger Mode
+	public const ushort P9111_TRGMOD_POST         =0x02;   //Post Trigger Mode
+
+//AO Setting
+	public const ushort P9111_AO_UNIPOLAR         =0;
+	public const ushort P9111_AO_BIPOLAR          =1;
+
+//-------- Constants for PCI-9118 --------------------
+	public const ushort P9118_AI_BiPolar          =0x00;
+	public const ushort P9118_AI_UniPolar         =0x01;
+
+	public const ushort P9118_AI_SingEnded        =0x00;
+	public const ushort P9118_AI_Differential     =0x02;
+
+	public const ushort P9118_AI_ExtG             =0x04;
+
+	public const ushort P9118_AI_ExtTrig          =0x08;
+
+	public const ushort P9118_AI_DtrgNegative     =0x00;
+	public const ushort P9118_AI_DtrgPositive     =0x10;
+
+	public const ushort P9118_AI_EtrgNegative     =0x00;
+	public const ushort P9118_AI_EtrgPositive     =0x20;
+
+	public const ushort P9118_AI_BurstModeEn      =0x40;
+	public const ushort P9118_AI_SampleHold       =0x80;
+	public const ushort P9118_AI_PostTrgEn        =0x100;
+	public const ushort P9118_AI_AboutTrgEn       =0x200;
+
+//-------- Constants for PCI-9116 --------------------
+	public const ushort P9116_AI_LocalGND	      =0x00;
+	public const ushort P9116_AI_UserCMMD         =0x01;
+	public const ushort P9116_AI_SingEnded        =0x00;
+	public const ushort P9116_AI_Differential     =0x02;
+	public const ushort P9116_AI_BiPolar          =0x00;
+	public const ushort P9116_AI_UniPolar         =0x04;
+
+	public const ushort P9116_TRGMOD_SOFT         =0x00;   //Software Trigger Mode
+	public const ushort P9116_TRGMOD_POST         =0x10;   //Post Trigger Mode
+	public const ushort P9116_TRGMOD_DELAY        =0x20;   //Delay Trigger Mode
+	public const ushort P9116_TRGMOD_PRE          =0x30;   //Pre-Trigger Mode
+	public const ushort P9116_TRGMOD_MIDL         =0x40;   //Middle Trigger Mode
+	public const ushort P9116_AI_TrgPositive      =0x00;
+	public const ushort P9116_AI_TrgNegative      =0x80;
+	public const ushort P9116_AI_ExtTimeBase	  =0x100;
+	public const ushort P9116_AI_IntTimeBase	  =0x000;
+	public const ushort P9116_AI_DlyInSamples     =0x200;
+	public const ushort P9116_AI_DlyInTimebase    =0x000;
+	public const ushort P9116_AI_ReTrigEn         =0x400;
+	public const ushort P9116_AI_MCounterEn       =0x800;
+	public const ushort P9116_AI_SoftPolling      =0x0000;
+	public const ushort P9116_AI_INT		      =0x1000;
+	public const ushort P9116_AI_DMA			  =0x2000;
+
+//-------- Constants for PCI-9812 --------------------
+//Trigger Mode
+	public const ushort P9812_TRGMOD_SOFT         =0x00;   //Software Trigger Mode
+	public const ushort P9812_TRGMOD_POST         =0x01;   //Post Trigger Mode
+	public const ushort P9812_TRGMOD_PRE          =0x02;   //Pre-Trigger Mode
+	public const ushort P9812_TRGMOD_DELAY        =0x03;   //Delay Trigger Mode
+	public const ushort P9812_TRGMOD_MIDL         =0x04;   //Middle Trigger Mode
+
+	public const ushort P9812_AIEvent_Manual      =0x08;   //Middle Trigger Mode
+
+    //Trigger Source
+	public const ushort P9812_TRGSRC_CH0          =0x00;   //trigger source --CH0
+	public const ushort P9812_TRGSRC_CH1          =0x08;   //trigger source --CH1
+	public const ushort P9812_TRGSRC_CH2          =0x10;   //trigger source --CH2
+	public const ushort P9812_TRGSRC_CH3          =0x18;   //trigger source --CH3
+	public const ushort P9812_TRGSRC_EXT_DIG      =0x20;   //External Digital Trigger
+
+//Trigger Polarity
+	public const ushort P9812_TRGSLP_POS          =0x00;   //Positive slope trigger
+	public const ushort P9812_TRGSLP_NEG          =0x40;   //Negative slope trigger
+
+//Frequency Selection
+	public const ushort P9812_AD2_GT_PCI          =0x80;   //Freq. of A/D clock > PCI clock freq.
+	public const ushort P9812_AD2_LT_PCI          =0x00;   //Freq. of A/D clock < PCI clock freq.
+
+//Clock Source
+	public const ushort P9812_CLKSRC_INT          =0x000;   //Internal clock
+	public const ushort P9812_CLKSRC_EXT_SIN      =0x100;  //External SIN wave clock
+	public const ushort P9812_CLKSRC_EXT_DIG      =0x200;  //External Square wave clock
+
+//EMG shdn ctrl code
+	public const ushort EMGSHDN_OFF                 =0;     //off
+	public const ushort EMGSHDN_ON                  =1; //on
+	public const ushort EMGSHDN_RECOVERY            =2; //recovery
+
+//Hot Reset Hold ctrl code
+	public const ushort HRH_OFF                     =0; //off
+    public const ushort HRH_ON                      =1; //on
+
+//-------- Timer/Counter -----------------------------
+//Counter Mode (8254)
+	public const ushort TOGGLE_OUTPUT             =0;     //Toggle output from low to high on terminal count
+	public const ushort PROG_ONE_SHOT             =1;     //Programmable one-shot
+	public const ushort RATE_GENERATOR            =2;     //Rate generator
+	public const ushort SQ_WAVE_RATE_GENERATOR    =3;     //Square wave rate generator
+	public const ushort SOFT_TRIG                 =4;     //Software-triggered strobe
+	public const ushort HARD_TRIG                 =5;     //Hardware-triggered strobe
+
+//General Purpose Timer/Counter
+//Counter Mode
+	public const ushort General_Counter           =0x00; //general counter
+	public const ushort Pulse_Generation          =0x01; //pulse generation
+//GPTC clock source
+	public const ushort GPTC_CLKSRC_EXT			  =0x08;
+	public const ushort GPTC_CLKSRC_INT			  =0x00;
+	public const ushort GPTC_GATESRC_EXT		  =0x10;
+	public const ushort GPTC_GATESRC_INT		  =0x00;
+	public const ushort GPTC_UPDOWN_SELECT_EXT	  =0x20;
+	public const ushort GPTC_UPDOWN_SELECT_SOFT	  =0x00;
+	public const ushort GPTC_UP_CTR				  =0x40;
+	public const ushort GPTC_DOWN_CTR			  =0x00;
+	public const ushort GPTC_ENABLE				  =0x80;
+	public const ushort GPTC_DISABLE			  =0x00;
+
+//Watchdog Timer
+//Counter action
+	public const ushort WDT_DISARM        =0;
+	public const ushort WDT_ARM           =1;
+	public const ushort WDT_RESTART       =2;
+
+//Pattern ID
+	public const ushort INIT_PTN               =0;
+	public const ushort EMGSHDN_PTN            =1;
+
+//16-bit binary or 4-decade BCD counter
+	public const ushort BIN             =0;
+	public const ushort BCD             =1;
+
+//Pattern ID for 7442
+    public const ushort INIT_PTN_CH0 =          0;
+	public const ushort INIT_PTN_CH1		   =1;
+	public const ushort SAFTOUT_PTN_CH0		   =4;
+	public const ushort SAFTOUT_PTN_CH1		   =5;	
+
+//DAQ Event type for the event message  
+	public const ushort AIEnd    =0;
+	public const ushort DIEnd    =0;
+	public const ushort DOEnd    =0;
+	public const ushort DBEvent  =1;
+
+	public const ushort RegBySlot=0x8000;
+
+/*------------------------------------------------------------------
+** PCIS-DASK Function prototype
+------------------------------------------------------------------*/
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short Register_Card (ushort CardType, ushort card_num);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short Release_Card  (ushort CardNumber);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short GetActualRate (ushort CardNumber, double fSampleRate, out double fActualRate);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short EMGShutDownControl (ushort CardNumber, byte ctrl);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short EMGShutDownStatus (ushort CardNumber, out byte sts);
+	[DllImport("PCI-Dask64.dll")]
+    public static extern short HotResetHoldControl(ushort wCardNumber, byte enable);
+    [DllImport("PCI-Dask64.dll")]
+    public static extern short HotResetHoldStatus(ushort wCardNumber, out byte sts);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short GetInitPattern (ushort CardNumber, byte patID, out uint pattern);
+    [DllImport("PCI-Dask64.dll")]
+    public static extern short SetInitPattern(ushort wCardNumber, byte patID, uint pattern);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short IdentifyLED_Control (ushort CardNumber, byte ctrl);
+/*---------------------------------------------------------------------------*/
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_9111_Config (ushort CardNumber, ushort TrigSource, ushort TrgMode, ushort TraceCnt);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_9112_Config (ushort CardNumber, ushort TrigSource);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_9113_Config (ushort CardNumber, ushort TrigSource);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_9114_Config (ushort CardNumber, ushort TrigSource);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_9116_Config (ushort CardNumber, ushort ConfigCtrl, ushort TrigCtrl, ushort PostCnt, ushort MCnt, ushort ReTrgCnt);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_9118_Config (ushort CardNumber, ushort ModeCtrl, ushort FunCtrl, ushort BurstCnt, ushort PostCnt);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_9812_Config (ushort CardNumber, ushort TrgMode, ushort TrgSrc, ushort TrgPol, ushort ClkSel, ushort TrgLevel, ushort PostCnt);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_9812_SetDiv (ushort wCardNumber, uint PacerVal);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_9114_PreTrigConfig (ushort CardNumber, ushort PreTrgEn, ushort TraceCnt);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_9116_CounterInterval (ushort wCardNumber, uint ScanIntrv, uint SampIntrv);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_InitialMemoryAllocated (ushort CardNumber, out uint MemSize);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_ReadChannel (ushort CardNumber, ushort Channel, ushort AdRange, out ushort Value);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_VReadChannel (ushort CardNumber, ushort Channel, ushort AdRange, out double voltage);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_VoltScale (ushort CardNumber, ushort AdRange, ushort reading, out double voltage);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_ContReadChannel (ushort CardNumber, ushort Channel, ushort AdRange,
+               ushort[] Buffer, uint ReadCount, double SampleRate, ushort SyncMode);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_ContReadMultiChannels (ushort CardNumber, ushort NumChans, ushort[] Chans,
+               ushort[] AdRanges, ushort[] Buffer, uint ReadCount,
+               double SampleRate, ushort SyncMode);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_ContScanChannels (ushort CardNumber, ushort Channel, ushort AdRange,
+               ushort[] Buffer, uint ReadCount, double SampleRate, ushort SyncMode);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_ContReadChannelToFile (ushort CardNumber, ushort Channel, ushort AdRange,
+               string FileName, uint ReadCount, double SampleRate, ushort SyncMode);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_ContReadMultiChannelsToFile (ushort CardNumber, ushort NumChans, ushort[] Chans,
+               ushort[] AdRanges, string[] FileName, uint ReadCount,
+               double SampleRate, ushort SyncMode);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_ContScanChannelsToFile (ushort CardNumber, ushort Channel, ushort AdRange,
+               string FileName, uint ReadCount, double SampleRate, ushort SyncMode);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_ContStatus (ushort CardNumber, out ushort Status);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_ContVScale (ushort wCardNumber, ushort adRange, ushort[] readingArray, double[] voltageArray, int count);
+	[DllImport("PCI-Dask64.dll")]
+    public static extern short AI_AsyncCheck(ushort CardNumber, out byte Stopped, out uint AccessCnt);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_AsyncClear (ushort CardNumber, out uint AccessCnt);
+	[DllImport("PCI-Dask64.dll")]
+    public static extern short AI_AsyncDblBufferHalfReady(ushort CardNumber, out byte HalfReady, out byte StopFlag);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_AsyncDblBufferMode (ushort CardNumber, bool Enable);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_AsyncDblBufferTransfer (ushort CardNumber, ushort[] Buffer);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_AsyncDblBufferOverrun (ushort CardNumber, ushort op, out ushort overrunFlag);
+	[DllImport("PCI-Dask64.dll")]
+    public static extern short AI_EventCallBack(ushort CardNumber, ushort mode, ushort EventType, MulticastDelegate callbackAddr);
+/*---------------------------------------------------------------------------*/
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AO_6208A_Config (ushort CardNumber, ushort V2AMode);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AO_6308A_Config (ushort CardNumber, ushort V2AMode);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AO_6308V_Config (ushort wCardNumber, ushort Channel, ushort wOutputPolarity, double refVoltage);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AO_9111_Config (ushort CardNumber, ushort OutputPolarity);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AO_9112_Config (ushort CardNumber, ushort Channel, double refVoltage);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AO_WriteChannel (ushort CardNumber, ushort Channel, short Value);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AO_VWriteChannel (ushort CardNumber, ushort Channel, double Voltage);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AO_VoltScale (ushort CardNumber, ushort Channel, double Voltage, out short binValue);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AO_SimuWriteChannel (ushort wCardNumber, ushort wGroup, short[] pwBuffer);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AO_SimuVWriteChannel (ushort wCardNumber, ushort wGroup, double[] VBuffer);
+/*---------------------------------------------------------------------------*/
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DI_7200_Config (ushort CardNumber, ushort TrigSource, ushort ExtTrigEn, ushort TrigPol, ushort I_REQ_Pol);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DI_7300A_Config (ushort CardNumber, ushort PortWidth, ushort TrigSource, ushort WaitStatus, ushort Terminator, ushort I_REQ_Pol, bool clear_fifo, bool disable_di);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DI_7300B_Config (ushort CardNumber, ushort PortWidth, ushort TrigSource, ushort WaitStatus, ushort Terminator, ushort I_Cntrl_Pol, bool clear_fifo, bool disable_di);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DI_InitialMemoryAllocated (ushort CardNumber, out uint DmaSize);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DI_ReadLine (ushort CardNumber, ushort Port, ushort Line, out ushort State);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DI_ReadPort (ushort CardNumber, ushort Port, out uint Value);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DI_ContReadPort (ushort CardNumber, ushort Port, byte[] Buffer,
+               uint ReadCount, double SampleRate, ushort SyncMode);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DI_ContReadPort (ushort CardNumber, ushort Port, ushort[] Buffer,
+               uint ReadCount, double SampleRate, ushort SyncMode);
+	[DllImport("PCI-Dask64.dll")]
+    public static extern short DI_ContReadPort(ushort CardNumber, ushort Port, uint[] Buffer,
+               uint ReadCount, double SampleRate, ushort SyncMode);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DI_ContReadPortToFile (ushort CardNumber, ushort Port, string FileName, 
+								  uint ReadCount, double SampleRate, ushort SyncMode);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DI_ContStatus (ushort CardNumber, out ushort Status);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DI_AsyncCheck (ushort CardNumber, out byte Stopped, out uint AccessCnt);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DI_AsyncClear (ushort CardNumber, out uint AccessCnt);
+	[DllImport("PCI-Dask64.dll")]
+    public static extern short DI_AsyncDblBufferHalfReady(ushort CardNumber, out byte HalfReady);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DI_AsyncDblBufferMode (ushort CardNumber, bool Enable);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DI_AsyncDblBufferTransfer (ushort CardNumber, byte[] Buffer);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DI_AsyncDblBufferTransfer (ushort CardNumber, short[] Buffer);
+	[DllImport("PCI-Dask64.dll")]
+    public static extern short DI_AsyncDblBufferTransfer(ushort CardNumber, uint[] Buffer);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DI_ContMultiBufferSetup (ushort wCardNumber, byte[] pwBuffer, uint dwReadCount, out ushort BufferId);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DI_ContMultiBufferSetup (ushort wCardNumber, short[] pwBuffer, uint dwReadCount, out ushort BufferId);
+	[DllImport("PCI-Dask64.dll")]
+    public static extern short DI_ContMultiBufferSetup(ushort wCardNumber, uint[] pwBuffer, uint dwReadCount, out ushort BufferId);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DI_ContMultiBufferStart (ushort wCardNumber, ushort wPort, double fSampleRate);
+	[DllImport("PCI-Dask64.dll")]
+    public static extern short DI_AsyncMultiBufferNextReady(ushort CardNumber, out byte bNextReady, out ushort wBufferId);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DI_AsyncDblBufferOverrun (ushort CardNumber, ushort op, out ushort overrunFlag);
+	[DllImport("PCI-Dask64.dll")]
+    public static extern short DI_EventCallBack(ushort CardNumber, short mode, short EventType, MulticastDelegate callbackAddr);
+/*---------------------------------------------------------------------------*/
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DO_7200_Config (ushort CardNumber, ushort TrigSource, ushort OutReqEn, ushort OutTrigSig);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DO_7300A_Config (ushort CardNumber, ushort PortWidth, ushort TrigSource, ushort WaitStatus, ushort Terminator, ushort O_REQ_Pol);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DO_7300B_Config (ushort CardNumber, ushort PortWidth, ushort TrigSource, ushort WaitStatus, ushort Terminator, ushort O_Cntrl_Pol, uint FifoThreshold);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DO_InitialMemoryAllocated (ushort CardNumber, out uint MemSize);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DO_WriteLine (ushort CardNumber, ushort Port, ushort Line, ushort Value);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DO_WritePort (ushort CardNumber, byte Port, uint Value);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DO_WritePort (ushort CardNumber, ushort Port, uint Value);
+	[DllImport("PCI-Dask64.dll")]
+    public static extern short DO_WritePort(ushort CardNumber, uint Port, uint Value);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DO_WriteExtTrigLine (ushort CardNumber, ushort Value);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DO_ReadLine (ushort CardNumber, ushort Port, ushort Line, out ushort Value);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DO_ReadPort (ushort CardNumber, ushort Port, out uint Value);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DO_ContWritePort (ushort CardNumber, ushort Port, byte[] Buffer,
+               uint WriteCount, ushort Iterations, double SampleRate, ushort SyncMode);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DO_ContWritePort (ushort CardNumber, ushort Port, ushort[] Buffer,
+               uint WriteCount, ushort Iterations, double SampleRate, ushort SyncMode);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DO_ContWritePort (ushort CardNumber, ushort Port, uint[] Buffer,
+               uint WriteCount, ushort Iterations, double SampleRate, ushort SyncMode);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DO_PGStart (ushort CardNumber, byte[] Buffer, uint WriteCount, double SampleRate);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DO_PGStart (ushort CardNumber, short[] Buffer, uint WriteCount, double SampleRate);
+	[DllImport("PCI-Dask64.dll")]
+    public static extern short DO_PGStart(ushort CardNumber, uint[] Buffer, uint WriteCount, double SampleRate);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DO_PGStop (ushort CardNumber);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DO_ContStatus (ushort CardNumber, out ushort Status);
+	[DllImport("PCI-Dask64.dll")]
+    public static extern short DO_AsyncCheck(ushort CardNumber, out byte Stopped, out uint AccessCnt);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DO_AsyncClear (ushort CardNumber, out uint AccessCnt);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short EDO_9111_Config (ushort CardNumber, ushort EDO_Fun);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DO_ContMultiBufferSetup (ushort CardNumber, byte[] pwBuffer, uint dwWriteCount, out ushort BufferId);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DO_ContMultiBufferSetup (ushort CardNumber, short[] pwBuffer, uint dwWriteCount, out ushort BufferId);
+	[DllImport("PCI-Dask64.dll")]
+    public static extern short DO_ContMultiBufferSetup(ushort CardNumber, uint[] pwBuffer, uint dwWriteCount, out ushort BufferId);
+	[DllImport("PCI-Dask64.dll")]
+    public static extern short DO_AsyncMultiBufferNextReady(ushort CardNumber, out byte bNextReady, out ushort wBufferId);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DO_ContMultiBufferStart (ushort wCardNumber, ushort wPort, double fSampleRate);
+	[DllImport("PCI-Dask64.dll")]
+    public static extern short DO_EventCallBack(ushort CardNumber, short mode, short EventType, MulticastDelegate callbackAddr);
+/*---------------------------------------------------------------------------*/
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DIO_PortConfig (ushort CardNumber, ushort Port, ushort Direction);
+	[DllImport("PCI-Dask64.dll")]
+    public static extern short DIO_LinesConfig(ushort wCardNumber, ushort wPort, ushort wLinesdirmap);
+    [DllImport("PCI-Dask64.dll")]
+    public static extern short DIO_LineConfig(ushort wCardNumber, ushort wPort, ushort wLine, ushort wDirection);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DIO_SetDualInterrupt (ushort CardNumber, short Int1Mode, short Int2Mode, long hEvent);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DIO_SetCOSInterrupt (ushort CardNumber, ushort Port, ushort ctlA, ushort ctlB, ushort ctlC);
+	[DllImport("PCI-Dask64.dll")]
+    public static extern short DIO_INT1_EventMessage(ushort CardNumber, short Int1Mode, long windowHandle, long message, MulticastDelegate callbackAddr);
+	[DllImport("PCI-Dask64.dll")]
+    public static extern short DIO_INT2_EventMessage(ushort CardNumber, short Int2Mode, long windowHandle, long message, MulticastDelegate callbackAddr);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DIO_7300SetInterrupt (ushort CardNumber, short AuxDIEn, short T2En, long hEvent);
+	[DllImport("PCI-Dask64.dll")]
+    public static extern short DIO_AUXDI_EventMessage(ushort CardNumber, short AuxDIEn, long windowHandle, uint message, MulticastDelegate callbackAddr);
+	[DllImport("PCI-Dask64.dll")]
+    public static extern short DIO_T2_EventMessage(ushort CardNumber, short T2En, long windowHandle, uint message, MulticastDelegate callbackAddr);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DIO_GetCOSLatchData(ushort wCardNumber, out ushort CosLData);
+	[DllImport("PCI-Dask64.dll")]
+    public static extern short DIO_SetCOSInterrupt32(ushort wCardNumber, byte Port, uint ctl, out long hEvent, bool bManualReset);
+	[DllImport("PCI-Dask64.dll")]
+    public static extern short DIO_GetCOSLatchDataInt32(ushort wCardNumber, byte Port, out uint CosLData);
+/*---------------------------------------------------------------------------*/
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short CTR_Setup (ushort CardNumber, ushort Ctr, ushort Mode, uint Count, ushort BinBcd);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short CTR_Clear (ushort CardNumber, ushort Ctr, ushort State);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short CTR_Read (ushort CardNumber, ushort Ctr, out uint Value);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short CTR_Update (ushort CardNumber, ushort Ctr, uint Count);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short CTR_8554_ClkSrc_Config (ushort CardNumber, ushort Ctr, ushort ClockSource);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short CTR_8554_CK1_Config (ushort CardNumber, ushort ClockSource);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short CTR_8554_Debounce_Config (ushort CardNumber, ushort DebounceClock);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short GCTR_Setup (ushort wCardNumber, ushort wGCtr, ushort wGCtrCtrl,uint dwCount);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short GCTR_Clear (ushort wCardNumber, ushort wGCtr);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short GCTR_Read (ushort wCardNumber, ushort wGCtr, out uint pValue);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short WDT_Setup (ushort CardNumber, ushort wCtr, float ovflowSec, out float actualSec, out long hEvent);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short WDT_Control (ushort wCardNumber, ushort wCtr, ushort action);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short WDT_Status (ushort wCardNumber, ushort Ctr, out uint pValue);
+    [DllImport("PCI-Dask64.dll")]
+    public static extern short WDT_Reload(ushort wCardNumber, float ovflowSec, out float actualSec);
+/*---------------------------------------------------------------------------*/
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_GetEvent(ushort wCardNumber, out long hEvent);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AO_GetEvent(ushort wCardNumber, out long hEvent);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DI_GetEvent(ushort wCardNumber, out long hEvent);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DO_GetEvent(ushort wCardNumber, out long hEvent);
+/*---------------------------------------------------------------------------*/
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short AI_GetView(ushort wCardNumber, uint[] pView);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DI_GetView(ushort wCardNumber, uint[] pView);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short DO_GetView(ushort wCardNumber, uint[] pView);
+/*---------------------------------------------------------------------------*/
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short GetCardType (ushort wCardNumber, out ushort cardType);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short GetCardIndexFromID (ushort wCardNumber, out ushort cardType, out ushort cardIndex);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short GetBaseAddr(ushort wCardNumber, uint[] BaseAddr, uint[] BaseAddr2);
+	[DllImport("PCI-Dask64.dll")]
+	public static extern short GetLCRAddr(ushort wCardNumber, uint[] LcrAddr);
+}
\ No newline at end of file
diff --git a/Plugin.DODriverControl/Api/DaskHelper.cs b/Plugin.DODriverControl/Api/DaskHelper.cs
new file mode 100644
index 0000000..4f5241e
--- /dev/null
+++ b/Plugin.DODriverControl/Api/DaskHelper.cs
@@ -0,0 +1,132 @@
+锘縩amespace Plugin.DODriverControl.Api
+{
+   public class DaskHelper
+    {
+        public static ushort CardType(string name)
+        {
+            ushort cardtype = 0;
+            switch (name)
+            {
+                case "PCI_6208V":
+                    cardtype = 1;
+                    break;
+                case "PCI_6208A":
+                    cardtype = 2;
+                    break;
+                case "PCI_6308V":
+                    cardtype = 3;
+                    break;
+                case "PCI_6308A":
+                    cardtype = 4;
+                    break;
+                case "PCI_7200":
+                    cardtype = 5;
+                    break;
+                case "PCI_7230":
+                    cardtype = 6;
+                    break;
+                case "PCI_7233":
+                    cardtype = 7;
+                    break;
+                case "PCI_7234":
+                    cardtype = 8;
+                    break;
+                case "PCI_7248":
+                    cardtype = 9;
+                    break;
+                case "PCI_7249":
+                    cardtype = 10;
+                    break;
+                case "PCI_7250":
+                    cardtype = 11;
+                    break;
+                case "PCI_7252":
+                    cardtype = 12;
+                    break;
+                case "PCI_7296":
+                    cardtype = 13;
+                    break;
+                case "PCI_7300A_RevA":
+                    cardtype = 14;
+                    break;
+                case "PCI_7300A_RevB":
+                    cardtype = 15;
+                    break;
+                case "PCI_7432":
+                    cardtype = 16;
+                    break;
+                case "PCI_7433":
+                    cardtype = 17;
+                    break;
+                case "PCI_7434":
+                    cardtype = 18;
+                    break;
+                case "PCI_8554":
+                    cardtype = 19;
+                    break;
+                case "PCI_9111DG":
+                    cardtype = 20;
+                    break;
+                case "PCI_9111HR":
+                    cardtype = 21;
+                    break;
+                case "PCI_9112":
+                    cardtype = 22;
+                    break;
+                case "PCI_9113":
+                    cardtype = 23;
+                    break;
+                case "PCI_9114DG":
+                    cardtype = 24;
+                    break;
+                case "PCI_9114HG":
+                    cardtype = 25;
+                    break;
+                case "PCI_9118DG":
+                    cardtype = 26;
+                    break;
+                case "PCI_9118HG":
+                    cardtype = 27;
+                    break;
+                case "PCI_9118HR":
+                    cardtype = 28;
+                    break;
+                case "PCI_9810":
+                    cardtype = 29;
+                    break;
+                case "PCI_9812":
+                    cardtype = 30;
+                    break;
+                case "PCI_7396":
+                    cardtype = 31;
+                    break;
+                case "PCI_9116":
+                    cardtype = 32;
+                    break;
+                case "PCI_7256":
+                    cardtype = 33;
+                    break;
+                case "PCI_7258":
+                    cardtype = 34;
+                    break;
+                case "PCI_7260":
+                    cardtype = 35;
+                    break;
+                case "PCI_7452":
+                    cardtype = 36;
+                    break;
+                case "PCI_7442":
+                    cardtype = 37;
+                    break;
+                case "MAX_CARD":
+                    cardtype = 32;
+                    break;
+                default:
+                    cardtype = 16;
+                    break;
+            }
+
+            return cardtype;
+        }
+    }
+}
diff --git a/Plugin.DODriverControl/Api/IDIOController.cs b/Plugin.DODriverControl/Api/IDIOController.cs
new file mode 100644
index 0000000..fa2252b
--- /dev/null
+++ b/Plugin.DODriverControl/Api/IDIOController.cs
@@ -0,0 +1,15 @@
+锘縩amespace Plugin.DODriverControl.Api
+{
+    public interface IDIOController : IDisposable
+    {
+        bool Start(out string msg);
+
+        bool ReadDIBool(string address, out bool res, out string msg);
+
+        bool ReadDOBool(string address, out bool res, out string msg);
+
+        bool WriteBool(string address, bool value, out string msg);
+
+        bool Close();
+    }
+}
diff --git a/Plugin.DODriverControl/Api/ModBusTcpController.cs b/Plugin.DODriverControl/Api/ModBusTcpController.cs
new file mode 100644
index 0000000..f366f5e
--- /dev/null
+++ b/Plugin.DODriverControl/Api/ModBusTcpController.cs
@@ -0,0 +1,55 @@
+锘縰sing HslCommunication.ModBus;
+
+namespace Plugin.DODriverControl.Api
+{
+    public class ModBusTcpController : IDIOController
+    {
+        public string Ip { get; set; }
+        public int Port { get; set; }
+
+        private ModbusTcpNet client;
+
+        public bool Start(out string errMsg)
+        {
+            errMsg = string.Empty;
+            client = new ModbusTcpNet(Ip, Port);
+            var res = client.ConnectServer();
+            errMsg = res.Message;
+            return res.IsSuccess;
+        }
+
+        public bool ReadDIBool(string address, out bool res, out string msg)
+        {
+            var result = client?.ReadBool($"x=2;{address}");
+            msg = result?.Message;
+            res = result?.Content == true;
+            return result?.IsSuccess == true;
+        }
+
+        public bool ReadDOBool(string address, out bool res, out string msg)
+        {
+            var result = client?.ReadBool(address);
+            msg = result?.Message;
+            res = result?.Content == true;
+            return result?.IsSuccess == true;
+        }
+
+        public bool WriteBool(string address, bool value, out string msg)
+        {
+            msg = string.Empty;
+            var res = client?.Write(address, value);
+            msg = res.Message;
+            return res?.IsSuccess == true;
+        }
+
+        public bool Close()
+        {
+            return client?.ConnectClose()?.IsSuccess == true;
+
+        }
+        public void Dispose()
+        {
+            client?.Dispose();
+        }
+    }
+}
diff --git a/Plugin.DODriverControl/Api/PCIController.cs b/Plugin.DODriverControl/Api/PCIController.cs
new file mode 100644
index 0000000..a8d1f30
--- /dev/null
+++ b/Plugin.DODriverControl/Api/PCIController.cs
@@ -0,0 +1,49 @@
+锘縩amespace Plugin.DODriverControl.Api
+{
+    public class PCIController : IDIOController
+    {
+        public string Name { get; set; }
+
+        private short id;
+
+        public bool Start(out string msg)
+        {
+            msg = string.Empty;
+            id = DASK.Register_Card(DaskHelper.CardType(Name), 0);
+            return id >= 0;
+        }
+
+        public bool ReadDIBool(string address, out bool res, out string msg)
+        {
+            msg = string.Empty;
+            DASK.DI_ReadLine((ushort)id, 0, ushort.Parse(address), out ushort value);
+            res = Convert.ToBoolean(value);
+            return true;
+        }
+
+        public bool ReadDOBool(string address, out bool res, out string msg)
+        {
+            msg = string.Empty;
+            DASK.DO_ReadLine((ushort)id, 0, ushort.Parse(address), out ushort value);
+            res = Convert.ToBoolean(value);
+            return true;
+        }
+
+        public bool WriteBool(string address, bool value, out string msg)
+        {
+            msg = string.Empty;
+            var res = DASK.DO_WriteLine((ushort)id, 0, ushort.Parse(address), Convert.ToUInt16(value));
+            return res == 0;
+        }
+
+        public bool Close()
+        {
+            return DASK.Release_Card((ushort)id) == 0;
+        }
+
+        public void Dispose()
+        {
+            this.Close();
+        }
+    }
+}
diff --git a/Plugin.DODriverControl/AssemblyInfo.cs b/Plugin.DODriverControl/AssemblyInfo.cs
new file mode 100644
index 0000000..cc29e7f
--- /dev/null
+++ b/Plugin.DODriverControl/AssemblyInfo.cs
@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly:ThemeInfo(
+    ResourceDictionaryLocation.None,            //where theme specific resource dictionaries are located
+                                                //(used if a resource is not found in the page,
+                                                // or application resource dictionaries)
+    ResourceDictionaryLocation.SourceAssembly   //where the generic resource dictionary is located
+                                                //(used if a resource is not found in the page,
+                                                // app, or any theme specific resource dictionaries)
+)]
diff --git a/Plugin.DODriverControl/ConfigModal.xaml b/Plugin.DODriverControl/ConfigModal.xaml
new file mode 100644
index 0000000..c1e4102
--- /dev/null
+++ b/Plugin.DODriverControl/ConfigModal.xaml
@@ -0,0 +1,54 @@
+锘�<pu:WindowX x:Class="Plugin.DODriverControl.ConfigModal"
+            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.DODriverControl"
+            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" 
+            xmlns:pde="clr-namespace:Plugin.DODriverControl.Entity" 
+            xmlns:sys="clr-namespace:System;assembly=mscorlib"
+            mc:Ignorable="d"
+            Title="Form"
+            SizeToContent="Height"
+            Width="600"
+            WindowStartupLocation="CenterOwner"
+            Style="{StaticResource ModalWindow}">
+    <Window.Resources>
+        <ObjectDataProvider x:Key="types" 
+                MethodName="GetValues" 
+                ObjectType="{x:Type sys:Enum}">
+            <ObjectDataProvider.MethodParameters>
+                <x:Type TypeName="pde:ConfigType"/>
+            </ObjectDataProvider.MethodParameters>
+        </ObjectDataProvider>
+    </Window.Resources>
+
+    <Grid Margin="10">
+        <StackPanel x:Name="formPanel">
+            <pu:FormGroup x:Name="FgType" Header="绫诲瀷"
+  Style="{StaticResource InputFormGroupStyle}"
+  dp:FormGroupHelper.IsRequired="True"
+  GroupName="MyFormGroup">
+                <ComboBox x:Name="CbType" Style="{StaticResource FormComboxStyle}" 
+                          ItemsSource="{Binding Source={StaticResource types}}" 
+                          SelectedValuePath="Content"/>
+            </pu:FormGroup>
+
+            <pu:FormGroup x:Name="FgIP" Header="IP"
+              Style="{StaticResource InputFormGroupStyle}"
+              dp:FormGroupHelper.IsRequired="True"
+              GroupName="MyFormGroup">
+                <TextBox x:Name="TbIP" Style="{StaticResource EditorTextBoxStyle}" />
+            </pu:FormGroup>
+
+            <pu:FormGroup x:Name="FgPort" Header="Port" 
+                          Style="{StaticResource InputFormGroupStyle}"
+  dp:FormGroupHelper.IsRequired="True"
+  GroupName="MyFormGroup">
+                <TextBox x:Name="TbPort" Style="{StaticResource EditorTextBoxStyle}" />
+            </pu:FormGroup>
+        </StackPanel>
+    </Grid>
+</pu:WindowX>
diff --git a/Plugin.DODriverControl/ConfigModal.xaml.cs b/Plugin.DODriverControl/ConfigModal.xaml.cs
new file mode 100644
index 0000000..835ca16
--- /dev/null
+++ b/Plugin.DODriverControl/ConfigModal.xaml.cs
@@ -0,0 +1,108 @@
+锘縰sing HMI.UI.DpUtil;
+using HMI.UI.Util;
+using Panuon.WPF.UI;
+using Plugin.DODriverControl.Entity;
+using System.ComponentModel;
+
+namespace Plugin.DODriverControl
+{
+    /// <summary>
+    /// ModalWindow.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class ConfigModal : WindowX
+    {
+        public ConfigModal()
+        {
+            this.DataContext = this;
+            InitializeComponent();
+        }
+
+        public ConfigData config { get; private set; }
+
+        private bool _isProcessing;
+
+        protected override void OnClosing(CancelEventArgs e)
+        {
+            //Press Continue Button
+            if (DialogResult == true)
+            {
+                if (_isProcessing)
+                {
+                    e.Cancel = true;
+                    return;
+                }
+                var okButton = WindowXModalDialog.GetOKButton(this);
+                try
+                {
+                    _isProcessing = true;
+                    ButtonHelper.SetIsPending(okButton, true);
+                    if (!ValidateForm())
+                    {
+                        e.Cancel = true;
+                        return;
+                    }
+                }
+                catch (Exception ex)
+                {
+                    ToastHelper.Error(this, $"Error锛亄ex.Message}");
+                    e.Cancel = true;
+                    return;
+                }
+                finally
+                {
+                    _isProcessing = false;
+                    ButtonHelper.SetIsPending(okButton, false);
+                }
+            }
+            base.OnClosing(e);
+        }
+
+        private bool ValidateName(object value, out string err)
+        {
+            err = null;
+            //string group = TbGroup.Text;
+            //var temp = Datasource.FirstOrDefault(d => d.Group == group && d.Name == value.ToString());
+            //if (temp != null && temp != Data)
+            //{
+            //    err = this.lang("App.MustBeUnique", $"{this.lang("doc.group")},{this.lang("doc.name")}");
+            //    return false;
+            //}
+            return true;
+        }
+
+        private bool ValidateAddress(object value, out string err)
+        {
+            err = null;
+            //string group = TbGroup.Text;
+            //var temp = Datasource.FirstOrDefault(d => d.Address == value.ToString());
+            //if (temp != null && temp != Data)
+            //{
+            //    err = this.lang("App.MustBeUnique", this.lang("doc.Address"));
+            //    return false;
+            //}
+            return true;
+        }
+
+        private bool ValidateForm()
+        {
+            bool flag = FormGroupHelper.ValidateForm(formPanel);
+            if (flag)
+            {
+                config.Type = (ConfigType)CbType.SelectedItem;
+                config.IP = TbIP.Text;
+                config.Port = TbPort.Text;
+            }
+            return flag;
+        }
+
+        public bool? Open(ConfigData config)
+        {
+            this.config = config;
+
+            CbType.SelectedItem = config.Type;
+            TbIP.Text = config.IP;
+            TbPort.Text = config.Port;
+            return this.ShowDialog();
+        }
+    }
+}
diff --git a/Plugin.DODriverControl/DOControl.xaml b/Plugin.DODriverControl/DOControl.xaml
new file mode 100644
index 0000000..413dc8f
--- /dev/null
+++ b/Plugin.DODriverControl/DOControl.xaml
@@ -0,0 +1,22 @@
+锘�<hb:SimpleMaskControl x:Class="Plugin.DODriverControl.DOControl"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:Plugin.DODriverControl"
+             mc:Ignorable="d" 
+             xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
+xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
+xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI"
+xmlns:mdi="clr-namespace:HMI.UI.MDI;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"
+                      Loaded="PluginMaskControl_Loaded"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <Border Style="{StaticResource CardBoarder}" Padding="0" Background="#e6e6e6">
+        <Grid>
+            <mdi:Workspace x:Name="workspace" SettingPath="{Binding WorkspaceSetting}"/>
+        </Grid>
+    </Border>
+</hb:SimpleMaskControl>
diff --git a/Plugin.DODriverControl/DOControl.xaml.cs b/Plugin.DODriverControl/DOControl.xaml.cs
new file mode 100644
index 0000000..78c1241
--- /dev/null
+++ b/Plugin.DODriverControl/DOControl.xaml.cs
@@ -0,0 +1,141 @@
+锘縰sing CommunityToolkit.Mvvm.ComponentModel;
+using HMI.Base;
+using HMI.UI.DpUtil;
+using HMI.UI.MDI;
+using System.ComponentModel;
+using System.IO;
+using System.Windows;
+
+namespace Plugin.DODriverControl
+{
+    /// <summary>
+    /// DOControl.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    [ObservableObject]
+    public partial class DOControl : SimpleMaskControl
+    {
+        public DOControl()
+        {
+            this.DataContext = this;
+            InitializeComponent();
+        }
+
+        [ObservableProperty]
+        private string workspaceSetting;
+
+        public override void InitFinish()
+        {
+            WorkspaceSetting = Path.Combine(BaseFolder, "Workspace.xml");
+            base.InitFinish();
+        }
+
+        private List<SingleControl> controls = new List<SingleControl>();
+
+        private List<CanvasPosition> InitGridSize()
+        {
+            List<CanvasPosition> result = new List<CanvasPosition>();
+            result.Add(new CanvasPosition
+            {
+                Left = 0,
+                Top = 0,
+                Width = 0.1 * ActualWidth,
+                Height = 0.5 * ActualHeight
+            });
+            result.Add(new CanvasPosition
+            {
+                Left = 0.1 * ActualWidth + 2,
+                Top = 0,
+                Width = 0.5 * ActualWidth - 4,
+                Height = ActualHeight,
+            });
+
+            result.Add(new CanvasPosition
+            {
+                Left = 0.6 * ActualWidth + 2,
+                Top = 0,
+                Width = 0.4 * ActualWidth - 2,
+                Height = 0.5 * ActualHeight - 2
+            });
+
+            result.Add(new CanvasPosition
+            {
+                Left = 0,
+                Top = 0.5 * ActualHeight + 2,
+                Width = 0.1 * ActualWidth - 2,
+                Height = 0.5 * ActualHeight - 2
+            });
+
+            result.Add(new CanvasPosition
+            {
+                Left = 0.6 * ActualWidth + 2,
+                Top = 0.5 * ActualHeight + 2,
+                Width = 0.4 * ActualWidth - 2,
+                Height = 0.5 * ActualHeight - 2
+            });
+
+            return result;
+        }
+
+        public override void Active()
+        {
+            foreach (var control in controls)
+            {
+                control.Active();
+            }
+            base.Active();
+        }
+
+        public override void DeActive()
+        {
+            foreach (var control in controls)
+            {
+                control.DeActive();
+            }
+            base.DeActive();
+        }
+
+        public override void OnClose(object sender, CancelEventArgs e)
+        {
+            Dispatcher.Invoke(() =>
+            {
+                foreach (var control in controls)
+                {
+                    control.Close();
+                }
+            });
+            base.DoClose(sender, e);
+        }
+
+        private void LoadSingleControl()
+        {
+            var ps = InitGridSize();
+            var control = new SingleControl("DIO", BaseFolder)
+            {
+                log = log
+            };
+            controls.Add(control);
+            workspace.AddInnerWindow(control.name, new InnerWindow()
+            {
+                Title = control.name,
+                Content = control
+            });
+
+            //var control2 = new SingleControl("DIO2", BaseFolder)
+            //{
+            //    log = log
+            //};
+            //controls.Add(control2);
+            //workspace.AddInnerWindow(control2.name, new InnerWindow()
+            //{
+            //    Title = control2.name,
+            //    Content = control2
+            //});
+        }
+
+        private void PluginMaskControl_Loaded(object sender, RoutedEventArgs e)
+        {
+            LoadSingleControl();
+            Loaded -= PluginMaskControl_Loaded;
+        }
+    }
+}
diff --git a/Plugin.DODriverControl/DataModal.xaml b/Plugin.DODriverControl/DataModal.xaml
new file mode 100644
index 0000000..b7920ca
--- /dev/null
+++ b/Plugin.DODriverControl/DataModal.xaml
@@ -0,0 +1,120 @@
+锘�<pu:WindowX x:Class="Plugin.DODriverControl.DataModal"
+            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+            xmlns:local="clr-namespace:Plugin.DODriverControl"
+            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" 
+            xmlns:pde="clr-namespace:Plugin.DODriverControl.Entity" 
+            xmlns:sys="clr-namespace:System;assembly=mscorlib"
+            mc:Ignorable="d"
+            Title="Form"
+            SizeToContent="Height"
+            Width="600"
+            WindowStartupLocation="CenterOwner"
+            Style="{StaticResource ModalWindow}">
+    <Window.Resources>
+        <ObjectDataProvider x:Key="enumValues" 
+                        MethodName="GetValues" 
+                        ObjectType="{x:Type sys:Enum}">
+            <ObjectDataProvider.MethodParameters>
+                <x:Type TypeName="pde:ControlType"/>
+            </ObjectDataProvider.MethodParameters>
+        </ObjectDataProvider>
+
+        <ObjectDataProvider x:Key="dioType" 
+                MethodName="GetValues" 
+                ObjectType="{x:Type sys:Enum}">
+            <ObjectDataProvider.MethodParameters>
+                <x:Type TypeName="pde:DIOType"/>
+            </ObjectDataProvider.MethodParameters>
+        </ObjectDataProvider>
+    </Window.Resources>
+    <Grid Margin="10">
+        <StackPanel x:Name="formPanel">
+            <!--<pu:FormGroup x:Name="FgGroup" 
+                          Header="{DynamicResource doc.group}"
+                          Style="{StaticResource InputFormGroupStyle}"
+                          dp:FormGroupHelper.IsRequired="True"
+                          GroupName="MyFormGroup">
+                <TextBox x:Name="TbGroup" Style="{StaticResource EditorTextBoxStyle}" />
+            </pu:FormGroup>-->
+
+            <pu:FormGroup x:Name="FgName" 
+              Header="{DynamicResource doc.name}"
+              Style="{StaticResource InputFormGroupStyle}"
+              dp:FormGroupHelper.IsRequired="True"
+              dp:FormGroupHelper.ValidateMethod="ValidateName"
+              GroupName="MyFormGroup">
+                <TextBox x:Name="TbName" Style="{StaticResource EditorTextBoxStyle}" />
+            </pu:FormGroup>
+
+            <pu:FormGroup x:Name="FgAddress" 
+  Header="{DynamicResource doc.address}"
+  Style="{StaticResource InputFormGroupStyle}"
+  dp:FormGroupHelper.IsRequired="True"
+  dp:FormGroupHelper.ValidateMethod="ValidateAddress"
+  GroupName="MyFormGroup">
+                <TextBox x:Name="TbAddress" Style="{StaticResource EditorTextBoxStyle}" />
+            </pu:FormGroup>
+
+            <!--<pu:FormGroup x:Name="FgDio" 
+Header="DI/O"
+Style="{StaticResource InputFormGroupStyle}"
+GroupName="MyFormGroup">
+                <CheckBox x:Name="CxReadOnly"/>
+            </pu:FormGroup>-->
+
+            <pu:FormGroup x:Name="FgDio" 
+Header="DI/O"
+Style="{StaticResource InputFormGroupStyle}"
+GroupName="MyFormGroup">
+                <ComboBox x:Name="CbDio" Style="{StaticResource FormComboxStyle}" 
+          ItemsSource="{Binding Source={StaticResource dioType}}"
+          SelectedValuePath="Content"/>
+            </pu:FormGroup>
+
+            <pu:FormGroup x:Name="FgControlType" 
+Header="{DynamicResource doc.controlType}"
+Style="{StaticResource InputFormGroupStyle}"
+dp:FormGroupHelper.IsRequired="True"
+GroupName="MyFormGroup">
+                <ComboBox x:Name="CbControlType" Style="{StaticResource FormComboxStyle}" 
+                          SelectionChanged="CbControlType_SelectionChanged"
+                          ItemsSource="{Binding Source={StaticResource enumValues}}"
+                          SelectedValuePath="Content">
+                </ComboBox>
+            </pu:FormGroup>
+
+            <pu:FormGroup x:Name="FgPressDownValue" 
+  Header="{DynamicResource doc.pressDownValue}"
+  Style="{StaticResource InputFormGroupStyle}"
+  dp:FormGroupHelper.IsRequired="True"
+  GroupName="MyFormGroup">
+                <TextBox x:Name="TbPressDownValue" Style="{StaticResource EditorTextBoxStyle}" />
+            </pu:FormGroup>
+
+            <pu:FormGroup x:Name="FgPressUpValue" 
+Header="{DynamicResource doc.pressUpValue}"
+Style="{StaticResource InputFormGroupStyle}"
+dp:FormGroupHelper.IsRequired="True"
+GroupName="MyFormGroup">
+                <TextBox x:Name="TbPressUpValue" Style="{StaticResource EditorTextBoxStyle}" />
+            </pu:FormGroup>
+
+            <pu:FormGroup x:Name="FgPressDownText" Header="鎸変笅鏂囨湰" 
+                          dp:FormGroupHelper.IsRequired="True" Style="{StaticResource InputFormGroupStyle}" 
+                          GroupName="MyFormGroup">
+                <TextBox x:Name="TbPressDownText" Style="{StaticResource EditorTextBoxStyle}" />
+            </pu:FormGroup>
+
+            <pu:FormGroup x:Name="FgPressUpText" Header="寮硅捣鏂囨湰" 
+                          dp:FormGroupHelper.IsRequired="True" Style="{StaticResource InputFormGroupStyle}" 
+                          GroupName="MyFormGroup">
+                <TextBox x:Name="TbPressUpText" Style="{StaticResource EditorTextBoxStyle}" />
+            </pu:FormGroup>
+        </StackPanel>
+    </Grid>
+</pu:WindowX>
diff --git a/Plugin.DODriverControl/DataModal.xaml.cs b/Plugin.DODriverControl/DataModal.xaml.cs
new file mode 100644
index 0000000..ba7ac53
--- /dev/null
+++ b/Plugin.DODriverControl/DataModal.xaml.cs
@@ -0,0 +1,177 @@
+锘縰sing HMI.Core.Util;
+using HMI.UI.DpUtil;
+using HMI.UI.Util;
+using Panuon.WPF.UI;
+using Plugin.DODriverControl.Entity;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+
+namespace Plugin.DODriverControl
+{
+    /// <summary>
+    /// ModalWindow.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class DataModal : WindowX
+    {
+        public DataModal()
+        {
+            this.DataContext = this;
+            InitializeComponent();
+        }
+
+        private string filePath;
+        public DIOData Data { get; set; }
+
+        private ObservableCollection<DIOData> Datasource;
+        private bool _isProcessing;
+
+        protected override void OnClosing(CancelEventArgs e)
+        {
+            //Press Continue Button
+            if (DialogResult == true)
+            {
+                if (_isProcessing)
+                {
+                    e.Cancel = true;
+                    return;
+                }
+                var okButton = WindowXModalDialog.GetOKButton(this);
+                try
+                {
+                    _isProcessing = true;
+                    ButtonHelper.SetIsPending(okButton, true);
+                    if (!ValidateForm())
+                    {
+                        e.Cancel = true;
+                        return;
+                    }
+
+                    SaveOrUpdateData();
+                }
+                catch (Exception ex)
+                {
+                    ToastHelper.Error(this, $"Error锛亄ex.Message}");
+                    e.Cancel = true;
+                    return;
+                }
+                finally
+                {
+                    _isProcessing = false;
+                    ButtonHelper.SetIsPending(okButton, false);
+                }
+            }
+            base.OnClosing(e);
+        }
+
+        private bool ValidateName(object value, out string err)
+        {
+            err = null;
+            //string group = TbGroup.Text;
+            //var temp = Datasource.FirstOrDefault(d => d.Group == group && d.Name == value.ToString());
+            //if (temp != null && temp != Data)
+            //{
+            //    err = this.lang("App.MustBeUnique", $"{this.lang("doc.group")},{this.lang("doc.name")}");
+            //    return false;
+            //}
+            return true;
+        }
+
+        private bool ValidateAddress(object value, out string err)
+        {
+            err = null;
+            //string group = TbGroup.Text;
+            //var temp = Datasource.FirstOrDefault(d => d.Address == value.ToString());
+            //if (temp != null && temp != Data)
+            //{
+            //    err = this.lang("App.MustBeUnique", this.lang("doc.Address"));
+            //    return false;
+            //}
+            return true;
+        }
+
+        private bool ValidateForm()
+        {
+            bool flag = FormGroupHelper.ValidateForm(formPanel);
+            if (flag)
+            {
+                Data.Name = TbName.Text;
+                Data.Address = TbAddress.Text;
+                Data.Dio = (DIOType)CbDio.SelectedItem;
+                Data.PressDownValue = TbPressDownValue.Text;
+                Data.PressUpValue = TbPressUpValue.Text;
+                Data.PressDownText = TbPressDownText.Text;
+                Data.PressUpText = TbPressUpText.Text;
+
+                Data.ControlType = (ControlType)CbControlType.SelectedItem;
+                if (Data.ControlType == ControlType.BOOL)
+                {
+                    Data.PressDownValue = true.ToString();
+                    Data.PressUpValue = false.ToString();
+                }
+            }
+            return flag;
+        }
+
+        public bool? Open(DIOData data, ObservableCollection<DIOData> datasource, string filePath)
+        {
+            this.filePath = filePath;
+            Datasource = datasource;
+            Data = data;
+
+            TbName.Text = Data.Name;
+            TbAddress.Text = Data.Address;
+            CbDio.SelectedItem = Data.Dio;
+            TbPressDownValue.Text = Data.PressDownValue;
+            TbPressUpValue.Text = Data.PressUpValue;
+            TbPressDownText.Text = Data.PressDownText;
+            TbPressUpText.Text = Data.PressUpText;
+
+            CbControlType.SelectedItem = Data.ControlType;
+            return this.ShowDialog();
+        }
+
+        private void SaveOrUpdateData()
+        {
+            int index = -1;
+            if (Datasource != null)
+            {
+                index = Datasource.IndexOf(Data);
+            }
+            if (index < 0)
+            {
+                InsertData(Data);
+            }
+            else
+            {
+                UpdateData(index, Data);
+            }
+        }
+
+        public void InsertData(DIOData data)
+        {
+            Datasource.Add(data);
+            XmlHelper.SerializeToXml(filePath, Datasource);
+        }
+
+        public void UpdateData(int index, DIOData data)
+        {
+            var temp = XmlHelper.DeepClone(Datasource);
+            temp[index] = data;
+            XmlHelper.SerializeToXml(filePath, temp);
+        }
+
+        private void CbControlType_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
+        {
+            if ((ControlType)e.AddedItems[0] == ControlType.BOOL)
+            {
+                FgPressDownValue.Visibility = System.Windows.Visibility.Collapsed;
+                FgPressUpValue.Visibility = System.Windows.Visibility.Collapsed;
+            }
+            else
+            {
+                FgPressDownValue.Visibility = System.Windows.Visibility.Visible;
+                FgPressUpValue.Visibility = System.Windows.Visibility.Visible;
+            }
+        }
+    }
+}
diff --git a/Plugin.DODriverControl/Entity/ConfigData.cs b/Plugin.DODriverControl/Entity/ConfigData.cs
new file mode 100644
index 0000000..c13572c
--- /dev/null
+++ b/Plugin.DODriverControl/Entity/ConfigData.cs
@@ -0,0 +1,17 @@
+锘縩amespace Plugin.DODriverControl.Entity
+{
+    public enum ConfigType
+    {
+        ModBusTcp = 0,
+        PCI_7432 = 1,
+        OPCServer = 2
+    }
+
+    public class ConfigData
+    {
+        public ConfigType Type { get; set; }
+        public string IP { get; set; }
+        public string Port { get; set; }
+        public string Name { get; set; }
+    }
+}
diff --git a/Plugin.DODriverControl/Entity/DIOData.cs b/Plugin.DODriverControl/Entity/DIOData.cs
new file mode 100644
index 0000000..caf9739
--- /dev/null
+++ b/Plugin.DODriverControl/Entity/DIOData.cs
@@ -0,0 +1,89 @@
+锘縰sing HMI.Core;
+using HMI.Core.Entity;
+
+namespace Plugin.DODriverControl.Entity
+{
+    public enum ControlType
+    {
+        BOOL = 0,
+        BYTE = 1,
+        INT = 2,
+        UINT = 3,
+        DINT = 4,
+        UDINT = 5,
+        REAL = 6,
+        LREAL = 7,
+        TIME = 8,
+        WORD = 9,
+        STRING = 10,
+        WSTRING = 11,
+        PULSE = 12
+    }
+
+    public enum DIOType
+    {
+        DI = 0,
+        DO = 1
+    }
+    public class DIOData : SelectBase
+    {
+        /// <summary>
+        /// 鎺у埗鐐瑰悕绉�
+        /// </summary>
+        [ExcelColumn("doc.name")]
+        public string Name { get; set; }
+
+        [ExcelColumn("DI/O")]
+        public DIOType Dio { get; set; }
+
+        /// <summary>
+        /// 鍦板潃
+        /// </summary>
+        [ExcelColumn("doc.address")]
+        public string Address { get; set; }
+
+        //[ExcelColumn("doc.readOnly")]
+        //public bool ReadOnly { get; set; }
+
+        /// <summary>
+        /// 鍒嗙粍鍚嶇О
+        /// </summary>
+        //[ExcelColumn("doc.group")]
+        public string Group { get; set; }
+
+        /// <summary>
+        /// 鎸変笅鍊�
+        /// </summary>
+        [ExcelColumn("doc.pressDownValue")]
+        public string PressDownValue { get; set; }
+
+        /// <summary>
+        /// 鎶捣鍊�
+        /// </summary>
+        [ExcelColumn("doc.pressUpValue")]
+        public string PressUpValue { get; set; }
+
+        /// <summary>
+        /// 鎸変笅鍊�
+        /// </summary>
+        [ExcelColumn("doc.pressDownText")]
+        public string PressDownText { get; set; }
+
+        /// <summary>
+        /// 鎶捣鍊�
+        /// </summary>
+        [ExcelColumn("doc.pressUpText")]
+        public string PressUpText { get; set; }
+
+        /// <summary>
+        /// 鏄惁鍚敤
+        /// </summary>
+        public bool Enable { get; set; }
+
+        /// <summary>
+        /// 鎺у埗绫诲瀷
+        /// </summary>
+        [ExcelColumn("doc.controlType")]
+        public ControlType ControlType { get; set; }
+    }
+}
diff --git a/Plugin.DODriverControl/Plugin.DODriverControl.csproj b/Plugin.DODriverControl/Plugin.DODriverControl.csproj
new file mode 100644
index 0000000..cb21245
--- /dev/null
+++ b/Plugin.DODriverControl/Plugin.DODriverControl.csproj
@@ -0,0 +1,28 @@
+锘�<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Library</OutputType>
+    <TargetFramework>net472</TargetFramework>
+    <Nullable>enable</Nullable>
+    <LangVersion>10.0</LangVersion>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <UseWPF>true</UseWPF>
+    <Platforms>AnyCPU;x64</Platforms>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
+    <PackageReference Include="log4net" Version="3.0.4" />
+    <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
+    <PackageReference Include="Panuon.WPF.UI" Version="1.3.0.2" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\hslCommunicationV12.0.3甯︽簮鐮乨emo\HslCommunication\HslCommunication.csproj" />
+    <ProjectReference Include="..\HMI.Base\HMI.Base.csproj" />
+    <ProjectReference Include="..\HMI.Core\HMI.Core.csproj" />
+    <ProjectReference Include="..\HMI.UI\HMI.UI.csproj" />
+    <ProjectReference Include="..\HMI.Util\HMI.Util.csproj" />
+  </ItemGroup>
+
+</Project>
diff --git a/Plugin.DODriverControl/SingleControl.xaml b/Plugin.DODriverControl/SingleControl.xaml
new file mode 100644
index 0000000..e04d895
--- /dev/null
+++ b/Plugin.DODriverControl/SingleControl.xaml
@@ -0,0 +1,365 @@
+锘�<hb:SimpleMaskControl x:Class="Plugin.DODriverControl.SingleControl"
+             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.DODriverControl"
+             mc:Ignorable="d"
+             xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
+            xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
+            xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI"
+            xmlns:mdi="clr-namespace:HMI.UI.MDI;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"
+             Background="#FFF"
+             Loaded="UserControl_Loaded"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <hb:SimpleMaskControl.Resources>
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <pu:StyleDictionary Includes="TabControl" />
+                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonListView.xaml"/>
+            </ResourceDictionary.MergedDictionaries>
+            <Style TargetType="GroupBox">
+                <Setter Property="FontSize" Value="14"/>
+                <Setter Property="HeaderTemplate">
+                    <Setter.Value>
+                        <DataTemplate>
+                            <TextBlock Text="{Binding }" FontSize="12" TextWrapping="Wrap"/>
+                        </DataTemplate>
+                    </Setter.Value>
+                </Setter>
+            </Style>
+        </ResourceDictionary>
+    </hb:SimpleMaskControl.Resources>
+    <Grid dp:GridHelper.Space="8">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition/>
+        </Grid.RowDefinitions>
+
+        <DockPanel>
+            <StackPanel dp:StackPanelHelper.ChildSpace="10" DockPanel.Dock="Right" Orientation="Horizontal">
+                <Button Style="{StaticResource AddBtn}" x:Name="addBtn" Click="Button_Click"/>
+                <Button Style="{StaticResource RfhBtn}" x:Name="rfhBtn" Click="Button_Click"/>
+                <Button Style="{StaticResource ExportBtn}" x:Name="exportBtn" Click="Button_Click"/>
+                <Button Style="{StaticResource ImportBtn}" x:Name="importBtn" Click="Button_Click"/>
+                <!--<Button Style="{StaticResource DelBtn}"/>-->
+            </StackPanel>
+
+            <StackPanel Orientation="Horizontal" VerticalAlignment="Center" dp:StackPanelHelper.ChildSpace="10">
+                <pu:FormGroup FontSize="18" Header="鎺у埗鍣ㄧ姸鎬�" Orientation="Horizontal">
+                    <ui:LightBall Size="20" Value="{Binding ConnectStatus}">
+                        <ui:LightBall.Style>
+                            <Style TargetType="ui:LightBall">
+                                <Style.Triggers>
+                                    <DataTrigger Binding="{Binding ConnectStatus}" Value="True">
+                                        <Setter Property="Content" Value="宸茶繛鎺�"/>
+                                    </DataTrigger>
+                                    <DataTrigger Binding="{Binding ConnectStatus}" Value="False">
+                                        <Setter Property="Content" Value="鏈繛鎺�"/>
+                                    </DataTrigger>
+                                </Style.Triggers>
+                            </Style>
+                        </ui:LightBall.Style>
+                    </ui:LightBall>
+                </pu:FormGroup>
+                <Button Style="{StaticResource ResetBtn}" 
+            x:Name="reConnectBtn" Click="Button_Click" Content="閲嶆柊杩炴帴"
+            IsEnabled="{Binding ConnectStatus,Converter={StaticResource reverseBool}}"/>
+                <Button Style="{StaticResource SettingBtn}" x:Name="configBtn" Click="Button_Click"/>
+            </StackPanel>
+        </DockPanel>
+
+        <TabControl Grid.Row="1"
+pu:TabControlHelper.HeaderPanelAlignment="Stretch"
+pu:TabControlHelper.ItemsBackground="{DynamicResource VerBar}"
+pu:TabControlHelper.ItemsForeground="#FFFFFF"
+pu:TabControlHelper.ItemsFontSize="14"
+pu:TabControlHelper.ItemsHeight="30"
+pu:TabControlHelper.ItemsHorizontalHeaderAlignment="Left"
+pu:TabControlHelper.ItemsHoverBackground="{DynamicResource SecondPrimaryColor}"
+pu:TabControlHelper.ItemsHoverForeground="#FFFFFF"
+pu:TabControlHelper.ItemsPadding="20,0,0,0"
+pu:TabControlHelper.ItemsRibbonLineCornerRadius="4"
+pu:TabControlHelper.ItemsRibbonLineMargin="0,5,0,5"
+pu:TabControlHelper.ItemsRibbonLineThickness="4"
+pu:TabControlHelper.ItemsRibbonLineVisibility="Visible"
+pu:TabControlHelper.ItemsSelectedBackground="#FFF"
+pu:TabControlHelper.ItemsSelectedRibbonLineBrush="#FFFFFF"
+pu:TabControlHelper.ItemsSelectedRibbonLineThickness="2"
+pu:TabControlHelper.ItemsWidth="100" >
+            <TabControl.Template>
+                <ControlTemplate TargetType="{x:Type TabControl}">
+                    <Grid>
+                        <Grid.RowDefinitions>
+                            <RowDefinition Height="Auto"/>
+                            <RowDefinition Height="*"/>
+                        </Grid.RowDefinitions>
+                        <Border BorderThickness="1,1,1,0" BorderBrush="#acacac" Background="{DynamicResource VerBar}" Padding="2,2,10,0">
+                            <TabPanel x:Name="HeaderPanel" Grid.Row="0" Panel.ZIndex="1" IsItemsHost="True"/>
+                        </Border>
+
+                        <Border Grid.Row="1" BorderBrush="{TemplateBinding BorderBrush}" 
+                    BorderThickness="1,0,1,1" Background="{TemplateBinding Background}">
+                            <ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent"/>
+                        </Border>
+                    </Grid>
+                </ControlTemplate>
+            </TabControl.Template>
+
+            <TabItem Header="DIO">
+                <Grid dp:GridHelper.Space="5" Background="#f7f7f7">
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition/>
+                    </Grid.RowDefinitions>
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition/>
+                        <ColumnDefinition/>
+                    </Grid.ColumnDefinitions>
+                    <Label Grid.Row="0" Grid.Column="0">DI</Label>
+                    <Label Grid.Row="0" Grid.Column="1">DO</Label>
+
+                    <ScrollViewer Grid.Row="1" Grid.Column="0"
+                                  Style="{StaticResource DefaultScrollViewer}"
+                                  VerticalScrollBarVisibility="Auto">
+                        <WrapPanel Background="#FFF"  x:Name="diWrap"/>
+                    </ScrollViewer>
+                    <ScrollViewer Grid.Row="1" Grid.Column="1" 
+                                  Style="{StaticResource DefaultScrollViewer}"
+                                  VerticalScrollBarVisibility="Auto">
+                        <WrapPanel Background="#FFF" x:Name="doWrap"/>
+                    </ScrollViewer>
+                </Grid>
+            </TabItem>
+            <TabItem Header="鍒楄〃">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition/>
+                    </Grid.RowDefinitions>
+                    <ListView Grid.Row="1" Style="{StaticResource DefaultListTable}" x:Name="table"
+                      ItemsSource="{Binding Datasource}"
+                      VirtualizingPanel.IsContainerVirtualizable="True"
+VirtualizingPanel.IsVirtualizing="True" 
+VirtualizingPanel.VirtualizationMode="Standard">
+                        <ListView.Resources>
+                            <Style x:Key="RowLink" TargetType="Button" BasedOn="{StaticResource LinkButton}">
+                                <Style.Triggers>
+                                    <DataTrigger Binding="{Binding Path=IsMouseOver,
+                RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ListViewItem,AncestorLevel=1}}" 
+         Value="True">
+                                        <Setter Property="Foreground" Value="#FFF"/>
+                                    </DataTrigger>
+                                    <DataTrigger Binding="{Binding Path=IsSelected,
+       RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ListViewItem,AncestorLevel=1}}" 
+Value="True">
+                                        <Setter Property="Foreground" Value="#FFF"/>
+                                    </DataTrigger>
+                                </Style.Triggers>
+                            </Style>
+
+                            <Style x:Key="DeleteLink" TargetType="Button" BasedOn="{StaticResource DangerLinkButton}">
+                                <Setter Property="Content" Value="{DynamicResource App.Delete}"/>
+                            </Style>
+
+                            <Style x:Key="EditLink" TargetType="Button" BasedOn="{StaticResource RowLink}">
+                                <Setter Property="Content" Value="{DynamicResource App.Edit}"/>
+                            </Style>
+                        </ListView.Resources>
+                        <ListView.View>
+                            <GridView AllowsColumnReorder="False">
+                                <GridViewColumn Width="60" Header="#">
+                                    <GridViewColumn.CellTemplate>
+                                        <DataTemplate>
+                                            <TextBlock Text="{Binding Converter={StaticResource lvRowNumber},
+RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ListViewItem,AncestorLevel=1}}"/>
+                                        </DataTemplate>
+                                    </GridViewColumn.CellTemplate>
+                                </GridViewColumn>
+
+                                <GridViewColumn Width="700">
+                                    <GridViewColumn.Header>
+                                        <TextBlock Text="{DynamicResource doc.name}"/>
+                                    </GridViewColumn.Header>
+                                    <GridViewColumn.CellTemplate>
+                                        <DataTemplate>
+                                            <Border Style="{StaticResource CellBorder}">
+                                                <TextBlock Style="{StaticResource ListTableTextBlock}" 
+                                                           Text="{Binding Name}"/>
+                                            </Border>
+                                        </DataTemplate>
+                                    </GridViewColumn.CellTemplate>
+                                </GridViewColumn>
+                                <GridViewColumn Width="100">
+                                    <GridViewColumn.Header>
+                                        <TextBlock Text="{DynamicResource doc.address}"/>
+                                    </GridViewColumn.Header>
+                                    <GridViewColumn.CellTemplate>
+                                        <DataTemplate>
+                                            <Border Style="{StaticResource CellBorder}">
+                                                <TextBlock Style="{StaticResource ListTableTextBlock}" 
+   Text="{Binding Address}"/>
+                                            </Border>
+                                        </DataTemplate>
+                                    </GridViewColumn.CellTemplate>
+                                </GridViewColumn>
+                                <GridViewColumn Width="120">
+                                    <GridViewColumn.Header>
+                                        <TextBlock Text="DIO"/>
+                                    </GridViewColumn.Header>
+                                    <GridViewColumn.CellTemplate>
+                                        <DataTemplate>
+                                            <Border Style="{StaticResource CellBorder}">
+                                                <TextBlock Style="{StaticResource ListTableTextBlock}" 
+                                                   Text="{Binding Dio}"/>
+                                            </Border>
+                                        </DataTemplate>
+                                    </GridViewColumn.CellTemplate>
+                                </GridViewColumn>
+                                <GridViewColumn Width="200">
+                                    <GridViewColumn.Header>
+                                        <TextBlock Text="{DynamicResource doc.pressDownValue}"/>
+                                    </GridViewColumn.Header>
+                                    <GridViewColumn.CellTemplate>
+                                        <DataTemplate>
+                                            <Border Style="{StaticResource CellBorder}">
+                                                <TextBlock Style="{StaticResource ListTableTextBlock}" 
+                                                   Text="{Binding PressDownValue}"/>
+                                            </Border>
+                                        </DataTemplate>
+                                    </GridViewColumn.CellTemplate>
+                                </GridViewColumn>
+                                <GridViewColumn Width="200">
+                                    <GridViewColumn.Header>
+                                        <TextBlock Text="{DynamicResource doc.pressDownText}"/>
+                                    </GridViewColumn.Header>
+                                    <GridViewColumn.CellTemplate>
+                                        <DataTemplate>
+                                            <Border Style="{StaticResource CellBorder}">
+                                                <TextBlock Style="{StaticResource ListTableTextBlock}" 
+                                                   Text="{Binding PressDownText}"/>
+                                            </Border>
+                                        </DataTemplate>
+                                    </GridViewColumn.CellTemplate>
+                                </GridViewColumn>
+                                <GridViewColumn Width="200">
+                                    <GridViewColumn.Header>
+                                        <TextBlock Text="{DynamicResource doc.pressUpValue}"/>
+                                    </GridViewColumn.Header>
+                                    <GridViewColumn.CellTemplate>
+                                        <DataTemplate>
+                                            <Border Style="{StaticResource CellBorder}">
+                                                <TextBlock Style="{StaticResource ListTableTextBlock}" 
+                                                   Text="{Binding PressUpValue}"/>
+                                            </Border>
+                                        </DataTemplate>
+                                    </GridViewColumn.CellTemplate>
+                                </GridViewColumn>
+                                <GridViewColumn Width="200">
+                                    <GridViewColumn.Header>
+                                        <TextBlock Text="{DynamicResource doc.pressUpText}"/>
+                                    </GridViewColumn.Header>
+                                    <GridViewColumn.CellTemplate>
+                                        <DataTemplate>
+                                            <Border Style="{StaticResource CellBorder}">
+                                                <TextBlock Style="{StaticResource ListTableTextBlock}" 
+                   Text="{Binding PressUpText}"/>
+                                            </Border>
+                                        </DataTemplate>
+                                    </GridViewColumn.CellTemplate>
+                                </GridViewColumn>
+                                <GridViewColumn Width="120">
+                                    <GridViewColumn.Header>
+                                        <TextBlock Text="{DynamicResource App.Opt}"/>
+                                    </GridViewColumn.Header>
+                                    <GridViewColumn.CellTemplate>
+                                        <DataTemplate>
+                                            <StackPanel Orientation="Horizontal" dp:StackPanelHelper.ChildSpace="10">
+                                                <Button x:Name="editBtn" Style="{StaticResource EditLink}" 
+                Click="Button_Click" Tag="{Binding }"/>
+
+                                                <Button x:Name="delBtn" Style="{StaticResource DeleteLink}" 
+                Click="Button_Click" Tag="{Binding }"/>
+                                            </StackPanel>
+                                        </DataTemplate>
+                                    </GridViewColumn.CellTemplate>
+                                </GridViewColumn>
+                            </GridView>
+                        </ListView.View>
+                    </ListView>
+                    <!--<DataGrid Grid.Row="1" Style="{StaticResource DefaultDataGrid}" ItemsSource="{Binding Datasource}">
+                        <DataGrid.Columns>
+                            <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 Name}">
+                                <DataGridTextColumn.Header>
+                                    <TextBlock Text="{DynamicResource doc.name}"/>
+                                </DataGridTextColumn.Header>
+                            </DataGridTextColumn>
+                            <DataGridTextColumn Width="*" Binding="{Binding Address}">
+                                <DataGridTextColumn.Header>
+                                    <TextBlock Text="{DynamicResource doc.address}"/>
+                                </DataGridTextColumn.Header>
+                            </DataGridTextColumn>
+                            <DataGridTextColumn Width="*" Binding="{Binding Dio}">
+                                <DataGridTextColumn.Header>
+                                    <TextBlock Text="DIO"/>
+                                </DataGridTextColumn.Header>
+                            </DataGridTextColumn>
+
+                            <DataGridTextColumn Width="*" Binding="{Binding PressDownValue}">
+                                <DataGridTextColumn.Header>
+                                    <TextBlock Text="{DynamicResource doc.pressDownValue}"/>
+                                </DataGridTextColumn.Header>
+                            </DataGridTextColumn>
+
+                            <DataGridTextColumn Width="*" Binding="{Binding PressDownText}">
+                                <DataGridTextColumn.Header>
+                                    <TextBlock Text="{DynamicResource doc.pressDownText}"/>
+                                </DataGridTextColumn.Header>
+                            </DataGridTextColumn>
+
+                            <DataGridTextColumn Width="*" Binding="{Binding PressUpValue}">
+                                <DataGridTextColumn.Header>
+                                    <TextBlock Text="{DynamicResource doc.pressUpValue}"/>
+                                </DataGridTextColumn.Header>
+                            </DataGridTextColumn>
+
+                            <DataGridTextColumn Width="*" Binding="{Binding PressUpText}">
+                                <DataGridTextColumn.Header>
+                                    <TextBlock Text="{DynamicResource doc.pressUpText}"/>
+                                </DataGridTextColumn.Header>
+                            </DataGridTextColumn>
+
+                            <DataGridTemplateColumn Width="110" IsReadOnly="True" CanUserResize="False">
+                                <DataGridTemplateColumn.Header>
+                                    <TextBlock Text="{DynamicResource App.Opt}"/>
+                                </DataGridTemplateColumn.Header>
+                                <DataGridTemplateColumn.CellTemplate>
+                                    <DataTemplate>
+                                        <StackPanel Orientation="Horizontal" dp:StackPanelHelper.ChildSpace="10">
+                                            <Button x:Name="editBtn" Style="{StaticResource EditLink}" 
+                                                    Click="Button_Click" DataContext="{Binding }"/>
+
+                                            <Button x:Name="delBtn" Style="{StaticResource DeleteLink}" 
+                                                    Click="Button_Click" DataContext="{Binding }"/>
+                                        </StackPanel>
+                                    </DataTemplate>
+                                </DataGridTemplateColumn.CellTemplate>
+                            </DataGridTemplateColumn>
+                        </DataGrid.Columns>
+                    </DataGrid>-->
+                </Grid>
+            </TabItem>
+        </TabControl>
+    </Grid>
+</hb:SimpleMaskControl>
diff --git a/Plugin.DODriverControl/SingleControl.xaml.cs b/Plugin.DODriverControl/SingleControl.xaml.cs
new file mode 100644
index 0000000..0767cb0
--- /dev/null
+++ b/Plugin.DODriverControl/SingleControl.xaml.cs
@@ -0,0 +1,568 @@
+锘縰sing CommunityToolkit.Mvvm.ComponentModel;
+using HMI.Base;
+using HMI.Core.Util;
+using HMI.UI;
+using HMI.UI.Util;
+using HMI.Util;
+using log4net;
+using Microsoft.Win32;
+using Plugin.DODriverControl.Api;
+using Plugin.DODriverControl.Entity;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Timers;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+
+namespace Plugin.DODriverControl
+{
+    /// <summary>
+    /// SingleControl.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    [ObservableObject]
+    public partial class SingleControl : SimpleMaskControl
+    {
+        public SingleControl(string name, string baseFolder)
+        {
+            this.name = name;
+            this.baseFolder = Path.Combine(baseFolder, name);
+            if (!Directory.Exists(this.baseFolder)) Directory.CreateDirectory(this.baseFolder);
+            this.dataFilePath = Path.Combine(this.baseFolder, "data.xml");
+            this.configFilePath = Path.Combine(this.baseFolder, "config.xml");
+            this.DataContext = this;
+
+            refreshTimer = new System.Timers.Timer();
+            refreshTimer.Interval = 200;
+            refreshTimer.AutoReset = false;
+            refreshTimer.Elapsed += RefreshTimer_Elapsed;
+
+            InitializeComponent();
+
+            commonColor = Application.Current.FindResource("VerBar") as Brush;
+            successColor = Application.Current.FindResource("GreenVerBar") as Brush;
+            errorColor = Application.Current.FindResource("DangerVerBar") as Brush;
+        }
+
+        [ObservableProperty]
+        private bool connectStatus = false;
+        [ObservableProperty]
+        private ObservableCollection<DIOData> datasource = new ObservableCollection<DIOData>();
+
+        public ILog log { get; set; }
+        public string name { get; private set; }
+        public string baseFolder { get; private set; }
+
+
+        private IDIOController controller;
+        private string dataFilePath;
+        private string configFilePath;
+        private System.Timers.Timer refreshTimer;
+
+        private ConfigData config;
+
+        private List<FrameworkElement> doControls = new List<FrameworkElement>();
+
+        private Brush successColor;
+        private Brush errorColor;
+        private Brush commonColor;
+
+        private CancellationTokenSource cts = new CancellationTokenSource();
+
+        public void Active()
+        {
+            if (refreshTimer != null) refreshTimer.Enabled = true;
+        }
+
+        public void DeActive()
+        {
+            if (refreshTimer != null) refreshTimer.Enabled = false;
+        }
+
+        public void Close()
+        {
+            cts.Cancel();
+            ConnectStatus = false;
+            if (refreshTimer != null)
+            {
+                refreshTimer.Enabled = false;
+                refreshTimer.Elapsed -= RefreshTimer_Elapsed;
+                refreshTimer = null;
+            }
+            controller?.Dispose();
+            controller = null;
+        }
+
+        private void Button_Click(object sender, RoutedEventArgs e)
+        {
+            var btn = sender as Button;
+            var data = btn?.Tag as DIOData;
+            switch (btn.Name)
+            {
+                case "addBtn":
+                    DoAddData();
+                    break;
+                case "editBtn":
+                    DoEditData(data);
+                    break;
+                case "exportBtn":
+                    DoExportExcel();
+                    break;
+                case "importBtn":
+                    DoImportExcel();
+                    break;
+                case "reConnectBtn":
+                    DoReconnect();
+                    break;
+                case "configBtn":
+                    DoOpenConfig();
+                    break;
+                case "rfhBtn":
+                    Refresh();
+                    break;
+                case "delBtn":
+                    DoDeleteData(data);
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        private void DoAddData()
+        {
+            var win = new DataModal
+            {
+                Owner = Window.GetWindow(this),
+                Title = "鏂板"
+            };
+            if (win.Open(new DIOData(), Datasource, dataFilePath) == true)
+            {
+                ToastHelper.Success("娣诲姞鎴愬姛");
+                Refresh();
+            }
+        }
+
+        private void DoEditData(DIOData data)
+        {
+            var win = new DataModal
+            {
+                Owner = Window.GetWindow(this),
+                Title = "缂栬緫"
+            };
+            if (win.Open(data, Datasource, dataFilePath) == true)
+            {
+                ToastHelper.Success("缂栬緫鎴愬姛");
+                Refresh();
+            }
+        }
+
+        private void DoExportExcel()
+        {
+            try
+            {
+                SaveFileDialog sfd = new SaveFileDialog();
+                sfd.Filter = "Excel|*.xls";
+                if (sfd.ShowDialog() != true)
+                {
+                    return;
+                }
+                string fileName = sfd.FileName;
+                ExcelHelper.ExportExcel(fileName, Datasource);
+                ToastHelper.Success("瀵煎嚭鎴愬姛");
+            }
+            catch (Exception ex)
+            {
+                log.Error("Export Error.", ex);
+                ToastHelper.Error($"瀵煎嚭澶辫触: {ex.Message}");
+            }
+        }
+
+        private void DoImportExcel()
+        {
+            try
+            {
+                OpenFileDialog ofd = new OpenFileDialog();
+                ofd.Filter = "Excel|*.xls";
+                if (ofd.ShowDialog() != true)
+                {
+                    return;
+                }
+                string fileName = ofd.FileName;
+                List<DIOData> datas = ExcelHelper.ReadFromExcel<DIOData>(fileName);
+                //foreach (var data in datas)
+                //{
+                //    Datasource.Add(data);
+                //}
+                Datasource = new ObservableCollection<DIOData>(datas);
+                SaveDatasourceToXml();
+                ToastHelper.Success("瀵煎叆鎴愬姛");
+                RefreshDOControl();
+            }
+            catch (Exception ex)
+            {
+                log.Error("Export Error.", ex);
+                ToastHelper.Error($"瀵煎叆澶辫触: {ex.Message}");
+            }
+        }
+
+        private void DoReconnect()
+        {
+            ConnectStatus = false;
+            ShowMask(() =>
+            {
+                refreshTimer.Enabled = false;
+                controller?.Close();
+                controller = CreateController();
+                var res = controller.Start(out string msg);
+                if (!res)
+                {
+                    throw new Exception($"{msg}");
+                }
+                refreshTimer.Enabled = true;
+                Dispatcher.BeginInvoke(new Action(() =>
+                {
+                    ConnectStatus = true;
+                }));
+            }, (ex) =>
+            {
+                controller = null;
+                log.Error("Reconnect Error.", ex);
+                Dispatcher.BeginInvoke(new Action(() =>
+                {
+                    MsgBoxHelper.Error(this, $"杩炴帴澶辫触锛歿ex.Message}");
+                }));
+            }, "璇风◢鍊�", "姝e湪閲嶈繛...");
+        }
+
+        private void DoOpenConfig()
+        {
+            ConfigModal modal = new ConfigModal
+            {
+                Title = "璁剧疆",
+                Owner = Window.GetWindow(this)
+            };
+            if (modal.Open(config) == true)
+            {
+                config = modal.config;
+                SaveConfigToXml();
+                DoReconnect();
+            }
+        }
+
+        private void DoDeleteData(DIOData data)
+        {
+            if (MsgBoxHelper.WarnMessage(Window.GetWindow(this), "纭畾瑕佸垹闄ら�変腑鐨勬暟鎹悧锛�") == MessageBoxResult.OK)
+            {
+                int index = Datasource.IndexOf(data);
+                if (index < 0) return;
+                Datasource.RemoveAt(index);
+                SaveDatasourceToXml();
+                ToastHelper.Success("鍒犻櫎鎴愬姛");
+                RefreshDOControl();
+            }
+        }
+
+        private void SaveConfigToXml()
+        {
+            XmlHelper.SerializeToXml(configFilePath, config);
+        }
+
+        private void SaveDatasourceToXml()
+        {
+            XmlHelper.SerializeToXml(dataFilePath, Datasource);
+        }
+
+        private void RefreshDOControl()
+        {
+            try
+            {
+                refreshTimer.Enabled = false;
+                diWrap.Children.Clear();
+                doWrap.Children.Clear();
+                doControls.Clear();
+                foreach (var data in Datasource)
+                {
+                    var fg = new GroupBox
+                    {
+                        //Header = $"{data.Name} 鍦板潃:{data.Address}",
+                        Header = string.IsNullOrEmpty(data.Name)?"棰勭暀":data.Name,// $"{data.Name}",
+                        Content = CreateButton(data),
+                        Height = 80,
+                        Width = 180
+                    };
+                    doControls.Add((FrameworkElement)fg.Content);
+                    if (data.Dio == DIOType.DI)
+                    {
+                        diWrap.Children.Add(fg);
+                    }
+                    else
+                    {
+                        doWrap.Children.Add(fg);
+                    }
+                }
+            }
+            finally
+            {
+                refreshTimer.Enabled = true;
+            }
+        }
+
+        private FrameworkElement CreateButton(DIOData data)
+        {
+            if (data.Dio == DIOType.DI)
+            {
+                return new LightBall
+                {
+                    Size = 40,
+                    DataContext = data
+                };
+            }
+            Style btnStyle = this.FindResource("PrimaryButton") as Style;
+            var btn = new Button
+            {
+                DataContext = data,
+                Style = btnStyle,
+                MinWidth = 120,
+                MinHeight = 40
+            };
+            btn.Click += ControlButton_Click;
+            return btn;
+        }
+
+        private void ControlButton_Click(object sender, RoutedEventArgs args)
+        {
+            if (!ConnectStatus)
+            {
+                ToastHelper.Error("璇峰厛杩炴帴鎺у埗鍣�");
+                return;
+            }
+            Button btn = sender as Button;
+            DIOData data = btn.DataContext as DIOData;
+            try
+            {
+                refreshTimer.Enabled = false;
+                var value = btn.Content?.ToString();
+                string val = value == data.PressDownText ? data.PressUpValue : data.PressDownValue;
+                var res = controller.WriteBool(data.Address, bool.Parse(val), out string msg);
+                if (!res) throw new Exception(msg);
+            }
+            catch (Exception ex)
+            {
+                log.Error("ControlButton_Click", ex);
+                ToastHelper.Error($"{data.Name}: {ex.Message}");
+            }
+            finally
+            {
+                refreshTimer.Enabled = true;
+            }
+        }
+
+        private void Refresh()
+        {
+            try
+            {
+                if (!File.Exists(dataFilePath))
+                {
+                    Datasource = new ObservableCollection<DIOData>();
+                }
+                else
+                {
+                    Datasource = XmlHelper.DeserializeFromXml<ObservableCollection<DIOData>>(dataFilePath);
+                }
+            }
+            catch (Exception ex)
+            {
+                log.Error("Load Data Error.", ex);
+                Datasource = new ObservableCollection<DIOData>();
+            }
+            RefreshDOControl();
+        }
+
+        private void UserControl_Loaded(object sender, RoutedEventArgs e)
+        {
+            ShowMask(() =>
+            {
+                try
+                {
+                    if (!File.Exists(configFilePath))
+                    {
+                        config = new ConfigData
+                        {
+                            Type = ConfigType.ModBusTcp,
+                            IP = "127.0.0.1",
+                            Port = "502"
+                        };
+                    }
+                    else
+                    {
+                        config = XmlHelper.DeserializeFromXml<ConfigData>(configFilePath);
+                    }
+                }
+                catch (Exception ex)
+                {
+                    log.Error("Load Config Error.", ex);
+                    config = new ConfigData
+                    {
+                        Type = ConfigType.ModBusTcp,
+                        IP = "127.0.0.1",
+                        Port = "502"
+                    };
+                }
+                var datasource = new ObservableCollection<DIOData>();
+                var connectStatus = false;
+                try
+                {
+                    if (File.Exists(dataFilePath))
+                    {
+                        datasource = XmlHelper.DeserializeFromXml<ObservableCollection<DIOData>>(dataFilePath);
+                    }
+                }
+                catch (Exception ex)
+                {
+                    log.Error("Load Data Error.", ex);
+                }
+
+                try
+                {
+                    controller = CreateController();
+                    var res = controller.Start(out string msg);
+                    if (!res)
+                    {
+                        throw new Exception($"鎺у埗鍣ㄨ繛鎺ュけ璐�: {msg}");
+                    }
+                    connectStatus = true;
+                }
+                catch (Exception ex)
+                {
+                    controller = null;
+                    log.Error("Modbus Connect Error.", ex);
+                    connectStatus = false;
+                }
+
+                Dispatcher.BeginInvoke(new Action(() =>
+                {
+                    ConnectStatus = connectStatus;
+                    Datasource = datasource;
+                    RefreshDOControl();
+                    refreshTimer.Enabled = true;
+                    this.Loaded -= UserControl_Loaded;
+                }));
+            }, null, "璇风◢鍚�", "鍔犺浇涓�...");
+        }
+
+        public IDIOController CreateController()
+        {
+            switch (config.Type)
+            {
+                case ConfigType.ModBusTcp:
+                    return new ModBusTcpController
+                    {
+                        Ip = config.IP,
+                        Port = int.Parse(config.Port),
+                    };
+                case ConfigType.PCI_7432:
+                    return new PCIController
+                    {
+                        Name = "PCI_7432",
+                    };
+                default:
+                    return null;
+            }
+        }
+
+        private void RefreshTimer_Elapsed(object sender, ElapsedEventArgs e)
+        {
+            bool isConnected = ConnectStatus;
+            if (!isConnected) return;
+            try
+            {
+                refreshTimer.Enabled = false;
+                if (cts.IsCancellationRequested)
+                {
+                    isConnected = false;
+                    return;
+                }
+
+                for (int i = doControls.Count - 1; i >= 0; i--)
+                {
+                    var ui = doControls[i];
+                    ui.Dispatcher.Invoke(() =>
+                    {
+                        DIOData data = ui.DataContext as DIOData;
+                        if (data == null) return;
+                        isConnected = GetValueByModbusTcp(data, out string value);
+                        if (!isConnected) return;
+                        switch (data.ControlType)
+                        {
+                            case ControlType.BOOL:
+                                if (data.Dio == DIOType.DI)
+                                {
+                                    ((LightBall)ui).Value = value == data.PressDownValue;
+                                    ((LightBall)ui).Content = value == data.PressDownValue ? data.PressDownText : data.PressUpText;
+                                }
+                                else
+                                {
+                                    var color = value == data.PressDownValue ? successColor : errorColor;
+                                    ((Button)ui).Background = color;
+                                    ((Button)ui).Content = value == data.PressDownValue ? data.PressDownText : data.PressUpText;
+                                }
+                                break;
+                            case ControlType.INT:
+                            case ControlType.WORD:
+                            case ControlType.REAL:
+                            case ControlType.LREAL:
+                            case ControlType.STRING:
+                            case ControlType.WSTRING:
+                                ((Button)ui).Content = value;
+                                break;
+                            default:
+                                break;
+                        }
+                    });
+                }
+            }
+            catch (TaskCanceledException ex)
+            {
+                isConnected = false;
+            }
+            finally
+            {
+                if (isConnected && ConnectStatus) refreshTimer.Enabled = true;
+                else ConnectStatus = false;
+            }
+        }
+
+        private bool GetValueByModbusTcp(DIOData data, out string value)
+        {
+            if (controller == null)
+            {
+                value = null;
+                return false;
+            }
+            switch (data.ControlType)
+            {
+                case ControlType.BOOL:
+                    bool val = false;
+                    bool res = false;
+                    if (data.Dio == DIOType.DI)
+                    {
+                        res = controller.ReadDIBool(data.Address, out val, out string msg);
+                    }
+                    else
+                    {
+                        res = controller.ReadDOBool(data.Address, out val, out string msg);
+                    }
+                    if (res == true)
+                    {
+                        value = val.ToString();
+                        return true;
+                    }
+                    value = null;
+                    return false;
+                default:
+                    value = null;
+                    return true;
+            }
+        }
+    }
+}
diff --git a/Plugin.DataBoard/DataBoard.xaml.cs b/Plugin.DataBoard/DataBoard.xaml.cs
index d820e13..099edc3 100644
--- a/Plugin.DataBoard/DataBoard.xaml.cs
+++ b/Plugin.DataBoard/DataBoard.xaml.cs
@@ -197,7 +197,7 @@
                         }
                         else if (snStatusCache[data.sn] == "Passed" && data.status == "Passed")
                         {
-                            oneTimePass--;
+                            if (oneTimePass > 0) oneTimePass--;
                         }
                     }
                     else
diff --git a/Plugin.JarUI/JarUI.xaml b/Plugin.JarUI/JarUI.xaml
index b0a870c..74be2e1 100644
--- a/Plugin.JarUI/JarUI.xaml
+++ b/Plugin.JarUI/JarUI.xaml
@@ -11,7 +11,7 @@
              xmlns:pu="https://opensource.panuon.com/wpf-ui"
              xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
              Background="#FFF"
-                  Loaded="PluginControl_Loaded"
+             Loaded="PluginControl_Loaded"
              d:DesignHeight="450" d:DesignWidth="800">
     <Border Style="{StaticResource CardBoarder}" Padding="0">
         <Grid dp:GridHelper.Space="5">
diff --git a/Plugin.JarUI/JarUI.xaml.cs b/Plugin.JarUI/JarUI.xaml.cs
index 4313b08..6ee24d7 100644
--- a/Plugin.JarUI/JarUI.xaml.cs
+++ b/Plugin.JarUI/JarUI.xaml.cs
@@ -14,7 +14,6 @@
 using System.IO;
 using System.IO.Ports;
 using System.Linq;
-using System.Reflection;
 using System.Windows;
 using System.Windows.Controls;
 
@@ -592,7 +591,7 @@
 
                 try
                 {
-                    log.Info($"Event: {EventCase}, NumericData: {MethodCode}, StringData: {strData}");
+                    //log.Info($"Event: {EventCase}, NumericData: {MethodCode}, StringData: {strData}");
                 }
                 catch
                 {
diff --git a/Plugin.MaterialOpt/AssemblyInfo.cs b/Plugin.MaterialOpt/AssemblyInfo.cs
new file mode 100644
index 0000000..cc29e7f
--- /dev/null
+++ b/Plugin.MaterialOpt/AssemblyInfo.cs
@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly:ThemeInfo(
+    ResourceDictionaryLocation.None,            //where theme specific resource dictionaries are located
+                                                //(used if a resource is not found in the page,
+                                                // or application resource dictionaries)
+    ResourceDictionaryLocation.SourceAssembly   //where the generic resource dictionary is located
+                                                //(used if a resource is not found in the page,
+                                                // app, or any theme specific resource dictionaries)
+)]
diff --git a/Plugin.MaterialOpt/MaterialOpt.xaml b/Plugin.MaterialOpt/MaterialOpt.xaml
new file mode 100644
index 0000000..4c7106b
--- /dev/null
+++ b/Plugin.MaterialOpt/MaterialOpt.xaml
@@ -0,0 +1,251 @@
+锘�<hb:SimpleControl x:Class="Plugin.MaterialOpt.MaterialOpt"
+             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.MaterialOpt"
+             mc:Ignorable="d"
+             xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
+             xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
+             xmlns:ui="clr-namespace:HMI.UI;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"
+             Loaded="SimpleControl_Loaded"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <hb:SimpleControl.Resources>
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonDataGrid.xaml"/>
+                <pu:StyleDictionary Includes="TabControl" />
+            </ResourceDictionary.MergedDictionaries>
+        </ResourceDictionary>
+    </hb:SimpleControl.Resources>
+    <Grid>
+        <Border Style="{StaticResource CardBoarder}">
+            <Grid dp:GridHelper.Space="10" Cursor="">
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition/>
+                </Grid.RowDefinitions>
+
+                <TextBlock FontSize="{StaticResource TitleFontSize}" 
+VerticalAlignment="Center" HorizontalAlignment="Left" Text="鐗╂枡鎿嶄綔"/>
+                <TabControl Grid.Row="1"
+    pu:TabControlHelper.HeaderPanelAlignment="Stretch"
+    pu:TabControlHelper.ItemsBackground="{DynamicResource VerBar}"
+    pu:TabControlHelper.ItemsForeground="#FFFFFF"
+    pu:TabControlHelper.ItemsFontSize="14"
+    pu:TabControlHelper.ItemsHeight="30"
+    pu:TabControlHelper.ItemsHorizontalHeaderAlignment="Left"
+    pu:TabControlHelper.ItemsHoverBackground="{DynamicResource SecondPrimaryColor}"
+    pu:TabControlHelper.ItemsHoverForeground="#FFFFFF"
+    pu:TabControlHelper.ItemsPadding="20,0,0,0"
+    pu:TabControlHelper.ItemsRibbonLineCornerRadius="4"
+    pu:TabControlHelper.ItemsRibbonLineMargin="0,5,0,5"
+    pu:TabControlHelper.ItemsRibbonLineThickness="4"
+    pu:TabControlHelper.ItemsRibbonLineVisibility="Visible"
+    pu:TabControlHelper.ItemsSelectedBackground="#FFF"
+    pu:TabControlHelper.ItemsSelectedRibbonLineBrush="#FFFFFF"
+    pu:TabControlHelper.ItemsSelectedRibbonLineThickness="2"
+    pu:TabControlHelper.ItemsWidth="150" >
+                    <TabControl.Template>
+                        <ControlTemplate TargetType="{x:Type TabControl}">
+                            <Grid>
+                                <Grid.RowDefinitions>
+                                    <RowDefinition Height="Auto"/>
+                                    <RowDefinition Height="*"/>
+                                </Grid.RowDefinitions>
+                                <Border BorderThickness="1,1,1,0" BorderBrush="#acacac" Background="{DynamicResource VerBar}" Padding="10,5,10,0">
+                                    <TabPanel x:Name="HeaderPanel" Grid.Row="0" Panel.ZIndex="1" IsItemsHost="True"/>
+                                </Border>
+
+                                <Border Grid.Row="1" BorderBrush="{TemplateBinding BorderBrush}" 
+                                        BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
+                                    <ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent"/>
+                                </Border>
+                            </Grid>
+                        </ControlTemplate>
+                    </TabControl.Template>
+                    <TabItem Header="缁戝畾">
+                        <Grid dp:GridHelper.Space="5">
+                            <Grid.RowDefinitions>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition/>
+                            </Grid.RowDefinitions>
+                            <DockPanel Grid.Row="0" Cursor="">
+                                <StackPanel DockPanel.Dock="Right" Style="{StaticResource ButtonGroup}">
+                                    <Button Style="{StaticResource RfhBtn}" Content="缁戝畾"
+                        x:Name="bindBtn" Click="Button_Click"/>
+
+                                    <Button Style="{StaticResource EditBtn}"
+                                        pu:ButtonHelper.Icon="&#xe752;" Content="鏌ヨ" 
+                        x:Name="bindQryBtn" Click="Button_Click"/>
+
+                                </StackPanel>
+
+                                <StackPanel Style="{StaticResource ButtonGroup}" HorizontalAlignment="Left" dp:StackPanelHelper.ChildSpace="10">
+                                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="浜у搧搴忓垪鍙�"/>
+                                    <TextBox Style="{StaticResource EditorTextBoxStyle}" Width="180"
+                         Text="{Binding CurrentProductSN,UpdateSourceTrigger=PropertyChanged}" />
+
+
+                                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="鐗╂枡鏉$爜"/>
+                                    <TextBox Style="{StaticResource EditorTextBoxStyle}" Width="180" 
+                                         Text="{Binding CurrentMaterialSN,UpdateSourceTrigger=PropertyChanged}" />
+
+                                    <!--<TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="鐗╂枡鍚嶇О"/>
+                                <TextBox Style="{StaticResource EditorTextBoxStyle}" Width="180" 
+                                         Text="{Binding CurrentMaterialName,UpdateSourceTrigger=PropertyChanged}" />-->
+
+                                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="宸ョ珯"/>
+                                    <ComboBox Style="{StaticResource FormComboxStyle}" Width="180" 
+          ItemsSource="{Binding WorkstationSource}"
+          SelectedItem="{Binding CurrentWorkstation,Mode=TwoWay}"
+          SelectedValuePath="Content"/>
+                                </StackPanel>
+                            </DockPanel>
+
+                            <DataGrid Grid.Row="1" x:Name="table" 
+                                      SelectionChanged="table_SelectionChanged"
+                                      Style="{StaticResource DefaultDataGrid}">
+                                <DataGrid.Columns>
+                                    <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 ProductSN}">
+                                        <DataGridTextColumn.Header>
+                                            <TextBlock Text="浜у搧搴忓垪鍙�"/>
+                                        </DataGridTextColumn.Header>
+                                    </DataGridTextColumn>
+                                    <DataGridTextColumn Width="*" Binding="{Binding MaterialSN}">
+                                        <DataGridTextColumn.Header>
+                                            <TextBlock Text="鐗╂枡鏉$爜"/>
+                                        </DataGridTextColumn.Header>
+                                    </DataGridTextColumn>
+                                    <!--<DataGridTextColumn Width="*" Binding="{Binding MaterialName}">
+                                    <DataGridTextColumn.Header>
+                                        <TextBlock Text="鐗╂枡鍚嶇О"/>
+                                    </DataGridTextColumn.Header>
+                                </DataGridTextColumn>-->
+                                    <DataGridTextColumn Width="*" Binding="{Binding Workstation}">
+                                        <DataGridTextColumn.Header>
+                                            <TextBlock Text="{DynamicResource cm.workstation}"/>
+                                        </DataGridTextColumn.Header>
+                                    </DataGridTextColumn>
+                                    <DataGridTextColumn Width="*" Binding="{Binding Time}">
+                                        <DataGridTextColumn.Header>
+                                            <TextBlock Text="鏃堕棿"/>
+                                        </DataGridTextColumn.Header>
+                                    </DataGridTextColumn>
+                                    <!--<DataGridTemplateColumn Width="110" IsReadOnly="True" CanUserResize="False">
+                                        <DataGridTemplateColumn.Header>
+                                            <TextBlock Text="{DynamicResource App.Opt}"/>
+                                        </DataGridTemplateColumn.Header>
+                                        <DataGridTemplateColumn.CellTemplate>
+                                            <DataTemplate>
+                                                <StackPanel Orientation="Horizontal">
+                                                    <Button x:Name="rowUnBindBtn" Style="{StaticResource EditLink}"
+                                                            Content="瑙g粦" Click="Button_Click" DataContext="{Binding Row}"/>
+                                                </StackPanel>
+                                            </DataTemplate>
+                                        </DataGridTemplateColumn.CellTemplate>
+                                    </DataGridTemplateColumn>-->
+                                </DataGrid.Columns>
+                            </DataGrid>
+                        </Grid>
+                    </TabItem>
+                    <TabItem Header="瑙g粦">
+                        <Grid dp:GridHelper.Space="5">
+                            <Grid.RowDefinitions>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition/>
+                            </Grid.RowDefinitions>
+                            <DockPanel Grid.Row="0" Cursor="">
+                                <StackPanel DockPanel.Dock="Right" Style="{StaticResource ButtonGroup}">
+                                    <Button Style="{StaticResource EditBtn}" Content="瑙g粦" x:Name="unbindBtn" Click="Button_Click"/>
+
+                                    <Button Style="{StaticResource EditBtn}"
+                                        pu:ButtonHelper.Icon="&#xe752;"
+                                        Content="鏌ヨ" x:Name="unbindQryBtn" Click="Button_Click"/>
+
+                                </StackPanel>
+
+                                <StackPanel Style="{StaticResource ButtonGroup}" HorizontalAlignment="Left" dp:StackPanelHelper.ChildSpace="10">
+                                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="浜у搧搴忓垪鍙�"/>
+                                    <TextBox Style="{StaticResource EditorTextBoxStyle}" Width="180"
+                         Text="{Binding CurrentProductSN,UpdateSourceTrigger=PropertyChanged}" />
+
+
+                                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="鐗╂枡鏉$爜"/>
+                                    <TextBox Style="{StaticResource EditorTextBoxStyle}" Width="180" 
+                                         Text="{Binding CurrentMaterialSN,UpdateSourceTrigger=PropertyChanged}" />
+
+                                    <!--<TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="鐗╂枡鍚嶇О"/>
+                                <TextBox Style="{StaticResource EditorTextBoxStyle}" Width="180" Text="{Binding CurrentMaterialName}" />-->
+                                </StackPanel>
+                            </DockPanel>
+
+                            <DataGrid Grid.Row="1" x:Name="unbindTable" Style="{StaticResource DefaultDataGrid}">
+                                <DataGrid.Columns>
+                                    <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 ProductSN}">
+                                        <DataGridTextColumn.Header>
+                                            <TextBlock Text="浜у搧搴忓垪鍙�"/>
+                                        </DataGridTextColumn.Header>
+                                    </DataGridTextColumn>
+                                    <DataGridTextColumn Width="*" Binding="{Binding MaterialSN}">
+                                        <DataGridTextColumn.Header>
+                                            <TextBlock Text="鐗╂枡鏉$爜"/>
+                                        </DataGridTextColumn.Header>
+                                    </DataGridTextColumn>
+                                    <!--<DataGridTextColumn Width="*" Binding="{Binding MaterialName}">
+                                    <DataGridTextColumn.Header>
+                                        <TextBlock Text="鐗╂枡鍚嶇О"/>
+                                    </DataGridTextColumn.Header>
+                                </DataGridTextColumn>
+                                    <DataGridTextColumn Width="*" Binding="{Binding Workstation}">
+                                        <DataGridTextColumn.Header>
+                                            <TextBlock Text="{DynamicResource cm.workstation}"/>
+                                        </DataGridTextColumn.Header>
+                                    </DataGridTextColumn>-->
+                                    <DataGridTextColumn Width="*" Binding="{Binding Time}">
+                                        <DataGridTextColumn.Header>
+                                            <TextBlock Text="鏃堕棿"/>
+                                        </DataGridTextColumn.Header>
+                                    </DataGridTextColumn>
+                                    <!--<DataGridTemplateColumn Width="110" IsReadOnly="True" CanUserResize="False">
+                                        <DataGridTemplateColumn.Header>
+                                            <TextBlock Text="{DynamicResource App.Opt}"/>
+                                        </DataGridTemplateColumn.Header>
+                                        <DataGridTemplateColumn.CellTemplate>
+                                            <DataTemplate>
+                                                <StackPanel Orientation="Horizontal">
+                                                    <Button x:Name="rowReBindBtn" Style="{StaticResource EditLink}"
+                                                            Content="閲嶆柊缁戝畾" Click="Button_Click" DataContext="{Binding }"/>
+                                                </StackPanel>
+                                            </DataTemplate>
+                                        </DataGridTemplateColumn.CellTemplate>
+                                    </DataGridTemplateColumn>-->
+                                </DataGrid.Columns>
+                            </DataGrid>
+                        </Grid>
+                    </TabItem>
+                </TabControl>
+            </Grid>
+        </Border>
+        <ui:MaskControl x:Name="mask"/>
+    </Grid>
+</hb:SimpleControl>
diff --git a/Plugin.MaterialOpt/MaterialOpt.xaml.cs b/Plugin.MaterialOpt/MaterialOpt.xaml.cs
new file mode 100644
index 0000000..cc4c5f0
--- /dev/null
+++ b/Plugin.MaterialOpt/MaterialOpt.xaml.cs
@@ -0,0 +1,429 @@
+锘縰sing CommunityToolkit.Mvvm.ComponentModel;
+using HMI.Base;
+using HMI.Core.Entity;
+using HMI.Core.Manager;
+using HMI.Core.Util;
+using HMI.UI.Util;
+using LSMSInterface;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Web.SessionState;
+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.Navigation;
+using System.Data;
+using System.Windows.Media.Media3D;
+
+namespace Plugin.MaterialOpt
+{
+    /// <summary>
+    /// MaterialOpt.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    [ObservableObject]
+    public partial class MaterialOpt : SimpleControl
+    {
+        public MaterialOpt()
+        {
+            DataContext = this;
+            api = new LSMSApi();
+            InitializeComponent();
+        }
+
+        [ObservableProperty]
+        private string currentProductSN;
+        [ObservableProperty]
+        private string currentMaterialSN;
+        [ObservableProperty]
+        private string currentMaterialName;
+        [ObservableProperty]
+        private string currentWorkstation;
+        [ObservableProperty]
+        private ObservableCollection<string> workstationSource;
+
+        private string lineName;
+        private string mesIp;
+        private int mesPort;
+        private LSMSApi api;
+        private bool isMesConnected = false;
+        private SqliteHelper sqliteHelper;
+
+        public override void InitFinish()
+        {
+            base.InitFinish();
+            ParameterData parameter;
+            try
+            {
+                parameter = ParameterManager.GetCommonParameterByName("MesIp");
+                mesIp = parameter.Value;
+            }
+            catch
+            {
+                mesIp = "127.0.0.1";
+            }
+            try
+            {
+                parameter = ParameterManager.GetCommonParameterByName("MesPort");
+                mesPort = int.Parse(parameter.Value);
+            }
+            catch
+            {
+                mesPort = 9909;
+            }
+
+            try
+            {
+                parameter = ParameterManager.GetCommonParameterByName("LineName");
+                lineName = parameter.Value;
+            }
+            catch
+            {
+                lineName = string.Empty;
+            }
+
+            //try
+            //{
+            //    parameter = ParameterManager.GetCommonParameterByName("ChangeModelByWorkOrder");
+            //    var res = bool.Parse(parameter.Value);
+            //    if (res) WorkOrderVisiable = Visibility.Visible;
+            //}
+            //catch
+            //{
+            //    WorkOrderVisiable = Visibility.Collapsed;
+            //}
+
+            try
+            {
+                var dbPath = Path.Combine(BaseFolder, "MaterialOpt.db");
+                if (!File.Exists(dbPath))
+                {
+                    SqliteHelper.CreateDatabase(dbPath);
+                }
+                sqliteHelper = new SqliteHelper(dbPath);
+                sqliteHelper.ExecuteSql(@"CREATE TABLE IF NOT EXISTS bind_record (
+                    id INTEGER PRIMARY KEY AUTOINCREMENT,
+                    ProductSN TEXT NOT NULL,
+                    MaterialSN TEXT NOT NULL,
+                    MaterialName TEXT ,
+                    Workstation TEXT ,
+                    Time TIME);");
+                sqliteHelper.ExecuteSql(@"CREATE TABLE IF NOT EXISTS unbind_record (
+                    id INTEGER PRIMARY KEY AUTOINCREMENT,
+                    ProductSN TEXT NOT NULL,
+                    MaterialSN TEXT NOT NULL,
+                    MaterialName TEXT ,
+                    Workstation TEXT ,
+                    Time TIME);");
+            }
+            catch (Exception ex)
+            {
+                log.Error("Init MaterialOpt DB Failed.", ex);
+            }
+        }
+
+        public override void Active()
+        {
+            base.Active();
+            //if (!isMesConnected)
+            //{
+            //    ConnectMes();
+            //    RefreshWorkstationSource();
+            //}
+        }
+
+        public void ConnectMes()
+        {
+            string msg = string.Empty;
+            try
+            {
+                if (api.Connect(this.mesIp, this.mesPort, ref msg))
+                {
+                    isMesConnected = true;
+                }
+                else
+                {
+                    throw new Exception(msg);
+                }
+            }
+            catch (Exception ex)
+            {
+                isMesConnected = false;
+                log.Error("ConnectMes Failed.", ex);
+                MsgBoxHelper.Error(Window.GetWindow(this), this.lang("cm.mesConnectFail", ex.Message));
+            }
+        }
+        private bool PingMes(out string errMsg)
+        {
+            errMsg = string.Empty;
+            if (!isMesConnected)
+            {
+                errMsg = "MES鏈繛鎺ワ紝璇峰厛杩炴帴MES";
+                return false;
+            }
+            try
+            {
+                if (!api.IsConnect(ref errMsg))
+                {
+                    isMesConnected = false;
+                    return false;
+                }
+                return true;
+            }
+            catch (Exception ex)
+            {
+                errMsg = ex.Message;
+                isMesConnected = false;
+                return false;
+            }
+        }
+
+        private void RefreshWorkstationSource()
+        {
+            string errMsg = string.Empty;
+            try
+            {
+                if (api.Connect(mesIp, mesPort, ref errMsg))
+                {
+                    string lineStr = api.GetAllStationByCurrentLine(lineName, out errMsg);
+                    JObject res = (JObject)JsonConvert.DeserializeObject(lineStr);
+                    string stations = res["WorkStations"]?.ToString();
+                    if (string.IsNullOrEmpty(stations))
+                    {
+                        WorkstationSource = new ObservableCollection<string>();
+                    }
+                    else
+                    {
+                        WorkstationSource = new ObservableCollection<string>(stations.Split(','));
+                    }
+                }
+                else
+                {
+                    MsgBoxHelper.ErrorMessage(this, $"杩炴帴鏈嶅姟鍣ㄥけ璐�: {errMsg}");
+                }
+            }
+            catch (Exception ex)
+            {
+                MsgBoxHelper.ErrorMessage(this, $"缁戝畾澶辫触: {errMsg}");
+            }
+            finally
+            {
+                api.DisConnect(ref errMsg);
+            }
+        }
+
+        private void Button_Click(object sender, RoutedEventArgs e)
+        {
+            var btn = sender as Button;
+            switch (btn.Name)
+            {
+                case "bindBtn":
+                    BindMaterial();
+                    break;
+                case "bindQryBtn":
+                    mask.ShowMask(this, RefreshBindLog, null, "璇风◢鍚�", "姝e湪鏌ヨ...");
+                    break;
+                case "unbindBtn":
+                    UnBindMaterial();
+                    break;
+                case "unbindQryBtn":
+                    mask.ShowMask(this, RefreshUnBindLog, null, "璇风◢鍚�", "姝e湪鏌ヨ...");
+                    break;
+                //case "rowUnBindBtn":
+                //    UnBindMaterial(data.ItemArray[1].ToString(), data.ItemArray[2].ToString());
+                //    break;
+                //case "rowReBindBtn":
+                //    BindMaterial();
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        private void BindMaterial()
+        {
+            if (string.IsNullOrEmpty(CurrentProductSN))
+            {
+                ToastHelper.Warning("璇疯緭鍏ヤ骇鍝佸簭鍒楀彿");
+                return;
+            }
+            if (string.IsNullOrEmpty(CurrentMaterialSN))
+            {
+                ToastHelper.Warning("璇疯緭鍏ョ墿鏂欐潯鐮�");
+                return;
+            }
+            //if (string.IsNullOrEmpty(CurrentMaterialName))
+            //{
+            //    ToastHelper.Warning("璇疯緭鍏ョ墿鏂欏悕绉�");
+            //    return;
+            //}
+            if (string.IsNullOrEmpty(CurrentWorkstation))
+            {
+                ToastHelper.Warning("璇烽�夋嫨宸ョ珯");
+                return;
+            }
+
+            if (MsgBoxHelper.WarnMessage(this, "纭畾缁戝畾璇ョ墿鏂欏悧?") != MessageBoxResult.OK)
+            {
+                return;
+            }
+            mask.ShowMask(this, () =>
+            {
+                string errMsg = string.Empty;
+                try
+                {
+                    if (!api.Connect(mesIp, mesPort, ref errMsg))
+                    {
+                        throw new Exception($"杩炴帴鏈嶅姟鍣ㄥけ璐�: {errMsg}");
+                    }
+                    string text2 = api.CheckBoolMaterial_2163(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.FFF"),
+                            CurrentProductSN, CurrentWorkstation, CurrentMaterialName, CurrentMaterialSN, out errMsg);
+                    if (!string.IsNullOrEmpty(errMsg))
+                    {
+                        throw new Exception(errMsg);
+                    }
+                    sqliteHelper.ExecuteSql($@"INSERT INTO bind_record (ProductSN, MaterialSN, Workstation, Time)
+Values ('{CurrentProductSN}', '{CurrentMaterialSN}', '{CurrentWorkstation}','{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.FFF")}')");
+                    ToastHelper.Success("缁戝畾鎴愬姛");
+                    RefreshBindLog();
+                }
+                finally
+                {
+                    api.DisConnect(ref errMsg);
+                }
+            },
+            (ex) =>
+            {
+                Dispatcher.BeginInvoke(new Action(() =>
+                {
+                    MsgBoxHelper.Error(this, $"缁戝畾澶辫触: {ex.Message}");
+                }));
+            }, "璇风◢鍚�", "姝e湪缁戝畾...");
+        }
+
+        private void UnBindMaterial(string productSN = null, string materialSN = null)
+        {
+            if (productSN == null)
+            {
+                productSN = CurrentProductSN;
+            }
+            if (materialSN == null)
+            {
+                materialSN = CurrentMaterialSN;
+            }
+
+            if (string.IsNullOrEmpty(productSN))
+            {
+                ToastHelper.Warning("璇疯緭鍏ヤ骇鍝佸簭鍒楀彿");
+                return;
+            }
+            if (string.IsNullOrEmpty(materialSN))
+            {
+                ToastHelper.Warning("璇疯緭鍏ョ墿鏂欐潯鐮�");
+                return;
+            }
+            if (MsgBoxHelper.WarnMessage(this, "纭畾瑙g粦璇ョ墿鏂欏悧?") != MessageBoxResult.OK)
+            {
+                return;
+            }
+
+            mask.ShowMask(this, () =>
+            {
+                string errMsg = string.Empty;
+                try
+                {
+                    if (!api.Connect(mesIp, mesPort, ref errMsg))
+                    {
+                        throw new Exception($"杩炴帴鏈嶅姟鍣ㄥけ璐�: {errMsg}");
+
+                    }
+                    string text2 = api.UnBookMaterial(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.FFF"),
+                           productSN, materialSN, out errMsg);
+                    if (!string.IsNullOrEmpty(errMsg))
+                    {
+                        throw new Exception(errMsg);
+                    }
+                    sqliteHelper.ExecuteSql($@"INSERT INTO unbind_record (ProductSN, MaterialSN, Workstation, Time)
+Values ('{productSN}', '{materialSN}', '', '{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.FFF")}')");
+                    ToastHelper.Success("瑙g粦鎴愬姛");
+                    RefreshUnBindLog();
+                }
+                finally
+                {
+                    api.DisConnect(ref errMsg);
+                }
+            },
+            (ex) =>
+            {
+                Dispatcher.BeginInvoke(new Action(() =>
+                {
+                    MsgBoxHelper.Error(this, $"瑙g粦澶辫触: {ex.Message}");
+                }));
+            }, "璇风◢鍚�", "姝e湪瑙g粦...");
+        }
+
+        private void RefreshBindLog()
+        {
+            var dt = sqliteHelper.GetDataTable("SELECT * FROM bind_record ORDER BY Time DESC LIMIT 100");
+            this.Dispatcher.BeginInvoke(new Action(() =>
+            {
+                table.ItemsSource = dt.DefaultView;
+            }));
+        }
+
+        private void RefreshUnBindLog()
+        {
+            var dt = sqliteHelper.GetDataTable("SELECT * FROM unbind_record ORDER BY Time DESC LIMIT 100");
+            this.Dispatcher.BeginInvoke(new Action(() =>
+            {
+                unbindTable.ItemsSource = dt.DefaultView;
+            }));
+        }
+
+        private void SimpleControl_Loaded(object sender, RoutedEventArgs e)
+        {
+            RefreshWorkstationSource();
+            RefreshBindLog();
+            RefreshUnBindLog();
+            //try
+            //{
+            //    ConnectMes();
+            //    RefreshWorkstationSource();
+            //}
+            //catch (Exception ex)
+            //{
+            //    log.Error("MaterialOpt Load Failed.", ex);
+            //}
+        }
+
+        private void table_SelectionChanged(object sender, SelectionChangedEventArgs e)
+        {
+            if (e.AddedItems == null || e.AddedItems.Count <= 0)
+            {
+                return;
+            }
+            var data = (e.AddedItems[0] as DataRowView).Row;
+            if (sender == table)
+            {
+                CurrentProductSN = data.ItemArray[1].ToString();
+                CurrentMaterialSN = data.ItemArray[2].ToString();
+                CurrentWorkstation = data.ItemArray[4].ToString();
+            }
+            else if (sender == unbindTable)
+            {
+                CurrentProductSN = data.ItemArray[1].ToString();
+                CurrentMaterialSN = data.ItemArray[2].ToString();
+            }
+        }
+    }
+}
diff --git a/Plugin.MaterialOpt/Plugin.MaterialOpt.csproj b/Plugin.MaterialOpt/Plugin.MaterialOpt.csproj
new file mode 100644
index 0000000..c85ac8f
--- /dev/null
+++ b/Plugin.MaterialOpt/Plugin.MaterialOpt.csproj
@@ -0,0 +1,27 @@
+锘�<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Library</OutputType>
+    <TargetFramework>net472</TargetFramework>
+    <Nullable>enable</Nullable>
+    <LangVersion>10.0</LangVersion>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <UseWPF>true</UseWPF>
+    <Platforms>AnyCPU;x64</Platforms>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
+    <PackageReference Include="log4net" Version="3.0.4" />
+    <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
+    <PackageReference Include="Panuon.WPF.UI" Version="1.3.0.2" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\MesApi\lmes.csproj" />
+    <ProjectReference Include="..\HMI.Base\HMI.Base.csproj" />
+    <ProjectReference Include="..\HMI.Core\HMI.Core.csproj" />
+    <ProjectReference Include="..\HMI.UI\HMI.UI.csproj" />
+  </ItemGroup>
+
+</Project>
diff --git a/Plugin.ModelManage/AssemblyInfo.cs b/Plugin.ModelManage/AssemblyInfo.cs
new file mode 100644
index 0000000..cc29e7f
--- /dev/null
+++ b/Plugin.ModelManage/AssemblyInfo.cs
@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly:ThemeInfo(
+    ResourceDictionaryLocation.None,            //where theme specific resource dictionaries are located
+                                                //(used if a resource is not found in the page,
+                                                // or application resource dictionaries)
+    ResourceDictionaryLocation.SourceAssembly   //where the generic resource dictionary is located
+                                                //(used if a resource is not found in the page,
+                                                // app, or any theme specific resource dictionaries)
+)]
diff --git a/Plugin.ModelManage/DataModal.xaml b/Plugin.ModelManage/DataModal.xaml
new file mode 100644
index 0000000..a90cd95
--- /dev/null
+++ b/Plugin.ModelManage/DataModal.xaml
@@ -0,0 +1,77 @@
+锘�<pu:WindowX x:Class="Plugin.ModelManage.DataModal"
+            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+            xmlns:local="clr-namespace:Plugin.ModelManage"
+            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" 
+            xmlns:pde="clr-namespace:Plugin.ModelManage.Entity" 
+            xmlns:sys="clr-namespace:System;assembly=mscorlib"
+            mc:Ignorable="d"
+            Title="Form"
+            SizeToContent="Height"
+            Width="600"
+            WindowStartupLocation="CenterOwner"
+            Style="{StaticResource ModalWindow}">
+    <Window.Resources>
+        <!--<ObjectDataProvider x:Key="enumValues" 
+                        MethodName="GetValues" 
+                        ObjectType="{x:Type sys:Enum}">
+            <ObjectDataProvider.MethodParameters>
+                <x:Type TypeName="pde:ControlType"/>
+            </ObjectDataProvider.MethodParameters>
+        </ObjectDataProvider>
+
+        <ObjectDataProvider x:Key="dioType" 
+                MethodName="GetValues" 
+                ObjectType="{x:Type sys:Enum}">
+            <ObjectDataProvider.MethodParameters>
+                <x:Type TypeName="pde:DIOType"/>
+            </ObjectDataProvider.MethodParameters>
+        </ObjectDataProvider>-->
+    </Window.Resources>
+    <Grid Margin="10">
+        <StackPanel x:Name="formPanel">
+            <pu:FormGroup x:Name="FgModel" 
+              Header="鍨嬪彿"
+              Style="{StaticResource InputFormGroupStyle}"
+              dp:FormGroupHelper.IsRequired="True"
+              dp:FormGroupHelper.ValidateMethod="ValidateModel"
+              GroupName="MyFormGroup">
+                <TextBox x:Name="TbModel" Style="{StaticResource EditorTextBoxStyle}" />
+            </pu:FormGroup>
+
+            <pu:FormGroup x:Name="FgCodeLength" Header="鏉$爜闀垮害"
+  Style="{StaticResource InputFormGroupStyle}"
+  dp:FormGroupHelper.IsRequired="True"
+  GroupName="MyFormGroup">
+                <pu:NumberInput x:Name="NiCodeLength" Style="{StaticResource FormNumberInput}" />
+            </pu:FormGroup>
+
+            <pu:FormGroup x:Name="FgStartIndex" Header="璧峰涓嬫爣"
+Style="{StaticResource InputFormGroupStyle}"
+dp:FormGroupHelper.IsRequired="True"
+GroupName="MyFormGroup">
+                <pu:NumberInput x:Name="NiStartIndex" Style="{StaticResource FormNumberInput}" />
+            </pu:FormGroup>
+
+            <!--<pu:FormGroup x:Name="FgLength" Header="鎴彇闀垮害"
+Style="{StaticResource InputFormGroupStyle}"
+dp:FormGroupHelper.IsRequired="True"
+GroupName="MyFormGroup">
+                <pu:NumberInput x:Name="NiLength" Style="{StaticResource FormNumberInput}" />
+            </pu:FormGroup>-->
+
+            <pu:FormGroup x:Name="FgFixcode" 
+Header="鍥哄畾鐮�"
+Style="{StaticResource InputFormGroupStyle}"
+dp:FormGroupHelper.IsRequired="True"
+              dp:FormGroupHelper.ValidateMethod="ValidateFixcode"
+GroupName="MyFormGroup">
+                <TextBox x:Name="TbFixcode" Style="{StaticResource EditorTextBoxStyle}" />
+            </pu:FormGroup>
+        </StackPanel>
+    </Grid>
+</pu:WindowX>
diff --git a/Plugin.ModelManage/DataModal.xaml.cs b/Plugin.ModelManage/DataModal.xaml.cs
new file mode 100644
index 0000000..3bf2936
--- /dev/null
+++ b/Plugin.ModelManage/DataModal.xaml.cs
@@ -0,0 +1,142 @@
+锘縰sing HMI.Core.Util;
+using HMI.UI.DpUtil;
+using HMI.UI.Util;
+using Panuon.WPF.UI;
+using Plugin.ModelManage.Entity;
+using System.ComponentModel;
+
+namespace Plugin.ModelManage
+{
+    /// <summary>
+    /// ModalWindow.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class DataModal : WindowX
+    {
+        public DataModal()
+        {
+            this.DataContext = this;
+            InitializeComponent();
+        }
+        public ModelData Data { get; set; }
+
+        private SqliteHelper sqliteHelper;
+        private bool _isProcessing;
+
+        protected override void OnClosing(CancelEventArgs e)
+        {
+            //Press Continue Button
+            if (DialogResult == true)
+            {
+                if (_isProcessing)
+                {
+                    e.Cancel = true;
+                    return;
+                }
+                var okButton = WindowXModalDialog.GetOKButton(this);
+                try
+                {
+                    _isProcessing = true;
+                    ButtonHelper.SetIsPending(okButton, true);
+                    if (!ValidateForm())
+                    {
+                        e.Cancel = true;
+                        return;
+                    }
+
+                    SaveOrUpdateData();
+                }
+                catch (Exception ex)
+                {
+                    ToastHelper.Error(this, $"Error锛亄ex.Message}");
+                    e.Cancel = true;
+                    return;
+                }
+                finally
+                {
+                    _isProcessing = false;
+                    ButtonHelper.SetIsPending(okButton, false);
+                }
+            }
+            base.OnClosing(e);
+        }
+
+        private bool ValidateModel(object value, out string err)
+        {
+            err = null;
+            string segment = string.IsNullOrEmpty(Data.Id) ? ";" : $"AND Id != {Data.Id};";
+            var sql = $"SELECT 1 FROM ModelData WHERE Model = '{value}' {segment}";
+            if (sqliteHelper.HasData(sql))
+            {
+                err = "鍨嬪彿涓嶈兘閲嶅";
+                return false;
+            }
+            return true;
+        }
+
+        private bool ValidateFixcode(object value, out string err)
+        {
+            err = null;
+            string segment = string.IsNullOrEmpty(Data.Id) ? ";" : $"AND Id != {Data.Id};";
+            var sql = $"SELECT 1 FROM ModelData WHERE Fixcode = '{value}' {segment}";
+            if (sqliteHelper.HasData(sql))
+            {
+                err = "鍥哄畾鐮佷笉鑳介噸澶�";
+                return false;
+            }
+            return true;
+        }
+
+        private bool ValidateForm()
+        {
+            bool flag = FormGroupHelper.ValidateForm(formPanel);
+            if (flag)
+            {
+                Data.Model = TbModel.Text;
+                Data.CodeLength = (int)NiCodeLength.Value;
+                Data.StartIndex = (int)NiStartIndex.Value;
+                //Data.Length = (int)NiLength.Value;
+                Data.Fixcode = TbFixcode.Text;
+            }
+            return flag;
+        }
+
+        public bool? Open(ModelData data, SqliteHelper sqliteHelper)
+        {
+            Data = data;
+            this.sqliteHelper = sqliteHelper;
+
+            TbModel.Text = Data.Model;
+            NiCodeLength.Value = Data.CodeLength;
+            NiStartIndex.Value = Data.StartIndex;
+            //NiLength.Value = Data.Length;
+            TbFixcode.Text = Data.Fixcode;
+            return this.ShowDialog();
+        }
+
+        private void SaveOrUpdateData()
+        {
+            if (string.IsNullOrEmpty(Data.Id))
+            {
+                InsertData(Data);
+            }
+            else
+            {
+                UpdateData(Data);
+            }
+        }
+
+        public void InsertData(ModelData data)
+        {
+            var sql = $@"INSERT INTO ModelData (Model, CodeLength, StartIndex, Length, Fixcode) 
+VALUES ('{data.Model}', {data.CodeLength}, {data.StartIndex}, {data.Length}, '{data.Fixcode}');";
+            sqliteHelper.ExecuteSql(sql);
+        }
+
+        public void UpdateData(ModelData data)
+        {
+            var sql = $@"UPDATE ModelData SET Model='{data.Model}', CodeLength={data.CodeLength}, StartIndex={data.StartIndex},
+Length={data.Length}, Fixcode='{data.Fixcode}' WHERE Id={data.Id}";
+            sqliteHelper.ExecuteSql(sql);
+        }
+    }
+}
diff --git a/Plugin.ModelManage/Entity/ModelData.cs b/Plugin.ModelManage/Entity/ModelData.cs
new file mode 100644
index 0000000..85d565b
--- /dev/null
+++ b/Plugin.ModelManage/Entity/ModelData.cs
@@ -0,0 +1,19 @@
+锘縰sing HMI.Core;
+using HMI.Core.Entity;
+
+namespace Plugin.ModelManage.Entity
+{
+    public class ModelData : SelectBase
+    {
+        public string Id { get; set; }
+        [ExcelColumn("鍨嬪彿")]
+        public string Model { get; set; }
+        [ExcelColumn("鏉$爜闀垮害")]
+        public int CodeLength { get; set; }
+        [ExcelColumn("璧峰涓嬫爣")]
+        public int StartIndex { get; set; }
+        public int Length { get; set; }
+        [ExcelColumn("鍥哄畾鐮�")]
+        public string Fixcode { get; set; }
+    }
+}
diff --git a/Plugin.ModelManage/ModelManage.xaml b/Plugin.ModelManage/ModelManage.xaml
new file mode 100644
index 0000000..61f34bc
--- /dev/null
+++ b/Plugin.ModelManage/ModelManage.xaml
@@ -0,0 +1,169 @@
+锘�<hb:SimpleMaskControl x:Class="Plugin.ModelManage.ModelManage"
+             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.ModelManage"
+             mc:Ignorable="d"
+             xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
+            xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
+            xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI"
+            xmlns:mdi="clr-namespace:HMI.UI.MDI;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"
+                      Loaded="SimpleMaskControl_Loaded"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <hb:SimpleMaskControl.Resources>
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonListView.xaml"/>
+            </ResourceDictionary.MergedDictionaries>
+        </ResourceDictionary>
+    </hb:SimpleMaskControl.Resources>
+
+    <Grid dp:GridHelper.Space="8">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition/>
+            <RowDefinition Height="Auto"/>
+        </Grid.RowDefinitions>
+
+        <DockPanel>
+            <StackPanel dp:StackPanelHelper.ChildSpace="10" DockPanel.Dock="Right" Orientation="Horizontal">
+                <Button Style="{StaticResource AddBtn}" x:Name="addBtn" Click="Button_Click"/>
+                <Button Style="{StaticResource RfhBtn}" x:Name="rfhBtn" Click="Button_Click"/>
+                <Button Style="{StaticResource ExportBtn}" x:Name="exportBtn" Click="Button_Click"/>
+                <Button Style="{StaticResource ImportBtn}" x:Name="importBtn" Click="Button_Click"/>
+                <Button Style="{StaticResource DelBtn}" x:Name="delBatchBtn" Click="Button_Click"/>
+                <!--<Button Style="{StaticResource DelBtn}"/>-->
+            </StackPanel>
+            <TextBlock Style="{StaticResource TitleBox}" Text="鍨嬪彿绠$悊"/>
+        </DockPanel>
+        <ListView Grid.Row="1" Style="{StaticResource DefaultListTable}" x:Name="table"
+                      ItemsSource="{Binding Datasource}"
+                      VirtualizingPanel.IsContainerVirtualizable="True"
+VirtualizingPanel.IsVirtualizing="True" 
+VirtualizingPanel.VirtualizationMode="Standard">
+            <ListView.View>
+                <GridView AllowsColumnReorder="False">
+
+                    <GridViewColumn Width="30">
+                        <GridViewColumn.CellTemplate>
+                            <DataTemplate>
+                                <CheckBox VerticalAlignment="Center"
+IsChecked="{Binding IsSelected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
+                            </DataTemplate>
+                        </GridViewColumn.CellTemplate>
+                    </GridViewColumn>
+
+                    <GridViewColumn Width="60" Header="#">
+                        <GridViewColumn.CellTemplate>
+                            <DataTemplate>
+                                <Border Style="{StaticResource CellBorder}">
+                                    <TextBlock Style="{StaticResource ListTableTextBlock}"  Text="{Binding Converter={StaticResource lvRowNumber},
+RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ListViewItem,AncestorLevel=1}}"/>
+                                </Border>
+                            </DataTemplate>
+                        </GridViewColumn.CellTemplate>
+                    </GridViewColumn>
+
+                    <GridViewColumn Width="700">
+                        <GridViewColumn.Header>
+                            <TextBlock Text="鍨嬪彿"/>
+                        </GridViewColumn.Header>
+                        <GridViewColumn.CellTemplate>
+                            <DataTemplate>
+                                <Border Style="{StaticResource CellBorder}">
+                                    <TextBlock Style="{StaticResource ListTableTextBlock}" 
+                                                           Text="{Binding Model}"/>
+                                </Border>
+                            </DataTemplate>
+                        </GridViewColumn.CellTemplate>
+                    </GridViewColumn>
+                    <GridViewColumn Width="200">
+                        <GridViewColumn.Header>
+                            <TextBlock Text="鏉$爜闀垮害"/>
+                        </GridViewColumn.Header>
+                        <GridViewColumn.CellTemplate>
+                            <DataTemplate>
+                                <Border Style="{StaticResource CellBorder}">
+                                    <TextBlock Style="{StaticResource ListTableTextBlock}" 
+   Text="{Binding CodeLength}"/>
+                                </Border>
+                            </DataTemplate>
+                        </GridViewColumn.CellTemplate>
+                    </GridViewColumn>
+                    <GridViewColumn Width="200">
+                        <GridViewColumn.Header>
+                            <TextBlock Text="璧峰涓嬫爣"/>
+                        </GridViewColumn.Header>
+                        <GridViewColumn.CellTemplate>
+                            <DataTemplate>
+                                <Border Style="{StaticResource CellBorder}">
+                                    <TextBlock Style="{StaticResource ListTableTextBlock}" 
+                                                   Text="{Binding StartIndex}"/>
+                                </Border>
+                            </DataTemplate>
+                        </GridViewColumn.CellTemplate>
+                    </GridViewColumn>
+                    <!--<GridViewColumn Width="200">
+                        <GridViewColumn.Header>
+                            <TextBlock Text="鎴彇闀垮害"/>
+                        </GridViewColumn.Header>
+                        <GridViewColumn.CellTemplate>
+                            <DataTemplate>
+                                <Border Style="{StaticResource CellBorder}">
+                                    <TextBlock Style="{StaticResource ListTableTextBlock}" 
+                                                   Text="{Binding Length}"/>
+                                </Border>
+                            </DataTemplate>
+                        </GridViewColumn.CellTemplate>
+                    </GridViewColumn>-->
+                    <GridViewColumn Width="500">
+                        <GridViewColumn.Header>
+                            <TextBlock Text="鍥哄畾鐮�"/>
+                        </GridViewColumn.Header>
+                        <GridViewColumn.CellTemplate>
+                            <DataTemplate>
+                                <Border Style="{StaticResource CellBorder}">
+                                    <TextBlock Style="{StaticResource ListTableTextBlock}" 
+                                                   Text="{Binding Fixcode}"/>
+                                </Border>
+                            </DataTemplate>
+                        </GridViewColumn.CellTemplate>
+                    </GridViewColumn>
+                    <GridViewColumn Width="120">
+                        <GridViewColumn.Header>
+                            <TextBlock Text="{DynamicResource App.Opt}"/>
+                        </GridViewColumn.Header>
+                        <GridViewColumn.CellTemplate>
+                            <DataTemplate>
+                                <StackPanel Orientation="Horizontal" dp:StackPanelHelper.ChildSpace="10">
+                                    <Button x:Name="editBtn" Style="{StaticResource EditLink}" 
+                Click="Button_Click" Tag="{Binding }"/>
+
+                                    <Button x:Name="delBtn" Style="{StaticResource DeleteLink}" 
+                Click="Button_Click" Tag="{Binding }"/>
+                                </StackPanel>
+                            </DataTemplate>
+                        </GridViewColumn.CellTemplate>
+                    </GridViewColumn>
+                </GridView>
+            </ListView.View>
+        </ListView>
+        <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right">
+            <pu:Pagination  Height="36" Margin="10"
+HorizontalAlignment="Left"
+Foreground="Black"
+                            FontSize="16"
+                            FontWeight="UltraBold"
+                            ItemsSelectedForeground="#FFF"
+ItemsBackground="Transparent"
+ItemsCornerRadius="12"
+ItemsSelectedBackground="{DynamicResource SecondPrimaryColor}" x:Name="page"
+                            CurrentPage="{Binding CurrentPage}" 
+                                        MaxPage="{Binding MaxPage}"/>
+        </StackPanel>
+    </Grid>
+</hb:SimpleMaskControl>
diff --git a/Plugin.ModelManage/ModelManage.xaml.cs b/Plugin.ModelManage/ModelManage.xaml.cs
new file mode 100644
index 0000000..e5f0bd7
--- /dev/null
+++ b/Plugin.ModelManage/ModelManage.xaml.cs
@@ -0,0 +1,300 @@
+锘縰sing CommunityToolkit.Mvvm.ComponentModel;
+using HMI.Base;
+using HMI.Core.Util;
+using HMI.UI.Util;
+using HMI.Util;
+using Microsoft.Win32;
+using Plugin.ModelManage.Entity;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Data;
+using System.Drawing;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+
+namespace Plugin.ModelManage
+{
+    /// <summary>
+    /// ModelManage.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    [ObservableObject]
+    public partial class ModelManage : SimpleMaskControl
+    {
+        private string dataFilePath;
+        private SqliteHelper sqliteHelper;
+        private double size = 30;
+
+        [ObservableProperty]
+        private ObservableCollection<ModelData> datasource;
+
+        [ObservableProperty]
+        private int currentPage;
+        [ObservableProperty]
+        private int maxPage;
+
+        public ModelManage()
+        {
+            DataContext = this;
+            InitializeComponent();
+        }
+
+        public override void InitFinish()
+        {
+            base.InitFinish();
+            dataFilePath = Path.Combine(BaseFolder, "data.db");
+            if (!File.Exists(dataFilePath))
+            {
+                SqliteHelper.CreateDatabase(dataFilePath);
+            }
+            sqliteHelper = new SqliteHelper(dataFilePath);
+            sqliteHelper.ExecuteSql(@"CREATE TABLE IF NOT EXISTS ModelData (
+                    Id INTEGER PRIMARY KEY AUTOINCREMENT,
+                    Model TEXT NOT NULL,
+                    CodeLength INT,
+                    StartIndex INT,
+                    Length INT,
+                    Fixcode TEXT,
+                    UNIQUE(Model),
+                    UNIQUE(Fixcode));");
+        }
+
+        private void Button_Click(object sender, RoutedEventArgs e)
+        {
+            Button btn = sender as Button;
+            ModelData data;
+            switch (btn.Name)
+            {
+                case "addBtn":
+                    OpenDataModal(new ModelData());
+                    break;
+                case "rfhBtn":
+                    RefreshPage(CurrentPage);
+                    break;
+                case "exportBtn":
+                    ExportExcel();
+                    break;
+                case "importBtn":
+                    ImportExcel();
+                    break;
+                case "editBtn":
+                    data = btn.Tag as ModelData;
+                    OpenDataModal(data);
+                    break;
+                case "delBtn":
+                    data = btn.Tag as ModelData;
+                    DeleteData(data);
+                    break;
+                case "delBatchBtn":
+                    DeleteBatchData();
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        private void RefreshPage(int? page = null)
+        {
+            try
+            {
+                var sql = "SELECT COUNT (*) as num FROM ModelData;";
+                var dt = sqliteHelper.GetDataTable(sql);
+                if (dt.Rows.Count <= 0)
+                {
+                    CurrentPage = 1;
+                    MaxPage = 1;
+                    return;
+                }
+                int count = int.Parse(dt.Rows[0]["num"].ToString());
+                MaxPage = (int)Math.Ceiling(count / size);
+
+                if (page != null)
+                {
+                    CurrentPage = page.Value;
+                }
+
+                if (CurrentPage > MaxPage)
+                {
+                    CurrentPage = MaxPage;
+                }
+
+                if (page == CurrentPage)
+                {
+                    RefreshData();
+                }
+            }
+            catch (Exception ex)
+            {
+                log.Error("RefreshPage Error.", ex);
+                ToastHelper.Error($"鍒锋柊鏁版嵁澶辫触: {ex.Message}");
+            }
+        }
+
+        private void RefreshData()
+        {
+            try
+            {
+                var list = new ObservableCollection<ModelData>();
+                var sql = $"SELECT Id, Model, CodeLength, StartIndex, Length, Fixcode FROM ModelData ORDER BY Id DESC LIMIT ({CurrentPage - 1} * {size}), {size};";
+                var dt = sqliteHelper.GetDataTable(sql);
+                if (dt.Rows.Count > 0)
+                {
+                    foreach (DataRow dr in dt.Rows)
+                    {
+                        var at = new ModelData
+                        {
+                            Id = dr["Id"].ToString(),
+                            Model = dr["Model"].ToString(),
+                            CodeLength = int.Parse(dr["CodeLength"].ToString()),
+                            StartIndex = int.Parse(dr["StartIndex"].ToString()),
+                            //Length = int.Parse(dr["Length"].ToString()),
+                            Fixcode = dr["Fixcode"].ToString()
+                        };
+                        list.Add(at);
+                    }
+                }
+                Datasource = list;
+            }
+            catch (Exception ex)
+            {
+                log.Error("RefreshData Error.", ex);
+                ToastHelper.Error($"鍒锋柊鏁版嵁澶辫触: {ex.Message}");
+            }
+        }
+
+        private void ExportExcel()
+        {
+            try
+            {
+                SaveFileDialog sfd = new SaveFileDialog
+                {
+                    FileName = "鍨嬪彿绠$悊",
+                    Filter = "Excel|*.xls"
+                };
+                if (sfd.ShowDialog() == true)
+                {
+                    ExcelHelper.ExportExcel(sfd.FileName, Datasource);
+                    ToastHelper.Success("瀵煎嚭鎴愬姛");
+                }
+            }
+            catch (Exception ex)
+            {
+                log.Error("ExportExcel Error.", ex);
+                ToastHelper.Error($"瀵煎嚭澶辫触: {ex.Message}");
+            }
+        }
+
+        private void ImportExcel()
+        {
+            try
+            {
+                OpenFileDialog ofd = new OpenFileDialog
+                {
+                    Filter = "Excel|*.xls;*.xlsx"
+                };
+                if (ofd.ShowDialog() == true)
+                {
+                    List<ModelData> list = ExcelHelper.ReadFromExcel<ModelData>(ofd.FileName);
+                    List<string> sqls = new List<string> {
+                        "DELETE FROM ModelData;"
+                    };
+                    foreach (ModelData at in list)
+                    {
+                        sqls.Add($"INSERT INTO ModelData (Model,CodeLength,StartIndex,Fixcode) VALUES ('{at.Model}',{at.CodeLength},{at.StartIndex},'{at.Fixcode}');");
+                    }
+
+                    sqliteHelper.ExecuteBatchSql(sqls);
+                    ToastHelper.Success("瀵煎叆鎴愬姛");
+                    RefreshPage(CurrentPage);
+                }
+            }
+            catch (Exception ex)
+            {
+                log.Error("ImportExcel Error.", ex);
+                ToastHelper.Error($"瀵煎叆澶辫触: {ex.Message}");
+            }
+        }
+
+        private void DeleteData(ModelData data)
+        {
+            try
+            {
+                if (MsgBoxHelper.WarnMessage(Window.GetWindow(this), "纭畾瑕佸垹闄よ鏁版嵁鍚楋紵") == MessageBoxResult.OK)
+                {
+                    string sql = $"DELETE FROM ModelData WHERE Id = {data.Id}";
+                    sqliteHelper.ExecuteSql(sql);
+                    ToastHelper.Success("鍒犻櫎鎴愬姛");
+                    RefreshPage(CurrentPage);
+                }
+            }
+            catch (Exception ex)
+            {
+                log.Error("DeleteData Error.", ex);
+                ToastHelper.Error($"鍒犻櫎澶辫触: {ex.Message}");
+            }
+        }
+
+        private void DeleteBatchData()
+        {
+            try
+            {
+                var datas = Datasource.Where(e => e.IsSelected).Select(e => e.Id).ToList();
+                if (datas.Count <= 0) return;
+                if (MsgBoxHelper.WarnMessage(Window.GetWindow(this), "纭畾瑕佸垹闄ら�変腑鐨勬暟鎹悧锛�") == MessageBoxResult.OK)
+                {
+                    var ids = string.Join("','", datas);
+                    string sql = $"DELETE FROM ModelData WHERE Id IN ('{ids}')";
+                    sqliteHelper.ExecuteSql(sql);
+                    ToastHelper.Success("鍒犻櫎鎴愬姛");
+                    RefreshPage(CurrentPage);
+                }
+            }
+            catch (Exception ex)
+            {
+                log.Error("DeleteData Error.", ex);
+                ToastHelper.Error($"鍒犻櫎澶辫触: {ex.Message}");
+            }
+        }
+
+        private void OpenDataModal(ModelData data)
+        {
+            try
+            {
+                var win = new DataModal
+                {
+                    Title = string.IsNullOrEmpty(data.Id) ? "鏂板" : "缂栬緫",
+                    Owner = Window.GetWindow(this)
+                };
+                if (win.Open(data, sqliteHelper) == true)
+                {
+                    RefreshPage(CurrentPage);
+                }
+            }
+            catch (Exception ex)
+            {
+                log.Error("OpenDataModal Error.", ex);
+            }
+        }
+
+        partial void OnCurrentPageChanged(int value)
+        {
+            RefreshData();
+        }
+
+        private void SimpleMaskControl_Loaded(object sender, RoutedEventArgs e)
+        {
+            RefreshPage(1);
+            Loaded -= SimpleMaskControl_Loaded;
+        }
+    }
+}
diff --git a/Plugin.ModelManage/Plugin.ModelManage.csproj b/Plugin.ModelManage/Plugin.ModelManage.csproj
new file mode 100644
index 0000000..8807b70
--- /dev/null
+++ b/Plugin.ModelManage/Plugin.ModelManage.csproj
@@ -0,0 +1,27 @@
+锘�<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Library</OutputType>
+    <TargetFramework>net472</TargetFramework>
+    <Nullable>enable</Nullable>
+    <LangVersion>10.0</LangVersion>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <UseWPF>true</UseWPF>
+    <AutoGenerateBindingRedirects>True</AutoGenerateBindingRedirects>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
+    <PackageReference Include="log4net" Version="3.0.4" />
+    <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
+    <PackageReference Include="Panuon.WPF.UI" Version="1.3.0.2" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\HMI.Base\HMI.Base.csproj" />
+    <ProjectReference Include="..\HMI.Core\HMI.Core.csproj" />
+    <ProjectReference Include="..\HMI.UI\HMI.UI.csproj" />
+    <ProjectReference Include="..\HMI.Util\HMI.Util.csproj" />
+  </ItemGroup>
+
+</Project>
diff --git a/Plugin.SeqEditor/Entity/EditablePO/NumValTestEPO.cs b/Plugin.SeqEditor/Entity/EditablePO/NumValTestEPO.cs
index 69d3272..ee71906 100644
--- a/Plugin.SeqEditor/Entity/EditablePO/NumValTestEPO.cs
+++ b/Plugin.SeqEditor/Entity/EditablePO/NumValTestEPO.cs
@@ -1,5 +1,4 @@
-锘縰sing CommunityToolkit.Mvvm.ComponentModel;
-using NationalInstruments.TestStand.Interop.API;
+锘縰sing NationalInstruments.TestStand.Interop.API;
 
 namespace Plugin.SeqEditor.Entity.EditablePO
 {
@@ -11,8 +10,26 @@
         public NumValTestEPO(StepUI stepUI)
         {
             StepUI = stepUI;
+            StepUI.PropertyChanged += StepUI_PropertyChanged;
+
             Step = stepUI?.step;
             PropertyObject = Step?.AsPropertyObject();
+        }
+
+        private void StepUI_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
+        {
+            if (e.PropertyName == "Low" || e.PropertyName == "High")
+            {
+                OnPropertyChanged(e.PropertyName);
+            }
+        }
+
+        ~NumValTestEPO()
+        {
+            if (StepUI != null)
+            {
+                StepUI.PropertyChanged -= StepUI_PropertyChanged;
+            }
         }
 
         public string ComparisonType
@@ -28,29 +45,23 @@
             }
         }
 
-        public double? Low
+        public string Low
         {
-            get => PropertyObject?.GetValNumber("Limits.Low", 0);
+            get => StepUI?.Low?.ToString();
             set
             {
-                if (PropertyObject != null)
-                {
-                    PropertyObject.SetValNumber("Limits.Low", 0, value ?? 0);
-                    CustomPropertyChanged(nameof(Low));
-                }
+                double.TryParse(value, out var dob);
+                StepUI.Low = value;
             }
         }
 
-        public double? High
+        public string High
         {
-            get => PropertyObject?.GetValNumber("Limits.High", 0);
+            get => StepUI?.High?.ToString();
             set
             {
-                if (PropertyObject != null)
-                {
-                    PropertyObject.SetValNumber("Limits.High", 0, value ?? 0);
-                    CustomPropertyChanged(nameof(Low));
-                }
+                double.TryParse(value, out var dob);
+                StepUI.High = dob;
             }
         }
     }
diff --git a/Plugin.SeqEditor/Entity/EditablePO/StringValTestEPO.cs b/Plugin.SeqEditor/Entity/EditablePO/StringValTestEPO.cs
index 352448b..1772d48 100644
--- a/Plugin.SeqEditor/Entity/EditablePO/StringValTestEPO.cs
+++ b/Plugin.SeqEditor/Entity/EditablePO/StringValTestEPO.cs
@@ -12,10 +12,19 @@
         public StringValTestEPO(StepUI stepUI)
         {
             StepUI = stepUI;
+            stepUI.PropertyChanged += StepUI_PropertyChanged;
             Step = StepUI?.step;
             PropertyObject = Step?.AsPropertyObject();
         }
 
+        private void StepUI_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
+        {
+            if (e.PropertyName == "Low")
+            {
+                OnPropertyChanged("StringValue");
+            }
+        }
+
         public string ComparisonType
         {
             get => PropertyObject?.GetValString("Comp", 0);
diff --git a/Plugin.SeqEditor/Entity/StepUI.cs b/Plugin.SeqEditor/Entity/StepUI.cs
index 91f5baf..c3cd66e 100644
--- a/Plugin.SeqEditor/Entity/StepUI.cs
+++ b/Plugin.SeqEditor/Entity/StepUI.cs
@@ -1,5 +1,6 @@
 锘縰sing CommunityToolkit.Mvvm.ComponentModel;
 using NationalInstruments.TestStand.Interop.API;
+using Plugin.SeqEditor.Constant;
 using System.Collections.ObjectModel;
 using System.Runtime.InteropServices;
 
@@ -21,10 +22,17 @@
 
         public event EventHandler<string> SettingChanged;
 
-        public int IndexInSequence { 
-            get {
-                return sequence.GetStepIndex(Name, StepGroup);
-            } 
+        public int IndexInSequence
+        {
+            get
+            {
+                return step.StepIndex;
+            }
+        }
+
+        public int? CustomIndex
+        {
+            get; set;
         }
 
         public StepUI(Step step, Sequence sequence)
@@ -57,6 +65,11 @@
             }
         }
 
+        public string Id
+        {
+            get => step?.UniqueStepId;
+        }
+
         public string Description
         {
             get => step?.Description;
@@ -64,7 +77,11 @@
 
         public string RunMode
         {
-            get => step?.GetRunModeEx();
+            get
+            {
+                var t = step?.GetRunModeEx();
+                return t;
+            }
             set
             {
                 if (step != null)
@@ -98,6 +115,63 @@
             }
         }
 
+        public bool HasLow => Type == StepTypeStr.NumericLimitTest || Type == StepTypeStr.StringValueTest;
+        public bool HasHigh => Type == StepTypeStr.NumericLimitTest;
+
+        public string Low
+        {
+            get
+            {
+                if (Type == StepTypeStr.NumericLimitTest)
+                {
+                    return step?.AsPropertyObject()?.GetValNumber("Limits.Low", 0).ToString();
+                }
+                else if (Type == StepTypeStr.StringValueTest)
+                {
+                    var value = step?.AsPropertyObject()?.GetValString("Limits.String", 0);
+                    if (string.IsNullOrEmpty(value))
+                    {
+                        value = step?.AsPropertyObject()?.GetValString("Limits.StringExpr", 0);
+                    }
+                    return value;
+                }
+                return null;
+            }
+            set
+            {
+                if (step != null && HasLow)
+                {
+                    if (Type == StepTypeStr.NumericLimitTest)
+                    {
+                        double.TryParse(value, out double tmp);
+                        step?.AsPropertyObject().SetValNumber("Limits.Low", 0, tmp);
+                    }
+                    else if (Type == StepTypeStr.StringValueTest)
+                    {
+                        step?.AsPropertyObject()?.SetValString("Limits.String", 0, value);
+                        step?.AsPropertyObject()?.SetValString("Limits.StringExpr", 0, null);
+                        step?.AsPropertyObject()?.SetValBoolean("Limits.UseStringExpr", 0, false);
+                    }
+                    UpdatePropertyChanged("Low");
+                    UpdatePropertyChanged("Description");
+                }
+            }
+        }
+
+        public double? High
+        {
+            get => HasHigh ? step?.AsPropertyObject()?.GetValNumber("Limits.High", 0) : null;
+            set
+            {
+                if (step != null && HasHigh)
+                {
+                    step?.AsPropertyObject().SetValNumber("Limits.High", 0, value ?? 0);
+                    UpdatePropertyChanged("High");
+                    UpdatePropertyChanged("Description");
+                }
+            }
+        }
+
         public StepGroups StepGroup
         {
             get
@@ -112,6 +186,191 @@
         [ObservableProperty]
         public ObservableCollection<StepUI> children = new();
 
+        public string LoopType
+        {
+            get => step?.AsPropertyObject()?.GetValString("TS.LoopType", 0);
+            set
+            {
+                if (step != null)
+                {
+                    if (value == "FixedNumLoops")
+                    {
+                        step.AsPropertyObject().SetValString("TS.LoopType", 0, value);
+                        step.AsPropertyObject().SetValString("TS.LoopWhile", 0, "RunState.LoopIndex < 10");
+                        step.AsPropertyObject().SetValString("TS.LoopStatus", 0, "RunState.LoopNumPassed / RunState.LoopNumIterations < 1 ? \"Failed\" : \"Passed\"");
+                    }
+                    else if (value == "PassFailCount")
+                    {
+                        step.AsPropertyObject().SetValString("TS.LoopType", 0, value);
+                        step.AsPropertyObject().SetValString("TS.LoopWhile", 0, "RunState.LoopIndex < 100 && RunState.LoopNumFailed < 10");
+                        step.AsPropertyObject().SetValString("TS.LoopStatus", 0, "RunState.LoopNumFailed >= 10 ? \"Failed\" : \"Passed\"");
+                    }
+                    UpdatePropertyChanged("LoopType");
+                    UpdatePropertyChanged("LoopCount");
+                    UpdatePropertyChanged("LoopCondition");
+                    UpdatePropertyChanged("LoopMax");
+                    UpdatePropertyChanged("LoopDescription");
+                }
+            }
+        }
+
+        public string LoopCount
+        {
+            get
+            {
+                if (step == null || step?.AsPropertyObject() == null) return null;
+                string type = step.AsPropertyObject().GetValString("TS.LoopType", 0);
+                string loopWhile;
+                switch (type)
+                {
+                    case "FixedNumLoops":
+                        loopWhile = step.AsPropertyObject().GetValString("TS.LoopWhile", 0);
+                        return loopWhile.Replace($"RunState.LoopIndex < ", "");
+                    case "PassFailCount":
+                        loopWhile = step.AsPropertyObject().GetValString("TS.LoopWhile", 0);
+                        return loopWhile.Split(' ')[2];
+                    default:
+                        return null;
+                }
+            }
+            set
+            {
+                if (step != null)
+                {
+                    string type = step.AsPropertyObject().GetValString("TS.LoopType", 0);
+                    switch (type)
+                    {
+                        case "FixedNumLoops":
+                            step.AsPropertyObject().SetValString("TS.LoopWhile", 0, $"RunState.LoopIndex < {value}");
+                            UpdatePropertyChanged("LoopDescription");
+                            return;
+                        case "PassFailCount":
+                            var field = LoopCondition == "pass" ? "LoopNumPassed" : "LoopNumFailed";
+                            step.AsPropertyObject().SetValString("TS.LoopWhile", 0,
+                                $"RunState.LoopIndex < {value} && RunState.{field} < {LoopMax}");
+                            UpdatePropertyChanged("LoopDescription");
+                            return;
+                        default:
+                            return;
+                    }
+                }
+            }
+        }
+
+        public string LoopCondition
+        {
+            get
+            {
+                if (step == null || step?.AsPropertyObject() == null) return null;
+                string type = step.AsPropertyObject().GetValString("TS.LoopType", 0);
+                string loopStatus;
+                switch (type)
+                {
+                    case "FixedNumLoops":
+                        loopStatus = step.AsPropertyObject().GetValString("TS.LoopStatus", 0);
+                        return loopStatus.Split(' ')[3];
+                    case "PassFailCount":
+                        loopStatus = step.AsPropertyObject().GetValString("TS.LoopStatus", 0);
+                        return loopStatus.Split(' ')[0] == "RunState.LoopNumPassed" ? "pass" : "fail";
+                    default:
+                        return null;
+                }
+            }
+            set
+            {
+                if (step != null)
+                {
+                    string type = step.AsPropertyObject().GetValString("TS.LoopType", 0);
+                    switch (type)
+                    {
+                        case "FixedNumLoops":
+                            step.AsPropertyObject().SetValString("TS.LoopStatus", 0,
+                                $"RunState.LoopNumPassed / RunState.LoopNumIterations {value} {double.Parse(LoopMax) / 100} ? \"Failed\" : \"Passed\"");
+                            UpdatePropertyChanged("LoopDescription");
+                            return;
+                        case "PassFailCount":
+                            var field = value == "pass" ? "LoopNumPassed" : "LoopNumFailed";
+                            var sb = value == "pass" ? "\"Passed\" : \"Failed\"" : "\"Failed\" : \"Passed\"";
+                            step.AsPropertyObject().SetValString("TS.LoopWhile", 0,
+                                $"RunState.LoopIndex < {LoopCount} && RunState.{field} < {LoopMax}");
+                            step.AsPropertyObject().SetValString("TS.LoopStatus", 0,
+                                $"RunState.{field} >= {LoopMax} ? {sb}");
+                            UpdatePropertyChanged("LoopDescription");
+                            return;
+                        default:
+                            return;
+                    }
+
+                }
+            }
+        }
+
+        public string LoopMax
+        {
+            get
+            {
+                if (step == null || step?.AsPropertyObject() == null) return null;
+                string type = step.AsPropertyObject().GetValString("TS.LoopType", 0);
+                string data;
+                switch (type)
+                {
+                    case "FixedNumLoops":
+                        data = step.AsPropertyObject().GetValString("TS.LoopStatus", 0);
+                        return (double.Parse(data.Split(' ')[4]) * 100).ToString();
+                    case "PassFailCount":
+                        data = step.AsPropertyObject().GetValString("TS.LoopWhile", 0);
+                        return data.Split(' ')[6];
+                    default:
+                        return null;
+                }
+            }
+            set
+            {
+                if (step != null)
+                {
+                    string type = step.AsPropertyObject().GetValString("TS.LoopType", 0);
+                    switch (type)
+                    {
+                        case "FixedNumLoops":
+                            step.AsPropertyObject().SetValString("TS.LoopStatus", 0,
+                                $"RunState.LoopNumPassed / RunState.LoopNumIterations {LoopCondition} {double.Parse(value) / 100} ? \"Failed\" : \"Passed\"");
+                            UpdatePropertyChanged("LoopDescription");
+                            return;
+                        case "PassFailCount":
+                            var field = LoopCondition == "pass" ? "LoopNumPassed" : "LoopNumFailed";
+                            step.AsPropertyObject().SetValString("TS.LoopWhile", 0,
+                                $"RunState.LoopIndex < {LoopCount} && RunState.{field} < {value}");
+                            UpdatePropertyChanged("LoopDescription");
+                            return;
+                        default:
+                            return;
+                    }
+
+                }
+            }
+        }
+
+        public string LoopDescription
+        {
+            get
+            {
+                if (step == null || step?.AsPropertyObject() == null) return null;
+                string type = step.AsPropertyObject().GetValString("TS.LoopType", 0);
+                switch (type)
+                {
+                    case "FixedNumLoops":
+                        return $"寰幆{LoopCount}娆★紝閫氳繃鐜噞LoopCondition}{LoopMax}%鏃剁粨鏋滀负PASS";
+                    case "PassFailCount":
+                        return $"寰幆{LoopCount}娆★紝{LoopCondition} {LoopMax}娆′互涓婄粨鏋滀负{LoopCondition}";
+                    default:
+                        return null;
+                }
+            }
+            set
+            {
+            }
+        }
+
         public override string ToString()
         {
             return Name;
diff --git a/Plugin.SeqEditor/SeqEditor.xaml b/Plugin.SeqEditor/SeqEditor.xaml
index 0867528..0120dad 100644
--- a/Plugin.SeqEditor/SeqEditor.xaml
+++ b/Plugin.SeqEditor/SeqEditor.xaml
@@ -4,6 +4,7 @@
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
              xmlns:local="clr-namespace:Plugin.SeqEditor"
+             xmlns:sp="clr-namespace:Plugin.SeqEditor.SettingPanel"
              mc:Ignorable="d" 
              xmlns:hcu="clr-namespace:HMI.Core.Util;assembly=HMI.Core"
              xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
@@ -30,6 +31,8 @@
                 <ResourceDictionary Source="/HMI.UI;component/Controls/TreeGrid/Themes/TreeItemContentControl.xaml"/>
                 <ResourceDictionary Source="/HMI.UI;component/Controls/TreeGrid/Themes/TreeGrid.xaml"/>
             </ResourceDictionary.MergedDictionaries>
+
+            <x:Static x:Key="runModeResource" Member="util:RunModeData.ItemSource"/>
 
             <uic:IndentConverter x:Key="IndentConverter"/>
 
@@ -156,8 +159,8 @@
             </Grid.RowDefinitions>
             <Grid.ColumnDefinitions>
                 <ColumnDefinition Width="0"/>
-                <ColumnDefinition Width="8*"/>
-                <ColumnDefinition Width="2*"/>
+                <ColumnDefinition Width="8.5*"/>
+                <ColumnDefinition Width="1.5*"/>
             </Grid.ColumnDefinitions>
 
             <DockPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" Margin="0,0,0,10">
@@ -379,6 +382,8 @@
 
                 <ui:TreeListView Grid.Row="1" Grid.Column="1" ItemsSource="{Binding AllSteps}"
                                  dd:DragDrop.IsDragSource="True"
+                                 FontSize="14"
+                                 pu:TreeViewHelper.ItemsHeight="{DynamicResource nanD}"
                                  dd:DragDrop.IsDropTarget="True"
                                  dd:DragDrop.DropTargetAdornerBrush="Red"
                                  dd:DragDrop.DropHandler="{Binding StepDropHandler}"
@@ -393,11 +398,12 @@
                     </i:Interaction.Triggers>
                     <ui:TreeListView.Resources>
                         <Style x:Key="CellBorder" TargetType="Border">
-                            <Setter Property="Padding" Value="5"/>
+                            <Setter Property="Padding" Value="2"/>
                             <Setter Property="BorderBrush" Value="{DynamicResource CommonBoarder}"/>
                             <Setter Property="BorderThickness" Value="1,0,0,1"/>
                             <Setter Property="Margin" Value="-6,0"/>
                         </Style>
+                        <Style TargetType="ScrollBar" BasedOn="{StaticResource ScrollStyle_Mobile}"/>
                     </ui:TreeListView.Resources>
 
                     <ui:TreeListView.Style>
@@ -405,6 +411,7 @@
                             <Setter Property="pu:TreeViewHelper.ToggleArrowToggleButtonStyle" Value="{StaticResource TreeTogg}"/>
                             <Setter Property="pu:TreeViewHelper.ItemsPadding" Value="0"/>
                             <Setter Property="Background" Value="#FFF"/>
+                            <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
                             <Setter Property="ColumnHeaderContainerStyle" Value="{StaticResource gvh}"/>
                             <Setter Property="Template">
                                 <Setter.Value>
@@ -437,19 +444,20 @@
                     </ui:TreeListView.Style>
 
                     <ui:TreeListView.Columns>
-                        <GridViewColumn Header="鍚嶇О" HeaderContainerStyle="{StaticResource gvh}"
+                        <GridViewColumn Header="搴忓彿" HeaderContainerStyle="{StaticResource gvh}"
+                                        Width="80"
                                         HeaderTemplate="{StaticResource gvht}">
                             <GridViewColumn.CellTemplate>
                                 <DataTemplate>
                                     <Border Style="{StaticResource CellBorder}">
-                                        <StackPanel Orientation="Horizontal">
+                                        <StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
                                             <ToggleButton x:Name="Expander"
       Style="{StaticResource ExpandCollapseToggleStyle}"
                                           Margin="{Binding level,Converter={StaticResource IndentConverter},RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ui:TreeListViewItem,AncestorLevel=1}}"
       Grid.Column="0"
 IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ui:TreeListViewItem,AncestorLevel=1}}"
 ClickMode="Press"/>
-                                            <TextBlock Margin="5,0,0,0" Padding="6,0" Text="{Binding Name}"/>
+                                            <TextBlock Margin="5,0,0,0" Padding="6,0" Text="{Binding CustomIndex}"/>
                                         </StackPanel>
                                     </Border>
 
@@ -465,8 +473,62 @@
                             </GridViewColumn.CellTemplate>
                         </GridViewColumn>
 
-                        <GridViewColumn Header="绫诲瀷" HeaderContainerStyle="{StaticResource gvh}" 
+                        <GridViewColumn Header="鍚嶇О" HeaderContainerStyle="{StaticResource gvh}"
+                                        Width="200"
                                         HeaderTemplate="{StaticResource gvht}">
+                            <GridViewColumn.CellTemplate>
+                                <DataTemplate>
+                                    <Border Style="{StaticResource CellBorder}">
+                                        <TextBlock Margin="5,0,0,0" Padding="6,0" Text="{Binding Name}"/>
+                                    </Border>
+                                </DataTemplate>
+                            </GridViewColumn.CellTemplate>
+                        </GridViewColumn>
+
+                        <GridViewColumn Header="{DynamicResource se.runMode}" 
+                                        Width="100"
+                                        HeaderContainerStyle="{StaticResource gvh}" 
+                                        HeaderTemplate="{StaticResource gvht}">
+                            <GridViewColumn.CellTemplate>
+                                <DataTemplate>
+                                    <Border Style="{StaticResource CellBorder}">
+                                        <!--<TextBlock Padding="6,0" Text="{Binding RunMode}"/>-->
+                                        <ui:TableComboBox CanEdit="True"
+                                                          DisplayMemberPath="DisplayName" SelectedValuePath="DataValue"
+                                                          ItemsSource="{StaticResource runModeResource}"
+                                                          Value="{Binding RunMode,Mode=TwoWay}"/>
+                                    </Border>
+                                </DataTemplate>
+                            </GridViewColumn.CellTemplate>
+                        </GridViewColumn>
+
+                        <GridViewColumn Header="Low" HeaderContainerStyle="{StaticResource gvh}"
+                                        Width="300"
+                HeaderTemplate="{StaticResource gvht}">
+                            <GridViewColumn.CellTemplate>
+                                <DataTemplate>
+                                    <Border Style="{StaticResource CellBorder}">
+                                        <ui:EditBox Value="{Binding Low,Mode=TwoWay}" CanEdit="{Binding HasLow}"/>
+                                    </Border>
+                                </DataTemplate>
+                            </GridViewColumn.CellTemplate>
+                        </GridViewColumn>
+
+                        <GridViewColumn Header="High" HeaderContainerStyle="{StaticResource gvh}"
+                                        Width="300"
+HeaderTemplate="{StaticResource gvht}">
+                            <GridViewColumn.CellTemplate>
+                                <DataTemplate>
+                                    <Border Style="{StaticResource CellBorder}">
+                                        <ui:EditBox Value="{Binding High,Mode=TwoWay}" CanEdit="{Binding HasHigh}"/>
+                                    </Border>
+                                </DataTemplate>
+                            </GridViewColumn.CellTemplate>
+                        </GridViewColumn>
+
+                        <GridViewColumn Header="绫诲瀷" HeaderContainerStyle="{StaticResource gvh}" 
+                                        Width="120"
+                HeaderTemplate="{StaticResource gvht}">
                             <GridViewColumn.CellTemplate>
                                 <DataTemplate>
                                     <Border Style="{StaticResource CellBorder}">
@@ -477,24 +539,12 @@
                         </GridViewColumn>
 
                         <GridViewColumn Header="鎻忚堪" HeaderContainerStyle="{StaticResource gvh}"
-                                        HeaderTemplate="{StaticResource gvht}">
-                            <GridViewColumn.CellTemplate>
-                                <DataTemplate>
-                                    <Border Style="{StaticResource CellBorder}">
-                                        <TextBlock Padding="6,0" Text="{Binding Description}"/>
-                                    </Border>
-                                </DataTemplate>
-                            </GridViewColumn.CellTemplate>
-                        </GridViewColumn>
-
-                        <GridViewColumn Header="{DynamicResource se.runMode}" 
-                                        Width="80"
-                                        HeaderContainerStyle="{StaticResource gvh}" 
-                                        HeaderTemplate="{StaticResource gvht}">
+                                        Width="400"
+                HeaderTemplate="{StaticResource gvht}">
                             <GridViewColumn.CellTemplate>
                                 <DataTemplate>
                                     <Border Style="{StaticResource CellBorder}" BorderThickness="1,0,1,1">
-                                        <TextBlock Padding="6,0" Text="{Binding RunMode}"/>
+                                        <TextBlock Padding="6,0" Text="{Binding Description}"/>
                                     </Border>
                                 </DataTemplate>
                             </GridViewColumn.CellTemplate>
@@ -513,11 +563,14 @@
 
             </pu:FormGroup>
 
-            <pu:FormGroup Header="{DynamicResource se.propertySetting}" Orientation="Vertical" 
-                          Grid.Row="2" Padding="0,10" Grid.Column="2">
-                <Border x:Name="editPanelContainer" Background="#FFF">
-                </Border>
-            </pu:FormGroup>
+            <StackPanel Grid.Row="2"  Grid.Column="2">
+                <pu:FormGroup Header="{DynamicResource se.propertySetting}" Orientation="Vertical" 
+             Padding="0,10" >
+                    <Border x:Name="editPanelContainer" Background="#FFF">
+                    </Border>
+                </pu:FormGroup>
+                <sp:LoopPanel x:Name="loopPanel" DataContext="{Binding CurrentStepUI}"/>
+            </StackPanel>
         </Grid>
     </Border>
 </hb:PluginControl>
diff --git a/Plugin.SeqEditor/SeqEditor.xaml.cs b/Plugin.SeqEditor/SeqEditor.xaml.cs
index a6d4213..3d95679 100644
--- a/Plugin.SeqEditor/SeqEditor.xaml.cs
+++ b/Plugin.SeqEditor/SeqEditor.xaml.cs
@@ -74,11 +74,12 @@
 
     private void TreeListView_SizeChanged(object sender, SizeChangedEventArgs e)
     {
-        var treeListView = sender as TreeListView;
-        var columns = treeListView.Columns;
-        var width = treeListView.ActualWidth;
+        //var treeListView = sender as TreeListView;
+        //var columns = treeListView.Columns;
+        //var width = treeListView.ActualWidth;
 
-        columns[0].Width = columns[0].Width + (width - columns[0].Width - columns[1].Width - columns[2].Width - columns[3].Width);
+        //columns[0].Width = columns[0].Width + (width - columns[0].Width - columns[1].Width - columns[2].Width - columns[3].Width);
+        
         //foreach (var col in columns) { 
         //    col.Width = 
         //}
diff --git a/Plugin.SeqEditor/SettingPanel/ActionPanel.xaml b/Plugin.SeqEditor/SettingPanel/ActionPanel.xaml
index 0d51042..8d8edf2 100644
--- a/Plugin.SeqEditor/SettingPanel/ActionPanel.xaml
+++ b/Plugin.SeqEditor/SettingPanel/ActionPanel.xaml
@@ -30,6 +30,7 @@
 
                 <ComboBox x:Name="CbRunMode" Style="{StaticResource FormComboxStyle}"
                           VirtualizingPanel.IsVirtualizing="True"
+                          pu:ComboBoxHelper.ClearButtonVisibility="Collapsed"
                 SelectedValue="{Binding RunMode}" 
             DisplayMemberPath="DisplayName" SelectedValuePath="DataValue"/>
             </pu:FormGroup>
diff --git a/Plugin.SeqEditor/SettingPanel/LoopPanel.xaml b/Plugin.SeqEditor/SettingPanel/LoopPanel.xaml
new file mode 100644
index 0000000..741ccbd
--- /dev/null
+++ b/Plugin.SeqEditor/SettingPanel/LoopPanel.xaml
@@ -0,0 +1,104 @@
+锘�<UserControl x:Class="Plugin.SeqEditor.SettingPanel.LoopPanel"
+             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.SeqEditor.SettingPanel"
+             mc:Ignorable="d" 
+             xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
+             xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
+             xmlns:ui="clr-namespace:HMI.UI;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"
+             Padding="5"
+             Background="#FFF"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <Grid>
+        <StackPanel>
+            <pu:FormGroup Header="寰幆" Orientation="Vertical" 
+                          Style="{StaticResource InputFormGroupStyle}">
+                <ComboBox x:Name="CbLoopType" Style="{StaticResource FormComboxStyle}" SelectedValue="{Binding LoopType}"
+                          DisplayMemberPath="DisplayName" SelectedValuePath="DataValue"/>
+            </pu:FormGroup>
+
+            <pu:FormGroup Header="寰幆娆℃暟" Orientation="Vertical" 
+                          Style="{StaticResource InputFormGroupStyle}">
+                <ui:NumberInput Style="{StaticResource EditorTextBoxStyle}" Text="{Binding LoopCount}" />
+            </pu:FormGroup>
+
+            <pu:FormGroup Header="缁撴灉鍒ゆ柇" Orientation="Vertical">
+                <pu:FormGroup.Style>
+                    <Style TargetType="pu:FormGroup" BasedOn="{StaticResource InputFormGroupStyle}">
+                        <Setter Property="Visibility" Value="Collapsed"/>
+                        <Style.Triggers>
+                            <DataTrigger Binding="{Binding LoopType}" Value="FixedNumLoops">
+                                <Setter Property="Visibility" Value="Visible"/>
+                            </DataTrigger>
+                        </Style.Triggers>
+                    </Style>
+                </pu:FormGroup.Style>
+                <Border Padding="2" BorderThickness="1" BorderBrush="Gray" Background="#e8e8e8">
+                    <Grid>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="Auto"/>
+                            <ColumnDefinition/>
+                            <ColumnDefinition/>
+                            <ColumnDefinition Width="Auto"/>
+                        </Grid.ColumnDefinitions>
+                        <TextBlock Grid.Column="0" Text="澶辫触鐜�" Margin="0,0,5,0" VerticalAlignment="Center"/>
+                        <ComboBox Grid.Column="1" x:Name="CBLoopCondition" 
+                              pu:ComboBoxHelper.ClearButtonVisibility="Collapsed"
+                              Style="{StaticResource FormComboxStyle}" SelectedValue="{Binding LoopCondition}"
+DisplayMemberPath="DisplayName" SelectedValuePath="DataValue"/>
+                        <ui:NumberInput Grid.Column="2" Style="{StaticResource EditorTextBoxStyle}" Text="{Binding LoopMax}" />
+                        <TextBlock Grid.Column="3" Text="%娴嬭瘯PASS" VerticalAlignment="Center"/>
+                    </Grid>
+                </Border>
+            </pu:FormGroup>
+
+            <pu:FormGroup Header="缁撴灉鍒ゆ柇" Orientation="Vertical">
+                <pu:FormGroup.Style>
+                    <Style TargetType="pu:FormGroup" BasedOn="{StaticResource InputFormGroupStyle}">
+                        <Setter Property="Visibility" Value="Collapsed"/>
+                        <Style.Triggers>
+                            <DataTrigger Binding="{Binding LoopType}" Value="PassFailCount">
+                                <Setter Property="Visibility" Value="Visible"/>
+                            </DataTrigger>
+                        </Style.Triggers>
+                    </Style>
+                </pu:FormGroup.Style>
+                <Border Padding="2" BorderThickness="1" BorderBrush="Gray" Background="#e8e8e8">
+                    <Grid>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition/>
+                            <ColumnDefinition/>
+                            <ColumnDefinition Width="Auto"/>
+                        </Grid.ColumnDefinitions>
+                        <ComboBox Grid.Column="0" x:Name="CBLoopCondition2" 
+                              pu:ComboBoxHelper.ClearButtonVisibility="Collapsed"
+                              Style="{StaticResource FormComboxStyle}" SelectedValue="{Binding LoopCondition}"
+DisplayMemberPath="DisplayName" SelectedValuePath="DataValue"/>
+                        <ui:NumberInput Grid.Column="1" Style="{StaticResource EditorTextBoxStyle}" Text="{Binding LoopMax}" />
+                        <TextBlock Grid.Column="2" Text="娆′箣鍚庣粨鏉熷惊鐜�" VerticalAlignment="Center"/>
+                    </Grid>
+                </Border>
+            </pu:FormGroup>
+
+            <pu:FormGroup Header="璇存槑" Orientation="Vertical" 
+                          Style="{StaticResource InputFormGroupStyle}">
+
+                <TextBox x:Name="TbRemark" IsReadOnly="True" 
+                         Style="{StaticResource EditorTextBoxStyle}" 
+                         Text="{Binding LoopDescription,Mode=OneWay}"/>
+            </pu:FormGroup>
+
+            <!--<pu:FormGroup Header="{DynamicResource se.adapter}" 
+        Orientation="Vertical"
+        Style="{StaticResource InputFormGroupStyle}">
+                <ComboBox x:Name="CbAdapter" Style="{StaticResource FormComboxStyle}" IsEnabled="False" SelectedValue="{Binding Adapter}" 
+                          DisplayMemberPath="DisplayName" SelectedValuePath="DataValue"/>
+            </pu:FormGroup>-->
+        </StackPanel>
+    </Grid>
+</UserControl>
diff --git a/Plugin.SeqEditor/SettingPanel/LoopPanel.xaml.cs b/Plugin.SeqEditor/SettingPanel/LoopPanel.xaml.cs
new file mode 100644
index 0000000..95c660f
--- /dev/null
+++ b/Plugin.SeqEditor/SettingPanel/LoopPanel.xaml.cs
@@ -0,0 +1,60 @@
+锘縰sing CommunityToolkit.Mvvm.ComponentModel;
+using NationalInstruments.TestStand.Interop.API;
+using Plugin.SeqEditor.Entity;
+using Plugin.SeqEditor.Util;
+using System.ComponentModel;
+using System.Windows.Controls;
+
+namespace Plugin.SeqEditor.SettingPanel
+{
+    /// <summary>
+    /// CommonProperty.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    [INotifyPropertyChanged]
+    public partial class LoopPanel : UserControl
+    {
+        public event EventHandler SettingChanged;
+
+        private StepUI stepUI;
+        public StepUI StepUI
+        {
+            get => stepUI;
+            set
+            {
+                this.stepUI = stepUI;
+                this.step = stepUI.step;
+                propertyObject = this.step?.AsPropertyObject();
+                this.DataContext = stepUI;
+            }
+        }
+
+        public Step step;
+        public PropertyObject propertyObject;
+
+        protected void CustomPropertyChanged(string name)
+        {
+            OnPropertyChanged(name);
+            SettingChanged?.Invoke(this, EventArgs.Empty);
+        }
+
+        public LoopPanel()
+        {
+            InitializeComponent();
+            CbLoopType.ItemsSource = LoopTypeData.ItemSource;
+            CBLoopCondition.ItemsSource = LoopConditionData.ItemSource;
+            CBLoopCondition2.ItemsSource = LoopCondition2Data.ItemSource;
+        }
+
+        public LoopPanel(StepUI stepUI)
+        {
+            this.stepUI = stepUI;
+            this.step = stepUI.step;
+            propertyObject = this.step?.AsPropertyObject();
+            this.DataContext = stepUI;
+            InitializeComponent();
+            CbLoopType.ItemsSource = LoopTypeData.ItemSource;
+            CBLoopCondition.ItemsSource = LoopConditionData.ItemSource;
+            CBLoopCondition2.ItemsSource = LoopCondition2Data.ItemSource;
+        }
+    }
+}
diff --git a/Plugin.SeqEditor/SettingPanel/StringValTestPanel.xaml b/Plugin.SeqEditor/SettingPanel/StringValTestPanel.xaml
index 7a8c260..61fdd55 100644
--- a/Plugin.SeqEditor/SettingPanel/StringValTestPanel.xaml
+++ b/Plugin.SeqEditor/SettingPanel/StringValTestPanel.xaml
@@ -51,7 +51,8 @@
 Orientation="Vertical"
 Style="{StaticResource InputFormGroupStyle}">
 
-            <TextBox IsReadOnly="{Binding IsReadOnly}" Style="{StaticResource EditorTextBoxStyle}" Text="{Binding StringValue}"/>
+            <TextBox IsReadOnly="{Binding IsReadOnly}" Style="{StaticResource EditorTextBoxStyle}" 
+                     Text="{Binding StringValue,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
         </pu:FormGroup>
     </StackPanel>
 </UserControl>
diff --git a/Plugin.SeqEditor/Util/ItemData.cs b/Plugin.SeqEditor/Util/ItemData.cs
index 2ca8896..29c5b7b 100644
--- a/Plugin.SeqEditor/Util/ItemData.cs
+++ b/Plugin.SeqEditor/Util/ItemData.cs
@@ -107,17 +107,18 @@
 
     public abstract class RunModeData
     {
+        //LangUtil.Get("se.normal")
         public static readonly ItemData Normal =
-            new(LangUtil.Get("se.normal"), "Normal");
+            new("Normal", "Normal");
 
         public static readonly ItemData Skip =
-            new(LangUtil.Get("se.skip"), "Skip");
+            new("Skip", "Skip");
 
         public static readonly ItemData ForcePass =
-            new(LangUtil.Get("se.pass"), "Pass");
+            new("Pass", "Pass");
 
         public static readonly ItemData ForceFail =
-            new(LangUtil.Get("se.fail"), "Fail");
+            new("Fail", "Fail");
 
         public static readonly List<ItemData> ItemSource =
             new List<ItemData> {
@@ -144,4 +145,52 @@
                 LabView,LabWindows,CCPlus,DotNet,ActiveX,None,LabViewNGX,Python,SequenceCall
             };
     }
+
+    public abstract class LoopTypeData
+    {
+        public static readonly ItemData None =
+            new("鏃�", "NoLooping");
+        public static readonly ItemData Fixed =
+            new("鍥哄畾娆℃暟寰幆", "FixedNumLoops");
+        public static readonly ItemData PassFailCount =
+            new("PassFailCount", "PassFailCount");
+
+        public static readonly ItemData Custom =
+            new("鑷畾涔�", "Custom");
+
+        public static readonly List<ItemData> ItemSource =
+            new List<ItemData> {
+                None,Fixed,PassFailCount
+            };
+    }
+
+    public abstract class LoopConditionData
+    {
+        public static readonly ItemData LT =
+            new("<", "<");
+        public static readonly ItemData LE =
+            new("<=", "<=");
+        public static readonly ItemData GT =
+            new(">", ">");
+
+        public static readonly ItemData GE =
+            new(">=", ">=");
+
+        public static readonly List<ItemData> ItemSource =
+            new List<ItemData> {
+                LT,LE, GT, GE
+            };
+    }
+
+    public abstract class LoopCondition2Data
+    {
+        public static readonly ItemData PASS =
+            new("pass", "pass");
+        public static readonly ItemData FAIL =
+            new("fail", "fail");
+        public static readonly List<ItemData> ItemSource =
+            new List<ItemData> {
+                PASS,FAIL
+            };
+    }
 }
diff --git a/Plugin.SeqEditor/ViewModel/SeqEditorViewModel.cs b/Plugin.SeqEditor/ViewModel/SeqEditorViewModel.cs
index 70fcd05..69b5ee8 100644
--- a/Plugin.SeqEditor/ViewModel/SeqEditorViewModel.cs
+++ b/Plugin.SeqEditor/ViewModel/SeqEditorViewModel.cs
@@ -185,6 +185,8 @@
             return steps;
         }
 
+        private int indexCache = 0;
+
         private StepUI Convert2StepUI(int index, Step step, Sequence sequence, string group)
         {
             var ui = new StepUI(step, sequence);
@@ -229,6 +231,10 @@
                     }
                     ui.Children = LoadSteps(sunSequence, group);
                 }
+            }
+            else
+            {
+                ui.CustomIndex = ++indexCache;
             }
             if (CurrentSeqUI != null)
             {
@@ -468,7 +474,7 @@
                 }
                 var newUI = new StepUI(newStep, targetSequence);
 
-                
+
                 targetSequence.InsertStep(newStep, index + 1, group);
                 ObservableCollection<StepUI> datasource = sequenceCache[targetSequence];
                 int uiIndex = datasource.IndexOf(CurrentStepUI);
@@ -488,7 +494,7 @@
                 //    var items = parentItem.Items;
                 //    int innerIndex = items.IndexOf(TreeItemData);
 
-                    
+
                 //    items.Insert(innerIndex + 1, newItem);
                 //    TreeItemDataCollection.ResetSubItems(items);
                 //}
@@ -544,6 +550,7 @@
 
         public void RefreshData()
         {
+            indexCache = 0;
             sequenceCache.Clear();
             AllSteps = LoadSteps(CurrentSeqUI.sequence);
         }
diff --git a/Plugin.TestBuilder/Plugin.TestBuilder.csproj b/Plugin.TestBuilder/Plugin.TestBuilder.csproj
index c8962ac..be3e7b1 100644
--- a/Plugin.TestBuilder/Plugin.TestBuilder.csproj
+++ b/Plugin.TestBuilder/Plugin.TestBuilder.csproj
@@ -129,7 +129,6 @@
     <Compile Include="UI\BottomBar.xaml.cs">
       <DependentUpon>BottomBar.xaml</DependentUpon>
     </Compile>
-    <Compile Include="UI\CanvasHelper.cs" />
     <Compile Include="Container\FunctionSetting.xaml.cs">
       <DependentUpon>FunctionSetting.xaml</DependentUpon>
     </Compile>
diff --git a/Plugin.TestBuilder/UI/InnerWindow.xaml b/Plugin.TestBuilder/UI/InnerWindow.xaml
index 9163890..79c530b 100644
--- a/Plugin.TestBuilder/UI/InnerWindow.xaml
+++ b/Plugin.TestBuilder/UI/InnerWindow.xaml
@@ -1,9 +1,9 @@
-锘�<UserControl x:Class="Plugin.TestBuilder.UI.InnerWindow"
+锘�<UserControl x:Class="HMI.UI.InnerWindow"
              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.TestBuilder.UI"
+             xmlns:local="clr-namespace:HMI.UI"
              mc:Ignorable="d" 
              HorizontalAlignment="Stretch"
              HorizontalContentAlignment="Stretch"
@@ -31,7 +31,7 @@
     </UserControl.Resources>
 
     <UserControl.Template>
-        <ControlTemplate TargetType="UserControl">
+        <ControlTemplate TargetType="local:InnerWindow">
             <AdornerDecorator x:Name="PART_Adorner" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
                 <Grid GotFocus="Grid_GotFocus">
                     <Grid.RowDefinitions>
diff --git a/Plugin.TestBuilder/UI/InnerWindow.xaml.cs b/Plugin.TestBuilder/UI/InnerWindow.xaml.cs
index 5242c2b..54edb24 100644
--- a/Plugin.TestBuilder/UI/InnerWindow.xaml.cs
+++ b/Plugin.TestBuilder/UI/InnerWindow.xaml.cs
@@ -8,7 +8,7 @@
 using System.Windows.Input;
 using System.Windows.Media;
 
-namespace Plugin.TestBuilder.UI
+namespace HMI.UI
 {
     /// <summary>
     /// InnerWindow.xaml 鐨勪氦浜掗�昏緫
diff --git a/Plugin.TestBuilder/UI/Resize.cs b/Plugin.TestBuilder/UI/Resize.cs
index 400e69e..de8812a 100644
--- a/Plugin.TestBuilder/UI/Resize.cs
+++ b/Plugin.TestBuilder/UI/Resize.cs
@@ -1,4 +1,5 @@
-锘縰sing System;
+锘縰sing HMI.UI.DpUtil;
+using System;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Controls.Primitives;
@@ -7,7 +8,7 @@
 using System.Windows.Media;
 using System.Windows.Shapes;
 
-namespace Plugin.TestBuilder.UI
+namespace HMI.UI
 {
     public class Resize
     {
diff --git a/Plugin.TestResult/DataTable.xaml b/Plugin.TestResult/DataTable.xaml
index 473cac1..501ee89 100644
--- a/Plugin.TestResult/DataTable.xaml
+++ b/Plugin.TestResult/DataTable.xaml
@@ -82,24 +82,28 @@
             </StatusBarItem>
 
             <StatusBarItem DockPanel.Dock="Right">
-                <StackPanel Orientation="Horizontal">
-                    <Ellipse Width="10" Height="10">
-                        <Ellipse.Style>
-                            <Style TargetType="Ellipse">
-                                <Setter Property="Fill" Value="LightGray"/>
-                                <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 Orientation="Horizontal" dp:StackPanelHelper.ChildSpace="8">
+                    <Button Style="{StaticResource DeleteLink}" Click="Button_Click_2" Content="鍋滄娴嬭瘯"/>
+
+                    <StackPanel Orientation="Horizontal">
+                        <Ellipse Width="10" Height="10">
+                            <Ellipse.Style>
+                                <Style TargetType="Ellipse">
+                                    <Setter Property="Fill" Value="LightGray"/>
+                                    <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>
                 </StackPanel>
             </StatusBarItem>
 
diff --git a/Plugin.TestResult/DataTable.xaml.cs b/Plugin.TestResult/DataTable.xaml.cs
index 54364f4..84911cd 100644
--- a/Plugin.TestResult/DataTable.xaml.cs
+++ b/Plugin.TestResult/DataTable.xaml.cs
@@ -6,7 +6,6 @@
 using Plugin.TestResult_WPF.Util;
 using SuperSocket.SocketBase.Protocol;
 using System;
-using System.CodeDom.Compiler;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.IO;
@@ -222,6 +221,12 @@
 
         private void ScanWindow_Click(object sender, RoutedEventArgs e)
         {
+            if (!parent.IsRunning)
+            {
+                MsgBoxHelper.WarnMessage(this, "璇峰厛鐐瑰嚮鍙充晶鍚姩鎸夐挳鍚姩娴嬭瘯绋嬪簭锛�", null, MessageBoxButton.OK);
+                return;
+            }
+
             if (scanner != null)
             {
                 scanner.Activate();
@@ -371,7 +376,7 @@
         private void TcpServer_NewRequestReceived(TcpSession session, StringRequestInfo requestInfo)
         {
             string msg = requestInfo.Body;
-            ViewControl.Logger.Info($"TCP Data From {session.RemoteEndPoint}: {msg}.");
+            //ViewControl.Logger.Info($"TCP Data From {session.RemoteEndPoint}: {msg}.");
             string[] datas_10015 = msg.Split(';');
             int cellIndex = int.Parse(datas_10015[1]);
 
@@ -524,6 +529,13 @@
 
         private void ClearAllSnr_Click(object sender, RoutedEventArgs e)
         {
+            var str = $"StationState.Start_{title}";
+            if (parent.engine.Globals.GetValBoolean(str, 0) == true)
+            {
+                MsgBoxHelper.Error(Window.GetWindow(this), "绋嬪簭杩愯涓紝涓嶈兘娓呯┖鏉$爜");
+                return;
+            }
+
             if (MsgBoxHelper.WarnMessage(Window.GetWindow(this), LangUtil.Get("Tr.MakeSureClearAllSNR")) == MessageBoxResult.OK)
             {
                 try
@@ -544,5 +556,13 @@
                 }
             }
         }
+
+        private void Button_Click_2(object sender, RoutedEventArgs e)
+        {
+            if (MsgBoxHelper.WarnMessage(Window.GetWindow(this), "纭畾瑕佸仠姝㈣鐐変綅鐨勮�佸寲娴嬭瘯鍚楋紵") == MessageBoxResult.OK)
+            {
+                parent.SetStartFlag(this.index, false);
+            }
+        }
     }
 }
diff --git a/Plugin.TestResult/Plugin.TestResult.csproj b/Plugin.TestResult/Plugin.TestResult.csproj
index eaf6682..f24e6c6 100644
--- a/Plugin.TestResult/Plugin.TestResult.csproj
+++ b/Plugin.TestResult/Plugin.TestResult.csproj
@@ -277,7 +277,6 @@
       <Generator>ResXFileCodeGenerator</Generator>
       <LastGenOutput>Resources.Designer.cs</LastGenOutput>
     </EmbeddedResource>
-    <None Include="app.config" />
     <None Include="Config\log4net.config" />
     <None Include="Config\log4net.unix.config" />
     <None Include="packages.config" />
@@ -326,6 +325,10 @@
       <Project>{AFEA100A-9223-448D-AB10-3015D93DD98C}</Project>
       <Name>HMI.Util</Name>
     </ProjectReference>
+    <ProjectReference Include="..\Plugin.TsSupport\Plugin.TsSupport.csproj">
+      <Project>{3068124e-f12b-473e-99a0-2da5a0385d05}</Project>
+      <Name>Plugin.TsSupport</Name>
+    </ProjectReference>
   </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')" />
diff --git a/Plugin.TestResult/TestResultView.xaml.cs b/Plugin.TestResult/TestResultView.xaml.cs
index 4f73cfe..f2f148d 100644
--- a/Plugin.TestResult/TestResultView.xaml.cs
+++ b/Plugin.TestResult/TestResultView.xaml.cs
@@ -5,6 +5,7 @@
 using NationalInstruments.TestStand.Utility;
 using Plugin.TestResult_WPF.Constant;
 using Plugin.TestResult_WPF.Model;
+using Plugin.TsSupport;
 using System;
 using System.Collections.Generic;
 using System.Windows;
@@ -46,6 +47,8 @@
 
         }
 
+        public bool IsRunning { get; set; } = false;
+
         public TestResultView(BaseConfig config)
         {
             this.rowCount = config.Row;
@@ -63,10 +66,10 @@
 
 
             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);
+            testApp = TestAppManager.GetAxApplicationMgr();
+            testApp.ExitApplication += this.axApplicationMgr_ExitApplication;
+            testApp.UIMessageEvent += this.axApplicationMgr_UIMessageEvent;
+            testApp.ExecutionClosed += this.axApplicationMgr_ExecutionClosed;
 
 
             ((System.ComponentModel.ISupportInitialize)(this.testApp)).BeginInit();//myClient涓虹涓夋柟鎺т欢鍚�
@@ -385,7 +388,6 @@
                 if (MethodCode == 12)
                 {
                     /// 淇濆瓨娓╁害鏂囦欢
-                    /// 
                     strData = e.uiMsg.StringData;
                     string[] datas = strData.Split(';');
                     int ovenIndex = int.Parse(datas[0]);
@@ -435,14 +437,17 @@
             if (this.SequenceContext == null) return;
             string name = tables[index].title;
             string key = $"StationState.Start_{name}";
-            this.engine.Globals.SetValBoolean(key, 0, data);
-            bool val = this.engine.Globals.GetValBoolean(key, 0);
+            this.engine.Globals.SetValBoolean(key, 1, data);
+            //bool val = this.engine.Globals.GetValBoolean(key, 0);
             //this.SequenceContext.StationGlobals.SetValBoolean(key, 0, data);
             //bool val = this.SequenceContext.StationGlobals.GetValBoolean(key, 0);
         }
 
         public void ReleaseResource()
         {
+            testApp.ExitApplication -= this.axApplicationMgr_ExitApplication;
+            testApp.UIMessageEvent -= this.axApplicationMgr_UIMessageEvent;
+            testApp.ExecutionClosed -= this.axApplicationMgr_ExecutionClosed;
             foreach (var table in tables)
             {
                 table.RelaseResource();
diff --git a/Plugin.TestResult/ViewControl.xaml b/Plugin.TestResult/ViewControl.xaml
index 56db280..48d2036 100644
--- a/Plugin.TestResult/ViewControl.xaml
+++ b/Plugin.TestResult/ViewControl.xaml
@@ -1,4 +1,4 @@
-锘�<hb:PluginControl x:Class="Plugin.TestResult_WPF.ViewControl"
+锘�<hb:PluginMaskControl 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" 
@@ -11,6 +11,7 @@
 xmlns:pu="https://opensource.panuon.com/wpf-ui"
 xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
              mc:Ignorable="d" 
+                      Style="{StaticResource pluginMaskControl}"
              Loaded="UserControl_Loaded"
              d:DesignHeight="450" d:DesignWidth="800">
     <UserControl.Resources>
@@ -42,7 +43,7 @@
             <GroupBox Header="{DynamicResource Tr.ProductInfo}">
                 <ComboBox x:Name="modelSelect" Style="{StaticResource FormComboxStyle}"
                           pu:ComboBoxHelper.ClearButtonVisibility="Collapsed"
-                          SelectionChanged="modelSelect_SelectionChanged"></ComboBox>
+                          SelectionChanged="modelSelect_SelectionChanged"/>
             </GroupBox>
             <GroupBox Header="{DynamicResource Tr.Status}">
                 <StackPanel>
@@ -116,4 +117,4 @@
             </GroupBox>
         </StackPanel>
     </Grid>
-</hb:PluginControl>
+</hb:PluginMaskControl>
diff --git a/Plugin.TestResult/ViewControl.xaml.cs b/Plugin.TestResult/ViewControl.xaml.cs
index f072a88..9c023ab 100644
--- a/Plugin.TestResult/ViewControl.xaml.cs
+++ b/Plugin.TestResult/ViewControl.xaml.cs
@@ -7,7 +7,6 @@
 using log4net;
 using NationalInstruments.TestStand.Interop.API;
 using NationalInstruments.TestStand.Interop.UI.Ax;
-using Plugin.TestResult;
 using Plugin.TestResult_WPF.Model;
 using Plugin.TestResult_WPF.Util;
 using System;
@@ -22,7 +21,7 @@
     /// <summary>
     /// ViewControl.xaml 鐨勪氦浜掗�昏緫
     /// </summary>
-    public partial class ViewControl : PluginControl
+    public partial class ViewControl : PluginMaskControl
     {
         private CommonCodeParser codeParser = new CommonCodeParser();
         private double TestTemperature = 60;
@@ -64,6 +63,20 @@
             Logger = logger;
         }
 
+        private bool isRunning = false;
+        public bool IsRunning
+        {
+            get => isRunning;
+            set
+            {
+                Set(ref isRunning, value);
+                testResultView.IsRunning = value;
+                this.modelSelect.IsEnabled = !value;
+                this.btnStart.IsEnabled = !value;
+                this.btnStop.IsEnabled = value;
+            }
+        }
+
         public ViewControl()
         {
             DataContext = this;
@@ -98,7 +111,13 @@
 
         public override void DoClose(object sender, EventArgs e)
         {
-            testResultView.ReleaseResource();
+            try
+            {
+                testResultView.ReleaseResource();
+            }
+            catch
+            {
+            }
         }
 
         private void axApplicationMgr_ExitApplication(object sender, EventArgs e)
@@ -118,6 +137,15 @@
                 string strData;
 
                 if (EventCase != 10014) return;
+
+                try
+                {
+                    log.Info($"EventCase: {EventCase}, MethodCode: {MethodCode}, StrData: {e.uiMsg.StringData}");
+                }
+                catch
+                {
+
+                }
 
                 if (MethodCode == 0)
                 {
@@ -150,22 +178,41 @@
         private bool isLoaded;
         private void UserControl_Loaded(object sender, RoutedEventArgs e)
         {
-            new MaskWindow(Window.GetWindow(this),
-                (win) =>
+            new MaskWindow(Window.GetWindow(this), (win) =>
+            {
+                try
+                {
+                    Dispatcher.BeginInvoke(new Action(() =>
+                    {
+                        Start_Click(null, null);
+                        Loaded -= UserControl_Loaded;
+                    }));
+                }
+                finally
                 {
                     Dispatcher.Invoke(() =>
                     {
-                        try
-                        {
-                            Start_Click(null, null);
-                            Loaded -= UserControl_Loaded;
-                        }
-                        finally
-                        {
-                            win.Close();
-                        }
+                        win.Close();
                     });
-                }).Info(LangUtil.Get("Ts.SequenceIsStarting"));
+                }
+            }).Info(LangUtil.Get("Ts.SequenceIsStarting"), 100);
+
+            //ShowMask(() =>
+            //{
+            //    Dispatcher.BeginInvoke(new Action(() =>
+            //    {
+            //        System.Threading.Thread.Sleep(100);
+            //        Start_Click(null, null);
+            //        Loaded -= UserControl_Loaded;
+            //    }));
+            //}, (ex) =>
+            //{
+            //    log.Error("Loaded Error.", ex);
+            //    Dispatcher.BeginInvoke(new Action(() =>
+            //    {
+            //        MsgBoxHelper.Error(Window.GetWindow(this), ex.Message);
+            //    }));
+            //}, "璇风◢鍚�", LangUtil.Get("Ts.SequenceIsStarting"));
         }
 
         public void LoadProduct()
@@ -223,9 +270,7 @@
             try
             {
                 testResultView.StartTestSequence();
-                this.modelSelect.IsEnabled = false;
-                this.btnStart.IsEnabled = false;
-                this.btnStop.IsEnabled = true;
+                IsRunning = true;
             }
             catch (Exception ex)
             {
@@ -240,9 +285,7 @@
             try
             {
                 testResultView.StopTest();
-                this.modelSelect.IsEnabled = true;
-                this.btnStart.IsEnabled = true;
-                this.btnStop.IsEnabled = false;
+                IsRunning = false;
             }
             catch (Exception ex)
             {
@@ -342,10 +385,11 @@
 
         private void ScannerSetting_Click(object sender, RoutedEventArgs e)
         {
-            Plugin.TestResult.ScannerSetting setting = new Plugin.TestResult.ScannerSetting() { 
+            Plugin.TestResult.ScannerSetting setting = new Plugin.TestResult.ScannerSetting()
+            {
                 Owner = Window.GetWindow(this)
             };
-            setting.Open(false,"Config");
+            setting.Open(false, "Config");
         }
     }
 }
diff --git a/Plugin.TestResult/app.config b/Plugin.TestResult/app.config
deleted file mode 100644
index 907b804..0000000
--- a/Plugin.TestResult/app.config
+++ /dev/null
@@ -1,15 +0,0 @@
-锘�<?xml version="1.0" encoding="utf-8"?>
-<configuration>
-  <runtime>
-    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
-      <dependentAssembly>
-        <assemblyIdentity name="log4net" publicKeyToken="669e0ddf0bb1aa2a" culture="neutral" />
-        <bindingRedirect oldVersion="0.0.0.0-3.0.4.0" newVersion="3.0.4.0" />
-      </dependentAssembly>
-      <dependentAssembly>
-        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
-        <bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" />
-      </dependentAssembly>
-    </assemblyBinding>
-  </runtime>
-</configuration>
\ No newline at end of file
diff --git a/Plugin.TsExecution/Entity/TestStep.cs b/Plugin.TsExecution/Entity/TestStep.cs
index 72962e8..2a768c6 100644
--- a/Plugin.TsExecution/Entity/TestStep.cs
+++ b/Plugin.TsExecution/Entity/TestStep.cs
@@ -8,6 +8,7 @@
         public string LimitUp { get; set; } 
         public string LimitDown { get; set; } 
         public string Unit { get; set; } 
+        public string ConsumeTime { get; set; } 
         public string Result { get; set; }
     }
 }
diff --git a/Plugin.TsExecution/Plugin.TsExecution.csproj b/Plugin.TsExecution/Plugin.TsExecution.csproj
index cc21afc..eb60ee6 100644
--- a/Plugin.TsExecution/Plugin.TsExecution.csproj
+++ b/Plugin.TsExecution/Plugin.TsExecution.csproj
@@ -180,7 +180,6 @@
       <Generator>ResXFileCodeGenerator</Generator>
       <LastGenOutput>Resources.Designer.cs</LastGenOutput>
     </EmbeddedResource>
-    <None Include="app.config" />
     <None Include="OpenTK.dll.config" />
     <None Include="packages.config" />
     <None Include="Properties\Settings.settings">
@@ -209,6 +208,10 @@
       <Project>{AFEA100A-9223-448D-AB10-3015D93DD98C}</Project>
       <Name>HMI.Util</Name>
     </ProjectReference>
+    <ProjectReference Include="..\SimpleUtil\HMI.MQUtil\HMI.MQUtil.csproj">
+      <Project>{f8b8b6b6-821b-4ff6-b556-9cab840ea57a}</Project>
+      <Name>HMI.MQUtil</Name>
+    </ProjectReference>
   </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <PropertyGroup>
diff --git a/Plugin.TsExecution/SingleControl.xaml b/Plugin.TsExecution/SingleControl.xaml
index 6d87ceb..11d834d 100644
--- a/Plugin.TsExecution/SingleControl.xaml
+++ b/Plugin.TsExecution/SingleControl.xaml
@@ -7,6 +7,7 @@
              xmlns:pw="https://opensource.panuon.com/wpf"
              xmlns:pu="https://opensource.panuon.com/wpf-ui"
              xmlns:local="clr-namespace:Plugin.TsExecution"
+             xmlns:cdp="clr-namespace:HMI.Core.DpUtil;assembly=HMI.Core"
              mc:Ignorable="d" 
              xmlns:sys="clr-namespace:System;assembly=mscorlib"
              Loaded="UserControl_Loaded"
@@ -18,6 +19,7 @@
     <UserControl.Resources>
         <ResourceDictionary>
             <ResourceDictionary.MergedDictionaries>
+                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonListView.xaml"/>
                 <ResourceDictionary Source="/HMI.UI;component/Style/PanuonDataGrid.xaml"/>
             </ResourceDictionary.MergedDictionaries>
             <sys:Double x:Key="nanD">NaN</sys:Double>
@@ -202,6 +204,139 @@
                 <RowDefinition Height="Auto"/>
             </Grid.RowDefinitions>
 
+            <!--<ListView Grid.Row="0" Style="{StaticResource DefaultListTable}" x:Name="table"
+                      ItemsSource="{Binding Datasource}"
+                      VirtualizingPanel.IsContainerVirtualizable="True"
+VirtualizingPanel.IsVirtualizing="True" 
+VirtualizingPanel.VirtualizationMode="Recycling"
+                      pu:ListViewHelper.ItemsHeight="{StaticResource nanD}"
+                      pu:ListViewHelper.ColumnHeaderFontSize="{Binding ActualWidth,
+            ElementName=control,
+Converter={StaticResource fc}, ConverterParameter='15,1093,15,22'}"
+                                  FontSize="{Binding ActualWidth,
+            ElementName=control,
+Converter={StaticResource fc}, ConverterParameter='15,1093,15,20'}"
+                      pu:ListViewHelper.ColumnHeaderHeight="{Binding ActualWidth,ElementName=control,
+Converter={StaticResource fc}, ConverterParameter='30,1093,-1,45'}">
+                <ListView.View>
+                    <GridView AllowsColumnReorder="False">
+                        <GridViewColumn Width="60" Header="#">
+                            <GridViewColumn.CellTemplate>
+                                <DataTemplate>
+                                    <TextBlock Text="{Binding Converter={StaticResource lvRowNumber},
+RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ListViewItem,AncestorLevel=1}}"/>
+                                </DataTemplate>
+                            </GridViewColumn.CellTemplate>
+                        </GridViewColumn>
+
+                        <GridViewColumn Width="200">
+                            <GridViewColumn.Header>
+                                <TextBlock Text="{DynamicResource Test.Name}"/>
+                            </GridViewColumn.Header>
+                            <GridViewColumn.CellTemplate>
+                                <DataTemplate>
+                                    <DockPanel>
+                                        <Border Style="{StaticResource CellBorder}">
+                                            <TextBlock Style="{StaticResource ListTableTextBlock}" 
+                                                       Text="{Binding Name}"/>
+                                        </Border>
+                                    </DockPanel>
+                                </DataTemplate>
+                            </GridViewColumn.CellTemplate>
+                        </GridViewColumn>
+                        <GridViewColumn Width="100">
+                            <GridViewColumn.Header>
+                                <TextBlock Text="{DynamicResource Test.Unit}"/>
+                            </GridViewColumn.Header>
+                            <GridViewColumn.CellTemplate>
+                                <DataTemplate>
+                                    <Grid HorizontalAlignment="Stretch" VerticalAlignment="Center">
+                                    <Border Style="{StaticResource CellBorder}" MouseDown="TextBlock_MouseDown">
+                                        <TextBlock Style="{StaticResource ListTableTextBlock}" 
+                                                   Text="{Binding Unit}"/>
+                                        </Border>
+                                    </Grid>
+                                </DataTemplate>
+                            </GridViewColumn.CellTemplate>
+                        </GridViewColumn>
+                        <GridViewColumn Width="120">
+                            <GridViewColumn.Header>
+                                <TextBlock Text="{DynamicResource Test.Value}"/>
+                            </GridViewColumn.Header>
+                            <GridViewColumn.CellTemplate>
+                                <DataTemplate>
+                                    <Border Style="{StaticResource CellBorder}">
+                                        <TextBlock Style="{StaticResource ListTableTextBlock}" 
+                                                   Text="{Binding Value}"/>
+                                    </Border>
+                                </DataTemplate>
+                            </GridViewColumn.CellTemplate>
+                        </GridViewColumn>
+                        <GridViewColumn Width="120">
+                            <GridViewColumn.Header>
+                                <TextBlock Text="{DynamicResource Test.LimitDown}"/>
+                            </GridViewColumn.Header>
+                            <GridViewColumn.CellTemplate>
+                                <DataTemplate>
+                                    <Border Style="{StaticResource CellBorder}">
+                                        <TextBlock Style="{StaticResource ListTableTextBlock}" 
+                                                   Text="{Binding LimitDown}"/>
+                                    </Border>
+                                </DataTemplate>
+                            </GridViewColumn.CellTemplate>
+                        </GridViewColumn>
+                        <GridViewColumn Width="120">
+                            <GridViewColumn.Header>
+                                <TextBlock Text="{DynamicResource Test.LimitUp}"/>
+                            </GridViewColumn.Header>
+                            <GridViewColumn.CellTemplate>
+                                <DataTemplate>
+                                    <Border Style="{StaticResource CellBorder}">
+                                        <TextBlock Style="{StaticResource ListTableTextBlock}" 
+                                                   Text="{Binding LimitUp}"/>
+                                    </Border>
+                                </DataTemplate>
+                            </GridViewColumn.CellTemplate>
+                        </GridViewColumn>
+                        <GridViewColumn Width="180">
+                            <GridViewColumn.Header>
+                                <TextBlock Text="{DynamicResource Test.ConsumeTime}"/>
+                            </GridViewColumn.Header>
+                            <GridViewColumn.CellTemplate>
+                                <DataTemplate>
+                                    <Border Style="{StaticResource CellBorder}">
+                                        <TextBlock Style="{StaticResource ListTableTextBlock}" 
+                                                   Text="{Binding ConsumeTime}"/>
+                                    </Border>
+                                </DataTemplate>
+                            </GridViewColumn.CellTemplate>
+                        </GridViewColumn>
+                        <GridViewColumn Width="120">
+                            <GridViewColumn.Header>
+                                <TextBlock Text="{DynamicResource Test.Result}"/>
+                            </GridViewColumn.Header>
+                            <GridViewColumn.CellTemplate>
+                                <DataTemplate>
+                                    <Grid x:Name="border" Margin="0">
+                                        <TextBlock x:Name="text" Margin="-1" Padding="0" VerticalAlignment="Center" Text="{Binding Result}"/>
+                                    </Grid>
+                                    <DataTemplate.Triggers>
+                                        <DataTrigger Binding="{Binding Result}" Value="Passed">
+                                            <Setter TargetName="border" Property="Background" Value="{StaticResource passedColor}"/>
+                                            <Setter TargetName="text" Property="Foreground" Value="#000"/>
+                                        </DataTrigger>
+                                        <DataTrigger Binding="{Binding Result}" Value="Failed">
+                                            <Setter TargetName="border" Property="Background" Value="{StaticResource DangerColor}"/>
+                                            <Setter TargetName="text" Property="Foreground" Value="#000"/>
+                                        </DataTrigger>
+                                    </DataTemplate.Triggers>
+                                </DataTemplate>
+                            </GridViewColumn.CellTemplate>
+                        </GridViewColumn>
+                    </GridView>
+                </ListView.View>
+            </ListView>-->
+
             <DataGrid Grid.Row="0" Style="{StaticResource DefaultDataGrid}" 
                       Margin="5,0,5,5"
                   pu:DataGridHelper.ColumnHeaderFontSize="{Binding ActualWidth,
@@ -237,15 +372,6 @@
                 </DataGrid.Resources>
 
                 <DataGrid.Columns>
-                    <!--<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 Header="#" 
                                         ElementStyle="{StaticResource TableTextBlock}"
                         Binding="{Binding Header, RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}, Mode=FindAncestor}}" 
@@ -278,6 +404,12 @@
                                         ElementStyle="{StaticResource TableTextBlock}">
                         <DataGridTextColumn.Header>
                             <TextBlock Text="{DynamicResource Test.LimitUp}"/>
+                        </DataGridTextColumn.Header>
+                    </DataGridTextColumn>
+                    <DataGridTextColumn Width="180" Binding="{Binding ConsumeTime}"
+                    ElementStyle="{StaticResource TableTextBlock}">
+                        <DataGridTextColumn.Header>
+                            <TextBlock Text="{DynamicResource Test.ConsumeTime}"/>
                         </DataGridTextColumn.Header>
                     </DataGridTextColumn>
                     <DataGridTemplateColumn Width="120">
@@ -346,8 +478,10 @@
 VerticalContentAlignment="Center" Content="{Binding ProbeWarn}"/>
                         </StackPanel>
 
-                        <Button FontSize="12" Style="{StaticResource LinkButton}" x:Name="clear" Margin="10,0,0,0" Click="Button_Click" 
-                            Content="{DynamicResource App.Clear}"/>
+                        <Button FontSize="12" Style="{StaticResource LinkButton}" 
+                                cdp:PermissionHelper.RequiredPermission="Permission:ClearProbeCount"
+                                x:Name="clear" Margin="10,0,0,0" Click="Button_Click" 
+                            Content="{DynamicResource App.Clear}" Cursor=""/>
                     </StackPanel>
 
                     <Label x:Name="probeCountAlarm" Visibility="Collapsed" DockPanel.Dock="Left" Padding="30,0" FontSize="12" Foreground="Red"
@@ -358,6 +492,8 @@
         </Grid>
 
         <Canvas Grid.Row="0" Grid.RowSpan="5" Visibility="Collapsed"
+                VirtualizingPanel.IsVirtualizing="False"
+                VirtualizingPanel.VirtualizationMode="Standard"
                 Background="#AAFFFFFF" x:Name="pausePanel" SizeChanged="pausePanel_Loaded">
             <Border BorderBrush="#000" Background="#FFF" BorderThickness="0.5"
                     CornerRadius="5"
@@ -379,10 +515,8 @@
 
                     <TextBlock Margin="20,20,20,0" Grid.Row="1" 
                                FontSize="40"
-                               FontFamily="/Panuon.WPF.UI;component/Resources/Fonts/#PanuonIcon"
-           Foreground="#F9D01A"
-           Text="&#xe931;"
-           />
+                               FontFamily="/Panuon.WPF.UI;component/Resources/Fonts/#PanuonIcon" 
+                               Foreground="#F9D01A" Text="&#xe931;"/>
 
                     <Label Margin="0,20,20,0" Grid.Row="1" Grid.Column="1" Content="{DynamicResource Ts.PauseMsg}"/>
                     <StackPanel Margin="10" Grid.Row="2" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Right">
@@ -399,7 +533,7 @@
             </Border>
         </Canvas>
 
-        <Canvas Grid.Row="0" Grid.RowSpan="5" Visibility="Collapsed"
+        <!--<Canvas Grid.Row="0" Grid.RowSpan="5" Visibility="Collapsed"
         Background="#AAFFFFFF" x:Name="scannerPanel" SizeChanged="pausePanel_Loaded">
             <Border BorderBrush="#000" Background="#FFF" BorderThickness="0.5" CornerRadius="5" x:Name="scannerVisualModal">
                 <Grid>
@@ -435,7 +569,7 @@
                     </StackPanel>
                 </Grid>
             </Border>
-        </Canvas>
+        </Canvas>-->
 
 
         <!--<DockPanel Grid.Row="4">
diff --git a/Plugin.TsExecution/SingleControl.xaml.cs b/Plugin.TsExecution/SingleControl.xaml.cs
index 33afb58..293f3f6 100644
--- a/Plugin.TsExecution/SingleControl.xaml.cs
+++ b/Plugin.TsExecution/SingleControl.xaml.cs
@@ -1,7 +1,5 @@
 锘縰sing HMI.Core.Manager;
 using HMI.Core.Util;
-using HMI.UI;
-using HMI.UI.Util;
 using HMI.Util;
 using NationalInstruments.TestStand.Interop.API;
 using Plugin.TsExecution.Entity;
@@ -10,10 +8,10 @@
 using System.ComponentModel;
 using System.IO;
 using System.Runtime.CompilerServices;
-using System.Threading;
 using System.Timers;
 using System.Windows;
 using System.Windows.Controls;
+using System.Windows.Media;
 
 namespace Plugin.TsExecution
 {
@@ -112,7 +110,8 @@
                 if (value == "Start Timing")
                 {
                     this.refreshTimer.Enabled = true;
-                    TimeNow = DateTime.Now;
+                    TestStartTime = DateTime.Now;
+                    StepStartTime = DateTime.Now;
                 }
                 else
                 {
@@ -136,7 +135,8 @@
             }
         }
 
-        private DateTime TimeNow = new DateTime();
+        private DateTime TestStartTime = new DateTime();
+        private DateTime StepStartTime = new DateTime();
         private TimeSpan TimeCount = new TimeSpan();
 
         private System.Timers.Timer refreshTimer;
@@ -266,7 +266,7 @@
 
             //AddTestData("1", "1", "1", "1", "1", "1", "1");
 
-            TimeCount = DateTime.Now - TimeNow;
+            TimeCount = DateTime.Now - TestStartTime;
             elapseTime = string.Format("{0:00}:{1:00}:{2:00}", TimeCount.Hours, TimeCount.Minutes, TimeCount.Seconds);
             Set("ElapseTime");
             Set("TestInfo");
@@ -297,8 +297,12 @@
                     Unit = unit,
                     Result = Status
                 };
-                Dispatcher.BeginInvoke(new Action(() =>
+                table.Dispatcher.Invoke(new Action(() =>
                 {
+                    var now = DateTime.Now;
+                    var span = now - StepStartTime;
+                    StepStartTime = now;
+                    data.ConsumeTime = $"{span.Seconds}s";
                     Datasource.Add(data);
                     table.ScrollIntoView(data);
                 }));
@@ -506,22 +510,22 @@
         }
         public void ShowScannerPanel()
         {
-            scannerPanel.Visibility = Visibility.Visible;
+            //scannerPanel.Visibility = Visibility.Visible;
         }
 
         private void SendSnr2Ts()
         {
-            try
-            {
-                string snr = TbManualSnr.Text;
-                SequenceFileContext?.StationGlobals.SetValString($"StationState.SN[{index}]", 1, snr);
-                scannerPanel.Visibility = Visibility.Collapsed;
-                TbManualSnr.Text = string.Empty;
-            }
-            catch (Exception ex)
-            {
-                ToastHelper.Error(ex.Message);
-            }
+            //try
+            //{
+            //    string snr = TbManualSnr.Text;
+            //    SequenceFileContext?.StationGlobals.SetValString($"StationState.SN[{index}]", 1, snr);
+            //    scannerPanel.Visibility = Visibility.Collapsed;
+            //    TbManualSnr.Text = string.Empty;
+            //}
+            //catch (Exception ex)
+            //{
+            //    ToastHelper.Error(ex.Message);
+            //}
         }
 
         private void pausePanel_Loaded(object sender, RoutedEventArgs e)
@@ -535,10 +539,18 @@
             }
             else
             {
-                var can = scannerPanel;
-                var modal = scannerVisualModal;
-                Canvas.SetLeft(modal, (scannerPanel.ActualWidth - scannerVisualModal.ActualWidth) / 2);
-                Canvas.SetTop(modal, (scannerPanel.ActualHeight - scannerVisualModal.ActualHeight) / 2);
+                //var can = scannerPanel;
+                //var modal = scannerVisualModal;
+                //Canvas.SetLeft(modal, (scannerPanel.ActualWidth - scannerVisualModal.ActualWidth) / 2);
+                //Canvas.SetTop(modal, (scannerPanel.ActualHeight - scannerVisualModal.ActualHeight) / 2);
+            }
+        }
+
+        private void TextBlock_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            DependencyObject parent = VisualTreeHelper.GetParent((DependencyObject)sender);
+            while (parent != null) {
+                parent = VisualTreeHelper.GetParent(parent);
             }
         }
     }
diff --git a/Plugin.TsExecution/TsExecution.xaml b/Plugin.TsExecution/TsExecution.xaml
index e2dbdfe..07384b6 100644
--- a/Plugin.TsExecution/TsExecution.xaml
+++ b/Plugin.TsExecution/TsExecution.xaml
@@ -11,127 +11,142 @@
          xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
          xmlns:ScottPlot="clr-namespace:ScottPlot.WPF;assembly=ScottPlot.WPF"
          mc:Ignorable="d" 
+                  xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI"
          Padding="10"
          Loaded="PluginControl_Loaded"
          SizeChanged="PluginControl_SizeChanged"
          Background="{StaticResource WorkspaceBackground}"
          d:DesignHeight="450" d:DesignWidth="800">
     <Grid>
-        <Grid.ColumnDefinitions>
-            <ColumnDefinition />
-            <ColumnDefinition Width="Auto"/>
-        </Grid.ColumnDefinitions>
+        <Grid>
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition />
+                <ColumnDefinition Width="Auto"/>
+            </Grid.ColumnDefinitions>
 
-        <Grid.RowDefinitions>
-            <RowDefinition Height="Auto"/>
-            <RowDefinition/>
-        </Grid.RowDefinitions>
-        <WindowsFormsHost x:Name="host" Visibility="Collapsed"></WindowsFormsHost>
-
-        <DockPanel Grid.Row="0" Grid.ColumnSpan="2" LastChildFill="False">
-            <DockPanel.Resources>
-                <Style TargetType="CheckBox">
-                    <Setter Property="FontSize" Value="20"/>
-                    <Setter Property="VerticalContentAlignment" Value="Center"/>
-                </Style>
-            </DockPanel.Resources>
-
-            <StackPanel Orientation="Horizontal" VerticalAlignment="Center" dp:StackPanelHelper.ChildSpace="20">
-                <pu:FormGroup HeaderHeight="Auto" HeaderPadding="0">
-                    <pu:FormGroup.Header>
-                        <TextBlock FontSize="20" Foreground="#000" Text="{DynamicResource Ts.SelectProductModel}"/>
-                    </pu:FormGroup.Header>
-                    <ComboBox Style="{StaticResource FormComboxStyle}" MinWidth="180" ItemsSource="{Binding Products}"
-DisplayMemberPath="Name" SelectedValuePath="Name"
-SelectedItem="{Binding SelectedProduct}" x:Name="CbProduct"/>
-                </pu:FormGroup>
-
-                <CheckBox Grid.Column="1"
-        IsChecked="{Binding EnableMes,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
-        Content="{DynamicResource Ts.EnableMes}"
-        />
-                <CheckBox Grid.Column="2" 
-              IsChecked="{Binding EnableNGPause,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
-              Content="{DynamicResource Ts.EnableNGPause}"
-              />
-                <CheckBox Grid.Column="3" 
-              IsChecked="{Binding EnableDebugMode,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
-              Content="{DynamicResource Ts.DebugMode}"/>
-                <CheckBox Grid.Column="4" 
-IsChecked="{Binding EnableSimpleMode,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
-Content="{DynamicResource Ts.EnableSimpleMode}"/>
-
-                <CheckBox Grid.Column="4" 
-IsChecked="{Binding UI_ShowInfoDock,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
-Content="{DynamicResource Ts.UI_ShowInfoDock}"/>
-
-                <Label FontSize="20" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Padding="0"
-                       Content="{DynamicResource HMI.User.Code}"
-                       />
-                <Label FontSize="20" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Padding="0" 
-                       Content="{Binding CurrentUserCode}" />
-            </StackPanel>
-
-
-
-            <StackPanel DockPanel.Dock="Right" Orientation="Horizontal" dp:StackPanelHelper.ChildSpace="20">
-                <Button Style="{StaticResource SuccessButton}" 
-x:Name="BtnStartSeq" Click="Button_Click" 
-Content="{DynamicResource Ts.BtnStartSeq}"/>
-                <Button Grid.Row="1" Style="{StaticResource DangerButton}" IsEnabled="False"
-x:Name="BtnStopSeq" Click="Button_Click"
-Content="{DynamicResource Ts.BtnStopSeq}"/>
-                <Button Grid.Row="2" Style="{StaticResource PrimaryButton}" 
-x:Name="BtnClearNum" Click="Button_Click"
-Content="{DynamicResource Ts.BtnClearNum}"/>
-                <Button Grid.Row="3"
-x:Name="BtnSample" Click="Button_Click">
-                    <Button.Style>
-                        <Style TargetType="Button" BasedOn="{StaticResource PrimaryButton}">
-                            <Style.Triggers>
-                                <DataTrigger Binding="{Binding SampleStatus}" Value="True">
-                                    <Setter Property="Content" Value="{DynamicResource Ts.BtnIsSample}"/>
-                                </DataTrigger>
-                                <DataTrigger Binding="{Binding SampleStatus}" Value="False">
-                                    <Setter Property="Content" Value="{DynamicResource Ts.BtnNotSample}"/>
-                                </DataTrigger>
-                            </Style.Triggers>
-                        </Style>
-                    </Button.Style>
-                </Button>
-            </StackPanel>
-        </DockPanel>
-
-        <Grid dp:GridHelper.Space="5" Grid.Row="1">
             <Grid.RowDefinitions>
                 <RowDefinition Height="Auto"/>
                 <RowDefinition/>
-                <RowDefinition Height="Auto"/>
             </Grid.RowDefinitions>
+            <WindowsFormsHost x:Name="host" Visibility="Collapsed"></WindowsFormsHost>
+
+            <DockPanel Grid.Row="0" Grid.ColumnSpan="2" LastChildFill="False">
+                <DockPanel.Resources>
+                    <Style TargetType="CheckBox">
+                        <Setter Property="FontSize" Value="20"/>
+                        <Setter Property="VerticalContentAlignment" Value="Center"/>
+                    </Style>
+                </DockPanel.Resources>
+
+                <StackPanel Orientation="Horizontal" VerticalAlignment="Center" dp:StackPanelHelper.ChildSpace="20">
+                    <pu:FormGroup HeaderHeight="Auto" HeaderPadding="0">
+                        <pu:FormGroup.Header>
+                            <TextBlock FontSize="20" Foreground="#000" Text="{DynamicResource Ts.SelectProductModel}"/>
+                        </pu:FormGroup.Header>
+                        <ComboBox Style="{StaticResource FormComboxStyle}" MinWidth="180" ItemsSource="{Binding Products}"
+DisplayMemberPath="Name" SelectedValuePath="Name"
+SelectedItem="{Binding SelectedProduct}" x:Name="CbProduct"/>
+                    </pu:FormGroup>
+
+                    <CheckBox Grid.Column="1"
+        IsChecked="{Binding EnableMes,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
+        Content="{DynamicResource Ts.EnableMes}"
+        />
+                    <CheckBox Grid.Column="2" 
+              IsChecked="{Binding EnableNGPause,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
+              Content="{DynamicResource Ts.EnableNGPause}"
+              />
+                    <CheckBox Grid.Column="3" 
+              IsChecked="{Binding EnableDebugMode,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
+              Content="{DynamicResource Ts.DebugMode}"/>
+                    <CheckBox Grid.Column="4" 
+IsChecked="{Binding EnableSimpleMode,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
+Content="{DynamicResource Ts.EnableSimpleMode}"/>
+
+                    <CheckBox Grid.Column="4" 
+IsChecked="{Binding UI_ShowInfoDock,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
+Content="{DynamicResource Ts.UI_ShowInfoDock}"/>
+
+                    <StackPanel Orientation="Horizontal" dp:StackPanelHelper.ChildSpace="5">
+                        <Label FontSize="20" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Padding="0"
+                       Content="{DynamicResource HMI.User.Code}" />
+                        <Label FontSize="20" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Padding="0" 
+                       Content="{Binding CurrentUserCode}" />
+                    </StackPanel>
 
 
-            <UniformGrid x:Name="panel" Grid.Row="1" Columns="2" dp:UniformGridHelper.InnerBorder="1"
+                    <StackPanel x:Name="workorderPanel" Visibility="Collapsed" Orientation="Horizontal" dp:StackPanelHelper.ChildSpace="5">
+                        <Label FontSize="20" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Padding="0"
+Content="宸ュ崟鍙�"
+/>
+                        <ui:EditBox FontSize="20" x:Name="workOrderEditor" MinWidth="50"
+                                CanEdit="True"
+                                ShowEmpty="True"
+                                PreEditing="RequirePassword"
+                                Value="{Binding CurrentWorkorder,Mode=TwoWay}"/>
+                    </StackPanel>
+                </StackPanel>
+
+
+
+                <StackPanel DockPanel.Dock="Right" Orientation="Horizontal" dp:StackPanelHelper.ChildSpace="20">
+                    <Button Style="{StaticResource SuccessButton}" 
+x:Name="BtnStartSeq" Click="Button_Click" 
+Content="{DynamicResource Ts.BtnStartSeq}"/>
+                    <Button Grid.Row="1" Style="{StaticResource DangerButton}" IsEnabled="False"
+x:Name="BtnStopSeq" Click="Button_Click"
+Content="{DynamicResource Ts.BtnStopSeq}"/>
+                    <Button Grid.Row="2" Style="{StaticResource PrimaryButton}" 
+x:Name="BtnClearNum" Click="Button_Click"
+Content="{DynamicResource Ts.BtnClearNum}"/>
+                    <Button Grid.Row="3"
+x:Name="BtnSample" Click="Button_Click">
+                        <Button.Style>
+                            <Style TargetType="Button" BasedOn="{StaticResource PrimaryButton}">
+                                <Style.Triggers>
+                                    <DataTrigger Binding="{Binding SampleStatus}" Value="True">
+                                        <Setter Property="Content" Value="{DynamicResource Ts.BtnIsSample}"/>
+                                    </DataTrigger>
+                                    <DataTrigger Binding="{Binding SampleStatus}" Value="False">
+                                        <Setter Property="Content" Value="{DynamicResource Ts.BtnNotSample}"/>
+                                    </DataTrigger>
+                                </Style.Triggers>
+                            </Style>
+                        </Button.Style>
+                    </Button>
+                </StackPanel>
+            </DockPanel>
+
+            <Grid dp:GridHelper.Space="5" Grid.Row="1">
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition/>
+                    <RowDefinition Height="Auto"/>
+                </Grid.RowDefinitions>
+
+
+                <UniformGrid x:Name="panel" Grid.Row="1" Columns="2" dp:UniformGridHelper.InnerBorder="1"
                          dp:UniformGridHelper.InnerBorderBrush="#3a3a3a">
 
 
-            </UniformGrid>
-            <Border x:Name="alarmPanel" VerticalAlignment="Bottom" Visibility="Visible" Background="Red"/>
-            <!--<Border Grid.Row="1" x:Name="alarmPanel" Visibility="{Binding AlarmVisiable,Converter={StaticResource vbConverter}}" 
+                </UniformGrid>
+                <!--<Border x:Name="alarmPanel" Height="80" VerticalAlignment="Bottom" Visibility="Visible" Background="Red"/>
+                <Border Grid.Row="1" x:Name="alarmPanel" Visibility="{Binding AlarmVisiable,Converter={StaticResource vbConverter}}" 
                      Height="50" Background="Red"/>-->
-        </Grid>
+            </Grid>
 
 
 
-        <Grid Grid.Row="1" Grid.Column="1" x:Name="detailPanel">
-            <Grid.RowDefinitions>
-                <RowDefinition Height="Auto"/>
-                <RowDefinition Height="Auto"/>
-                <RowDefinition Height="Auto"/>
-                <RowDefinition />
-                <RowDefinition Height="Auto"/>
-            </Grid.RowDefinitions>
+            <Grid Grid.Row="1" Grid.Column="1" x:Name="detailPanel">
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition />
+                    <RowDefinition Height="Auto"/>
+                </Grid.RowDefinitions>
 
-            <!--<GroupBox>
+                <!--<GroupBox>
                 <pu:FormGroup Orientation="Vertical" 
                               HeaderHeight="Auto"
                               HeaderPadding="0" Header="{DynamicResource Ts.SelectProductModel}">
@@ -143,57 +158,57 @@
                 </pu:FormGroup>
             </GroupBox>-->
 
-            <GroupBox Grid.Row="2">
-                <StackPanel>
-                    <pu:FormGroup Header="{DynamicResource Ts.MesStatus}" GroupName="Number">
-                        <Label Grid.Row="1">
-                            <Label.Style>
-                                <Style TargetType="Label">
-                                    <Setter Property="Foreground" Value="#FFF"/>
-                                    <Setter Property="HorizontalContentAlignment" Value="Center"/>
-                                    <Setter Property="FontSize" Value="16"/>
-                                    <Setter Property="FontWeight" Value="Bold"/>
-                                    <Style.Triggers>
-                                        <DataTrigger Binding="{Binding EnableMes}" Value="True">
-                                            <Setter Property="Background" Value="Green"/>
-                                            <Setter Property="Content" Value="{DynamicResource Ts.MesStatus.Enable}"/>
-                                        </DataTrigger>
-                                        <DataTrigger Binding="{Binding EnableMes}" Value="False">
-                                            <Setter Property="Background" Value="Red"/>
-                                            <Setter Property="Content" Value="{DynamicResource Ts.MesStatus.Disable}"/>
-                                        </DataTrigger>
-                                    </Style.Triggers>
-                                </Style>
-                            </Label.Style>
-                        </Label>
-                    </pu:FormGroup>
-                    <pu:FormGroup Header="{DynamicResource Ts.Total}" GroupName="Number">
-                        <TextBox Style="{StaticResource EditorTextBoxStyle}"  IsReadOnly="True"
+                <GroupBox Grid.Row="2">
+                    <StackPanel>
+                        <pu:FormGroup Header="{DynamicResource Ts.MesStatus}" GroupName="Number">
+                            <Label Grid.Row="1">
+                                <Label.Style>
+                                    <Style TargetType="Label">
+                                        <Setter Property="Foreground" Value="#FFF"/>
+                                        <Setter Property="HorizontalContentAlignment" Value="Center"/>
+                                        <Setter Property="FontSize" Value="16"/>
+                                        <Setter Property="FontWeight" Value="Bold"/>
+                                        <Style.Triggers>
+                                            <DataTrigger Binding="{Binding EnableMes}" Value="True">
+                                                <Setter Property="Background" Value="Green"/>
+                                                <Setter Property="Content" Value="{DynamicResource Ts.MesStatus.Enable}"/>
+                                            </DataTrigger>
+                                            <DataTrigger Binding="{Binding EnableMes}" Value="False">
+                                                <Setter Property="Background" Value="Red"/>
+                                                <Setter Property="Content" Value="{DynamicResource Ts.MesStatus.Disable}"/>
+                                            </DataTrigger>
+                                        </Style.Triggers>
+                                    </Style>
+                                </Label.Style>
+                            </Label>
+                        </pu:FormGroup>
+                        <pu:FormGroup Header="{DynamicResource Ts.Total}" GroupName="Number">
+                            <TextBox Style="{StaticResource EditorTextBoxStyle}"  IsReadOnly="True"
                  x:Name="TbTotal"/>
-                    </pu:FormGroup>
-                    <pu:FormGroup Header="{DynamicResource Ts.Pass}" GroupName="Number">
-                        <TextBox Style="{StaticResource EditorTextBoxStyle}" IsReadOnly="True"
+                        </pu:FormGroup>
+                        <pu:FormGroup Header="{DynamicResource Ts.Pass}" GroupName="Number">
+                            <TextBox Style="{StaticResource EditorTextBoxStyle}" IsReadOnly="True"
                  x:Name="TbPass"/>
-                    </pu:FormGroup>
-                    <pu:FormGroup Header="{DynamicResource Ts.Fail}" GroupName="Number">
-                        <TextBox Style="{StaticResource EditorTextBoxStyle}" IsReadOnly="True"
+                        </pu:FormGroup>
+                        <pu:FormGroup Header="{DynamicResource Ts.Fail}" GroupName="Number">
+                            <TextBox Style="{StaticResource EditorTextBoxStyle}" IsReadOnly="True"
                  x:Name="TbFail"/>
-                    </pu:FormGroup>
-                    <pu:FormGroup Header="{DynamicResource Ts.PassRate}" GroupName="Number">
-                        <TextBox Style="{StaticResource EditorTextBoxStyle}" IsReadOnly="True"
+                        </pu:FormGroup>
+                        <pu:FormGroup Header="{DynamicResource Ts.PassRate}" GroupName="Number">
+                            <TextBox Style="{StaticResource EditorTextBoxStyle}" IsReadOnly="True"
                  x:Name="TbPassRate"/>
-                    </pu:FormGroup>
-                </StackPanel>
-            </GroupBox>
+                        </pu:FormGroup>
+                    </StackPanel>
+                </GroupBox>
 
-            <GroupBox Grid.Row="3">
-                <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
-                    <ScottPlot:WpfPlot Background="Transparent" x:Name="pieChart" MinWidth="300" MinHeight="300"
+                <GroupBox Grid.Row="3">
+                    <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
+                        <ScottPlot:WpfPlot Background="Transparent" x:Name="pieChart" MinWidth="300" MinHeight="300"
                    SizeChanged="PluginControl_SizeChanged"/>
-                </ScrollViewer>
-            </GroupBox>
+                    </ScrollViewer>
+                </GroupBox>
 
-            <!-- <GroupBox Grid.Row="4">
+                <!-- <GroupBox Grid.Row="4">
                 <Grid dp:GridHelper.Space="20">
                     <Grid.RowDefinitions>
                         <RowDefinition/>
@@ -255,7 +270,7 @@
                     </pu:Switch>
                 </Grid>
         </GroupBox> -->
+            </Grid>
         </Grid>
-
     </Grid>
 </hb:PluginControl>
diff --git a/Plugin.TsExecution/TsExecution.xaml.cs b/Plugin.TsExecution/TsExecution.xaml.cs
index 89c3ac3..724fe97 100644
--- a/Plugin.TsExecution/TsExecution.xaml.cs
+++ b/Plugin.TsExecution/TsExecution.xaml.cs
@@ -1,9 +1,12 @@
 锘縰sing HMI.Base;
 using HMI.Core;
+using HMI.Core.Api;
 using HMI.Core.Entity;
 using HMI.Core.Manager;
 using HMI.Core.Util;
+using HMI.MQUtil;
 using HMI.UI;
+using HMI.UI.Controls;
 using HMI.UI.Util;
 using HMI.Util;
 using log4net;
@@ -22,6 +25,7 @@
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Forms.DataVisualization.Charting;
+using System.Windows.Media;
 
 namespace Plugin.TsExecution
 {
@@ -90,6 +94,8 @@
 
         private ProductData selectedProduct;
 
+        private MSMQHelper mSMQHelper = new MSMQHelper("HMI_Alarm");
+
         public ProductData SelectedProduct
         {
             get { return selectedProduct; }
@@ -119,6 +125,7 @@
 
         private Pie pie;
         private string CountIniPath;
+        private bool IsOptRequirePassword;
 
         private bool alarmVisiable;
         public bool AlarmVisiable
@@ -136,9 +143,12 @@
             get => _enableMes;
             set
             {
-                if (WriteConfig("EnableMes", value))
+                if (RequirePassword() == true)
                 {
-                    Set(ref _enableMes, value);
+                    if (WriteConfig("EnableMes", value))
+                    {
+                        Set(ref _enableMes, value);
+                    }
                 }
             }
         }
@@ -162,9 +172,12 @@
             get => _enableDebugMode;
             set
             {
-                if (WriteConfig("EnableDebugMode", value))
+                if (RequirePassword() == true)
                 {
-                    Set(ref _enableDebugMode, value);
+                    if (WriteConfig("EnableDebugMode", value))
+                    {
+                        Set(ref _enableDebugMode, value);
+                    }
                 }
             }
         }
@@ -175,9 +188,12 @@
             get => _enableNGPause;
             set
             {
-                if (WriteConfig("EnableNGPause", value))
+                if (RequirePassword() == true)
                 {
-                    Set(ref _enableNGPause, value);
+                    if (WriteConfig("EnableNGPause", value))
+                    {
+                        Set(ref _enableNGPause, value);
+                    }
                 }
             }
         }
@@ -210,6 +226,34 @@
             }
         }
 
+        private bool autoExecuteSeq;
+        public bool AutoExecuteSeq
+        {
+            get => autoExecuteSeq;
+            set
+            {
+                Set(ref autoExecuteSeq, value);
+            }
+        }
+
+        private string currentWorkorder;
+        public string CurrentWorkorder
+        {
+            get => currentWorkorder;
+            set
+            {
+                if (WriteIni("UI_CurrentWorkorder", value))
+                {
+                    Set(ref currentWorkorder, value);
+                }
+            }
+        }
+
+        private string dataLogPath;
+        private SqliteHelper sqliteHelper;
+
+        private MQConnector mqConnector;
+
         public TsExecution()
         {
             DataContext = this;
@@ -220,7 +264,6 @@
             //鍒濆鍖朤estStand寮曟搸
             this.InitTestStandEngine();
         }
-
         public override void SetBaseFolder(string folder)
         {
             CountIniPath = Path.Combine(folder, "Count.ini");
@@ -232,7 +275,70 @@
             this.ReadCount(out int pass, out int fail);
             this.DisplayCount(pass, fail);
             InitProducts();
+            ReadConfig();
+            if (!File.Exists(dataLogPath))
+            {
+                SqliteHelper.CreateDatabase(dataLogPath);
+            }
+            sqliteHelper = new SqliteHelper(dataLogPath);
+            sqliteHelper.ExecuteSql(@"CREATE TABLE IF NOT EXISTS serialData (
+                    id INTEGER PRIMARY KEY AUTOINCREMENT,
+                    sn TEXT NOT NULL,
+                    station TEXT NOT NULL,
+                    step TEXT NOT NULL,
+                    limitDown TEXT,
+                    limitUp TEXT,
+                    unit TEXT,
+                    offset TEXT,
+                    value TEXT,
+                    status TEXT,
+                    finalStatus TEXT,
+                    consumeTime TEXT, 
+                    totalConsumeTime TEXT, 
+                    time DATETIME NOT NULL);");
             base.InitFinish();
+        }
+
+        private void InitMQ()
+        {
+            mqConnector = new MQConnector("UI");
+            mqConnector.BeginReceive(HandleMQUI);
+        }
+
+        void HandleMQUI(string msg)
+        {
+            string[] arr = msg.Split('|');
+            int eventCode = int.Parse(arr[0]);
+            int index = int.Parse(arr[1]);
+            string strData = arr[2];
+            Dispatcher.BeginInvoke(new Action(() =>
+            {
+                HandleUIMessage(eventCode, index, strData);
+            }));
+        }
+
+        void createChildInNewThread(int index, string configFile, int probeWarn)
+        {
+            HostVisual hostVisual = new HostVisual();
+
+            UIElement content = new VisualHost(hostVisual);
+
+            System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(() =>
+            {
+                VisualTarget visualTarget = new VisualTarget(hostVisual);
+
+                var control = new SingleControl(index, configFile, probeWarn);
+                control.Arrange(new Rect(new Point(), content.RenderSize));
+
+                visualTarget.RootVisual = control;
+                tsSockets.Add(control);
+                System.Windows.Threading.Dispatcher.Run();
+            }));
+            //panel.Children.Add(content);
+
+            thread.SetApartmentState(System.Threading.ApartmentState.STA);
+            thread.IsBackground = true;
+            thread.Start();
         }
 
         /// <summary>
@@ -242,7 +348,7 @@
         {
             pieChart.Plot.Axes.Frameless();
             pieChart.Plot.HideGrid();
-            pieChart.Plot.DataBackground.Color = Colors.Transparent;
+            pieChart.Plot.DataBackground.Color = ScottPlot.Colors.Transparent;
 
             var legend = pieChart.Plot.ShowLegend(Alignment.UpperRight);
             legend.Alignment = Alignment.UpperRight;
@@ -252,15 +358,25 @@
             pieChart.Plot.Font.Automatic();
 
             List<PieSlice> slices = new List<PieSlice> {
-    new PieSlice() { Value = 5, FillColor = Colors.Green,
+    new PieSlice() { Value = 5, FillColor = ScottPlot.Colors.Green,
         LabelFontSize = (float)WidthSizeConverter.CountFontSize(20,300,300),
         Label = "Pass",LegendText="Pass" },
-    new PieSlice() { Value = 2, FillColor = Colors.Red,
+    new PieSlice() { Value = 2, FillColor = ScottPlot.Colors.Red,
         LabelFontSize = (float)WidthSizeConverter.CountFontSize(20,300,300),
         Label = "Fail",LegendText="Fail" } };
 
 
             pie = pieChart.Plot.Add.Pie(slices);
+        }
+
+        private bool? RequirePassword()
+        {
+            if (!IsOptRequirePassword) return true;
+            var win = new RequirePasswordWindow
+            {
+                Owner = Window.GetWindow(this)
+            };
+            return win.ShowDialog();
         }
 
         private void InitProducts()
@@ -315,6 +431,7 @@
         private void InitTsEngine()
         {
             AxApplicationMgr = new AxApplicationMgr();
+            AxApplicationMgr.EndExecution += AxApplicationMgr_EndExecution;
             AxApplicationMgr.ExitApplication += new EventHandler(this.AxApplicationMgr_ExitApplication);
             AxApplicationMgr.UIMessageEvent +=
                 new _ApplicationMgrEvents_UIMessageEventEventHandler(this.AxApplicationMgr_UIMessageEvent);
@@ -323,6 +440,11 @@
             ((System.ComponentModel.ISupportInitialize)(AxApplicationMgr)).BeginInit();
             host.Child = AxApplicationMgr;
             ((System.ComponentModel.ISupportInitialize)(AxApplicationMgr)).EndInit();
+        }
+
+        private void AxApplicationMgr_EndExecution(object sender, _ApplicationMgrEvents_EndExecutionEvent e)
+        {
+            ReleaseSequenceFile();
         }
 
         private void PluginControl_Loaded(object sender, RoutedEventArgs e)
@@ -343,10 +465,9 @@
                             {
                                 log.Warn("Get Login User Failed.", ex);
                             }
-                            ReadConfig();
                         });
                         isLoadOK = true;
-                        StartSequence();
+                        if (AutoExecuteSeq) StartSequence();
                     }
                     catch (Exception ex)
                     {
@@ -369,25 +490,6 @@
                     {
                         if (isLoadOK) Loaded -= PluginControl_Loaded;
                     });
-
-                    //Dispatcher.Invoke(() =>
-                    //{
-                    //    try
-                    //    {
-                    //        ReadConfig();
-                    //        //杩炴帴OPChandle
-                    //        //this.OpcHandle.Connect2("127.0.0.1", 6543, 18081, out string ErrMsg, null);
-                    //        isLoadOK = true;
-                    //        //鍚姩娴嬭瘯杩涚▼
-                    //        StartSequence();
-                    //    }
-                    //    catch (Exception ex)
-                    //    {
-                    //        log.Error("TsExecution Loaded Failed.", ex);
-                    //        MsgBoxHelper.Error(this, $"{LangUtil.Get("Ts.UserControlLoadFailed")}: {ex.Message}");
-                    //    }
-
-                    //});
                 }
                 ).Info(LangUtil.Get("App.UsercontrolIsLoading"));
         }
@@ -415,6 +517,8 @@
                 SingleControl control = new SingleControl(i, CountIniPath, ProbeWarn);
                 panel.Children.Add(control);
                 tsSockets.Add(control);
+
+                //createChildInNewThread(i, CountIniPath, ProbeWarn);
             }
             if (tsSockets.Count == 1)
             {
@@ -537,6 +641,16 @@
 
             try
             {
+                value = ParameterManager.GetCommonParameterByName("AutoExecuteSeq");
+                AutoExecuteSeq = bool.Parse(value.Value);
+            }
+            catch
+            {
+                AutoExecuteSeq = true;
+            }
+
+            try
+            {
 
                 value = ParameterManager.GetCommonParameterByName("TestReportFolder");
                 csvDir = value.Value;
@@ -548,6 +662,55 @@
             catch
             {
                 csvDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TestReport");
+            }
+
+            try
+            {
+
+                value = ParameterManager.GetCommonParameterByName("IsOptRequirePassword");
+                IsOptRequirePassword = bool.Parse(value.Value);
+            }
+            catch
+            {
+                IsOptRequirePassword = false;
+            }
+
+            try
+            {
+
+                value = ParameterManager.GetCommonParameterByName("UI_CurrentWorkorder");
+                currentWorkorder = value.Value;
+                Set("CurrentWorkorder");
+            }
+            catch
+            {
+                currentWorkorder = string.Empty;
+                Set("CurrentWorkorder");
+            }
+
+            try
+            {
+
+                value = ParameterManager.GetCommonParameterByName("TS_ShowTitleWorkorder");
+                var boo = bool.Parse(value.Value);
+                if (boo)
+                {
+                    workorderPanel.Visibility = Visibility.Visible;
+                }
+            }
+            catch
+            {
+            }
+
+            try
+            {
+
+                value = ParameterManager.GetCommonParameterByName("TS_DataLogPath");
+                dataLogPath = value.Value;
+            }
+            catch
+            {
+                dataLogPath = Path.Combine(PathHelper.ConfigFolder, "DataBoard", "DataLog.db");
             }
 
             //Convert.ToBoolean(IniFileHelper.ReadIniData("Config", "EnableMes", "true", CountIniPath));
@@ -577,6 +740,27 @@
                     {
                         ts?.SequenceFileContext?.StationGlobals.SetValBoolean($"SystemSetting.{name}", 1, value);
                     }
+                    return true;
+                }
+                else
+                {
+                    throw new Exception(errMsg);
+                }
+            }
+            catch (Exception ex)
+            {
+                MsgBoxHelper2.Error(ex.Message);
+                return false;
+            }
+        }
+
+        private bool WriteIni(string name, object value)
+        {
+            //IniFileHelper.WriteIniData("Config", name, value.ToString(), CountIniPath);
+            try
+            {
+                if (ParameterManager.UpdateCommonParameter(name, value, out string errMsg))
+                {
                     return true;
                 }
                 else
@@ -664,10 +848,6 @@
                 engine?.CommitGlobalsToDisk(promptOnSaveConflicts: false);
                 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);
-
-                //var sb = testSeq.GetSequenceByName("MainSequence");
-                //sb.
-
                 this.execution = this.engine.NewExecution(this.testSeq, "Test UUTs", this.modeSeq, false, 0, null, null, null);
                 IsTesting = true;
             }
@@ -706,10 +886,6 @@
                                 win.Close();
                             });
                         }
-                        //Dispatcher.Invoke(() =>
-                        //{
-
-                        //});
                     }
                     ).Info(LangUtil.Get("App.SequenceIsStopping"));
         }
@@ -736,7 +912,9 @@
                     }
                 }
                 if (this.execution == null) return true;
-                return this.execution.WaitForEndEx(2000, true, null, null);
+
+                bool res = this.execution.WaitForEndEx(2000, true, null, null);
+                return res;
             }
             catch (Exception ex)
             {
@@ -805,21 +983,7 @@
         /// <param name="e"></param>
         private void AxApplicationMgr_ExecutionClosed(object sender, _ApplicationMgrEvents_ExecutionClosedEvent e)
         {
-            try
-            {
-                if (this.testSeq == null)
-                    return;
-                this.engine.ReleaseSequenceFileEx(this.testSeq, 0);
-                this.engine.ReleaseSequenceFileEx(this.modeSeq, 0);
-                this.engine.UnloadAllModules();
-                this.testSeq = null;
-                this.modeSeq = null;
-            }
-            catch (Exception ex)
-            {
-                log.Error("ReleaseSequenceFileEx Failed.", ex);
-                MsgBoxHelper.Error(Window.GetWindow(this), $"{LangUtil.Get("Ts.ReleaseSequenceFileFail")}: {ex.Message}");
-            }
+            ReleaseSequenceFile();
         }
 
         public void UpdatePositionStatus(int index, string status)
@@ -859,6 +1023,125 @@
             this.tsSockets[index].WriteToCSV(csvPath);
         }
 
+        public void SavePositionDB(int index)
+        {
+            var socket = this.tsSockets[index];
+            var time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+            string snr = socket.Barcode;
+
+            List<string> sqls = new List<string>();
+            foreach (var data in socket.Datasource)
+            {
+                sqls.Add($@"INSERT INTO serialData (sn,station,step,limitDown,limitUp,unit,value,status,finalStatus,consumeTime,totalConsumeTime,time) VALUES
+('{snr}','','{data.Name}','{data.LimitDown}','{data.LimitUp}','{data.Unit}','{data.Value}','{data.Result}','{socket.Status}','{data.ConsumeTime}','{socket.ElapseTime}','{time}')");
+            }
+            sqliteHelper.ExecuteBatchSql(sqls);
+        }
+
+        private void HandleUIMessage(int eventCode, int index, string strData)
+        {
+            switch (eventCode)
+            {
+                case 10002://鏇存柊娴嬭瘯鐘舵��
+                    string status = strData;
+                    UpdatePositionStatus(index, status);
+                    break;
+
+                case 10003://鍙戦�佹祴璇曟暟鎹�
+                    AddTestData(index, strData);
+                    break;
+
+                case 10004://鍙戦�佹祴璇曡繃绋嬩俊鎭�
+                    this.tsSockets[index].CurrentStep = strData;
+                    break;
+
+                case 10005://鍒濆鍖栨祴璇曟暟鎹紝娓呯┖娴嬭瘯鏁版嵁
+                    this.tsSockets[index].IniTestData();
+                    break;
+
+                case 10006://鍙戦�佷骇鍝佹潯鐮�
+                    SetPositionBarcode(index, strData);
+                    break;
+
+                case 10007://寮�濮嬫祴璇曡鏃�
+                    tsSockets[index].ElapseTime = "Start Timing";
+                    BtnStopSeq.IsEnabled = false;
+                    break;
+
+                case 10008://鍋滄娴嬭瘯璁℃椂
+                    tsSockets[index].ElapseTime = "Stop Timing";
+                    BtnStopSeq.IsEnabled = true;
+                    break;
+
+                case 10009:
+                    // 
+                    break;
+
+                case 10010:
+
+                    //PropertyObj.SetValBoolean("Locals.MD5_Used", 0, true);
+                    //PropertyObj.SetValVariant("FileGlobals.MD5_Value", 0, MD5_DATA);
+                    //PropertyObj.SetValVariant("StationGlobals.MD5", 0, MD5_DATA);
+                    break;
+
+                case 10011:
+                    // write model
+
+                    break;
+
+                case 10012:
+                    // read model
+
+                    break;
+
+                case 10013:
+                    // read continue
+
+
+                    break;
+
+                case 10014:
+
+                    break;
+
+                case 10015:
+
+                    break;
+                case 10016://鏄剧ず3D鎷兼帴鍥剧墖
+
+                    break;
+                case 10017://鏇存柊娴嬭瘯璁℃椂锛岄拡瀵归儴鍒嗚鏃舵湁PLC瀹屾垚鐨勯」鐩�
+                    this.tsSockets[index].ElapseTime = strData;
+                    break;
+                case (int)UIMessageCodes.UIMsg_EndExecution://鎵ц缁撴潫
+                    IsTesting = false;
+                    //this.ReleaseSequenceFile();
+                    break;
+                case 10030://鏄剧ず娴嬭瘯杩涘害
+                    int ProcessVal = Convert.ToInt32(strData);
+                    this.tsSockets[index].SetTestProcess(ProcessVal);
+                    break;
+                case 10031://瀵煎嚭CSV
+                    SavePositionCSV(index);
+                    SavePositionDB(index);
+                    break;
+                case 10032://鎸変笅鏍囨殏鍋滄祴璇�
+                    this.tsSockets[index].ShowPausePanel();
+                    break;
+                case 10033:
+                    ShowAlarm(strData);
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        private void ShowAlarm(string info)
+        {
+            AlarmShowHelper.ShowAlarm(info);
+            mSMQHelper.Send("Alarm", $"ATE娴嬭瘯 {info}");
+        }
+
         /// <summary>
         /// TestStand鐢ㄦ埛浜や簰浜嬩欢
         /// </summary>
@@ -868,113 +1151,24 @@
         {
             try
             {
-                int EventCase = Convert.ToInt32(e.uiMsg.Event);
-                int Index = Convert.ToInt16(e.uiMsg.NumericData);//SocketIndex
+                int eventCode = Convert.ToInt32(e.uiMsg.Event);
+                int index = Convert.ToInt16(e.uiMsg.NumericData);//SocketIndex
                 string strData = e.uiMsg.StringData;
 
                 try
                 {
-                    log.Info($"Event: {EventCase}, NumericData: {Index}, StringData: {strData}");
+                    log.Info($"Event: {eventCode}, NumericData: {index}, StringData: {strData}");
                 }
                 catch
                 {
 
                 }
-
-                switch (EventCase)
+                if (eventCode == 10001)
                 {
-                    case 10001://浼犻�扴ocketContext
-                        tsSockets[Index].SequenceFileContext = (SequenceContext)e.uiMsg.ActiveXData;
-                        break;
-
-                    case 10002://鏇存柊娴嬭瘯鐘舵��
-                        string status = e.uiMsg.StringData;
-                        UpdatePositionStatus(Index, status);
-                        break;
-
-                    case 10003://鍙戦�佹祴璇曟暟鎹�
-                        AddTestData(Index, e.uiMsg.StringData);
-                        break;
-
-                    case 10004://鍙戦�佹祴璇曡繃绋嬩俊鎭�
-                        this.tsSockets[Index].CurrentStep = e.uiMsg.StringData;
-                        break;
-
-                    case 10005://鍒濆鍖栨祴璇曟暟鎹紝娓呯┖娴嬭瘯鏁版嵁
-                        this.tsSockets[Index].IniTestData();
-                        break;
-
-                    case 10006://鍙戦�佷骇鍝佹潯鐮�
-                        SetPositionBarcode(Index, e.uiMsg.StringData);
-                        break;
-
-                    case 10007://寮�濮嬫祴璇曡鏃�
-                        tsSockets[Index].ElapseTime = "Start Timing";
-                        BtnStopSeq.IsEnabled = false;
-                        break;
-
-                    case 10008://鍋滄娴嬭瘯璁℃椂
-                        tsSockets[Index].ElapseTime = "Stop Timing";
-                        BtnStopSeq.IsEnabled = true;
-                        break;
-
-                    case 10009:
-                        // 
-                        break;
-
-                    case 10010:
-
-                        //PropertyObj.SetValBoolean("Locals.MD5_Used", 0, true);
-                        //PropertyObj.SetValVariant("FileGlobals.MD5_Value", 0, MD5_DATA);
-                        //PropertyObj.SetValVariant("StationGlobals.MD5", 0, MD5_DATA);
-                        break;
-
-                    case 10011:
-                        // write model
-
-                        break;
-
-                    case 10012:
-                        // read model
-
-                        break;
-
-                    case 10013:
-                        // read continue
-
-
-                        break;
-
-                    case 10014:
-
-                        break;
-
-                    case 10015:
-
-                        break;
-                    case 10016://鏄剧ず3D鎷兼帴鍥剧墖
-
-                        break;
-                    case 10017://鏇存柊娴嬭瘯璁℃椂锛岄拡瀵归儴鍒嗚鏃舵湁PLC瀹屾垚鐨勯」鐩�
-                        this.tsSockets[Index].ElapseTime = e.uiMsg.StringData;
-                        break;
-                    case (int)UIMessageCodes.UIMsg_EndExecution://鎵ц缁撴潫
-                        IsTesting = false;
-                        // this.ReleaseSequenceFile();
-                        break;
-                    case 10030://鏄剧ず娴嬭瘯杩涘害
-                        int ProcessVal = Convert.ToInt32(e.uiMsg.StringData);
-                        this.tsSockets[Index].SetTestProcess(ProcessVal);
-                        break;
-                    case 10031://瀵煎嚭CSV
-                        SavePositionCSV(Index);
-                        break;
-                    case 10032://鎸変笅鏍囨殏鍋滄祴璇�
-                        this.tsSockets[Index].ShowPausePanel();
-                        break;
-                    default:
-                        break;
+                    tsSockets[index].SequenceFileContext = (SequenceContext)e.uiMsg.ActiveXData;
+                    return;
                 }
+                HandleUIMessage(eventCode, index, strData);
             }
             catch (Exception ex)
             {
@@ -1027,13 +1221,6 @@
             try
             {
                 StopSequence();
-                // this.engine?.TerminateAll();
-                // this.engine?.ReleaseSequenceFileEx(this.testSeq, 0);
-                // this.engine?.ReleaseSequenceFileEx(this.modeSeq, 0);
-                // this.engine?.UnloadAllModules();
-                // this.engine?.ShutDown(true);
-                //this.testSeq = null;
-                //this.modeSeq = null;
                 base.OnClose(sender, e);
             }
             catch (Exception ex)
@@ -1042,6 +1229,36 @@
             }
         }
 
+        /// <summary>
+        /// 閲婃斁鎵�鏈夌殑搴忓垪鏂囦欢
+        /// </summary>
+        private bool ReleaseSequenceFile()
+        {
+            try
+            {
+                lock (this)
+                {
+                    if (this.testSeq == null)
+                        return true;
+                    if (this.engine.ReleaseSequenceFileEx(this.testSeq, 4))
+                    {
+                        this.testSeq = null;
+                    }
+                    if (this.engine.ReleaseSequenceFileEx(this.modeSeq, 4))
+                    {
+                        this.modeSeq = null;
+                    }
+                    this.engine.UnloadAllModules();
+                    return true;
+                }
+            }
+            catch (Exception ex)
+            {
+                log.Warn("ReleaseSequenceFile Error.", ex);
+                return false;
+            }
+        }
+
         private void PluginControl_SizeChanged(object sender, SizeChangedEventArgs e)
         {
 
diff --git a/Plugin.TsExecution/app.config b/Plugin.TsExecution/app.config
deleted file mode 100644
index a0cefc2..0000000
--- a/Plugin.TsExecution/app.config
+++ /dev/null
@@ -1,11 +0,0 @@
-锘�<?xml version="1.0" encoding="utf-8"?>
-<configuration>
-  <runtime>
-    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
-      <dependentAssembly>
-        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
-        <bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" />
-      </dependentAssembly>
-    </assemblyBinding>
-  </runtime>
-</configuration>
\ No newline at end of file
diff --git a/Plugin.TsSupport/Plugin.TsSupport.csproj b/Plugin.TsSupport/Plugin.TsSupport.csproj
new file mode 100644
index 0000000..7b4a211
--- /dev/null
+++ b/Plugin.TsSupport/Plugin.TsSupport.csproj
@@ -0,0 +1,58 @@
+<?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>{3068124E-F12B-473E-99A0-2DA5A0385D05}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>Plugin.TsSupport</RootNamespace>
+    <AssemblyName>Plugin.TsSupport</AssemblyName>
+    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <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>
+  <ItemGroup>
+    <Reference Include="NationalInstruments.TestStand.Interop.API, Version=20.0.0.49158, Culture=neutral, PublicKeyToken=ad9244eb3b825cd8, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <EmbedInteropTypes>True</EmbedInteropTypes>
+      <HintPath>..\..\..\..\Program Files\National Instruments\TestStand 2020\API\DotNET\Assemblies\CurrentVersion\NationalInstruments.TestStand.Interop.API.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>..\..\..\..\Program Files\National Instruments\TestStand 2020\API\DotNET\Assemblies\CurrentVersion\NationalInstruments.TestStand.Interop.UI.AxControls.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="TestAppManager.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>
\ No newline at end of file
diff --git a/Plugin.TsSupport/Properties/AssemblyInfo.cs b/Plugin.TsSupport/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..427f8ed
--- /dev/null
+++ b/Plugin.TsSupport/Properties/AssemblyInfo.cs
@@ -0,0 +1,33 @@
+锘縰sing System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// 鏈夊叧绋嬪簭闆嗙殑涓�鑸俊鎭敱浠ヤ笅
+// 鎺у埗銆傛洿鏀硅繖浜涚壒鎬у�煎彲淇敼
+// 涓庣▼搴忛泦鍏宠仈鐨勪俊鎭��
+[assembly: AssemblyTitle("Plugin.TsSupport")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Plugin.TsSupport")]
+[assembly: AssemblyCopyright("Copyright 漏  2025")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 灏� ComVisible 璁剧疆涓� false 浼氫娇姝ょ▼搴忛泦涓殑绫诲瀷
+//瀵� COM 缁勪欢涓嶅彲瑙併�傚鏋滈渶瑕佷粠 COM 璁块棶姝ょ▼搴忛泦涓殑绫诲瀷
+//璇峰皢姝ょ被鍨嬬殑 ComVisible 鐗规�ц缃负 true銆�
+[assembly: ComVisible(false)]
+
+// 濡傛灉姝ら」鐩悜 COM 鍏紑锛屽垯涓嬪垪 GUID 鐢ㄤ簬绫诲瀷搴撶殑 ID
+[assembly: Guid("3068124e-f12b-473e-99a0-2da5a0385d05")]
+
+// 绋嬪簭闆嗙殑鐗堟湰淇℃伅鐢变笅鍒楀洓涓�肩粍鎴�: 
+//
+//      涓荤増鏈�
+//      娆$増鏈�
+//      鐢熸垚鍙�
+//      淇鍙�
+//
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Plugin.TsSupport/TestAppManager.cs b/Plugin.TsSupport/TestAppManager.cs
new file mode 100644
index 0000000..aff44ab
--- /dev/null
+++ b/Plugin.TsSupport/TestAppManager.cs
@@ -0,0 +1,17 @@
+锘縰sing NationalInstruments.TestStand.Interop.UI.Ax;
+
+namespace Plugin.TsSupport
+{
+    public class TestAppManager
+    {
+        private static AxApplicationMgr instance;
+        public static AxApplicationMgr GetAxApplicationMgr()
+        {
+            if (instance == null)
+            {
+                instance = new AxApplicationMgr();
+            }
+            return instance;
+        }
+    }
+}
diff --git a/SimpleUtil/HMI.MQUtil/HMI.MQUtil.csproj b/SimpleUtil/HMI.MQUtil/HMI.MQUtil.csproj
new file mode 100644
index 0000000..67d0915
--- /dev/null
+++ b/SimpleUtil/HMI.MQUtil/HMI.MQUtil.csproj
@@ -0,0 +1,49 @@
+<?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>{F8B8B6B6-821B-4FF6-B556-9CAB840EA57A}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>HMI.MQUtil</RootNamespace>
+    <AssemblyName>HMI.MQUtil</AssemblyName>
+    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <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>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Messaging" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="MQConnector.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>
\ No newline at end of file
diff --git a/SimpleUtil/HMI.MQUtil/MQConnector.cs b/SimpleUtil/HMI.MQUtil/MQConnector.cs
new file mode 100644
index 0000000..d93b519
--- /dev/null
+++ b/SimpleUtil/HMI.MQUtil/MQConnector.cs
@@ -0,0 +1,110 @@
+锘縰sing System;
+using System.Messaging;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace HMI.MQUtil
+{
+    public class MQConnector : IDisposable
+    {
+        private MessageQueue myQueue = null;
+
+        public MQConnector(string name)
+        {
+            string mqName = $".\\private$\\{name}";
+            //濡傛灉涓嶅瓨鍦╩qName娑堟伅闃熷垪锛屽垯鍒涘缓璇qName娑堟伅闃熷垪  
+            if (!MessageQueue.Exists(mqName))
+            {
+                MessageQueue.Create(mqName);
+
+                myQueue = new MessageQueue(mqName);
+                myQueue.SetPermissions("Everyone", MessageQueueAccessRights.FullControl, AccessControlEntryType.Allow);    //鍔犳潈闄�
+                myQueue.SetPermissions("ANONYMOUS LOGON", MessageQueueAccessRights.FullControl, AccessControlEntryType.Allow);
+            }
+            else
+            {
+                myQueue = new MessageQueue(mqName);
+            }
+        }
+
+        public MQConnector(string host, string name)
+        {
+            string mqName = $@"FormatName:Direct=TCP:{host}\Private$\{name}";
+            if (!MessageQueue.Exists(mqName))
+            {
+                MessageQueue.Create(mqName);
+
+                myQueue = new MessageQueue(mqName);
+                myQueue.SetPermissions("Everyone", MessageQueueAccessRights.FullControl, AccessControlEntryType.Allow);    //鍔犳潈闄�
+                myQueue.SetPermissions("ANONYMOUS LOGON", MessageQueueAccessRights.FullControl, AccessControlEntryType.Allow);
+            }
+            else
+            {
+                myQueue = new MessageQueue(mqName);
+            }
+        }
+
+        public void ClearAll()
+        {
+            myQueue?.Purge();
+        }
+
+        public void Close()
+        {
+            myQueue?.Close();
+        }
+
+
+
+        ManualResetEvent signal = new ManualResetEvent(false);
+
+        public void BeginReceive(Action<string> action)
+        {
+            Task.Run(() =>
+            {
+                //杩欓噷浣跨敤浜嗗鎵�,褰撴帴鏀舵秷鎭畬鎴愮殑鏃跺�欏氨鎵цMyReceiveCompleted鏂规硶
+                myQueue.ReceiveCompleted += new ReceiveCompletedEventHandler((source, e) =>
+                {
+                    try
+                    {
+                        MessageQueue mq = (MessageQueue)source;
+                        //瀹屾垚鎸囧畾鐨勫紓姝ユ帴鏀舵搷浣�
+                        Message message = mq.EndReceive(e.AsyncResult);
+                        signal.Set();
+                        action(message.Body.ToString());
+                        mq.BeginReceive();
+                    }
+                    catch (MessageQueueException)
+                    {
+
+                    }
+                });
+                myQueue.Formatter = new XmlMessageFormatter(new Type[] { typeof(String) });
+                myQueue.BeginReceive();
+                signal.WaitOne();
+            });
+        }
+
+        private void MyReceiveCompleted(Object source, ReceiveCompletedEventArgs asyncResult)
+        {
+
+        }
+
+        public bool SendMessage(string message)
+        {
+            myQueue.Send(message);
+            return true;
+        }
+
+        public bool SendUIMessage(int eventCode, int number, string message)
+        {
+            myQueue.Send($"{eventCode}|{number}|{message}");
+            return true;
+        }
+
+        public void Dispose()
+        {
+            Close();
+        }
+    }
+}
diff --git a/SimpleUtil/HMI.MQUtil/Properties/AssemblyInfo.cs b/SimpleUtil/HMI.MQUtil/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..06a22e8
--- /dev/null
+++ b/SimpleUtil/HMI.MQUtil/Properties/AssemblyInfo.cs
@@ -0,0 +1,33 @@
+锘縰sing System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// 鏈夊叧绋嬪簭闆嗙殑涓�鑸俊鎭敱浠ヤ笅
+// 鎺у埗銆傛洿鏀硅繖浜涚壒鎬у�煎彲淇敼
+// 涓庣▼搴忛泦鍏宠仈鐨勪俊鎭��
+[assembly: AssemblyTitle("HMI.MQUtil")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("HMI.MQUtil")]
+[assembly: AssemblyCopyright("Copyright 漏  2025")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 灏� ComVisible 璁剧疆涓� false 浼氫娇姝ょ▼搴忛泦涓殑绫诲瀷
+//瀵� COM 缁勪欢涓嶅彲瑙併�傚鏋滈渶瑕佷粠 COM 璁块棶姝ょ▼搴忛泦涓殑绫诲瀷
+//璇峰皢姝ょ被鍨嬬殑 ComVisible 鐗规�ц缃负 true銆�
+[assembly: ComVisible(false)]
+
+// 濡傛灉姝ら」鐩悜 COM 鍏紑锛屽垯涓嬪垪 GUID 鐢ㄤ簬绫诲瀷搴撶殑 ID
+[assembly: Guid("f8b8b6b6-821b-4ff6-b556-9cab840ea57a")]
+
+// 绋嬪簭闆嗙殑鐗堟湰淇℃伅鐢变笅鍒楀洓涓�肩粍鎴�: 
+//
+//      涓荤増鏈�
+//      娆$増鏈�
+//      鐢熸垚鍙�
+//      淇鍙�
+//
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/SimpleUtil/HMI.SerialData.Util/SerialDataHelper.cs b/SimpleUtil/HMI.SerialData.Util/SerialDataHelper.cs
index 4661184..178c70d 100644
--- a/SimpleUtil/HMI.SerialData.Util/SerialDataHelper.cs
+++ b/SimpleUtil/HMI.SerialData.Util/SerialDataHelper.cs
@@ -17,7 +17,15 @@
                     sn TEXT NOT NULL,
                     station TEXT NOT NULL,
                     step TEXT NOT NULL,
-                    status TEXT NOT NULL,
+                    limitDown TEXT,
+                    limitUp TEXT,
+                    unit TEXT,
+                    offset TEXT,
+                    value TEXT,
+                    status TEXT,
+                    finalStatus TEXT,
+                    consumeTime BIGINT, 
+                    totalConsumeTime BIGINT, 
                     time DATETIME NOT NULL);";
 
         private static string GetConnectionStr(string dbFile)
diff --git a/SimpleUtil/HMI.Simple.Util/HMI.Simple.Util.csproj b/SimpleUtil/HMI.Simple.Util/HMI.Simple.Util.csproj
index 8d18e6e..eea004c 100644
--- a/SimpleUtil/HMI.Simple.Util/HMI.Simple.Util.csproj
+++ b/SimpleUtil/HMI.Simple.Util/HMI.Simple.Util.csproj
@@ -36,6 +36,9 @@
     </Reference>
     <Reference Include="System" />
     <Reference Include="System.Core" />
+    <Reference Include="System.Data.SQLite">
+      <HintPath>..\..\..\HMI.Old\Plugin_OPCServer\Libs\System.Data.SQLite.dll</HintPath>
+    </Reference>
     <Reference Include="System.Xml.Linq" />
     <Reference Include="System.Data.DataSetExtensions" />
     <Reference Include="Microsoft.CSharp" />
@@ -46,8 +49,10 @@
   <ItemGroup>
     <Compile Include="ParameterUtil.cs" />
     <Compile Include="Parameter.cs" />
+    <Compile Include="ProductModelHelper.cs" />
     <Compile Include="ProductUtil.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="SqliteHelper.cs" />
     <Compile Include="XmlHelper.cs" />
   </ItemGroup>
   <ItemGroup>
diff --git a/SimpleUtil/HMI.Simple.Util/Parameter.cs b/SimpleUtil/HMI.Simple.Util/Parameter.cs
index 71237da..97bc1da 100644
--- a/SimpleUtil/HMI.Simple.Util/Parameter.cs
+++ b/SimpleUtil/HMI.Simple.Util/Parameter.cs
@@ -1,6 +1,6 @@
 锘縩amespace HMI.Simple.Util
 {
-    public class ParameterData
+    internal class ParameterData
     {
         public string Id { get; set; }
         public string Name { get; set; }
diff --git a/SimpleUtil/HMI.Simple.Util/ProductModelHelper.cs b/SimpleUtil/HMI.Simple.Util/ProductModelHelper.cs
new file mode 100644
index 0000000..dd584fb
--- /dev/null
+++ b/SimpleUtil/HMI.Simple.Util/ProductModelHelper.cs
@@ -0,0 +1,106 @@
+锘縰sing System;
+using System.Data;
+using System.IO;
+
+namespace HMI.Simple.Util
+{
+    public class ProductModelHelper
+    {
+        public static bool GetModelBySNR(string snr, out string model, out string msg)
+        {
+            model = string.Empty;
+            msg = string.Empty;
+            SqliteHelper sqliteHelper = null;
+            try
+            {
+                string appFolder = RegUtil.GetLocaltion();
+                string dbFilePath = Path.Combine(appFolder, "Config", "ModelManage", "data.db");
+                if (!File.Exists(dbFilePath))
+                {
+                    msg = "鏁版嵁搴撴枃浠朵笉瀛樺湪";
+                    return false;
+                }
+                sqliteHelper = new SqliteHelper(dbFilePath);
+
+                int codeLength = snr.Length;
+                var sql = $@"SELECT Model, CodeLength, StartIndex, Fixcode FROM ModelData 
+WHERE CodeLength = {codeLength} AND SUBSTR('{snr}', StartIndex, LENGTH(Fixcode) + StartIndex + 1) = Fixcode";
+                //var sql = $@"SELECT Model, CodeLength, StartIndex, Length, Fixcode FROM ModelData WHERE CodeLength={codeLength} AND '{snr}' LIKE '%'||Fixcode||'%'";
+                DataTable dt = sqliteHelper.GetDataTable(sql);
+                if (dt.Rows.Count <= 0)
+                {
+                    msg = "娌℃湁鍖归厤鐨勫瀷鍙�";
+                    return false;
+                }
+                foreach (DataRow dr in dt.Rows)
+                {
+                    var startIndex = int.Parse(dr["StartIndex"].ToString());
+                    var fixcode = dr["Fixcode"].ToString();
+                    if (string.Equals(snr.Substring(startIndex, fixcode.Length), fixcode, StringComparison.CurrentCultureIgnoreCase))
+                    {
+                        model = dr["Model"].ToString();
+                        return true;
+                    }
+                }
+                msg = "娌℃湁鍖归厤鐨勫瀷鍙�";
+                return false;
+            }
+            catch (Exception ex)
+            {
+                msg = ex.Message;
+                return false;
+            }
+            finally
+            {
+                sqliteHelper?.Dispose();
+            }
+        }
+
+        public static bool GetModelBySNR(string dbFilePath, string snr, out string model, out string msg)
+        {
+            model = string.Empty;
+            msg = string.Empty;
+            SqliteHelper sqliteHelper = null;
+            try
+            {
+                if (!File.Exists(dbFilePath))
+                {
+                    msg = "鏁版嵁搴撴枃浠朵笉瀛樺湪";
+                    return false;
+                }
+                sqliteHelper = new SqliteHelper(dbFilePath);
+
+                int codeLength = snr.Length;
+                var sql = $@"SELECT Model, CodeLength, StartIndex, Fixcode FROM ModelData 
+WHERE CodeLength = {codeLength} AND SUBSTR('{snr}', StartIndex, LENGTH(Fixcode) + StartIndex + 1) = Fixcode";
+                DataTable dt = sqliteHelper.GetDataTable(sql);
+                if (dt.Rows.Count <= 0)
+                {
+                    msg = "娌℃湁鍖归厤鐨勫瀷鍙�";
+                    return false;
+                }
+                foreach (DataRow dr in dt.Rows)
+                {
+                    var startIndex = int.Parse(dr["StartIndex"].ToString());
+                    var fixcode = dr["Fixcode"].ToString();
+                    if (string.Equals(snr.Substring(startIndex, fixcode.Length), fixcode, StringComparison.CurrentCultureIgnoreCase))
+                    {
+                        model = dr["Model"].ToString();
+                        return true;
+                    }
+                }
+                msg = "娌℃湁鍖归厤鐨勫瀷鍙�";
+                return false;
+            }
+            catch (Exception ex)
+            {
+                msg = ex.Message;
+                return false;
+            }
+            finally
+            {
+                sqliteHelper?.Dispose();
+            }
+        }
+    }
+}
diff --git a/SimpleUtil/HMI.Simple.Util/SqliteHelper.cs b/SimpleUtil/HMI.Simple.Util/SqliteHelper.cs
new file mode 100644
index 0000000..768d49c
--- /dev/null
+++ b/SimpleUtil/HMI.Simple.Util/SqliteHelper.cs
@@ -0,0 +1,238 @@
+锘縰sing System;
+using System.Collections.Generic;
+using System.Data;
+using System.Data.SQLite;
+
+namespace HMI.Simple.Util
+{
+    internal class SqliteHelper : IDisposable
+    {
+        public string DataBaseFile { get; private set; } = string.Empty;
+
+        private string ConnectString = string.Empty;
+
+        private SQLiteConnection conn = null;
+
+        private SQLiteCommand cmd = null;
+
+        public SqliteHelper(string DBFile)
+        {
+            DataBaseFile = DBFile;
+            ConnectString = "DataSource=" + DataBaseFile + ";Version=3;Pooling=False;Max Pool Size=100;";
+            conn = new SQLiteConnection(ConnectString);
+            cmd = new SQLiteCommand(conn);
+        }
+
+        public static bool CreateDatabase(string DataBaseFile)
+        {
+            try
+            {
+                SQLiteConnection.CreateFile(DataBaseFile);
+                return true;
+            }
+            catch (Exception ex)
+            {
+                throw new Exception($"Create Database [${DataBaseFile}] Failed.", ex);
+            }
+        }
+
+        public int ExecuteSql(string Sql)
+        {
+            try
+            {
+                Connect();
+                cmd.CommandType = CommandType.Text;
+                cmd.CommandText = Sql;
+                return cmd.ExecuteNonQuery();
+            }
+            catch (Exception ex)
+            {
+                string message = "ExecuteSql Error:" + ex.Message;
+                throw new Exception(message);
+            }
+            finally
+            {
+                DisConnect();
+            }
+        }
+
+        public void ExecuteBatchSql(IEnumerable<string> sqls)
+        {
+            SQLiteTransaction tr = null;
+            try
+            {
+                Connect();
+                tr = conn.BeginTransaction();
+                cmd.CommandType = CommandType.Text;
+                foreach (string sql in sqls)
+                {
+                    if (string.IsNullOrEmpty(sql) || string.IsNullOrWhiteSpace(sql)) continue;
+                    cmd.CommandText = sql;
+                    cmd.ExecuteNonQuery();
+                }
+                tr.Commit();
+            }
+            catch (Exception ex)
+            {
+                tr?.Rollback();
+                throw ex;
+            }
+            finally
+            {
+                DisConnect();
+            }
+        }
+
+        public DataTable GetDataTable(string Sql)
+        {
+            DataTable dataTable = new DataTable();
+            SQLiteDataAdapter sQLiteDataAdapter = new SQLiteDataAdapter();
+            try
+            {
+                Connect();
+                cmd.CommandType = CommandType.Text;
+                cmd.CommandText = Sql;
+                sQLiteDataAdapter.SelectCommand = cmd;
+                sQLiteDataAdapter.Fill(dataTable);
+            }
+            catch (Exception ex)
+            {
+                string message = "ExecuteSql Error:" + ex.Message;
+                throw new Exception(message);
+            }
+            finally
+            {
+                DisConnect();
+            }
+
+            return dataTable;
+        }
+
+        public string[,] GetDataTableToArray(string Sql)
+        {
+            try
+            {
+                DataTable dataTable = GetDataTable(Sql);
+                string[,] array = new string[dataTable.Rows.Count, dataTable.Columns.Count];
+                int num = 0;
+                int num2 = 0;
+                foreach (DataRow row in dataTable.Rows)
+                {
+                    num2 = 0;
+                    object[] itemArray = row.ItemArray;
+                    foreach (object obj in itemArray)
+                    {
+                        array[num, num2] = obj.ToString();
+                        num2++;
+                    }
+
+                    num++;
+                }
+
+                return array;
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message);
+            }
+        }
+
+        public void Close()
+        {
+            try
+            {
+                if (conn.State == ConnectionState.Open)
+                {
+                    conn.Close();
+                }
+
+                if (cmd != null)
+                {
+                    cmd.Dispose();
+                }
+
+                if (conn != null)
+                {
+                    conn.Dispose();
+                }
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message);
+            }
+        }
+
+        public bool HasData(string sql)
+        {
+            DataTable dataTable = GetDataTable(sql);
+            return dataTable.Rows.Count > 0;
+        }
+
+        public string[] GetColumnNameFromTable(string TableName)
+        {
+            try
+            {
+                string sql = $"PRAGMA table_info('{TableName}')";
+                DataTable dataTable = GetDataTable(sql);
+                string[] array = new string[dataTable.Rows.Count];
+                int num = 0;
+                foreach (DataRow row in dataTable.Rows)
+                {
+                    array[num] = row.ItemArray[1].ToString();
+                    num++;
+                }
+
+                return array;
+            }
+            catch (Exception ex)
+            {
+                throw new Exception("鑾峰彇琛ㄥ瓧娈靛悕绉板け璐�:" + ex.Message);
+            }
+        }
+
+        public string[] GetAllTableInFile()
+        {
+            try
+            {
+                string sql = "select name from sqlite_master where type = 'table' order by name";
+                DataTable dataTable = GetDataTable(sql);
+                string[] array = new string[dataTable.Rows.Count];
+                int num = 0;
+                foreach (DataRow row in dataTable.Rows)
+                {
+                    array[num] = row.ItemArray[0].ToString();
+                    num++;
+                }
+
+                return array;
+            }
+            catch (Exception ex)
+            {
+                throw new Exception("鑾峰彇琛ㄥ悕澶辫触:" + ex.Message);
+            }
+        }
+
+        private void Connect()
+        {
+            if (conn.State == ConnectionState.Closed)
+            {
+                conn.ConnectionString = ConnectString;
+                cmd.Connection = conn;
+                conn.Open();
+            }
+        }
+
+        private void DisConnect()
+        {
+            if (conn.State == ConnectionState.Open)
+            {
+                conn.Close();
+            }
+        }
+
+        public void Dispose()
+        {
+            this.Close();
+        }
+    }
+}
diff --git a/WPFProject/Api/Api.txt b/WPFProject/Api/Api.txt
deleted file mode 100644
index 5f28270..0000000
--- a/WPFProject/Api/Api.txt
+++ /dev/null
@@ -1 +0,0 @@
-锘�
\ No newline at end of file
diff --git a/WPFProject/Api/SQLite.Interop.dll b/WPFProject/Api/SQLite.Interop.dll
new file mode 100644
index 0000000..f666eab
--- /dev/null
+++ b/WPFProject/Api/SQLite.Interop.dll
Binary files differ
diff --git a/WPFProject/App.xaml b/WPFProject/App.xaml
index e68ae7c..289fbe1 100644
--- a/WPFProject/App.xaml
+++ b/WPFProject/App.xaml
@@ -18,6 +18,7 @@
                 <ResourceDictionary Source="/HMI.UI;component/Style/Colors.xaml"/>
                 <ResourceDictionary Source="/HMI.UI;component/Style/PanuonResource.xaml"/>
                 <ResourceDictionary Source="/HMI.UI;component/Style/MsgBoxStyle.xaml"/>
+                <ResourceDictionary Source="/HMI.Base;component/MaskControl.xaml"/>
             </ResourceDictionary.MergedDictionaries>
             <sys:Double x:Key="nanD">NaN</sys:Double>
             <Style TargetType="{x:Type pu:FormGroup}" BasedOn="{StaticResource InputFormGroupStyle}"></Style>
@@ -25,6 +26,7 @@
             <uic:DataGridRowNumberConverter x:Key="rowNumber"/>
             <uic:VisibilityBoolConverter x:Key="vbConverter"/>
             <uic:DataGridRowFlagConverter x:Key="rowFlag"/>
+            <uic:ReverseBoolConverter x:Key="reverseBool"/>
             <hc:LangConverter x:Key="lc"/>
             <hc:WidthSizeConverter x:Key="fc"/>
             <hc:HeightSizeConverter x:Key="hfc"/>
diff --git a/WPFProject/BuildCmd.bat b/WPFProject/BuildCmd.bat
index 1936efa..cfde15f 100644
--- a/WPFProject/BuildCmd.bat
+++ b/WPFProject/BuildCmd.bat
@@ -26,6 +26,10 @@
 move /y "$(TargeDir)Plugin.DataBoard.dll" $(TargeDir)Plugin\Plugin.DataBoard.dll"
 move /y "$(TargeDir)Plugin.JarUI.dll" $(TargeDir)Plugin\Plugin.JarUI.dll"
 move /y "$(TargeDir)Plugin.SeqEditor.dll" $(TargeDir)Plugin\Plugin.SeqEditor.dll"
+move /y "$(TargeDir)Plugin.ChangeModel.dll" $(TargeDir)Plugin\Plugin.ChangeModel.dll"
+move /y "$(TargeDir)Plugin.MaterialOpt.dll" $(TargeDir)Plugin\Plugin.MaterialOpt.dll"
+move /y "$(TargeDir)Plugin.DODriverControl.dll" $(TargeDir)Plugin\Plugin.DODriverControl.dll"
+move /y "$(TargeDir)Plugin.ModelManage.dll" $(TargeDir)Plugin\Plugin.ModelManage.dll"
 
 setlocal enabledelayedexpansion
 
@@ -35,6 +39,12 @@
 rmdir /S /Q "%destination_folder%"
 mkdir "%destination_folder%"
 
+for %%F in ("%source_folder%\*") do (
+    if /I "%%~nxF"=="System.Data.SQLite.dll" (
+        xcopy "%source_folder%\%%F" "%source_folder%\Api\%%F" /Y
+    )
+)
+
 for /d %%D in ("%source_folder%\*") do (
     if /I not "%%~nxD"=="Api" if /I not "%%~nxD"=="Config" if /I not "%%~nxD"=="Icon" if /I not "%%~nxD"=="Lang" if /I not "%%~nxD"=="Libs" if /I not "%%~nxD"=="Log" if /I not "%%~nxD"=="Plugin" if /I not "%%~nxD"=="TestPlatform.exe.WebView2" (
         move /Y "%%D" "%destination_folder%"
@@ -42,7 +52,7 @@
 )
 
 for %%F in ("%source_folder%\*") do (
-    if /I not "%%~nxF"=="$(TargetFileName)" if /I not "%%~nxF"=="$(TargetFileName).config" if /I not "%%~nxF"=="$(TargetName).pdb" if /I not "%%~nxF"=="clear.bat" (
+    if /I not "%%~nxF"=="$(TargetFileName)" if /I not "%%~nxF"=="$(TargetFileName).config" if /I not "%%~nxF"=="$(TargetName).pdb" if /I not "%%~nxF"=="clear.bat" if /I not "%%~nxF"=="瀹夎MSMQ.bat" (
         move /Y "%%F" "%destination_folder%"
     )
 )
diff --git a/WPFProject/Controls/MainTabControl.xaml b/WPFProject/Controls/MainTabControl.xaml
index 2950b42..f28f6ee 100644
--- a/WPFProject/Controls/MainTabControl.xaml
+++ b/WPFProject/Controls/MainTabControl.xaml
@@ -187,12 +187,45 @@
             </DockPanel>
         </Border>
 
-
-
-
-
         <Border x:Name="page"  Grid.Row="2" BorderThickness="0" Margin="0" Background="White" Padding="0">
 
         </Border>
+
+        <Border Height="80" Grid.Row="2" Background="Red" Visibility="Collapsed" VerticalAlignment="Top" x:Name="alarmBorder">
+            <Border.Style>
+                <Style TargetType="Border">
+                    <Style.Triggers>
+                        <Trigger Property="Visibility" Value="Visible">
+                            <Trigger.EnterActions>
+                                <BeginStoryboard>
+                                    <Storyboard>
+                                        <ColorAnimation Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
+                    From="#ff2100" 
+                    To="#fe7975" 
+                    Duration="0:0:0.7"
+                    AutoReverse="True"
+                    RepeatBehavior="Forever"/>
+                                    </Storyboard>
+                                </BeginStoryboard>
+                            </Trigger.EnterActions>
+                        </Trigger>
+                    </Style.Triggers>
+                </Style>
+            </Border.Style>
+
+            <DockPanel>
+                <Button DockPanel.Dock="Right" 
+                FontSize="25" 
+                FontWeight="Bold"
+                Margin="0,10,10,0"
+                VerticalAlignment="Top"
+                Click="Button_Click_1"
+                FontFamily="{StaticResource iconfont}" Background="Transparent" Content="&#xe609;"/>
+
+                <Label Margin="10,0,0,0" FontSize="25" FontWeight="Bold" 
+               VerticalAlignment="Center" Content="{Binding AlarmInfo}"/>
+
+            </DockPanel>
+        </Border>
     </Grid>
 </UserControl>
diff --git a/WPFProject/Controls/MainTabControl.xaml.cs b/WPFProject/Controls/MainTabControl.xaml.cs
index 874fe44..39d6077 100644
--- a/WPFProject/Controls/MainTabControl.xaml.cs
+++ b/WPFProject/Controls/MainTabControl.xaml.cs
@@ -1,6 +1,7 @@
 锘縰sing HMI.Base;
 using HMI.Core.Api;
 using Panuon.WPF.UI;
+using Plugin.TestResult_WPF.Model;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.ComponentModel;
@@ -196,5 +197,27 @@
             }
             Loaded -= UserControl_Loaded;
         }
+
+        private string alarmInfo;
+        public string AlarmInfo
+        {
+            get => alarmInfo;
+            set
+            {
+                RaiseAndSetIfChanged(ref alarmInfo, value);
+            }
+        }
+
+        private void Button_Click_1(object sender, RoutedEventArgs e)
+        {
+            alarmBorder.Visibility = Visibility.Collapsed;
+            AlarmInfo = string.Empty;
+        }
+
+        public void ShowAlarm(string message)
+        {
+            AlarmInfo = message;
+            alarmBorder.Visibility = Visibility.Visible;
+        }
     }
 }
diff --git a/WPFProject/Lang/en-US/AlarmInfoManage.json b/WPFProject/Lang/en-US/AlarmInfoManage.json
index 2e092d2..1b9c928 100644
--- a/WPFProject/Lang/en-US/AlarmInfoManage.json
+++ b/WPFProject/Lang/en-US/AlarmInfoManage.json
@@ -4,6 +4,7 @@
   "Alm.Code": "Code",
   "Alm.Info": "Info",
   "Alm.Time": "Time",
+  "Alm.Description": "Description",
   "Alm.OnlySupportXlsm": "Only .xlsm can be supported",
   "Alm.RowImportFailed": "Import failed in row: {0}",
 
diff --git a/WPFProject/Lang/en-US/ChangeModel.json b/WPFProject/Lang/en-US/ChangeModel.json
new file mode 100644
index 0000000..92b6f90
--- /dev/null
+++ b/WPFProject/Lang/en-US/ChangeModel.json
@@ -0,0 +1,25 @@
+{
+  "cm.title": "Change Model",
+  "cm.lineName": "Line Name",
+  "cm.workorder": "Work Order",
+  "cm.productModel": "Model",
+  "cm.customProperty": "Custom Property",
+  "cm.workstation": "Workstation",
+  "cm.changeTime": "Time",
+  "cm.changeResult": "Result",
+  "cm.info": "Info",
+  "cm.cm": "Change",
+  "cm.rfhInfo": "Refresh",
+  "cm.rfhLog": "Refresh Log",
+  "cm.clrLog": "Clear Log",
+  "cm.rfhInfoErr": "Clear Current Model Failed: {0}",
+  "cm.makeSureChangeModel": "Are you sure you want to change model锛�",
+  "cm.makeSureDelLog": "Are you sure you want to clear log? Only 10 data will be left.",
+  "cm.mesConnectFail": "MES connected failed: {0}",
+  "cm.rfhLineNameFail": "Refresh line name failed: {0}",
+  "cm.rfhWorkOrderFail": "Refresh work order failed: {0}",
+  "cm.rfhModelFail": "Refresh model failed: {0}",
+  "cm.rfhLogFail": "Refresh log failed: {0}",
+  "cm.delLogFail": "Clear log failed: {0}",
+  "cm.changeFail": "Change model failed: {0}"
+}
\ No newline at end of file
diff --git a/WPFProject/Lang/en-US/app.json b/WPFProject/Lang/en-US/app.json
index 1f219a1..bf31815 100644
--- a/WPFProject/Lang/en-US/app.json
+++ b/WPFProject/Lang/en-US/app.json
@@ -87,6 +87,7 @@
   "Test.LimitUp": "Limit up",
   "Test.Result": "Result",
   "Test.Range": "Range",
+  "Test.ConsumeTime": "Consume Time",
   "Test.Condition": "Condition",
   "Test.UpdateTime": "Update Time",
 
diff --git a/WPFProject/Lang/zh-CN/AlarmInfoManage.json b/WPFProject/Lang/zh-CN/AlarmInfoManage.json
index 8e48f05..d2ca3d6 100644
--- a/WPFProject/Lang/zh-CN/AlarmInfoManage.json
+++ b/WPFProject/Lang/zh-CN/AlarmInfoManage.json
@@ -4,6 +4,7 @@
   "Alm.Code": "缂栧彿",
   "Alm.Info": "淇℃伅",
   "Alm.Time": "鏃堕棿",
+  "Alm.Description": "鎶ヨ鎻忚堪",
   "Alm.OnlySupportXlsm": "浠呮敮鎸亁lsm鏍煎紡鏂囦欢",
   "Alm.RowImportFailed": "绗瑊0}琛屽鍏ュ紓甯�",
 
diff --git a/WPFProject/Lang/zh-CN/ChangeModel.json b/WPFProject/Lang/zh-CN/ChangeModel.json
new file mode 100644
index 0000000..59ebbba
--- /dev/null
+++ b/WPFProject/Lang/zh-CN/ChangeModel.json
@@ -0,0 +1,25 @@
+{
+  "cm.title": "浜у搧鎹㈠瀷",
+  "cm.lineName": "浜х嚎鍚嶇О",
+  "cm.workorder": "宸ュ崟鍙�",
+  "cm.productModel": "浜у搧鍨嬪彿",
+  "cm.customProperty": "闄勫姞灞炴��",
+  "cm.workstation": "宸ョ珯",
+  "cm.changeTime": "鎹㈠瀷鏃堕棿",
+  "cm.changeResult": "鎹㈠瀷缁撴灉",
+  "cm.info": "淇℃伅",
+  "cm.cm": "鎹㈠瀷",
+  "cm.rfhInfo": "鍒锋柊",
+  "cm.rfhLog": "鍒锋柊Log",
+  "cm.clrLog": "娓呯悊Log",
+  "cm.rfhInfoErr": "鍒锋柊褰撳墠鍨嬪彿澶辫触: {0}",
+  "cm.makeSureChangeModel": "纭畾瑕佽繘琛屾崲鍨嬪悧锛�",
+  "cm.makeSureDelLog": "纭畾瑕佹竻鐞嗘棩蹇楀悧锛熷皢鍒犻櫎鏈�杩�10鏉℃棩蹇椾箣澶栫殑鎵�鏈夋棩蹇楄褰曘��",
+  "cm.mesConnectFail": "MES杩炴帴澶辫触: {0}",
+  "cm.rfhLineNameFail": "浜х嚎淇℃伅鍒锋柊澶辫触: {0}",
+  "cm.rfhWorkOrderFail": "宸ュ崟淇℃伅鍒锋柊澶辫触: {0}",
+  "cm.rfhModelFail": "浜у搧淇℃伅鍒锋柊澶辫触: {0}",
+  "cm.rfhLogFail": "Log鍒锋柊澶辫触: {0}",
+  "cm.delLogFail": "娓呯悊Log澶辫触: {0}",
+  "cm.changeFail": "鎹㈠瀷澶辫触: {0}"
+}
\ No newline at end of file
diff --git a/WPFProject/Lang/zh-CN/DOControl.json b/WPFProject/Lang/zh-CN/DOControl.json
index 2d37d79..7566651 100644
--- a/WPFProject/Lang/zh-CN/DOControl.json
+++ b/WPFProject/Lang/zh-CN/DOControl.json
@@ -7,5 +7,7 @@
   "doc.readOnly": "鍙",
   "doc.pressDownValue": "鎸変笅鍊�",
   "doc.pressUpValue": "寮硅捣鍊�",
-  "doc.controlType": "鎺т欢绫诲瀷"
+  "doc.controlType": "鎺т欢绫诲瀷",
+  "doc.pressDownText": "鎸変笅鏂囨湰",
+  "doc.pressUpText": "寮硅捣鏂囨湰"
 }
\ No newline at end of file
diff --git a/WPFProject/Lang/zh-CN/app.json b/WPFProject/Lang/zh-CN/app.json
index 6b4b85f..4273245 100644
--- a/WPFProject/Lang/zh-CN/app.json
+++ b/WPFProject/Lang/zh-CN/app.json
@@ -87,6 +87,7 @@
   "Test.LimitUp": "涓婇檺",
   "Test.Result": "缁撴灉",
   "Test.Range": "娴嬭瘯鑼冨洿",
+  "Test.ConsumeTime": "鑰楁椂",
   "Test.Condition": "娴嬭瘯鏉′欢",
   "Test.UpdateTime": "鏇存柊鏃堕棿",
 
diff --git a/WPFProject/Login.xaml b/WPFProject/Login.xaml
index f656f1f..3bc96d1 100644
--- a/WPFProject/Login.xaml
+++ b/WPFProject/Login.xaml
@@ -26,17 +26,6 @@
                 <ResourceDictionary Source="/HMI.UI;component/Style/Colors.xaml"/>
             </ResourceDictionary.MergedDictionaries>
 
-            <Style x:Key="SignInPasswordBoxStyle" TargetType="PasswordBox" BasedOn="{StaticResource {x:Type PasswordBox}}">
-                <Setter Property="pu:PasswordBoxHelper.FocusedBorderBrush" Value="#6CBCEA" />
-                <Setter Property="pu:PasswordBoxHelper.FocusedShadowColor" Value="#6CBCEA" />
-                <Setter Property="pu:PasswordBoxHelper.CornerRadius" Value="2" />
-                <Setter Property="pu:PasswordBoxHelper.WatermarkForeground" Value="#4A4A4A" />
-                <Setter Property="pu:PasswordBoxHelper.FocusedWatermarkForeground" Value="#AAAAAA" />
-                <Setter Property="pu:PasswordBoxHelper.ClearButtonVisibility" Value="VisibleOnHoverAndNonnull" />
-                <Setter Property="Height" Value="35" />
-                <Setter Property="Padding" Value="10,0" />
-            </Style>
-
             <Style x:Key="SignInButtonStyle" TargetType="Button" BasedOn="{StaticResource PrimaryButton}">
                 <Setter Property="pu:ButtonHelper.PendingSpinStyle">
                     <Setter.Value>
diff --git a/WPFProject/Login.xaml.cs b/WPFProject/Login.xaml.cs
index 3be043e..03bc65c 100644
--- a/WPFProject/Login.xaml.cs
+++ b/WPFProject/Login.xaml.cs
@@ -1,4 +1,5 @@
-锘縰sing HMI.Core.Entity;
+锘縰sing HMI.Base;
+using HMI.Core.Entity;
 using HMI.Core.Setting;
 using HMI.Core.Util;
 using HMI.UI.DpUtil;
@@ -6,10 +7,12 @@
 using log4net;
 using Panuon.WPF.UI;
 using System;
+using System.Collections.Generic;
 using System.Data;
 using System.IO;
 using System.Threading.Tasks;
 using System.Windows;
+using System.Windows.Threading;
 using WPFProject.Utils;
 using static System.Windows.Forms.VisualStyles.VisualStyleElement.StartPanel;
 
@@ -52,43 +55,98 @@
             }
         }
 
-        private void DoLogin(string username, string password, bool rememberMe)
+        private List<PermissionData> SelectPermission(string username, SqliteHelper sqliteHelper, out List<PermissionData> allPermission)
         {
-            SqliteHelper helper = null;
-
+            List<PermissionData> data = new List<PermissionData>();
+            allPermission = new List<PermissionData>();
+            Dictionary<string, PermissionData> cache = new Dictionary<string, PermissionData>();
             try
             {
-                helper = new SqliteHelper(PathHelper.DatabasePath);
-                string sql = $"SELECT id,code,username,password,name From user WHERE username='{username}' and password='{password}'";
-                DataTable dt = helper.GetDataTable(sql);
-                if (dt == null || dt.Rows.Count <= 0)
-                {
-                    throw new LoginException(LangUtil.Get("App.ErrorUsernameOrPassword"));
-                }
-                UserInfo user = new UserInfo(
-                    dt.Rows[0]["id"].ToString(),
-                    dt.Rows[0]["code"].ToString(),
-                    dt.Rows[0]["username"].ToString(),
-                    dt.Rows[0]["name"].ToString());
-                UserInfo.Login(user);
-                if (rememberMe)
-                {
-                    // 璁颁綇鐧诲綍鎿嶄綔
-                    string userToken =
-                        JsonUtil.Serialize(
-                            new UserInfo(null, user.Code, user.Username, user.Name, dt.Rows[0]["password"].ToString()));
-                    // 瀛樺偍鏁版嵁
-                    BinUtil.SaveStrToBin(encryptor.Encrypt(userToken),
-                        Path.Combine(PathHelper.DatabaseFolder, "user.bin"));
-                }
+                string sql = $@"SELECT DISTINCT m.* FROM menu AS m INNER JOIN role_menu_rel AS rrel ON m.code = rrel.menuCode
+INNER JOIN role AS r ON rrel.roleCode = r.code
+INNER JOIN user_role_rel AS urel ON r.code = urel.roleCode
+AND urel.username = '{username}' ORDER BY m.level,m.sort, m.id;";
 
-                Dispatcher.Invoke(() =>
+                DataTable dt = sqliteHelper.GetDataTable(sql);
+                if (dt != null)
                 {
-                    LoadingWindow loading = new LoadingWindow();
-                    loading.Show();
-                    this.Closed -= window_Closed;
-                    this.Close();
-                });
+                    foreach (DataRow dr in dt.Rows)
+                    {
+                        string path = Path.Combine(PathHelper.IconFolder, dr["iconPath"].ToString());
+                        var menu = new PermissionData
+                        {
+                            Code = dr["code"].ToString(),
+                            Name = dr["name"].ToString(),
+                            Type = dr["type"].ToString(),
+                            ParentCode = dr["parentCode"].ToString(),
+                            DllPath = dr["dllPath"].ToString(),
+                            ClassName = dr["className"].ToString(),
+                            IconFont = dr["icon"].ToString()
+                        };
+                        cache[menu.Code] = menu;
+                        allPermission.Add(menu);
+                        if (!cache.ContainsKey(menu.ParentCode))
+                        {
+                            data.Add(menu);
+                        }
+                        else
+                        {
+                            cache[menu.ParentCode].Children.Add(menu);
+                        }
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                //LoadMessageAppend?.Invoke(this, ex.Message);
+            }
+            return data;
+        }
+
+        private void DoLogin(string username, string password, bool rememberMe)
+        {
+            try
+            {
+                using (SqliteHelper helper = new SqliteHelper(PathHelper.DatabasePath))
+                {
+                    string sql = $"SELECT id,code,username,password,name From user WHERE username='{username}' and password='{password}'";
+                    DataTable dt = helper.GetDataTable(sql);
+                    if (dt == null || dt.Rows.Count <= 0)
+                    {
+                        throw new LoginException(LangUtil.Get("App.ErrorUsernameOrPassword"));
+                    }
+                    List<PermissionData> permissions = SelectPermission(username, helper, out List<PermissionData> allPermission);
+                    UserInfo user = new UserInfo(
+                        dt.Rows[0]["id"].ToString(),
+                        dt.Rows[0]["code"].ToString(),
+                        dt.Rows[0]["username"].ToString(),
+                        dt.Rows[0]["name"].ToString(),
+                        permissions,
+                        allPermission,
+                        dt.Rows[0]["password"].ToString()
+                    );
+
+                    UserInfo.Login(user);
+                    if (rememberMe)
+                    {
+                        // 璁颁綇鐧诲綍鎿嶄綔
+                        string userToken =
+                            JsonUtil.Serialize(
+                                new UserInfo(null, user.Code, user.Username, user.Name, null, null,
+                                dt.Rows[0]["password"].ToString()));
+                        // 瀛樺偍鏁版嵁
+                        BinUtil.SaveStrToBin(encryptor.Encrypt(userToken),
+                            Path.Combine(PathHelper.DatabaseFolder, "user.bin"));
+                    }
+
+                    Dispatcher.Invoke(() =>
+                    {
+                        LoadingWindow loading = new LoadingWindow();
+                        loading.Show();
+                        this.Closed -= window_Closed;
+                        this.Close();
+                    });
+                }
             }
             catch (Exception ex)
             {
@@ -100,6 +158,7 @@
             }
             finally
             {
+
                 Dispatcher.Invoke(() =>
                 {
                     ButtonHelper.SetIsPending(BtnLogin, false);
diff --git a/WPFProject/PanuonWindow.xaml b/WPFProject/PanuonWindow.xaml
index 867d337..09086ef 100644
--- a/WPFProject/PanuonWindow.xaml
+++ b/WPFProject/PanuonWindow.xaml
@@ -18,6 +18,7 @@
             xmlns:hc="clr-namespace:HMI.Core;assembly=HMI.Core"
             Closed="window_Closed"
             Topmost="False"
+            pu:WindowXCaption.HeaderAlignment="Stretch"
             Title="TestPlatform"
             pu:WindowXCaption.Background="Transparent"
             pu:WindowXCaption.Height="80"
@@ -46,91 +47,200 @@
 
     <pu:WindowXCaption.HeaderTemplate>
         <DataTemplate>
-            <StackPanel DataContext="{Binding ElementName=window}" Orientation="Horizontal"
-                        VerticalAlignment="Center"
-                        dp:StackPanelHelper.ChildSpace="30" Margin="5,0,0,0" Height="100">
-                <Image Source="{Binding WindowLogo}" Height="60" VerticalAlignment="Center" Stretch="Fill"
-       RenderOptions.BitmapScalingMode="HighQuality" />
-                <TextBlock FontSize="30" VerticalAlignment="Center" Text="{Binding WindowTitle}">
-                    <TextBlock.Effect>
-                        <DropShadowEffect ShadowDepth="8"
-      Direction="330"
-      Color="Black"
-      Opacity="0.5"
-     BlurRadius="4"/>
-                    </TextBlock.Effect>
-                </TextBlock>
-            </StackPanel>
+            <DockPanel DataContext="{Binding ElementName=window}" WindowChrome.IsHitTestVisibleInChrome="True"
+                        VerticalAlignment="Center" Height="100">
+                <StackPanel Orientation="Horizontal">
+                    <Image DockPanel.Dock="Left" Source="{Binding WindowLogo}" Margin="5,0,20,0" Height="60" VerticalAlignment="Center" Stretch="Fill"
+  RenderOptions.BitmapScalingMode="HighQuality" />
+                    <TextBlock FontSize="30" VerticalAlignment="Center" Text="{Binding WindowTitle}">
+                        <TextBlock.Effect>
+                            <DropShadowEffect ShadowDepth="8"
+ Direction="330"
+ Color="Black"
+ Opacity="0.5"
+BlurRadius="4"/>
+                        </TextBlock.Effect>
+                    </TextBlock>
+                </StackPanel>
+
+                <StackPanel DockPanel.Dock="Right" VerticalAlignment="Top" Margin="10,10,0,0" Orientation="Horizontal">
+                    <Menu Margin="0" Padding="0" VerticalAlignment="Top"
+  VerticalContentAlignment="Center" Style="{StaticResource UserMenu}"
+  
+  Background="Transparent">
+                        <MenuItem x:Name="userMenu" Header="{Binding WelcomeTitle}" FontSize="16">
+                            <MenuItem Icon="&#xe61d;" Cursor="Hand" FontSize="14" Header="閫�鍑虹櫥褰�" 
+                                      x:Name="MiLogout" Click="MenuItem_Click"/>
+                            <MenuItem Cursor="Hand" FontSize="14" Icon="&#xe867;"
+              Header="閲嶆柊鍚姩"
+              x:Name="MiRestart"
+              Click="MenuItem_Click"/>
+                        </MenuItem>
+                    </Menu>
+
+                    <Button x:Name="BtnSwitchTheme"
+FontSize="18"
+FontFamily="/HMI.UI;component/Resources/Font/#iconfont"
+Content="&#xe60e;"
+RenderTransformOrigin="0.5,0.5"
+Click="Button_Click">
+                        <Button.Style>
+                            <Style TargetType="Button" BasedOn="{StaticResource {x:Static pu:WindowXCaption.MinimizeButtonStyleKey}}">
+                                <Setter Property="Focusable" Value="False"/>
+                                <Style.Triggers>
+                                    <DataTrigger Binding="{Binding ShowMenu}" Value="True">
+                                        <Setter Property="Button.RenderTransform">
+                                            <Setter.Value>
+                                                <RotateTransform x:Name="MyAnimatedTransform" Angle="180" />
+                                            </Setter.Value>
+                                        </Setter>
+                                    </DataTrigger>
+                                </Style.Triggers>
+                            </Style>
+                        </Button.Style>
+                    </Button>
+
+                    <Button FontSize="18" Click="Button_Click" x:Name="minBtn">
+                        <Button.Style>
+                            <Style TargetType="Button" BasedOn="{StaticResource {x:Static pu:WindowXCaption.MinimizeButtonStyleKey}}">
+                                <Setter Property="Focusable" Value="False"/>
+                                <Setter Property="IsHitTestVisible" Value="True"/>
+                            </Style>
+                        </Button.Style>
+                    </Button>
+
+                    <Button FontSize="18" Click="Button_Click" x:Name="maxBtn">
+                        <Button.Style>
+                            <Style TargetType="Button" BasedOn="{StaticResource {x:Static pu:WindowXCaption.MaximizeButtonStyleKey}}">
+                                <Setter Property="Focusable" Value="False"/>
+                            </Style>
+                        </Button.Style>
+                    </Button>
+
+                    <Button FontSize="18" Click="Button_Click" x:Name="closeBtn">
+                        <Button.Style>
+                            <Style TargetType="Button" BasedOn="{StaticResource {x:Static pu:WindowXCaption.CloseButtonStyleKey}}">
+                                <Setter Property="Focusable" Value="False"/>
+                            </Style>
+                        </Button.Style>
+                    </Button>
+                </StackPanel>
+
+                <ListBox x:Name="list"
+                             
+                        Margin="20,15,10,15"
+                        Background="#6FFFFFFF"
+                        BorderThickness="0"
+                             pu:ListBoxHelper.CornerRadius="5"
+                             pu:ListBoxHelper.ItemsCornerRadius="5"
+                             pu:ListBoxHelper.ItemsBackground="Red"
+                             pu:ListBoxHelper.ItemsHoverBackground="#b20000"
+                             pu:ListBoxHelper.ItemsSelectedBackground="#b20000"
+                             pu:ListBoxHelper.ItemsIcon="&#xe931;"
+                             ItemsSource="{Binding Alarms}"
+                             Loaded="list_Loaded"
+                             
+                             FontSize="15"
+Foreground="#FFF"
+                             
+                             pu:IconHelper.FontFamily="/Panuon.WPF.UI;component/Resources/Fonts/#PanuonIcon"
+                             pu:IconHelper.Foreground="Green"
+                             pu:IconHelper.FontSize="20"
+                             pu:IconHelper.Margin="5,0,5,0"
+                             FontWeight="UltraBlack"
+                            
+                             
+                             pu:ListBoxHelper.ItemsSeparatorVisibility="Visible"
+                             pu:ListBoxHelper.ItemsSeparatorBrush="Transparent"
+                             pu:ListBoxHelper.ItemsRemoveButtonVisibility="Visible">
+
+                    <ListBox.Style>
+                        <Style TargetType="ListBox" BasedOn="{StaticResource {x:Static pu:StyleKeys.ListBoxStyle}}">
+                            <Setter Property="Visibility" Value="Visible"/>
+                            <Style.Triggers>
+                                <DataTrigger Binding="{Binding Alarms.Count}" Value="0">
+                                    <Setter Property="Visibility" Value="Hidden"/>
+                                </DataTrigger>
+                            </Style.Triggers>
+                        </Style>
+                    </ListBox.Style>
+
+                    <ListBox.Resources>
+                        <sys:Double x:Key="{x:Static SystemParameters.VerticalScrollBarWidthKey}">6</sys:Double>
+
+                        <ControlTemplate x:Key="VerticalScrollBar" TargetType="RangeBase">
+                            <Border Width="{TemplateBinding Width}" CornerRadius="2" 
+                                        Background="{DynamicResource CommonBoarder}">
+                                <Track Name="PART_Track" IsDirectionReversed="true">
+                                    <Track.DecreaseRepeatButton>
+                                        <RepeatButton Command="ScrollBar.PageUpCommand" Focusable="False">
+                                            <RepeatButton.Template>
+                                                <ControlTemplate>
+                                                    <Border Background="Transparent"></Border>
+                                                </ControlTemplate>
+                                            </RepeatButton.Template>
+                                        </RepeatButton>
+                                    </Track.DecreaseRepeatButton>
+                                    <Track.Thumb>
+                                        <Thumb Width="{TemplateBinding Width}"  Focusable="False">
+                                            <Thumb.Template>
+                                                <ControlTemplate>
+                                                    <Border Background="Gray"
+                                                                CornerRadius="2" />
+                                                </ControlTemplate>
+                                            </Thumb.Template>
+                                        </Thumb>
+                                    </Track.Thumb>
+                                    <Track.IncreaseRepeatButton>
+                                        <RepeatButton Command="ScrollBar.PageDownCommand" Focusable="False">
+                                            <RepeatButton.Template>
+                                                <ControlTemplate>
+                                                    <Border Background="Transparent"></Border>
+                                                </ControlTemplate>
+                                            </RepeatButton.Template>
+                                        </RepeatButton>
+                                    </Track.IncreaseRepeatButton>
+                                </Track>
+                            </Border>
+                        </ControlTemplate>
+
+                        <Style x:Key="ScrollStyle_Mobile" TargetType="ScrollBar">
+                            <Style.Resources>
+                                <sys:Double x:Key="{x:Static SystemParameters.VerticalScrollBarWidthKey}">6</sys:Double>
+                                <sys:Double x:Key="{x:Static SystemParameters.HorizontalScrollBarHeightKey}">6</sys:Double>
+                            </Style.Resources>
+                            <Style.Triggers>
+                                <Trigger Property="Orientation" Value="Vertical">
+                                    <Setter Property="Width" Value="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}" />
+                                    <Setter Property="Height" Value="Auto" />
+                                    <Setter Property="Margin" Value="0" />
+                                    <Setter Property="Template" Value="{DynamicResource VerticalScrollBar}" />
+                                </Trigger>
+                            </Style.Triggers>
+                        </Style>
+
+
+                        <Style TargetType="ScrollBar" BasedOn="{StaticResource ScrollStyle_Mobile}"/>
+                    </ListBox.Resources>
+
+                    <ListBox.ItemContainerStyle>
+                        <Style TargetType="ListBoxItem" BasedOn="{StaticResource {x:Static pu:StyleKeys.ListBoxItemStyle}}">
+                            <Setter Property="Padding" Value="0"/>
+                            <Setter Property="VerticalContentAlignment" Value="Center"/>
+                            <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
+                            <Setter Property="Height" Value="70"/>
+                            <Setter Property="Margin" Value="0,0,0,1"/>
+                            <Setter Property="BorderThickness" Value="0"/>
+                        </Style>
+                    </ListBox.ItemContainerStyle>
+                </ListBox>
+            </DockPanel>
         </DataTemplate>
     </pu:WindowXCaption.HeaderTemplate>
 
-    <pu:WindowXCaption.ExtendControl>
-        <StackPanel Orientation="Horizontal">
-            <Menu Margin="0" Padding="0" VerticalAlignment="Top"
-                  VerticalContentAlignment="Center" Style="{StaticResource UserMenu}"
-                  
-                  Background="Transparent">
-                <MenuItem x:Name="userMenu" FontSize="16">
-                    <MenuItem 
-                        Icon="&#xe61d;"
-                        Cursor="Hand" FontSize="14" Header="閫�鍑虹櫥褰�"
-                              x:Name="MiLogout"
-                              Click="MenuItem_Click"/>
-                    <MenuItem Cursor="Hand" FontSize="14" 
-                        Icon="&#xe867;"
-                              Header="閲嶆柊鍚姩"
-                              x:Name="MiRestart"
-                              Click="MenuItem_Click"/>
-                </MenuItem>
-            </Menu>
-
-            <Button x:Name="BtnSwitchTheme"
-                FontSize="18"
-                FontFamily="/HMI.UI;component/Resources/Font/#iconfont"
-                Content="&#xe60e;"
-                RenderTransformOrigin="0.5,0.5"
-                Click="Button_Click">
-                <Button.Style>
-                    <Style TargetType="Button" BasedOn="{StaticResource {x:Static pu:WindowXCaption.MinimizeButtonStyleKey}}">
-                        <Setter Property="Focusable" Value="False"/>
-                        <Style.Triggers>
-                            <DataTrigger Binding="{Binding ShowMenu}" Value="True">
-                                <Setter Property="Button.RenderTransform">
-                                    <Setter.Value>
-                                        <RotateTransform x:Name="MyAnimatedTransform" Angle="180" />
-                                    </Setter.Value>
-                                </Setter>
-                            </DataTrigger>
-                        </Style.Triggers>
-                    </Style>
-                </Button.Style>
-            </Button>
-
-            <Button FontSize="18" Click="Button_Click" x:Name="minBtn">
-                <Button.Style>
-                    <Style TargetType="Button" BasedOn="{StaticResource {x:Static pu:WindowXCaption.MinimizeButtonStyleKey}}">
-                        <Setter Property="Focusable" Value="False"/>
-                    </Style>
-                </Button.Style>
-            </Button>
-
-            <Button FontSize="18" Click="Button_Click" x:Name="maxBtn">
-                <Button.Style>
-                    <Style TargetType="Button" BasedOn="{StaticResource {x:Static pu:WindowXCaption.MaximizeButtonStyleKey}}">
-                        <Setter Property="Focusable" Value="False"/>
-                    </Style>
-                </Button.Style>
-            </Button>
-
-            <Button FontSize="18" Click="Button_Click" x:Name="closeBtn">
-                <Button.Style>
-                    <Style TargetType="Button" BasedOn="{StaticResource {x:Static pu:WindowXCaption.CloseButtonStyleKey}}">
-                        <Setter Property="Focusable" Value="False"/>
-                    </Style>
-                </Button.Style>
-            </Button>
-        </StackPanel>
-    </pu:WindowXCaption.ExtendControl>
+    <!--<pu:WindowXCaption.ExtendControl>
+        
+    </pu:WindowXCaption.ExtendControl>-->
 
     <Grid>
         <Grid.RowDefinitions>
diff --git a/WPFProject/PanuonWindow.xaml.cs b/WPFProject/PanuonWindow.xaml.cs
index 71de9ee..f2026f0 100644
--- a/WPFProject/PanuonWindow.xaml.cs
+++ b/WPFProject/PanuonWindow.xaml.cs
@@ -6,18 +6,30 @@
 using HMI.UI.Util;
 using Panuon.WPF.UI;
 using System;
+using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.IO;
+using System.Linq;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Media.Imaging;
 
 namespace WPFProject
 {
+    public class SimpleAlarm
+    {
+        public string Time { get; set; }
+        public string Message { get; set; }
+        public override string ToString()
+        {
+            return $"{Time} {Message}";
+        }
+    }
+
     /// <summary>
     /// PanuonWindow.xaml 鐨勪氦浜掗�昏緫
     /// </summary>
-    public partial class PanuonWindow : WindowX, IPluginContainer
+    public partial class PanuonWindow : WindowX, IPluginContainer,IAlarmShow
     {
         public PanuonWindow()
         {
@@ -34,12 +46,45 @@
             }
         }
 
+        private string welcomeTitle;
+        public string WelcomeTitle
+        {
+            get => welcomeTitle;
+            set
+            {
+                Set(ref welcomeTitle, value);
+            }
+        }
+
+        private ObservableCollection<SimpleAlarm> alarms = new ObservableCollection<SimpleAlarm>();
+        public ObservableCollection<SimpleAlarm> Alarms
+        {
+            get => alarms;
+            set
+            {
+                Set(ref alarms, value);
+            }
+        }
+
+        public int AlarmCount
+        {
+            get => Alarms?.Count ?? 0;
+        }
+
+        public ListBox sb {
+            get;set;
+        }
+
         private void PanuonWindow_Loaded(object sender, RoutedEventArgs e)
         {
             DataContext = this;
             RefrshConfig();
             InitializeComponent();
-            userMenu.Header = this.lang("App.Welcome", UserInfo.User.Name);
+            AlarmShowHelper.Register(this);
+            var sb = WindowXCaption.GetHeaderTemplate(this);
+            //sb.FindName("list",this.Con);
+                
+            WelcomeTitle = this.lang("App.Welcome", UserInfo.User.Name);
             ShowMenu = false;
             Application.Current.MainWindow = this;
             SystemSetting.SystemSettingChanged += SystemSetting_SystemSettingChanged;
@@ -188,6 +233,32 @@
             }
         }
 
+        public bool ShowAlarm(string message)
+        {
+            bool res = false;
+            Dispatcher.Invoke(() =>
+            {
+                if (Alarms.FirstOrDefault(e => e.Message == message) == null)
+                {
+                    var data = new SimpleAlarm
+                    {
+                        Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
+                        Message = message
+                    };
+                    Alarms.Add(data);
+                    sb?.ScrollIntoView(data);
+                    res = true;
+                }
+            });
+            return res;
+        }
+
+        private void list_Loaded(object sender, RoutedEventArgs e)
+        {
+            sb = sender as ListBox;
+            sb.Loaded -= list_Loaded;
+        }
+
         //private void BaseWindow_Loaded(object sender, RoutedEventArgs e)
         //{
         //    var s = (Style)this.Resources["WindowButton"];
diff --git a/WPFProject/Properties/app.manifest b/WPFProject/Properties/app.manifest
index f7c83c6..e1175ad 100644
--- a/WPFProject/Properties/app.manifest
+++ b/WPFProject/Properties/app.manifest
@@ -16,7 +16,7 @@
             濡傛灉浣犵殑搴旂敤绋嬪簭闇�瑕佹铏氭嫙鍖栨潵瀹炵幇鍚戝悗鍏煎鎬э紝鍒欑Щ闄ゆ
             鍏冪礌銆�
         -->
-				<requestedExecutionLevel level="asInvoker" uiAccess="false" />
+				<requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />
 			</requestedPrivileges>
 			<applicationRequestMinimum>
 				<defaultAssemblyRequest permissionSetReference="Custom" />
diff --git a/WPFProject/Utils/MainToastHandler.cs b/WPFProject/Utils/MainToastHandler.cs
index 3572136..9356303 100644
--- a/WPFProject/Utils/MainToastHandler.cs
+++ b/WPFProject/Utils/MainToastHandler.cs
@@ -2,6 +2,7 @@
 using Panuon.WPF.UI;
 using Panuon.WPF.UI.Configurations;
 using HMI.UI.Util;
+using System;
 
 namespace WPFProject.Utils
 {
@@ -15,11 +16,11 @@
             if (window is PanuonWindow pw)
             {
                 double num = 20 + (pw.menuTab.MenuVisibility == Visibility.Visible ? pw.menuTab.Menu.ActualHeight + pw.menuTab.SubMenu.ActualHeight : 0);
-                Toast.Show(pw, message, icon, position, num, durationMs, setting);
+                Show(pw, message, icon, position, num, durationMs, setting);
             }
             else if (window is WindowX wx)
             {
-                Toast.Show(wx, message, icon, position, 0, durationMs, setting);
+                Show(wx, message, icon, position, 0, durationMs, setting);
             }
         }
 
@@ -31,11 +32,11 @@
             if (window is PanuonWindow pw)
             {
                 double num = 20 + (pw.menuTab.MenuVisibility == Visibility.Visible ? pw.menuTab.Menu.ActualHeight + pw.menuTab.SubMenu.ActualHeight : 0);
-                Toast.Show(pw, message, icon, position, num, durationMs, setting);
+                Show(pw, message, icon, position, num, durationMs, setting);
             }
             else if (window is WindowX wx)
             {
-                Toast.Show(wx, message, icon, position, 0, durationMs, setting);
+                Show(wx, message, icon, position, 0, durationMs, setting);
             }
         }
 
@@ -45,11 +46,11 @@
             if (window is PanuonWindow pw)
             {
                 double num = 20 + (pw.menuTab.MenuVisibility == Visibility.Visible ? pw.menuTab.Menu.ActualHeight + pw.menuTab.SubMenu.ActualHeight : 0);
-                Toast.Show(pw, message, icon, position, num, durationMs, setting);
+                Show(pw, message, icon, position, num, durationMs, setting);
             }
             else if (window is WindowX wx)
             {
-                Toast.Show(wx, message, icon, position, 0, durationMs, setting);
+                Show(wx, message, icon, position, 0, durationMs, setting);
             }
         }
 
@@ -59,12 +60,17 @@
             if (window is PanuonWindow pw)
             {
                 double num = 20 + (pw.menuTab.MenuVisibility == Visibility.Visible ? pw.menuTab.Menu.ActualHeight + pw.menuTab.SubMenu.ActualHeight : 0);
-                Toast.Show(pw, message, icon, position, num, durationMs, setting);
+                Show(pw, message, icon, position, num, durationMs, setting);
             }
             else if (window is WindowX wx)
             {
-                Toast.Show(wx, message, icon, position, 0, durationMs, setting);
+                Show(wx, message, icon, position, 0, durationMs, setting);
             }
         }
+
+        private void Show(WindowX window, string message, MessageBoxIcon icon, ToastPosition position, double offset, int durationMs, ToastSetting setting)
+        {
+            Toast.Show(window, message, icon, position, offset, durationMs, setting);
+        }
     }
 }
diff --git a/WPFProject/WPFProject.csproj b/WPFProject/WPFProject.csproj
index 55d3de6..4558ef5 100644
--- a/WPFProject/WPFProject.csproj
+++ b/WPFProject/WPFProject.csproj
@@ -279,7 +279,7 @@
       <DependentUpon>Settings.settings</DependentUpon>
       <DesignTimeSharedInput>True</DesignTimeSharedInput>
     </Compile>
-    <Content Include="Api\Api.txt">
+    <Content Include="Api\SQLite.Interop.dll">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Content>
     <Content Include="Config\Put Your Config Here.txt">
@@ -292,6 +292,9 @@
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Content>
     <Content Include="SuperSocket.SocketEngine.dll">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="瀹夎MSMQ.bat">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Content>
     <Resource Include="Icon\b1.jpg" />
@@ -404,6 +407,12 @@
     <Content Include="Lang\en-US\SeqEditor.json">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Content>
+    <Content Include="Lang\zh-CN\ChangeModel.json">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="Lang\en-US\ChangeModel.json">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
     <None Include="packages.config" />
     <None Include="Properties\app.manifest" />
     <None Include="Properties\Settings.settings">
@@ -455,6 +464,10 @@
       <Project>{de4205c6-5923-46fd-a4b9-65169bf61c75}</Project>
       <Name>HMI.MenuManage</Name>
     </ProjectReference>
+    <ProjectReference Include="..\Plugin.ChangeModel\Plugin.ChangeModel.csproj">
+      <Project>{ef5972b1-dd20-404d-b2b3-37e70e2a478a}</Project>
+      <Name>Plugin.ChangeModel</Name>
+    </ProjectReference>
     <ProjectReference Include="..\Plugin.DataBoard\Plugin.DataBoard.csproj">
       <Project>{be42ab26-3270-43e1-84b8-6929d862ff8d}</Project>
       <Name>Plugin.DataBoard</Name>
@@ -463,9 +476,21 @@
       <Project>{306a83dc-a7db-4057-baa7-477d8756f5d9}</Project>
       <Name>Plugin.DOControl</Name>
     </ProjectReference>
+    <ProjectReference Include="..\Plugin.DODriverControl\Plugin.DODriverControl.csproj">
+      <Project>{61df853d-829a-4426-bef8-0135586005b6}</Project>
+      <Name>Plugin.DODriverControl</Name>
+    </ProjectReference>
     <ProjectReference Include="..\Plugin.JarUI\Plugin.JarUI.csproj">
       <Project>{968fe602-22d6-42bd-981c-601ad518a896}</Project>
       <Name>Plugin.JarUI</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\Plugin.MaterialOpt\Plugin.MaterialOpt.csproj">
+      <Project>{e1f90cbd-a0d4-48da-ab25-7f490935aa4a}</Project>
+      <Name>Plugin.MaterialOpt</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\Plugin.ModelManage\Plugin.ModelManage.csproj">
+      <Project>{76ce4d21-702c-416f-95b4-f4f4c5b2d162}</Project>
+      <Name>Plugin.ModelManage</Name>
     </ProjectReference>
     <ProjectReference Include="..\Plugin.SeqEditor\Plugin.SeqEditor.csproj">
       <Project>{717fd815-ff95-4f30-b4d9-9cea978ca40e}</Project>
@@ -474,10 +499,6 @@
     <ProjectReference Include="..\Plugin.ServoManage\Plugin.ServoManage.csproj">
       <Project>{1853e00f-6865-4bc6-974b-6a5e1c00bafe}</Project>
       <Name>Plugin.ServoManage</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\Plugin.TestBuilder\Plugin.TestBuilder.csproj">
-      <Project>{30201ec1-9a82-48db-94b9-bd5c1190228b}</Project>
-      <Name>Plugin.TestBuilder</Name>
     </ProjectReference>
     <ProjectReference Include="..\Plugin.TestResult\Plugin.TestResult.csproj">
       <Project>{7375b74c-b6bf-4814-9838-e1caacad1897}</Project>
@@ -520,7 +541,8 @@
   <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <PropertyGroup>
-    <PostBuildEvent>
+    <PostBuildEvent>IF "$(ConfigurationName)"=="Release" Confuser.CLI.exe $(TargeDir)..\ConfuserEx\$(TargetFileName).crproj
+
 move /y "$(TargeDir)HMI.Core.dll" $(TargeDir)Libs\HMI.Core.dll"
 move /y "$(TargeDir)HMI.Base.dll" $(TargeDir)Libs\HMI.Base.dll"
 move /y "$(TargeDir)HMI.UI.dll" $(TargeDir)Libs\HMI.UI.dll"
@@ -547,6 +569,10 @@
 move /y "$(TargeDir)Plugin.DataBoard.dll" $(TargeDir)Plugin\Plugin.DataBoard.dll"
 move /y "$(TargeDir)Plugin.JarUI.dll" $(TargeDir)Plugin\Plugin.JarUI.dll"
 move /y "$(TargeDir)Plugin.SeqEditor.dll" $(TargeDir)Plugin\Plugin.SeqEditor.dll"
+move /y "$(TargeDir)Plugin.ChangeModel.dll" $(TargeDir)Plugin\Plugin.ChangeModel.dll"
+move /y "$(TargeDir)Plugin.MaterialOpt.dll" $(TargeDir)Plugin\Plugin.MaterialOpt.dll"
+move /y "$(TargeDir)Plugin.DODriverControl.dll" $(TargeDir)Plugin\Plugin.DODriverControl.dll"
+move /y "$(TargeDir)Plugin.ModelManage.dll" $(TargeDir)Plugin\Plugin.ModelManage.dll"
 
 setlocal enabledelayedexpansion
 
@@ -556,6 +582,12 @@
 rmdir /S /Q "%25destination_folder%25"
 mkdir "%25destination_folder%25"
 
+for %25%25F in ("%25source_folder%25\*") do (
+    if /I "%25%25~nxF"=="System.Data.SQLite.dll" (
+        xcopy "%25source_folder%25\%25%25F" "%25source_folder%25\Api\%25%25F" /Y
+    )
+)
+
 for /d %25%25D in ("%25source_folder%25\*") do (
     if /I not "%25%25~nxD"=="Api" if /I not "%25%25~nxD"=="Config" if /I not "%25%25~nxD"=="Icon" if /I not "%25%25~nxD"=="Lang" if /I not "%25%25~nxD"=="Libs" if /I not "%25%25~nxD"=="Log" if /I not "%25%25~nxD"=="Plugin" if /I not "%25%25~nxD"=="TestPlatform.exe.WebView2" (
         move /Y "%25%25D" "%25destination_folder%25"
@@ -563,7 +595,7 @@
 )
 
 for %25%25F in ("%25source_folder%25\*") do (
-    if /I not "%25%25~nxF"=="$(TargetFileName)" if /I not "%25%25~nxF"=="$(TargetFileName).config" if /I not "%25%25~nxF"=="$(TargetName).pdb" if /I not "%25%25~nxF"=="clear.bat" (
+    if /I not "%25%25~nxF"=="$(TargetFileName)" if /I not "%25%25~nxF"=="$(TargetFileName).config" if /I not "%25%25~nxF"=="$(TargetName).pdb" if /I not "%25%25~nxF"=="clear.bat" if /I not "%25%25~nxF"=="瀹夎MSMQ.bat" (
         move /Y "%25%25F" "%25destination_folder%25"
     )
 )
diff --git a/WPFProject/WPFProject.sln b/WPFProject/WPFProject.sln
index 1e074b0..366f8fa 100644
--- a/WPFProject/WPFProject.sln
+++ b/WPFProject/WPFProject.sln
@@ -65,6 +65,22 @@
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin.SeqEditor", "..\Plugin.SeqEditor\Plugin.SeqEditor.csproj", "{717FD815-FF95-4F30-B4D9-9CEA978CA40E}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin.ChangeModel", "..\Plugin.ChangeModel\Plugin.ChangeModel.csproj", "{EF5972B1-DD20-404D-B2B3-37E70E2A478A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "lmes", "..\..\MesApi\lmes.csproj", "{376CA791-49D8-B4D7-6F98-6E86BD46CAEF}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin.MaterialOpt", "..\Plugin.MaterialOpt\Plugin.MaterialOpt.csproj", "{E1F90CBD-A0D4-48DA-AB25-7F490935AA4A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HMI.MQUtil", "..\SimpleUtil\HMI.MQUtil\HMI.MQUtil.csproj", "{F8B8B6B6-821B-4FF6-B556-9CAB840EA57A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin.DODriverControl", "..\Plugin.DODriverControl\Plugin.DODriverControl.csproj", "{61DF853D-829A-4426-BEF8-0135586005B6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HslCommunication", "..\..\hslCommunicationV12.0.3甯︽簮鐮乨emo\HslCommunication\HslCommunication.csproj", "{AA30059E-7269-415C-9DF2-55C310DD5548}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin.TsSupport", "..\Plugin.TsSupport\Plugin.TsSupport.csproj", "{3068124E-F12B-473E-99A0-2DA5A0385D05}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin.ModelManage", "..\Plugin.ModelManage\Plugin.ModelManage.csproj", "{76CE4D21-702C-416F-95B4-F4F4C5B2D162}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -313,6 +329,70 @@
 		{717FD815-FF95-4F30-B4D9-9CEA978CA40E}.Release|Any CPU.Build.0 = Release|Any CPU
 		{717FD815-FF95-4F30-B4D9-9CEA978CA40E}.Release|x64.ActiveCfg = Release|x64
 		{717FD815-FF95-4F30-B4D9-9CEA978CA40E}.Release|x64.Build.0 = Release|x64
+		{EF5972B1-DD20-404D-B2B3-37E70E2A478A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{EF5972B1-DD20-404D-B2B3-37E70E2A478A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{EF5972B1-DD20-404D-B2B3-37E70E2A478A}.Debug|x64.ActiveCfg = Debug|x64
+		{EF5972B1-DD20-404D-B2B3-37E70E2A478A}.Debug|x64.Build.0 = Debug|x64
+		{EF5972B1-DD20-404D-B2B3-37E70E2A478A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{EF5972B1-DD20-404D-B2B3-37E70E2A478A}.Release|Any CPU.Build.0 = Release|Any CPU
+		{EF5972B1-DD20-404D-B2B3-37E70E2A478A}.Release|x64.ActiveCfg = Release|Any CPU
+		{EF5972B1-DD20-404D-B2B3-37E70E2A478A}.Release|x64.Build.0 = Release|Any CPU
+		{376CA791-49D8-B4D7-6F98-6E86BD46CAEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{376CA791-49D8-B4D7-6F98-6E86BD46CAEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{376CA791-49D8-B4D7-6F98-6E86BD46CAEF}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{376CA791-49D8-B4D7-6F98-6E86BD46CAEF}.Debug|x64.Build.0 = Debug|Any CPU
+		{376CA791-49D8-B4D7-6F98-6E86BD46CAEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{376CA791-49D8-B4D7-6F98-6E86BD46CAEF}.Release|Any CPU.Build.0 = Release|Any CPU
+		{376CA791-49D8-B4D7-6F98-6E86BD46CAEF}.Release|x64.ActiveCfg = Release|Any CPU
+		{376CA791-49D8-B4D7-6F98-6E86BD46CAEF}.Release|x64.Build.0 = Release|Any CPU
+		{E1F90CBD-A0D4-48DA-AB25-7F490935AA4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{E1F90CBD-A0D4-48DA-AB25-7F490935AA4A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{E1F90CBD-A0D4-48DA-AB25-7F490935AA4A}.Debug|x64.ActiveCfg = Debug|x64
+		{E1F90CBD-A0D4-48DA-AB25-7F490935AA4A}.Debug|x64.Build.0 = Debug|x64
+		{E1F90CBD-A0D4-48DA-AB25-7F490935AA4A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{E1F90CBD-A0D4-48DA-AB25-7F490935AA4A}.Release|Any CPU.Build.0 = Release|Any CPU
+		{E1F90CBD-A0D4-48DA-AB25-7F490935AA4A}.Release|x64.ActiveCfg = Release|Any CPU
+		{E1F90CBD-A0D4-48DA-AB25-7F490935AA4A}.Release|x64.Build.0 = Release|Any CPU
+		{F8B8B6B6-821B-4FF6-B556-9CAB840EA57A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{F8B8B6B6-821B-4FF6-B556-9CAB840EA57A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{F8B8B6B6-821B-4FF6-B556-9CAB840EA57A}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{F8B8B6B6-821B-4FF6-B556-9CAB840EA57A}.Debug|x64.Build.0 = Debug|Any CPU
+		{F8B8B6B6-821B-4FF6-B556-9CAB840EA57A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{F8B8B6B6-821B-4FF6-B556-9CAB840EA57A}.Release|Any CPU.Build.0 = Release|Any CPU
+		{F8B8B6B6-821B-4FF6-B556-9CAB840EA57A}.Release|x64.ActiveCfg = Release|Any CPU
+		{F8B8B6B6-821B-4FF6-B556-9CAB840EA57A}.Release|x64.Build.0 = Release|Any CPU
+		{61DF853D-829A-4426-BEF8-0135586005B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{61DF853D-829A-4426-BEF8-0135586005B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{61DF853D-829A-4426-BEF8-0135586005B6}.Debug|x64.ActiveCfg = Debug|x64
+		{61DF853D-829A-4426-BEF8-0135586005B6}.Debug|x64.Build.0 = Debug|x64
+		{61DF853D-829A-4426-BEF8-0135586005B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{61DF853D-829A-4426-BEF8-0135586005B6}.Release|Any CPU.Build.0 = Release|Any CPU
+		{61DF853D-829A-4426-BEF8-0135586005B6}.Release|x64.ActiveCfg = Release|Any CPU
+		{61DF853D-829A-4426-BEF8-0135586005B6}.Release|x64.Build.0 = Release|Any CPU
+		{AA30059E-7269-415C-9DF2-55C310DD5548}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{AA30059E-7269-415C-9DF2-55C310DD5548}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{AA30059E-7269-415C-9DF2-55C310DD5548}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{AA30059E-7269-415C-9DF2-55C310DD5548}.Debug|x64.Build.0 = Debug|Any CPU
+		{AA30059E-7269-415C-9DF2-55C310DD5548}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{AA30059E-7269-415C-9DF2-55C310DD5548}.Release|Any CPU.Build.0 = Release|Any CPU
+		{AA30059E-7269-415C-9DF2-55C310DD5548}.Release|x64.ActiveCfg = Release|Any CPU
+		{AA30059E-7269-415C-9DF2-55C310DD5548}.Release|x64.Build.0 = Release|Any CPU
+		{3068124E-F12B-473E-99A0-2DA5A0385D05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{3068124E-F12B-473E-99A0-2DA5A0385D05}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{3068124E-F12B-473E-99A0-2DA5A0385D05}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{3068124E-F12B-473E-99A0-2DA5A0385D05}.Debug|x64.Build.0 = Debug|Any CPU
+		{3068124E-F12B-473E-99A0-2DA5A0385D05}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{3068124E-F12B-473E-99A0-2DA5A0385D05}.Release|Any CPU.Build.0 = Release|Any CPU
+		{3068124E-F12B-473E-99A0-2DA5A0385D05}.Release|x64.ActiveCfg = Release|Any CPU
+		{3068124E-F12B-473E-99A0-2DA5A0385D05}.Release|x64.Build.0 = Release|Any CPU
+		{76CE4D21-702C-416F-95B4-F4F4C5B2D162}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{76CE4D21-702C-416F-95B4-F4F4C5B2D162}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{76CE4D21-702C-416F-95B4-F4F4C5B2D162}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{76CE4D21-702C-416F-95B4-F4F4C5B2D162}.Debug|x64.Build.0 = Debug|Any CPU
+		{76CE4D21-702C-416F-95B4-F4F4C5B2D162}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{76CE4D21-702C-416F-95B4-F4F4C5B2D162}.Release|Any CPU.Build.0 = Release|Any CPU
+		{76CE4D21-702C-416F-95B4-F4F4C5B2D162}.Release|x64.ActiveCfg = Release|Any CPU
+		{76CE4D21-702C-416F-95B4-F4F4C5B2D162}.Release|x64.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -322,6 +402,7 @@
 		{6735FDB8-B471-41C3-93CB-E8ADBD0989F0} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
 		{6D0AAB08-0F3E-48CE-B755-9D29BC8F1225} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
 		{D59E25D1-9B27-46A1-8438-92661DD23FAE} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+		{F8B8B6B6-821B-4FF6-B556-9CAB840EA57A} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {784CEA1D-A890-4625-B0C1-72294A6B4803}
diff --git "a/WPFProject/\345\256\211\350\243\205MSMQ.bat" "b/WPFProject/\345\256\211\350\243\205MSMQ.bat"
new file mode 100644
index 0000000..4396704
--- /dev/null
+++ "b/WPFProject/\345\256\211\350\243\205MSMQ.bat"
@@ -0,0 +1,5 @@
+;@ECHO on
+title AUTO START Windows function
+color 1F
+start /w pkgmgr /iu:MSMQ-Container;MSMQ-Server
+pause
\ No newline at end of file

--
Gitblit v1.9.1