alielie
2025-08-25 c4a3f8db90530a727ffab77c3bb5bb7d782792bf
上传代码;
98个文件已修改
84个文件已添加
1 文件已重命名
5个文件已删除
13770 ■■■■■ 已修改文件
HMI.AlermInfoManage/AlarmInfoManage.xaml 261 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.AlermInfoManage/AlarmInfoManage.xaml.cs 584 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.AlermInfoManage/AlarmSettingList.xaml 236 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.AlermInfoManage/AlarmSettingList.xaml.cs 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.AlermInfoManage/Entity/AlarmSetting.cs 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.AlermInfoManage/Entity/AlarmText.cs 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.AlermInfoManage/HMI.AlarmInfoManage.csproj 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.AlermInfoManage/Modal/AlarmSettingModal.xaml 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.AlermInfoManage/Modal/AlarmSettingModal.xaml.cs 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.AlermInfoManage/Model/XmlModel.cs 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.AlermInfoManage/SimpleAlarm.xaml 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.AlermInfoManage/SimpleAlarm.xaml.cs 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Base/HMI.Base.csproj 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Base/MaskControl.xaml 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Base/PluginLoader.cs 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Base/PluginMaskControl.cs 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Base/SimpleControl.cs 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Base/SimpleMaskControl.cs 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Base/Themes/Generic.xaml 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Api/AlarmShowHelper.cs 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Api/IAlarmShow.cs 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/DpUtil/PermissionHelper.cs 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Entity/PermissionData.cs 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Entity/UserInfo.cs 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/HMI.Core.csproj 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Util/MSMQHelper.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/app.config 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.DataViewer/DataViewer.xaml 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.IOMonitor/HMI.IOMonitor.csproj 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.IOMonitor/app.config 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.LogViewer/LogViewer.xaml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.LogViewer/LogViewer.xaml.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.MenuManage/MenuManage.xaml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.MenuManage/MenuManage.xaml.cs 140 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.MenuManage/Modal/MenuModal.xaml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.MenuManage/Modal/MenuModal.xaml.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.MenuManage/Modal/RoleModal.xaml.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.MenuManage/Model/MenuManageModel.cs 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.MenuManage/RoleManage.xaml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.MenuManage/UserManage.xaml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/ParameterManage.xaml 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/ParameterSetting.xaml 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/TestItem.xaml 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.SettingControl/SettingControl.xaml 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.SettingControl/SystemConfig.xaml 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Controls/LightBall.cs 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Controls/NumberInput.cs 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Controls/VisualHost.cs 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Converter/DataGridRowNumberConverter.cs 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Converter/ReverseBoolConverter.cs 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/DpUtil/CanvasHelper.cs 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/DpUtil/FormGroupHelper.cs 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/DpUtil/Resize.cs 640 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/EditBox.cs 194 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/HMI.UI.csproj 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/MDI/BottomBar.xaml 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/MDI/BottomBar.xaml.cs 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/MDI/InnerWindow.xaml 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/MDI/InnerWindow.xaml.cs 267 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/MDI/MinInnerWindow.xaml 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/MDI/MinInnerWindow.xaml.cs 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/MDI/Workspace.xaml 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/MDI/Workspace.xaml.cs 366 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/MaskControl.xaml 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/MaskControl.xaml.cs 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/RequirePasswordWindow.xaml 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/RequirePasswordWindow.xaml.cs 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Resources/Font/iconfont.ttf 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Resources/Icon/dot.png 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Resources/Icon/dot.svg 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Resources/Icon/dot2.png 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Style/FormStyle.xaml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Style/MsgBoxStyle.xaml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Style/PanuonDataGrid.xaml 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Style/PanuonListView.xaml 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Style/PanuonResource.xaml 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Style/ScrollBarResource.xaml 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Style/WindowResources.xaml 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/TableComboBox.cs 249 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Themes/Generic.xaml 145 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Util/MsgBoxHelper.cs 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Util/ToastHelper.cs 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UnitTest/HMI.UnitTest.csproj 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UnitTest/MainWindow.xaml.cs 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Util/ExcelHelper.cs 140 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.ChangeModel/AssemblyInfo.cs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.ChangeModel/ChangeModel.xaml 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.ChangeModel/ChangeModel.xaml.cs 430 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.ChangeModel/Entity/CmLogData.cs 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.ChangeModel/Entity/ModelData.cs 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.ChangeModel/Entity/WorkOrderData.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.ChangeModel/Plugin.ChangeModel.csproj 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DOControl/DOControl.xaml 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DOControl/DOControl.xaml.cs 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DOControl/Plugin.DOControl.csproj 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DODriverControl/Api/Dask.cs 801 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DODriverControl/Api/DaskHelper.cs 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DODriverControl/Api/IDIOController.cs 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DODriverControl/Api/ModBusTcpController.cs 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DODriverControl/Api/PCIController.cs 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DODriverControl/AssemblyInfo.cs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DODriverControl/ConfigModal.xaml 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DODriverControl/ConfigModal.xaml.cs 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DODriverControl/DOControl.xaml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DODriverControl/DOControl.xaml.cs 141 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DODriverControl/DataModal.xaml 120 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DODriverControl/DataModal.xaml.cs 177 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DODriverControl/Entity/ConfigData.cs 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DODriverControl/Entity/DIOData.cs 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DODriverControl/Plugin.DODriverControl.csproj 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DODriverControl/SingleControl.xaml 365 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DODriverControl/SingleControl.xaml.cs 568 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DataBoard/DataBoard.xaml.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.JarUI/JarUI.xaml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.JarUI/JarUI.xaml.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.MaterialOpt/AssemblyInfo.cs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.MaterialOpt/MaterialOpt.xaml 251 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.MaterialOpt/MaterialOpt.xaml.cs 429 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.MaterialOpt/Plugin.MaterialOpt.csproj 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.ModelManage/AssemblyInfo.cs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.ModelManage/DataModal.xaml 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.ModelManage/DataModal.xaml.cs 142 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.ModelManage/Entity/ModelData.cs 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.ModelManage/ModelManage.xaml 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.ModelManage/ModelManage.xaml.cs 300 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.ModelManage/Plugin.ModelManage.csproj 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.SeqEditor/Entity/EditablePO/NumValTestEPO.cs 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.SeqEditor/Entity/EditablePO/StringValTestEPO.cs 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.SeqEditor/Entity/StepUI.cs 269 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.SeqEditor/SeqEditor.xaml 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.SeqEditor/SeqEditor.xaml.cs 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.SeqEditor/SettingPanel/ActionPanel.xaml 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.SeqEditor/SettingPanel/LoopPanel.xaml 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.SeqEditor/SettingPanel/LoopPanel.xaml.cs 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.SeqEditor/SettingPanel/StringValTestPanel.xaml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.SeqEditor/Util/ItemData.cs 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.SeqEditor/ViewModel/SeqEditorViewModel.cs 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestBuilder/Plugin.TestBuilder.csproj 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestBuilder/UI/InnerWindow.xaml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestBuilder/UI/InnerWindow.xaml.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestBuilder/UI/Resize.cs 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/DataTable.xaml 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/DataTable.xaml.cs 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Plugin.TestResult.csproj 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/TestResultView.xaml.cs 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/ViewControl.xaml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/ViewControl.xaml.cs 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/app.config 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsExecution/Entity/TestStep.cs 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsExecution/Plugin.TsExecution.csproj 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsExecution/SingleControl.xaml 168 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsExecution/SingleControl.xaml.cs 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsExecution/TsExecution.xaml 305 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsExecution/TsExecution.xaml.cs 533 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsExecution/app.config 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsSupport/Plugin.TsSupport.csproj 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsSupport/Properties/AssemblyInfo.cs 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsSupport/TestAppManager.cs 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SimpleUtil/HMI.MQUtil/HMI.MQUtil.csproj 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SimpleUtil/HMI.MQUtil/MQConnector.cs 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SimpleUtil/HMI.MQUtil/Properties/AssemblyInfo.cs 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SimpleUtil/HMI.SerialData.Util/SerialDataHelper.cs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SimpleUtil/HMI.Simple.Util/HMI.Simple.Util.csproj 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SimpleUtil/HMI.Simple.Util/Parameter.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SimpleUtil/HMI.Simple.Util/ProductModelHelper.cs 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SimpleUtil/HMI.Simple.Util/SqliteHelper.cs 238 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Api/Api.txt 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Api/SQLite.Interop.dll 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/App.xaml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/BuildCmd.bat 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Controls/MainTabControl.xaml 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Controls/MainTabControl.xaml.cs 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Lang/en-US/AlarmInfoManage.json 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Lang/en-US/ChangeModel.json 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Lang/en-US/app.json 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Lang/zh-CN/AlarmInfoManage.json 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Lang/zh-CN/ChangeModel.json 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Lang/zh-CN/DOControl.json 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Lang/zh-CN/app.json 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Login.xaml 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Login.xaml.cs 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/PanuonWindow.xaml 274 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/PanuonWindow.xaml.cs 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Properties/app.manifest 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Utils/MainToastHandler.cs 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/WPFProject.csproj 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/WPFProject.sln 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/安装MSMQ.bat 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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>
HMI.AlermInfoManage/AlarmInfoManage.xaml.cs
@@ -1,15 +1,19 @@
using HMI.AlarmInfoManage.Entity;
using HMI.AlarmInfoManage.Model;
using 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>
        /// ä»£ç ç®¡ç†
        /// </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 @@
        /// å¯¼å…¥æŠ¥è­¦ä»£ç æ–‡æœ¬
        /// </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>
        /// å¯¼å…¥æŠ¥è­¦ä»£ç æ–‡æœ¬
        /// </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");
        }
    }
}
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>
HMI.AlermInfoManage/AlarmSettingList.xaml.cs
@@ -1,7 +1,10 @@
using 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();
                }
HMI.AlermInfoManage/Entity/AlarmSetting.cs
@@ -1,4 +1,5 @@
using HMI.Core.Entity;
using HMI.Core;
using HMI.Core.Entity;
namespace HMI.AlarmInfoManage.Entity
{
@@ -9,16 +10,22 @@
        /// <summary>
        /// å¼€å§‹DB地址
        /// </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; }
    }
}
HMI.AlermInfoManage/Entity/AlarmText.cs
@@ -1,4 +1,5 @@
using System.Xml.Serialization;
using HMI.Core;
using System.Xml.Serialization;
namespace HMI.AlarmInfoManage.Entity
{
@@ -7,42 +8,50 @@
        /// <summary>
        /// æŠ¥è­¦ä»£ç 
        /// </summary>
        [ExcelColumn("组合报警代码")]
        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>
        /// æ¨¡å—报警代码
        /// </summary>
        [ExcelColumn("报警代码")]
        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> 
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" />
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>
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();
        }
HMI.AlermInfoManage/Model/XmlModel.cs
@@ -1,14 +1,5 @@
using HMI.AlarmInfoManage.Entity;
using HMI.Core.Entity;
using HMI.Core.Util;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using 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)
        {
HMI.AlermInfoManage/SimpleAlarm.xaml
New file
@@ -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>
HMI.AlermInfoManage/SimpleAlarm.xaml.cs
New file
@@ -0,0 +1,100 @@
using 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);
        }
    }
}
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>
HMI.Base/MaskControl.xaml
New file
@@ -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>
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;
        }
    }
}
HMI.Base/PluginMaskControl.cs
New file
@@ -0,0 +1,89 @@
using 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);
            }
        }
    }
}
HMI.Base/SimpleControl.cs
New file
@@ -0,0 +1,59 @@
using 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)
        {
        }
    }
}
HMI.Base/SimpleMaskControl.cs
New file
@@ -0,0 +1,79 @@
using 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);
            }
        }
    }
}
HMI.Base/Themes/Generic.xaml
New file
@@ -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>
HMI.Core/Api/AlarmShowHelper.cs
New file
@@ -0,0 +1,24 @@
namespace 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;
        }
    }
}
HMI.Core/Api/IAlarmShow.cs
New file
@@ -0,0 +1,13 @@
using 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);
    }
}
HMI.Core/DpUtil/PermissionHelper.cs
New file
@@ -0,0 +1,47 @@
using 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;
                }
            }
        }
    }
}
HMI.Core/Entity/PermissionData.cs
New file
@@ -0,0 +1,24 @@
using 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>();
    }
}
HMI.Core/Entity/UserInfo.cs
@@ -1,15 +1,22 @@
namespace HMI.Core.Entity
using 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)
        {
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" />
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
                {
HMI.Core/app.config
File was deleted
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>
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>
HMI.IOMonitor/app.config
File was deleted
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>
HMI.LogViewer/LogViewer.xaml.cs
@@ -1,7 +1,6 @@
using 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
{
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>
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;
        }
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>
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;
                }
HMI.MenuManage/Modal/RoleModal.xaml.cs
@@ -1,5 +1,4 @@
using HMI.Core.Exceptions;
using HMI.Core.Util;
using HMI.Core.Util;
using HMI.MenuManage.Entity;
using HMI.MenuManage.Util;
using HMI.UI.DpUtil;
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);
        }
    }
}
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>
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>
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>
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="">
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">
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>
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>
HMI.UI/Controls/LightBall.cs
New file
@@ -0,0 +1,43 @@
using 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);
            }
        }
    }
}
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))
            {
HMI.UI/Controls/VisualHost.cs
New file
@@ -0,0 +1,30 @@
using 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; }
        }
    }
}
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)
HMI.UI/Converter/ReverseBoolConverter.cs
New file
@@ -0,0 +1,27 @@
using 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;
        }
    }
}
HMI.UI/DpUtil/CanvasHelper.cs
File was renamed from Plugin.TestBuilder/UI/CanvasHelper.cs
@@ -1,9 +1,21 @@
using System;
using 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)
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;
        }
    }
}
HMI.UI/DpUtil/Resize.cs
New file
@@ -0,0 +1,640 @@
using 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();
                // å®žçŽ°é€»è¾‘
                // åˆ¤æ–­å±žæ€§è®¾ç½®çš„æ˜¯ä¸æ˜¯true
                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;
                    // åˆå§‹åŒ–thumb
                    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,就把InnerWindowZIndex置顶
                    // TODO å¯ä»¥æ”¹æˆæŽ¥å£æˆ–者DependencyPrope来做
                    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)
                {
                    // ç›´æŽ¥ç»™grid布局,grid内部的thumb会自动布局。
                    _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;
        }
    }
}
HMI.UI/EditBox.cs
New file
@@ -0,0 +1,194 @@
using 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) =>
            {
                // èŽ·å–ç›¸äº¤çš„UI元素
                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);
            }
        }
    }
}
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>
HMI.UI/MDI/BottomBar.xaml
New file
@@ -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>
HMI.UI/MDI/BottomBar.xaml.cs
New file
@@ -0,0 +1,29 @@
using 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;
        }
    }
}
HMI.UI/MDI/InnerWindow.xaml
New file
@@ -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>
HMI.UI/MDI/InnerWindow.xaml.cs
New file
@@ -0,0 +1,267 @@
using 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
    }
}
HMI.UI/MDI/MinInnerWindow.xaml
New file
@@ -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>
HMI.UI/MDI/MinInnerWindow.xaml.cs
New file
@@ -0,0 +1,42 @@
using 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);
        }
    }
}
HMI.UI/MDI/Workspace.xaml
New file
@@ -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>
HMI.UI/MDI/Workspace.xaml.cs
New file
@@ -0,0 +1,366 @@
using 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);
        }
    }
}
HMI.UI/MaskControl.xaml
New file
@@ -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>
HMI.UI/MaskControl.xaml.cs
New file
@@ -0,0 +1,63 @@
using 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;
                    }));
                }
            });
        }
    }
}
HMI.UI/RequirePasswordWindow.xaml
New file
@@ -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>
HMI.UI/RequirePasswordWindow.xaml.cs
New file
@@ -0,0 +1,49 @@
using 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;
        }
    }
}
HMI.UI/Resources/Font/iconfont.ttf
Binary files differ
HMI.UI/Resources/Icon/dot.png
HMI.UI/Resources/Icon/dot.svg
New file
@@ -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>
HMI.UI/Resources/Icon/dot2.png
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>
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>
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}"/>
HMI.UI/Style/PanuonListView.xaml
New file
@@ -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>
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>
HMI.UI/Style/ScrollBarResource.xaml
New file
@@ -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>
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"/>
HMI.UI/TableComboBox.cs
New file
@@ -0,0 +1,249 @@
using 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) =>
            {
                // èŽ·å–ç›¸äº¤çš„UI元素
                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);
            }
        }
    }
}
HMI.UI/Themes/Generic.xaml
New file
@@ -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>
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
{
HMI.UI/Util/ToastHelper.cs
@@ -1,6 +1,6 @@
using 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"));
                }
            }));
        }
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\" />
HMI.UnitTest/MainWindow.xaml.cs
@@ -1,10 +1,10 @@
using 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)
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();
                }
            }
        }
    }
Plugin.ChangeModel/AssemblyInfo.cs
New file
@@ -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)
)]
Plugin.ChangeModel/ChangeModel.xaml
New file
@@ -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>
Plugin.ChangeModel/ChangeModel.xaml.cs
New file
@@ -0,0 +1,430 @@
using 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, "请稍后", "正在刷新...");
                    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, "请稍后", "正在清理...");
        }
        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();
        }
    }
}
Plugin.ChangeModel/Entity/CmLogData.cs
New file
@@ -0,0 +1,13 @@
namespace 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; }
    }
}
Plugin.ChangeModel/Entity/ModelData.cs
New file
@@ -0,0 +1,13 @@
using 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; }
    }
}
Plugin.ChangeModel/Entity/WorkOrderData.cs
New file
@@ -0,0 +1,7 @@
namespace Plugin.ChangeModel.Entity
{
    public class WorkOrderData
    {
        public string Code { get; set; }
    }
}
Plugin.ChangeModel/Plugin.ChangeModel.csproj
New file
@@ -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>
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">
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;
        }
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" />
Plugin.DODriverControl/Api/Dask.cs
New file
@@ -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);
}
Plugin.DODriverControl/Api/DaskHelper.cs
New file
@@ -0,0 +1,132 @@
namespace 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;
        }
    }
}
Plugin.DODriverControl/Api/IDIOController.cs
New file
@@ -0,0 +1,15 @@
namespace 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();
    }
}
Plugin.DODriverControl/Api/ModBusTcpController.cs
New file
@@ -0,0 +1,55 @@
using 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();
        }
    }
}
Plugin.DODriverControl/Api/PCIController.cs
New file
@@ -0,0 +1,49 @@
namespace 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();
        }
    }
}
Plugin.DODriverControl/AssemblyInfo.cs
New file
@@ -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)
)]
Plugin.DODriverControl/ConfigModal.xaml
New file
@@ -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>
Plugin.DODriverControl/ConfigModal.xaml.cs
New file
@@ -0,0 +1,108 @@
using 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();
        }
    }
}
Plugin.DODriverControl/DOControl.xaml
New file
@@ -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>
Plugin.DODriverControl/DOControl.xaml.cs
New file
@@ -0,0 +1,141 @@
using 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;
        }
    }
}
Plugin.DODriverControl/DataModal.xaml
New file
@@ -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>
Plugin.DODriverControl/DataModal.xaml.cs
New file
@@ -0,0 +1,177 @@
using 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;
            }
        }
    }
}
Plugin.DODriverControl/Entity/ConfigData.cs
New file
@@ -0,0 +1,17 @@
namespace 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; }
    }
}
Plugin.DODriverControl/Entity/DIOData.cs
New file
@@ -0,0 +1,89 @@
using 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; }
    }
}
Plugin.DODriverControl/Plugin.DODriverControl.csproj
New file
@@ -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带源码demo\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>
Plugin.DODriverControl/SingleControl.xaml
New file
@@ -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>
Plugin.DODriverControl/SingleControl.xaml.cs
New file
@@ -0,0 +1,568 @@
using 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}");
                }));
            }, "请稍候", "正在重连...");
        }
        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;
            }
        }
    }
}
Plugin.DataBoard/DataBoard.xaml.cs
@@ -197,7 +197,7 @@
                        }
                        else if (snStatusCache[data.sn] == "Passed" && data.status == "Passed")
                        {
                            oneTimePass--;
                            if (oneTimePass > 0) oneTimePass--;
                        }
                    }
                    else
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">
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
                {
Plugin.MaterialOpt/AssemblyInfo.cs
New file
@@ -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)
)]
Plugin.MaterialOpt/MaterialOpt.xaml
New file
@@ -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="解绑" Click="Button_Click" DataContext="{Binding Row}"/>
                                                </StackPanel>
                                            </DataTemplate>
                                        </DataGridTemplateColumn.CellTemplate>
                                    </DataGridTemplateColumn>-->
                                </DataGrid.Columns>
                            </DataGrid>
                        </Grid>
                    </TabItem>
                    <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 EditBtn}" Content="解绑" 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>
Plugin.MaterialOpt/MaterialOpt.xaml.cs
New file
@@ -0,0 +1,429 @@
using 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, "请稍后", "正在查询...");
                    break;
                case "unbindBtn":
                    UnBindMaterial();
                    break;
                case "unbindQryBtn":
                    mask.ShowMask(this, RefreshUnBindLog, null, "请稍后", "正在查询...");
                    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}");
                }));
            }, "请稍后", "正在绑定...");
        }
        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, "确定解绑该物料吗?") != 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("解绑成功");
                    RefreshUnBindLog();
                }
                finally
                {
                    api.DisConnect(ref errMsg);
                }
            },
            (ex) =>
            {
                Dispatcher.BeginInvoke(new Action(() =>
                {
                    MsgBoxHelper.Error(this, $"解绑失败: {ex.Message}");
                }));
            }, "请稍后", "正在解绑...");
        }
        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();
            }
        }
    }
}
Plugin.MaterialOpt/Plugin.MaterialOpt.csproj
New file
@@ -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>
Plugin.ModelManage/AssemblyInfo.cs
New file
@@ -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)
)]
Plugin.ModelManage/DataModal.xaml
New file
@@ -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>
Plugin.ModelManage/DataModal.xaml.cs
New file
@@ -0,0 +1,142 @@
using 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);
        }
    }
}
Plugin.ModelManage/Entity/ModelData.cs
New file
@@ -0,0 +1,19 @@
using 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; }
    }
}
Plugin.ModelManage/ModelManage.xaml
New file
@@ -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>
Plugin.ModelManage/ModelManage.xaml.cs
New file
@@ -0,0 +1,300 @@
using 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;
        }
    }
}
Plugin.ModelManage/Plugin.ModelManage.csproj
New file
@@ -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>
Plugin.SeqEditor/Entity/EditablePO/NumValTestEPO.cs
@@ -1,5 +1,4 @@
using CommunityToolkit.Mvvm.ComponentModel;
using NationalInstruments.TestStand.Interop.API;
using 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;
            }
        }
    }
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);
Plugin.SeqEditor/Entity/StepUI.cs
@@ -1,5 +1,6 @@
using 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;
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>
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 = 
        //}
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>
Plugin.SeqEditor/SettingPanel/LoopPanel.xaml
New file
@@ -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>
Plugin.SeqEditor/SettingPanel/LoopPanel.xaml.cs
New file
@@ -0,0 +1,60 @@
using 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;
        }
    }
}
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>
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
            };
    }
}
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);
        }
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>
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>
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 çš„交互逻辑
Plugin.TestBuilder/UI/Resize.cs
@@ -1,4 +1,5 @@
using System;
using 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
    {
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>
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);
            }
        }
    }
}
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')" />
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();
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>
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");
        }
    }
}
Plugin.TestResult/app.config
File was deleted
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; }
    }
}
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>
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">
Plugin.TsExecution/SingleControl.xaml.cs
@@ -1,7 +1,5 @@
using 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);
            }
        }
    }
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>
Plugin.TsExecution/TsExecution.xaml.cs
@@ -1,9 +1,12 @@
using 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 @@
            //初始化TestStand引擎
            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://传递SocketContext
                        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)
        {
Plugin.TsExecution/app.config
File was deleted
Plugin.TsSupport/Plugin.TsSupport.csproj
New file
@@ -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>
Plugin.TsSupport/Properties/AssemblyInfo.cs
New file
@@ -0,0 +1,33 @@
using 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")]
Plugin.TsSupport/TestAppManager.cs
New file
@@ -0,0 +1,17 @@
using 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;
        }
    }
}
SimpleUtil/HMI.MQUtil/HMI.MQUtil.csproj
New file
@@ -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>
SimpleUtil/HMI.MQUtil/MQConnector.cs
New file
@@ -0,0 +1,110 @@
using 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}";
            //如果不存在mqName消息队列,则创建该mqName消息队列
            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();
        }
    }
}
SimpleUtil/HMI.MQUtil/Properties/AssemblyInfo.cs
New file
@@ -0,0 +1,33 @@
using 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")]
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)
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>
SimpleUtil/HMI.Simple.Util/Parameter.cs
@@ -1,6 +1,6 @@
namespace HMI.Simple.Util
{
    public class ParameterData
    internal class ParameterData
    {
        public string Id { get; set; }
        public string Name { get; set; }
SimpleUtil/HMI.Simple.Util/ProductModelHelper.cs
New file
@@ -0,0 +1,106 @@
using 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();
            }
        }
    }
}
SimpleUtil/HMI.Simple.Util/SqliteHelper.cs
New file
@@ -0,0 +1,238 @@
using 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();
        }
    }
}
WPFProject/Api/Api.txt
File was deleted
WPFProject/Api/SQLite.Interop.dll
Binary files differ
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"/>
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%"
    )
)
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>
WPFProject/Controls/MainTabControl.xaml.cs
@@ -1,6 +1,7 @@
using 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;
        }
    }
}
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}",
WPFProject/Lang/en-US/ChangeModel.json
New file
@@ -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}"
}
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",
WPFProject/Lang/zh-CN/AlarmInfoManage.json
@@ -4,6 +4,7 @@
  "Alm.Code": "编号",
  "Alm.Info": "信息",
  "Alm.Time": "时间",
  "Alm.Description": "报警描述",
  "Alm.OnlySupportXlsm": "仅支持xlsm格式文件",
  "Alm.RowImportFailed": "第{0}行导入异常",
WPFProject/Lang/zh-CN/ChangeModel.json
New file
@@ -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}"
}
WPFProject/Lang/zh-CN/DOControl.json
@@ -7,5 +7,7 @@
  "doc.readOnly": "只读",
  "doc.pressDownValue": "按下值",
  "doc.pressUpValue": "弹起值",
  "doc.controlType": "控件类型"
  "doc.controlType": "控件类型",
  "doc.pressDownText": "按下文本",
  "doc.pressUpText": "弹起文本"
}
WPFProject/Lang/zh-CN/app.json
@@ -87,6 +87,7 @@
  "Test.LimitUp": "上限",
  "Test.Result": "结果",
  "Test.Range": "测试范围",
  "Test.ConsumeTime": "耗时",
  "Test.Condition": "测试条件",
  "Test.UpdateTime": "更新时间",
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>
WPFProject/Login.xaml.cs
@@ -1,4 +1,5 @@
using HMI.Core.Entity;
using 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);
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>
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"];
WPFProject/Properties/app.manifest
@@ -16,7 +16,7 @@
            å¦‚果你的应用程序需要此虚拟化来实现向后兼容性,则移除此
            å…ƒç´ ã€‚
        -->
                <requestedExecutionLevel level="asInvoker" uiAccess="false" />
                <requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />
            </requestedPrivileges>
            <applicationRequestMinimum>
                <defaultAssemblyRequest permissionSetReference="Custom" />
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);
        }
    }
}
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"
    )
)
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带源码demo\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}
WPFProject/°²×°MSMQ.bat
New file
@@ -0,0 +1,5 @@
;@ECHO on
title AUTO START Windows function
color 1F
start /w pkgmgr /iu:MSMQ-Container;MSMQ-Server
pause