alielie
2025-03-03 4c3eb7f334abb8129b57e86f2935d75d0b3070b4
完善UI;新增TestResult;
34个文件已修改
57个文件已添加
1 文件已重命名
5553 ■■■■■ 已修改文件
HMI.AlermInfoManage/AlarmInfoManage_Setting.xaml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.AlermInfoManage/AlarmInfoManage_Setting.xaml.cs 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.AlermInfoManage/Modal/AlarmSettingModal.xaml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.AlermInfoManage/Modal/AlarmSettingModal.xaml.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Api/IConfigManager.cs 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/HMI.Core.csproj 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.DataViewer/DataViewer.xaml 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.DataViewer/DataViewer.xaml.cs 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.DataViewer/DbSettingModal.xaml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.DataViewer/DbSettingModal.xaml.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.IOMonitor/IOMonitor.xaml 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.IOMonitor/IOMonitor.xaml.cs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.IOMonitor/IPManageModal.xaml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.IOMonitor/IPManageModal.xaml.cs 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.LogViewer/HMI.LogViewer.csproj 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.LogViewer/LogViewer.xaml 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.LogViewer/LogViewer.xaml.cs 210 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.LogViewer/Properties/AssemblyInfo.cs 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.LogViewer/Properties/Resources.Designer.cs 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.LogViewer/Properties/Resources.resx 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.LogViewer/Properties/Settings.Designer.cs 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.LogViewer/Properties/Settings.settings 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.LogViewer/packages.config 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/Entity/ParameterData.cs 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/Entity/ProductData.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/HMI.ParameterManage.csproj 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/Model/SqlModel.cs 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/Model/XmlModel.cs 210 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/ParameterManage.xaml 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/ParameterManage.xaml.cs 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/ParameterModal.xaml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/ParameterModal.xaml.cs 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/ProductModal.xaml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/ProductModal.xaml.cs 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.SettingControl/SettingControl.xaml 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.SettingControl/SettingControl.xaml.cs 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.SettingControl/SystemConfig.xaml 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.SettingControl/SystemConfig.xaml.cs 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/DpUtil/DataGridHelper.cs 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/DpUtil/FormGroupHelper.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/HMI.UI.csproj 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Style/FormStyle.xaml 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Style/PanuonResource.xaml 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Util/ToastHelper.cs 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Chart.xaml 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Chart.xaml.cs 314 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/ChartWindow.xaml 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/ChartWindow.xaml.cs 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Constant/CommonConstant.cs 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/DataTable.xaml 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/DataTable.xaml.cs 285 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/InfoTable.xaml 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/InfoTable.xaml.cs 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/InfoWindow.xaml 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/InfoWindow.xaml.cs 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Model/BaseConfig.cs 157 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Model/BaseModel.cs 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Model/CellInfo.cs 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Model/DataTableModel.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Model/Info.cs 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Model/TcpServer.cs 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/OpenTK.dll.config 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Plugin.TestResult.csproj 313 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Properties/AssemblyInfo.cs 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Properties/Resources.Designer.cs 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Properties/Resources.resx 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Properties/Settings.Designer.cs 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Properties/Settings.settings 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Resource/alert.png 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Resource/barcode.png 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Resource/checkIn.png 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Resource/checkInFailed2.png 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Resource/ng2.png 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Resource/ok2.png 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Resource/ready.png 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Resource/testing2.png 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Resource/waiting2.png 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Scanner.xaml 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Scanner.xaml.cs 216 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/SingleCell.xaml 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/SingleCell.xaml.cs 195 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/TestResultView.xaml 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/TestResultView.xaml.cs 383 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Util/CommonCodeParser.cs 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Util/DpUtil.cs 232 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Util/FontSizeConverter.cs 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/ViewControl.xaml 161 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/ViewControl.xaml.cs 288 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/packages.config 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/LoadingWindow.xaml.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/WPFProject.csproj 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/WPFProject.sln 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.AlermInfoManage/AlarmInfoManage_Setting.xaml
@@ -22,8 +22,6 @@
                    </pu:FormGroup>
                </StackPanel>
            </GroupBox>
            <Button Click="Button_Click">保存</Button>
        </StackPanel>
    </Grid>
</UserControl>
HMI.AlermInfoManage/AlarmInfoManage_Setting.xaml.cs
@@ -1,4 +1,5 @@
using HMI.AlarmInfoManage.Entity;
using HMI.Core.Api;
using HMI.Core.Util;
using System.Windows;
using System.Windows.Controls;
@@ -8,7 +9,7 @@
    /// <summary>
    /// AlarmInfoManage_Setting.xaml 的交互逻辑
    /// </summary>
    public partial class AlarmInfoManage_Setting : UserControl
    public partial class AlarmInfoManage_Setting : UserControl, IConfigManager
    {
        public AlarmInfoManage_Setting()
        {
@@ -17,17 +18,26 @@
        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            DataContext = AimSetting.Instance.GetModel();
            ResetConfig();
        }
        private void Button_Click(object sender, RoutedEventArgs e)
        public object GetConfig()
        {
            return AimSetting.Instance;
        }
        public void SaveConfig()
        {
            AimSettingModel model = (AimSettingModel)DataContext;
            AimSetting newSetting = AimSetting.FromModel(model);
            XmlHelper.SerializeToXml(PathHelper.ConfigFolder + "\\AimSetting.xml", model);
        }
        public void ResetConfig()
        {
            DataContext = AimSetting.Instance.GetModel();
        }
    }
}
HMI.AlermInfoManage/Modal/AlarmSettingModal.xaml
@@ -8,6 +8,7 @@
            xmlns:pu="https://opensource.panuon.com/wpf-ui"
            xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
            xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI"
            xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
            mc:Ignorable="d"
            Title="Form"
            SizeToContent="Height"
@@ -26,19 +27,19 @@
            pu:WindowXModalDialog.ButtonPanelMargin="0,30,10,10">
    <Grid Margin="10">
        <StackPanel x:Name="formPanel">
            <pu:FormGroup ui:FormGroupHelper.IsRequired="True"
                                ui:FormGroupHelper.ValidateMethod="ValidateDBAddress"
            <pu:FormGroup dp:FormGroupHelper.IsRequired="True"
                                dp:FormGroupHelper.ValidateMethod="ValidateDBAddress"
                          Header="DB地址" GroupName="MyFormGroup">
                <pu:NumberInput Style="{StaticResource FormNumberInput}" 
                                x:Name="dbAddress" Interval="1"/>
            </pu:FormGroup>
            <pu:FormGroup ui:FormGroupHelper.IsRequired="True"
            <pu:FormGroup dp:FormGroupHelper.IsRequired="True"
              Header="起始地址" GroupName="MyFormGroup">
                <pu:NumberInput Style="{StaticResource FormNumberInput}" x:Name="startAddress" Interval="1"/>
            </pu:FormGroup>
            <pu:FormGroup ui:FormGroupHelper.IsRequired="True"
            <pu:FormGroup dp:FormGroupHelper.IsRequired="True"
              Header="长度" GroupName="MyFormGroup">
                <pu:NumberInput Style="{StaticResource FormNumberInput}" x:Name="length" Interval="1"/>
            </pu:FormGroup>
HMI.AlermInfoManage/Modal/AlarmSettingModal.xaml.cs
@@ -1,6 +1,6 @@
using HMI.AlarmInfoManage.Entity;
using HMI.Core.Util;
using HMI.UI;
using HMI.UI.DpUtil;
using HMI.UI.Util;
using log4net;
using Panuon.WPF.UI;
HMI.Core/Api/IConfigManager.cs
New file
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HMI.Core.Api
{
    public interface IConfigManager
    {
        object GetConfig();
        void SaveConfig();
        void ResetConfig();
    }
}
HMI.Core/HMI.Core.csproj
@@ -80,6 +80,7 @@
    <Reference Include="System.Xml" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Api\IConfigManager.cs" />
    <Compile Include="Api\IConfigRefresh.cs" />
    <Compile Include="Api\IFolderOwner.cs" />
    <Compile Include="Api\IPlugin.cs" />
HMI.DataViewer/DataViewer.xaml
@@ -55,10 +55,10 @@
            </Grid>-->
            <StackPanel Grid.Row="1" Grid.ColumnSpan="2" HorizontalAlignment="Left" Style="{StaticResource ButtonGroup}">
                <TextBlock Style="{StaticResource TitleBox}" Text="开始时间"/>
                <pu:DateTimePicker Width="190" Style="{StaticResource TimePicker}" SelectedDateTime="{x:Null}" x:Name="startTimeInput"/>
                <TextBlock Style="{StaticResource TitleBox}" Text="结束时间"/>
                <pu:DateTimePicker Width="190" Style="{StaticResource TimePicker}" SelectedDateTime="{x:Null}" x:Name="endTimeInput"/>
                <TextBlock Style="{StaticResource TitleBox}" FontSize="{StaticResource ContentFontSize2}" Text="开始时间"/>
                <pu:DateTimePicker Style="{StaticResource TimePicker}" SelectedDateTime="{x:Null}" x:Name="startTimeInput"/>
                <TextBlock Style="{StaticResource TitleBox}" FontSize="{StaticResource ContentFontSize2}" Text="结束时间"/>
                <pu:DateTimePicker Style="{StaticResource TimePicker}" SelectedDateTime="{x:Null}" x:Name="endTimeInput"/>
                <Button Style="{StaticResource PrimaryButton}" pu:ButtonHelper.Icon="&#xe752;" 
                        x:Name="srhBtn" Click="Button_Click">查询</Button>
@@ -66,9 +66,9 @@
                <Button Style="{StaticResource PrimaryButton}" pu:ButtonHelper.Icon="&#xe752;" 
                        x:Name="resetBtn" Click="Button_Click">重置</Button>
                <Menu x:Name="anyBtn" >
                <Menu x:Name="anyBtn" Style="{StaticResource CommonMenu}">
                    <MenuItem Header="分析">
                        <MenuItem Header="CPK" />
                        <MenuItem Header="CPK" Click="MenuItem_Click"/>
                        <MenuItem Header="RandR" />
                    </MenuItem>
                </Menu>
@@ -77,8 +77,9 @@
            <DockPanel Grid.Row="2" Grid.ColumnSpan="2" Margin="0,10,0,0">
                <pu:Drawer DockPanel.Dock="Left" x:Name="DrwMenu" MinWidth="34" MaxWidth="400"
                           Padding="0"
                           ShadowColor="{x:Null}"
                           AnimationDuration="0" BorderThickness="2,2,0,2" BorderBrush="{StaticResource CommonBoarder}">
                           AnimationDuration="0" BorderThickness="2,0,0,2" BorderBrush="{StaticResource CommonBoarder}">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
@@ -90,7 +91,7 @@
                        </Grid.ColumnDefinitions>
                        <DockPanel Grid.ColumnSpan="2" Background="{StaticResource CommonBoarder}">
                            <ToggleButton Grid.Column="1" x:Name="drawerBtn" BorderThickness="0"
                            <ToggleButton x:Name="drawerBtn" BorderThickness="0"
                                          DockPanel.Dock="Right"
                                          Background="Transparent"
                                          Width="34" Height="34" HorizontalAlignment="Right"
@@ -98,7 +99,7 @@
                                          FontFamily="{StaticResource iconfont}"
                                          Content="&#xe60e;" IsChecked="{Binding IsOpen, ElementName=DrwMenu, Mode=TwoWay}">
                                <ToggleButton.Style>
                                    <Style TargetType="ToggleButton">
                                    <Style TargetType="ToggleButton" BasedOn="{StaticResource CommonToggle}">
                                        <Style.Triggers>
                                            <DataTrigger Binding="{Binding IsOpen, ElementName=DrwMenu}" Value="False">
                                                <Setter Property="RenderTransform">
@@ -122,7 +123,7 @@
                                    </Style>
                                </ToggleButton.Style>
                            </ToggleButton>
                            <TextBlock Text="高级查询" FontSize="16" VerticalAlignment="Center" HorizontalAlignment="Center"/>
                        </DockPanel>
@@ -140,9 +141,10 @@
                                <Label Content="选择查询项目"/>
                                <ComboBox Grid.Column="1" x:Name="programInput" SelectionChanged="programInput_SelectionChanged"/>
                            </DockPanel>
                            <DataGrid Margin="10" Grid.Row="1" Grid.ColumnSpan="2" Style="{StaticResource DefaultDataGrid}"
                                      IsReadOnly="False"
                                      SelectionMode="Single"
                                      BorderThickness="0"
                                      ItemsSource="{Binding Datas}">
                                <DataGrid.Columns>
@@ -216,7 +218,12 @@
                    </Grid>
                </pu:Drawer>
                <DataGrid BorderThickness="2" BorderBrush="{StaticResource CommonBoarder}" x:Name="dataTable" Style="{StaticResource DefaultDataGrid}">
                <DataGrid BorderThickness="2" BorderBrush="{StaticResource CommonBoarder}" x:Name="dataTable"
                          Style="{StaticResource DefaultDataGrid}"
                          SelectionUnit="CellOrRowHeader"
                          SelectionMode="Extended"
                          pu:DataGridHelper.CellSelectedBackground="{StaticResource PrimaryColor}"
                          PreviewMouseLeftButtonDown="dataTable_PreviewMouseLeftButtonDown">
                    <DataGrid.Columns>
                        <DataGridTemplateColumn Header="#" IsReadOnly="True" CanUserResize="False">
                            <DataGridTemplateColumn.CellTemplate>
HMI.DataViewer/DataViewer.xaml.cs
@@ -2,6 +2,7 @@
using HMI.Core.Util;
using HMI.DataViewer.Entity;
using HMI.UI.Util;
using Panuon.WPF.UI;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -10,7 +11,9 @@
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Media;
namespace HMI.DataViewer
{
@@ -97,7 +100,7 @@
            }
            DataBase_Setting setting = Map[program];
            string fields = string.Join(",", Datas?.Select(d => $"[{d.Key}]").ToList());
@@ -116,7 +119,7 @@
                conditon += $" AND {timeCol} < '{endTimeInput.Text}'";
            }
            conditon = string.IsNullOrEmpty(conditon) ? string.Empty : $" WHERE {conditon}";
            SqliteHelper helper = null;
            string sql = string.Empty;
            try
@@ -124,14 +127,13 @@
                helper = new SqliteHelper(setting.DBFilePath);
                sql = $"SELECT {fields} FROM {setting.TableName} {conditon}";
                DataTable dt = helper.GetDataTable(sql);
                HMI.UI.DpUtil.DataGridHelper.SetSelectedColumn(dataTable, null);
                dataTable.ItemsSource = dt.DefaultView;
            }
            catch (Exception ex)
            {
                log.Error($"Select Data Failed. SQL: {sql}", ex);
                ToastHelper.Error(Window.GetWindow(this), $"查询失败!{ex.Message}");
            }
            finally
            {
@@ -191,6 +193,7 @@
                setting?.Condition?.Select(d => new QueryData { Key = d.Key, Name = d.Desc, Condition = "=" }).ToList());
            var temp = dataTable.Columns[0];
            HMI.UI.DpUtil.DataGridHelper.SetSelectedColumn(dataTable, null);
            dataTable.Columns.Clear();
            dataTable.Columns.Add(temp);
            foreach (var data in Datas)
@@ -204,11 +207,55 @@
            DataGridTextColumn textColumn = new DataGridTextColumn
            {
                Header = header,
                Binding = new Binding(bindingPath)
                ,
                Binding = new Binding(bindingPath),
                Width = new DataGridLength(1, DataGridLengthUnitType.Star)
            };
            dataTable.Columns.Add(textColumn);
        }
        private void dataTable_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            // 获取点击的元素
            DependencyObject dep = (DependencyObject)e.OriginalSource;
            // 遍历找到 DataGridColumnHeader
            while (dep != null && !(dep is DataGridColumnHeader) && !(dep is DataGridRow))
            {
                dep = VisualTreeHelper.GetParent(dep);
            }
            HMI.UI.DpUtil.DataGridHelper.SetSelectedColumn(dataTable, null);
            if (dep is DataGridColumnHeader columnHeader)
            {
                // 获取列索引
                int columnIndex = columnHeader.Column.DisplayIndex;
                // 清除已有选择
                dataTable.SelectedCells.Clear();
                // 遍历所有行,选中该列的所有单元格
                foreach (var item in dataTable.Items)
                {
                    DataGridCellInfo cellInfo = new DataGridCellInfo(item, columnHeader.Column);
                    dataTable.SelectedCells.Add(cellInfo);
                }
                HMI.UI.DpUtil.DataGridHelper.SetSelectedColumn(dataTable, columnHeader.Column);
                e.Handled = true; // 防止默认行为
            }
            else if (dep is DataGridRow row)
            {
                // 清除已有选择
                dataTable.SelectedCells.Clear();
                dataTable.SelectedItem = row.Item;
                e.Handled = true;
            }
        }
        private void MenuItem_Click(object sender, RoutedEventArgs e)
        {
            DataGridColumn column = HMI.UI.DpUtil.DataGridHelper.GetSelectedColumn(dataTable);
        }
    }
}
HMI.DataViewer/DbSettingModal.xaml
@@ -27,11 +27,11 @@
            pu:WindowXModalDialog.ButtonPanelMargin="0,30,10,10">
    <Grid Margin="10">
        <StackPanel x:Name="formPanel">
            <pu:FormGroup ui:FormGroupHelper.IsRequired="True" Header="名称" GroupName="MyFormGroup">
            <pu:FormGroup dp:FormGroupHelper.IsRequired="True" Header="名称" GroupName="MyFormGroup">
                <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="programInput"/>
            </pu:FormGroup>
            <pu:FormGroup ui:FormGroupHelper.IsRequired="True" Header="数据文件" GroupName="MyFormGroup">
            <pu:FormGroup dp:FormGroupHelper.IsRequired="True" Header="数据文件" GroupName="MyFormGroup">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition/>
@@ -42,7 +42,7 @@
                </Grid>
            </pu:FormGroup>
            <pu:FormGroup ui:FormGroupHelper.IsRequired="True" Header="数据表" GroupName="MyFormGroup">
            <pu:FormGroup dp:FormGroupHelper.IsRequired="True" Header="数据表" GroupName="MyFormGroup">
                <ComboBox Style="{StaticResource FormComboxStyle}" SelectedValuePath="Content" 
                          x:Name="tableNameInput" SelectionChanged="ComboxSelectionChanged"/>
            </pu:FormGroup>
HMI.DataViewer/DbSettingModal.xaml.cs
@@ -1,6 +1,6 @@
using HMI.Core.Util;
using HMI.DataViewer.Entity;
using HMI.UI;
using HMI.UI.DpUtil;
using HMI.UI.Util;
using log4net;
using Microsoft.Win32;
HMI.IOMonitor/IOMonitor.xaml
@@ -62,7 +62,7 @@
                    </Grid.RowDefinitions>
                    <Label Grid.Row="0" Grid.Column="0" Content="DI名称"
                       FontSize="{StaticResource TitleFontSize}"/>
                       FontSize="{StaticResource ContentFontSize}"/>
                    <ComboBox Grid.Row="0" Grid.Column="1" x:Name="diName" 
                          IsEditable="True" Style="{StaticResource FormComboxStyle}"/>
@@ -73,13 +73,13 @@
                        x:Name="diReset" Click="Button_Click">DI重置</Button>
                    <Label Grid.Row="1" Grid.Column="0" Content="DI编号"
       FontSize="{StaticResource TitleFontSize}"/>
       FontSize="{StaticResource ContentFontSize}"/>
                    <ComboBox Grid.Row="1" Grid.Column="1" x:Name="diCode" 
                          IsEditable="True"
                          Style="{StaticResource FormComboxStyle}"/>
                    <Label Grid.Row="2" Grid.Column="0" Content="DI分组"
       FontSize="{StaticResource TitleFontSize}"/>
       FontSize="{StaticResource ContentFontSize}"/>
                    <ComboBox Grid.Row="2" Grid.Column="1" x:Name="diGroup" 
                          IsEditable="True" Style="{StaticResource FormComboxStyle}"/>
@@ -154,7 +154,7 @@
                    </Grid.RowDefinitions>
                    <Label Grid.Row="0" Grid.Column="0" Content="DO名称"
                   FontSize="{StaticResource TitleFontSize}"/>
                   FontSize="{StaticResource ContentFontSize}"/>
                    <ComboBox Grid.Row="0" Grid.Column="1" x:Name="doName"
                          IsEditable="True" Style="{StaticResource FormComboxStyle}"/>
@@ -165,12 +165,12 @@
                        >DO重置</Button>
                    <Label Grid.Row="1" Grid.Column="0" Content="DO编号"
   FontSize="{StaticResource TitleFontSize}"/>
   FontSize="{StaticResource ContentFontSize}"/>
                    <ComboBox Grid.Row="1" Grid.Column="1" x:Name="doCode"
                          IsEditable="True" Style="{StaticResource FormComboxStyle}"/>
                    <Label Grid.Row="2" Grid.Column="0" Content="DO分组"
   FontSize="{StaticResource TitleFontSize}"/>
   FontSize="{StaticResource ContentFontSize}"/>
                    <ComboBox Grid.Row="2" Grid.Column="1" x:Name="doGroup"
                          IsEditable="True" Style="{StaticResource FormComboxStyle}"/>
HMI.IOMonitor/IOMonitor.xaml.cs
@@ -48,20 +48,14 @@
        /// <summary>The dis
        /// DI口对象
        /// </summary>
        private ObservableCollection<DIO> _dis = new ObservableCollection<DIO>() {
            new DIO(null,null,null,false),
            new DIO(null,null,null,true),
        };
        private ObservableCollection<DIO> _dis = new ObservableCollection<DIO>();
        public ObservableCollection<DIO> Dis
        {
            get => _dis;
            set { Set(ref _dis, value); }
        }
        private ObservableCollection<DIO> _dos = new ObservableCollection<DIO>() {
            new DIO(null,null,null,false),
            new DIO(null,null,null,true),
        };
        private ObservableCollection<DIO> _dos = new ObservableCollection<DIO>();
        public ObservableCollection<DIO> Dos
        {
            get => _dos;
HMI.IOMonitor/IPManageModal.xaml
@@ -8,6 +8,7 @@
            xmlns:pu="https://opensource.panuon.com/wpf-ui"
            xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
            xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI"
            xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
            mc:Ignorable="d"
            Title="Form"
            SizeToContent="Height"
@@ -26,12 +27,12 @@
            pu:WindowXModalDialog.ButtonPanelMargin="0,30,10,10">
    <Grid Margin="10">
        <StackPanel x:Name="formPanel">
            <pu:FormGroup ui:FormGroupHelper.IsRequired="True"
            <pu:FormGroup dp:FormGroupHelper.IsRequired="True"
  Header="I点读取标签" GroupName="MyFormGroup">
                <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="iTag"/>
            </pu:FormGroup>
            <pu:FormGroup ui:FormGroupHelper.IsRequired="True"
            <pu:FormGroup dp:FormGroupHelper.IsRequired="True"
  Header="Q点读取标签" GroupName="MyFormGroup">
                <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="qTag"/>
            </pu:FormGroup>
HMI.IOMonitor/IPManageModal.xaml.cs
@@ -1,6 +1,7 @@
using HMI.Core.Util;
using HMI.IOMonitor.Entity;
using HMI.UI;
using HMI.UI.DpUtil;
using HMI.UI.Util;
using log4net;
using Panuon.WPF.UI;
HMI.LogViewer/HMI.LogViewer.csproj
New file
@@ -0,0 +1,132 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{20C23602-B029-4E21-8C11-B9E5F2223237}</ProjectGuid>
    <OutputType>library</OutputType>
    <RootNamespace>HMI.LogViewer</RootNamespace>
    <AssemblyName>HMI.LogViewer</AssemblyName>
    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
    <WarningLevel>4</WarningLevel>
    <Deterministic>true</Deterministic>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
    <DebugSymbols>true</DebugSymbols>
    <OutputPath>bin\x64\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <DebugType>full</DebugType>
    <PlatformTarget>x64</PlatformTarget>
    <LangVersion>7.3</LangVersion>
    <ErrorReport>prompt</ErrorReport>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
    <OutputPath>bin\x64\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <Optimize>true</Optimize>
    <DebugType>pdbonly</DebugType>
    <PlatformTarget>x64</PlatformTarget>
    <LangVersion>7.3</LangVersion>
    <ErrorReport>prompt</ErrorReport>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="log4net, Version=3.0.3.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\log4net.3.0.3\lib\net462\log4net.dll</HintPath>
    </Reference>
    <Reference Include="Panuon.WPF, Version=1.1.2.2, Culture=neutral, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\Panuon.WPF.1.1.2.2\lib\net472\Panuon.WPF.dll</HintPath>
    </Reference>
    <Reference Include="Panuon.WPF.UI, Version=1.2.4.8, Culture=neutral, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\Panuon.WPF.UI.1.2.4.9\lib\net472\Panuon.WPF.UI.dll</HintPath>
    </Reference>
    <Reference Include="System" />
    <Reference Include="System.Configuration" />
    <Reference Include="System.Data" />
    <Reference Include="System.Web" />
    <Reference Include="System.Xml" />
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System.Core" />
    <Reference Include="System.Xml.Linq" />
    <Reference Include="System.Data.DataSetExtensions" />
    <Reference Include="System.Net.Http" />
    <Reference Include="System.Xaml">
      <RequiredTargetFramework>4.0</RequiredTargetFramework>
    </Reference>
    <Reference Include="WindowsBase" />
    <Reference Include="PresentationCore" />
    <Reference Include="PresentationFramework" />
  </ItemGroup>
  <ItemGroup>
    <Page Include="LogViewer.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
    </Page>
    <Compile Include="LogViewer.xaml.cs">
      <DependentUpon>LogViewer.xaml</DependentUpon>
      <SubType>Code</SubType>
    </Compile>
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Properties\AssemblyInfo.cs">
      <SubType>Code</SubType>
    </Compile>
    <Compile Include="Properties\Resources.Designer.cs">
      <AutoGen>True</AutoGen>
      <DesignTime>True</DesignTime>
      <DependentUpon>Resources.resx</DependentUpon>
    </Compile>
    <Compile Include="Properties\Settings.Designer.cs">
      <AutoGen>True</AutoGen>
      <DependentUpon>Settings.settings</DependentUpon>
      <DesignTimeSharedInput>True</DesignTimeSharedInput>
    </Compile>
    <EmbeddedResource Include="Properties\Resources.resx">
      <Generator>ResXFileCodeGenerator</Generator>
      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
    </EmbeddedResource>
    <None Include="packages.config" />
    <None Include="Properties\Settings.settings">
      <Generator>SettingsSingleFileGenerator</Generator>
      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
    </None>
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\HMI.Base\HMI.Base.csproj">
      <Project>{2a6f9720-a890-4036-9d73-41a0dae28341}</Project>
      <Name>HMI.Base</Name>
    </ProjectReference>
    <ProjectReference Include="..\HMI.Core\HMI.Core.csproj">
      <Project>{3e3bf44a-0b69-45c5-9512-528cb48a7106}</Project>
      <Name>HMI.Core</Name>
    </ProjectReference>
    <ProjectReference Include="..\HMI.UI\HMI.UI.csproj">
      <Project>{f17de6b5-af1d-48c6-94a0-6b763a6546a6}</Project>
      <Name>HMI.UI</Name>
    </ProjectReference>
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <PropertyGroup>
    <PostBuildEvent>copy /Y "$(TargetPath)" "$(SolutionDir)bin\$(PlatformName)\$(ConfigurationName)\Plugin\$(TargetFileName)"
copy /Y "$(TargetDir)$(TargetName).pdb" "$(SolutionDir)bin\$(PlatformName)\$(ConfigurationName)\Plugin\$(TargetName).pdb"</PostBuildEvent>
  </PropertyGroup>
</Project>
HMI.LogViewer/LogViewer.xaml
New file
@@ -0,0 +1,126 @@
<hb:PluginControl x:Class="HMI.LogViewer.LogViewer"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
             xmlns:local="clr-namespace:HMI.LogViewer"
             xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
             xmlns:pw="https://opensource.panuon.com/wpf"
             xmlns:pu="https://opensource.panuon.com/wpf-ui"
             xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
             mc:Ignorable="d"
             Loaded="UserControl_Loaded"
             Background="{StaticResource WorkspaceBackground}"
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonResource.xaml"/>
                <ResourceDictionary Source="/HMI.UI;component/Style/FormStyle.xaml"/>
                <ResourceDictionary Source="/HMI.UI;component/Style/Colors.xaml"/>
                <ResourceDictionary Source="/HMI.UI;component/Resources/Font/Font.xaml"/>
            </ResourceDictionary.MergedDictionaries>
            <uic:PathImageConverter x:Key="path2Image"/>
            <uic:DataGridRowNumberConverter x:Key="rowNumber"/>
        </ResourceDictionary>
    </UserControl.Resources>
    <Border Style="{StaticResource CardBoarder}">
        <Grid dp:GridHelper.Space="10" Cursor="">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <TextBlock FontSize="{StaticResource TitleFontSize}"
VerticalAlignment="Center" HorizontalAlignment="Left" Text="日志查询"/>
            <DockPanel Grid.Row="1" Cursor="">
                <StackPanel DockPanel.Dock="Right" Style="{StaticResource ButtonGroup}">
                    <Button Style="{StaticResource PrimaryButton}" pu:ButtonHelper.Icon="&#xe631;"
                            x:Name="queryBtn" Click="Button_Click">查询</Button>
                    <Button Style="{StaticResource PrimaryButton}" pu:ButtonHelper.Icon="&#xe631;"
                            x:Name="resetBtn" Click="Button_Click" Cursor="Hand">重置</Button>
                    <Button x:Name="exportBtn"
                            Style="{StaticResource PrimaryButton}" Click="Button_Click">导出</Button>
                </StackPanel>
                <StackPanel Style="{StaticResource ButtonGroup}" HorizontalAlignment="Left">
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="级别"/>
                    <ComboBox Style="{StaticResource FormComboxStyle}" x:Name="levelInput"
                              Width="180"
                              SelectedValuePath="Content">
                        <ComboBoxItem Content="info"/>
                        <ComboBoxItem Content="warn"/>
                        <ComboBoxItem Content="error"/>
                    </ComboBox>
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="开始时间"/>
                    <pu:DateTimePicker Style="{StaticResource TimePicker}" x:Name="startTimeInput"/>
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="结束时间"/>
                    <pu:DateTimePicker Style="{StaticResource TimePicker}" x:Name="endTimeInput"/>
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="来源"/>
                    <!--<ComboBox Style="{StaticResource FormComboxStyle}" x:Name="sourceInput" Width="180"></ComboBox>-->
                    <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="sourceInput" Width="180"/>
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="实时刷新"/>
                    <CheckBox x:Name="autoRfhInput" HorizontalAlignment="Center" VerticalAlignment="Center"
                              IsChecked="{Binding AutoRfh,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                              />
                </StackPanel>
            </DockPanel>
            <DataGrid Grid.Row="2" x:Name="table" Style="{StaticResource DefaultDataGrid}">
                <DataGrid.RowStyle>
                    <Style TargetType="DataGridRow" BasedOn="{StaticResource {x:Static pu:StyleKeys.DataGridRowStyle}}">
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding type}" Value="warn">
                                <Setter Property="Background" Value="{StaticResource WarningColor}"/>
                                <Setter Property="Foreground" Value="Black"/>
                            </DataTrigger>
                            <DataTrigger Binding="{Binding type}" Value="error">
                                <Setter Property="Background" Value="{StaticResource DangerColor}"/>
                                <Setter Property="Foreground" Value="Black"/>
                            </DataTrigger>
                            <Trigger Property="IsSelected" Value="True">
                                <Setter Property="Background" Value="Transparent"/>
                            </Trigger>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="Background" Value="Transparent"/>
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </DataGrid.RowStyle>
                <DataGrid.Columns>
                    <DataGridTemplateColumn Header="" Width="Auto" CanUserResize="False">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <CheckBox VerticalAlignment="Center" IsChecked="{Binding Selected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                    <DataGridTemplateColumn Header="#" CanUserResize="False">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Converter={StaticResource rowNumber},
    RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGridRow},AncestorLevel=1}}"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                    <DataGridTextColumn Width="*" Binding="{Binding type}" Header="级别" />
                    <DataGridTextColumn Width="*" Binding="{Binding time}" Header="时间" />
                    <DataGridTextColumn Width="*" Binding="{Binding source}" Header="来源" />
                    <DataGridTextColumn Width="*" Binding="{Binding text}" Header="文本" />
                </DataGrid.Columns>
            </DataGrid>
        </Grid>
    </Border>
</hb:PluginControl>
HMI.LogViewer/LogViewer.xaml.cs
New file
@@ -0,0 +1,210 @@
using HMI.Base;
using HMI.Core.Util;
using HMI.UI.Util;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
namespace HMI.LogViewer
{
    /// <summary>
    /// UserControl1.xaml 的交互逻辑
    /// </summary>
    public partial class LogViewer : PluginControl
    {
        private string logDir;
        private string todayLogFile;
        private bool autoRfh;
        public bool AutoRfh
        {
            get { return autoRfh; }
            set
            {
                Set(ref autoRfh, value);
                if (autoRfhTimer != null) autoRfhTimer.Enabled = value;
            }
        }
        private Timer autoRfhTimer;
        public LogViewer()
        {
            string logDir = Path.Combine(PathHelper.AppFolder, "Log");
            todayLogFile = Path.Combine(logDir, $"log{DateTime.Now.ToString("yyyy-MM-dd")}.db");
            if (!Directory.Exists(logDir))
            {
                Directory.CreateDirectory(logDir);
            }
            this.logDir = logDir;
            DataContext = this;
            InitializeComponent();
        }
        public override void InitFinish()
        {
            base.InitFinish();
            string baseFile = Path.Combine(logDir, "log.db");
            try
            {
                if (!File.Exists(baseFile))
                {
                    SqliteHelper.CreateDatabase(baseFile);
                }
                SqliteHelper sqliteHelper = new SqliteHelper(baseFile);
                sqliteHelper.ExecuteSql(@"CREATE TABLE IF NOT EXISTS log (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    type TEXT NOT NULL,
                    text TEXT NOT NULL,
                    source TEXT,
                    time TIME
                    );");
            }
            catch (Exception ex)
            {
                log.Error("Create Log File Failed.", ex);
            }
        }
        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            autoRfhTimer = new Timer
            {
                Enabled = false,
                Interval = 1000
            };
            autoRfhTimer.Elapsed += AutoRfhTimer_Elapsed;
            Loaded -= UserControl_Loaded;
        }
        private void AutoRfhTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            Refresh100items();
        }
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Button btn = sender as Button;
            switch (btn.Name)
            {
                case "queryBtn":
                    QueryLog();
                    break;
                case "resetBtn":
                    QueryReset();
                    break;
                case "exportBtn":
                    break;
                default:
                    break;
            }
        }
        private void QueryLog()
        {
            try
            {
                queryBtn.IsEnabled = false;
                autoRfhInput.IsEnabled = false;
                Filtrate();
                queryBtn.IsEnabled = true;
                autoRfhInput.IsEnabled = true;
            }
            catch (Exception ex)
            {
                log.Error("Log Query Failed.", ex);
                this.Error($"查询失败!{ex.Message}");
            }
        }
        private void QueryReset()
        {
            levelInput.Text = null;
            startTimeInput.SelectedDateTime = null;
            endTimeInput.SelectedDateTime = null;
            startTimeInput.Text = null;
            table.ItemsSource = null;
        }
        private void Filtrate()
        {
            string str = LoadSql();
            DataTable dt = new DataTable();
            var files = Directory.GetFiles(logDir, "*.db");
            foreach (string file in files)
            {
                SqliteHelper sql = new SqliteHelper(file);
                dt.Merge(sql.GetDataTable(str));
                sql.Close();
            }
            dt.DefaultView.Sort = "time Desc";
            table.ItemsSource = dt.DefaultView;
        }
        /// <summary>
        /// 加载界面判断数据库语句
        /// </summary>
        /// <returns> str数据库的语句 </returns>
        private string LoadSql()
        {
            string sql = " select type,time,source,text from log";
            List<string> datas = new List<string>();
            if (!string.IsNullOrEmpty(levelInput.Text))
            {
                datas.Add($"type='{levelInput.Text}'");
            }
            if (!string.IsNullOrEmpty(startTimeInput.Text))
            {
                datas.Add($"time>='{startTimeInput.Text}'");
            }
            if (!string.IsNullOrEmpty(endTimeInput.Text))
            {
                datas.Add($"time<='{endTimeInput.Text}'");
            }
            if (!string.IsNullOrEmpty(sourceInput.Text))
            {
                datas.Add($"source='{sourceInput.Text}'");
            }
            string condition = string.Join(" AND ", datas);
            condition = string.IsNullOrEmpty(condition) ? string.Empty : $" WHERE {condition}";
            sql = $"{sql}{condition} order by time desc";
            return sql;
        }
        /// <summary>
        /// 刷新100条数据
        /// </summary>
        private void Refresh100items()
        {
            try
            {
                if (!File.Exists(todayLogFile)) return;
                string str = "select type,time,source,text from log order by time desc limit 100";
                SqliteHelper sql = new SqliteHelper(todayLogFile);
                var dt = sql.GetDataTable(str);
                this.Dispatcher.BeginInvoke((Action)delegate ()
                {
                    table.ItemsSource = dt.DefaultView;
                });
            }
            catch (Exception ex)
            {
                log.Error("Log Auto Refresh Failed.", ex);
                this.Error($"自动刷新失败!{ex.Message}");
            }
        }
    }
}
HMI.LogViewer/Properties/AssemblyInfo.cs
New file
@@ -0,0 +1,52 @@
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
// 有关程序集的一般信息由以下
// 控制。更改这些特性值可修改
// 与程序集关联的信息。
[assembly: AssemblyTitle("HMI.LogViewer")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("HMI.LogViewer")]
[assembly: AssemblyCopyright("Copyright ©  2025")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// 将 ComVisible 设置为 false 会使此程序集中的类型
//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
//请将此类型的 ComVisible 特性设置为 true。
[assembly: ComVisible(false)]
//若要开始生成可本地化的应用程序,请设置
//.csproj 文件中的 <UICulture>CultureYouAreCodingWith</UICulture>
//在 <PropertyGroup> 中。例如,如果你使用的是美国英语。
//使用的是美国英语,请将 <UICulture> 设置为 en-US。  然后取消
//对以下 NeutralResourceLanguage 特性的注释。  更新
//以下行中的“en-US”以匹配项目文件中的 UICulture 设置。
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
[assembly:ThemeInfo(
    ResourceDictionaryLocation.None, //主题特定资源词典所处位置
                             //(未在页面中找到资源时使用,
                             //或应用程序资源字典中找到时使用)
    ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置
                                      //(未在页面中找到资源时使用,
                                      //、应用程序或任何主题专用资源字典中找到时使用)
)]
// 程序集的版本信息由下列四个值组成:
//
//      主版本
//      次版本
//      生成号
//      修订号
//
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
HMI.LogViewer/Properties/Resources.Designer.cs
New file
@@ -0,0 +1,62 @@
//------------------------------------------------------------------------------
// <auto-generated>
//     此代码由工具生成。
//     运行时版本: 4.0.30319.42000
//
//     对此文件的更改可能导致不正确的行为,如果
//     重新生成代码,则所做更改将丢失。
// </auto-generated>
//------------------------------------------------------------------------------
namespace HMI.LogViewer.Properties {
    /// <summary>
    ///   强类型资源类,用于查找本地化字符串等。
    /// </summary>
    // 此类是由 StronglyTypedResourceBuilder
    // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
    // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
    // (以 /str 作为命令选项),或重新生成 VS 项目。
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
    internal class Resources {
        private static global::System.Resources.ResourceManager resourceMan;
        private static global::System.Globalization.CultureInfo resourceCulture;
        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        internal Resources() {
        }
        /// <summary>
        ///   返回此类使用的缓存 ResourceManager 实例。
        /// </summary>
        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
        internal static global::System.Resources.ResourceManager ResourceManager {
            get {
                if ((resourceMan == null)) {
                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("HMI.LogViewer.Properties.Resources", typeof(Resources).Assembly);
                    resourceMan = temp;
                }
                return resourceMan;
            }
        }
        /// <summary>
        ///   重写当前线程的 CurrentUICulture 属性,对
        ///   使用此强类型资源类的所有资源查找执行重写。
        /// </summary>
        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
        internal static global::System.Globalization.CultureInfo Culture {
            get {
                return resourceCulture;
            }
            set {
                resourceCulture = value;
            }
        }
    }
}
HMI.LogViewer/Properties/Resources.resx
New file
@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
  <!--
    Microsoft ResX Schema
    Version 2.0
    The primary goals of this format is to allow a simple XML format
    that is mostly human readable. The generation and parsing of the
    various data types are done through the TypeConverter classes
    associated with the data types.
    Example:
    ... ado.net/XML headers & schema ...
    <resheader name="resmimetype">text/microsoft-resx</resheader>
    <resheader name="version">2.0</resheader>
    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
        <value>[base64 mime encoded serialized .NET Framework object]</value>
    </data>
    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
        <comment>This is a comment</comment>
    </data>
    There are any number of "resheader" rows that contain simple
    name/value pairs.
    Each data row contains a name, and value. The row also contains a
    type or mimetype. Type corresponds to a .NET class that support
    text/value conversion through the TypeConverter architecture.
    Classes that don't support this are serialized and stored with the
    mimetype set.
    The mimetype is used for serialized objects, and tells the
    ResXResourceReader how to depersist the object. This is currently not
    extensible. For a given mimetype the value must be set accordingly:
    Note - application/x-microsoft.net.object.binary.base64 is the format
    that the ResXResourceWriter will generate, however the reader can
    read any of the formats listed below.
    mimetype: application/x-microsoft.net.object.binary.base64
    value   : The object must be serialized with
            : System.Serialization.Formatters.Binary.BinaryFormatter
            : and then encoded with base64 encoding.
    mimetype: application/x-microsoft.net.object.soap.base64
    value   : The object must be serialized with
            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
            : and then encoded with base64 encoding.
    mimetype: application/x-microsoft.net.object.bytearray.base64
    value   : The object must be serialized into a byte array
            : using a System.ComponentModel.TypeConverter
            : and then encoded with base64 encoding.
    -->
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xsd:element name="root" msdata:IsDataSet="true">
      <xsd:complexType>
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="metadata">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" />
              <xsd:attribute name="type" type="xsd:string" />
              <xsd:attribute name="mimetype" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="assembly">
            <xsd:complexType>
              <xsd:attribute name="alias" type="xsd:string" />
              <xsd:attribute name="name" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="data">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="resheader">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" />
            </xsd:complexType>
          </xsd:element>
        </xsd:choice>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>
  <resheader name="resmimetype">
    <value>text/microsoft-resx</value>
  </resheader>
  <resheader name="version">
    <value>2.0</value>
  </resheader>
  <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <resheader name="writer">
    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
</root>
HMI.LogViewer/Properties/Settings.Designer.cs
New file
@@ -0,0 +1,30 @@
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.42000
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace HMI.LogViewer.Properties
{
    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
    {
        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
        public static Settings Default
        {
            get
            {
                return defaultInstance;
            }
        }
    }
}
HMI.LogViewer/Properties/Settings.settings
New file
@@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
  <Profiles>
    <Profile Name="(Default)" />
  </Profiles>
  <Settings />
</SettingsFile>
HMI.LogViewer/packages.config
New file
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="log4net" version="3.0.3" targetFramework="net472" />
  <package id="Panuon.WPF" version="1.1.2.2" targetFramework="net472" />
  <package id="Panuon.WPF.UI" version="1.2.4.9" targetFramework="net472" />
</packages>
HMI.ParameterManage/Entity/ParameterData.cs
@@ -1,12 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HMI.Core.Entity;
namespace HMI.ParameterManage.Entity
{
    public class ParameterData
    public class ParameterData : SelectBase
    {
        public string Id { get; set; }
        public string Name { get; set; }
HMI.ParameterManage/Entity/ProductData.cs
@@ -1,8 +1,12 @@

using HMI.Core.Entity;
using System.Xml.Serialization;
namespace HMI.ParameterManage.Entity
{
    public class ProductData
    public class ProductData:SelectBase
    {
        [XmlIgnore]
        public string Id { get; set; }
        public string Name { get; set; }
        public bool IsMaster { get; set; }
HMI.ParameterManage/HMI.ParameterManage.csproj
@@ -81,11 +81,12 @@
    <Compile Include="ProductModal.xaml.cs">
      <DependentUpon>ProductModal.xaml</DependentUpon>
    </Compile>
    <Compile Include="Model\XmlModel.cs" />
    <Page Include="ParameterManage.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
    </Page>
    <Compile Include="ControlModel.cs" />
    <Compile Include="Model\SqlModel.cs" />
    <Compile Include="Entity\ParameterData.cs" />
    <Compile Include="Entity\ProductData.cs" />
    <Compile Include="ParameterManage.xaml.cs">
HMI.ParameterManage/Model/SqlModel.cs
File was renamed from HMI.ParameterManage/ControlModel.cs
@@ -1,16 +1,40 @@
using System.Collections.ObjectModel;
using System.Data;
using System.IO;
using HMI.Core.Entity;
using HMI.Core.Util;
using HMI.ParameterManage.Entity;
namespace HMI.ParameterManage
namespace HMI.ParameterManage.Model
{
    public class ControlModel : NotifyBase
    public class SqlModel : NotifyBase
    {
        public ControlModel(SqliteHelper sqliteHelper)
        public SqlModel(string baseFolder)
        {
            SqliteHelper = sqliteHelper;
            string dbpath = Path.Combine(baseFolder, "ParameterManage.db");
            if (!File.Exists(dbpath))
            {
                SqliteHelper.CreateDatabase(dbpath);
            }
            SqliteHelper = new SqliteHelper(dbpath);
            SqliteHelper.ExecuteSql(@"CREATE TABLE IF NOT EXISTS product (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    name TEXT NOT NULL,
                    isMaster INTEGER,
                    parentId INTEGER,
                    UNIQUE(name)
                    );
CREATE TABLE IF NOT EXISTS parameter (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    name TEXT NOT NULL,
                    value TEXT,
                    unit TEXT,
                    description TEXT,
                    pGroup TEXT,
                    productName TEXT,
                    UNIQUE(productName,name)
                    );");
        }
        public SqliteHelper SqliteHelper { get; private set; }
HMI.ParameterManage/Model/XmlModel.cs
New file
@@ -0,0 +1,210 @@
using HMI.Core.Entity;
using HMI.Core.Util;
using HMI.ParameterManage.Entity;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
namespace HMI.ParameterManage.Model
{
    public class XmlModel : NotifyBase
    {
        private string ProductSettingFile;
        private string ParameterSettingFolder;
        public XmlModel(string baseFolder)
        {
            ProductSettingFile = Path.Combine(baseFolder, "Product.setting");
            ParameterSettingFolder = Path.Combine(baseFolder, "Parameter");
            if (!Directory.Exists(ParameterSettingFolder))
            {
                Directory.CreateDirectory(ParameterSettingFolder);
            }
        }
        private ObservableCollection<ProductData> products;
        public ObservableCollection<ProductData> Products
        {
            get { return products; }
            set { Set(ref products, value); }
        }
        private ObservableCollection<ParameterData> parameters;
        public ObservableCollection<ParameterData> Parameters
        {
            get { return parameters; }
            set { Set(ref parameters, value); }
        }
        private ProductData selectedProduct;
        public ProductData SelectedProduct
        {
            get { return selectedProduct; }
            set
            {
                Set(ref selectedProduct, value);
                LoadParameters();
            }
        }
        private ParameterData selectedParameter;
        public ParameterData SelectedParameter
        {
            get { return selectedParameter; }
            set
            {
                Set(ref selectedParameter, value);
            }
        }
        public bool ValidateProductName(object value, out string msg)
        {
            msg = null;
            string name = value.ToString();
            if (Products == null) return true;
            var temp = Products.FirstOrDefault(p => p.Name == name);
            if (temp != null && temp != SelectedProduct)
            {
                msg = "产品名称不能重复";
                return false;
            }
            return true;
        }
        public bool ValidateParameterName(object value, out string msg)
        {
            msg = null;
            string name = value.ToString();
            if (Products == null) return true;
            var temp = Parameters.FirstOrDefault(p => p.Name == name);
            if (temp != null && temp != SelectedParameter)
            {
                msg = "参数名称不能重复";
                return false;
            }
            return true;
        }
        public void LoadProducts()
        {
            ObservableCollection<ProductData> products;
            if (File.Exists(ProductSettingFile))
            {
                products = XmlHelper.DeserializeFromXml<ObservableCollection<ProductData>>(ProductSettingFile);
            }
            else
            {
                products = new ObservableCollection<ProductData>();
            }
            Products = products;
        }
        public void LoadParameters()
        {
            ObservableCollection<ParameterData> parameters = new ObservableCollection<ParameterData>();
            if (SelectedProduct != null)
            {
                string fileName = $"Parameter_{SelectedProduct.Name}.setting";
                string parameterSettingFile = Path.Combine(ParameterSettingFolder, fileName);
                if (File.Exists(parameterSettingFile))
                {
                    parameters = XmlHelper.DeserializeFromXml<ObservableCollection<ParameterData>>(parameterSettingFile);
                }
            }
            Parameters = parameters;
        }
        public void SaveOrUpdateData(ProductData data)
        {
            int index = Products.IndexOf(data);
            if (index > -1)
            {
                UpdateData(index, data);
            }
            else
            {
                InsertData(data);
            }
        }
        public void SaveOrUpdateParameter(ParameterData data)
        {
            int index = Parameters.IndexOf(data);
            if (index > -1)
            {
                UpdateParameter(index, data);
            }
            else
            {
                InsertParameter(data);
            }
        }
        public void InsertData(ProductData data)
        {
            var temp = XmlHelper.DeepClone(Products);
            temp.Add(data);
            XmlHelper.SerializeToXml(ProductSettingFile, temp);
        }
        public void UpdateData(int index, ProductData data)
        {
            var temp = XmlHelper.DeepClone(Products);
            temp[index] = data;
            XmlHelper.SerializeToXml(ProductSettingFile, temp);
        }
        public void InsertParameter(ParameterData data)
        {
            var temp = XmlHelper.DeepClone(Parameters);
            temp.Add(data);
            string fileName = Path.Combine(ParameterSettingFolder, $"Parameter_{SelectedProduct.Name}.setting");
            XmlHelper.SerializeToXml(fileName, temp);
        }
        public void UpdateParameter(int index, ParameterData data)
        {
            var temp = XmlHelper.DeepClone(Parameters);
            temp[index] = data;
            string fileName = Path.Combine(ParameterSettingFolder, $"Parameter_{SelectedProduct.Name}.setting");
            XmlHelper.SerializeToXml(fileName, temp);
        }
        public void DeleteProduct(ProductData data)
        {
            int index = Products.IndexOf(data);
            if (index > -1)
            {
                var temp = Products.Where(p => p != data).ToList();
                XmlHelper.SerializeToXml(ProductSettingFile, temp);
            }
        }
        public void DeleteProducts()
        {
            var temp = Products.Where(p => !p.IsSelected).ToList();
            XmlHelper.SerializeToXml(ProductSettingFile, temp);
        }
        public void DeleteParameter(ParameterData data)
        {
            int index = Parameters.IndexOf(data);
            if (index > -1)
            {
                var temp = Parameters.Where(p => p != data).ToList();
                string fileName = Path.Combine(ParameterSettingFolder, $"Parameter_{SelectedProduct.Name}.setting");
                XmlHelper.SerializeToXml(fileName, temp);
            }
        }
        public void DeleteParameters()
        {
            var temp = Parameters.Where(p => !p.IsSelected).ToList();
            string fileName = Path.Combine(ParameterSettingFolder, $"Parameter_{SelectedProduct.Name}.setting");
            XmlHelper.SerializeToXml(fileName, temp);
        }
    }
}
HMI.ParameterManage/ParameterManage.xaml
@@ -17,29 +17,38 @@
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="2*"/>
            <ColumnDefinition Width="8*"/>
            <ColumnDefinition Width="3*"/>
            <ColumnDefinition Width="7*"/>
        </Grid.ColumnDefinitions>
        <Border Style="{StaticResource CardBoarder}" Margin="0,0,2,0">
            <StackPanel>
                <DockPanel Margin="0,0,0,10" Cursor="">
                    <Button DockPanel.Dock="Right" x:Name="delBatchBtn" pu:ButtonHelper.Icon="&#xe60f;" Style="{StaticResource DangerButton}">删除</Button>
                    <Button DockPanel.Dock="Right" Style="{StaticResource PrimaryButton}"
                        pu:ButtonHelper.Icon="&#xe631;" Margin="0,0,10,0" x:Name="refreshBtn" Click="Button_Click">刷新</Button>
                    <Button DockPanel.Dock="Right" Style="{StaticResource PrimaryButton}"
                        pu:ButtonHelper.Icon="&#xe61a;" Margin="0,0,10,0" x:Name="addBtn" Click="Button_Click">新增</Button>
            <Grid dp:GridHelper.Space="10">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition />
                </Grid.RowDefinitions>
                <DockPanel>
                    <StackPanel DockPanel.Dock="Right" Style="{StaticResource ButtonGroup}">
                        <Button Style="{StaticResource PrimaryButton}"
        pu:ButtonHelper.Icon="&#xe61a;" x:Name="addBtn" Click="Button_Click">新增</Button>
                        <Button Style="{StaticResource PrimaryButton}"
pu:ButtonHelper.Icon="&#xe631;" x:Name="refreshBtn" Click="Button_Click">刷新</Button>
                        <Button x:Name="delBatchBtn"
                            pu:ButtonHelper.Icon="&#xe60f;" Style="{StaticResource DangerButton}"
                                Click="Button_Click">删除</Button>
                    </StackPanel>
                    <TextBlock DockPanel.Dock="Left" FontSize="{StaticResource TitleFontSize}" 
                       VerticalAlignment="Center" HorizontalAlignment="Left" Text="产品列表"/>
VerticalAlignment="Center" HorizontalAlignment="Left" Text="产品列表"/>
                </DockPanel>
                <DataGrid Grid.Column="1" x:Name="table" Style="{StaticResource DefaultDataGrid}"
                <DataGrid Grid.Row="1" x:Name="productTable" Style="{StaticResource DefaultDataGrid}"
                      SelectedItem="{Binding SelectedProduct}" ItemsSource="{Binding Products}">
                    <DataGrid.Columns>
                        <DataGridTemplateColumn Width="Auto" CanUserResize="False">
                            <DataGridTemplateColumn.CellTemplate>
                                <DataTemplate>
                                    <CheckBox VerticalAlignment="Center"
                      IsChecked="{Binding Selected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
                      IsChecked="{Binding IsSelected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
                                </DataTemplate>
                            </DataGridTemplateColumn.CellTemplate>
                        </DataGridTemplateColumn>
@@ -67,38 +76,45 @@
                        </DataGridTemplateColumn>
                    </DataGrid.Columns>
                </DataGrid>
            </StackPanel>
            </Grid>
        </Border>
        <Border Grid.Column="1" Style="{StaticResource CardBoarder}" Margin="2,0,0,0">
            <StackPanel>
                <DockPanel Margin="0,0,0,10" >
                    <Button DockPanel.Dock="Right" pu:ButtonHelper.Icon="&#xe60f;" Style="{StaticResource DangerButton}">删除</Button>
                    <Button DockPanel.Dock="Right" Style="{StaticResource PrimaryButton}"
    pu:ButtonHelper.Icon="&#xe631;" Margin="0,0,10,0" x:Name="refreshParameterBtn" Click="Button_Click">刷新</Button>
                    <Button DockPanel.Dock="Right" Style="{StaticResource PrimaryButton}"
    pu:ButtonHelper.Icon="&#xe61a;" Margin="0,0,10,0" x:Name="addParameterBtn" Click="Button_Click">新增</Button>
            <Grid dp:GridHelper.Space="10">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition />
                </Grid.RowDefinitions>
                <DockPanel>
                    <StackPanel DockPanel.Dock="Right" Style="{StaticResource ButtonGroup}">
                        <Button Style="{StaticResource PrimaryButton}"
pu:ButtonHelper.Icon="&#xe61a;" x:Name="addParameterBtn" Click="Button_Click">新增</Button>
                        <Button Style="{StaticResource PrimaryButton}"
pu:ButtonHelper.Icon="&#xe631;" x:Name="refreshParameterBtn" Click="Button_Click">刷新</Button>
                        <Button pu:ButtonHelper.Icon="&#xe60f;" x:Name="deleteBatchParameterBtn"
                                Style="{StaticResource DangerButton}" Click="Button_Click">删除</Button>
                    </StackPanel>
                    <TextBlock DockPanel.Dock="Left" FontSize="{StaticResource TitleFontSize}" 
   VerticalAlignment="Center" HorizontalAlignment="Left" Text="产品列表"/>
                    <TextBlock DockPanel.Dock="Left" FontSize="{StaticResource TitleFontSize}"
                       VerticalAlignment="Center" HorizontalAlignment="Left" Text="产品参数"/>
VerticalAlignment="Center" HorizontalAlignment="Left" Text="产品参数"/>
                </DockPanel>
                <DataGrid x:Name="roleTable" Style="{StaticResource DefaultDataGrid}" SelectionMode="Single"
                      ItemsSource="{Binding Parameters}">
                <DataGrid Grid.Row="1" x:Name="parameterTable" Style="{StaticResource DefaultDataGrid}" SelectionMode="Single"
                          SelectedItem="{Binding SelectedParameter}"
                ItemsSource="{Binding Parameters}">
                    <DataGrid.Columns>
                        <DataGridTemplateColumn Header="" Width="Auto" CanUserResize="False">
                            <DataGridTemplateColumn.CellTemplate>
                                <DataTemplate>
                                    <CheckBox VerticalAlignment="Center" 
                HorizontalAlignment="Center" IsChecked="{Binding Selected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
          HorizontalAlignment="Center" IsChecked="{Binding IsSelected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
                                </DataTemplate>
                            </DataGridTemplateColumn.CellTemplate>
                        </DataGridTemplateColumn>
                        <DataGridTextColumn Header="#" Binding="{Binding Converter={StaticResource rowNumber},
      RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGridRow},AncestorLevel=1}}"
              CanUserResize="False"/>
RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGridRow},AncestorLevel=1}}"
        CanUserResize="False"/>
                        <DataGridTextColumn Width="*" Binding="{Binding Name}" Header="名称" />
                        <DataGridTextColumn Width="*" Binding="{Binding Value}" Header="值" />
                        <DataGridTextColumn Width="*" Binding="{Binding Unit}" Header="单位" />
@@ -109,18 +125,18 @@
                                <DataTemplate>
                                    <StackPanel Orientation="Horizontal">
                                        <Button x:Name="editParameterBtn" Style="{StaticResource RowLink}" Margin="0,0,10,0"
                DataContext="{Binding }"
                Click="Button_Click">编辑</Button>
          DataContext="{Binding }"
          Click="Button_Click">编辑</Button>
                                        <Button x:Name="delParameterBtn" Style="{StaticResource DangerLinkButton}"
                DataContext="{Binding }"
                Click="Button_Click">删除</Button>
          DataContext="{Binding }"
          Click="Button_Click">删除</Button>
                                    </StackPanel>
                                </DataTemplate>
                            </DataGridTemplateColumn.CellTemplate>
                        </DataGridTemplateColumn>
                    </DataGrid.Columns>
                </DataGrid>
            </StackPanel>
            </Grid>
        </Border>
    </Grid>
</hb:PluginControl>
HMI.ParameterManage/ParameterManage.xaml.cs
@@ -1,9 +1,12 @@
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using HMI.Base;
using HMI.Core.Util;
using HMI.ParameterManage.Entity;
using HMI.ParameterManage.Model;
using HMI.UI.Util;
namespace HMI.ParameterManage
@@ -13,8 +16,7 @@
    /// </summary>
    public partial class ParameterManage : PluginControl
    {
        private ControlModel Model;
        private SqliteHelper SqliteHelper;
        private XmlModel Model;
        public ParameterManage()
        {
@@ -23,31 +25,7 @@
        public override void InitFinish()
        {
            string dbpath = Path.Combine(BaseFolder, "ParameterManage.db");
            if (!File.Exists(dbpath))
            {
                SqliteHelper.CreateDatabase(dbpath);
            }
            SqliteHelper = new SqliteHelper(dbpath);
            SqliteHelper.ExecuteSql(@"CREATE TABLE IF NOT EXISTS product (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    name TEXT NOT NULL,
                    isMaster INTEGER,
                    parentId INTEGER,
                    UNIQUE(name)
                    );
CREATE TABLE IF NOT EXISTS parameter (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    name TEXT NOT NULL,
                    value TEXT,
                    unit TEXT,
                    description TEXT,
                    pGroup TEXT,
                    productName TEXT,
                    UNIQUE(productName,name)
                    );
");
            Model = new ControlModel(SqliteHelper);
            Model = new XmlModel(BaseFolder);
            DataContext = Model;
        }
@@ -63,7 +41,16 @@
            switch (btn.Name)
            {
                case "addBtn":
                    OpenAddDialog();
                    OpenProductDialog(null);
                    break;
                case "editProductBtn":
                    OpenProductDialog((ProductData)btn.DataContext);
                    break;
                case "delProductBtn":
                    TryDeleteProduct((ProductData)btn.DataContext);
                    break;
                case "delBatchBtn":
                    TryDeleteProducts();
                    break;
                case "refrshBtn":
                    Model.LoadProducts();
@@ -77,20 +64,24 @@
                case "editParameterBtn":
                    OpenAddParameterDialog((ParameterData)btn.DataContext);
                    break;
                case "delParameterBtn":
                    TryDeleteParameter((ParameterData)btn.DataContext);
                    break;
                case "deleteBatchParameterBtn":
                    TryDeleteParameters();
                    break;
                default:
                    break;
            }
        }
        private void OpenAddDialog()
        private void OpenProductDialog(ProductData data)
        {
            var win = new ProductModal
            var win = new ProductModal(Model)
            {
                Title = "新增产品",
                Title = data == null ? "新增产品" : "编辑产品",
                Owner = Window.GetWindow(this),
                sqliteHelper = SqliteHelper
            };
            if (win.Open(new ProductData()) == true)
            if (win.Open(data ?? new ProductData()) == true)
            {
                Model.LoadProducts();
            }
@@ -103,11 +94,10 @@
                ToastHelper.Warning(Window.GetWindow(this), "请先选择一项产品");
                return;
            }
            var win = new ParameterModal
            var win = new ParameterModal(Model)
            {
                Title = data == null ? "新增参数" : "编辑参数",
                Owner = Window.GetWindow(this),
                sqliteHelper = SqliteHelper
            };
            if (win.Open(data ?? new ParameterData { ProductName = Model.SelectedProduct.Name }) == true)
            {
@@ -115,5 +105,56 @@
            }
        }
        private void TryDeleteProduct(ProductData data)
        {
            if (MsgBoxHelper.WarnMessage(Window.GetWindow(this), $"确定要删除产品[{data.Name}]吗?该操作无法撤销")
                == MessageBoxResult.OK)
            {
                Model.DeleteProduct(data);
                Model.LoadProducts();
            }
        }
        private void TryDeleteProducts()
        {
            var temp = Model.Products.Where(p => p.IsSelected).Count();
            if (temp <= 0)
            {
                this.Warning("请选择要删除的产品");
                return;
            }
            if (MsgBoxHelper.WarnMessage(Window.GetWindow(this), $"确定要删除选中的[{temp}]条产品吗?该操作无法撤销")
                == MessageBoxResult.OK)
            {
                Model.DeleteProducts();
                Model.LoadProducts();
            }
        }
        private void TryDeleteParameter(ParameterData data)
        {
            if (MsgBoxHelper.WarnMessage(Window.GetWindow(this), $"确定要删除参数[{data.Name}]吗?该操作无法撤销") == MessageBoxResult.OK)
            {
                Model.DeleteParameter(data);
                Model.LoadParameters();
            }
        }
        private void TryDeleteParameters()
        {
            var temp = Model.Parameters.Where(p => p.IsSelected).Count();
            if (temp <= 0)
            {
                this.Warning("请选择要删除的参数");
                return;
            }
            if (MsgBoxHelper.WarnMessage(Window.GetWindow(this), $"确定要删除选中的[{temp}]条参数吗?该操作无法撤销")
                == MessageBoxResult.OK)
            {
                Model.DeleteParameters();
                Model.LoadParameters();
            }
        }
    }
}
HMI.ParameterManage/ParameterModal.xaml
@@ -8,6 +8,7 @@
            xmlns:pu="https://opensource.panuon.com/wpf-ui"
            xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
            xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI"
            xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
            mc:Ignorable="d"
            Title="Form"
            SizeToContent="Height"
@@ -26,19 +27,19 @@
            pu:WindowXModalDialog.ButtonPanelMargin="0,30,10,10">
    <Grid Margin="10">
        <StackPanel x:Name="formPanel">
            <pu:FormGroup x:Name="FgProductName" ui:FormGroupHelper.IsRequired="True"
            <pu:FormGroup x:Name="FgProductName" dp:FormGroupHelper.IsRequired="True"
                          Header="产品名称" GroupName="MyFormGroup">
                <TextBox x:Name="TbProductName" IsReadOnly="True" Style="{StaticResource EditorTextBoxStyle}" />
            </pu:FormGroup>
            <pu:FormGroup x:Name="FgName" ui:FormGroupHelper.IsRequired="True"
                          ui:FormGroupHelper.ValidateMethod="ValidateName"
            <pu:FormGroup x:Name="FgName" dp:FormGroupHelper.IsRequired="True"
                          dp:FormGroupHelper.ValidateMethod="ValidateName"
                              Header="参数名称" GroupName="MyFormGroup">
                <TextBox x:Name="TbName" Style="{StaticResource EditorTextBoxStyle}" />
            </pu:FormGroup>
            <pu:FormGroup x:Name="FgValue" Style="{StaticResource RequiredInputFormGroupStyle}" 
                          ui:FormGroupHelper.IsRequired="True"
                          dp:FormGroupHelper.IsRequired="True"
                  Header="参数值" GroupName="MyFormGroup">
                <TextBox x:Name="TbValue" Style="{StaticResource EditorTextBoxStyle}" />
            </pu:FormGroup>
HMI.ParameterManage/ParameterModal.xaml.cs
@@ -1,18 +1,11 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Windows;
using Microsoft.Win32;
using Panuon.WPF.UI;
using HMI.Core.Annotation;
using HMI.Core.Entity;
using HMI.Core.Exceptions;
using HMI.Core.Util;
using HMI.Core.Util;
using HMI.ParameterManage.Entity;
using HMI.UI;
using HMI.ParameterManage.Model;
using HMI.UI.DpUtil;
using Panuon.WPF.UI;
using System;
using System.ComponentModel;
using System.Windows.Interop;
namespace HMI.ParameterManage
{
@@ -21,15 +14,15 @@
    /// </summary>
    public partial class ParameterModal : WindowX
    {
        public ParameterModal()
        private XmlModel DataModel;
        public ParameterModal(XmlModel model)
        {
            DataModel = model;
            this.DataContext = this;
            InitializeComponent();
        }
        public ParameterData Data { get; set; }
        public SqliteHelper sqliteHelper { get; set; }
        private bool _isProcessing;
@@ -54,7 +47,7 @@
                        return;
                    }
                    SaveOrUpdateData();
                    DataModel.SaveOrUpdateParameter(Data);
                }
                catch (Exception ex)
                {
@@ -73,15 +66,7 @@
        public bool ValidateName(object value, out string err)
        {
            err = null;
            string segment = string.IsNullOrEmpty(Data.Id) ? "" : $" AND id!='{Data.Id}'";
            string sql = $"SELECT 1 FROM parameter WHERE name='{TbName.Text}' AND productName='{Data.ProductName}'{segment};";
            if (sqliteHelper.HasData(sql))
            {
                err = "参数名称不能重复";
                return false;
            }
            return true;
            return DataModel.ValidateParameterName(value, out err);
        }
        private bool ValidateForm()
@@ -109,33 +94,6 @@
            TbDescription.Text = Data.Description;
            TbPGroup.Text = Data.PGroup;
            return this.ShowDialog();
        }
        private void SaveOrUpdateData()
        {
            if (string.IsNullOrEmpty(Data.Id))
            {
                InsertData(Data);
            }
            else
            {
                UpdateData(Data);
            }
        }
        public void InsertData(ParameterData data)
        {
            string sql = $@"INSERT INTO parameter (name,value,unit,description,productName,pGroup)
                VALUES
                ('{data.Name}','{data.Value}','{data.Unit}','{data.Description}','{data.ProductName}','{data.PGroup}');";
            sqliteHelper.ExecuteSql(sql);
        }
        public void UpdateData(ParameterData data)
        {
            string sql = $@"UPDATE parameter SET name='{data.Name}',value='{data.Value}',unit='{data.Unit}',
description='{data.Description}',productName='{data.ProductName}',pGroup='{data.PGroup}' WHERE id='{data.Id}';";
            sqliteHelper.ExecuteSql(sql);
        }
    }
}
HMI.ParameterManage/ProductModal.xaml
@@ -7,6 +7,7 @@
            xmlns:pw="https://opensource.panuon.com/wpf"
            xmlns:pu="https://opensource.panuon.com/wpf-ui"
            xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
            xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
            mc:Ignorable="d"
            Title="Form"
            SizeToContent="Height"
@@ -37,9 +38,12 @@
    </Window.Resources>
    <Grid Margin="10">
        <StackPanel>
        <StackPanel x:Name="formPanel">
            <pu:FormGroup x:Name="FgName" Style="{StaticResource RequiredInputFormGroupStyle}" 
                              Header="产品名称" GroupName="MyFormGroup">
                              Header="产品名称" GroupName="MyFormGroup"
                          dp:FormGroupHelper.IsRequired="True"
                          dp:FormGroupHelper.ValidateMethod="ValidateName"
                          >
                <TextBox x:Name="TbName" Style="{StaticResource EditorTextBoxStyle}" />
            </pu:FormGroup>
HMI.ParameterManage/ProductModal.xaml.cs
@@ -13,6 +13,8 @@
using HMI.Core.Util;
using HMI.ParameterManage.Entity;
using HMI.UI;
using HMI.ParameterManage.Model;
using HMI.UI.DpUtil;
namespace HMI.ParameterManage
{
@@ -21,15 +23,17 @@
    /// </summary>
    public partial class ProductModal : WindowX
    {
        public ProductModal()
        private XmlModel DataModel;
        public ProductModal(XmlModel dataModel)
        {
            DataModel = dataModel;
            this.DataContext = this;
            InitializeComponent();
        }
        public ProductData Data { get; set; }
        public SqliteHelper sqliteHelper { get; set; }
        private bool _isProcessing;
@@ -54,7 +58,7 @@
                        return;
                    }
                    SaveOrUpdateData();
                    DataModel.SaveOrUpdateData(Data);
                }
                catch (Exception ex)
                {
@@ -71,50 +75,20 @@
            base.OnClosing(e);
        }
        private bool ValidateName(object value, out string msg)
        {
            return DataModel.ValidateProductName(value, out msg);
        }
        private bool ValidateForm()
        {
            try
            bool flag = FormGroupHelper.ValidateForm(formPanel);
            if (flag)
            {
                //
                FormValidateException ex = new FormValidateException();
                string segment = string.IsNullOrEmpty(Data.Id) ? "" : $" AND id!='{Data.Id}'";
                string sql = $"SELECT 1 FROM product WHERE name='{TbName.Text}'{segment};";
                if (string.IsNullOrEmpty(TbName.Text))
                {
                    ex.Result.Add(new FormGroupValidateResult("请输入产品名称", FgName));
                }
                else if (sqliteHelper.HasData(sql))
                {
                    ex.Result.Add(new FormGroupValidateResult("产品名称不能重复", FgName));
                }
                else
                {
                    FgName.ValidateResult = ValidateResult.None;
                    FgName.Message = null;
                }
                if (!ex.Success)
                {
                    throw ex;
                }
                Data.Name = TbName.Text;
                Data.IsMaster = TbIsMaster.IsChecked == true;
            }
            catch (FormValidateException ex)
            {
                foreach (var data in ex.Result)
                {
                    var fg = (FormGroup)data.Sender;
                    fg.ValidateResult = ValidateResult.Error;
                    fg.Message = data.Message;
                }
                return false;
            }
            Data.Name = TbName.Text;
            Data.IsMaster = TbIsMaster.IsChecked == true;
            Data.ParentId = TbParentId.Text;
            return true;
            return flag;
        }
        public bool? Open(ProductData data)
@@ -125,35 +99,6 @@
            TbParentId.Text = Data.ParentId;
            return this.ShowDialog();
        }
        private void SaveOrUpdateData()
        {
            if (string.IsNullOrEmpty(Data.Id))
            {
                InsertData(Data);
            }
            else
            {
                UpdateData(Data);
            }
        }
        public void InsertData(ProductData data)
        {
            string sql = $@"INSERT INTO product (name,isMaster,parentId)
                VALUES
                ('{data.Name}',{(data.IsMaster ? 1 : 0)} ,{data.ParentId} );";
            sqliteHelper.ExecuteSql(sql);
        }
        public void UpdateData(ProductData data)
        {
            string sql = $@"UPDATE product SET name='{data.Name}',isMaster='{(data.IsMaster ? 1 : 0)}',parentId = {data.ParentId}
WHERE id='{data.Id}';";
            sqliteHelper.ExecuteSql(sql);
        }
    }
}
HMI.SettingControl/SettingControl.xaml
@@ -9,7 +9,7 @@
             xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
            xmlns:pw="https://opensource.panuon.com/wpf"
            xmlns:pu="https://opensource.panuon.com/wpf-ui"
            xmlns:dp="clr-namespace:HMI.UI;assembly=HMI.UI"
            xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
             Loaded="UserControl_Loaded"
             Background="{StaticResource WorkspaceBackground}"
             d:DesignHeight="450" d:DesignWidth="800">
@@ -28,33 +28,35 @@
    <Grid>
        <Border Style="{StaticResource CardBoarder}">
            <Grid>
            <Grid dp:GridHelper.Space="10">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition />
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>
                <TextBlock VerticalAlignment="Center" Text="系统设置" Margin="0,0,0,10"
                           FontSize="{StaticResource TitleFontSize}" />
                <TabControl Grid.Row="1"
                <TextBlock VerticalAlignment="Center" Text="系统设置" FontSize="{StaticResource TitleFontSize}" />
                <TabControl Grid.Row="1" x:Name="tabControl"
        BorderBrush="{StaticResource CommonBoarder}"
        ItemsSource="{Binding TabItems}">
                <TabControl.Resources>
                    <Style TargetType="TabControl" BasedOn="{StaticResource {x:Static pu:StyleKeys.TabControlStyle}}">
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate TargetType="TabControl">
                                    <Border BorderBrush="Black" BorderThickness="1">
                    <TabControl.Resources>
                        <Style TargetType="TabControl" BasedOn="{StaticResource {x:Static pu:StyleKeys.TabControlStyle}}">
                            <Setter Property="Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="TabControl">
                                        <Border BorderBrush="Black" BorderThickness="1">
                                            <ContentPresenter/>
                                        </Border>
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                    </Style>
                </TabControl.Resources>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </TabControl.Resources>
                </TabControl>
                <StackPanel Grid.Row="2" Style="{StaticResource ButtonGroup}">
                    <Button Style="{StaticResource PrimaryButton}" x:Name="resetBtn" Click="Button_Click">重置</Button>
                    <Button Style="{StaticResource PrimaryButton}" x:Name="saveBtn" Click="Button_Click">保存</Button>
                </StackPanel>
            </Grid>
        </Border>
    </Grid>
HMI.SettingControl/SettingControl.xaml.cs
@@ -1,10 +1,12 @@
using System.Collections.ObjectModel;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using HMI.Base;
using HMI.Core.Api;
using HMI.UI.Util;
using log4net;
namespace HMI.SettingControl
@@ -51,5 +53,73 @@
            }
            Loaded -= UserControl_Loaded;
        }
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Button btn = sender as Button;
            switch (btn.Name)
            {
                case "resetBtn":
                    ResetCurrentConfig();
                    break;
                case "saveBtn":
                    SaveAllConfig();
                    break;
                default:
                    break;
            }
        }
        private void SaveAllConfig()
        {
            foreach (var d in tabControl.ItemsSource)
            {
                if (!(d is TabItem tabItem)) continue;
                if (tabItem.Content is IConfigManager manager)
                {
                    try
                    {
                        manager.SaveConfig();
                    }
                    catch (Exception ex)
                    {
                        MsgBoxHelper.Error(this, $"保存{tabItem.Header}配置失败!{ex.Message}");
                    }
                }
            }
            this.Success("保存成功");
        }
        private void SaveCurrentConfig()
        {
            var content = tabControl.SelectedItem as TabItem;
            if (content is IConfigManager manager)
            {
                try
                {
                    manager.SaveConfig();
                }
                catch (Exception ex)
                {
                    MsgBoxHelper.Error(this, $"保存配置失败!{ex.Message}");
                }
            }
        }
        private void ResetCurrentConfig()
        {
            var tabItem = tabControl.SelectedItem as TabItem;
            if (tabItem.Content is IConfigManager manager)
            {
                try
                {
                    manager.ResetConfig();
                }
                catch (Exception ex)
                {
                    MsgBoxHelper.Error(this, $"保存配置失败!{ex.Message}");
                }
            }
        }
    }
}
HMI.SettingControl/SystemConfig.xaml
@@ -53,9 +53,5 @@
                </pu:FormGroup>
            </StackPanel>
        </GroupBox>
        <DockPanel Grid.Row="3" LastChildFill="False">
            <Button DockPanel.Dock="Right" Style="{StaticResource PrimaryButton}" Click="Button_Click">保存</Button>
        </DockPanel>
    </Grid>
</UserControl>
HMI.SettingControl/SystemConfig.xaml.cs
@@ -1,5 +1,6 @@
using System;
using System.Windows.Controls;
using HMI.Core.Api;
using HMI.Core.Setting;
using HMI.Core.Util;
using HMI.UI.Util;
@@ -9,7 +10,7 @@
    /// <summary>
    /// SystemConfig.xaml 的交互逻辑
    /// </summary>
    public partial class SystemConfig : UserControl
    public partial class SystemConfig : UserControl, IConfigManager
    {
        public SystemConfig()
        {
@@ -19,14 +20,29 @@
            InitializeComponent();
        }
        public object GetConfig()
        {
            return SystemSetting.Instance;
        }
        public void ResetConfig()
        {
            var model = new SystemSettingModel();
            BeanUtil.Copy(SystemSetting.Instance, model);
            DataContext = model;
        }
        public void SaveConfig()
        {
            SystemSettingModel settingModel = (SystemSettingModel)DataContext;
            XmlHelper.SerializeToXml(PathHelper.ConfigFolder + "\\SystemConfig.xml", settingModel);
            SystemSetting.ReLoad();
        }
        private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            try
            {
                SystemSettingModel settingModel = (SystemSettingModel)DataContext;
                XmlHelper.SerializeToXml(PathHelper.ConfigFolder + "\\SystemConfig.xml", settingModel);
                SystemSetting.ReLoad();
                this.Success("保存成功");
            }
            catch (Exception ex)
            {
HMI.UI/DpUtil/DataGridHelper.cs
New file
@@ -0,0 +1,20 @@
using System.Windows;
using System.Windows.Controls;
namespace HMI.UI.DpUtil
{
    public class DataGridHelper
    {
        public static DataGridColumn GetSelectedColumn(DataGrid obj)
        {
            return (DataGridColumn)obj.GetValue(SelectedColumnProperty);
        }
        public static void SetSelectedColumn(DataGrid obj, DataGridColumn value)
        {
            obj.SetValue(SelectedColumnProperty, value);
        }
        public static readonly DependencyProperty SelectedColumnProperty =
            DependencyProperty.RegisterAttached("SelectedColumn", typeof(DataGridColumn), typeof(Grid),
                new PropertyMetadata(null));
    }
}
HMI.UI/DpUtil/FormGroupHelper.cs
@@ -1,11 +1,8 @@
using System;
using System.ComponentModel;
using Panuon.WPF.UI;
using System.Windows;
using System.Windows.Controls;
using Panuon.WPF.UI;
using Panuon.WPF.UI.Configurations;
namespace HMI.UI
namespace HMI.UI.DpUtil
{
    public class FormGroupHelper
    {
HMI.UI/HMI.UI.csproj
@@ -82,6 +82,7 @@
  <ItemGroup>
    <Compile Include="Converter\DataGridRowNumberConverter.cs" />
    <Compile Include="Converter\PathImageConverter.cs" />
    <Compile Include="DpUtil\DataGridHelper.cs" />
    <Compile Include="DpUtil\FormGroupHelper.cs" />
    <Compile Include="DpUtil\GridHelper.cs" />
    <Compile Include="DpUtil\StackPanelHelper.cs" />
HMI.UI/Style/FormStyle.xaml
@@ -90,7 +90,7 @@
        <Setter Property="VerticalContentAlignment" Value="Center"/>
        <Style.Triggers>
            <Trigger Property="IsReadOnly" Value="True">
                <Setter Property="Background" Value="LightGray"/>
                <Setter Property="Background" Value="#e8e8e8"/>
            </Trigger>
        </Style.Triggers>
    </Style>
@@ -172,7 +172,9 @@
        <Setter Property="ItemsHoverBackground" Value="{StaticResource SecondPrimaryColor}"/>
        <Setter Property="ItemsHoverForeground" Value="{StaticResource SecondPrimaryColorContract}"/>
        <Setter Property="TextStringFormat" Value="yyyy-MM-dd HH:mm:ss"/>
        <Setter Property="Width" Value="180"/>
        <Setter Property="Mode" Value="DateTime"/>
        <Setter Property="SelectedDateTime" Value="{x:Null}"/>
    </Style>
</ResourceDictionary>
HMI.UI/Style/PanuonResource.xaml
@@ -13,6 +13,7 @@
    <sys:Double x:Key="TitleFontSize">20</sys:Double>
    <sys:Double x:Key="ContentFontSize">16</sys:Double>
    <sys:Double x:Key="ContentFontSize2">14</sys:Double>
    <Style x:Key="PrimaryButton" TargetType="Button" BasedOn="{StaticResource {x:Static pu:StyleKeys.ButtonStyle}}">
        <Setter Property="Background" Value="{StaticResource PrimaryColor}"/>
@@ -65,6 +66,22 @@
    <Style x:Key="DangerLinkButton" TargetType="Button" BasedOn="{StaticResource LinkButton}">
        <Setter Property="Foreground" Value="{StaticResource DangerColor}"/>
    </Style>
    <Style x:Key="CommonToggle" TargetType="ToggleButton" BasedOn="{StaticResource {x:Static pu:StyleKeys.ToggleButtonStyle}}">
        <Setter Property="Background" Value="#FFF"/>
        <Setter Property="Foreground" Value="Black"/>
        <Setter Property="BorderBrush" Value="LightGray"/>
        <Setter Property="Cursor" Value="Hand"/>
    </Style>
    <Style x:Key="CommonMenu" TargetType="Menu" BasedOn="{StaticResource {x:Static pu:StyleKeys.MenuStyle}}">
        <Setter Property="Background" Value="{StaticResource PrimaryColor}"/>
        <Setter Property="Foreground" Value="#FFF"/>
        <Setter Property="pu:IconHelper.FontFamily" Value="{StaticResource iconfont}"/>
        <Setter Property="pu:IconHelper.Margin" Value="0,0,10,0"/>
        <Setter Property="BorderThickness" Value="0"/>
        <Setter Property="Cursor" Value="Hand"/>
    </Style>
    <Style x:Key="CardBoarder" TargetType="Border">
@@ -171,4 +188,10 @@
        <Setter Property="VerticalAlignment" Value="Center"/>
        <Setter Property="FontSize" Value="{StaticResource TitleFontSize}"/>
    </Style>
    <Style x:Key="FormHeader" TargetType="TextBlock">
        <Setter Property="VerticalAlignment" Value="Center"/>
        <Setter Property="HorizontalAlignment" Value="Center"/>
        <Setter Property="FontSize" Value="{StaticResource ContentFontSize2}"/>
    </Style>
</ResourceDictionary>
HMI.UI/Util/ToastHelper.cs
@@ -141,7 +141,10 @@
            ToastPosition position = ToastPosition.Top, double offset = 0, int durationMs = 3000,
            ToastWindow targetWindow = ToastWindow.ActiveWindow)
        {
            ToastHelper.Error(Window.GetWindow(control), message, icon, position, offset, durationMs, targetWindow);
            Application.Current.Dispatcher.Invoke(() =>
            {
                ToastHelper.Error(Window.GetWindow(control), message, icon, position, offset, durationMs, targetWindow);
            });
        }
        public static void Info(this UserControl control,
Plugin.TestResult/Chart.xaml
New file
@@ -0,0 +1,14 @@
<UserControl x:Class="Plugin.TestResult_WPF.Chart"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:Plugin.TestResult_WPF"
             xmlns:ScottPlot="clr-namespace:ScottPlot.WPF;assembly=ScottPlot.WPF"
             mc:Ignorable="d"
             Loaded="UserControl_Loaded"
             Unloaded="UserControl_Unloaded"
             SizeChanged="UserControl_SizeChanged"
             d:DesignHeight="450" d:DesignWidth="800">
    <ScottPlot:WpfPlot x:Name="chart" />
</UserControl>
Plugin.TestResult/Chart.xaml.cs
New file
@@ -0,0 +1,314 @@
using ScottPlot;
using ScottPlot.Plottables;
using ScottPlot.TickGenerators;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Timers;
using System.Windows.Controls;
namespace Plugin.TestResult_WPF
{
    /// <summary>
    /// Chart.xaml 的交互逻辑
    /// </summary>
    public partial class Chart : UserControl
    {
        private readonly int maxSize;
        private readonly Queue<long> xData;
        private readonly Queue<double> yData;
        private readonly Dictionary<string, Queue<long>> xValues;
        private readonly Dictionary<string, Queue<double>> yValues;
        readonly DataLogger singleLogger;
        private readonly string title;
        private readonly bool multi;
        private readonly int refreshInterval;
        private double maxYValue = -1;
        private double minYValue = double.MaxValue;
        private Timer timer;
        private readonly Dictionary<string, DataLogger> loggers = new Dictionary<string, DataLogger>();
        private DataLogger InitLogger()
        {
            DataLogger logger = chart.Plot.Add.DataLogger();
            //logger.ViewSlide(maxSize * 5);
            logger.ViewFull();
            //logger.Add(new Coordinates(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), 0));
            return logger;
        }
        public Chart(string title, bool multi, int maxSize, int refreshInterval)
        {
            this.title = title;
            this.maxSize = maxSize;
            this.multi = multi;
            this.refreshInterval = refreshInterval;
            InitializeComponent();
            if (this.multi)
            {
                xValues = new Dictionary<string, Queue<long>>();
                yValues = new Dictionary<string, Queue<double>>();
                //chart.Plot.ShowLegend(Alignment.UpperLeft, Orientation.Vertical);
                var legend = chart.Plot.ShowLegend(Edge.Top);
                legend.Alignment = Alignment.UpperLeft;
                legend.Padding = new PixelPadding(0);
                chart.Plot.Axes.Left.TickLabelStyle.FontSize = 12;
                chart.Plot.Axes.Bottom.TickLabelStyle.FontSize = 12;
                chart.Plot.Axes.Left.Label.FontSize = 15;
            }
            else
            {
                singleLogger = InitLogger();
                singleLogger.Color = Colors.Red;
                xData = new Queue<long>(maxSize);
                yData = new Queue<double>(maxSize);
                chart.Plot.Axes.Left.TickLabelStyle.FontSize = (float)FontSizeConverter.CountFontSize(12, ActualHeight, 92);
                chart.Plot.Axes.Bottom.TickLabelStyle.FontSize = (float)FontSizeConverter.CountFontSize(12, ActualHeight, 92);
                chart.Plot.Axes.Left.Label.FontSize = (float)FontSizeConverter.CountFontSize(15, ActualHeight, 92);
            }
            /// 禁用鼠标
            chart.UserInputProcessor.Disable();
            //chart.Plot.Layout.Frameless();
            //Streamer1 = chart.Plot.Add.DataStreamer(maxSize);
            //Streamer1.ViewScrollLeft();
            //Streamer1.Color = Colors.Green;
            //chart.Plot.Add.Scatter(xData.ToArray(), yData.ToArray());
            //string CustomFormatter(DateTime dt)
            //{
            //    return dt.ToString("HH:mm:ss.fff");
            //}
            //var axis = chart.Plot.Axes.DateTimeTicksBottom();
            //var tickGen = (DateTimeAutomatic)axis.TickGenerator;
            //tickGen.LabelFormatter = CustomFormatter;
            //chart.Plot.Axes.Bottom.TickGenerator = new DateTimeAutomatic();
            //chart.Plot.Axes.DateTimeTicksBottom();
            //chart.Plot.RenderManager.RenderStarting += (s, e) =>
            //{
            //    try
            //    {
            //        Tick[] ticks = chart.Plot.Axes.Bottom.TickGenerator.Ticks;
            //        for (int i = 0; i < ticks.Length; i++)
            //        {
            //            long ms = ticks[i].Position < 0 ? 0 : (long)ticks[i].Position;
            //            DateTime dt = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1)).AddMilliseconds(ms);
            //            string label = $"{dt:hh:mm:ss.fff}";
            //            ticks[i] = new Tick(ticks[i].Position, label);
            //        }
            //    }
            //    catch (Exception ex)
            //    {
            //    }
            //};
            string CustomFormatter(double position)
            {
                if (position > 0)
                    return TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1)).AddMilliseconds(position).ToString("HH:mm:ss");
                return "";
            }
            EvenlySpacedMinorTickGenerator minorTickGen = new EvenlySpacedMinorTickGenerator(10);
            NumericAutomatic myTickGenerator = new NumericAutomatic()
            {
                LabelFormatter = CustomFormatter,
                MinorTickGenerator = minorTickGen
            };
            chart.Plot.Axes.Bottom.TickGenerator = myTickGenerator;
            if (!string.IsNullOrEmpty(this.title))
            {
                chart.Plot.Axes.Left.Label.Text = this.title;
                chart.Plot.Axes.Left.Label.Bold = false;
                chart.Plot.Font.Automatic();
                //RefreshData();
            }
            /// 设置刷新定时器
            //refreshTimer = new System.Timers.Timer() { Interval = this.refreshInterval, AutoReset = true };
            //refreshTimer.Elapsed += RefreshTimer_Elapsed;
            //chart.Plot.DataBackground.Color = new Color(0, 0, 0);
            //chart.Plot.Grid.MajorLineColor = Colors.Green.WithOpacity(.6);
            //chart.Plot.Grid.MajorLineWidth = 1;
            //chart.Plot.Grid.MinorLineColor = Colors.Gray.WithOpacity(.3);
            //chart.Plot.Grid.MinorLineWidth = 1;
            //chart.Plot.Axes.Bottom.TickLabelStyle.IsVisible = false;
            //chart.Plot.Axes.Bottom.MajorTickStyle.Length = 0;
            //chart.Plot.Axes.Left.TickLabelStyle.IsVisible = false;
            //chart.Plot.Axes.Left.MajorTickStyle.Length = 0;
            //chart.Plot.Axes.Bottom.FrameLineStyle.Width = 0;
            //chart.Plot.Axes.Left.FrameLineStyle.Width = 0;
            //chart.Plot.Axes.Top.FrameLineStyle.Width = 0;
            //chart.Plot.Axes.Right.FrameLineStyle.Width = 0;
            timer = new Timer();
            timer.Interval = this.refreshInterval;
            timer.Elapsed += Timer_Elapsed;
        }
        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            if (maxYValue > 0)
            {
                RefreshData();
            }
        }
        public void RefreshData()
        {
            /// 限制y轴
            if (maxYValue > -1)
            {
                chart.Plot.Axes.SetLimitsY(minYValue > 5 ? minYValue - 5 : 0, maxYValue + 5);
            }
            else
            {
                chart.Plot.Axes.SetLimitsY(0, 100);
            }
            chart.Refresh();
        }
        public void Clear()
        {
            this.maxYValue = -1;
            this.minYValue = double.MaxValue;
            if (this.multi)
            {
                foreach (var name in loggers.Keys)
                {
                    loggers[name].Clear();
                    xValues[name].Clear();
                    yValues[name].Clear();
                }
                chart.Refresh();
                return;
            }
            singleLogger.Clear();
            xData.Clear();
            yData.Clear();
            chart.Refresh();
        }
        public void AddData(double y)
        {
            if (this.multi)
            {
                return;
            }
            this.maxYValue = y > maxYValue ? y : maxYValue;
            this.minYValue = y < minYValue ? y : minYValue;
            var nowTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
            InsertData(xData, yData, nowTime, y);
            singleLogger.Add(new Coordinates(nowTime, y));
            //Streamer1.Add(new Coordinates(DateTimeOffset.Now.ToUnixTimeSeconds(), y));
            chart.Plot.GetPlottables<Marker>()
                .ToList()
                .ForEach(m => m.X -= 1);
            chart.Plot.GetPlottables<Marker>()
                .Where(m => m.X < 0)
                .ToList()
                .ForEach(m => chart.Plot.Remove(m));
            //RefreshData();
        }
        private void InsertData(Queue<long> xAxis, Queue<double> yAxis, long x, double y)
        {
            if (xAxis.Count >= maxSize)
            {
                xAxis.Dequeue();
                yAxis.Dequeue();
            }
            xAxis.Enqueue(x);
            yAxis.Enqueue(y);
        }
        public void AddData(string name, double y)
        {
            if (!this.multi)
            {
                return;
            }
            this.maxYValue = y > maxYValue ? y : maxYValue;
            this.minYValue = y < minYValue ? y : minYValue;
            if (loggers.ContainsKey(name))
            {
                var logger = loggers[name];
                var x = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
                logger.Add(new Coordinates(x, y));
                InsertData(xValues[name], yValues[name], x, y);
            }
            else
            {
                var logger = InitLogger();
                logger.LegendText = name;
                loggers.Add(name, logger);
                var xAxis = new Queue<long>(maxSize);
                var yAxis = new Queue<double>(maxSize);
                xValues.Add(name, xAxis);
                yValues.Add(name, yAxis);
                var x = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
                logger.Add(new Coordinates(x, y));
                InsertData(xAxis, yAxis, x, y);
            }
            //Streamer1.Add(new Coordinates(DateTimeOffset.Now.ToUnixTimeSeconds(), y));
            chart.Plot.GetPlottables<Marker>()
                .ToList()
                .ForEach(m => m.X -= 1);
            chart.Plot.GetPlottables<Marker>()
                .Where(m => m.X < 0)
                .ToList()
                .ForEach(m => chart.Plot.Remove(m));
            //RefreshData();
        }
        private void UserControl_Loaded(object sender, System.Windows.RoutedEventArgs e)
        {
            //refreshTimer.Start();
            timer?.Start();
        }
        private void UserControl_Unloaded(object sender, System.Windows.RoutedEventArgs e)
        {
            timer?.Stop();
        }
        private void UserControl_SizeChanged(object sender, System.Windows.SizeChangedEventArgs e)
        {
            if (this.multi) return;
            chart.Plot.Axes.Left.Label.FontSize = (float)FontSizeConverter.CountFontSize(15, ActualHeight, 92);
            chart.Plot.Axes.Left.TickLabelStyle.FontSize = (float)FontSizeConverter.CountFontSize(10, ActualHeight, 92);
            chart.Plot.Axes.Bottom.TickLabelStyle.FontSize = (float)FontSizeConverter.CountFontSize(10, ActualHeight, 92);
        }
    }
}
Plugin.TestResult/ChartWindow.xaml
New file
@@ -0,0 +1,13 @@
<Window x:Class="Plugin.TestResult_WPF.ChartWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Plugin.TestResult_WPF"
        mc:Ignorable="d"
        Unloaded="Window_Unloaded"
        Title="{Binding title}" Height="450" Width="800">
    <UniformGrid x:Name="mainGrid" Columns="1">
    </UniformGrid>
</Window>
Plugin.TestResult/ChartWindow.xaml.cs
New file
@@ -0,0 +1,45 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace Plugin.TestResult_WPF
{
    /// <summary>
    /// ChartWindow.xaml 的交互逻辑
    /// </summary>
    public partial class ChartWindow : Window, INotifyPropertyChanged
    {
        #region ...
        public event PropertyChangedEventHandler PropertyChanged;
        public virtual void RaisePropertyChanged(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
        public void RaiseAndSetIfchanged<T>(ref T old, T @new, [CallerMemberName] string propertyName = null)
        {
            old = @new;
            if (propertyName != null) RaisePropertyChanged(propertyName);
        }
        #endregion
        private string _title;
        public string title
        {
            get => _title;
            set => RaiseAndSetIfchanged(ref _title, value);
        }
        public ChartWindow(string title)
        {
            this.title = $"{title}详细曲线";
            this.DataContext = this;
            InitializeComponent();
        }
        private void Window_Unloaded(object sender, RoutedEventArgs e)
        {
            this.mainGrid.Children.Clear();
        }
    }
}
Plugin.TestResult/Constant/CommonConstant.cs
New file
@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.Windows.Data;
namespace Plugin.TestResult_WPF.Constant
{
    public class CommonStatus
    {
        public static readonly string OK = "ok";
        public static readonly string NG = "ng";
        public static readonly string WAITING = "waiting";
        public static readonly string CHECKINFAILED = "checkInFailed";
        public static readonly string CHECK_IN = "checkIn";
        public static readonly string TESTING = "testing";
        public static readonly string ALERT = "alert";
        public static readonly string READY = "ready";
        private static string[] cache = new string[] { "ok", "ng", "waiting", "checkInFailed", "checkIn", "testing", "alert", "ready" };
        public static readonly Dictionary<string, string> STATUS_MAP = new Dictionary<string, string>() {
            { "ok","正常"},
            { "ng","异常"},
            { "waiting","等待中"},
            { "checkInFailed","CheckIn失败"},
            { "checkIn","CheckIn"},
            { "testing","测试中"},
            { "alert","报警"},
            { "ready","准备就绪"},
        };
        public static readonly Dictionary<string, SolidBrush> COLOR_MAP = new Dictionary<string, SolidBrush>() {
            { "ok",new SolidBrush(Color.FromArgb(0,0,0,0))},
            { "ng",new SolidBrush(Color.FromArgb(0,0,0,0))},
            { "waiting",new SolidBrush(Color.FromArgb(0,0,0,0))},
            { "testing",new SolidBrush(Color.FromArgb(0,0,0,0))},
            { "checkInFailed",new SolidBrush(Color.FromArgb(0,0,0,0))},
            { "checkIn",new SolidBrush(Color.FromArgb(0,0,0,0))},
        };
        public static string GetRandomStatus()
        {
            return cache[new Random().Next(cache.Length)];
        }
        public static string GetStatusDescription(string status)
        {
            if (STATUS_MAP.ContainsKey(status)) return STATUS_MAP[status];
            return "";
        }
    }
    public class StatusColorConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is string status)
            {
            }
            return null;
        }
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}
Plugin.TestResult/DataTable.xaml
New file
@@ -0,0 +1,84 @@
<UserControl x:Class="Plugin.TestResult_WPF.DataTable"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:Plugin.TestResult_WPF"
             mc:Ignorable="d"
             Background="#FFF"
             BorderBrush="Black"
             BorderThickness="1"
             x:Name="control"
             Loaded="UserControl_Loaded"
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <local:FontSizeConverter x:Key="FontSizeConverter"/>
    </UserControl.Resources>
    <Grid x:Name="grid">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="8.5*"/>
            <RowDefinition Height="1.5*"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid x:Name="infoGrid" Grid.Row="0"
             Background="LightBlue">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Label Grid.Row="0" Grid.Column="0" Grid.RowSpan="1" FontSize="20" FontWeight="Bold"
                   Padding="0,0,20,0"
                   BorderThickness="0,0,0,1"
                   BorderBrush="Black"
                   VerticalContentAlignment="Center" Background="Transparent" Content="{Binding title}"/>
            <local:InfoTable x:Name="infoTable" Grid.Row="0" Grid.Column="1"
               FontSize="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl},
            Converter={StaticResource FontSizeConverter}, ConverterParameter='15,940'}"
                             ></local:InfoTable>
        </Grid>
        <UniformGrid x:Name="mainGrid" Grid.Row="1" Columns="6" local:GridProperties.ShowBorder="True"
                     local:GridProperties.InnerBorderIgnore="All">
        </UniformGrid>
        <!--<local:Chart Grid.Row="2" x:Name="chart" BorderThickness="0,1,0,1" BorderBrush="Black"
                      MouseDoubleClick="chart_MouseDoubleClick"/>-->
        <StatusBar Grid.Row="3">
            <StatusBar.ItemsPanel>
                <ItemsPanelTemplate>
                    <DockPanel/>
                </ItemsPanelTemplate>
            </StatusBar.ItemsPanel>
            <StatusBarItem DockPanel.Dock="Right">
                <StackPanel Orientation="Horizontal">
                    <Ellipse Width="10" Height="10">
                        <Ellipse.Style>
                            <Style TargetType="Ellipse">
                                <Setter Property="Fill" Value="Gray"/>
                                <Style.Triggers>
                                    <DataTrigger Binding="{Binding TcpServerStatus,ElementName=control}" Value="ERROR">
                                        <Setter Property="Fill" Value="Red"/>
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding TcpServerStatus,ElementName=control}" Value="RUNNING">
                                        <Setter Property="Fill" Value="Green"/>
                                    </DataTrigger>
                                </Style.Triggers>
                            </Style>
                        </Ellipse.Style>
                    </Ellipse>
                    <TextBlock Text="{Binding TcpServerInfo}"/>
                    <Button Padding="0" Margin="0" BorderThickness="0" Click="Button_Click">🔄</Button>
                </StackPanel>
            </StatusBarItem>
            <StatusBarItem >
                <TextBox IsReadOnly="True" Background="Transparent" BorderThickness="0" Text="{Binding statusText}"/>
            </StatusBarItem>
        </StatusBar>
        <Button Grid.Row="4" Margin="0" Click="SetStationGlobal_Click" BorderThickness="0,1,0,0">启动</Button>
    </Grid>
</UserControl>
Plugin.TestResult/DataTable.xaml.cs
New file
@@ -0,0 +1,285 @@
using Plugin.TestResult_WPF.Model;
using SuperSocket.SocketBase.Protocol;
using System;
using System.ComponentModel;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
namespace Plugin.TestResult_WPF
{
    /// <summary>
    /// DataTable.xaml 的交互逻辑
    /// </summary>
    public partial class DataTable : UserControl, INotifyPropertyChanged
    {
        #region ...
        public event PropertyChangedEventHandler PropertyChanged;
        public virtual void RaisePropertyChanged(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
        public void RaiseAndSetIfchanged<T>(ref T old, T @new, [CallerMemberName] string propertyName = null)
        {
            old = @new;
            if (propertyName != null) RaisePropertyChanged(propertyName);
        }
        #endregion
        private string _title;
        public string title
        {
            get { return _title; }
            set => RaiseAndSetIfchanged(ref _title, value);
        }
        private string _statusText;
        public string statusText
        {
            get { return _statusText; }
            set => RaiseAndSetIfchanged(ref _statusText, value);
        }
        private string tcpServerStatus;
        public string TcpServerStatus
        {
            get { return tcpServerStatus; }
            set { RaiseAndSetIfchanged(ref tcpServerStatus, value); }
        }
        private string tcpServerInfo = "未启动";
        public string TcpServerInfo
        {
            get { return tcpServerInfo; }
            set { RaiseAndSetIfchanged(ref tcpServerInfo, value); }
        }
        private readonly int index;
        private readonly int cellCount;
        private readonly string chartTitle;
        private readonly TestResultView parent;
        private BaseConfig config;
        private Chart chart;
        private Chart multiChart;
        private SingleCell[] cells;
        private TcpServer tcpServer;
        public DataTable(TestResultView parent,int index, string title, BaseConfig config)
        {
            this.config = config;
            this.index = index;
            this.parent = parent;
            this.title = title;
            this.cellCount = config.PositionCount;
            this.cells = new SingleCell[cellCount];
            this.chartTitle = config.MainChartTitle;
            DataContext = this;
            InitializeComponent();
            this.chart = new Chart(this.chartTitle, false, config.MaxSize, config.RefreshInterval);
            this.multiChart = new Chart(config.DetailChartTitle, true, config.MaxSize, config.RefreshInterval);
            this.grid.Children.Add(this.chart);
            Grid.SetRow(this.chart, 2);
            this.chart.MouseDoubleClick += chart_MouseDoubleClick;
            for (int i = 0; i < this.cellCount; i++)
            {
                var cell = new SingleCell(title, i, config.CellListMaxSize);
                mainGrid.Children.Add(cell);
                cells[i] = cell;
            }
        }
        public int GetSize()
        {
            return this.cellCount;
        }
        public SingleCell GetSingleCell(int index)
        {
            return cells[index];
        }
        public void SetStatusText(string statusText)
        {
            this.statusText = statusText;
        }
        public void SetTemperature(double t)
        {
            infoTable.temperature = t;
            chart.AddData(t);
        }
        public void SetTemperature(string name, double t)
        {
            multiChart.AddData(name, t);
        }
        public void SetStatus(string status)
        {
            infoTable.status = status;
        }
        public void SetOkCount(int count)
        {
            infoTable.okCount = count;
        }
        public void SetNgCount(int count)
        {
            infoTable.ngCount = count;
        }
        public void SetStartTime(string startTime)
        {
            infoTable.startTimeStr = startTime;
        }
        public void SetEndTime(string endTime)
        {
            infoTable.endTimeStr = endTime;
        }
        public void SetConsumeTime(double consumeTime)
        {
            infoTable.consumeTime = consumeTime;
        }
        private ChartWindow window;
        private void chart_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            /// 打开多个温度曲线
            if (window == null)
            {
                window = new ChartWindow(title);
                window.Owner = Window.GetWindow(this);
                window.Closed += Window_Closed;
                window.mainGrid.Children.Add(multiChart);
                window.Show();
            }
            else
            {
                window.Activate();
            }
        }
        private void Window_Closed(object sender, EventArgs e)
        {
            window = null;
        }
        private void SetStationGlobal_Click(object sender, RoutedEventArgs e)
        {
            ClearChartData();
            parent.SetStartFlag(this.index, true);
        }
        /// <summary>
        /// 清空两个图表数据
        /// </summary>
        public void ClearChartData()
        {
            chart.Clear();
            multiChart.Clear();
        }
        private void StartTcpServer()
        {
            try
            {
                if (tcpServer == null)
                {
                    tcpServer = new TcpServer();
                    tcpServer.NewRequestReceived += TcpServer_NewRequestReceived;
                    if (!tcpServer.Setup(config.PortStart + index))
                    {
                        ChangeTcpServerStatus("ERROR", "服务设置异常");
                        return;
                    }
                    if (!tcpServer.Start())
                    {
                        ChangeTcpServerStatus("ERROR", "服务启动异常");
                        return;
                    }
                    ChangeTcpServerStatus("RUNNING", "服务运行中");
                }
            }
            catch (Exception ex)
            {
                ChangeTcpServerStatus("ERROR", $"服务异常:{ex.Message}");
                tcpServer?.Stop();
                tcpServer = null;
            }
        }
        private void StopTcpServer()
        {
            if (tcpServer == null) return;
            try
            {
                tcpServer.Stop();
                tcpServer = null;
            }
            catch (Exception ex)
            {
            }
        }
        private void ChangeTcpServerStatus(string status, string info)
        {
            Dispatcher.Invoke(() =>
            {
                TcpServerStatus = status;
                TcpServerInfo = info;
            });
        }
        private void TcpServer_NewRequestReceived(TcpSession session, StringRequestInfo requestInfo)
        {
            string msg = requestInfo.Body;
            string[] datas_10015 = msg.Split(';');
            int cellIndex = int.Parse(datas_10015[1]);
            string strs = datas_10015[2];
            string[] values = strs.Split(',');
            //string step = datas_10015[2];
            //string result = datas_10015[3];
            GetSingleCell(cellIndex).SetInfos(new Info
            {
                step = values[0],
                value = values[1],
                result = values[2],
                range = values[3],
                condition = values[4],
                updateTime = values[5]
            });
            session.Send("1");
        }
        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            StartTcpServer();
            Loaded -= UserControl_Loaded;
        }
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            StartTcpServer();
        }
    }
}
Plugin.TestResult/InfoTable.xaml
New file
@@ -0,0 +1,81 @@
<UserControl x:Class="Plugin.TestResult_WPF.InfoTable"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:Plugin.TestResult_WPF"
             mc:Ignorable="d"
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <Style TargetType="Label">
            <Setter Property="VerticalContentAlignment" Value="Center"/>
            <Setter Property="FontWeight" Value="Bold"/>
        </Style>
        <Style TargetType="TextBox">
            <Setter Property="VerticalContentAlignment" Value="Center"/>
            <Setter Property="FontWeight" Value="Bold"/>
            <Setter Property="IsReadOnly" Value="True"/>
            <Setter Property="BorderThickness" Value="0"/>
        </Style>
    </UserControl.Resources>
    <Grid x:Name="infoGrid" Grid.Row="0" local:GridProperties.ShowBorder="True" local:GridProperties.InnerBorderIgnore="Top,Right">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="2*"/>
            <ColumnDefinition Width="1.5*"/>
            <ColumnDefinition Width="2*"/>
            <ColumnDefinition Width="1.5*"/>
            <ColumnDefinition Width="2*"/>
        </Grid.ColumnDefinitions>
        <Label Grid.Row="0" Grid.Column="0">温度</Label>
        <Label Grid.Row="0" Grid.Column="1" Background="Yellow" Content="{Binding temperatureStr}"/>
        <Label Grid.Row="0" Grid.Column="2">状态</Label>
        <Label Grid.Row="0" Grid.Column="3" Content="{Binding statusStr}">
            <Label.Style>
                <Style TargetType="Label">
                    <Setter Property="FontWeight" Value="Bold"/>
                    <Setter Property="VerticalContentAlignment" Value="Center"/>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding status}" Value="ok">
                            <Setter Property="Background" Value="#FF0F8853"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding status}" Value="ng">
                            <Setter Property="Background" Value="#fd6b6d"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding status}" Value="checkInFailed">
                            <Setter Property="Background" Value="#ffac00"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding status}" Value="checkIn">
                            <Setter Property="Background" Value="#FF00E2FF"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding status}" Value="testing">
                            <Setter Property="Background" Value="#1296db"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding status}" Value="waiting">
                            <Setter Property="Background" Value="#6e6e6e"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding status}" Value="alert">
                            <Setter Property="Background" Value="#e04733"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding status}" Value="ready">
                            <Setter Property="Background" Value="#FF08FF00"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Label.Style>
        </Label>
        <Label Grid.Row="0" Grid.Column="4">老化数量</Label>
        <Label Grid.Row="0" Grid.Column="5" Background="Yellow" Content="{Binding countStr}"/>
        <Label Grid.Row="1" Grid.Column="0">开始时间</Label>
        <Label Grid.Row="1" Grid.Column="1" Background="Yellow" Content="{Binding startTimeStr}"/>
        <Label Grid.Row="1" Grid.Column="2">已老化时间</Label>
        <Label Grid.Row="1" Grid.Column="3" Background="Yellow" Content="{Binding consumeTimeStr}"/>
        <Label Grid.Row="1" Grid.Column="4">预计结束时间</Label>
        <Label Grid.Row="1" Grid.Column="5" Background="Yellow" Content="{Binding endTimeStr}"/>
    </Grid>
</UserControl>
Plugin.TestResult/InfoTable.xaml.cs
New file
@@ -0,0 +1,153 @@
using Plugin.TestResult_WPF.Constant;
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Controls;
namespace Plugin.TestResult_WPF
{
    /// <summary>
    /// InfoTable.xaml 的交互逻辑
    /// </summary>
    public partial class InfoTable : UserControl, INotifyPropertyChanged
    {
        #region ...
        public event PropertyChangedEventHandler PropertyChanged;
        public virtual void RaisePropertyChanged(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
        public void RaiseAndSetIfchanged<T>(ref T old, T @new, [CallerMemberName] string propertyName = null)
        {
            old = @new;
            if (propertyName != null) RaisePropertyChanged(propertyName);
        }
        #endregion
        private double _temperature;
        public double temperature
        {
            get => _temperature;
            set
            {
                RaiseAndSetIfchanged(ref _temperature, value);
                RaisePropertyChanged("temperatureStr");
            }
        }
        public string temperatureStr
        {
            get => $"{_temperature.ToString("f2")}℃";
        }
        private string _status;
        public string status
        {
            get => _status;
            set
            {
                RaiseAndSetIfchanged(ref _status, value);
                RaisePropertyChanged("statusStr");
            }
        }
        public string statusStr
        {
            get => status != null && CommonStatus.STATUS_MAP.ContainsKey(status) ? CommonStatus.STATUS_MAP[status] : "";
        }
        private int _okCount;
        public int okCount
        {
            get => _okCount;
            set
            {
                RaiseAndSetIfchanged(ref _okCount, value);
                RefreshCount();
            }
        }
        private int _ngCount;
        public int ngCount
        {
            get => _ngCount;
            set
            {
                RaiseAndSetIfchanged(ref _ngCount, value);
                RefreshCount();
            }
        }
        private int _allCount;
        public int allCount
        {
            get => _allCount;
            set
            {
                RaiseAndSetIfchanged(ref _allCount, value);
            }
        }
        public string countStr
        {
            get => $"OK:{okCount},NG:{ngCount},ALL:{allCount}";
        }
        private string _startTimeStr;
        public string startTimeStr
        {
            get => _startTimeStr;
            set
            {
                RaiseAndSetIfchanged(ref _startTimeStr, value);
            }
        }
        private double _consumeTime;
        public double consumeTime
        {
            get => _consumeTime;
            set
            {
                RaiseAndSetIfchanged(ref _consumeTime, value);
                RaisePropertyChanged("consumeTimeStr");
            }
        }
        public string consumeTimeStr
        {
            get
            {
                if (consumeTime < 60) return $"{consumeTime.ToString("f2")}s";
                else if (consumeTime < 3600) return $"{Math.Floor(consumeTime / 60)}m{(consumeTime % 60).ToString("f2")}s";
                return $"{Math.Floor(consumeTime / 3600)}h{Math.Floor((consumeTime % 3600) / 60)}m{(consumeTime % 60).ToString("f2")}s";
            }
        }
        private string _endTimeStr;
        public string endTimeStr
        {
            get => _endTimeStr;
            set => RaiseAndSetIfchanged(ref _endTimeStr, value);
        }
        private void RefreshCount()
        {
            this.allCount = this.okCount + this.ngCount;
            RaisePropertyChanged("countStr");
        }
        public InfoTable()
        {
            this.DataContext = this;
            InitializeComponent();
        }
    }
}
Plugin.TestResult/InfoWindow.xaml
New file
@@ -0,0 +1,25 @@
<Window x:Class="Plugin.TestResult_WPF.InfoWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Plugin.TestResult_WPF"
        mc:Ignorable="d"
        Title="{Binding title}" Height="500" Width="800">
    <DataGrid ItemsSource="{Binding dataSource}" AutoGenerateColumns="False"
              CanUserAddRows="False"
              CanUserDeleteRows="False"
              IsReadOnly="True"
              ColumnWidth="*"
              FontSize="16"
              >
        <DataGrid.Columns>
            <DataGridTextColumn Header="测试项名称" Binding="{Binding step}"/>
            <DataGridTextColumn Header="测试值" Binding="{Binding value}"/>
            <DataGridTextColumn Header="测试结果" Binding="{Binding result}"/>
            <DataGridTextColumn Header="测试范围" Binding="{Binding range}"/>
            <DataGridTextColumn Header="测试条件" Binding="{Binding condition}"/>
            <DataGridTextColumn Header="更新时间" Binding="{Binding updateTime}"/>
        </DataGrid.Columns>
    </DataGrid>
</Window>
Plugin.TestResult/InfoWindow.xaml.cs
New file
@@ -0,0 +1,58 @@
using Plugin.TestResult_WPF.Model;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace Plugin.TestResult_WPF
{
    /// <summary>
    /// InfoWindow.xaml 的交互逻辑
    /// </summary>
    public partial class InfoWindow : Window, INotifyPropertyChanged
    {
        #region ...
        public event PropertyChangedEventHandler PropertyChanged;
        public virtual void RaisePropertyChanged(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
        public void RaiseAndSetIfchanged<T>(ref T old, T @new, [CallerMemberName] string propertyName = null)
        {
            old = @new;
            if (propertyName != null) RaisePropertyChanged(propertyName);
        }
        #endregion
        private string _title;
        public string title
        {
            get => _title;
            set => RaiseAndSetIfchanged(ref _title, value);
        }
        private ObservableCollection<Info> _dataSource;
        public ObservableCollection<Info> dataSource
        {
            get => _dataSource == null ? new ObservableCollection<Info>() : _dataSource;
            set => RaiseAndSetIfchanged(ref _dataSource, value);
        }
        public InfoWindow(string title, ObservableCollection<Info> infos)
        {
            this.title = title;
            this.dataSource = infos;
            this.DataContext = this;
            InitializeComponent();
        }
        public void UpdateSource(ObservableCollection<Info> infos)
        {
            this.dataSource = infos;
        }
    }
}
Plugin.TestResult/Model/BaseConfig.cs
New file
@@ -0,0 +1,157 @@
using System.Collections;
using System.Configuration;
using System.Runtime.CompilerServices;
namespace Plugin.TestResult_WPF.Model
{
    public class BaseConfig
    {
        private static Hashtable customConfig = (Hashtable)ConfigurationManager.GetSection("TestResultConfig");
        /// <summary>
        /// 首页布局行数
        /// </summary>
        private int row = 1;
        public int Row
        {
            get
            {
                var data = GetValue();
                return data == null ? row : int.Parse(data);
            }
        }
        private string GetValue([CallerMemberName] string propertyName = null)
        {
            return (customConfig != null && customConfig.ContainsKey(propertyName)) ? customConfig[propertyName].ToString() : null;
        }
        /// <summary>
        /// 首页布局列数
        /// </summary>
        private int column = 2;
        public int Column
        {
            get
            {
                var data = GetValue();
                return data == null ? column : int.Parse(data);
            }
        }
        /// <summary>
        /// 炉数 -1 就是row*column
        /// </summary>
        private int cellCount = -1;
        public int CellCount
        {
            get
            {
                var data = GetValue();
                return data == null ? cellCount : int.Parse(data);
            }
        }
        /// <summary>
        /// 每个炉子的点位数
        /// </summary>
        private int positionCount = 54;
        public int PositionCount
        {
            get
            {
                var data = GetValue();
                return data == null ? positionCount : int.Parse(data);
            }
        }
        /// <summary>
        /// 曲线最多保存多少数据
        /// </summary>
        private int maxSize = 10000;
        public int MaxSize
        {
            get
            {
                var data = GetValue();
                return data == null ? maxSize : int.Parse(data);
            }
        }
        /// <summary>
        /// 图表刷新频率 ms
        /// </summary>
        private int refreshInterval = 500;
        public int RefreshInterval
        {
            get
            {
                var data = GetValue();
                return data == null ? refreshInterval : int.Parse(data);
            }
        }
        /// <summary>
        /// 炉子名称
        /// </summary>
        private string nameStr = "A,B,C,D,E,F,G";
        public string NameStr
        {
            get
            {
                var data = GetValue();
                return data == null ? nameStr : data;
            }
        }
        /// <summary>
        /// 默认展示的图表标题
        /// </summary>
        private string mainChartTitle = "平均温度";
        public string MainChartTitle
        {
            get
            {
                var data = GetValue();
                return data == null ? mainChartTitle : data;
            }
        }
        /// <summary>
        /// 双击打开的详细图表标题
        /// </summary>
        private string detailChartTitle = "详细温度";
        public string DetailChartTitle
        {
            get
            {
                var data = GetValue();
                return data == null ? detailChartTitle : data;
            }
        }
        /// <summary>
        /// 穴位Step列表最大容量
        /// </summary>
        private int cellListMaxSize = 10000;
        public int CellListMaxSize
        {
            get
            {
                var data = GetValue();
                return data == null ? cellListMaxSize : int.Parse(data);
            }
        }
        private int portStart = 6660;
        public int PortStart
        {
            get
            {
                var data = GetValue();
                return data == null ? portStart : int.Parse(data);
            }
        }
        public bool testing = false;
    }
}
Plugin.TestResult/Model/BaseModel.cs
New file
@@ -0,0 +1,21 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Plugin.TestResult_WPF.Model
{
    public class BaseModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public virtual void RaisePropertyChanged(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
        public void RaiseAndSetIfchanged<T>(ref T old, T @new, [CallerMemberName] string propertyName = null)
        {
            old = @new;
            if (propertyName != null) RaisePropertyChanged(propertyName);
        }
    }
}
Plugin.TestResult/Model/CellInfo.cs
New file
@@ -0,0 +1,9 @@
namespace Plugin.TestResult_WPF.Model
{
    public class CellInfo
    {
        public string MachineName { get; set; }
        public string OvenName { get; set; }
        public int Index { get; set; }
    }
}
Plugin.TestResult/Model/DataTableModel.cs
New file
@@ -0,0 +1,7 @@
namespace Plugin.TestResult_WPF.Model
{
    public class DataTableModel : BaseModel
    {
    }
}
Plugin.TestResult/Model/Info.cs
New file
@@ -0,0 +1,60 @@
namespace Plugin.TestResult_WPF.Model
{
    public class Info : BaseModel
    {
        private string _step;
        public string step
        {
            get => _step;
            set => RaiseAndSetIfchanged(ref _step, value);
        }
        private string _value;
        public string value
        {
            get => _value;
            set => RaiseAndSetIfchanged(ref _value, value);
        }
        private string _result;
        public string result
        {
            get => _result;
            set => RaiseAndSetIfchanged(ref _result, value);
        }
        private string _range;
        public string range
        {
            get => _range;
            set => RaiseAndSetIfchanged(ref _range, value);
        }
        private string _condition;
        public string condition
        {
            get => _condition;
            set => RaiseAndSetIfchanged(ref _condition, value);
        }
        private string _updateTime;
        public string updateTime
        {
            get => _updateTime;
            set => RaiseAndSetIfchanged(ref _updateTime, value);
        }
    }
}
Plugin.TestResult/Model/TcpServer.cs
New file
@@ -0,0 +1,41 @@
using ScottPlot.Colormaps;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using System;
using System.Text;
namespace Plugin.TestResult_WPF.Model
{
    public class DTReceiveFilter : ReceiveFilterBase<StringRequestInfo>
    {
        public override StringRequestInfo Filter(byte[] readBuffer, int offset, int length, bool toBeCopied, out int rest)
        {
            rest = 0;
            StringBuilder sb = new StringBuilder();
            int i = offset;
            while (i < readBuffer.Length)
            {
                byte b = readBuffer[i++];
                if (b == '\n')
                {
                    break;
                }
                sb.Append((char)b);
            }
            return new StringRequestInfo(DateTime.Now + "", sb.ToString(), null);
        }
    }
    public class TcpServer : AppServer<TcpSession>
    {
        public TcpServer() : base(new DefaultReceiveFilterFactory<DTReceiveFilter, StringRequestInfo>())
        {
        }
    }
    public class TcpSession : AppSession<TcpSession, StringRequestInfo>
    {
    }
}
Plugin.TestResult/OpenTK.dll.config
New file
@@ -0,0 +1,25 @@
<configuration>
  <dllmap os="linux" dll="opengl32.dll" target="libGL.so.1"/>
  <dllmap os="linux" dll="glu32.dll" target="libGLU.so.1"/>
  <dllmap os="linux" dll="openal32.dll" target="libopenal.so.1"/>
  <dllmap os="linux" dll="alut.dll" target="libalut.so.0"/>
  <dllmap os="linux" dll="opencl.dll" target="libOpenCL.so"/>
  <dllmap os="linux" dll="libX11" target="libX11.so.6"/>
  <dllmap os="linux" dll="libXi" target="libXi.so.6"/>
  <dllmap os="linux" dll="SDL2.dll" target="libSDL2-2.0.so.0"/>
  <dllmap os="osx" dll="opengl32.dll" target="/System/Library/Frameworks/OpenGL.framework/OpenGL"/>
  <dllmap os="osx" dll="openal32.dll" target="/System/Library/Frameworks/OpenAL.framework/OpenAL" />
  <dllmap os="osx" dll="alut.dll" target="/System/Library/Frameworks/OpenAL.framework/OpenAL" />
  <dllmap os="osx" dll="libGLES.dll" target="/System/Library/Frameworks/OpenGLES.framework/OpenGLES" />
  <dllmap os="osx" dll="libGLESv1_CM.dll" target="/System/Library/Frameworks/OpenGLES.framework/OpenGLES" />
  <dllmap os="osx" dll="libGLESv2.dll" target="/System/Library/Frameworks/OpenGLES.framework/OpenGLES" />
  <dllmap os="osx" dll="opencl.dll" target="/System/Library/Frameworks/OpenCL.framework/OpenCL"/>
  <dllmap os="osx" dll="SDL2.dll" target="libSDL2.dylib"/>
  <!-- XQuartz compatibility (X11 on Mac) -->
  <dllmap os="osx" dll="libGL.so.1" target="/usr/X11/lib/libGL.dylib"/>
  <dllmap os="osx" dll="libX11" target="/usr/X11/lib/libX11.dylib"/>
  <dllmap os="osx" dll="libXcursor.so.1" target="/usr/X11/lib/libXcursor.dylib"/>
  <dllmap os="osx" dll="libXi" target="/usr/X11/lib/libXi.dylib"/>
  <dllmap os="osx" dll="libXinerama" target="/usr/X11/lib/libXinerama.dylib"/>
  <dllmap os="osx" dll="libXrandr.so.2" target="/usr/X11/lib/libXrandr.dylib"/>
</configuration>
Plugin.TestResult/Plugin.TestResult.csproj
New file
@@ -0,0 +1,313 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{7375B74C-B6BF-4814-9838-E1CAACAD1897}</ProjectGuid>
    <OutputType>library</OutputType>
    <RootNamespace>Plugin.TestResult</RootNamespace>
    <AssemblyName>Plugin.TestResult</AssemblyName>
    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
    <WarningLevel>4</WarningLevel>
    <Deterministic>true</Deterministic>
    <NuGetPackageImportStamp>
    </NuGetPackageImportStamp>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
    <DebugSymbols>true</DebugSymbols>
    <OutputPath>bin\x64\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <DebugType>full</DebugType>
    <PlatformTarget>x64</PlatformTarget>
    <LangVersion>7.3</LangVersion>
    <ErrorReport>prompt</ErrorReport>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
    <OutputPath>bin\x64\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <Optimize>true</Optimize>
    <DebugType>pdbonly</DebugType>
    <PlatformTarget>x64</PlatformTarget>
    <LangVersion>7.3</LangVersion>
    <ErrorReport>prompt</ErrorReport>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="CommonLibs">
      <HintPath>..\Libs\CommonLibs.dll</HintPath>
    </Reference>
    <Reference Include="GLWpfControl, Version=3.3.0.0, Culture=neutral, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\OpenTK.GLWpfControl.3.3.0\lib\net452\GLWpfControl.dll</HintPath>
    </Reference>
    <Reference Include="HarfBuzzSharp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\HarfBuzzSharp.7.3.0.3\lib\net462\HarfBuzzSharp.dll</HintPath>
    </Reference>
    <Reference Include="NationalInstruments.TestStand.Interop.AdapterAPI, Version=20.0.0.49158, Culture=neutral, PublicKeyToken=ad9244eb3b825cd8, processorArchitecture=MSIL">
      <SpecificVersion>False</SpecificVersion>
      <EmbedInteropTypes>True</EmbedInteropTypes>
      <HintPath>..\Libs\NationalInstruments.TestStand.Interop.AdapterAPI.dll</HintPath>
    </Reference>
    <Reference Include="NationalInstruments.TestStand.Interop.API, Version=20.0.0.49158, Culture=neutral, PublicKeyToken=ad9244eb3b825cd8, processorArchitecture=MSIL">
      <SpecificVersion>False</SpecificVersion>
      <EmbedInteropTypes>True</EmbedInteropTypes>
      <HintPath>..\Libs\NationalInstruments.TestStand.Interop.API.dll</HintPath>
    </Reference>
    <Reference Include="NationalInstruments.TestStand.Interop.SequenceAnalyzer, Version=20.0.0.49158, Culture=neutral, PublicKeyToken=ad9244eb3b825cd8, processorArchitecture=MSIL">
      <SpecificVersion>False</SpecificVersion>
      <EmbedInteropTypes>True</EmbedInteropTypes>
      <HintPath>..\Libs\NationalInstruments.TestStand.Interop.SequenceAnalyzer.dll</HintPath>
    </Reference>
    <Reference Include="NationalInstruments.TestStand.Interop.UI, Version=20.0.0.49158, Culture=neutral, PublicKeyToken=ad9244eb3b825cd8, processorArchitecture=MSIL">
      <SpecificVersion>False</SpecificVersion>
      <EmbedInteropTypes>True</EmbedInteropTypes>
      <HintPath>..\Libs\NationalInstruments.TestStand.Interop.UI.dll</HintPath>
    </Reference>
    <Reference Include="NationalInstruments.TestStand.Interop.UI.AxControls, Version=20.0.0.49158, Culture=neutral, PublicKeyToken=ad9244eb3b825cd8, processorArchitecture=MSIL">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>..\Libs\NationalInstruments.TestStand.Interop.UI.AxControls.dll</HintPath>
    </Reference>
    <Reference Include="NationalInstruments.TestStand.Interop.UI.Support, Version=20.0.0.49158, Culture=neutral, PublicKeyToken=ad9244eb3b825cd8, processorArchitecture=MSIL">
      <SpecificVersion>False</SpecificVersion>
      <EmbedInteropTypes>True</EmbedInteropTypes>
      <HintPath>..\Libs\NationalInstruments.TestStand.Interop.UI.Support.dll</HintPath>
    </Reference>
    <Reference Include="NationalInstruments.TestStand.Utility">
      <HintPath>..\Libs\NationalInstruments.TestStand.Utility.dll</HintPath>
    </Reference>
    <Reference Include="OpenTK, Version=3.3.1.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\OpenTK.3.3.1\lib\net20\OpenTK.dll</HintPath>
    </Reference>
    <Reference Include="ParameterHelper">
      <HintPath>..\Libs\ParameterHelper.dll</HintPath>
    </Reference>
    <Reference Include="ScottPlot, Version=5.0.47.0, Culture=neutral, PublicKeyToken=86698dc10387c39e, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\ScottPlot.5.0.47\lib\net462\ScottPlot.dll</HintPath>
    </Reference>
    <Reference Include="ScottPlot.WPF, Version=5.0.47.0, Culture=neutral, PublicKeyToken=86698dc10387c39e, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\ScottPlot.WPF.5.0.47\lib\net462\ScottPlot.WPF.dll</HintPath>
    </Reference>
    <Reference Include="SkiaSharp, Version=2.88.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\SkiaSharp.2.88.9\lib\net462\SkiaSharp.dll</HintPath>
    </Reference>
    <Reference Include="SkiaSharp.HarfBuzz, Version=2.88.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\SkiaSharp.HarfBuzz.2.88.9\lib\net462\SkiaSharp.HarfBuzz.dll</HintPath>
    </Reference>
    <Reference Include="SkiaSharp.Views.Desktop.Common, Version=2.88.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\SkiaSharp.Views.Desktop.Common.2.88.9\lib\net462\SkiaSharp.Views.Desktop.Common.dll</HintPath>
    </Reference>
    <Reference Include="SkiaSharp.Views.WPF, Version=2.88.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\SkiaSharp.Views.WPF.2.88.9\lib\net462\SkiaSharp.Views.WPF.dll</HintPath>
    </Reference>
    <Reference Include="SuperSocket.Facility">
      <HintPath>..\Libs\SuperSocket.Facility.dll</HintPath>
    </Reference>
    <Reference Include="SuperSocket.SocketBase">
      <HintPath>..\Libs\SuperSocket.SocketBase.dll</HintPath>
    </Reference>
    <Reference Include="SuperSocket.SocketEngine">
      <HintPath>..\Libs\SuperSocket.SocketEngine.dll</HintPath>
    </Reference>
    <Reference Include="SupersocketCommon">
      <HintPath>..\Libs\SupersocketCommon.dll</HintPath>
    </Reference>
    <Reference Include="System" />
    <Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
    </Reference>
    <Reference Include="System.Configuration" />
    <Reference Include="System.Data" />
    <Reference Include="System.Drawing" />
    <Reference Include="System.Drawing.Common, Version=4.0.0.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\System.Drawing.Common.4.7.3\lib\net461\System.Drawing.Common.dll</HintPath>
    </Reference>
    <Reference Include="System.Memory, Version=4.0.1.2, Culture=neutral, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll</HintPath>
      <SpecificVersion>False</SpecificVersion>
    </Reference>
    <Reference Include="System.Numerics" />
    <Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
    </Reference>
    <Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\System.Runtime.CompilerServices.Unsafe.4.5.3\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
    </Reference>
    <Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll</HintPath>
    </Reference>
    <Reference Include="System.Windows.Forms" />
    <Reference Include="System.Xml" />
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System.Core" />
    <Reference Include="System.Xml.Linq" />
    <Reference Include="System.Data.DataSetExtensions" />
    <Reference Include="System.Net.Http" />
    <Reference Include="System.Xaml">
      <RequiredTargetFramework>4.0</RequiredTargetFramework>
    </Reference>
    <Reference Include="WindowsBase" />
    <Reference Include="PresentationCore" />
    <Reference Include="PresentationFramework" />
    <Reference Include="WindowsFormsIntegration" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="ChartWindow.xaml.cs">
      <DependentUpon>ChartWindow.xaml</DependentUpon>
    </Compile>
    <Compile Include="Constant\CommonConstant.cs" />
    <Compile Include="Model\BaseConfig.cs" />
    <Compile Include="Model\CellInfo.cs" />
    <Compile Include="Model\Info.cs" />
    <Compile Include="Model\TcpServer.cs" />
    <Compile Include="Scanner.xaml.cs">
      <DependentUpon>Scanner.xaml</DependentUpon>
    </Compile>
    <Compile Include="Util\CommonCodeParser.cs" />
    <Compile Include="Util\FontSizeConverter.cs" />
    <Compile Include="ViewControl.xaml.cs">
      <DependentUpon>ViewControl.xaml</DependentUpon>
    </Compile>
    <Page Include="Chart.xaml">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
    <Page Include="ChartWindow.xaml">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
    <Page Include="DataTable.xaml">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
    <Page Include="InfoTable.xaml">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
    <Page Include="InfoWindow.xaml">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
    <Page Include="Scanner.xaml">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
    <Page Include="SingleCell.xaml">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
    <Page Include="TestResultView.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
    </Page>
    <Compile Include="Chart.xaml.cs">
      <DependentUpon>Chart.xaml</DependentUpon>
    </Compile>
    <Compile Include="DataTable.xaml.cs">
      <DependentUpon>DataTable.xaml</DependentUpon>
    </Compile>
    <Compile Include="Util\DpUtil.cs" />
    <Compile Include="InfoTable.xaml.cs">
      <DependentUpon>InfoTable.xaml</DependentUpon>
    </Compile>
    <Compile Include="InfoWindow.xaml.cs">
      <DependentUpon>InfoWindow.xaml</DependentUpon>
    </Compile>
    <Compile Include="Model\BaseModel.cs" />
    <Compile Include="Model\DataTableModel.cs" />
    <Compile Include="SingleCell.xaml.cs">
      <DependentUpon>SingleCell.xaml</DependentUpon>
    </Compile>
    <Compile Include="TestResultView.xaml.cs">
      <DependentUpon>TestResultView.xaml</DependentUpon>
      <SubType>Code</SubType>
    </Compile>
    <Page Include="ViewControl.xaml">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Properties\AssemblyInfo.cs">
      <SubType>Code</SubType>
    </Compile>
    <Compile Include="Properties\Resources.Designer.cs">
      <AutoGen>True</AutoGen>
      <DesignTime>True</DesignTime>
      <DependentUpon>Resources.resx</DependentUpon>
    </Compile>
    <Compile Include="Properties\Settings.Designer.cs">
      <AutoGen>True</AutoGen>
      <DependentUpon>Settings.settings</DependentUpon>
      <DesignTimeSharedInput>True</DesignTimeSharedInput>
    </Compile>
    <EmbeddedResource Include="Properties\Resources.resx">
      <Generator>ResXFileCodeGenerator</Generator>
      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
    </EmbeddedResource>
    <None Include="OpenTK.dll.config" />
    <None Include="packages.config" />
    <None Include="Properties\Settings.settings">
      <Generator>SettingsSingleFileGenerator</Generator>
      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
    </None>
  </ItemGroup>
  <ItemGroup>
    <Resource Include="Resource\checkInFailed2.png" />
    <Resource Include="Resource\ng2.png" />
    <Resource Include="Resource\ok2.png" />
    <Resource Include="Resource\testing2.png" />
    <Resource Include="Resource\waiting2.png" />
  </ItemGroup>
  <ItemGroup>
    <Resource Include="Resource\alert.png" />
  </ItemGroup>
  <ItemGroup>
    <Resource Include="Resource\checkIn.png" />
  </ItemGroup>
  <ItemGroup>
    <Resource Include="Resource\ready.png" />
  </ItemGroup>
  <ItemGroup>
    <Resource Include="Resource\barcode.png" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <Import Project="..\WPFProject\packages\HarfBuzzSharp.NativeAssets.macOS.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.macOS.targets" Condition="Exists('..\WPFProject\packages\HarfBuzzSharp.NativeAssets.macOS.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.macOS.targets')" />
  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
    <PropertyGroup>
      <ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
    </PropertyGroup>
    <Error Condition="!Exists('..\WPFProject\packages\HarfBuzzSharp.NativeAssets.macOS.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.macOS.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\WPFProject\packages\HarfBuzzSharp.NativeAssets.macOS.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.macOS.targets'))" />
    <Error Condition="!Exists('..\WPFProject\packages\HarfBuzzSharp.NativeAssets.Win32.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Win32.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\WPFProject\packages\HarfBuzzSharp.NativeAssets.Win32.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Win32.targets'))" />
    <Error Condition="!Exists('..\WPFProject\packages\SkiaSharp.NativeAssets.macOS.2.88.9\build\net462\SkiaSharp.NativeAssets.macOS.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\WPFProject\packages\SkiaSharp.NativeAssets.macOS.2.88.9\build\net462\SkiaSharp.NativeAssets.macOS.targets'))" />
    <Error Condition="!Exists('..\WPFProject\packages\SkiaSharp.NativeAssets.Win32.2.88.9\build\net462\SkiaSharp.NativeAssets.Win32.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\WPFProject\packages\SkiaSharp.NativeAssets.Win32.2.88.9\build\net462\SkiaSharp.NativeAssets.Win32.targets'))" />
    <Error Condition="!Exists('..\WPFProject\packages\HarfBuzzSharp.NativeAssets.Linux.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Linux.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\WPFProject\packages\HarfBuzzSharp.NativeAssets.Linux.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Linux.targets'))" />
    <Error Condition="!Exists('..\WPFProject\packages\SkiaSharp.NativeAssets.Linux.NoDependencies.2.88.9\build\net462\SkiaSharp.NativeAssets.Linux.NoDependencies.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\WPFProject\packages\SkiaSharp.NativeAssets.Linux.NoDependencies.2.88.9\build\net462\SkiaSharp.NativeAssets.Linux.NoDependencies.targets'))" />
  </Target>
  <Import Project="..\WPFProject\packages\HarfBuzzSharp.NativeAssets.Win32.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Win32.targets" Condition="Exists('..\WPFProject\packages\HarfBuzzSharp.NativeAssets.Win32.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Win32.targets')" />
  <Import Project="..\WPFProject\packages\SkiaSharp.NativeAssets.macOS.2.88.9\build\net462\SkiaSharp.NativeAssets.macOS.targets" Condition="Exists('..\WPFProject\packages\SkiaSharp.NativeAssets.macOS.2.88.9\build\net462\SkiaSharp.NativeAssets.macOS.targets')" />
  <Import Project="..\WPFProject\packages\SkiaSharp.NativeAssets.Win32.2.88.9\build\net462\SkiaSharp.NativeAssets.Win32.targets" Condition="Exists('..\WPFProject\packages\SkiaSharp.NativeAssets.Win32.2.88.9\build\net462\SkiaSharp.NativeAssets.Win32.targets')" />
  <Import Project="..\WPFProject\packages\HarfBuzzSharp.NativeAssets.Linux.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Linux.targets" Condition="Exists('..\WPFProject\packages\HarfBuzzSharp.NativeAssets.Linux.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Linux.targets')" />
  <Import Project="..\WPFProject\packages\SkiaSharp.NativeAssets.Linux.NoDependencies.2.88.9\build\net462\SkiaSharp.NativeAssets.Linux.NoDependencies.targets" Condition="Exists('..\WPFProject\packages\SkiaSharp.NativeAssets.Linux.NoDependencies.2.88.9\build\net462\SkiaSharp.NativeAssets.Linux.NoDependencies.targets')" />
  <PropertyGroup>
    <PostBuildEvent>copy /Y "$(TargetPath)" "$(SolutionDir)bin\$(PlatformName)\$(ConfigurationName)\Plugin\$(TargetFileName)"
copy /Y "$(TargetDir)$(TargetName).pdb" "$(SolutionDir)bin\$(PlatformName)\$(ConfigurationName)\Plugin\$(TargetName).pdb"</PostBuildEvent>
  </PropertyGroup>
</Project>
Plugin.TestResult/Properties/AssemblyInfo.cs
New file
@@ -0,0 +1,53 @@
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows;
// 有关程序集的一般信息由以下
// 控制。更改这些特性值可修改
// 与程序集关联的信息。
[assembly: AssemblyTitle("Plugin.TestResult_WPF")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Plugin.TestResult_WPF")]
[assembly: AssemblyCopyright("Copyright ©  2024")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// 将 ComVisible 设置为 false 会使此程序集中的类型
//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
//请将此类型的 ComVisible 特性设置为 true。
[assembly: ComVisible(false)]
//若要开始生成可本地化的应用程序,请设置
//.csproj 文件中的 <UICulture>CultureYouAreCodingWith</UICulture>
//在 <PropertyGroup> 中。例如,如果你使用的是美国英语。
//使用的是美国英语,请将 <UICulture> 设置为 en-US。  然后取消
//对以下 NeutralResourceLanguage 特性的注释。  更新
//以下行中的“en-US”以匹配项目文件中的 UICulture 设置。
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
[assembly: ThemeInfo(
    ResourceDictionaryLocation.None, //主题特定资源词典所处位置
                                     //(未在页面中找到资源时使用,
                                     //或应用程序资源字典中找到时使用)
    ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置
                                              //(未在页面中找到资源时使用,
                                              //、应用程序或任何主题专用资源字典中找到时使用)
)]
// 程序集的版本信息由下列四个值组成:
//
//      主版本
//      次版本
//      生成号
//      修订号
//
//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
//通过使用 "*",如下所示:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
Plugin.TestResult/Properties/Resources.Designer.cs
New file
@@ -0,0 +1,62 @@
//------------------------------------------------------------------------------
// <auto-generated>
//     此代码由工具生成。
//     运行时版本: 4.0.30319.42000
//
//     对此文件的更改可能导致不正确的行为,如果
//     重新生成代码,则所做更改将丢失。
// </auto-generated>
//------------------------------------------------------------------------------
namespace Plugin.TestResult_WPF.Properties {
    /// <summary>
    ///   强类型资源类,用于查找本地化字符串等。
    /// </summary>
    // 此类是由 StronglyTypedResourceBuilder
    // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
    // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
    // (以 /str 作为命令选项),或重新生成 VS 项目。
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
    internal class Resources {
        private static global::System.Resources.ResourceManager resourceMan;
        private static global::System.Globalization.CultureInfo resourceCulture;
        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        internal Resources() {
        }
        /// <summary>
        ///   返回此类使用的缓存 ResourceManager 实例。
        /// </summary>
        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
        internal static global::System.Resources.ResourceManager ResourceManager {
            get {
                if ((resourceMan == null)) {
                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Plugin.TestResult_WPF.Properties.Resources", typeof(Resources).Assembly);
                    resourceMan = temp;
                }
                return resourceMan;
            }
        }
        /// <summary>
        ///   重写当前线程的 CurrentUICulture 属性,对
        ///   使用此强类型资源类的所有资源查找执行重写。
        /// </summary>
        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
        internal static global::System.Globalization.CultureInfo Culture {
            get {
                return resourceCulture;
            }
            set {
                resourceCulture = value;
            }
        }
    }
}
Plugin.TestResult/Properties/Resources.resx
New file
@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
  <!--
    Microsoft ResX Schema
    Version 2.0
    The primary goals of this format is to allow a simple XML format
    that is mostly human readable. The generation and parsing of the
    various data types are done through the TypeConverter classes
    associated with the data types.
    Example:
    ... ado.net/XML headers & schema ...
    <resheader name="resmimetype">text/microsoft-resx</resheader>
    <resheader name="version">2.0</resheader>
    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
        <value>[base64 mime encoded serialized .NET Framework object]</value>
    </data>
    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
        <comment>This is a comment</comment>
    </data>
    There are any number of "resheader" rows that contain simple
    name/value pairs.
    Each data row contains a name, and value. The row also contains a
    type or mimetype. Type corresponds to a .NET class that support
    text/value conversion through the TypeConverter architecture.
    Classes that don't support this are serialized and stored with the
    mimetype set.
    The mimetype is used for serialized objects, and tells the
    ResXResourceReader how to depersist the object. This is currently not
    extensible. For a given mimetype the value must be set accordingly:
    Note - application/x-microsoft.net.object.binary.base64 is the format
    that the ResXResourceWriter will generate, however the reader can
    read any of the formats listed below.
    mimetype: application/x-microsoft.net.object.binary.base64
    value   : The object must be serialized with
            : System.Serialization.Formatters.Binary.BinaryFormatter
            : and then encoded with base64 encoding.
    mimetype: application/x-microsoft.net.object.soap.base64
    value   : The object must be serialized with
            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
            : and then encoded with base64 encoding.
    mimetype: application/x-microsoft.net.object.bytearray.base64
    value   : The object must be serialized into a byte array
            : using a System.ComponentModel.TypeConverter
            : and then encoded with base64 encoding.
    -->
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xsd:element name="root" msdata:IsDataSet="true">
      <xsd:complexType>
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="metadata">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" />
              <xsd:attribute name="type" type="xsd:string" />
              <xsd:attribute name="mimetype" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="assembly">
            <xsd:complexType>
              <xsd:attribute name="alias" type="xsd:string" />
              <xsd:attribute name="name" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="data">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="resheader">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" />
            </xsd:complexType>
          </xsd:element>
        </xsd:choice>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>
  <resheader name="resmimetype">
    <value>text/microsoft-resx</value>
  </resheader>
  <resheader name="version">
    <value>2.0</value>
  </resheader>
  <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <resheader name="writer">
    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
</root>
Plugin.TestResult/Properties/Settings.Designer.cs
New file
@@ -0,0 +1,30 @@
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.42000
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Plugin.TestResult_WPF.Properties
{
    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
    {
        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
        public static Settings Default
        {
            get
            {
                return defaultInstance;
            }
        }
    }
}
Plugin.TestResult/Properties/Settings.settings
New file
@@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
  <Profiles>
    <Profile Name="(Default)" />
  </Profiles>
  <Settings />
</SettingsFile>
Plugin.TestResult/Resource/alert.png
Plugin.TestResult/Resource/barcode.png
Plugin.TestResult/Resource/checkIn.png
Plugin.TestResult/Resource/checkInFailed2.png
Plugin.TestResult/Resource/ng2.png
Plugin.TestResult/Resource/ok2.png
Plugin.TestResult/Resource/ready.png
Plugin.TestResult/Resource/testing2.png
Plugin.TestResult/Resource/waiting2.png
Plugin.TestResult/Scanner.xaml
New file
@@ -0,0 +1,54 @@
<Window x:Class="Plugin.TestResult_WPF.Scanner"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Plugin.TestResult_WPF"
        WindowStartupLocation="CenterScreen"
        Closing="Window_Closing"
        mc:Ignorable="d"
        Title="{Binding title}" Height="450" Width="800">
    <Window.Resources>
        <local:FontSizeConverter x:Key="FontSizeConverter"/>
        <Style TargetType="Label">
            <Setter Property="HorizontalAlignment" Value="Right"/>
            <Setter Property="FontSize" Value="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=Window},
Converter={StaticResource FontSizeConverter}, ConverterParameter='100,1920'}" />
        </Style>
        <Style TargetType="TextBox">
            <Setter Property="FontSize" Value="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=Window},
Converter={StaticResource FontSizeConverter}, ConverterParameter='100,1920'}" />
        </Style>
        <Style TargetType="Button">
            <Setter Property="FontSize" Value="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=Window},
Converter={StaticResource FontSizeConverter}, ConverterParameter='100,1920'}" />
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition />
            <!--<RowDefinition />-->
            <RowDefinition />
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" HorizontalContentAlignment="Center" HorizontalAlignment="Stretch">请依次扫下列条码</Label>
        <Label Grid.Row="1" Grid.Column="0">储位条码</Label>
        <!--<Label Grid.Row="2" Grid.Column="0">夹具条码</Label>-->
        <Label Grid.Row="2" Grid.Column="0">产品条码</Label>
        <TextBox Grid.Row="1" Grid.Column="1" x:Name="positionText" TextWrapping="NoWrap"/>
        <!--<TextBox Grid.Row="2" Grid.Column="1"/>-->
        <TextBox Grid.Row="2" Grid.Column="1" x:Name="snrText"/>
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2">
            <Button Margin="20" Padding="15" Click="Con_Click">继续扫描</Button>
            <Button Margin="20" Padding="15" Click="Ok_Click">扫描完成</Button>
        </StackPanel>
    </Grid>
</Window>
Plugin.TestResult/Scanner.xaml.cs
New file
@@ -0,0 +1,216 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO.Ports;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace Plugin.TestResult_WPF
{
    public class BarcodeEventArg
    {
        public string name { get; private set; }
        public string positionCode { get; private set; }
        public string snrCode { get; private set; }
        public BarcodeEventArg(string name, string positionCode, string snrCode)
        {
            this.name = name;
            this.positionCode = positionCode;
            this.snrCode = snrCode;
        }
    }
    /// <summary>
    /// Scanner.xaml 的交互逻辑
    /// </summary>
    public partial class Scanner : Window, INotifyPropertyChanged
    {
        #region ...
        public event PropertyChangedEventHandler PropertyChanged;
        public virtual void RaisePropertyChanged(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
        public void RaiseAndSetIfchanged<T>(ref T old, T @new, [CallerMemberName] string propertyName = null)
        {
            old = @new;
            if (propertyName != null) RaisePropertyChanged(propertyName);
        }
        #endregion
        private string _title;
        public string title
        {
            get { return _title; }
            set => RaiseAndSetIfchanged(ref _title, value);
        }
        public event EventHandler<BarcodeEventArg> FinishBarcode;
        private DataTable dataTable;
        SerialPort serialPort;
        public Scanner(string title)
        {
            this.title = title;
            this.DataContext = this;
            InitializeComponent();
            InitScanner();
        }
        /// <summary>
        /// 设置扫码枪
        /// </summary>
        private void InitScanner()
        {
            string comStr = "";
            if (serialPort != null) return;
            if (this.title == "一区扫码")
            {
                comStr = "COM1";
            }
            else
            {
                comStr = "COM2";
            }
            // 配置串口
            serialPort = new SerialPort
            {
                PortName = comStr,
                BaudRate = 115200,
                Parity = Parity.None,
                DataBits = 8,
                StopBits = StopBits.One,
                Handshake = Handshake.None,
                DtrEnable = true,
                RtsEnable = true
                //ReadTimeout = 500,
                //WriteTimeout = 500,
                //ReciveBufferSize=4096
            };
            try
            {
                // 打开串口
                serialPort.Open();
                // 注册接收数据事件
                serialPort.DataReceived += SerialPort_DataReceived;
            }
            catch (Exception ex)
            {
                MessageBox.Show($"初始化扫码枪失败!{ex.StackTrace}");
            }
        }
        private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            try
            {
                SerialPort sp = (SerialPort)sender;
                string barcode = ReadDataFrom(sp);
                //string barcode =
                Dispatcher.Invoke(() =>
                {
                    if (snrText.IsFocused)
                    {
                        snrText.Text = barcode;
                        Con_Click(null, null);
                    }
                    else
                    {
                        positionText.Text = barcode;
                        //positionText.Focus();
                        //snrText.Text = "";
                        snrText.Focus();
                    }
                });
            }
            catch (Exception ex)
            {
                MessageBox.Show($"获取扫码枪数据失败!{ex.StackTrace}");
            }
        }
        private void Con_Click(object sender, RoutedEventArgs e)
        {
            Dispatcher.Invoke(() =>
            {
                string position = positionText.Text, snr = snrText.Text;
                if (string.IsNullOrEmpty(position) || string.IsNullOrEmpty(snr))
                {
                    MessageBox.Show("请输入储位条码和产品条码");
                    return;
                }
                try
                {
                    FinishBarcode.Invoke(this, new BarcodeEventArg(title, position, snr));
                    //positionText.Text = "";
                    //snrText.Text = "";
                    positionText.Focus();
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }
            });
        }
        private void Ok_Click(object sender, RoutedEventArgs e)
        {
            string position = positionText.Text, snr = snrText.Text;
            if (string.IsNullOrEmpty(position) || string.IsNullOrEmpty(snr))
            {
                MessageBox.Show("请输入储位条码和产品条码");
                return;
            }
            try
            {
                FinishBarcode.Invoke(this, new BarcodeEventArg(title, position, snr));
                this.Close();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
        private string ReadDataFrom(SerialPort sp)
        {
            string barcode = "";
            while (sp.BytesToRead > 0)
            {
                byte[] buffer = new byte[sp.BytesToRead];
                sp.Read(buffer, 0, buffer.Length);
                barcode += System.Text.Encoding.UTF8.GetString(buffer);
                System.Threading.Thread.Sleep(50);
            }
            return barcode;
        }
        private void Window_Closing(object sender, CancelEventArgs e)
        {
            try
            {
                serialPort.Close();
            }
            catch (Exception ex)
            {
            }
        }
    }
}
Plugin.TestResult/SingleCell.xaml
New file
@@ -0,0 +1,101 @@
<UserControl x:Class="Plugin.TestResult_WPF.SingleCell"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:Plugin.TestResult_WPF"
             mc:Ignorable="d"
             x:Name="uc"
             Padding="5"
             MouseDoubleClick="UserControl_MouseDoubleClick"
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <local:FontSizeConverter x:Key="FontSizeConverter"/>
    </UserControl.Resources>
    <UserControl.ToolTip>
        <ToolTip Padding="5" BorderThickness="1" BorderBrush="Black" Background="Yellow" Content="{Binding tooltipContent}"/>
    </UserControl.ToolTip>
    <UserControl.Style>
        <Style TargetType="UserControl">
            <Style.Triggers>
                <DataTrigger Binding="{Binding status}" Value="ok">
                    <Setter Property="Background" Value="#FF0F8853"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding status}" Value="ng">
                    <Setter Property="Background" Value="#fd6b6d"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding status}" Value="checkInFailed">
                    <Setter Property="Background" Value="#ffac00"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding status}" Value="checkIn">
                    <Setter Property="Background" Value="#FF00E2FF"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding status}" Value="testing">
                    <Setter Property="Background" Value="#1296db"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding status}" Value="waiting">
                    <Setter Property="Background" Value="#6e6e6e"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding status}" Value="alert">
                    <Setter Property="Background" Value="#e04733"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding status}" Value="ready">
                    <Setter Property="Background" Value="#FF08FF00"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding status}" Value="hadCode">
                    <Setter Property="Background" Value="#FFF"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </UserControl.Style>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <!--<TextBox Grid.Row="0" IsReadOnly="True" TextAlignment="Center" Margin="0,0,0,3" FontSize="15"
                 Background="Transparent" BorderThickness="0" Text="{Binding title}"/>-->
        <Label Grid.Row="0" HorizontalContentAlignment="Left" VerticalContentAlignment="Center" Padding="0" Margin="0,0,0,3"
               FontSize="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl},
            Converter={StaticResource FontSizeConverter}, ConverterParameter='15,103'}"
               FontWeight="Bold"
         Background="Transparent" BorderThickness="0" Content="{Binding title}"/>
        <Image Grid.Row="1">
            <Image.Style>
                <Style TargetType="Image">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding status}" Value="ok">
                            <Setter Property="Source" Value="Resource/ok2.png"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding status}" Value="ng">
                            <Setter Property="Source" Value="Resource/ng2.png"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding status}" Value="checkInFailed">
                            <Setter Property="Source" Value="Resource/checkInFailed2.png"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding status}" Value="checkIn">
                            <Setter Property="Source" Value="Resource/checkIn.png"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding status}" Value="testing">
                            <Setter Property="Source" Value="Resource/testing2.png"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding status}" Value="waiting">
                            <Setter Property="Source" Value="Resource/waiting2.png"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding status}" Value="alert">
                            <Setter Property="Source" Value="Resource/alert.png"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding status}" Value="ready">
                            <Setter Property="Source" Value="Resource/ready.png"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding status}" Value="hadCode">
                            <Setter Property="Source" Value="Resource/barcode.png"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Image.Style>
        </Image>
    </Grid>
</UserControl>
Plugin.TestResult/SingleCell.xaml.cs
New file
@@ -0,0 +1,195 @@
using Plugin.TestResult_WPF.Constant;
using Plugin.TestResult_WPF.Model;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace Plugin.TestResult_WPF
{
    /// <summary>
    /// SingleCell.xaml 的交互逻辑
    /// </summary>
    public partial class SingleCell : UserControl, INotifyPropertyChanged
    {
        #region ...
        public event PropertyChangedEventHandler PropertyChanged;
        public virtual void RaisePropertyChanged(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
        public void RaiseAndSetIfchanged<T>(ref T old, T @new, [CallerMemberName] string propertyName = null)
        {
            old = @new;
            if (propertyName != null) RaisePropertyChanged(propertyName);
        }
        #endregion
        private InfoWindow window;
        private int maxSize;
        public SingleCell()
        {
            DataContext = this;
            InitializeComponent();
        }
        public SingleCell(string parentName, int index,int maxSize)
        {
            this.parentName = parentName;
            this.index = index;
            this.maxSize = maxSize;
            DataContext = this;
            InitializeComponent();
        }
        private string _parentName;
        public string parentName
        {
            get { return _parentName; }
            set
            {
                RaiseAndSetIfchanged(ref _parentName, value);
                RaisePropertyChanged("title");
            }
        }
        private int _index;
        public int index
        {
            get { return _index; }
            set
            {
                RaiseAndSetIfchanged(ref _index, value);
                RaisePropertyChanged("title");
            }
        }
        public string title => $"{parentName}{index + 1}";
        private string _status = CommonStatus.WAITING;
        public string tooltipContent
        {
            get
            {
                string dataStr = "";
                if (infos != null && infos.Count > 0)
                {
                    foreach (var info in infos)
                    {
                        dataStr += $"\r\nStep:{info.step},Result:{info.result}";
                    }
                }
                return $"{title}\r\n状态:{CommonStatus.GetStatusDescription(status)}{dataStr}";
            }
        }
        public string status
        {
            get { return _status; }
            set => RaiseAndSetIfchanged(ref _status, value);
        }
        private ConcurrentQueue<Info> queue = new ConcurrentQueue<Info>();
        private ObservableCollection<Info> _infos;
        public ObservableCollection<Info> infos
        {
            get => _infos;
            set
            {
                RaiseAndSetIfchanged(ref _infos, value);
                RaisePropertyChanged("tooltipContent");
            }
        }
        private void UserControl_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            if (window == null)
            {
                window = new InfoWindow(title, infos);
                window.Closed += Window_Closed;
                window.Owner = Window.GetWindow(this);
                window.Show();
            }
            else
            {
                window.Activate();
            }
        }
        private void Window_Closed(object sender, EventArgs e)
        {
            this.window = null;
        }
        public void SetStatus(string status)
        {
            this.status = status;
        }
        public void SetInfos(List<Info> infos)
        {
            this.infos = new ObservableCollection<Info>(infos);
            if (window != null) window.UpdateSource(this.infos);
        }
        public void SetInfos(string[,] infos)
        {
            var data = new ObservableCollection<Info>();
            for (int i = 0; i < infos.GetLength(0); i++)
            {
                data.Add(new Info
                {
                    step = infos[i, 0],
                    result = infos[i, 1]
                });
            }
            this.infos = data;
            if (window != null) window.UpdateSource(this.infos);
        }
        public void SetInfos(Info info)
        {
            if (queue.Count >= maxSize)
            {
                queue.TryDequeue(out var first);
            }
            queue.Enqueue(info);
            var list = new ObservableCollection<Info>(queue);
            this.infos = list;
            if (window != null) window.UpdateSource(this.infos);
        }
        public void ClearInfo()
        {
            queue = new ConcurrentQueue<Info>();
            var list = new ObservableCollection<Info>(queue);
            this.infos = list;
            if (window != null) window.UpdateSource(this.infos);
        }
        public void SetInfos(string step, string result)
        {
            if (queue.Count >= maxSize)
            {
                queue.TryDequeue(out var info);
            }
            queue.Enqueue(new Info() { step = step, result = result });
            var list = new ObservableCollection<Info>(queue);
            this.infos = list;
            if (window != null) window.UpdateSource(this.infos);
        }
    }
}
Plugin.TestResult/TestResultView.xaml
New file
@@ -0,0 +1,16 @@
<UserControl x:Class="Plugin.TestResult_WPF.TestResultView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:Plugin.TestResult_WPF"
             mc:Ignorable="d"
             FontFamily="Microsoft YaHei"
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid x:Name="panel">
        <WindowsFormsHost x:Name="host" Visibility="Collapsed"></WindowsFormsHost>
        <Grid x:Name="mainGrid" local:GridProperties.BorderSpace="10">
        </Grid>
    </Grid>
</UserControl>
Plugin.TestResult/TestResultView.xaml.cs
New file
@@ -0,0 +1,383 @@
using NationalInstruments.TestStand.Interop.API;
using NationalInstruments.TestStand.Interop.UI.Ax;
using NationalInstruments.TestStand.Utility;
using Plugin.TestResult_WPF.Constant;
using Plugin.TestResult_WPF.Model;
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace Plugin.TestResult_WPF
{
    /// <summary>
    /// TestResultView.xaml 的交互逻辑
    /// </summary>
    public partial class TestResultView : UserControl
    {
        private int rowCount;
        private int colCount;
        private int cellCount;
        private DataTable[] tables;
        public Dictionary<string, DataTable> nameTables { get; private set; }
        private readonly BaseConfig config;
        public AxApplicationMgr testApp;
        public string testSeqFile;
        /// <summary>
        /// 测试序列
        /// </summary>
        private SequenceFile testSeq = null;
        public Engine engine;
        private Execution execution = null;
        /// <summary>
        /// 测试序列操作句柄
        /// </summary>
        private SequenceContext SequenceContext = null;
        /// <summary>
        /// 测试模型序列
        /// </summary>
        private SequenceFile modeSeq;
        public TestResultView() : this(new BaseConfig { testing = false })
        {
        }
        public TestResultView(BaseConfig config)
        {
            this.rowCount = config.Row;
            this.colCount = config.Column;
            this.config = config;
            if (config.CellCount < 0)
            {
                this.cellCount = rowCount * colCount;
            }
            else
            {
                this.cellCount = config.CellCount;
            }
            tables = new DataTable[this.cellCount];
            InitializeComponent();
            testApp = new AxApplicationMgr();
            testApp.ExitApplication += new EventHandler(this.axApplicationMgr_ExitApplication);
            testApp.UIMessageEvent += new _ApplicationMgrEvents_UIMessageEventEventHandler(this.axApplicationMgr_UIMessageEvent);
            testApp.ExecutionClosed += new _ApplicationMgrEvents_ExecutionClosedEventHandler(this.axApplicationMgr_ExecutionClosed);
            ((System.ComponentModel.ISupportInitialize)(this.testApp)).BeginInit();//myClient为第三方控件名
            host.Child = testApp;
            ((System.ComponentModel.ISupportInitialize)(this.testApp)).EndInit();
            InitMainGrid();
            //InitTestStandEngine();
            //StartTestSequence();
        }
        private void Refresh()
        {
            for (int i = 0; i < tables.Length; i++)
            {
                var sb = new System.Timers.Timer()
                {
                    Interval = 1000,
                    AutoReset = true,
                };
                var table = tables[i];
                Random random = new Random(i * 13);
                sb.Elapsed += (s, e) =>
                {
                    table.SetTemperature(random.Next(100));
                    for (int j = 0; j < 3; j++)
                    {
                        table.SetTemperature(j + "89", random.Next(100));
                    }
                    table.SetOkCount(random.Next(100));
                    table.SetNgCount(random.Next(100));
                    table.SetStatus(CommonStatus.GetRandomStatus());
                    table.SetStartTime(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
                    table.SetEndTime(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
                    table.SetConsumeTime(random.Next(10000));
                    var cell = table.GetSingleCell(random.Next(table.GetSize()));
                    cell.SetStatus(CommonStatus.GetRandomStatus());
                    cell.SetInfos(random.Next(1000) + "", random.Next(1000) + "");
                    table.SetStatusText(DateTime.Now + "");
                };
                sb.Start();
            }
        }
        public void InitMainGrid()
        {
            nameTables = new Dictionary<string, DataTable>();
            for (int r = 0; r < rowCount; r++)
            {
                RowDefinition row = new RowDefinition();
                mainGrid.RowDefinitions.Add(row);
            }
            for (int c = 0; c < colCount; c++)
            {
                ColumnDefinition col = new ColumnDefinition();
                mainGrid.ColumnDefinitions.Add(col);
            }
            string[] names = config.NameStr.Split(',');
            for (int n = 0; n < cellCount; n++)
            {
                string name = n < names.Length ? names[n] : "";
                DataTable child = new DataTable(this, n, name, config);
                mainGrid.Children.Add(child);
                tables[n] = child;
                nameTables[name] = child;
                int row = n / colCount;
                int col = n % colCount;
                Grid.SetRow(child, row);
                Grid.SetColumn(child, col);
            }
            if (config.testing) Refresh();
        }
        public DataTable GetDataTable(int index)
        {
            if (index > 0 && index < tables.Length) return tables[index];
            return null;
        }
        /// <summary>
        /// 启动测试序列
        /// </summary>
        public void InitTestStandEngine()
        {
            try
            {
                this.engine = this.testApp.GetEngine();
                this.engine.PersistBreakpoints = false;
                this.testApp.IsEditor = true;
                this.testApp.LoginOnStart = false;
                this.testApp.Start();
                this.modeSeq = this.engine.GetSequenceFileEx(@"C:\Program Files\National Instruments\TestStand 2020\Components\Models\TestStandModels\ParallelModel.seq");
            }
            catch (Exception ex)
            {
                throw new Exception("初始化TS引擎失败:" + ex.Message);
            }
        }
        public void StartTestSequence()
        {
            try
            {
                this.modeSeq = this.engine.GetSequenceFileEx(@"C:\Program Files\National Instruments\TestStand 2020\Components\Models\TestStandModels\ParallelModel.seq");
                this.testSeq = this.engine.GetSequenceFileEx(this.testSeqFile, 0, TypeConflictHandlerTypes.ConflictHandler_UseGlobalType);
                this.execution = this.engine.NewExecution(this.testSeq, "Test UUTs", this.modeSeq, false, 0, null, null, null);
                this.engine.Globals.SetValBoolean("StationState.StopTest", 0, false);
            }
            catch (Exception ex)
            {
                throw new Exception("启动测试序列失败:" + ex.Message);
            }
        }
        public void StopTest()
        {
            try
            {
                if (this.SequenceContext == null)
                    return;
                bool val = this.SequenceContext.StationGlobals.Exists("StationState.StopTest", 0);
                if (val)
                {
                    this.SequenceContext.StationGlobals.SetValBoolean("StationState.StopTest", 0, true);
                    bool isStop = this.execution.WaitForEndEx(2000, true, null, null);
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }
        private void axApplicationMgr_ExitApplication(object sender, EventArgs e)
        {
            Environment.ExitCode = testApp.ExitCode;
            TSHelper.DoSynchronousGCForCOMObjectDestruction();
        }
        private void axApplicationMgr_ExecutionClosed(object sender, _ApplicationMgrEvents_ExecutionClosedEvent e)
        {
            try
            {
                //this.ReleaseSequenceFile();
            }
            catch (Exception ex)
            {
                MessageBox.Show("释放测试序列失败:" + ex.Message);
            }
        }
        private void axApplicationMgr_UIMessageEvent(object sender, _ApplicationMgrEvents_UIMessageEventEvent e)
        {
            try
            {
                int EventCase = Convert.ToInt32(e.uiMsg.Event);
                int MethodCode = Convert.ToInt16(e.uiMsg.NumericData);
                string strData;
                if (EventCase != 10014) return;
                if (MethodCode == 0)
                {
                    /// 设置句柄
                    this.SequenceContext = (SequenceContext)e.uiMsg.ActiveXData;
                    return;
                }
                if (MethodCode == 1)
                {
                    /// 更新炉子状态
                    strData = e.uiMsg.StringData;
                    string[] datas_10014 = strData.Split(';');
                    int ovenIndex = int.Parse(datas_10014[0]);
                    string status = datas_10014[1];
                    tables[ovenIndex].SetStatus(status);
                    return;
                }
                if (MethodCode == 2)
                {
                    /// 更新炉子数量信息
                    strData = e.uiMsg.StringData;
                    string[] datas_10014 = strData.Split(';');
                    int ovenIndex = int.Parse(datas_10014[0]);
                    string okCount = datas_10014[1];
                    string ngCount = datas_10014[2];
                    tables[ovenIndex].SetOkCount(int.Parse(okCount));
                    tables[ovenIndex].SetNgCount(int.Parse(ngCount));
                    return;
                }
                if (MethodCode == 3)
                {
                    /// 更新炉子时间信息
                    strData = e.uiMsg.StringData;
                    string[] datas_10014 = strData.Split(';');
                    int ovenIndex = int.Parse(datas_10014[0]);
                    string startTime = datas_10014[1];
                    string consumeTime = datas_10014[2];
                    string planEndTime = datas_10014[3];
                    tables[ovenIndex].SetStartTime(startTime);
                    tables[ovenIndex].SetConsumeTime(double.Parse(consumeTime));
                    tables[ovenIndex].SetEndTime(planEndTime);
                    return;
                }
                if (MethodCode == 4)
                {
                    /// 更新老化炉底部状态栏
                    strData = e.uiMsg.StringData;
                    string[] datas_10014 = strData.Split(';');
                    int ovenIndex = int.Parse(datas_10014[0]);
                    string statusBar = datas_10014[1];
                    tables[ovenIndex].SetStatusText(statusBar);
                    return;
                }
                if (MethodCode == 5)
                {
                    /// 采集炉温度
                    strData = e.uiMsg.StringData;
                    string[] datas_10016 = strData.Split(';');
                    int ovenIndex = int.Parse(datas_10016[0]);
                    string[] names = datas_10016[1].Split(',');
                    string[] values = datas_10016[2].Split(',');
                    for (int x = 0; x < values.Length; x++)
                    {
                        if (x == 0) tables[ovenIndex].SetTemperature(double.Parse(values[x]));
                        else tables[ovenIndex].SetTemperature(names[x - 1], double.Parse(values[x]));
                    }
                    return;
                }
                if (MethodCode == 6)
                {
                    /// 更新穴位状态
                    strData = e.uiMsg.StringData;
                    string[] datas_10015 = strData.Split(';');
                    int ovenIndex = int.Parse(datas_10015[0]);
                    int cellIndex = int.Parse(datas_10015[1]);
                    string status_10015 = datas_10015[2];
                    tables[ovenIndex].GetSingleCell(cellIndex).SetStatus(status_10015);
                    return;
                }
                if (MethodCode == 7)
                {
                    /// 更新穴位参数
                    strData = e.uiMsg.StringData;
                    string[] datas_10015 = strData.Split(';');
                    int ovenIndex = int.Parse(datas_10015[0]);
                    int cellIndex = int.Parse(datas_10015[1]);
                    string strs = datas_10015[2];
                    string[] values = strs.Split(',');
                    //string step = datas_10015[2];
                    //string result = datas_10015[3];
                    tables[ovenIndex].GetSingleCell(cellIndex).SetInfos(new Info
                    {
                        step = values[0],
                        value = values[1],
                        result = values[2],
                        range = values[3],
                        condition = values[4],
                        updateTime = values[5]
                    });
                    return;
                }
                if (MethodCode == 10)
                {
                    /// 清空炉子的温度图表
                    strData = e.uiMsg.StringData;
                    int ovenIndex = int.Parse(strData);
                    tables[ovenIndex].ClearChartData();
                    return;
                }
                if (MethodCode == 11)
                {
                    /// 清除穴位参数
                    strData = e.uiMsg.StringData;
                    string[] datas_10015 = strData.Split(';');
                    int ovenIndex = int.Parse(datas_10015[0]);
                    int cellIndex = int.Parse(datas_10015[1]);
                    tables[ovenIndex].GetSingleCell(cellIndex).ClearInfo();
                    return;
                }
            }
            catch (Exception ex)
            {
            }
        }
        public void SetStartFlag(int index, bool data)
        {
            if (this.SequenceContext == null) return;
            char name = (char)(index + 65);
            string key = $"StationState.Start_{name}";
            this.engine.Globals.SetValBoolean(key, 0, data);
            bool val = this.engine.Globals.GetValBoolean(key, 0);
            //this.SequenceContext.StationGlobals.SetValBoolean(key, 0, data);
            //bool val = this.SequenceContext.StationGlobals.GetValBoolean(key, 0);
        }
        public void SetSnrToSeqContext(int index, string snr)
        {
        }
    }
}
Plugin.TestResult/Util/CommonCodeParser.cs
New file
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Plugin.TestResult_WPF.Model;
namespace Plugin.TestResult_WPF.Util
{
    public class CommonCodeParser
    {
        public CellInfo GetCellInfoByCode(string code)
        {
            string[] arr = code.Split('-');
            if (arr == null || arr.Length != 3)
            {
                throw new Exception("扫码格式异常");
            }
            return new CellInfo()
            {
                MachineName = arr[0],
                OvenName = arr[1],
                Index = int.Parse(arr[2]) - 1,
            };
        }
    }
}
Plugin.TestResult/Util/DpUtil.cs
New file
@@ -0,0 +1,232 @@
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
namespace Plugin.TestResult_WPF
{
    public class GridProperties
    {
        public static bool GetShowBorder(DependencyObject obj)
        {
            return (bool)obj.GetValue(ShowBorderProperty);
        }
        public static void SetShowBorder(DependencyObject obj, bool value)
        {
            obj.SetValue(ShowBorderProperty, value);
        }
        public static readonly DependencyProperty ShowBorderProperty =
            DependencyProperty.RegisterAttached("ShowBorder", typeof(bool), typeof(Grid), new PropertyMetadata(false, OnShowBorderChanged));
        public static int GetBorderSpace(DependencyObject obj)
        {
            return (int)obj.GetValue(BorderSpaceProperty);
        }
        public static void SetBorderSpace(DependencyObject obj, int value)
        {
            obj.SetValue(BorderSpaceProperty, value);
        }
        public static readonly DependencyProperty BorderSpaceProperty =
            DependencyProperty.RegisterAttached("BorderSpace", typeof(int), typeof(Grid), new PropertyMetadata(0, OnShowBorderChanged));
        public static string GetInnerBorderIgnore(DependencyObject obj)
        {
            return (string)obj.GetValue(InnerBorderIgnoreProperty);
        }
        public static void SetInnerBorderIgnore(DependencyObject obj, string value)
        {
            obj.SetValue(InnerBorderIgnoreProperty, value);
        }
        public static readonly DependencyProperty InnerBorderIgnoreProperty =
            DependencyProperty.RegisterAttached("InnerBorderIgnore", typeof(string),
                typeof(Grid), new PropertyMetadata("None", OnShowBorderChanged));
        private static void OnShowBorderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is Grid grid)
            {
                if (e.Property == ShowBorderProperty)
                {
                    grid.Loaded += GridInnerBorderLoaded;
                    return;
                }
                else if (e.Property == BorderSpaceProperty)
                {
                    grid.Loaded += GridSpaceOnLoaded;
                    return;
                }
            }
            else if (d is UniformGrid uniformGrid)
            {
                uniformGrid.Loaded += UniformGridOnLoad;
                return;
            }
        }
        private static void GridInnerBorderLoaded(object sender, RoutedEventArgs e)
        {
            if (!(sender is Grid grid)) return;
            var rowCount = grid.RowDefinitions.Count;
            var columnCount = grid.ColumnDefinitions.Count;
            int totalCount = rowCount * columnCount;
            int width = 1;
            string baseData = GetInnerBorderIgnore(grid);
            HashSet<string> data = new HashSet<string>(baseData.Split(','));
            bool ignoreTop = data.Contains("All") || data.Contains("Top"),
                ignoreBottom = data.Contains("All") || data.Contains("Bottom"),
                ignoreLeft = data.Contains("All") || data.Contains("Left"),
                ignoreRight = data.Contains("All") || data.Contains("Right");
            foreach (var child in grid.Children)
            {
                int row = Grid.GetRow((UIElement)child);
                int col = Grid.GetColumn((UIElement)child);
                var bt = new Thickness(col == 0 ? (ignoreLeft ? 0 : width) : 0,
                    row == 0 ? (ignoreTop ? 0 : width) : 0,
                    (col == columnCount - 1 && ignoreRight) ? 0 : width,
                    width);
                if (child is UserControl control)
                {
                    control.BorderBrush = Brushes.Black;
                    control.BorderThickness = bt;
                }
                if (child is Label label)
                {
                    label.BorderBrush = Brushes.Black;
                    label.BorderThickness = bt;
                }
                if (child is TextBox box)
                {
                    box.BorderBrush = Brushes.Black;
                    box.BorderThickness = bt;
                }
            }
            /// 弃用
            //for (int i = 0; i < totalCount; i++)
            //{
            //    int row = i / columnCount;
            //    int col = i % columnCount;
            //    if (i > grid.Children.Count - 1) {
            //        continue;
            //    }
            //    var cell = grid.Children[i];
            //    var bt = new Thickness(col == 0 ? (ignoreLeft ? 0 : width) : 0,
            //        row == 0 ? (ignoreTop ? 0 : width) : 0,
            //        (col == columnCount - 1 && ignoreRight) ? 0 : width,
            //        width);
            //    if (cell is UserControl child)
            //    {
            //        child.BorderBrush = Brushes.Black;
            //        child.BorderThickness = bt;
            //    }
            //    if (cell is Label label)
            //    {
            //        label.BorderBrush = Brushes.Black;
            //        label.BorderThickness = bt;
            //    }
            //    if (cell is TextBox box)
            //    {
            //        box.BorderBrush = Brushes.Black;
            //        box.BorderThickness = bt;
            //    }
            //}
            grid.Loaded -= GridInnerBorderLoaded;
        }
        private static void GridSpaceOnLoaded(object sender, RoutedEventArgs e)
        {
            if (!(sender is Grid grid)) return;
            int space = GetBorderSpace(grid);
            if (space <= 0)
            {
                return;
            }
            var rowCount = grid.RowDefinitions.Count;
            var columnCount = grid.ColumnDefinitions.Count;
            int totalCount = rowCount * columnCount;
            for (int i = 0; i < totalCount; i++)
            {
                int row = i / columnCount;
                int col = i % columnCount;
                if (i > grid.Children.Count - 1)
                {
                    continue;
                }
                var cell = grid.Children[i];
                var bt = new Thickness(col == 0 ? space : 0, row == 0 ? space : 0, space, space);
                if (cell is UserControl child)
                {
                    child.Margin = bt;
                }
            }
            grid.Loaded -= GridSpaceOnLoaded;
        }
        private static void UniformGridOnLoad(object sender, RoutedEventArgs e)
        {
            if (!(sender is UniformGrid grid)) return;
            //var rowCount = grid.Rows;
            int columnCount = grid.Columns;
            int totalCount = grid.Children.Count;
            int rowCount = totalCount / columnCount;
            int width = 1;
            for (int i = 0; i < grid.Children.Count; i++)
            {
                int row = i / columnCount;
                int col = i % columnCount;
                var child = grid.Children[i];
                string baseData = GetInnerBorderIgnore(grid);
                HashSet<string> data = new HashSet<string>(baseData.Split(','));
                bool ignoreTop = data.Contains("All") || data.Contains("Top"),
                    ignoreBottom = data.Contains("All") || data.Contains("Bottom"),
                    ignoreLeft = data.Contains("All") || data.Contains("Left"),
                    ignoreRight = data.Contains("All") || data.Contains("Right");
                var bt = new Thickness(col == 0 ? (ignoreLeft ? 0 : width) : 0,
                    row == 0 ? (ignoreTop ? 0 : width) : 0,
                    (col == columnCount - 1 && ignoreRight) ? 0 : width,
                    (row == rowCount && ignoreBottom) ? 0 : width);
                if (child is UserControl control)
                {
                    control.BorderBrush = Brushes.Black;
                    control.BorderThickness = bt;
                }
                if (child is Label label)
                {
                    label.BorderBrush = Brushes.Black;
                    label.BorderThickness = bt;
                }
                if (child is TextBox box)
                {
                    box.BorderBrush = Brushes.Black;
                    box.BorderThickness = bt;
                }
            }
            grid.Loaded -= UniformGridOnLoad;
        }
    }
}
Plugin.TestResult/Util/FontSizeConverter.cs
New file
@@ -0,0 +1,33 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace Plugin.TestResult_WPF
{
    public class FontSizeConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (parameter == null) return 15;
            string[] ps = parameter.ToString().Split(',');
            int size = int.Parse(ps[0]);
            int baseSize = int.Parse(ps[1]);
            if (value is double width)
            {
                return CountFontSize(size, width, baseSize);
            }
            return 15;
        }
        public static double CountFontSize(double size, double limit, double baseSize)
        {
            return size * limit / baseSize;
        }
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}
Plugin.TestResult/ViewControl.xaml
New file
@@ -0,0 +1,161 @@
<UserControl x:Class="Plugin.TestResult_WPF.ViewControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:Plugin.TestResult_WPF"
             mc:Ignorable="d"
             Loaded="UserControl_Loaded"
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <local:FontSizeConverter x:Key="FontSizeConverter"/>
        <Style TargetType="GroupBox">
            <Setter Property="FontSize" Value="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl},
Converter={StaticResource FontSizeConverter}, ConverterParameter='30,1920'}" />
        </Style>
        <Style TargetType="Button">
            <Setter Property="Margin" Value="0,10"/>
            <Setter Property="Padding" Value="5"/>
        </Style>
        <Style TargetType="Label">
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="BorderBrush" Value="LightGray"/>
            <Setter Property="Margin" Value="0,10"/>
            <Setter Property="Padding" Value="5"/>
        </Style>
    </UserControl.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="7.5*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <local:TestResultView x:Name="testResultView" Grid.Column="0" Loaded="testResultView_Loaded" />
        <StackPanel Grid.Column="1" Background="#FFF">
            <GroupBox Header="产品信息">
                <ComboBox x:Name="modelSelect" SelectionChanged="modelSelect_SelectionChanged"></ComboBox>
            </GroupBox>
            <GroupBox Header="生产统计">
                <StackPanel>
                    <Label HorizontalContentAlignment="Center" HorizontalAlignment="Stretch">
                        <Label.Style>
                            <Style TargetType="Label">
                                <Setter Property="HorizontalContentAlignment"  Value="Center"/>
                                <Setter Property="HorizontalAlignment"  Value="Stretch"/>
                                <Setter Property="Margin" Value="0,10"/>
                                <Style.Triggers>
                                    <DataTrigger Binding="{Binding mesStatus}" Value="true">
                                        <Setter Property="Background" Value="Green"/>
                                        <Setter Property="Content" Value="MES开"/>
                                        <Setter Property="Foreground" Value="White"/>
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding mesStatus}" Value="false">
                                        <Setter Property="Background" Value="Red"/>
                                        <Setter Property="Content" Value="MES关"/>
                                        <Setter Property="Foreground" Value="White"/>
                                    </DataTrigger>
                                </Style.Triggers>
                            </Style>
                        </Label.Style>
                    </Label>
                    <Label>
                        <Label.Style>
                            <Style TargetType="Label">
                                <Setter Property="HorizontalContentAlignment"  Value="Center"/>
                                <Setter Property="HorizontalAlignment"  Value="Stretch"/>
                                <Setter Property="Content" Value="{Binding produceStatus}"/>
                                <Setter Property="Margin" Value="0,10"/>
                                <Style.Triggers>
                                    <DataTrigger Binding="{Binding produceStatus}" Value="INIT">
                                        <Setter Property="Background" Value="Gray"/>
                                        <Setter Property="Foreground" Value="White"/>
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding produceStatus}" Value="STOP">
                                        <Setter Property="Background" Value="Red"/>
                                        <Setter Property="Foreground" Value="White"/>
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding produceStatus}" Value="RUNNING">
                                        <Setter Property="Background" Value="Green"/>
                                        <Setter Property="Foreground" Value="White"/>
                                    </DataTrigger>
                                </Style.Triggers>
                            </Style>
                        </Label.Style>
                    </Label>
                </StackPanel>
            </GroupBox>
            <GroupBox Header="操作">
                <StackPanel>
                    <Button x:Name="btnStart" Click="Start_Click">
                        <Button.Content>启动</Button.Content>
                        <Button.Style>
                            <Style TargetType="Button">
                                <Setter Property="Background" Value="Green"/>
                                <Setter Property="Foreground" Value="White"/>
                                <Setter Property="Margin" Value="0,10"/>
                                <Setter Property="Padding" Value="5"/>
                                <Style.Triggers>
                                    <Trigger Property="IsEnabled" Value="False">
                                        <Setter Property="Foreground" Value="Gray"/>
                                    </Trigger>
                                </Style.Triggers>
                            </Style>
                        </Button.Style>
                    </Button>
                    <Button x:Name="btnStop" Click="Stop_Click">
                        <Button.Content>停止</Button.Content>
                        <Button.Style>
                            <Style TargetType="Button">
                                <Setter Property="Background" Value="Red"/>
                                <Setter Property="Foreground" Value="White"/>
                                <Setter Property="Margin" Value="0,10"/>
                                <Setter Property="Padding" Value="5"/>
                                <Style.Triggers>
                                    <Trigger Property="IsEnabled" Value="False">
                                        <Setter Property="Foreground" Value="Gray"/>
                                    </Trigger>
                                </Style.Triggers>
                            </Style>
                        </Button.Style>
                    </Button>
                    <Button x:Name="btnScan1" Click="Scan_Click_1">
                        <Button.Content>一区扫码</Button.Content>
                        <Button.Style>
                            <Style TargetType="Button">
                                <Setter Property="Margin" Value="0,10"/>
                                <Setter Property="Padding" Value="5"/>
                                <Style.Triggers>
                                    <Trigger Property="IsEnabled" Value="False">
                                        <Setter Property="Foreground" Value="Gray"/>
                                    </Trigger>
                                </Style.Triggers>
                            </Style>
                        </Button.Style>
                    </Button>
                    <Button x:Name="btnScan2" Click="Scan_Click_2">
                        <Button.Content>二区扫码</Button.Content>
                        <Button.Style>
                            <Style TargetType="Button">
                                <Setter Property="Margin" Value="0,10"/>
                                <Setter Property="Padding" Value="5"/>
                                <Style.Triggers>
                                    <Trigger Property="IsEnabled" Value="False">
                                        <Setter Property="Foreground" Value="Gray"/>
                                    </Trigger>
                                </Style.Triggers>
                            </Style>
                        </Button.Style>
                    </Button>
                    <!-- <Button Content="计数清零"/>
                    <Button Content="扫码"/>
                    <Button Content="一区手动"/>
                    <Button Content="二区手动"/>-->
                </StackPanel>
            </GroupBox>
        </StackPanel>
    </Grid>
</UserControl>
Plugin.TestResult/ViewControl.xaml.cs
New file
@@ -0,0 +1,288 @@
using NationalInstruments.TestStand.Interop.API;
using NationalInstruments.TestStand.Interop.UI.Ax;
using NationalInstruments.TestStand.Utility;
using ParameterHelper;
using Plugin.TestResult_WPF.Model;
using Plugin.TestResult_WPF.Util;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Plugin.TestResult_WPF
{
    /// <summary>
    /// ViewControl.xaml 的交互逻辑
    /// </summary>
    public partial class ViewControl : UserControl, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public void RaisePropertyChanged(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
        public void RaiseAndSetIfChanged<T>(ref T old, T @new, [CallerMemberName] string propertyName = null)
        {
            old = @new;
            if (propertyName != null)
            {
                RaisePropertyChanged(propertyName);
            }
        }
        private ParameterManager parameterManager;
        private CommonCodeParser codeParser = new CommonCodeParser();
        private double TestTemperature = 60;
        private int TestTime = 3600;
        private double SaftyTemperature = 40;
        private double TestVoltage = 12;
        private double Time_Heating_Low = 300;
        private double Time_Heating_High = 2400;
        private string testSeqFile;
        private Scanner scannerWindow1;
        private Scanner scannerWindow2;
        /// <summary>
        /// 测试序列操作句柄
        /// </summary>
        private SequenceContext SequenceContext = null;
        private bool _mesStatus;
        public bool mesStatus
        {
            get => _mesStatus;
            set => RaiseAndSetIfChanged(ref _mesStatus, value);
        }
        private string _produceStatus = "INIT";
        public string produceStatus
        {
            get => _produceStatus;
            set => RaiseAndSetIfChanged(ref _produceStatus, value);
        }
        public ViewControl()
        {
            DataContext = this;
            InitializeComponent();
            testResultView.testApp.ExitApplication += new EventHandler(this.axApplicationMgr_ExitApplication);
            testResultView.testApp.UIMessageEvent += new _ApplicationMgrEvents_UIMessageEventEventHandler(this.axApplicationMgr_UIMessageEvent);
            testResultView.testApp.ExecutionClosed += new _ApplicationMgrEvents_ExecutionClosedEventHandler(this.axApplicationMgr_ExecutionClosed);
        }
        private void axApplicationMgr_ExitApplication(object sender, EventArgs e)
        {
        }
        private void axApplicationMgr_ExecutionClosed(object sender, _ApplicationMgrEvents_ExecutionClosedEvent e)
        {
        }
        private void axApplicationMgr_UIMessageEvent(object sender, _ApplicationMgrEvents_UIMessageEventEvent e)
        {
            try
            {
                int EventCase = Convert.ToInt32(e.uiMsg.Event);
                int MethodCode = Convert.ToInt16(e.uiMsg.NumericData);
                string strData;
                if (EventCase != 10014) return;
                if (MethodCode == 0)
                {
                    /// 设置句柄
                    this.SequenceContext = (SequenceContext)e.uiMsg.ActiveXData;
                    return;
                }
                if (MethodCode == 8)
                {
                    /// 更新MES状态
                    strData = e.uiMsg.StringData;
                    mesStatus = strData == "1";
                    return;
                }
                if (MethodCode == 9)
                {
                    /// 更新生产状态
                    strData = e.uiMsg.StringData;
                    produceStatus = strData;
                    return;
                }
            }
            catch
            { }
        }
        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            LoadProduct();
            testResultView.InitTestStandEngine();
            Start_Click(null, null);
        }
        public void LoadProduct()
        {
            ProductManager prdManager = new ProductManager();
            prdManager.LoadProducts();
            this.modelSelect.Items.Clear();
            foreach (Product prd in prdManager.Products)
            {
                this.modelSelect.Items.Add(prd.Name);
            }
            if (prdManager.Products.Count > 0)
            {
                this.modelSelect.SelectedIndex = 0;
            }
        }
        private void modelSelect_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            try
            {
                string ParaFile = @".\Config\ProductionSetting\Parameter\Parameter_" + this.modelSelect.SelectedItem + ".setting";
                this.parameterManager = new ParameterManager(ParaFile);
                this.parameterManager.LoadParameters();
                //MessageBox.Show("ParaFile:" + ParaFile + "ParaCount:" + this.parameterManager.Parameters.Count.ToString());
                //this.TestTemperature = Convert.ToDouble(this.parameterManager.Find("TestTemperature").ParmValue);
                //this.TestTime = Convert.ToInt32(this.parameterManager.Find("TestTime").ParmValue);
                //this.TestVoltage = Convert.ToDouble(this.parameterManager.Find("TestVoltage").ParmValue);
                //this.SaftyTemperature = Convert.ToDouble(this.parameterManager.Find("SaftyTemperature").ParmValue);
                //this.Time_Heating_Low = Convert.ToDouble(this.parameterManager.Find("Time_Heat_Low").ParmValue);
                //this.Time_Heating_High = Convert.ToDouble(this.parameterManager.Find("Time_Heat_High").ParmValue);
                testResultView.testSeqFile = this.parameterManager.Find("TestSequence").ParmValue.ToString(); // @"D:\BurnIn\TestDemo.seq"
            }
            catch (Exception ex)
            {
                MessageBox.Show("切换产品型号失败!" + ex.Message + ex.StackTrace);
            }
        }
        private void Start_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                testResultView.StartTestSequence();
                this.modelSelect.IsEnabled = false;
                this.btnStart.IsEnabled = false;
                this.btnStop.IsEnabled = true;
            }
            catch (Exception ex)
            {
                MessageBox.Show("启动测试失败!" + ex.Message);
            }
        }
        private void Stop_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                testResultView.StopTest();
                this.modelSelect.IsEnabled = true;
                this.btnStart.IsEnabled = true;
                this.btnStop.IsEnabled = false;
            }
            catch (Exception ex)
            {
                MessageBox.Show("停止测试失败!" + ex.Message);
            }
        }
        private void Scan_Click_1(object sender, RoutedEventArgs e)
        {
            if (scannerWindow1 == null)
            {
                scannerWindow1 = new Scanner("一区扫码");
                scannerWindow1.FinishBarcode += ScannerWindow_FinishBarcode;
                scannerWindow1.Owner = Window.GetWindow(this);
                scannerWindow1.Closed += ScannerWindow1_Closed;
                scannerWindow1.Show();
            }
            else
            {
                scannerWindow1.Activate();
            }
        }
        private void Scan_Click_2(object sender, RoutedEventArgs e)
        {
            if (scannerWindow2 == null)
            {
                scannerWindow2 = new Scanner("二区扫码");
                scannerWindow2.FinishBarcode += ScannerWindow_FinishBarcode;
                scannerWindow2.Owner = Window.GetWindow(this);
                scannerWindow2.Closed += ScannerWindow2_Closed;
                scannerWindow2.Show();
            }
            else
            {
                scannerWindow2.Activate();
            }
        }
        /// <summary>
        /// 扫码完成逻辑
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ScannerWindow_FinishBarcode(object sender, BarcodeEventArg e)
        {
            if (this.SequenceContext == null) return;
            CellInfo cellInfo = codeParser.GetCellInfoByCode(e.positionCode);
            string paramName = e.name == "一区扫码" ? "StationState.StartScanner_A" : "StationState.StartScanner_B";
            testResultView.engine.Globals.SetValBoolean(paramName, 0, true);
            string positionCode = e.positionCode, snrCode = e.snrCode;
            //需要增加条码校验功能
            //将读到的条码写入到SN中
            int dutNo = Convert.ToInt16(positionCode.Split('-')[2]) - 1;
            string LookupStr = e.name == "一区扫码" ? $"StationState.SN[{dutNo}]" : $"StationState.SN[{(dutNo + 54)}]";
            //this.SequenceContext.StationGlobals.SetValString(LookupStr, 0, snrCode);
            //string code = this.SequenceContext.StationGlobals.GetValString(LookupStr, 0);
            testResultView.engine.Globals.SetValString(LookupStr, 0, snrCode);
            string code = testResultView.engine.Globals.GetValString(LookupStr, 0);
            testResultView.nameTables[cellInfo.OvenName].GetSingleCell(cellInfo.Index).SetStatus("hadCode");
        }
        private void ScannerWindow1_Closed(object sender, EventArgs e)
        {
            scannerWindow1 = null;
        }
        private void ScannerWindow2_Closed(object sender, EventArgs e)
        {
            scannerWindow2 = null;
        }
        private void testResultView_Loaded(object sender, RoutedEventArgs e)
        {
        }
    }
}
Plugin.TestResult/packages.config
New file
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="HarfBuzzSharp" version="7.3.0.3" targetFramework="net472" />
  <package id="HarfBuzzSharp.NativeAssets.Linux" version="7.3.0.3" targetFramework="net472" />
  <package id="HarfBuzzSharp.NativeAssets.macOS" version="7.3.0.3" targetFramework="net472" />
  <package id="HarfBuzzSharp.NativeAssets.Win32" version="7.3.0.3" targetFramework="net472" />
  <package id="OpenTK" version="3.3.1" targetFramework="net472" />
  <package id="OpenTK.GLWpfControl" version="3.3.0" targetFramework="net472" />
  <package id="ScottPlot" version="5.0.47" targetFramework="net472" />
  <package id="ScottPlot.WPF" version="5.0.47" targetFramework="net472" />
  <package id="SkiaSharp" version="2.88.9" targetFramework="net472" />
  <package id="SkiaSharp.HarfBuzz" version="2.88.9" targetFramework="net472" />
  <package id="SkiaSharp.NativeAssets.Linux.NoDependencies" version="2.88.9" targetFramework="net472" />
  <package id="SkiaSharp.NativeAssets.macOS" version="2.88.9" targetFramework="net472" />
  <package id="SkiaSharp.NativeAssets.Win32" version="2.88.9" targetFramework="net472" />
  <package id="SkiaSharp.Views.Desktop.Common" version="2.88.9" targetFramework="net472" />
  <package id="SkiaSharp.Views.WPF" version="2.88.9" targetFramework="net472" />
  <package id="System.Buffers" version="4.5.1" targetFramework="net472" />
  <package id="System.Drawing.Common" version="4.7.3" targetFramework="net472" />
  <package id="System.Memory" version="4.5.5" targetFramework="net472" />
  <package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net472" />
  <package id="System.Runtime.CompilerServices.Unsafe" version="4.5.3" targetFramework="net472" />
  <package id="System.Runtime.Loader" version="4.3.0" targetFramework="net472" />
  <package id="System.ValueTuple" version="4.5.0" targetFramework="net472" />
</packages>
WPFProject/LoadingWindow.xaml.cs
@@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using System.Windows;
using HMI.Base;
@@ -24,9 +25,9 @@
        private void Loader_LoadMessageAppend(object sender, string e)
        {
            Dispatcher.BeginInvoke(() =>
            this.Dispatcher.BeginInvoke((Action)delegate ()
            {
                Console.Text = Console.Text+$"{e}\r\n";
                Console.Text = Console.Text + $"{e}\r\n";
            });
        }
WPFProject/WPFProject.csproj
@@ -369,6 +369,10 @@
      <Project>{f7663c79-7047-42f3-b129-73a2575ee7c6}</Project>
      <Name>HMI.IOMonitor</Name>
    </ProjectReference>
    <ProjectReference Include="..\HMI.LogViewer\HMI.LogViewer.csproj">
      <Project>{20c23602-b029-4e21-8c11-b9e5f2223237}</Project>
      <Name>HMI.LogViewer</Name>
    </ProjectReference>
    <ProjectReference Include="..\HMI.ParameterManage\HMI.ParameterManage.csproj">
      <Project>{736cd77d-b81e-4866-bb00-84b0b57110f0}</Project>
      <Name>HMI.ParameterManage</Name>
@@ -389,6 +393,10 @@
      <Project>{de4205c6-5923-46fd-a4b9-65169bf61c75}</Project>
      <Name>HMI.MenuManage</Name>
    </ProjectReference>
    <ProjectReference Include="..\Plugin.TestResult\Plugin.TestResult.csproj">
      <Project>{7375b74c-b6bf-4814-9838-e1caacad1897}</Project>
      <Name>Plugin.TestResult</Name>
    </ProjectReference>
  </ItemGroup>
  <ItemGroup>
    <Content Include="Icon\xinzeng_1.png">
WPFProject/WPFProject.sln
@@ -33,6 +33,10 @@
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommonLibs", "..\..\HMI.Old\Framework\CommonLibs\CommonLibs\CommonLibs.csproj", "{0212E104-4E85-4F9F-AA70-FC4FAC25C3E5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HMI.LogViewer", "..\HMI.LogViewer\HMI.LogViewer.csproj", "{20C23602-B029-4E21-8C11-B9E5F2223237}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin.TestResult", "..\Plugin.TestResult\Plugin.TestResult.csproj", "{7375B74C-B6BF-4814-9838-E1CAACAD1897}"
EndProject
Global
    GlobalSection(SolutionConfigurationPlatforms) = preSolution
        Debug|Any CPU = Debug|Any CPU
@@ -161,6 +165,22 @@
        {0212E104-4E85-4F9F-AA70-FC4FAC25C3E5}.Release|Any CPU.Build.0 = Release|Any CPU
        {0212E104-4E85-4F9F-AA70-FC4FAC25C3E5}.Release|x64.ActiveCfg = Release|x64
        {0212E104-4E85-4F9F-AA70-FC4FAC25C3E5}.Release|x64.Build.0 = Release|x64
        {20C23602-B029-4E21-8C11-B9E5F2223237}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {20C23602-B029-4E21-8C11-B9E5F2223237}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {20C23602-B029-4E21-8C11-B9E5F2223237}.Debug|x64.ActiveCfg = Debug|x64
        {20C23602-B029-4E21-8C11-B9E5F2223237}.Debug|x64.Build.0 = Debug|x64
        {20C23602-B029-4E21-8C11-B9E5F2223237}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {20C23602-B029-4E21-8C11-B9E5F2223237}.Release|Any CPU.Build.0 = Release|Any CPU
        {20C23602-B029-4E21-8C11-B9E5F2223237}.Release|x64.ActiveCfg = Release|Any CPU
        {20C23602-B029-4E21-8C11-B9E5F2223237}.Release|x64.Build.0 = Release|Any CPU
        {7375B74C-B6BF-4814-9838-E1CAACAD1897}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {7375B74C-B6BF-4814-9838-E1CAACAD1897}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {7375B74C-B6BF-4814-9838-E1CAACAD1897}.Debug|x64.ActiveCfg = Debug|x64
        {7375B74C-B6BF-4814-9838-E1CAACAD1897}.Debug|x64.Build.0 = Debug|x64
        {7375B74C-B6BF-4814-9838-E1CAACAD1897}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {7375B74C-B6BF-4814-9838-E1CAACAD1897}.Release|Any CPU.Build.0 = Release|Any CPU
        {7375B74C-B6BF-4814-9838-E1CAACAD1897}.Release|x64.ActiveCfg = Release|x64
        {7375B74C-B6BF-4814-9838-E1CAACAD1897}.Release|x64.Build.0 = Release|x64
    EndGlobalSection
    GlobalSection(SolutionProperties) = preSolution
        HideSolutionNode = FALSE