alielie
2026-06-04 771a0e7b6b1276087515bbe94de28358cb92963b
优化老化;
43个文件已修改
75个文件已添加
1个文件已删除
11530 ■■■■■ 已修改文件
HMI.Core/Entity/SortParam.cs 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/HMI.Core.csproj 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Setting/ParamSetting.cs 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Util/MSMQHelper.cs 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Util/Sqlite3Helper.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/HMI.ParameterManage.csproj 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/Model/XmlModel.cs 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/SettingModal.xaml.cs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/DpUtil/UniformGridHelper.cs 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Util/Alarm/AlarmUtil.cs 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Util/ExcelHelper.cs 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Util/HMI.Util.csproj 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/AssemblyInfo.cs 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/BurnInUI.xaml 159 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/BurnInUI.xaml.cs 494 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/ChartWindow.xaml 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/ChartWindow.xaml.cs 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Constant/CommonConstant.cs 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Cooler.xaml 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Cooler.xaml.cs 206 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/CoolerStatus.xaml 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/CoolerStatus.xaml.cs 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/DataTable.xaml 164 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/DataTable.xaml.cs 887 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/InfoTable.xaml 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/InfoTable.xaml.cs 203 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/InfoWindow.xaml 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/InfoWindow.xaml.cs 176 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/ManualDebugWindow.xaml 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/ManualDebugWindow.xaml.cs 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Model/BaseConfig.cs 198 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Model/BaseModel.cs 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Model/CellInfo.cs 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Model/DataTableModel.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Model/Info.cs 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Model/ScannerConfig.cs 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Model/TcpServer.cs 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Plugin.BurnIn.csproj 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Resource/abort.png 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Resource/alert.png 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Resource/barcode.png 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Resource/checkIn.png 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Resource/checkInFailed2.png 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Resource/ng2.png 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Resource/ok2.png 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Resource/ready.png 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Resource/testing2.png 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Resource/waiting2.png 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Scanner.xaml 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Scanner.xaml.cs 465 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/ScannerSetting.xaml 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/ScannerSetting.xaml.cs 147 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/SingleCell.xaml 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/SingleCell.xaml.cs 1174 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/TestResultView.xaml 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/TestResultView.xaml.cs 683 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/UI/LiveChart.xaml 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/UI/LiveChart.xaml.cs 516 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Util/CommonCodeParser.cs 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Util/DpUtil.cs 220 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Util/FontSizeConverter.cs 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Util/ICodeParser.cs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Util/JKCodeParser.cs 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Util/ModelCodeParser.cs 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Util/PNUtil.cs 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Util/RunTimeVariablePool.cs 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Util/SNValidator.cs 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Util/ScannerParser.cs 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DataBoard/DataBoard.xaml 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DataBoard/DataBoard.xaml.cs 468 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DataBoard/Plugin.DataBoard.csproj 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DataBoard/SimpleData.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DataBoard/app.config 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DataBoard/packages.config 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.QualityReport/AssemblyInfo.cs 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.QualityReport/Libs/Static/CMK模板.xls 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.QualityReport/Libs/Static/MSA模板.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.QualityReport/Libs/Static/重复性模板.xls 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.QualityReport/Plugin.QualityReport.csproj 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.QualityReport/QualityReport.xaml 249 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.QualityReport/QualityReport.xaml.cs 705 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/DataTable.xaml 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/DataTable.xaml.cs 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Properties/AssemblyInfo.cs 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Scanner.xaml.cs 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/SingleCell.xaml.cs 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/TestResultView.xaml.cs 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/ViewControl.xaml.cs 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsExecution/Plugin.TsExecution.csproj 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsExecution/Properties/AssemblyInfo.cs 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsExecution/ScreenTouch.xaml 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsExecution/ScreenTouch.xaml.cs 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsExecution/SingleControl.xaml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsExecution/SingleControl.xaml.cs 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsExecution/TsExecution.xaml.cs 574 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsSupport/ITestExecutionWrapper.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsSupport/Plugin.TsSupport.csproj 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsSupport/TapWrapper.cs 238 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsSupport/TeststandWrapper.cs 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsSupport/TsExecutionWrapper.cs 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
S7Driver/PLCS7Driver.cs 166 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
S7Driver/Properties/AssemblyInfo.cs 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SimpleUtil/HMI.Simple.Util/AlarmUtil.cs 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SimpleUtil/HMI.Simple.Util/MSMQHelper.cs 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SimpleUtil/HMI.TapApi/HMI.TapApi.csproj 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SimpleUtil/HMI.TapApi/ProductUtil.cs 164 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SimpleUtil/HMI.TapApi/Properties/AssemblyInfo.cs 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
StationGlobalsReader/App.xaml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
StationGlobalsReader/App.xaml.cs 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
StationGlobalsReader/AssemblyInfo.cs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
StationGlobalsReader/MainWindow.xaml 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
StationGlobalsReader/MainWindow.xaml.cs 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
StationGlobalsReader/StationGlobalsReader.csproj 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/App.config 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/BuildCmd.bat 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Plugin/Here put your plugins.txt 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/ResourceListener.cs 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/WPFProject.csproj 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/WPFProject.sln 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Entity/SortParam.cs
New file
@@ -0,0 +1,18 @@
using System.ComponentModel;
namespace HMI.Core.Entity
{
    public class SortParam
    {
        public string field { get; set; }
        public ListSortDirection direction { get; set; }
        public string sort { get; set; }
    }
    public class PageParam
    {
        public int limit { get; set; }
        public int offset { get; set; }
    }
}
HMI.Core/HMI.Core.csproj
@@ -118,6 +118,7 @@
    <Compile Include="Entity\PermissionData.cs" />
    <Compile Include="Entity\ProductData.cs" />
    <Compile Include="Entity\SelectBase.cs" />
    <Compile Include="Entity\SortParam.cs" />
    <Compile Include="Entity\TestItemData.cs" />
    <Compile Include="Entity\UserInfo.cs" />
    <Compile Include="Event\TabChange.cs" />
HMI.Core/Setting/ParamSetting.cs
@@ -1,17 +1,38 @@
using HMI.Core.Manager;
using HMI.Core.Util;
using System.Collections;
using System.Configuration;
using System.IO;
using System.Runtime.CompilerServices;
namespace HMI.Core.Setting
{
    public class ParamSetting
    {
        private static Hashtable customConfig = (Hashtable)ConfigurationManager.GetSection("TestResultConfig");
        private static string GetValue([CallerMemberName] string propertyName = null)
        {
            return (customConfig != null && customConfig.ContainsKey(propertyName)) ? customConfig[propertyName].ToString() : null;
        }
        /// <summary>
        /// MesAddress
        /// </summary>
        public static string MesAddress { get; private set; } = "127.0.0.1:8080";
        /// <summary>
        /// è€åŒ–导出行转列
        /// </summary>
        public static bool ReverseCSV { get; private set; } = false;
        /// <summary>
        /// å®¢æˆ·ç¼–码
        /// </summary>
        public static string CustomerCode { get; private set; }
        /// <summary>
        /// ç”Ÿäº§æ•°æ®DB存放目录
        /// </summary>
        public static string DataLogFolder { get; private set; } = null;
@@ -28,15 +49,32 @@
        public static bool ProbeByModel { get; private set; } = false;
        public static int ReverseCSVSkipRow { get; private set; } = 3;
        /// <summary>
        /// æ“ä½œæ˜¯å¦éœ€è¦å¯†ç 
        /// </summary>
        public static bool IsOptRequirePassword { get; private set; } = false;
        /// <summary>
        /// å¯ç”¨æ‰«ç æ¢åž‹
        /// </summary>
        public static bool EnableAutoModel { get; private set; } = false;
        public const string ShareConfigKey = "ShareConfig";
        public static string ShareConfig { get; private set; } = null;
        public static bool ShowBurnInAbort { get; private set; } = true;
        public static string SocketNames { get; private set; } = string.Empty;
        public static string StationName { get; private set; } = string.Empty;
        public static string EngineType
        {
            get
            {
                var data = GetValue();
                return data == null ? "TestStand" : data.ToString();
            }
        }
        static ParamSetting()
        {
@@ -154,7 +192,7 @@
            try
            {
                ShareConfig = ParameterManager.GetCommonParameterByName<string>("ShareConfig", null);
                ShareConfig = ParameterManager.GetCommonParameterByName<string>(ShareConfigKey, null);
            }
            catch
            {
@@ -178,6 +216,15 @@
            {
            }
            try
            {
                StationName = ParameterManager.GetCommonParameterByName<string>("StationName", string.Empty);
            }
            catch
            {
            }
        }
    }
}
HMI.Core/Util/MSMQHelper.cs
@@ -13,7 +13,7 @@
    /// <summary>
    /// æ¶ˆæ¯é˜Ÿåˆ—操作类
    /// </summary>
    public class MSMQHelper
    public class MSMQHelperCCC
    {
        /// <summary>
        /// æ¶ˆæ¯é˜Ÿåˆ—名称
@@ -34,7 +34,7 @@
        /// æž„造函数
        /// </summary>
        /// <param name="Name">消息队列名称</param>
        public MSMQHelper(string Name)
        public MSMQHelperCCC(string Name)
        {
            try
            {
HMI.Core/Util/Sqlite3Helper.cs
@@ -11,6 +11,8 @@
        private string ConnectString = string.Empty;
        public const int MAX_ATTACHED = 30;
        private SQLiteConnection conn = null;
        public Sqlite3Helper(string DBFile)
HMI.ParameterManage/HMI.ParameterManage.csproj
@@ -172,6 +172,10 @@
      <Project>{afea100a-9223-448d-ab10-3015d93dd98c}</Project>
      <Name>HMI.Util</Name>
    </ProjectReference>
    <ProjectReference Include="..\SimpleUtil\HMI.LogUtil\HMI.LogUtil.csproj">
      <Project>{6d0aab08-0f3e-48ce-b755-9d29bc8f1225}</Project>
      <Name>HMI.LogUtil</Name>
    </ProjectReference>
  </ItemGroup>
  <ItemGroup>
    <Folder Include="Entity\" />
HMI.ParameterManage/Model/XmlModel.cs
@@ -1,10 +1,12 @@
using HMI.Core.Entity;
using HMI.Core.Manager;
using HMI.Core.Util;
using HMI.LogUtil;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
namespace HMI.ParameterManage.Model
{
@@ -71,8 +73,16 @@
        public bool ValidateParameterName(object value, out string msg)
        {
            string pattern = @"^[\p{L}_][\p{L}\p{N}_]*$";
            msg = null;
            string name = value.ToString();
            if (!Regex.IsMatch(name, pattern))
            {
                msg = "参数名不能以数字开头";
                return false;
            }
            if (Products == null) return true;
            var temp = Parameters.FirstOrDefault(p => p.Name == name);
            if (temp != null && temp != SelectedParameter)
@@ -125,6 +135,7 @@
            else
            {
                InsertParameter(data);
                LogHelper.Info("HMI", $"[{data.ProductName}] Add New Parameter {data.Name}. Value: {data.Value}", out _);
            }
        }
@@ -215,8 +226,13 @@
        public void UpdateParameter(int index, ParameterData data)
        {
            var temp = XmlHelper.DeepClone(Parameters);
            var old = temp[index];
            temp[index] = data;
            SaveParameter(temp);
            if (old.Value != data.Value)
            {
                LogHelper.Info("HMI", $"Update Parameter {data.Name}. Value: {old.Value} => {data.Value}", out _);
            }
        }
        /// <summary>
HMI.ParameterManage/SettingModal.xaml.cs
@@ -11,7 +11,9 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Controls;
using System.Windows.Interop;
namespace HMI.ParameterManage
{
@@ -116,6 +118,14 @@
        public bool ValidateName(object value, out string err)
        {
            err = null;
            string pattern = @"^[\p{L}_][\p{L}\p{N}_]*$";
            if (!Regex.IsMatch(value.ToString(), pattern))
            {
                err = "参数名不能以数字开头";
                return false;
            }
            var data = Datasource?.FirstOrDefault(e => e.Name == value.ToString());
            if (data != null && data != Data)
            {
HMI.UI/DpUtil/UniformGridHelper.cs
@@ -108,6 +108,24 @@
                    control.BorderThickness = bt;
                }
                if (child is Border border)
                {
                    int col = i % columnCount;
                    int row = i / columnCount;
                    double left = (col == 0 && ignoreLeft) ? 0 : width;
                    double top = (row == 0 && ignoreTop) ? 0 : width;
                    double right = ignoreRight ? 0 : ((col == columnCount - 1) ? width : 0);
                    double bottom = ignoreBottom ? 0 : ((row == rowCount - 1) ? width : 0);
                    var bt = new Thickness(left, top, right, bottom);
                    border.BorderBrush = brush;
                    border.BorderThickness = bt;
                }
            }
        }
HMI.Util/Alarm/AlarmUtil.cs
@@ -2,6 +2,7 @@
using HMI.Core.Entity;
using HMI.Core.Setting;
using HMI.Core.Util;
using HMI.Simple.Util;
using log4net;
using S7Driver;
using System;
@@ -192,7 +193,7 @@
        private void RefershAlarm()
        {
            alarmRefershToMain.Send("AlarmRefersh", string.Empty);
            alarmRefershToMain.Send("AlarmRefersh", string.Empty, string.Empty, 0);
            List<AlarmText> alarms = new List<AlarmText>();
            foreach (AlarmText alarm in currentPLCAlarmCache)
@@ -223,8 +224,8 @@
            try
            {
                mqTimer.Enabled = false;
                alarmQueue.Receive(100, out string MessageID, out string MsgStr);
                if (MessageID != string.Empty)
                alarmQueue.Receive(100, out string MessageID, out MQAlarm alarm);
                if (MessageID != string.Empty && alarm != null)
                {
                    switch (MessageID)
                    {
@@ -251,28 +252,19 @@
                            //}
                            break;
                        default:
                            string[] arr = MsgStr.Split(',');
                            string source = "PC";
                            string info = string.Empty;
                            if (arr.Length > 1)
                            {
                                source = arr[0];
                                info = arr[1];
                            }
                            else
                            {
                                info = MsgStr;
                            }
                            string source = string.IsNullOrEmpty(alarm.Source) ? "PC" : alarm.Source;
                            string info = alarm.Content;
                            AlarmText newAlarm = new AlarmText
                            {
                                Source = source,
                                VirtualId = $"{source}:{info}",
                                AlarmInfo = info,
                                Level = 3,
                                Level = alarm.Level,
                                Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
                            };
                            if (currentMQAlarmCache
                                .Find(alarm => alarm.Source == newAlarm.Source && alarm.AlarmInfo == newAlarm.AlarmInfo) == null)
                                .Find(a => a.Source == newAlarm.Source && a.AlarmInfo == newAlarm.AlarmInfo) == null)
                            {
                                currentMQAlarmCache.Add(newAlarm);
                                RefershAlarm();
@@ -297,7 +289,7 @@
            BlockReadCache.Clear();
            try
            {
                BlockSettings = XmlHelper.DeserializeFromXml<ObservableCollection<AlarmSetting>>(blockFilePath);
                BlockSettings = HMI.Core.Util.XmlHelper.DeserializeFromXml<ObservableCollection<AlarmSetting>>(blockFilePath);
            }
            catch (Exception ex)
            {
@@ -321,7 +313,7 @@
        {
            try
            {
                MarkSettings = XmlHelper.DeserializeFromXml<ObservableCollection<AlarmSetting>>(markFilePath);
                MarkSettings = HMI.Core.Util.XmlHelper.DeserializeFromXml<ObservableCollection<AlarmSetting>>(markFilePath);
            }
            catch (Exception ex)
            {
@@ -338,13 +330,13 @@
        public void UpdateBlockSettings(IEnumerable<AlarmSetting> settings)
        {
            XmlHelper.SerializeToXml(blockFilePath, settings);
            HMI.Core.Util.XmlHelper.SerializeToXml(blockFilePath, settings);
            RefreshBlockSettings();
        }
        public void UpdateMarkSettings(IEnumerable<AlarmSetting> settings)
        {
            XmlHelper.SerializeToXml(markFilePath, settings);
            HMI.Core.Util.XmlHelper.SerializeToXml(markFilePath, settings);
            RefreshMarkSettings();
        }
    }
HMI.Util/ExcelHelper.cs
@@ -104,6 +104,36 @@
            }
        }
        public static void ExportExcel(string targetPath, List<string[]> rows)
        {
            IWorkbook workbook = null;
            if (targetPath.EndsWith(".xlsx"))
            {
                workbook = new XSSFWorkbook();
            }
            else if (targetPath.EndsWith(".xls"))
            {
                workbook = new HSSFWorkbook();
            }
            workbook.CreateSheet("sheet1");////新建一个工作表
            ISheet SheetOne = workbook.GetSheet("sheet1");
            for (int i = 0; i < rows.Count; i++)
            {
                IRow rowTemp = SheetOne.CreateRow(i);
                for (int j = 0; j < rows[i].Length; j++)
                {
                    rowTemp.CreateCell(j).SetCellValue(rows[i][j]);
                }
            }
            using (FileStream fs = new FileStream(targetPath, FileMode.OpenOrCreate))
            {
                workbook.Write(fs);
            }
        }
        public static List<T> ReadFromExcel<T>(string sourcePath)
        {
HMI.Util/HMI.Util.csproj
@@ -99,6 +99,10 @@
      <Project>{628ef12b-9ed2-4b20-950f-dc3ecc3fd15f}</Project>
      <Name>S7Driver</Name>
    </ProjectReference>
    <ProjectReference Include="..\SimpleUtil\HMI.Simple.Util\HMI.Simple.Util.csproj">
      <Project>{CF5BEBE9-2803-49C3-B773-73D082E0984A}</Project>
      <Name>HMI.Simple.Util</Name>
    </ProjectReference>
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
Plugin.BurnIn/AssemblyInfo.cs
New file
@@ -0,0 +1,12 @@
using HMI.Core.Annotation;
using System.Windows;
[assembly:ThemeInfo(
    ResourceDictionaryLocation.None,            //where theme specific resource dictionaries are located
                                                //(used if a resource is not found in the page,
                                                // or application resource dictionaries)
    ResourceDictionaryLocation.SourceAssembly   //where the generic resource dictionary is located
                                                //(used if a resource is not found in the page,
                                                // app, or any theme specific resource dictionaries)
)]
[assembly: PluginClassName("Plugin.BurnIn.BurnInUI")]
Plugin.BurnIn/BurnInUI.xaml
New file
@@ -0,0 +1,159 @@
<hb:PluginMaskControl x:Class="Plugin.BurnIn.BurnInUI"
                      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                      xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
                      xmlns:std="clr-namespace:HMI.StdControl;assembly=HMI.StdControl"
                      xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
                      xmlns:local="clr-namespace:Plugin.BurnIn"
                      xmlns:pu="https://opensource.panuon.com/wpf-ui"
                      mc:Ignorable="d"
                      Style="{StaticResource pluginMaskControl}"
             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='20,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.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <!--<Grid.ColumnDefinitions>
            <ColumnDefinition Width="7.5*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>-->
        <local:TestResultView x:Name="testResultView" Grid.Row="1" Grid.Column="0" Loaded="testResultView_Loaded" />
        <DockPanel Margin="10,0" Grid.Row="0" Grid.Column="1" LastChildFill="False" Background="#FFF" VerticalAlignment="Center">
            <pu:FormGroup DockPanel.Dock="Right">
                <StackPanel Orientation="Horizontal" VerticalAlignment="Center" dp:StackPanelHelper.ChildSpace="20">
                    <Button x:Name="btnStart" Click="Start_Click"
                       Width="100" FontSize="14" Padding="0" Height="30" Content="{DynamicResource App.Start}">
                        <Button.Style>
                            <Style TargetType="Button" BasedOn="{StaticResource SuccessButton}">
                                <Style.Triggers>
                                    <DataTrigger Binding="{Binding EnableManualDebug}" Value="True">
                                        <Setter Property="IsEnabled" Value="False"/>
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding IsRunning}" Value="True">
                                        <Setter Property="IsEnabled" Value="False"/>
                                    </DataTrigger>
                                </Style.Triggers>
                            </Style>
                        </Button.Style>
                    </Button>
                    <Button x:Name="btnStop" Click="Stop_Click" Style="{StaticResource DangerButton}"
                            Width="100" FontSize="14" Padding="0" Height="30"
                    Content="{DynamicResource App.Stop}"/>
                    <Button x:Name="btnClear" Click="BtnClear_Click" Style="{StaticResource PrimaryButton}"
                            Width="100" FontSize="14" Padding="0" Height="30"
                    Content="{DynamicResource Ts.BtnClearNum}"/>
                    <Button x:Name="scannerSetting" Click="ScannerSetting_Click" Style="{StaticResource PrimaryButton}"
                            Width="100" FontSize="14" Padding="0" Height="30" Content="{DynamicResource App.ScannerSetting}" Cursor="Hand"/>
                    <!--<Button x:Name="btnScan1" Click="Scan_Click_1" Style="{StaticResource PrimaryButton}"
                    Content="{DynamicResource Tr.Zone1Scan}"/>
            <Button x:Name="btnScan2" Click="Scan_Click_2" Style="{StaticResource PrimaryButton}"
                    Content="{DynamicResource Tr.Zone2Scan}"/>-->
                </StackPanel>
            </pu:FormGroup>
            <pu:FormGroup Header="{DynamicResource Tr.ProductInfo}" FontSize="16" Visibility="Collapsed">
                <ComboBox x:Name="modelSelect" Style="{StaticResource FormComboxStyle}" Width="200"
                          Height="28"
                          pu:ComboBoxHelper.ClearButtonVisibility="Collapsed"
                          SelectionChanged="modelSelect_SelectionChanged"/>
            </pu:FormGroup>
            <pu:FormGroup Header="{DynamicResource Tr.TestProgram}" FontSize="16">
                <TextBox IsReadOnly="True" x:Name="tbFileName" Style="{StaticResource EditorTextBoxStyle}" Width="200"
Height="28" Text="{Binding FileName}"/>
            </pu:FormGroup>
            <pu:FormGroup>
                <std:StdEnableMes DockPanel.Dock="Left" FontSize="20"
                                  x:Name="stdEnableMes"
                                  ValueChanged="StdEnableMes_ValueChanged"/>
            </pu:FormGroup>
            <pu:FormGroup x:Name="FgEnableManualDebug" Visibility="Collapsed">
                <CheckBox FontSize="20"
                          IsChecked="{Binding EnableManualDebug,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
          VerticalContentAlignment="Center"
          Content="手动调试" />
            </pu:FormGroup>
            <!--<pu:FormGroup Header="{DynamicResource Tr.Status}" FontSize="16">
                <StackPanel Orientation="Horizontal" dp:StackPanelHelper.ChildSpace="10">
                    <Label HorizontalContentAlignment="Center" VerticalAlignment="Center">
                        <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="{DynamicResource Tr.MES_OPEN}"/>
                                        <Setter Property="Foreground" Value="White"/>
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding mesStatus}" Value="false">
                                        <Setter Property="Background" Value="Red"/>
                                        <Setter Property="Content" Value="{DynamicResource Tr.MES_CLOSE}"/>
                                        <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="VerticalAlignment"  Value="Center"/>
                                <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>
            </pu:FormGroup>-->
        </DockPanel>
    </Grid>
</hb:PluginMaskControl>
Plugin.BurnIn/BurnInUI.xaml.cs
New file
@@ -0,0 +1,494 @@
using HMI.Base;
using HMI.Core.Api;
using HMI.Core.Entity;
using HMI.Core.Manager;
using HMI.Core.Setting;
using HMI.Core.Util;
using HMI.LogUtil;
using HMI.StdControl;
using HMI.UI.Util;
using log4net;
using NationalInstruments.TestStand.Interop.API;
using PLCHelper;
using Plugin.BurnIn.Util;
using Plugin.TsSupport;
using System.ComponentModel;
using System.IO;
using System.Windows;
using System.Windows.Controls;
namespace Plugin.BurnIn
{
    /// <summary>
    /// ViewControl.xaml çš„交互逻辑
    /// </summary>
    public partial class BurnInUI : PluginMaskControl, IAlarmHandler
    {
        private System.Timers.Timer timer;
        public static ILog Logger { get; private set; }
        private bool _mesStatus;
        public bool mesStatus
        {
            get => _mesStatus;
            set => Set(ref _mesStatus, value);
        }
        private string _produceStatus = "INIT";
        public string produceStatus
        {
            get => _produceStatus;
            set => Set(ref _produceStatus, value);
        }
        private string _fileName;
        public string FileName
        {
            get => _fileName;
            set => Set(ref _fileName, value);
        }
        private bool enableManualDebug;
        public bool EnableManualDebug
        {
            get => enableManualDebug;
            set
            {
                if (IsRunning)
                {
                    MsgBoxHelper2.Warn("请先结束自动测试");
                    return;
                }
                testResultView.inManualDebugMode = value;
                Set(ref enableManualDebug, value);
            }
        }
        public override void SetLogger(ILog logger)
        {
            base.SetLogger(logger);
            Logger = logger;
        }
        private bool isRunning = false;
        public bool IsRunning
        {
            get => isRunning;
            set
            {
                Set(ref isRunning, value);
                testResultView.IsRunning = value;
                this.modelSelect.IsEnabled = !value;
                this.btnStop.IsEnabled = value;
                stdEnableMes.CbEnabled = !value;
            }
        }
        private bool AutoExecuteSeq = true;
        #region TS相关
        /// <summary>
        /// æµ‹è¯•序列操作句柄
        /// </summary>
        private SequenceContext SequenceContext = null;
        #endregion
        public BurnInUI()
        {
            S7Context.Init(SystemSetting.Instance.PlcIp, SystemSetting.Instance.PlcPort);
            DataContext = this;
            timer = new System.Timers.Timer();
            timer.Interval = 1000;
            timer.Elapsed += Timer_Elapsed;
            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 HandleUIMessage(int EventCase, int MethodCode, string strData, object xData)
        {
            try
            {
                try
                {
                    log.Debug($"EventCase: {EventCase}, MethodCode: {MethodCode}, StrData: {strData}");
                }
                catch
                {
                }
                if (EventCase == 8)
                {
                    IsRunning = false;
                    return;
                }
                if (EventCase != 10014) return;
                if (MethodCode == 0)
                {
                    /// è®¾ç½®å¥æŸ„
                    this.SequenceContext = (SequenceContext)xData;
                    return;
                }
                if (MethodCode == 8)
                {
                    /// æ›´æ–°MES状态
                    mesStatus = strData == "1";
                    return;
                }
                if (MethodCode == 9)
                {
                    /// æ›´æ–°ç”Ÿäº§çŠ¶æ€
                    produceStatus = strData;
                    return;
                }
            }
            catch
            { }
        }
        private void ExecutionWrapper_UIMessageEvent(object sender, UIMessageEvent e)
        {
            HandleUIMessage(e.EventCode, Convert.ToInt16(e.NumberData), e.StrData, e.Data);
        }
        private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            testResultView.RefreshCooler();
        }
        public TestResultView View
        {
            get => testResultView;
        }
        public static string SannerIniFile { get; private set; }
        public override void InitFinish()
        {
            base.InitFinish();
            string path = Path.Combine(BaseFolder, "Count.ini");
            testResultView.SetCountIniPath(path);
            testResultView.InitMainGrid();
            path = Path.Combine(BaseFolder, "Scanner.ini");
            if (!File.Exists(path))
            {
                File.Create(path).Close();
            }
            SannerIniFile = path;
            LoadProduct();
            var api = S7Context.Get(RunTimeVariablePool.DEFAULT_PLC_NAME);
            log.Debug($"PLC {api.IpAddress}:{api.Port}");
            try
            {
                var value = ParameterManager.GetCommonParameterByName("AutoExecuteSeq");
                AutoExecuteSeq = bool.Parse(value.Value);
            }
            catch
            {
                AutoExecuteSeq = true;
            }
            try
            {
                var value = ParameterManager.GetCommonParameterByName("BurnInMainSeq");
                testResultView.testSeqFile = value.Value;
                FileName = Path.GetFileName(value.Value);
            }
            catch
            {
                testResultView.testSeqFile = string.Empty;
                FileName = string.Empty;
            }
            string type = Path.GetExtension(FileName);
            bool isTap = string.Equals(".tapplan", type, StringComparison.OrdinalIgnoreCase);
            testResultView.InitTestStandEngine(isTap);
            testResultView.executionWrapper.UIMessageEvent += ExecutionWrapper_UIMessageEvent;
            testResultView.SetOvenName();
            RefreshUIConfig();
        }
        /// <summary>
        /// åˆ·æ–°UI配置
        /// </summary>
        private void RefreshUIConfig()
        {
            try
            {
                /// è¿è¡Œä¸­å°±ä¸ç®¡
                if (IsRunning)
                {
                    return;
                }
                bool enableManualDebug = ParameterManager.GetCommonParameterByName("EnableManualDebug", false);
                if (enableManualDebug)
                {
                    FgEnableManualDebug.Visibility = Visibility.Visible;
                }
                else
                {
                    FgEnableManualDebug.Visibility = Visibility.Collapsed;
                    EnableManualDebug = false;
                }
            }
            catch (Exception ex)
            {
                log.Error("RefreshUIConfig Error.", ex);
            }
        }
        private bool isLoaded;
        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            AlarmShowHelper.AddHandler(this);
            if (AutoExecuteSeq)
            {
                ShowMask(() =>
                {
                    Dispatcher.BeginInvoke(new Action(() =>
                    {
                        Start_Click(null, null);
                    }), System.Windows.Threading.DispatcherPriority.Background);
                }, (ex) =>
                {
                    ToastHelper.Error("Loaded Failed.");
                    log.Error("Loaded Failed.", ex);
                }, "请稍后", LangUtil.Get("Ts.SequenceIsStarting"));
            }
            else
            {
                IsRunning = false;
            }
            Loaded -= UserControl_Loaded;
        }
        public void LoadProduct()
        {
            List<ProductData> products = ProductManager.GetProducts<List<ProductData>>()
                ?? new List<ProductData>();
            this.modelSelect.Items.Clear();
            List<string> productSource = new List<string>();
            foreach (var prd in products)
            {
                this.modelSelect.Items.Add(prd.Name);
                productSource.Add(prd.Name);
            }
            testResultView.SetProductSource(productSource);
            testResultView.SetCurrentProduct(null);
            //string model = testResultView.executionWrapper.GetStationGlobalValue<string>("ProductModel", 1);
            ////testResultView.engine.Globals.GetValString("ProductModel", 1);
            //if (!string.IsNullOrEmpty(model))
            //{
            //    this.modelSelect.SelectedItem = model;
            //    testResultView.SetCurrentProduct(model);
            //}
            //else if (products.Count > 0)
            //{
            //    this.modelSelect.SelectedIndex = 0;
            //    testResultView.SetCurrentProduct(products[0].Name);
            //}
        }
        private void modelSelect_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            try
            {
                string currentModel = this.modelSelect.SelectedItem.ToString();
                List<ParameterData> parameters = ProductManager.GetParametersByProductName(currentModel);
                testResultView.executionWrapper.SetStationGlobalValue<string>("ProductModel", 0, currentModel);
                testResultView.executionWrapper.CommitToDisk(false);
                //testResultView.engine.Globals.SetValString("ProductModel", 0, currentModel);
                //testResultView.engine.CommitGlobalsToDisk(promptOnSaveConflicts: false);
            }
            catch (Exception ex)
            {
                MessageBox.Show("切换产品型号失败!" + ex.Message + ex.StackTrace);
            }
        }
        private void Start_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                if (string.IsNullOrEmpty(testResultView.testSeqFile))
                {
                    MsgBoxHelper2.Warn("请先设置BurnInMainSeq属性!");
                    IsRunning = false;
                    return;
                }
                LogHelper.Info("UI", "总启动按钮按下", out _);
                log.Info("总启动按钮按下");
                ShowMask(() =>
                {
                    testResultView.StartTestSequence();
                    Dispatcher.Invoke(() =>
                    {
                        IsRunning = true;
                    });
                }, (ex) =>
                {
                    log.Error("StartTestSequence Error.", ex);
                    Dispatcher.Invoke(() =>
                    {
                        MsgBoxHelper.Error(Window.GetWindow(this), $"{this.lang("Ts.StartTestSequenceFailed")}: {ex.Message}");
                    });
                }, null, "请稍后...");
            }
            catch (Exception ex)
            {
                log.Error("Start_Click Failed.", ex);
                MsgBoxHelper.Error(Window.GetWindow(this),
                    $"{this.lang("Ts.StartTestSequenceFailed")}: {ex.Message}");
            }
        }
        private void Stop_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                bool breakStop = false;
                foreach (var oven in TestResultView.tables)
                {
                    if (oven.IsTesting)
                    {
                        breakStop = true;
                        break;
                    }
                }
                if (breakStop)
                {
                    MsgBoxHelper2.Warn(this.lang("Tr.OvenIsTesting"));
                    return;
                }
                if (MsgBoxHelper2.WarnChoose(this.lang("Tr.MakeSureStopAllTest")) != MessageBoxResult.OK)
                {
                    return;
                }
                LogHelper.Info("UI", "总停止按钮按下", out _);
                log.Info("总停止按钮按下");
                testResultView.StopTest();
                /// å·²ç»åŠ äº†WaitForExit,这里可以改为false
                IsRunning = false;
            }
            catch (Exception ex)
            {
                log.Error("Stop_Click Failed.", ex);
                MsgBoxHelper.Error(Window.GetWindow(this),
                    $"{this.lang("Ts.StopTestSequenceFailed")}: {ex.Message}");
            }
        }
        private void testResultView_Loaded(object sender, RoutedEventArgs e)
        {
        }
        /// <summary>
        /// è®¡æ•°æ¸…零按钮事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void BtnClear_Click(object sender, RoutedEventArgs e)
        {
            testResultView.ClearNumber();
        }
        private void ScannerSetting_Click(object sender, RoutedEventArgs e)
        {
            ScannerSetting setting = new ScannerSetting()
            {
                Owner = Window.GetWindow(this)
            };
            setting.Open(false, "Config");
        }
        private void StdEnableMes_ValueChanged(object sender, StdUIEventArg e)
        {
            testResultView.executionWrapper.SetStationGlobalValue<bool>($"SystemSetting.{e.StringValue}", 1, e.BoolValue);
            //testResultView.SequenceContext?.StationGlobals.SetValBoolean($"SystemSetting.{e.StringValue}", 1, e.BoolValue);
        }
        public void HandleAlarm(AlarmText alarm)
        {
            if (alarm.Level >= 4 && TestResultView.tables != null)
            {
                log.Info($"4级报警触发: [{alarm.AlarmInfo}],将停止所有炉体测试");
                foreach (var oven in TestResultView.tables)
                {
                    testResultView.SetStartFlag(oven.index, false);
                }
                return;
            }
            DataTable targetOven = null;
            foreach (var oven in TestResultView.tables)
            {
                if (alarm.AlarmInfo?.StartsWith(oven.title) == true)
                {
                    targetOven = oven;
                    break;
                }
            }
            if (targetOven == null)
            {
                return;
            }
            if (alarm.Level >= 3)
            {
                /// 3级以上的报警会停止测试
                log.Info($"3级报警触发: [{alarm.AlarmInfo}],将停止老化炉[{targetOven.title}]测试");
                testResultView.SetStartFlag(targetOven.index, false);
            }
        }
        public override void Active()
        {
            base.Active();
            LoadProduct();
            RefreshUIConfig();
        }
        public override void OnClose(object sender, CancelEventArgs e)
        {
            if (IsRunning)
            {
                MsgBoxHelper2.Warn("请先停止老化测试!");
                e.Cancel = true;
            }
        }
        public override void DoClose(object sender, EventArgs e)
        {
            try
            {
                timer?.Stop();
                testResultView.ReleaseResource();
            }
            catch
            {
            }
        }
    }
}
Plugin.BurnIn/ChartWindow.xaml
New file
@@ -0,0 +1,23 @@
<pu:WindowX x:Class="Plugin.BurnIn.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.BurnIn"
        mc:Ignorable="d"
        Style="{StaticResource ModalWindow}"
 xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
 xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
 xmlns:pw="https://opensource.panuon.com/wpf"
 xmlns:pu="https://opensource.panuon.com/wpf-ui"
 xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
 xmlns:cc="clr-namespace:HMI.Core;assembly=HMI.Core"
            InteropOwnersMask="False"
        pu:WindowXModalDialog.Buttons="{x:Null}"
        Unloaded="Window_Unloaded"
        Title="{Binding title}" Height="450" Width="800">
    <UniformGrid x:Name="mainGrid" Columns="1">
    </UniformGrid>
</pu:WindowX>
Plugin.BurnIn/ChartWindow.xaml.cs
New file
@@ -0,0 +1,31 @@
using HMI.Core.Util;
using Panuon.WPF.UI;
using System.Windows;
namespace Plugin.BurnIn
{
    /// <summary>
    /// ChartWindow.xaml çš„交互逻辑
    /// </summary>
    public partial class ChartWindow : WindowX
    {
        private string _title;
        public string title
        {
            get => _title;
            set => Set(ref _title, value);
        }
        public ChartWindow(string title)
        {
            this.title = $"{title}{this.lang("Tr.DetailChart")}";
            this.DataContext = this;
            InitializeComponent();
        }
        private void Window_Unloaded(object sender, RoutedEventArgs e)
        {
            this.mainGrid.Children.Clear();
        }
    }
}
Plugin.BurnIn/Constant/CommonConstant.cs
New file
@@ -0,0 +1,70 @@
using System.Drawing;
using System.Globalization;
using System.Windows.Data;
namespace Plugin.BurnIn.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","准备就绪"},
            { "abort","手动停止"},
        };
        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))},
            { "abort",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.BurnIn/Cooler.xaml
New file
@@ -0,0 +1,77 @@
<UserControl x:Class="Plugin.BurnIn.Cooler"
             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.BurnIn"
             mc:Ignorable="d"
        xmlns:pu="https://opensource.panuon.com/wpf-ui"
             d:DesignHeight="450" d:DesignWidth="800">
    <GroupBox Header="{Binding Title}">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <Label x:Name="label">
                <Label.Style>
                    <Style TargetType="Label">
                        <Setter Property="HorizontalContentAlignment" Value="Center"/>
                        <Setter Property="VerticalContentAlignment" Value="Center"/>
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding Status}" Value="0">
                                <Setter Property="Background" Value="Gray"/>
                                <Setter Property="Content" Value="停止"/>
                            </DataTrigger>
                            <DataTrigger Binding="{Binding Status}" Value="1">
                                <Setter Property="Content" Value="运水中"/>
                            </DataTrigger>
                            <DataTrigger Binding="{Binding Status}" Value="2">
                                <Setter Property="Background" Value="Gray"/>
                                <Setter Property="Content" Value="{Binding LabelContent}"/>
                            </DataTrigger>
                            <DataTrigger Binding="{Binding Status}" Value="3">
                                <Setter Property="Background" Value="Gray"/>
                                <Setter Property="Content" Value="测压中"/>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </Label.Style>
            </Label>
            <StackPanel Grid.Row="1">
                <!--<Button Click="Button_Click" Style="{StaticResource PrimaryButton}" Tag="1" IsEnabled="False" Content="运水"/>-->
                <Button Click="Start_Click" Tag="2" x:Name="startBtn" Content="吹水">
                    <Button.Style>
                        <Style TargetType="Button" BasedOn="{StaticResource PrimaryButton}">
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding Status}" Value="2">
                                    <Setter Property="IsEnabled" Value="False"/>
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </Button.Style>
                </Button>
                <!--<Button Click="Button_Click" Style="{StaticResource PrimaryButton}" Tag="3" IsEnabled="False" Content="测压"/>-->
                <Button Click="Stop_Click" Style="{StaticResource DangerButton}" Tag="0" Content="停止" Cursor="Hand"/>
                <!--<Button.Style>
                    <Style TargetType="Button" BasedOn="{StaticResource PrimaryButton}">
                        <Setter Property="Content" Value="手动吹水"/>
                        <Setter Property="IsEnabled" Value="False"/>
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding Status}" Value="0">
                                <Setter Property="Content" Value="手动吹水"/>
                                <Setter Property="IsEnabled" Value="True"/>
                                <Setter Property="Background" Value="{DynamicResource SuccessColor}"/>
                            </DataTrigger>
                            <DataTrigger Binding="{Binding Status}" Value="2">
                                <Setter Property="Content" Value="停止吹水"/>
                                <Setter Property="IsEnabled" Value="True"/>
                                <Setter Property="Background" Value="{DynamicResource DangerColor}"/>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </Button.Style>-->
            </StackPanel>
        </Grid>
    </GroupBox>
</UserControl>
Plugin.BurnIn/Cooler.xaml.cs
New file
@@ -0,0 +1,206 @@
using HMI.Core.Manager;
using HMI.UI.Util;
using PLCHelper;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
namespace Plugin.BurnIn
{
    /// <summary>
    /// Cooler.xaml çš„交互逻辑
    /// </summary>
    public partial class Cooler : UserControl, INotifyPropertyChanged
    {
        #region ...
        public virtual event PropertyChangedEventHandler PropertyChanged;
        protected virtual void Set([CallerMemberName] string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
        protected void Set<T>(ref T old, T @new, [CallerMemberName] string propertyName = null)
        {
            old = @new;
            if (propertyName != null)
            {
                Set(propertyName);
            }
        }
        #endregion
        public int index { get; private set; }
        public string Title { get; set; }
        private System.Timers.Timer timer;
        public Cooler(int index)
        {
            DataContext = this;
            this.index = index;
            this.Title = $"第{index + 1}路水冷机";
            InitializeComponent();
        }
        private ushort status;
        public ushort Status
        {
            get => status;
            set
            {
                Set(ref status, value);
            }
        }
        private int totalTime;
        private string labelContent = "吹水中";
        public string LabelContent
        {
            get => labelContent;
            set
            {
                Set(ref labelContent, value);
            }
        }
        private void InitTimer(int time)
        {
            if (time <= 0)
            {
                time = 240;
            }
            totalTime = time;
            LabelContent = $"吹水中\r\n剩{totalTime}s...";
            timer = new System.Timers.Timer();
            timer.Interval = 1000;
            timer.Elapsed += Timer_Elapsed;
            timer.Enabled = true;
        }
        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            try
            {
                timer.AutoReset = false;
                if (totalTime > 0)
                {
                    totalTime -= 1;
                    LabelContent = $"吹水中\r\n剩{totalTime}s...";
                }
                else
                {
                    if (S7Context.WriteData("", $"Cooler{index + 1}_Status", 0, out string errMsg))
                    {
                        Status = 0;
                        Dispatcher.Invoke(() =>
                        {
                            startBtn.IsEnabled = true;
                        });
                    }
                    else
                    {
                        Dispatcher.Invoke(() =>
                        {
                            ToastHelper.Error(errMsg);
                        });
                    }
                    Release();
                }
            }
            catch (Exception ex)
            {
            }
            finally
            {
                if (timer?.Enabled == true) timer.AutoReset = true;
            }
        }
        private void Start_Click(object sender, RoutedEventArgs e)
        {
            if (timer != null) return;
            var btn = sender as Button;
            int totalTime = 240;
            try
            {
                var p = ParameterManager.GetCommonParameterByName("吹水时间")?.Value;
                if (!string.IsNullOrEmpty(p)) totalTime = int.Parse(p);
            }
            catch (Exception ex)
            {
            }
            if (MsgBoxHelper2.WarnChoose($"第 [{index + 1}] è·¯æ°´å†·æœºç¡®å®šè¦{btn.Content}吗?") == MessageBoxResult.OK)
            {
                try
                {
                    if (S7Context.WriteData("", $"Cooler{index + 1}_Status", 2, out string errMsg))
                    {
                        Status = 2;
                        InitTimer(totalTime);
                        btn.IsEnabled = false;
                    }
                    else
                    {
                        ToastHelper.Error(errMsg);
                    }
                }
                catch (Exception ex)
                {
                    ToastHelper.Error(ex.Message);
                }
            }
        }
        private void Stop_Click(object sender, RoutedEventArgs e)
        {
            var btn = sender as Button;
            if (MsgBoxHelper2.WarnChoose($"第 [{index + 1}] è·¯æ°´å†·æœºç¡®å®šè¦{btn.Content}吗?") == MessageBoxResult.OK)
            {
                try
                {
                    if (timer != null)
                    {
                        timer.Enabled = false;
                        timer.Dispose();
                        timer = null;
                    }
                    if (S7Context.WriteData("", $"Cooler{index + 1}_Status", 0, out string errMsg))
                    {
                        Status = 0;
                        startBtn.IsEnabled = true;
                    }
                    else
                    {
                        ToastHelper.Error(errMsg);
                    }
                }
                catch (Exception ex)
                {
                    ToastHelper.Error(ex.Message);
                }
            }
        }
        public void Release()
        {
            if (timer != null)
            {
                timer.Enabled = false;
                timer.Dispose();
                timer = null;
            }
        }
    }
}
Plugin.BurnIn/CoolerStatus.xaml
New file
@@ -0,0 +1,25 @@
<pu:WindowX x:Class="Plugin.BurnIn.CoolerStatus"
        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.BurnIn"
        WindowStartupLocation="CenterScreen"
        FontSize="20"
        mc:Ignorable="d"
        Style="{StaticResource ModalWindow}"
        xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
        xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
        xmlns:pw="https://opensource.panuon.com/wpf"
        xmlns:pu="https://opensource.panuon.com/wpf-ui"
        xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
        xmlns:cc="clr-namespace:HMI.Core;assembly=HMI.Core"
        InteropOwnersMask="False"
        pu:WindowXModalDialog.Buttons="{x:Null}"
        Closed="WindowX_Closed"
        Loaded="WindowX_Loaded"
        Width="600" Height="400">
    <Window.Resources>
    </Window.Resources>
    <Grid Margin="10" x:Name="mainGrid"></Grid>
</pu:WindowX>
Plugin.BurnIn/CoolerStatus.xaml.cs
New file
@@ -0,0 +1,93 @@
using Panuon.WPF.UI;
using PLCHelper;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
namespace Plugin.BurnIn
{
    /// <summary>
    /// CoolerStatus.xaml çš„交互逻辑
    /// </summary>
    public partial class CoolerStatus : WindowX
    {
        private int ovenIndex;
        private int columns;
        private System.Timers.Timer timer;
        private List<Cooler> coolers = new List<Cooler>();
        public CoolerStatus(int ovenIndex, int columns, string title)
        {
            DataContext = this;
            this.ovenIndex = ovenIndex;
            this.columns = columns;
            Title = title;
            timer = new System.Timers.Timer();
            timer.Interval = 1000;
            timer.Elapsed += Timer_Elapsed;
            InitializeComponent();
            RefreshUI();
            RefreshStatus();
        }
        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            RefreshStatus();
        }
        private void RefreshStatus()
        {
            foreach (var cooler in coolers)
            {
                try
                {
                    if (S7Context.GetData("", $"Cooler{cooler.index + 1}_Status", out object val))
                    {
                        cooler.Dispatcher.Invoke(() =>
                        {
                            cooler.Status = (ushort)val;
                        });
                    }
                }
                catch (Exception ex)
                {
                }
            }
        }
        private void RefreshUI()
        {
            mainGrid.Children.Clear();
            mainGrid.ColumnDefinitions.Clear();
            for (int i = 0; i < this.columns; i++)
            {
                ColumnDefinition newCol = new ColumnDefinition();
                newCol.Width = new GridLength(1, GridUnitType.Star);
                mainGrid.ColumnDefinitions.Add(newCol);
                var cooler = new Cooler(ovenIndex * columns + i);
                Grid.SetColumn(cooler, i);
                mainGrid.Children.Add(cooler);
                coolers.Add(cooler);
            }
        }
        private void WindowX_Closed(object sender, EventArgs e)
        {
            timer.Enabled = false;
            timer.Dispose();
            foreach (var cooler in coolers)
            {
                cooler.Release();
            }
        }
        private void WindowX_Loaded(object sender, RoutedEventArgs e)
        {
            timer.Enabled = true;
            this.Loaded -= WindowX_Loaded;
        }
    }
}
Plugin.BurnIn/DataTable.xaml
New file
@@ -0,0 +1,164 @@
<UserControl x:Class="Plugin.BurnIn.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.BurnIn"
            xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
xmlns:pw="https://opensource.panuon.com/wpf"
xmlns:pu="https://opensource.panuon.com/wpf-ui"
xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI"
xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
xmlns:dc="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
             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"/>
        <dc:ReverseBoolConverter x:Key="reverseConverter"/>
    </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="#a1eafb">
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <TextBlock FontSize="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl},
Converter={StaticResource FontSizeConverter}, ConverterParameter='20,940'}"
                   FontWeight="Bold"
                   Margin="0"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center" Text="{Binding title}"/>
            <ComboBox Grid.Row="1" FontSize="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl},
Converter={StaticResource FontSizeConverter}, ConverterParameter='15,940'}"
                      Style="{StaticResource FormComboxStyle}"
                      IsEnabled="{Binding IsTesting,Converter={StaticResource reverseConverter}}"
                      pu:ComboBoxHelper.ClearButtonVisibility="Collapsed"
                      BorderBrush="Black"
                      Height="26"
                      BorderThickness="0,1,0,0"
                      pu:ComboBoxHelper.Watermark="{DynamicResource Tr.ProductInfo}"
                      Width="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl},
Converter={StaticResource FontSizeConverter}, ConverterParameter='150,940'}"
                      SelectedValuePath="Content"
                      x:Name="CbProduct" SelectionChanged="CbProduct_SelectionChanged"/>
            <local:InfoTable x:Name="infoTable" Grid.Row="0" Grid.Column="1"
                             Grid.RowSpan="2"
               FontSize="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl},
            Converter={StaticResource FontSizeConverter}, ConverterParameter='12,940'}"></local:InfoTable>
        </Grid>
        <UniformGrid x:Name="mainGrid" Grid.Row="1" dp:UniformGridHelper.InnerBorder="1"
                     dp:UniformGridHelper.InnerBorderIgnore="Left,Right,Bottom">
        </UniformGrid>
        <!--<local:Chart Grid.Row="2" x:Name="chart" BorderThickness="0,1,0,1" BorderBrush="Black"
                      MouseDoubleClick="chart_MouseDoubleClick"/>-->
        <StatusBar FontSize="14" Grid.Row="3">
            <StatusBar.ItemsPanel>
                <ItemsPanelTemplate>
                    <DockPanel/>
                </ItemsPanelTemplate>
            </StatusBar.ItemsPanel>
            <StatusBarItem DockPanel.Dock="Left">
                <Button Style="{StaticResource LinkButton}" Click="Button_Click_1" Content="{DynamicResource App.ScannerSetting}"/>
            </StatusBarItem>
            <StatusBarItem DockPanel.Dock="Right">
                <StackPanel Orientation="Horizontal" dp:StackPanelHelper.ChildSpace="8">
                    <!--<Button Style="{StaticResource DeleteLink}" Click="Button_Click_2" Content="停止测试"/>-->
                    <StackPanel Orientation="Horizontal">
                        <Ellipse Width="10" Height="10">
                            <Ellipse.Style>
                                <Style TargetType="Ellipse">
                                    <Setter Property="Fill" Value="LightGray"/>
                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding TcpServerStatus,ElementName=control}" Value="ERROR">
                                            <Setter Property="Fill" Value="Red"/>
                                        </DataTrigger>
                                        <DataTrigger Binding="{Binding TcpServerStatus,ElementName=control}" Value="RUNNING">
                                            <Setter Property="Fill" Value="Green"/>
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </Ellipse.Style>
                        </Ellipse>
                        <TextBlock Text="{Binding TcpServerInfo}"/>
                        <Button Padding="0" Margin="0" BorderThickness="0" Click="Button_Click">🔄</Button>
                    </StackPanel>
                </StackPanel>
            </StatusBarItem>
            <StatusBarItem DockPanel.Dock="Right">
                <Button Click="Manual_Cooler_Opt" x:Name="coolerSetting">
                    <Button.Style>
                        <Style TargetType="Button" BasedOn="{StaticResource LinkButton}">
                            <Setter Property="Content" Value="{DynamicResource Tr.CoolerSetting}"/>
                            <Setter Property="Foreground" Value="{DynamicResource PrimaryColor}"/>
                        </Style>
                    </Button.Style>
                </Button>
            </StatusBarItem>
            <StatusBarItem >
                <TextBox IsReadOnly="True" FontSize="16" Background="Transparent" BorderThickness="0" Text="{Binding statusText}"/>
            </StatusBarItem>
        </StatusBar>
        <UniformGrid Grid.Row="4" Rows="1">
            <!--<UniformGrid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition/>
            </UniformGrid.ColumnDefinitions>-->
            <Button Click="ScanWindow_Click" Style="{StaticResource PrimaryButton}"
                    IsEnabled="{Binding IsTesting,Converter={StaticResource reverseBool}}"
        Content="{DynamicResource Tr.Scan}"/>
            <Button Grid.Column="1" Click="ClearAllSnr_Click" Style="{StaticResource DangerButton}"
                    IsEnabled="{Binding IsTesting,Converter={StaticResource reverseBool}}"
Content="{DynamicResource Tr.ClearAllSNR}"/>
            <!--<Button Grid.Column="2" Click="SetStationGlobal_Click" Style="{StaticResource SuccessButton}"
        Content="{DynamicResource App.Start}"/> Background="#1296db"-->
            <Button Grid.Column="2" Click="Abort_Click" x:Name="abortBtn"
        Style="{DynamicResource DangerButton}" Background="#1296db" Content="Abort"/>
            <Button Grid.Column="3" Click="StartSingleClick">
                <Button.Style>
                    <Style TargetType="Button" BasedOn="{StaticResource SuccessButton}">
                        <Setter Property="Content" Value="{DynamicResource App.Start}"/>
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding IsTesting}" Value="True">
                                <Setter Property="Background" Value="{DynamicResource DangerVerBar}"/>
                                <Setter Property="Content" Value="停止测试"/>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </Button.Style>
            </Button>
        </UniformGrid>
    </Grid>
</UserControl>
Plugin.BurnIn/DataTable.xaml.cs
New file
@@ -0,0 +1,887 @@
using HMI.Base;
using HMI.Core.Setting;
using HMI.Core.Util;
using HMI.LogUtil;
using HMI.UI.Util;
using HMI.Util;
using Plugin.BurnIn.Model;
using Plugin.BurnIn.Util;
using Plugin.TsSupport;
using SuperSocket.SocketBase.Config;
using SuperSocket.SocketBase.Protocol;
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
namespace Plugin.BurnIn
{
    /// <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 bool _isTesting;
        public bool IsTesting
        {
            get { return _isTesting; }
            set => RaiseAndSetIfchanged(ref _isTesting, value);
        }
        private ushort coolerStatus;
        public ushort CoolerStatus
        {
            get => coolerStatus;
            set
            {
                RaiseAndSetIfchanged(ref coolerStatus, value);
            }
        }
        private string tcpServerStatus;
        public string TcpServerStatus
        {
            get { return tcpServerStatus; }
            set { RaiseAndSetIfchanged(ref tcpServerStatus, value); }
        }
        private string tcpServerInfo = LangUtil.Get("App.NotStarted");
        public string TcpServerInfo
        {
            get { return tcpServerInfo; }
            set { RaiseAndSetIfchanged(ref tcpServerInfo, value); }
        }
        public int index { get; private set; }
        public readonly int cellCount;
        private readonly string chartTitle;
        public readonly TestResultView parent;
        private BaseConfig config;
        private LiveChart chart;
        private LiveChart multiChart;
        public SingleCell[] cells { get; private set; }
        private TcpServer tcpServer;
        private string CountIniPath;
        private DateTime? testStartTime;
        private System.Timers.Timer testTimeTimer;
        // å¹³å‡æ¸©åº¦
        public List<double> tempratures = new List<double>();
        public Dictionary<string, List<double>> detailTempratures
            = new Dictionary<string, List<double>>();
        public DataTable(TestResultView parent, int index, string title,
            string countIniPath,
            BaseConfig config)
        {
            this.CountIniPath = countIniPath;
            this.config = config;
            this.index = index;
            this.parent = parent;
            this.title = title;
            this.cellCount = config.PositionCount;
            this.cells = new SingleCell[cellCount];
            this.chartTitle = this.lang("Tr.AvgTemp");
            DataContext = this;
            InitializeComponent();
            mainGrid.Columns = config.PositionColumn;
            this.chart = new LiveChart("平均温度,平均温度(℃)", false, true);
            this.multiChart = new LiveChart(",详细温度(℃)", true)
            {
                BorderThickness = new Thickness(0)
            };
            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 cellRow = (i / config.PositionColumn) + 1;
                var cellCol = (i % config.PositionColumn) + 1;
                var cellTitle = $"{title}-{cellRow}-{cellCol}";
                var cell = new SingleCell(this, cellTitle, i, config.CellListMaxSize, countIniPath);
                mainGrid.Children.Add(cell);
                cells[i] = cell;
            }
            ScannerParser.Init(config.CodeParserType);
            ReadCount();
            testTimeTimer = new System.Timers.Timer();
            testTimeTimer.Interval = 10000;
            testTimeTimer.AutoReset = true;
            testTimeTimer.Elapsed += TestTimeTimer_Elapsed;
            if (!ParamSetting.ShowCoolerSetting) coolerSetting.Visibility = Visibility.Collapsed;
            if (!ParamSetting.ShowBurnInAbort) abortBtn.Visibility = Visibility.Collapsed;
        }
        private void TestTimeTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            if (testStartTime == null) return;
            var now = DateTime.Now;
            Dispatcher.BeginInvoke(new Action(() =>
            {
                //SetConsumeTime((now - testStartTime).Value.TotalSeconds);
            }));
        }
        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;
            tempratures.Add(t);
            chart.AddDynamicData("平均温度", "平均温度(℃)", DateTime.Now, t);
        }
        public void SetTemperature(string name, double t)
        {
            //List<double> list;
            //if (detailTempratures.ContainsKey(name))
            //{
            //    list = detailTempratures[name];
            //}
            //else
            //{
            //    list = new List<double>();
            //    detailTempratures.Add(name, list);
            //}
            //list.Add(t);
            multiChart.AddDynamicData(name, "详细温度(℃)", DateTime.Now, t);
            //multiChart.AddData(name, t);
        }
        public void SetStatus(string status)
        {
            BurnInUI.Logger.Debug($"In Status: {status}");
            Dispatcher.BeginInvoke(new Action(() =>
            {
                var lastStatus = infoTable.status;
                if (status == "preCheck")
                {
                    BurnInUI.Logger.Debug($"In preCheck");
                    IsTesting = true;
                    return;
                }
                if (status == "stop")
                {
                    IsTesting = false;
                    return;
                }
                if (status == "abort")
                {
                    IsTesting = false;
                }
                infoTable.status = status;
                //if (lastStatus != "testing" && status == "testing")
                //{
                //    testStartTime = DateTime.Now;
                //    SetStartTime(testStartTime?.ToString("yyyy-MM-dd HH:mm:ss"));
                //    testTimeTimer.Start();
                //}
                //if (lastStatus == "testing" && status != "testing")
                //{
                //    testTimeTimer.Stop();
                //    var now = DateTime.Now;
                //    infoTable.UpdateConsumeTime((now - testStartTime).Value.TotalSeconds, null);
                //    SetEndTime(now.ToString("yyyy-MM-dd HH:mm:ss"));
                //    testStartTime = null;
                //}
            }));
        }
        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 UpdateConsumeTime(double consumeTime, double? leftTime)
        {
            infoTable.UpdateConsumeTime(consumeTime, leftTime);
        }
        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 Scanner scanner;
        private void Window_Closed(object sender, EventArgs e)
        {
            window = null;
        }
        private void SetStationGlobal_Click(object sender, RoutedEventArgs e)
        {
        }
        private void StartSingleClick(object sender, RoutedEventArgs e)
        {
            if (IsTesting)
            {
                if (MsgBoxHelper2.WarnChoose(this.lang("Tr.MakeSureStopOvenTest", title)) == MessageBoxResult.OK)
                {
                    LogHelper.Info("UI", $"老化炉[{title}]停止按钮按下", out _);
                    BurnInUI.Logger.Info($"老化炉[{title}]停止按钮按下");
                    parent.SetStartFlag(this.index, false);
                }
            }
            else
            {
                /// æ£€æŸ¥æ˜¯å¦å…³é—¨
                //if (!GetDoorClose(parent.executionWrapper))
                //{
                //    /// æ²¡å…³é—¨ä¸å…è®¸å¯åЍ
                //    MsgBoxHelper2.Warn("请先关闭舱门!");
                //    return;
                //}
                /// æ£€æŸ¥æ˜¯å¦æœ‰æ¡ç 
                bool flag = false;
                foreach (var cell in cells)
                {
                    if (!string.IsNullOrEmpty(cell.sn))
                    {
                        flag = true;
                        break;
                    }
                }
                if (flag)
                {
                    LogHelper.Info("UI", $"老化炉[{title}]启动按钮按下", out _);
                    BurnInUI.Logger.Info($"老化炉[{title}]启动按钮按下");
                    ClearChartData();
                    parent.SetStartFlag(this.index, true);
                }
                else
                {
                    MsgBoxHelper2.Warn("请先完成扫码!");
                }
            }
        }
        private void ScanWindow_Click(object sender, RoutedEventArgs e)
        {
            if (!parent.IsRunning)
            {
                MsgBoxHelper.WarnMessage(this, this.lang("Tr.StartSeqFirst"), null, MessageBoxButton.OK);
                return;
            }
            if (scanner != null)
            {
                scanner.ShowDialog();
            }
            else
            {
                int rows = config.CellCount / config.PositionColumn + 1;
                scanner = new Scanner(this.index, this.lang("Tr.ScanTitle", title), CbProduct.SelectedItem?.ToString(), mainGrid.Columns, rows)
                {
                    Owner = Window.GetWindow(this),
                    Tag = title
                };
                scanner.FinishBarcode += Scanner_FinishBarcode;
                scanner.ComplateClick += Scanner_ComplateClick; ;
                scanner.Closed += Scanner_Closed;
                scanner.ShowDialog();
            }
        }
        private void Scanner_ComplateClick(object sender, RoutedEventArgs e)
        {
            string paramName = $"StationHMI.UUTHMI[{index}].Scanner";
            parent.executionWrapper.SetStationGlobalValue<bool>(paramName, 1, true);
            //parent.engine.Globals.SetValBoolean(paramName, 1, true);
        }
        private void Scanner_Closed(object sender, EventArgs e)
        {
            scanner = null;
        }
        public bool GetDoorClose(ITestExecutionWrapper wrapper)
        {
            return wrapper?.GetStationGlobalValue<bool>($"StationHMI.UUTHMI[{this.index}].DoorClose", 0) == true;
        }
        private void Scanner_FinishBarcode(object sender, BarcodeEventArg e)
        {
            if (parent.executionWrapper == null) return;
            //if (parent.SequenceContext == null) return;
            int rows = (config.CellCount / config.PositionColumn) + 1;
            CellInfo cellInfo = ScannerParser.GetCellInfoByCode(CbProduct.SelectedItem?.ToString(), mainGrid.Columns, rows, e.positionCode);
            string positionCode = e.positionCode, snrCode = e.snrCode;
            //if (cellInfo.OvenName != this.title)
            //{
            //    MsgBoxHelper2.Warn($"请切换至 [{cellInfo.OvenName}] åŒºæ‰«ç ï¼Œå½“前为 [{this.title}] åŒºï¼");
            //    return;
            //}
            var targetTable = TestResultView.nameTables[cellInfo.OvenName];
            //将读到的条码写入到SN中
            string LookupStr = $"StationHMI.UUTHMI[{targetTable.index}].SN[{cellInfo.Index}]";
            cellInfo.LookUpStr = LookupStr;
            BurnInUI.Logger.Info($"PositionCode: [{e.positionCode}], SNR: [{e.snrCode}], LookUpStr: [{LookupStr}]");
            BurnInUI.Logger.Info($"OvenName: [{cellInfo.OvenName}], CellIndex: [{cellInfo.Index}]");
            var cell = targetTable.GetSingleCell(cellInfo.Index);
            if (targetTable.GetDoorClose(parent.executionWrapper))
            {
                /// å…³é—¨äº†å°±ä¸å…è®¸æ¸…条码
                MsgBoxHelper2.Warn($"{targetTable.title}炉生产中,不允许操作条码");
                //MsgBoxHelper2.Warn(this.lang("Tr.CantClearSNR"));
                return;
            }
            if (snrCode == null)
            {
                var oldCellInfo = e.lastInfo;
                var oldTable = TestResultView.nameTables[oldCellInfo.OvenName];
                var oldCell = oldTable.GetSingleCell(oldCellInfo.Index);
                if (oldCell.status != "hadCode")
                {
                    throw new Exception(this.lang("Tr.PositionCantClearSNR", oldCellInfo.OriginCode));
                }
                // ç§»é™¤æ¡ç 
                parent.executionWrapper.SetStationGlobalValue<string>(oldCellInfo.LookUpStr, 1, "");
                //parent.executionWrapper.CommitToDisk(false);
                //parent.engine.Globals.SetValString(oldCellInfo.LookUpStr, 1, "");
                //parent.engine.CommitGlobalsToDisk(promptOnSaveConflicts: false);
                oldCell.SetStatus("waiting");
                oldCell.ClearSN();
                return;
            }
            /// æ¸…掉上个条码的记录
            var lastSn = cell.sn;
            if (!string.IsNullOrEmpty(lastSn))
            {
                Scanner.RemoveSNR(lastSn);
            }
            // å†™å…¥æ¡ç 
            parent.executionWrapper.SetStationGlobalValue<string>(LookupStr, 1, snrCode);
            //parent.executionWrapper.CommitToDisk(false);
            cell.SetStatus("hadCode");
            cell.sn = snrCode;
            Scanner.InsertNewCode(snrCode, cellInfo);
            cell.LightGreen(true, out string msg);
        }
        /// <summary>
        /// æ¸…空两个图表数据
        /// </summary>
        public void ClearChartData()
        {
            chart.Clear();
            multiChart.Clear();
            tempratures.Clear();
            detailTempratures.Clear();
        }
        /// <summary>
        /// å¯åЍTcp服务器
        /// </summary>
        private void StartTcpServer()
        {
            var serverConfig = new ServerConfig()
            {
                MaxRequestLength = 1024 * 1024,
                Port = config.PortStart + index
            };
            try
            {
                if (tcpServer == null)
                {
                    tcpServer = new TcpServer();
                    tcpServer.NewRequestReceived += TcpServer_NewRequestReceived;
                    if (!tcpServer.Setup(serverConfig))
                    {
                        ChangeTcpServerStatus("ERROR", this.lang("Tr.TcpServer.SettingFailed"));
                        return;
                    }
                    if (!tcpServer.Start())
                    {
                        ChangeTcpServerStatus("ERROR", this.lang("Tr.TcpServer.StartFailed"));
                        return;
                    }
                    ChangeTcpServerStatus("RUNNING", this.lang("Tr.TcpServer.Running"));
                }
                else if (tcpServer.State != SuperSocket.SocketBase.ServerState.Running)
                {
                    if (!tcpServer.Setup(serverConfig))
                    {
                        ChangeTcpServerStatus("ERROR", this.lang("Tr.TcpServer.SettingFailed"));
                        return;
                    }
                    if (!tcpServer.Start())
                    {
                        ChangeTcpServerStatus("ERROR", this.lang("Tr.TcpServer.StartFailed"));
                        return;
                    }
                    ChangeTcpServerStatus("RUNNING", this.lang("Tr.TcpServer.Running"));
                }
            }
            catch (Exception ex)
            {
                ChangeTcpServerStatus("ERROR", $"{ex.Message}");
                tcpServer?.Stop();
                tcpServer = null;
            }
        }
        /// <summary>
        /// æ›´æ–°Tcp服务器状态
        /// </summary>
        /// <param name="status"></param>
        /// <param name="info"></param>
        private void ChangeTcpServerStatus(string status, string info)
        {
            Dispatcher.Invoke(() =>
            {
                TcpServerStatus = status;
                TcpServerInfo = info;
            });
        }
        /// <summary>
        /// åœæ­¢Tcp服务器
        /// </summary>
        private void StopTcpServer()
        {
            try
            {
                tcpServer?.Stop();
                tcpServer = null;
            }
            catch (Exception ex)
            {
            }
        }
        /// <summary>
        /// é‡Šæ”¾èµ„源
        /// </summary>
        public void RelaseResource()
        {
            testTimeTimer?.Stop();
            StopTcpServer();
        }
        /// <summary>
        /// Tcp消息处理
        /// </summary>
        /// <param name="session"></param>
        /// <param name="requestInfo"></param>
        private void TcpServer_NewRequestReceived(TcpSession session, StringRequestInfo requestInfo)
        {
            string msg = requestInfo.Body;
            BurnInUI.Logger.Debug($"TCP Data From {session.RemoteEndPoint}: {msg}.");
            string[] datas_10015 = msg.Split(';');
            int cellIndex = int.Parse(datas_10015[1]);
            string strs = datas_10015[2];
            string[] values = strs.Split(',');
            Dispatcher.Invoke(() =>
            {
                GetSingleCell(cellIndex).SetInfos(new Info
                {
                    step = values[0],
                    value = values[1],
                    limitUp = values[2],
                    limitDown = values[3],
                    unit = values[4],
                    result = values[5],
                    updateTime = values[6]
                });
            });
            //session.Send("1");
        }
        public void RefreshCount(int ok, int ng)
        {
            infoTable.UpdateNumber(infoTable.okCount + ok, infoTable.ngCount + ng);
            if (ParamSetting.ProbeByModel)
            {
            }
            else
            {
                IniFileHelper.WriteIniData($"Oven_{index}", "Pass", infoTable.okCount.ToString(),
                   this.CountIniPath);
                IniFileHelper.WriteIniData($"Oven_{index}", "Fail", infoTable.ngCount.ToString(),
                    this.CountIniPath);
            }
        }
        /// <summary>
        /// è¯»å–保存的数据
        /// </summary>
        public void ReadCount()
        {
            try
            {
                int ok = Convert.ToInt32(IniFileHelper.ReadIniData($"Oven_{index}", "Pass", "", CountIniPath));
                int ng = Convert.ToInt32(IniFileHelper.ReadIniData($"Oven_{index}", "Fail", "", CountIniPath));
                infoTable.UpdateNumber(ok, ng);
            }
            catch (Exception ex)
            {
                //do nothing
            }
        }
        /// <summary>
        /// è®¡æ•°æ¸…é›¶
        /// </summary>
        public void ClearNumber()
        {
            infoTable.UpdateNumber(0, 0);
            IniFileHelper.WriteIniData($"Oven_{index}", "Pass", infoTable.okCount.ToString(),
                this.CountIniPath);
            IniFileHelper.WriteIniData($"Oven_{index}", "Fail", infoTable.ngCount.ToString(),
                this.CountIniPath);
        }
        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            StartTcpServer();
            Loaded -= UserControl_Loaded;
        }
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            StartTcpServer();
        }
        public void SaveTempFile(string filePath)
        {
            try
            {
                FileInfo fi = new FileInfo(filePath);
                if (!fi.Directory.Exists)
                {
                    fi.Directory.Create();  //文件不存在,创建文件
                }
                using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
                using (StreamWriter sw = new StreamWriter(fs, System.Text.Encoding.UTF8))
                {
                    string title = string.Join(",", detailTempratures.Keys) + "," + this.lang("Tr.AvgTemp");
                    sw.WriteLine(title);
                    for (int i = 0; i < tempratures.Count; i++)
                    {
                        string line = "";
                        foreach (string key in detailTempratures.Keys)
                        {
                            if (detailTempratures[key] == null || detailTempratures[key].Count <= i)
                            {
                                line += ",";
                            }
                            else
                            {
                                line += $"{detailTempratures[key][i]},";
                            }
                        }
                        line += tempratures[i];
                        sw.WriteLine(line);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }
        public void SetProductSource(List<string> productSource)
        {
            CbProduct.ItemsSource = productSource;
        }
        public void SetCurrentProduct(string currentProduct)
        {
            CbProduct.SelectedItem = currentProduct;
        }
        private void CbProduct_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (e.AddedItems.Count > 0)
            {
                var str = e.AddedItems[0];
                parent.SetOvenProduct(index, str.ToString());
            }
            else
            {
                parent.SetOvenProduct(index, "");
            }
            foreach (var cell in cells)
            {
                cell.RefreshProbe();
                cell.RefreshCharts();
            }
        }
        public void ClearCellData()
        {
            foreach (var cell in cells)
            {
                cell.ClearChart();
            }
        }
        //public void ClearElectricData()
        //{
        //    foreach (var cell in cells)
        //    {
        //        cell.ClearElectricData();
        //    }
        //}
        //public void ClearVData()
        //{
        //    foreach (var cell in cells)
        //    {
        //        cell.ClearVData();
        //    }
        //}
        //public void ClearWaterData()
        //{
        //    foreach (var cell in cells)
        //    {
        //        cell.ClearWaterData();
        //    }
        //}
        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            ScannerSetting setting = new ScannerSetting()
            {
                Owner = Window.GetWindow(this)
            };
            setting.Open(true, this.title);
        }
        private void Manual_Cooler_Opt(object sender, RoutedEventArgs e)
        {
            //var btn = (Button)sender;
            //var target = CoolerStatus == 2 ? 0 : 2;
            //if (MsgBoxHelper2.WarnChoose($"确定要进行{btn.Content}吗?") == MessageBoxResult.OK)
            //{
            //    if (S7Context.WriteData("1", $"Oven{index + 1}_CoolerStatus", (ushort)target, out string msg))
            //    {
            //        CoolerStatus = (ushort)target;
            //        ToastHelper.Success("Success");
            //    }
            //    else
            //    {
            //        ToastHelper.Error($"{msg}");
            //    }
            //}
            if (parent.IsRunning)
            {
                MsgBoxHelper2.Warn(this.lang("Tr.CantOptCooler"));
                return;
            }
            var columns = mainGrid.Columns;
            var window = new CoolerStatus(this.index, columns, title + this.lang("Tr.CoolerSetting"))
            {
                Owner = Window.GetWindow(this)
            };
            window.ShowDialog();
        }
        private void ClearAllSnr_Click(object sender, RoutedEventArgs e)
        {
            if (GetDoorClose(parent.executionWrapper))
            {
                /// å…³é—¨äº†å°±ä¸å…è®¸æ¸…条码
                MsgBoxHelper2.Warn(this.lang("Tr.CantClearSNR"));
                return;
            }
            if (MsgBoxHelper.WarnMessage(Window.GetWindow(this), LangUtil.Get("Tr.MakeSureClearAllSNR")) == MessageBoxResult.OK)
            {
                try
                {
                    ClearAllSN();
                    ToastHelper.Success(LangUtil.Get("App.DeleteSuccess"));
                }
                catch (Exception ex)
                {
                    BurnInUI.Logger.Error("ClearAllSnr_Click Error.", ex);
                    MsgBoxHelper.Error(Window.GetWindow(this), ex.Message);
                }
            }
        }
        public void ClearAllSN()
        {
            for (int i = 0; i < this.cellCount; i++)
            {
                ClearCellSN(i);
            }
        }
        public void TryClearCellSN(int cellIndex)
        {
            if (GetDoorClose(parent.executionWrapper))
            {
                MsgBoxHelper.Error(Window.GetWindow(this), this.lang("Tr.CantClearSNR"));
                return;
            }
            ClearCellSN(cellIndex);
        }
        private void ClearCellSN(int index)
        {
            var cell = GetSingleCell(index);
            string LookupStr = $"StationHMI.UUTHMI[{this.index}].SN[{index}]";
            var lastSN = cell.sn;
            cell.SetStatus("waiting");
            parent.executionWrapper.SetStationGlobalValue<string>(LookupStr, 1, "");
            //parent.executionWrapper.CommitToDisk(false);
            //parent.engine.Globals.SetValString(LookupStr, 1, "");
            //parent.engine.CommitGlobalsToDisk(promptOnSaveConflicts: false);
            if (!string.IsNullOrEmpty(lastSN)) Scanner.RemoveSNR(lastSN);
            cell.ClearSN();
        }
        private void Abort_Click(object sender, RoutedEventArgs e)
        {
            if (!parent.IsRunning)
            {
                MsgBoxHelper.WarnMessage(this, this.lang("Tr.StartSeqFirst"), null, MessageBoxButton.OK);
                return;
            }
            //string key = $"StationState.Start[{this.index}]";
            //if (parent.executionWrapper?.GetStationGlobalValue<bool>(key, 0) == true)
            //{
            //    MsgBoxHelper2.Warn("测试启动中,不允许Abort");
            //    return;
            //}
            var win = new RequirePasswordByUser("admin")
            {
                Owner = Window.GetWindow(this)
            };
            if (win.ShowDialog() != true)
            {
                return;
            }
            if (MsgBoxHelper2.WarnChoose("请确认老化仓在安全温度,水冷机吹水完成,单击OK按钮仓位回到初始化状态") != MessageBoxResult.OK)
            {
                return;
            }
            LogHelper.Info("UI", $"{title} Abort按钮按下", out _);
            BurnInUI.Logger.Info($"{title} Abort按钮按下");
            string key = $"StationHMI.UUTHMI[{this.index}].Abort";
            parent.executionWrapper?.SetStationGlobalValue(key, 0, true);
        }
    }
}
Plugin.BurnIn/InfoTable.xaml
New file
@@ -0,0 +1,92 @@
<UserControl x:Class="Plugin.BurnIn.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.BurnIn"
             xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
xmlns:pw="https://opensource.panuon.com/wpf"
xmlns:pu="https://opensource.panuon.com/wpf-ui"
xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI"
xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
             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" dp:GridHelper.InnerBorder="1"
          dp:GridHelper.InnerBorderIgnore="Top,Right,Bottom">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="1.5*"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="1.5*"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="2*"/>
        </Grid.ColumnDefinitions>
        <Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Tr.Temperature}"/>
        <Label Grid.Row="0" Grid.Column="1" Background="{DynamicResource CellValue}" Content="{Binding temperatureStr}"/>
        <Label Grid.Row="0" Grid.Column="2" Content="{DynamicResource Tr.Status}"/>
        <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="#A5A5A5"/>
                        </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="abort">
                            <Setter Property="Background" Value="Black"/>
                            <Setter Property="Foreground" Value="White"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Label.Style>
        </Label>
        <Label Grid.Row="0" Grid.Column="4" Content="{DynamicResource Tr.Count}"/>
        <Label Grid.Row="0" Grid.Column="5" Background="{DynamicResource CellValue}" Content="{Binding countStr}"/>
        <Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Tr.StartTime}"/>
        <Label Grid.Row="1" Grid.Column="1" Background="{DynamicResource CellValue}" Content="{Binding startTimeStr}"/>
        <Label Grid.Row="1" Grid.Column="2" Content="{DynamicResource Tr.ElapsedTime}"/>
        <Label Grid.Row="1" Grid.Column="3" Background="{DynamicResource CellValue}" Content="{Binding consumeTimeStr}"/>
        <Label Grid.Row="1" Grid.Column="4" Content="{DynamicResource Tr.EstimatedEndTime}"/>
        <Label Grid.Row="1" Grid.Column="5" Background="{DynamicResource CellValue}" Content="{Binding endTimeStr}"/>
    </Grid>
</UserControl>
Plugin.BurnIn/InfoTable.xaml.cs
New file
@@ -0,0 +1,203 @@
using Plugin.BurnIn.Constant;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Controls;
namespace Plugin.BurnIn
{
    /// <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);
            }
        }
        private int _ngCount;
        public int ngCount
        {
            get => _ngCount;
            set
            {
                RaiseAndSetIfchanged(ref _ngCount, value);
            }
        }
        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);
            }
        }
        private double? _leftTime;
        public double? leftTime
        {
            get => _leftTime;
            set
            {
                RaiseAndSetIfchanged(ref _leftTime, value);
            }
        }
        public string consumeTimeStr
        {
            get
            {
                var str1 = ConvertDurtion(consumeTime);
                if (leftTime != null)
                {
                    str1 += $"/{ConvertDurtion(leftTime.Value)}";
                }
                return str1;
            }
        }
        private string ConvertDurtion(double seconds)
        {
            if (seconds <= 0)
            {
                return "0s";
            }
            if (seconds <= 60)
            {
                return $"{seconds.ToString("f2")}s";
            }
            else if (seconds <= 3600)
            {
                var min = Math.Floor(seconds / 60);
                seconds = seconds % 60;
                if (seconds > 0)
                {
                    return $"{min}m{seconds.ToString("f2")}s";
                }
                return $"{min}m";
            }
            else
            {
                var hour = Math.Floor(seconds / 3600);
                var min = Math.Floor((seconds % 3600) / 60);
                seconds = seconds % 60;
                if (seconds > 0)
                {
                    return $"{hour}h{min}m{seconds.ToString("f2")}s";
                }
                return $"{hour}h{min}m";
            }
        }
        private string _endTimeStr;
        public string endTimeStr
        {
            get => _endTimeStr;
            set => RaiseAndSetIfchanged(ref _endTimeStr, value);
        }
        public void UpdateNumber(int ok, int ng)
        {
            this._okCount = ok;
            this._ngCount = ng;
            this._allCount = this._okCount + this._ngCount;
            RaisePropertyChanged("countStr");
        }
        public void UpdateConsumeTime(double consumeTime, double? leftTime)
        {
            this.consumeTime = consumeTime;
            this.leftTime = leftTime;
            RaisePropertyChanged("consumeTimeStr");
        }
        public InfoTable()
        {
            this.DataContext = this;
            InitializeComponent();
        }
    }
}
Plugin.BurnIn/InfoWindow.xaml
New file
@@ -0,0 +1,117 @@
<pu:WindowX x:Class="Plugin.BurnIn.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.BurnIn"
        mc:Ignorable="d"
        Style="{StaticResource ModalWindow}"
        xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
        xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
        xmlns:pw="https://opensource.panuon.com/wpf"
        xmlns:pu="https://opensource.panuon.com/wpf-ui"
        xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
        xmlns:cc="clr-namespace:HMI.Core;assembly=HMI.Core"
        InteropOwnersMask="False"
        Topmost="False"
        Loaded="Window_Loaded"
        SizeToContent="Height"
        Unloaded="Window_Unloaded"
        SizeChanged="Window_SizeChanged"
        pu:WindowXModalDialog.Buttons="{x:Null}"
        Title="{Binding title}" Width="800" Height="825">
    <Grid x:Name="mainGrid">
        <Grid.RowDefinitions>
            <RowDefinition Height="500"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="25"/>
        </Grid.RowDefinitions>
        <DataGrid ItemsSource="{Binding dataSource}" AutoGenerateColumns="False"
              CanUserAddRows="False"
              CanUserDeleteRows="False"
              IsReadOnly="True"
              ColumnWidth="*"
              VirtualizingPanel.IsContainerVirtualizable="True"
              VirtualizingPanel.IsVirtualizing="True"
              VirtualizingStackPanel.VirtualizationMode="Recycling"
              FontSize="16">
            <DataGrid.Resources>
                <Style TargetType="ScrollBar" BasedOn="{StaticResource ScrollStyle_Mobile}"/>
            </DataGrid.Resources>
            <DataGrid.Columns>
                <!--<DataGridTemplateColumn Header="#" CanUserResize="False" Width="Auto">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Converter={StaticResource rowNumber},
RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGridRow},AncestorLevel=1}}"/>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>-->
                <DataGridTextColumn Binding="{Binding step}">
                    <DataGridTextColumn.Header>
                        <TextBlock Text="{DynamicResource Test.Name}"/>
                    </DataGridTextColumn.Header>
                </DataGridTextColumn>
                <DataGridTextColumn Binding="{Binding value}">
                    <DataGridTextColumn.Header>
                        <TextBlock Text="{DynamicResource Test.Value}"/>
                    </DataGridTextColumn.Header>
                </DataGridTextColumn>
                <DataGridTextColumn Binding="{Binding limitUp}">
                    <DataGridTextColumn.Header>
                        <TextBlock Text="{DynamicResource Test.LimitUp}"/>
                    </DataGridTextColumn.Header>
                </DataGridTextColumn>
                <DataGridTextColumn Binding="{Binding limitDown}">
                    <DataGridTextColumn.Header>
                        <TextBlock Text="{DynamicResource Test.LimitDown}"/>
                    </DataGridTextColumn.Header>
                </DataGridTextColumn>
                <DataGridTextColumn Binding="{Binding unit}">
                    <DataGridTextColumn.Header>
                        <TextBlock Text="{DynamicResource Test.Unit}"/>
                    </DataGridTextColumn.Header>
                </DataGridTextColumn>
                <DataGridTextColumn Binding="{Binding result}">
                    <DataGridTextColumn.Header>
                        <TextBlock Text="{DynamicResource Test.Result}"/>
                    </DataGridTextColumn.Header>
                </DataGridTextColumn>
                <DataGridTextColumn Binding="{Binding updateTime}">
                    <DataGridTextColumn.Header>
                        <TextBlock Text="{DynamicResource Test.TestTime}"/>
                    </DataGridTextColumn.Header>
                </DataGridTextColumn>
            </DataGrid.Columns>
        </DataGrid>
        <Grid Background="#f1eded" Grid.Row="4">
            <DockPanel>
                <StackPanel DockPanel.Dock="Right" Orientation="Horizontal" Margin="0,0,30,0">
                    <Label Padding="0" FontSize="12"
VerticalAlignment="Center"
VerticalContentAlignment="Center" Content="{DynamicResource Ts.ProbeCount}"/>
                    <Label Padding="0" FontSize="12"  Margin="0,0,15,0"
VerticalAlignment="Center"
VerticalContentAlignment="Center" Content="{Binding ProbeCount}"/>
                    <Label x:Name="alarmLabel" Padding="0" FontSize="12"
VerticalAlignment="Center"
VerticalContentAlignment="Center" Content="{DynamicResource Ts.ProbeAlarmCount}"/>
                    <Label x:Name="alarmValue" Padding="0" FontSize="12"
VerticalAlignment="Center"
VerticalContentAlignment="Center" Content="{Binding ProbeWarn}"/>
                    <Button Style="{StaticResource LinkButton}" Margin="10,0,0,0" Click="Button_Click" >清空</Button>
                </StackPanel>
                <Label x:Name="probeCountAlarm" Visibility="Collapsed" DockPanel.Dock="Left" Padding="30,0" FontSize="12" Foreground="Red"
VerticalAlignment="Center"
VerticalContentAlignment="Center" Content="{DynamicResource Ts.ProbeAlarm}"/>
            </DockPanel>
        </Grid>
    </Grid>
</pu:WindowX>
Plugin.BurnIn/InfoWindow.xaml.cs
New file
@@ -0,0 +1,176 @@
using HMI.Core.Util;
using HMI.UI.Util;
using Panuon.WPF.UI;
using Plugin.BurnIn.Model;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
namespace Plugin.BurnIn
{
    /// <summary>
    /// InfoWindow.xaml çš„交互逻辑
    /// </summary>
    public partial class InfoWindow : WindowX
    {
        private string _title;
        public string title
        {
            get => _title;
            set => Set(ref _title, value);
        }
        private ObservableCollection<Info> _dataSource;
        public ObservableCollection<Info> dataSource
        {
            get => _dataSource == null ? new ObservableCollection<Info>() : _dataSource;
            set => Set(ref _dataSource, value);
        }
        private LiveChart waterChart;
        private LiveChart electricChart;
        private LiveChart vChart;
        //public InfoWindow(string title, ObservableCollection<Info> infos, Chart waterChart, Chart electricChart, Chart vChart, int probeCount, int probeWarn)
        //{
        //    this.title = title;
        //    this.dataSource = infos;
        //    DataContext = this;
        //    Topmost = false;
        //    this.ProbeWarn = probeWarn;
        //    InitializeComponent();
        //    UpdateProbeCount(probeCount,probeWarn);
        //    if (waterChart != null)
        //    {
        //        this.waterChart = waterChart;
        //        mainGrid.Children.Add(waterChart);
        //        Grid.SetRow(waterChart, 1);
        //    }
        //    this.electricChart = electricChart;
        //    mainGrid.Children.Add(electricChart);
        //    Grid.SetRow(electricChart, 2);
        //    this.vChart = vChart;
        //    mainGrid.Children.Add(vChart);
        //    Grid.SetRow(vChart, 3);
        //}
        public InfoWindow(string title, ObservableCollection<Info> infos, LiveChart[] charts, int probeCount, int probeWarn)
        {
            this.title = title;
            this.dataSource = infos;
            DataContext = this;
            Topmost = false;
            this.ProbeWarn = probeWarn;
            InitializeComponent();
            UpdateProbeCount(probeCount, probeWarn);
            int row = 1;
            foreach (var chart in charts)
            {
                if (chart == null) return;
                mainGrid.Children.Add(chart);
                Grid.SetRow(chart, row++);
            }
            //if (waterChart != null)
            //{
            //    this.waterChart = waterChart;
            //    mainGrid.Children.Add(waterChart);
            //    Grid.SetRow(waterChart, 1);
            //}
            //this.electricChart = electricChart;
            //mainGrid.Children.Add(electricChart);
            //Grid.SetRow(electricChart, 2);
            //this.vChart = vChart;
            //mainGrid.Children.Add(vChart);
            //Grid.SetRow(vChart, 3);
        }
        public event EventHandler ClearProbeCountEvent;
        private int probeCount;
        public int ProbeCount
        {
            get => probeCount;
            set
            {
                Set(ref probeCount, value);
            }
        }
        public int ProbeWarn
        {
            get;
            set;
        }
        public void UpdateProbeCount(int count, int warn)
        {
            Dispatcher.Invoke(() =>
            {
                ProbeCount = count;
                ProbeWarn = warn;
                if (ProbeWarn < 0)
                {
                    alarmLabel.Visibility = Visibility.Collapsed;
                    alarmValue.Visibility = Visibility.Collapsed;
                }
                else
                {
                    probeCountAlarm.Visibility = (ProbeCount >= ProbeWarn) ? Visibility.Visible : Visibility.Collapsed;
                }
            });
        }
        public void UpdateSource(ObservableCollection<Info> infos)
        {
            this.dataSource = infos;
        }
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            DataContext = this;
        }
        private void Window_Unloaded(object sender, RoutedEventArgs e)
        {
            DataContext = null;
        }
        protected override void OnClosed(EventArgs e)
        {
            waterChart = null;
            electricChart = null;
            vChart = null;
            mainGrid.Children.Clear();
            base.OnClosed(e);
        }
        private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            //if (electricChart != null)
            //{
            //    electricChart.Height = this.ActualHeight * 0.3;
            //}
        }
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            if (MsgBoxHelper.WarnMessage(this, this.lang("Ts.MakeSureToClearProbeCount")) == MessageBoxResult.OK)
            {
                ClearProbeCountEvent?.Invoke(this, null);
            }
        }
    }
}
Plugin.BurnIn/ManualDebugWindow.xaml
New file
@@ -0,0 +1,21 @@
<pu:WindowX x:Class="Plugin.BurnIn.ManualDebugWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        xmlns:sb="clr-namespace:Util.SB;assembly=Util.ManualDebug"
        Title="ManualDebugWindow"
        xmlns:pu="https://opensource.panuon.com/wpf-ui"
        Style="{StaticResource ModalWindow}"
        Closing="Window_Closing"
        WindowStartupLocation="CenterScreen"
        pu:WindowXModalDialog.Buttons="{x:Null}"
        Height="1000" Width="900">
    <Grid x:Name="sb">
        <sb:ManualDebug OvenName="{Binding OvenName,Mode=OneWay}"
    CellName="{Binding CellName,Mode=OneWay}"
    OvenIndex="{Binding OvenIndex,Mode=OneWay}"
    CellIndex="{Binding CellIndex,Mode=OneWay}"/>
    </Grid>
</pu:WindowX>
Plugin.BurnIn/ManualDebugWindow.xaml.cs
New file
@@ -0,0 +1,93 @@
using HMI.Util;
using Panuon.WPF.UI;
using System.ComponentModel;
using System.IO;
using System.Windows;
using Util.SB;
namespace Plugin.BurnIn
{
    /// <summary>
    /// ManualDebugWindow.xaml çš„交互逻辑
    /// </summary>
    public partial class ManualDebugWindow : WindowX
    {
        private ManualDebug debug;
        public ManualDebugWindow(string seqPath, string modelPath)
        {
            DataContext = this;
            InitializeComponent();
            debug = (ManualDebug)sb.Children[0];
            ReadBtns();
            //List<BtnConfig> settings = new List<BtnConfig> { new BtnConfig { Title = "上电激活", Name = "Active" } };
            debug.seqPath = seqPath;
            debug.modelPath = modelPath;
        }
        private string ovenName;
        public string OvenName
        {
            get => ovenName;
            set
            {
                Set(ref ovenName, value);
            }
        }
        private string cellName;
        public string CellName
        {
            get => cellName;
            set
            {
                Set(ref cellName, value);
            }
        }
        private int ovenIndex;
        public int OvenIndex
        {
            get => ovenIndex;
            set
            {
                Set(ref ovenIndex, value);
            }
        }
        private int cellIndex;
        public int CellIndex
        {
            get => cellIndex;
            set
            {
                Set(ref cellIndex, value);
            }
        }
        private void ReadBtns()
        {
            string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Settings.ini");
            var map = IniFileHelper.GetKeyValuesBySection(path, "Btns");
            List<BtnConfig> settings = new List<BtnConfig>();
            foreach (var kv in map)
            {
                settings.Add(new BtnConfig { Title = kv.Key, Name = kv.Value });
            }
            debug.RefreshButtons(settings);
        }
        private void Window_Closing(object sender, CancelEventArgs e)
        {
            if (MessageBox.Show("确定要关闭吗?", "提示", MessageBoxButton.OKCancel) == MessageBoxResult.OK)
            {
                debug.Stop();
            }
            else
            {
                e.Cancel = true;
            }
        }
    }
}
Plugin.BurnIn/Model/BaseConfig.cs
New file
@@ -0,0 +1,198 @@
using HMI.Core.Entity;
using HMI.Core.Manager;
using System;
using System.Collections;
using System.Configuration;
using System.Runtime.CompilerServices;
namespace Plugin.BurnIn.Model
{
    public class BaseConfig
    {
        private static Hashtable customConfig = (Hashtable)ConfigurationManager.GetSection("TestResultConfig");
        private string GetValue([CallerMemberName] string propertyName = null)
        {
            return (customConfig != null && customConfig.ContainsKey(propertyName)) ? customConfig[propertyName].ToString() : null;
        }
        /// <summary>
        /// é¦–页布局行数
        /// </summary>
        private int row = 1;
        public int Row
        {
            get
            {
                var data = GetValue();
                return data == null ? row : int.Parse(data);
            }
            set
            {
                customConfig[nameof(Row)] = value;
            }
        }
        /// <summary>
        /// é¦–页布局列数
        /// </summary>
        private int column = 2;
        public int Column
        {
            get
            {
                var data = GetValue();
                return data == null ? column : int.Parse(data);
            }
            set
            {
                customConfig[nameof(Column)] = value;
            }
        }
        /// <summary>
        /// ç‚‰æ•° -1 å°±æ˜¯row*column
        /// </summary>
        private int cellCount = -1;
        public int CellCount
        {
            get
            {
                var data = GetValue();
                return data == null ? cellCount : int.Parse(data);
            }
            set
            {
                customConfig[nameof(CellCount)] = value;
            }
        }
        /// <summary>
        /// ç‚¹ä½åˆ—æ•°
        /// </summary>
        private int positionColumn = 6;
        public int PositionColumn
        {
            get
            {
                var data = GetValue();
                return data == null ? positionColumn : int.Parse(data);
            }
            set
            {
                customConfig[nameof(PositionColumn)] = value;
            }
        }
        /// <summary>
        /// æ¯ä¸ªç‚‰å­çš„点位数
        /// </summary>
        private int positionCount = 54;
        public int PositionCount
        {
            get
            {
                var data = GetValue();
                return data == null ? positionCount : int.Parse(data);
            }
            set
            {
                customConfig[nameof(PositionCount)] = value;
            }
        }
        /// <summary>
        /// æ›²çº¿æœ€å¤šä¿å­˜å¤šå°‘数据
        /// </summary>
        private int maxSize = 10000;
        public int MaxSize
        {
            get
            {
                var data = GetValue();
                return data == null ? maxSize : int.Parse(data);
            }
            set
            {
                customConfig[nameof(MaxSize)] = value;
            }
        }
        /// <summary>
        /// å›¾è¡¨åˆ·æ–°é¢‘率 ms
        /// </summary>
        private int refreshInterval = 500;
        public int RefreshInterval
        {
            get
            {
                var data = GetValue();
                return data == null ? refreshInterval : int.Parse(data);
            }
            set
            {
                customConfig[nameof(RefreshInterval)] = value;
            }
        }
        /// <summary>
        /// ç‚‰å­åç§°
        /// </summary>
        private string nameStr = "A,B,C,D,E,F,G";
        public string NameStr
        {
            get
            {
                var data = GetValue();
                return data == null ? nameStr : data;
            }
            set
            {
                customConfig[nameof(NameStr)] = value;
            }
        }
        /// <summary>
        /// ç©´ä½Step列表最大容量
        /// </summary>
        private int cellListMaxSize = 10000;
        public int CellListMaxSize
        {
            get
            {
                var data = GetValue();
                return data == null ? cellListMaxSize : int.Parse(data);
            }
            set
            {
                customConfig[nameof(CellListMaxSize)] = value;
            }
        }
        private int portStart = 6660;
        public int PortStart
        {
            get
            {
                var data = GetValue();
                return data == null ? portStart : int.Parse(data);
            }
            set
            {
                customConfig[nameof(PortStart)] = value;
            }
        }
        private string codeParserType;
        public string CodeParserType
        {
            get
            {
                var data = GetValue();
                return data == null ? codeParserType : data.ToString();
            }
        }
        public bool testing = false;
    }
}
Plugin.BurnIn/Model/BaseModel.cs
New file
@@ -0,0 +1,21 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Plugin.BurnIn.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.BurnIn/Model/CellInfo.cs
New file
@@ -0,0 +1,17 @@
namespace Plugin.BurnIn.Model
{
    public class CellInfo
    {
        public string OriginCode { get; set; }
        public string MachineName { get; set; }
        /// <summary>
        /// ç‚‰å
        /// </summary>
        public string OvenName { get; set; }
        /// <summary>
        /// ç©´ä½ä¸‹æ ‡
        /// </summary>
        public int Index { get; set; }
        public string LookUpStr { get; set; }
    }
}
Plugin.BurnIn/Model/DataTableModel.cs
New file
@@ -0,0 +1,7 @@
namespace Plugin.BurnIn.Model
{
    public class DataTableModel : BaseModel
    {
    }
}
Plugin.BurnIn/Model/Info.cs
New file
@@ -0,0 +1,69 @@
namespace Plugin.BurnIn.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 _unit;
        public string unit
        {
            get => _unit;
            set => RaiseAndSetIfchanged(ref _unit, value);
        }
        private string _result;
        public string result
        {
            get => _result;
            set => RaiseAndSetIfchanged(ref _result, value);
        }
        private string _limitDown;
        public string limitDown
        {
            get => _limitDown;
            set => RaiseAndSetIfchanged(ref _limitDown, value);
        }
        private string _limitUp;
        public string limitUp
        {
            get => _limitUp;
            set => RaiseAndSetIfchanged(ref _limitUp, value);
        }
        private string _updateTime;
        public string updateTime
        {
            get => _updateTime;
            set => RaiseAndSetIfchanged(ref _updateTime, value);
        }
    }
}
Plugin.BurnIn/Model/ScannerConfig.cs
New file
@@ -0,0 +1,34 @@
using HMI.Util;
using System;
using System.IO.Ports;
namespace Plugin.BurnIn.Model
{
    public class ScannerConfig
    {
        public string PortName { get; set; }
        public int BaudRate { get; set; }
        public Parity Parity { get; set; } = Parity.None;
        public int DataBits { get; set; }
        public StopBits StopBits { get; set; } = StopBits.One;
        public Handshake Handshake { get; set; } = Handshake.None;
        public bool DtrEnable { get; set; } = true;
        public bool RtsEnable { get; set; } = true;
        public static ScannerConfig ReadFromIni(string iniFile, string section)
        {
            var res = new ScannerConfig();
            res.PortName = IniFileHelper.ReadIniData(section, "PortName", "", iniFile);
            res.BaudRate = int.Parse(IniFileHelper.ReadIniData(section, "BaudRate", "0", iniFile));
            res.Parity = (Parity)Enum.Parse(typeof(Parity), IniFileHelper.ReadIniData(section, "Parity", "None", iniFile));
            res.DataBits = int.Parse(IniFileHelper.ReadIniData(section, "DataBits", "0", iniFile));
            res.StopBits = (StopBits)Enum.Parse(typeof(StopBits), IniFileHelper.ReadIniData(section, "StopBits", "One", iniFile));
            res.Handshake = (Handshake)Enum.Parse(typeof(Handshake), IniFileHelper.ReadIniData(section, "Handshake", "None", iniFile));
            res.DtrEnable = bool.Parse(IniFileHelper.ReadIniData(section, "DtrEnable", "true", iniFile));
            res.RtsEnable = bool.Parse(IniFileHelper.ReadIniData(section, "RtsEnable", "true", iniFile));
            return res;
        }
    }
}
Plugin.BurnIn/Model/TcpServer.cs
New file
@@ -0,0 +1,59 @@
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;
using System;
using System.Collections.Generic;
using System.Text;
namespace Plugin.BurnIn.Model
{
    public class DTReceiveFilter : ReceiveFilterBase<StringRequestInfo>
    {
        public override StringRequestInfo Filter(byte[] readBuffer, int offset, int length, bool toBeCopied, out int rest)
        {
            rest = 0;
            int i = offset;
            List<byte> bytes = new List<byte>();
            while (i < readBuffer.Length)
            {
                byte b = readBuffer[i++];
                if (b == '\n')
                {
                    break;
                }
                bytes.Add(b);
            }
            byte[] arr = bytes.ToArray();
            return new StringRequestInfo(DateTime.Now.ToString("yyyyMMddHHmmssfff"), Encoding.UTF8.GetString(arr), null);//sb.ToString()
        }
    }
    public class StringParser : IRequestInfoParser<StringRequestInfo>
    {
        public StringRequestInfo ParseRequestInfo(string source)
        {
            return new StringRequestInfo(DateTime.Now.ToString("yyyyMMddHHmmssfff"), source, null);
        }
    }
    //public class TcpServer : AppServer<TcpSession>
    //{
    //    public TcpServer() : base(new DefaultReceiveFilterFactory<DTReceiveFilter, StringRequestInfo>())
    //    {
    //    }
    //}
    public class TcpServer : AppServer<TcpSession>
    {
        public TcpServer() : base(new TerminatorReceiveFilterFactory("\n", Encoding.UTF8, new StringParser()))
        {
        }
    }
    public class TcpSession : AppSession<TcpSession, StringRequestInfo>
    {
    }
}
Plugin.BurnIn/Plugin.BurnIn.csproj
New file
@@ -0,0 +1,70 @@
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Library</OutputType>
    <TargetFramework>net472</TargetFramework>
    <Nullable>enable</Nullable>
    <LangVersion>10.0</LangVersion>
    <ImplicitUsings>enable</ImplicitUsings>
    <UseWPF>true</UseWPF>
    <Platforms>AnyCPU;x64</Platforms>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
    <PackageReference Include="LiveChartsCore" Version="2.0.0-rc5.4" />
    <PackageReference Include="LiveChartsCore.SkiaSharpView" Version="2.0.0-rc5.4" />
    <PackageReference Include="LiveChartsCore.SkiaSharpView.WPF" Version="2.0.0-rc5.4" />
    <PackageReference Include="log4net" Version="3.0.4" />
    <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
    <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
    <PackageReference Include="Panuon.WPF.UI" Version="1.3.0.2" />
    <PackageReference Include="SuperSocket" Version="1.6.6.1" />
    <PackageReference Include="SuperSocket.Engine" Version="1.6.6.1" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\HMI.Base\HMI.Base.csproj" />
    <ProjectReference Include="..\HMI.Core\HMI.Core.csproj" />
    <ProjectReference Include="..\HMI.StdControl\HMI.StdControl.csproj" />
    <ProjectReference Include="..\PLCHelper\PLCHelper.csproj" />
    <ProjectReference Include="..\Plugin.TsSupport\Plugin.TsSupport.csproj" />
    <ProjectReference Include="..\SimpleUtil\HMI.LogUtil\HMI.LogUtil.csproj" />
    <ProjectReference Include="..\Util.ManualDebug\Util.ManualDebug.csproj" />
  </ItemGroup>
  <ItemGroup>
    <Reference Include="HslCommunication">
      <HintPath>..\Libs\HslCommunication.dll</HintPath>
    </Reference>
    <Reference Include="NationalInstruments.TestStand.Interop.API">
      <HintPath>..\Libs\NationalInstruments.TestStand.Interop.API.dll</HintPath>
    </Reference>
    <Reference Include="NationalInstruments.TestStand.Interop.UI.AxControls">
      <HintPath>..\Libs\NationalInstruments.TestStand.Interop.UI.AxControls.dll</HintPath>
    </Reference>
    <Reference Include="System.Net.Http" />
    <Reference Include="System.Windows.Forms" />
    <Reference Include="WindowsFormsIntegration" />
  </ItemGroup>
  <ItemGroup>
    <Resource Include="Resource\abort.png" />
    <Resource Include="Resource\alert.png" />
    <Resource Include="Resource\barcode.png" />
    <Resource Include="Resource\checkIn.png" />
    <Resource Include="Resource\checkInFailed2.png" />
    <Resource Include="Resource\ng2.png" />
    <Resource Include="Resource\ok2.png" />
    <Resource Include="Resource\ready.png" />
    <Resource Include="Resource\testing2.png" />
    <Resource Include="Resource\waiting2.png" />
  </ItemGroup>
  <ItemGroup>
    <Compile Update="TestResultView.xaml.cs">
      <SubType>Code</SubType>
    </Compile>
  </ItemGroup>
</Project>
Plugin.BurnIn/Resource/abort.png
Plugin.BurnIn/Resource/alert.png
Plugin.BurnIn/Resource/barcode.png
Plugin.BurnIn/Resource/checkIn.png
Plugin.BurnIn/Resource/checkInFailed2.png
Plugin.BurnIn/Resource/ng2.png
Plugin.BurnIn/Resource/ok2.png
Plugin.BurnIn/Resource/ready.png
Plugin.BurnIn/Resource/testing2.png
Plugin.BurnIn/Resource/waiting2.png
Plugin.BurnIn/Scanner.xaml
New file
@@ -0,0 +1,78 @@
<pu:WindowX x:Class="Plugin.BurnIn.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.BurnIn"
        WindowStartupLocation="CenterScreen"
        Closing="Window_Closing"
        FontSize="20"
        mc:Ignorable="d"
        Style="{StaticResource ModalWindow}"
        xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
        xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
        xmlns:pw="https://opensource.panuon.com/wpf"
        xmlns:pu="https://opensource.panuon.com/wpf-ui"
        xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
        xmlns:cc="clr-namespace:HMI.Core;assembly=HMI.Core"
        InteropOwnersMask="False"
        SizeToContent="Height"
        pu:WindowXModalDialog.Buttons="{x:Null}"
        Title="{Binding title}" Width="600">
    <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='80,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" BasedOn="{StaticResource PrimaryButton}">
            <Setter Property="FontSize" Value="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=Window},
Converter={StaticResource FontSizeConverter}, ConverterParameter='20,1920'}" />
        </Style>
    </Window.Resources>
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <!--<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" HorizontalAlignment="Stretch" Padding="0"
               Content="{DynamicResource Tr.PleaseScanSnr}"
               Margin="0,0,0,20"
               />-->
        <GroupBox Grid.Row="1" Header="{DynamicResource Tr.PleaseScanSnr}" Padding="0,0,0,10" Grid.ColumnSpan="2" >
            <StackPanel>
                <pu:FormGroup
         Header="{DynamicResource Tr.PositionSnr}"
         dp:FormGroupHelper.IsRequired="True"
         Style="{StaticResource InputFormGroupStyle}">
                    <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="positionText" TextWrapping="NoWrap" KeyDown="TextBox_KeyDown"/>
                </pu:FormGroup>
                <pu:FormGroup
         Header="{DynamicResource Tr.ProductSnr}"
         dp:FormGroupHelper.IsRequired="True" Style="{StaticResource InputFormGroupStyle}">
                    <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="snrText" TextWrapping="NoWrap" KeyDown="TextBox_KeyDown"/>
                </pu:FormGroup>
            </StackPanel>
        </GroupBox>
        <!--<Button Grid.Row="2" Click="Con_Click" Style="{StaticResource PrimaryButton}"
        Content="{DynamicResource Tr.ContinueScan}"/>-->
        <Button Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Click="Ok_Click" Style="{StaticResource SuccessButton}"
        Content="{DynamicResource Tr.FinishScan}"/>
    </Grid>
</pu:WindowX>
Plugin.BurnIn/Scanner.xaml.cs
New file
@@ -0,0 +1,465 @@
using HMI.Core.Manager;
using HMI.Core.Setting;
using HMI.Core.Util;
using HMI.UI.Util;
using HMI.Util;
using Panuon.WPF.UI;
using Plugin.BurnIn.Model;
using Plugin.BurnIn.Util;
using System.ComponentModel;
using System.IO.Ports;
using System.Windows;
using System.Windows.Input;
namespace Plugin.BurnIn
{
    public class BarcodeEventArg
    {
        public string name { get; private set; }
        public string positionCode { get; private set; }
        public string snrCode { get; private set; }
        public CellInfo lastInfo { get; private set; }
        public BarcodeEventArg(string name, string positionCode, string snrCode, CellInfo lastInfo)
        {
            this.name = name;
            this.positionCode = positionCode;
            this.snrCode = snrCode;
            this.lastInfo = lastInfo;
        }
    }
    /// <summary>
    /// Scanner.xaml çš„交互逻辑
    /// </summary>
    public partial class Scanner : WindowX
    {
        private string _title;
        public string title
        {
            get { return _title; }
            set => Set(ref _title, value);
        }
        public event EventHandler<BarcodeEventArg> FinishBarcode;
        public event EventHandler<RoutedEventArgs> ComplateClick;
        private static Dictionary<string, CellInfo> snrCache = new Dictionary<string, CellInfo>();
        SerialPort serialPort;
        private string model;
        private int columns;
        private int rows;
        private int ovenIndex;
        private CellInfo targetCellInfo;
        public Scanner(int index, string title, string model, int columns, int rows)
        {
            this.ovenIndex = index;
            this.title = title;
            this.model = model;
            this.columns = columns;
            this.rows = rows;
            this.DataContext = this;
            InitializeComponent();
            Loaded += Scanner_Loaded;
        }
        private void Scanner_Loaded(object sender, RoutedEventArgs e)
        {
            try
            {
                InitScanner();
                Loaded -= Scanner_Loaded;
            }
            catch (Exception ex)
            {
                BurnInUI.Logger.Error("Open Scanner Window Error.", ex);
                MsgBoxHelper.Error(this, $"{ex.Message}");
            }
        }
        /// <summary>
        /// è®¾ç½®æ‰«ç æžª
        /// </summary>
        private void InitScanner()
        {
            string comStr = "";
            if (serialPort != null) return;
            string section = this.Tag.ToString();
            ScannerConfig common = ScannerConfig.ReadFromIni(BurnInUI.SannerIniFile, "Config");
            string portName = IniFileHelper.ReadIniData(section, "PortName", "", BurnInUI.SannerIniFile);
            if (string.IsNullOrEmpty(portName))
            {
                if (section.StartsWith("A"))
                {
                    portName = "COM1";
                }
                else
                {
                    portName = "COM2";
                }
            }
            common.PortName = portName;
            // é…ç½®ä¸²å£
            serialPort = new SerialPort
            {
                PortName = common.PortName,
                BaudRate = common.BaudRate,
                Parity = Parity.None,
                DataBits = common.DataBits,
                StopBits = common.StopBits,
                Handshake = Handshake.None,
                DtrEnable = common.DtrEnable,
                RtsEnable = common.RtsEnable
                //ReadTimeout = 500,
                //WriteTimeout = 500,
                //ReciveBufferSize=4096
            };
            try
            {
                // æ‰“开串口
                serialPort.Open();
                // æ³¨å†ŒæŽ¥æ”¶æ•°æ®äº‹ä»¶
                serialPort.DataReceived += SerialPort_DataReceived;
            }
            catch (Exception ex)
            {
                BurnInUI.Logger.Error("Init Scan Gun Failed.", ex);
                MsgBoxHelper.Error(this, $"{this.lang("Tr.InitGunFailed")}: {ex.StackTrace}");
            }
        }
        private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            try
            {
                SerialPort sp = (SerialPort)sender;
                string barcode = ReadDataFrom(sp).Trim();
                //string barcode =
                Dispatcher.Invoke(() =>
                {
                    if (snrText.IsFocused)
                    {
                        snrText.Text = barcode;
                        Con_Click(null, null);
                    }
                    else
                    {
                        try
                        {
                            var targetCellInfo = ScannerParser.GetCellInfoByCode(this.model, this.columns, this.rows, barcode);
                            if (!TestResultView.nameTables.ContainsKey(targetCellInfo.OvenName))
                            {
                                MsgBoxHelper2.Error($"老化炉 [{targetCellInfo.OvenName}] ä¸å­˜åœ¨ï¼Œè¯·æ£€æŸ¥é…ç½®");
                                return;
                            }
                            var sourceTable = TestResultView.tables[this.ovenIndex];
                            if (sourceTable.title != targetCellInfo.OvenName)
                            {
                                MsgBoxHelper2.Error($"请切换至 [{targetCellInfo.OvenName}] åŒºæ‰«ç ï¼Œå½“前为 [{sourceTable.title}] åŒºï¼");
                                return;
                            }
                            var oven = TestResultView.nameTables[targetCellInfo.OvenName];
                            if (oven.cells.Length < targetCellInfo.Index + 1)
                            {
                                MsgBoxHelper2.Error($"穴位号 [{barcode}] è¶…出索引 {targetCellInfo.Index}");
                                return;
                            }
                            this.targetCellInfo = targetCellInfo;
                            positionText.Text = barcode;
                            //positionText.Focus();
                            //snrText.Text = "";
                            snrText.Focus();
                        }
                        catch (Exception ex)
                        {
                            BurnInUI.Logger.Error("Parse Position Code Error.", ex);
                            MsgBoxHelper2.Error($"穴位号 [{barcode}] è§£æžå¤±è´¥: {ex.Message}");
                        }
                    }
                });
            }
            catch (Exception ex)
            {
                BurnInUI.Logger.Error("Get Scan Gun Data Failed.", ex);
                MessageBox.Show($"{this.lang("Tr.GetGunData")}: {ex.StackTrace}");
            }
        }
        public static void InsertNewCode(string snr, CellInfo info)
        {
            snrCache[snr] = info;
        }
        public static void RemoveSNR(string snr)
        {
            snrCache.Remove(snr);
        }
        private void SendCode(string tag, string position, string snr)
        {
            if (snrCache.ContainsKey(snr))
            {
                string oldPosition = snrCache[snr].OriginCode;
                if (MsgBoxHelper2.WarnChoose($"条码[{snr}]已放置于[{oldPosition}]中,是否清除?") == MessageBoxResult.OK)
                {
                    try
                    {
                        FinishBarcode.Invoke(this, new BarcodeEventArg(tag, position, null, snrCache[snr]));
                        snrCache.Remove(snr);
                        FinishBarcode.Invoke(this, new BarcodeEventArg(tag, position, snr, null));
                    }
                    catch (Exception ex)
                    {
                        ToastHelper.Error(ex.Message);
                    }
                }
            }
            else
            {
                FinishBarcode.Invoke(this, new BarcodeEventArg(tag, position, snr, null));
            }
        }
        private void Con_Click(object sender, RoutedEventArgs e)
        {
            Dispatcher.Invoke(() =>
            {
                string position = positionText.Text, snr = snrText.Text;
                try
                {
                    var EnableMes = ParameterManager.GetCommonParameterByName<bool>("EnableMes", false);
                    if (ParamSetting.CustomerCode == "LIXUN" && EnableMes)
                    {
                        var table = TestResultView.nameTables[targetCellInfo.OvenName];
                        // TestResultView.tables[this.ovenIndex]
                        string currentPn = table.CbProduct.Text;
                        /// æ£€æŸ¥æ ¼å¼
                        if (!SNValidator.ValidateSN(currentPn, snr, out string msg))
                        {
                            table.GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
                            MsgBoxHelper2.Error($"检查产品条码异常:{msg}");
                            snrText.Text = string.Empty;
                            snrText.Focus();
                            return;
                        }
                        /// èŽ·å–ç«™å
                        string stationName = ProductManager.GetParameterByProductAndName(currentPn, "MES站位")?.Value;
                        if (string.IsNullOrEmpty(stationName))
                        {
                            table.GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
                            MsgBoxHelper2.Error($"检查产品条码异常,当前产品[{currentPn}]未设置参数MES站位");
                            snrText.Text = string.Empty;
                            snrText.Focus();
                            return;
                        }
                        /// æ£€æŸ¥PN
                        if (PNUtil.GetPN(ParamSetting.MesAddress, stationName, snr, out string pn, out msg))
                        {
                            if (pn != currentPn)
                            {
                                table.GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
                                MsgBoxHelper2.Error($"当前测试料号为 [{currentPn}],扫码产品料号为 [{pn}] ä¸å…è®¸æµ‹è¯•");
                                snrText.Text = string.Empty;
                                snrText.Focus();
                                return;
                            }
                        }
                        else
                        {
                            table.GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
                            MsgBoxHelper2.Error($"检查产品条码异常:{msg}");
                            snrText.Text = string.Empty;
                            snrText.Focus();
                            return;
                        }
                        /// æ£€æŸ¥å·¥åº
                        if (PNUtil.CheckIn(BurnInUI.Logger, ParamSetting.MesAddress, stationName, snr, out bool needCheckIn, out msg))
                        {
                            if (needCheckIn != true)
                            {
                                table.GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
                                MsgBoxHelper2.Error($"条码[{snr}]工序不在本站:{msg}");
                                snrText.Text = string.Empty;
                                snrText.Focus();
                                return;
                            }
                        }
                        else
                        {
                            table.GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
                            MsgBoxHelper2.Error($"条码[{snr}]CheckIn失败:{msg}");
                            snrText.Text = string.Empty;
                            snrText.Focus();
                            return;
                        }
                    }
                    //var _ = ScannerParser.GetCellInfoByCode(this.model, this.columns, this.rows, snr);
                    //ToastHelper.Warning(this, "产品码格式异常");
                    //snrText.Text = string.Empty;
                    //snrText.Focus();
                    //return;
                }
                catch (Exception ex)
                {
                    BurnInUI.Logger.Error("Scanner Check Error.", ex);
                    MsgBoxHelper2.Error($"扫码检查异常:{ex.Message}");
                    return;
                }
                if (string.IsNullOrEmpty(position) || string.IsNullOrEmpty(snr))
                {
                    ToastHelper.Warning(this, this.lang("Tr.PleaseInputSnrs"));
                    return;
                }
                try
                {
                    SendCode(Tag.ToString(), position, snr);
                    positionText.Text = "";
                    snrText.Text = "";
                    positionText.Focus();
                }
                catch (Exception ex)
                {
                    BurnInUI.Logger.Error("Con_Click Failed.", ex);
                    ToastHelper.Warning(this, ex.Message);
                }
            });
        }
        /// <summary>
        /// åŽŸæ¥çš„å®Œæˆæ‰«æé€»è¾‘
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        //private void Ok_Click(object sender, RoutedEventArgs e)
        //{
        //    string position = positionText.Text, snr = snrText.Text;
        //    if (string.IsNullOrEmpty(position) || string.IsNullOrEmpty(snr))
        //    {
        //        ToastHelper.Warning(this, this.lang("Tr.PleaseInputSnrs"));
        //        return;
        //    }
        //    try
        //    {
        //        SendCode(Tag.ToString(), position, snr);
        //        this.Close();
        //    }
        //    catch (Exception ex)
        //    {
        //        ViewControl.Logger.Error("Con_Click Failed.", ex);
        //        ToastHelper.Warning(this, ex.Message);
        //    }
        //}
        private void Ok_Click(object sender, RoutedEventArgs e)
        {
            ComplateClick?.Invoke(this, e);
            this.Close();
        }
        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.ASCII.GetString(buffer);
                System.Threading.Thread.Sleep(50);
            }
            return barcode;
        }
        private void Window_Closing(object sender, CancelEventArgs e)
        {
            try
            {
                targetCellInfo = null;
                serialPort.Dispose();
                serialPort = null;
            }
            catch (Exception ex)
            {
            }
        }
        private void TextBox_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                //string barcode =
                Dispatcher.Invoke(() =>
                {
                    if (snrText.IsFocused)
                    {
                        Con_Click(null, null);
                    }
                    else
                    {
                        //if (ParamSetting.CustomerCode != "LIXUN")
                        //{
                        //    snrText.Focus();
                        //    return;
                        //}
                        string barcode = positionText.Text;
                        try
                        {
                            targetCellInfo = ScannerParser.GetCellInfoByCode(this.model, this.columns, this.rows, barcode);
                            if (!TestResultView.nameTables.ContainsKey(targetCellInfo.OvenName))
                            {
                                MsgBoxHelper2.Error($"老化炉 [{targetCellInfo.OvenName}] ä¸å­˜åœ¨ï¼Œè¯·æ£€æŸ¥é…ç½®");
                                positionText.Text = string.Empty;
                                positionText.Focus();
                                targetCellInfo = null;
                                return;
                            }
                            var sourceTable = TestResultView.tables[this.ovenIndex];
                            if (sourceTable.title != targetCellInfo.OvenName)
                            {
                                MsgBoxHelper2.Error($"请切换至 [{targetCellInfo.OvenName}] åŒºæ‰«ç ï¼Œå½“前为 [{sourceTable.title}] åŒºï¼");
                                positionText.Text = string.Empty;
                                positionText.Focus();
                                targetCellInfo = null;
                                return;
                            }
                            var oven = TestResultView.nameTables[targetCellInfo.OvenName];
                            if (oven.cells.Length < targetCellInfo.Index + 1)
                            {
                                MsgBoxHelper2.Error($"穴位号 [{barcode}] è¶…出索引 {targetCellInfo.Index}");
                                positionText.Text = string.Empty;
                                positionText.Focus();
                                targetCellInfo = null;
                                return;
                            }
                            snrText.Focus();
                        }
                        catch (Exception ex)
                        {
                            positionText.Text = string.Empty;
                            positionText.Focus();
                            BurnInUI.Logger.Error("Parse Position Code Error.", ex);
                            MsgBoxHelper2.Error($"穴位号 [{barcode}] è§£æžå¤±è´¥: {ex.Message}");
                        }
                    }
                });
            }
        }
    }
}
Plugin.BurnIn/ScannerSetting.xaml
New file
@@ -0,0 +1,66 @@
<pu:WindowX x:Class="Plugin.BurnIn.ScannerSetting"
        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.BurnIn"
        mc:Ignorable="d"
        Style="{StaticResource ModalWindow}"
        xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
        xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
        xmlns:pw="https://opensource.panuon.com/wpf"
        xmlns:pu="https://opensource.panuon.com/wpf-ui"
        xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
        xmlns:cc="clr-namespace:HMI.Core;assembly=HMI.Core"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:ports="clr-namespace:System.IO.Ports;assembly=System"
        SizeToContent="Height"
        WindowStartupLocation="CenterOwner"
        Title="{Binding title}" Width="600">
    <Window.Resources>
        <ObjectDataProvider x:Key="stopBitsItems"
                  MethodName="GetValues"
                  ObjectType="{x:Type sys:Enum}">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="ports:StopBits"/>
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </Window.Resources>
    <Grid Margin="10">
        <StackPanel x:Name="formPanel">
            <pu:FormGroup x:Name="FgPortName" Style="{StaticResource InputFormGroupStyle}" Header="{DynamicResource Tr.PortName}"
              GroupName="MyFormGroup">
                <ComboBox x:Name="CbPortName" Style="{StaticResource FormComboxStyle}" />
            </pu:FormGroup>
            <pu:FormGroup Style="{StaticResource InputFormGroupStyle}" Header="{DynamicResource Tr.BaudRate}"
                          dp:FormGroupHelper.IsRequired="True"
                          GroupName="MyFormGroup">
                <pu:NumberInput x:Name="NiBaudRate" IsEnabled="{Binding SbIsEnabled}" Style="{StaticResource FormNumberInput}" />
            </pu:FormGroup>
            <pu:FormGroup Style="{StaticResource InputFormGroupStyle}" Header="{DynamicResource Tr.DataBits}"
            dp:FormGroupHelper.IsRequired="True" GroupName="MyFormGroup">
                <pu:NumberInput x:Name="NiDataBits" IsEnabled="{Binding SbIsEnabled}" Style="{StaticResource FormNumberInput}" />
            </pu:FormGroup>
            <pu:FormGroup Style="{StaticResource InputFormGroupStyle}" Header="{DynamicResource Tr.StopBits}"
                          dp:FormGroupHelper.IsRequired="True" GroupName="MyFormGroup">
                <ComboBox x:Name="CbStopBits" IsEnabled="{Binding SbIsEnabled}" Style="{StaticResource FormComboxStyle}"
                          ItemsSource="{Binding Source={StaticResource stopBitsItems}}"
          SelectedValuePath="Content">
                </ComboBox>
            </pu:FormGroup>
            <pu:FormGroup Style="{StaticResource InputFormGroupStyle}" Header="{DynamicResource Tr.DtrEnable}" GroupName="MyFormGroup">
                <CheckBox x:Name="CxDtrEnable" IsEnabled="{Binding SbIsEnabled}"/>
            </pu:FormGroup>
            <pu:FormGroup Style="{StaticResource InputFormGroupStyle}" Header="{DynamicResource Tr.RtsEnable}"  GroupName="MyFormGroup">
                <CheckBox x:Name="CxRtsEnable" IsEnabled="{Binding SbIsEnabled}"/>
            </pu:FormGroup>
        </StackPanel>
    </Grid>
</pu:WindowX>
Plugin.BurnIn/ScannerSetting.xaml.cs
New file
@@ -0,0 +1,147 @@
using HMI.Core.Util;
using HMI.UI.DpUtil;
using HMI.UI.Util;
using HMI.Util;
using Panuon.WPF.UI;
using Plugin.BurnIn.Model;
using System.ComponentModel;
using System.IO.Ports;
namespace Plugin.BurnIn
{
    /// <summary>
    /// ScannerSetting.xaml çš„交互逻辑
    /// </summary>
    public partial class ScannerSetting : WindowX
    {
        public ScannerSetting()
        {
            DataContext = this;
            InitializeComponent();
        }
        public ScannerConfig Data { get; set; }
        private bool _isProcessing;
        protected override void OnClosing(CancelEventArgs e)
        {
            //Press Continue Button
            if (DialogResult == true)
            {
                if (_isProcessing)
                {
                    e.Cancel = true;
                    return;
                }
                var okButton = WindowXModalDialog.GetOKButton(this);
                try
                {
                    _isProcessing = true;
                    ButtonHelper.SetIsPending(okButton, true);
                    if (!ValidateForm())
                    {
                        e.Cancel = true;
                        return;
                    }
                    SaveOrUpdateData();
                }
                catch (Exception ex)
                {
                    ToastHelper.Error(this, $"Error!{ex.Message}");
                    e.Cancel = true;
                    return;
                }
                finally
                {
                    _isProcessing = false;
                    ButtonHelper.SetIsPending(okButton, false);
                }
            }
            base.OnClosing(e);
        }
        private bool ValidateForm()
        {
            bool flag = FormGroupHelper.ValidateForm(formPanel);
            if (flag)
            {
                Data.PortName = CbPortName.SelectedItem?.ToString();
                Data.BaudRate = (int)(NiBaudRate.Value??0);
                Data.DataBits = (int)(NiDataBits.Value??0);
                Data.StopBits = (StopBits)CbStopBits.SelectedItem;
                Data.DtrEnable = CxDtrEnable.IsChecked ?? false;
                Data.RtsEnable = CxRtsEnable.IsChecked ?? false;
            }
            return flag;
        }
        private bool _isEnabled;
        public bool SbIsEnabled {
            get => _isEnabled;
            set {
                Set(ref _isEnabled, value);
            }
        }
        private string section;
        public bool? Open(bool showPortName, string section)
        {
            string[] ports = SerialPort.GetPortNames();
            CbPortName.ItemsSource = ports;
            this.section = section;
            this.Title = section == "Config" ? this.lang("Tr.CommonSetting") : (section + this.lang("Tr.ScannerSetting"));
            if (showPortName)
            {
                FgPortName.Visibility = System.Windows.Visibility.Visible;
                FormGroupHelper.SetIsRequired(FgPortName, true);
                SbIsEnabled = false;
            }
            else
            {
                FgPortName.Visibility = System.Windows.Visibility.Collapsed;
                FormGroupHelper.SetIsRequired(FgPortName, false);
                SbIsEnabled = true;
            }
            var data = ScannerConfig.ReadFromIni(BurnInUI.SannerIniFile, "Config");
            string portName = IniFileHelper.ReadIniData(section, "PortName", "", BurnInUI.SannerIniFile);
            if (string.IsNullOrEmpty(portName)) {
                if (section.StartsWith("A"))
                {
                    portName = "COM1";
                }
                else {
                    portName = "COM2";
                }
            }
            data.PortName = portName;
            Data = data;
            CbPortName.SelectedItem = Data.PortName;
            NiBaudRate.Value = Data.BaudRate;
            NiDataBits.Value = Data.DataBits;
            CbStopBits.SelectedItem = Data.StopBits;
            CxDtrEnable.IsChecked = Data.DtrEnable;
            CxRtsEnable.IsChecked = Data.RtsEnable;
            return this.ShowDialog();
        }
        private void SaveOrUpdateData()
        {
            string iniFile = BurnInUI.SannerIniFile;
            IniFileHelper.WriteIniData(section, "PortName", Data.PortName, iniFile);
            if (section == "Config")
            {
                IniFileHelper.WriteIniData(section, "BaudRate", Data.BaudRate.ToString(), iniFile);
                IniFileHelper.WriteIniData(section, "DataBits", Data.DataBits.ToString(), iniFile);
                IniFileHelper.WriteIniData(section, "StopBits", Data.StopBits.ToString(), iniFile);
                IniFileHelper.WriteIniData(section, "DtrEnable", Data.DtrEnable.ToString(), iniFile);
                IniFileHelper.WriteIniData(section, "RtsEnable", Data.RtsEnable.ToString(), iniFile);
            }
        }
    }
}
Plugin.BurnIn/SingleCell.xaml
New file
@@ -0,0 +1,146 @@
<UserControl x:Class="Plugin.BurnIn.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.BurnIn"
             mc:Ignorable="d"
             xmlns:pw="https://opensource.panuon.com/wpf"
xmlns:pu="https://opensource.panuon.com/wpf-ui"
xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI"
             x:Name="uc"
             PreviewMouseRightButtonDown="OnPreviewMouseRightButtonDown"
             MouseDoubleClick="UserControl_MouseDoubleClick"
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <local:FontSizeConverter x:Key="FontSizeConverter"/>
    </UserControl.Resources>
    <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="#A5A5A5"/>
                </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>
                <DataTrigger Binding="{Binding status}" Value="abort">
                    <Setter Property="Background" Value="Black"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </UserControl.Style>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="14"/>
            <RowDefinition Height="14"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <Label Grid.Row="0" Grid.Column="0" HorizontalContentAlignment="Left" VerticalContentAlignment="Top" Padding="0" Margin="5,5,0,0"
      FontSize="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl},
   Converter={StaticResource FontSizeConverter}, ConverterParameter='8,103'}"
      FontWeight="Bold"
Background="Transparent" BorderThickness="0" Content="{Binding title}">
            <Label.Style>
                <Style TargetType="Label">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding status}" Value="abort">
                            <Setter Property="Foreground" Value="#FFF"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Label.Style>
        </Label>
        <Image Grid.Row="0" Grid.Column="1" Width="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl},
   Converter={StaticResource FontSizeConverter}, ConverterParameter='10,20'}" HorizontalAlignment="Right"
               Margin="0,5,5,0">
            <Image.Style>
                <Style TargetType="Image">
                    <Setter Property="HorizontalAlignment" Value="Center"/>
                    <Setter Property="VerticalAlignment" Value="Top"/>
                    <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>
                        <DataTrigger Binding="{Binding status}" Value="abort">
                            <Setter Property="Source" Value="Resource/abort.png"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Image.Style>
        </Image>
        <ui:ScrollLabel Grid.Row="1" Grid.Column="0" FontSize="10" Grid.ColumnSpan="2" Margin="3,0,0,0" Padding="0" Value="{Binding sn}"/>
        <StackPanel Orientation="Horizontal" Background="LightGray" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" VerticalAlignment="Top" HorizontalAlignment="Right">
            <TextBlock Foreground="Red" Text="{DynamicResource Ts.ProbeAlarm}" FontSize="10">
                <TextBlock.Style>
                    <Style TargetType="TextBlock">
                        <Setter Property="Visibility" Value="Collapsed"/>
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding probeAlarm}" Value="True">
                                <Setter Property="Visibility" Value="Visible"/>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </TextBlock.Style>
            </TextBlock>
            <TextBlock Margin="0" FontSize="10">
        <Run Text="{Binding ProbeCount}"/>
        <Run Text="/"/>
        <Run Text="{Binding ProbeWarn}"/>
            </TextBlock>
        </StackPanel>
    </Grid>
</UserControl>
Plugin.BurnIn/SingleCell.xaml.cs
New file
@@ -0,0 +1,1174 @@
using HMI.Core.Entity;
using HMI.Core.Manager;
using HMI.Core.Setting;
using HMI.Core.Util;
using HMI.UI.Util;
using HMI.Util;
using PLCHelper;
using Plugin.BurnIn.Constant;
using Plugin.BurnIn.Model;
using Plugin.BurnIn.Util;
using System.Collections.Concurrent;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace Plugin.BurnIn
{
    /// <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 static readonly HashSet<string> IgnoreTestItem = new HashSet<string> {
            "产品条码","checkin","预检电流","precheckresult","checkout",
        };
        private InfoWindow window;
        private int maxSize;
        public DataTable table { get; private set; }
        public SingleCell(DataTable table, string title, int index, int maxSize, string iniFile)
        {
            this.table = table;
            this.title = title;
            this.index = index;
            this.maxSize = maxSize;
            this.countIniPath = iniFile;
            DataContext = this;
            InitializeComponent();
            RefreshProbe();
        }
        public void RefreshProbe()
        {
            try
            {
                string key = string.Empty;
                ParameterData value;
                if (ParamSetting.ProbeByModel)
                {
                    string model = table.CbProduct?.SelectedItem?.ToString();
                    key = $"{title}_{model}";
                    value = ProductManager.GetParameterByProductAndName(model, "ProbeWarn");
                    if (value == null)
                    {
                        value = ParameterManager.GetCommonParameterByName("ProbeWarn");
                    }
                    ProbeWarn = value != null ? int.Parse(value.Value) : 1000;
                }
                else
                {
                    key = $"{title}";
                    value = ParameterManager.GetCommonParameterByName("ProbeWarn");
                    ProbeWarn = value != null ? int.Parse(value.Value) : 1000;
                }
                string temp = IniFileHelper.ReadIniData(key, "ProbeCount", "0", countIniPath);
                int count = Convert.ToInt32(temp);
                ProbeCount = count;
            }
            catch
            {
                ProbeWarn = 1000;
            }
            if (window != null) window.UpdateProbeCount(ProbeCount, ProbeWarn);
        }
        public void RefreshCharts()
        {
            charts = new LiveChart[3];
            try
            {
                ParameterData value;
                string model = table.CbProduct?.SelectedItem?.ToString();
                for (int i = 0; i < charts.Length; i++)
                {
                    value = ProductManager.GetParameterByProductAndName(model, $"ChartConfig_{i}");
                    if (value == null)
                    {
                        value = ParameterManager.GetCommonParameterByName($"ChartConfig_{i}");
                    }
                    var config = value != null ? value.Value : null;
                    if (string.IsNullOrEmpty(config))
                    {
                        charts[i] = null;
                    }
                    else
                    {
                        charts[i] = new LiveChart(config) //new string[] { "温度(℃)", "流量(L/m)" }
                        {
                            MinHeight = 120
                        };
                    }
                }
            }
            catch
            {
                charts = new LiveChart[0];
            }
        }
        private string _sn;
        public string sn
        {
            get { return _sn; }
            set
            {
                RaiseAndSetIfchanged(ref _sn, value);
                RaisePropertyChanged("sn");
            }
        }
        private int _index;
        public int index
        {
            get { return _index; }
            set
            {
                RaiseAndSetIfchanged(ref _index, value);
                RaisePropertyChanged("title");
            }
        }
        private bool cellIsDisabled;
        public bool CellIsDisabled
        {
            get { return cellIsDisabled; }
            set
            {
                RaiseAndSetIfchanged(ref cellIsDisabled, value);
            }
        }
        private string _title;
        public string title
        {
            get => _title;
            set
            {
                RaiseAndSetIfchanged(ref _title, value);
            }
        }
        private int _probeWarn;
        public int ProbeWarn
        {
            get => _probeWarn;
            set
            {
                RaiseAndSetIfchanged(ref _probeWarn, value);
            }
        }
        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 List<Info> allInfos = new List<Info>();
        private ObservableCollection<Info> _infos = new ObservableCollection<Info>();
        public ObservableCollection<Info> infos
        {
            get => _infos;
            set
            {
                RaiseAndSetIfchanged(ref _infos, value);
                RaisePropertyChanged("tooltipContent");
            }
        }
        private string countIniPath;
        /// <summary>
        /// æŽ¢é’ˆå¯¿å‘½
        /// </summary>
        private int probeCount;
        public int ProbeCount
        {
            get => probeCount;
            set
            {
                RaiseAndSetIfchanged(ref probeCount, value);
                probeAlarm = value >= ProbeWarn;
            }
        }
        private bool _probeAlarm;
        public bool probeAlarm
        {
            get => _probeAlarm;
            set
            {
                RaiseAndSetIfchanged(ref _probeAlarm, value);
            }
        }
        private void UpdateProbeCount(int count)
        {
            ProbeCount = count;
            string key = $"{title}";
            if (ParamSetting.ProbeByModel)
            {
                string model = table?.CbProduct?.SelectedItem?.ToString();
                key = $"{title}_{model}";
            }
            IniFileHelper.WriteIniData(key, "ProbeCount", ProbeCount.ToString(), countIniPath);
            if (window != null) window.UpdateProbeCount(ProbeCount, ProbeWarn);
        }
        private void UserControl_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            if (window == null)
            {
                window = new InfoWindow(title, infos, charts, ProbeCount, ProbeWarn);
                window.Closed += Window_Closed;
                window.ClearProbeCountEvent += Window_ClearProbeCountEvent;
                window.Owner = Window.GetWindow(this);
                window.Show();
            }
            else
            {
                window.Activate();
            }
        }
        private void Window_ClearProbeCountEvent(object sender, EventArgs e)
        {
            UpdateProbeCount(0);
        }
        private void Window_Closed(object sender, EventArgs e)
        {
            this.window = null;
        }
        private string visualTestId;
        private Sqlite3Helper helper;
        private void InitCurrentTestHelper()
        {
            if (helper == null)
            {
                var now = DateTime.Now;
                string dateStr = now.ToString("yyyyMMdd");
                visualTestId = $"{title}_{now.ToString("yyyyMMddHHmmssfff")}";
                helper = CheckSqlHelper(dateStr);
            }
        }
        public void SetStatus(string status)
        {
            Dispatcher.Invoke(() =>
            {
                var oldStatus = this.status;
                this.status = status;
                if (oldStatus != "testing" && status == "testing")
                {
                    UpdateProbeCount(ProbeCount + 1);
                    InitCurrentTestHelper();
                    InsertARecord();
                }
                if (oldStatus != "ok" && status == "ok")
                {
                    SaveInfoToDB();
                }
                if (oldStatus != "ng" && status == "ng")
                {
                    SaveInfoToDB();
                }
            });
        }
        private void InsertARecord()
        {
            try
            {
                string model = table?.CbProduct?.SelectedItem?.ToString();
                var timestr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                var sql = $"INSERT INTO testResult (sn,model,station,position,result,time,resultId) VALUES ('{sn}','{model}','','{title}','{this.status}','{timestr}','{visualTestId}');";
                helper.Execute(sql);
            }
            catch (Exception ex)
            {
                BurnInUI.Logger.Error($"Cell[{title}] InsertARecord Error.", ex);
            }
        }
        public void WriteCSV(Info info)
        {
            //try
            //{
            //    if (ParamSetting.CustomerCode != "LIXUN")
            //    {
            //        return;
            //    }
            //    string fileName = this.sn?.Replace("/", "#")?.Replace("\\", "#") + ".csv";
            //    string dir = Path.Combine(ParamSetting.DataLogFolder, "csv", this.title);
            //    string title = $"{this.lang("Test.Name")},{this.lang("Test.Value")},{this.lang("Test.LimitDown")},{this.lang("Test.LimitUp")},{this.lang("Test.Unit")},{this.lang("Test.Result")},{this.lang("Test.TestTime")}";
            //    string line = $"{info.step},{info.value},{info.limitDown},{info.limitUp},{info.unit},{info.result},{info.updateTime}";
            //    CSVPrinter.Print(dir, fileName, title, line);
            //    // ReverseCSV(Path.Combine(dir, fileName));
            //}
            //catch (Exception ex)
            //{
            //}
        }
        public void SendToChart(Info info)
        {
            try
            {
                foreach (var chart in charts)
                {
                    if (chart?.CanHandle(info.step) == true)
                    {
                        chart.AddData(info.step, DateTime.Parse(info.updateTime), double.Parse(info.value));
                    }
                }
                //if (electricChart.CanHandle(info.step))
                //{
                //    electricChart.AddData(info.step, DateTime.Parse(info.updateTime), double.Parse(info.value));
                //}
                //if (vChart.CanHandle(info.step))
                //{
                //    vChart.AddData(info.step, DateTime.Parse(info.updateTime), double.Parse(info.value));
                //}
                //if (waterChart?.CanHandle(info.step) == true)
                //{
                //    waterChart?.AddData(info.step, DateTime.Parse(info.updateTime), double.Parse(info.value));
                //}
            }
            catch (Exception ex)
            {
            }
        }
        public void SetInfos(Info info)
        {
            string sql = string.Empty;
            try
            {
                allInfos.Add(info);
                SendToChart(info);
                WriteCSV(info);
                /// ç›®å‰å…ˆä¸é™åˆ¶æ•°é‡ï¼ŒåŽç»­å¦‚果数据过多导致性能问题再考虑限制数量
                //if (queue.Count >= maxSize)
                //{
                //    queue.TryDequeue(out var first);
                //    queue.Enqueue(info);
                //    this.infos = new ObservableCollection<Info>(queue);
                //    if (window != null) window.UpdateSource(this.infos);
                //}
                //else
                //{
                //    queue.Enqueue(info);
                //    if (window != null)
                //    {
                //        window.Dispatcher.Invoke(() =>
                //        {
                //            this.infos.Add(info);
                //        });
                //    }
                //    else
                //    {
                //        Dispatcher.Invoke(() =>
                //        {
                //            this.infos.Add(info);
                //        });
                //    }
                //}
                if (window != null)
                {
                    window.Dispatcher.Invoke(() =>
                    {
                        this.infos.Add(info);
                    });
                }
                else
                {
                    Dispatcher.Invoke(() =>
                    {
                        this.infos.Add(info);
                    });
                }
                Dispatcher.Invoke(() =>
                {
                    string dateStr = string.Empty;
                    try
                    {
                        dateStr = DateTime.Parse(info.updateTime).ToString("yyyy-MM-dd HH:mm:ss");
                    }
                    catch
                    {
                        dateStr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                    }
                    try
                    {
                        InitCurrentTestHelper();
                        string model = table?.CbProduct?.SelectedItem?.ToString();
                        sql = $@"INSERT INTO serialData (sn,model,station,position,step,limitDown,limitUp,unit,value,status,finalStatus,time,resultId)
VALUES ('{sn}','{model}','','{this.title}','{info.step}','{info.limitDown}','{info.limitUp}','{info.unit}','{info.value}','{info.result}',
NULL,'{dateStr}','{visualTestId}');";
                        helper.Execute(sql);
                    }
                    catch (Exception ex)
                    {
                        BurnInUI.Logger.Error($"Save DB Test Data Error. {sql}", ex);
                    }
                });
            }
            catch (Exception ex)
            {
                BurnInUI.Logger.Error($"Add Test Data Error. {sql}", ex);
            }
        }
        public void ClearInfo()
        {
            allInfos.Clear();
            queue = new ConcurrentQueue<Info>();
            var list = new ObservableCollection<Info>(queue);
            this.infos = list;
            if (window != null) window.UpdateSource(this.infos);
        }
        //private Chart waterChart = null;
        //private Chart electricChart = new Chart(LangUtil.Get("Tr.CurrentChart"), false, 20)
        //{
        //    Height = 120
        //};
        //private Chart vChart = new Chart(LangUtil.Get("Tr.VoltChart"), false, 20)
        //{
        //    Height = 120
        //};
        private LiveChart[] charts = new LiveChart[0];
        //private LiveChart waterChart = null;
        //private LiveChart electricChart = new LiveChart("子板电流,电流(A);母板电流,电流(A)") //new string[] { "子板电流", "模板电流" }
        //{
        //    MinHeight = 120
        //};
        //private LiveChart vChart = new LiveChart("子板电压,电压(V);母板电压,电压(V)") //new string[] { "子板电压", "模板电压" }
        //{
        //    MinHeight = 120
        //};
        public void AddWaterData(double temp, double backTemp, double pressure)
        {
            //waterChart?.AddData(0, "水箱温度", temp);
            //waterChart?.AddData(0, "回水温度", backTemp);
            //waterChart?.AddData(1, "流量", pressure);
            //waterChart?.AddData("水箱温度", temp);
            //waterChart?.AddData("回水温度", backTemp);
            //waterChart?.AddData("流量", pressure);
        }
        public void AddElectricData(double data)
        {
            //electricChart.AddData("", data);
        }
        public void AddVData(double data)
        {
            //vChart.AddData("", data);
        }
        public void ClearChart()
        {
            foreach (var chart in charts)
            {
                chart?.Clear();
            }
        }
        //public void ClearWaterData()
        //{
        //    waterChart?.Clear();
        //}
        //public void ClearElectricData()
        //{
        //    electricChart.Clear();
        //}
        //public void ClearVData()
        //{
        //    vChart.Clear();
        //}
        public void SaveElectricDataToCSV(string filePath)
        {
            try
            {
                List<double> datas = charts[1]?.GetSingleData("");
                FileInfo fi = new FileInfo(filePath);
                if (!fi.Directory.Exists)
                {
                    fi.Directory.Create();  //文件不存在,创建文件
                }
                using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
                using (StreamWriter sw = new StreamWriter(fs, System.Text.Encoding.UTF8))
                {
                    string title = this.lang("Tr.CurrentChart");
                    sw.WriteLine(title);
                    if (datas != null)
                    {
                        for (int i = 0; i < datas.Count; i++)
                        {
                            sw.WriteLine(datas[i].ToString());
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }
        public void SaveVDataToCSV(string filePath)
        {
            try
            {
                List<double> datas = charts[2]?.GetSingleData("");
                FileInfo fi = new FileInfo(filePath);
                if (!fi.Directory.Exists)
                {
                    fi.Directory.Create();  //文件不存在,创建文件
                }
                using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
                using (StreamWriter sw = new StreamWriter(fs, System.Text.Encoding.UTF8))
                {
                    string title = this.lang("Tr.VoltChart");
                    sw.WriteLine(title);
                    if (datas != null)
                    {
                        for (int i = 0; i < datas.Count; i++)
                        {
                            sw.WriteLine(datas[i].ToString());
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }
        public void NormalCSV(string filePath)
        {
            try
            {
                FileInfo fi = new FileInfo(filePath);
                if (!fi.Directory.Exists)
                {
                    fi.Directory.Create();  //文件不存在,创建文件
                }
                using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
                using (StreamWriter sw = new StreamWriter(fs, System.Text.Encoding.UTF8))
                {
                    string title = $"{this.lang("Test.Name")},{this.lang("Test.Value")},{this.lang("Test.LimitDown")},{this.lang("Test.LimitUp")},{this.lang("Test.Unit")},{this.lang("Test.Result")},{this.lang("Test.TestTime")}";
                    sw.WriteLine(title);
                    foreach (var info in allInfos)
                    {
                        string row = $"{info.step},{info.value},{info.limitDown},{info.limitUp},{info.unit},{info.result},{info.updateTime}";
                        sw.WriteLine(row);
                    }
                }
            }
            catch (Exception ex)
            {
                BurnInUI.Logger.Error($"Cell[{title}] SaveInfoToCSV Error.", ex);
            }
        }
        public void ReverseCSV_Bak(string filePath)
        {
            try
            {
                if (allInfos == null || allInfos.Count < 4) return;
                Dictionary<string, int> columnMap = new Dictionary<string, int>();
                int colPoint = 0;
                List<string> titleRow = new List<string>();
                List<string[]> datas = new List<string[]>();
                void AddTitle(string title)
                {
                    if (titleRow.IndexOf(title) < 0)
                    {
                        titleRow.Add(title);
                    }
                }
                // å…ˆè®¾ç½®è¡¨å¤´
                for (int row = 0; row < allInfos.Count; row++)
                {
                    var info = allInfos[row];
                    if (IgnoreTestItem.Contains(info.step.ToLower()))
                    {
                        continue;
                    }
                    string title = $"{info.step}{(string.IsNullOrEmpty(info.unit) ? "" : $"({info.unit})")}";
                    AddTitle("测试时间");
                    AddTitle(title);
                    if (!string.IsNullOrEmpty(info.limitDown))
                    {
                        string key2 = $"{info.step}下限";
                        AddTitle(key2);
                    }
                    if (!string.IsNullOrEmpty(info.limitUp))
                    {
                        string key2 = $"{info.step}上限";
                        AddTitle(key2);
                    }
                    if (!string.IsNullOrEmpty(info.limitUp))
                    {
                        string key2 = $"{info.step}测试结果";
                        AddTitle(key2);
                    }
                    //if (!string.IsNullOrEmpty(info.updateTime))
                    //{
                    //    string key2 = $"{info.step}测试时间";
                    //    AddTitle(key2);
                    //}
                }
                int rowPoint = -1;
                for (int row = 0; row < allInfos.Count; row++)
                {
                    var info = allInfos[row];
                    if (IgnoreTestItem.Contains(info.step.ToLower()))
                    {
                        continue;
                    }
                    string title = $"{info.step}{(string.IsNullOrEmpty(info.unit) ? "" : $"({info.unit})")}";
                    int col = titleRow.IndexOf(title);
                    if (col == 1)
                    {
                        rowPoint++;
                    }
                    if (datas.Count <= rowPoint)
                    {
                        datas.Add(new string[titleRow.Count]);
                    }
                    var arr = datas[rowPoint];
                    arr[col] = info.value;
                    if (!string.IsNullOrEmpty(info.limitDown))
                    {
                        string key2 = $"{info.step}下限";
                        int col2 = titleRow.IndexOf(key2);
                        arr[col2] = info.limitDown;
                    }
                    if (!string.IsNullOrEmpty(info.limitUp))
                    {
                        string key2 = $"{info.step}上限";
                        int col2 = titleRow.IndexOf(key2);
                        arr[col2] = info.limitUp;
                    }
                    if (!string.IsNullOrEmpty(info.limitUp))
                    {
                        string key2 = $"{info.step}测试结果";
                        int col2 = titleRow.IndexOf(key2);
                        arr[col2] = info.result;
                    }
                    if (!string.IsNullOrEmpty(info.updateTime))
                    {
                        string key2 = "测试时间";// $"{info.step}测试时间";
                        int col2 = titleRow.IndexOf(key2);
                        arr[col2] = info.updateTime;
                    }
                }
                FileInfo fi = new FileInfo(filePath);
                if (!fi.Directory.Exists)
                {
                    fi.Directory.Create();  //文件不存在,创建文件
                }
                using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
                using (StreamWriter sw = new StreamWriter(fs, System.Text.Encoding.UTF8))
                {
                    string title = string.Join(",", titleRow);
                    sw.WriteLine(title);
                    foreach (var info in datas)
                    {
                        string row = string.Join(",", info);
                        sw.WriteLine(row);
                    }
                }
            }
            catch (Exception ex)
            {
                BurnInUI.Logger.Error($"Cell[{title}] SaveInfoToCSV Error.", ex);
            }
        }
        public void ReverseCSV(string filePath)
        {
            try
            {
                if (allInfos == null || allInfos.Count < 4) return;
                Dictionary<string, int> columnMap = new Dictionary<string, int>();
                int colPoint = 0;
                List<string> titleRow = new List<string>();
                List<string[]> datas = new List<string[]>();
                List<string> timeRow = new List<string>();
                void AddTitle(string title)
                {
                    if (titleRow.IndexOf(title) < 0)
                    {
                        titleRow.Add(title);
                    }
                }
                // å…ˆè®¾ç½®è¡¨å¤´
                for (int i = 0; i < allInfos.Count; i++)
                {
                    var info = allInfos[i];
                    if (IgnoreTestItem.Contains(info.step.ToLower()))
                    {
                        continue;
                    }
                    string testTime = info.updateTime;
                    int rowIndex = timeRow.IndexOf(testTime);
                    if (rowIndex < 0)
                    {
                        timeRow.Add(testTime);
                    }
                    string title = $"{info.step}{(string.IsNullOrEmpty(info.unit) ? "" : $"({info.unit})")}";
                    AddTitle("测试时间");
                    AddTitle(title);
                    if (!string.IsNullOrEmpty(info.limitDown))
                    {
                        string key2 = $"{info.step}下限";
                        AddTitle(key2);
                    }
                    if (!string.IsNullOrEmpty(info.limitUp))
                    {
                        string key2 = $"{info.step}上限";
                        AddTitle(key2);
                    }
                    if (!string.IsNullOrEmpty(info.limitUp))
                    {
                        string key2 = $"{info.step}测试结果";
                        AddTitle(key2);
                    }
                    //if (!string.IsNullOrEmpty(info.updateTime))
                    //{
                    //    string key2 = $"{info.step}测试时间";
                    //    AddTitle(key2);
                    //}
                }
                for (int i = 0; i < allInfos.Count; i++)
                {
                    var info = allInfos[i];
                    if (IgnoreTestItem.Contains(info.step.ToLower()))
                    {
                        continue;
                    }
                    string testTime = info.updateTime;
                    int row = timeRow.IndexOf(testTime);
                    if (row < 0)
                    {
                        continue;
                    }
                    string title = $"{info.step}{(string.IsNullOrEmpty(info.unit) ? "" : $"({info.unit})")}";
                    int col = titleRow.IndexOf(title);
                    if (datas.Count <= row)
                    {
                        datas.Add(new string[titleRow.Count]);
                    }
                    var arr = datas[row];
                    arr[col] = info.value;
                    if (!string.IsNullOrEmpty(info.limitDown))
                    {
                        string key2 = $"{info.step}下限";
                        int col2 = titleRow.IndexOf(key2);
                        arr[col2] = info.limitDown;
                    }
                    if (!string.IsNullOrEmpty(info.limitUp))
                    {
                        string key2 = $"{info.step}上限";
                        int col2 = titleRow.IndexOf(key2);
                        arr[col2] = info.limitUp;
                    }
                    if (!string.IsNullOrEmpty(info.limitUp))
                    {
                        string key2 = $"{info.step}测试结果";
                        int col2 = titleRow.IndexOf(key2);
                        arr[col2] = info.result;
                    }
                    if (!string.IsNullOrEmpty(info.updateTime))
                    {
                        string key2 = "测试时间";// $"{info.step}测试时间";
                        int col2 = titleRow.IndexOf(key2);
                        arr[col2] = info.updateTime;
                    }
                }
                FileInfo fi = new FileInfo(filePath);
                if (!fi.Directory.Exists)
                {
                    fi.Directory.Create();  //文件不存在,创建文件
                }
                using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
                using (StreamWriter sw = new StreamWriter(fs, System.Text.Encoding.UTF8))
                {
                    string title = string.Join(",", titleRow);
                    sw.WriteLine(title);
                    foreach (var info in datas)
                    {
                        string row = string.Join(",", info);
                        sw.WriteLine(row);
                    }
                }
            }
            catch (Exception ex)
            {
                BurnInUI.Logger.Error($"Cell[{title}] SaveInfoToCSV Error.", ex);
            }
        }
        /// <summary>
        /// ä¿å­˜æµ‹è¯•数据到CSV
        /// </summary>
        /// <param name="filePath"></param>
        public void SaveInfoToCSV(string filePath)
        {
            if (ParamSetting.ReverseCSV)
            {
                ReverseCSV(filePath);
            }
            else
            {
                NormalCSV(filePath);
            }
        }
        private Sqlite3Helper CheckSqlHelper(string dateStr = null)
        {
            dateStr = string.IsNullOrEmpty(dateStr) ? DateTime.Now.ToString("yyyyMMdd") : dateStr;
            string dbPath = Path.Combine(ParamSetting.DataLogFolder, $"Data_{dateStr}.db");
            if (!File.Exists(dbPath))
            {
                Sqlite3Helper.CreateDatabase(dbPath);
            }
            var sqliteHelper = new Sqlite3Helper(dbPath);
            sqliteHelper.Execute(@"CREATE TABLE IF NOT EXISTS serialData (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    sn TEXT NOT NULL,
                    model TEXT,
                    station TEXT NOT NULL,
                    step TEXT NOT NULL,
                    position TEXT,
                    limitDown TEXT,
                    limitUp TEXT,
                    unit TEXT,
                    offset TEXT,
                    value TEXT,
                    status TEXT,
                    finalStatus TEXT,
                    consumeTime TEXT,
                    totalConsumeTime TEXT,
                    resultId TEXT,
                    time DATETIME NOT NULL);");
            sqliteHelper.Execute(@"CREATE TABLE IF NOT EXISTS testResult (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    sn TEXT NOT NULL,
                    model TEXT,
                    station TEXT NOT NULL,
                    position TEXT,
                    status TEXT,
                    result TEXT NOT NULL,
                    resultId TEXT,
                    time DATETIME NOT NULL);");
            if (!sqliteHelper.HasData("SELECT 1 FROM pragma_table_info('serialData') WHERE name = 'resultId';"))
            {
                sqliteHelper.Execute("ALTER TABLE serialData ADD resultId TEXT;");
            }
            if (!sqliteHelper.HasData("SELECT 1 FROM pragma_table_info('testResult') WHERE name = 'resultId';"))
            {
                sqliteHelper.Execute("ALTER TABLE testResult ADD resultId TEXT;");
            }
            return sqliteHelper;
        }
        /// <summary>
        /// ä¿å­˜æµ‹è¯•数据到Sqlite
        /// </summary>
        /// <param name="filePath"></param>
        public void SaveInfoToDB()
        {
            Dispatcher.Invoke(() =>
            {
                try
                {
                    string model = table?.CbProduct?.SelectedItem?.ToString();
                    var timestr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                    //var sql = $"INSERT INTO testResult (sn,model,station,position,result,time,resultId) VALUES ('{sn}','{model}','','{title}','{this.status}','{timestr}','{visualTestId}');";
                    var sql = $"UPDATE testResult SET time = '{timestr}', result = '{this.status}' WHERE resultId = '{visualTestId}'";
                    helper.Execute(sql);
                    helper.Dispose();
                    helper = null;
                    //                using (var helper = CheckSqlHelper())
                    //                {
                    //                    List<string> sqls = new List<string>();
                    //                    var timestr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                    //                    foreach (var info in allInfos)
                    //                    {
                    //                        string sql = $@"INSERT INTO serialData (sn,station,step,limitDown,limitUp,unit,value,status,finalStatus,time)
                    //VALUES ('{sn}','','{info.step}','{info.limitDown}','{info.limitUp}','{info.unit}','{info.value}','{info.result}','{this.status}','{timestr}');";
                    //                        sqls.Add(sql);
                    //                    }
                    //                    sqls.Add($"INSERT INTO testResult (sn,station,position,result,time) VALUES ('{sn}','','{title}','{this.status}','{timestr}');");
                    //                    helper.ExecuteBatchSql(sqls);
                    //                }
                }
                catch (Exception ex)
                {
                    BurnInUI.Logger.Error($"Cell[{title}] SaveToDB Error.", ex);
                }
            });
        }
        private void MenuItem_Click(object sender, RoutedEventArgs e)
        {
            var item = sender as MenuItem;
            switch (item?.Name)
            {
                case "clearSNRItem":
                    table.TryClearCellSN(this.index);
                    break;
                case "exportCSVItem":
                    TryExportCSV();
                    break;
                case "manualDebugItem":
                    OpenManualDebugWindow();
                    break;
                default:
                    break;
            }
        }
        /// <summary>
        /// æ‰“开手动调试窗口
        /// </summary>
        private void OpenManualDebugWindow()
        {
            string manualDebugSeq = ParameterManager.GetCommonParameterByName("ManualDebugSeqPath")?.Value;
            if (string.IsNullOrEmpty(manualDebugSeq))
            {
                MsgBoxHelper2.Error("请先设置手动调试序列路径[ManualDebugSeqPath]!");
                return;
            }
            string modelPath = table.parent.testModelFile;
            int dutNo = table.index * table.cellCount + this.index;
            var win = new ManualDebugWindow(manualDebugSeq, modelPath)
            {
                Title = $"{this.title}手动调试",
                Owner = Window.GetWindow(this),
                OvenIndex = this.table.index,
                OvenName = this.table.title,
                CellIndex = dutNo,
                CellName = this.title
            };
            win.ShowDialog();
        }
        /// <summary>
        /// å°è¯•导出CSV
        /// </summary>
        private void TryExportCSV()
        {
            System.Windows.Forms.SaveFileDialog sfd = new System.Windows.Forms.SaveFileDialog
            {
                Filter = "CSV (*.csv)|*.csv"
            };
            if (sfd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                this.NormalCSV(sfd.FileName);
            }
        }
        public void ClearSN()
        {
            sn = string.Empty;
            LightGreen(false, out _);
            LightRed(false, out _);
        }
        public bool LightGreen(bool on, out string msg)
        {
            string key = $"Oven{table.index + 1}_Cell{index + 1}_Light_OK";
            bool res = S7Context.WriteData(RunTimeVariablePool.DEFAULT_PLC_NAME, key, on, out msg);
            if (res)
            {
                BurnInUI.Logger.Debug($"Set Light [{key}] {on}.");
            }
            else
            {
                BurnInUI.Logger.Debug($"Open Light [{key}] {on} Error.{msg}");
            }
            return res;
        }
        public bool LightRed(bool on, out string msg)
        {
            return S7Context.WriteData(RunTimeVariablePool.DEFAULT_PLC_NAME,
                $"Oven{table.index + 1}_Cell{index + 1}_Light_NG", on, out msg);
        }
        private void OnPreviewMouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            ContextMenu contextMenu = new ContextMenu();
            contextMenu.Closed += ContextMenu_Closed;
            //<  />
            //<
            //< MenuItem IsCheckable = "False" x: Name = "manualDebugItem" Click = "MenuItem_Click" Header = "手动调试" >
            //    < MenuItem.Style >
            //        < Style TargetType = "MenuItem" BasedOn = "{StaticResource {x:Static pu:StyleKeys.MenuItemStyle}}" >
            //        </ Style >
            //    </ MenuItem.Style >
            //</ MenuItem >
            var item = new MenuItem
            {
                IsCheckable = false,
                IsEnabled = !table.IsTesting,
                Name = "clearSNRItem",
                Header = this.lang("Tr.ClearSNR"),
            };
            item.Click += MenuItem_Click;
            contextMenu.Items.Add(item);
            item = new MenuItem
            {
                IsCheckable = false,
                Name = "exportCSVItem",
                Header = this.lang("App.ExportCSV"),
            };
            item.Click += MenuItem_Click;
            contextMenu.Items.Add(item);
            if (table.parent.inManualDebugMode)
            {
                item = new MenuItem
                {
                    IsCheckable = false,
                    Name = "manualDebugItem",
                    Header = this.lang("手动调试"),
                };
                item.Click += MenuItem_Click;
                contextMenu.Items.Add(item);
            }
            this.ContextMenu = contextMenu;
        }
        private void ContextMenu_Closed(object sender, RoutedEventArgs e)
        {
            this.ContextMenu = null;
        }
    }
}
Plugin.BurnIn/TestResultView.xaml
New file
@@ -0,0 +1,21 @@
<UserControl x:Class="Plugin.BurnIn.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.BurnIn"
xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
xmlns:pw="https://opensource.panuon.com/wpf"
xmlns:pu="https://opensource.panuon.com/wpf-ui"
xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI"
xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
             mc:Ignorable="d"
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid x:Name="panel">
        <WindowsFormsHost x:Name="host" Visibility="Collapsed"></WindowsFormsHost>
        <Grid x:Name="mainGrid" dp:GridHelper.Space="6">
        </Grid>
    </Grid>
</UserControl>
Plugin.BurnIn/TestResultView.xaml.cs
New file
@@ -0,0 +1,683 @@
using HMI.Core.Entity;
using HMI.Core.Manager;
using HMI.Core.Util;
using HMI.UI.Util;
using NationalInstruments.TestStand.Interop.UI.Ax;
using Plugin.BurnIn.Model;
using Plugin.TsSupport;
using System.Windows;
using System.Windows.Controls;
namespace Plugin.BurnIn
{
    /// <summary>
    /// TestResultView.xaml çš„交互逻辑
    /// </summary>
    public partial class TestResultView : UserControl
    {
        private int rowCount;
        private int colCount;
        private int cellCount;
        public static DataTable[] tables { get; private set; }
        public static Dictionary<string, DataTable> nameTables { get; private set; } = new Dictionary<string, DataTable>();
        private readonly BaseConfig config;
        public string testModelFile;
        public string testSeqFile;
        public bool inManualDebugMode = false;
        #region TS相关
        //public AxApplicationMgr testApp;
        ///// <summary>
        ///// æµ‹è¯•序列
        ///// </summary>
        //private SequenceFile testSeq = null;
        //public Engine engine;
        //private Execution execution = null;
        ///// <summary>
        ///// æµ‹è¯•序列操作句柄
        ///// </summary>
        //public SequenceContext SequenceContext { get; private set; } = null;
        ///// <summary>
        ///// æµ‹è¯•模型序列
        ///// </summary>
        //private SequenceFile modeSeq;
        public ITestExecutionWrapper executionWrapper;
        #endregion
        public TestResultView() : this(new BaseConfig { testing = false })
        {
        }
        public bool IsRunning { get; set; } = false;
        public void RefreshCooler()
        {
        }
        private void RefreshNumberConfig(BaseConfig config)
        {
            ParameterData value = null;
            try
            {
                value = ParameterManager.GetCommonParameterByName("BurnInOvenCount");
                string[] datas = value.Value.Trim().Split(',');
                config.Row = int.Parse(datas[0]);
                config.Column = int.Parse(datas[1]);
            }
            catch (Exception ex)
            {
            }
            try
            {
                value = ParameterManager.GetCommonParameterByName("BurnInOvenName");
                config.NameStr = value.Value;
            }
            catch (Exception ex)
            {
            }
            try
            {
                value = ParameterManager.GetCommonParameterByName("BurnInOvenPort");
                config.PortStart = int.Parse(value.Value);
            }
            catch (Exception ex)
            {
            }
            try
            {
                value = ParameterManager.GetCommonParameterByName("BurnInCellCount");
                string[] datas = value.Value.Trim().Split(',');
                config.PositionColumn = int.Parse(datas[0]);
                config.PositionCount = int.Parse(datas[1]);
            }
            catch (Exception ex)
            {
            }
        }
        public TestResultView(BaseConfig config)
        {
            try
            {
                var value = ParameterManager.GetCommonParameterByName("TsModelPath");
                testModelFile = value?.Value ?? @"C:\Users\Public\Documents\National Instruments\TestStand 2020 (64-bit)\Components\Models\ParallelModel.seq";
            }
            catch
            {
                //@"C:\Program Files\National Instruments\TestStand 2020\Components\Models\TestStandModels\ParallelModel.seq"
                testModelFile = @"C:\Users\Public\Documents\National Instruments\TestStand 2020 (64-bit)\Components\Models\ParallelModel.seq";
            }
            RefreshNumberConfig(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();
        }
        private void ExecutionWrapper_UIMessageEvent(object sender, UIMessageEvent e)
        {
            try
            {
                HandleUIMessage(e.EventCode, Convert.ToInt16(e.NumberData), e.StrData, e.Data);
            }
            catch (Exception ex)
            {
            }
        }
        private void Refresh()
        {
        }
        /// <summary>
        /// æŽ¢é’ˆè®¡æ•°é…ç½®è·¯å¾„
        /// </summary>
        private string CountIniPath;
        public void SetCountIniPath(string countIniPath)
        {
            CountIniPath = countIniPath;
        }
        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, CountIniPath, 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(bool isTap)
        {
            try
            {
                if (isTap)
                {
                    executionWrapper = new TapWrapper();
                }
                else
                {
                    executionWrapper = new TsExecutionWrapper();
                }
                executionWrapper.UIMessageEvent += ExecutionWrapper_UIMessageEvent;
                if (executionWrapper is TsExecutionWrapper tsWrapper)
                {
                    ((System.ComponentModel.ISupportInitialize)(tsWrapper.AxApplicationMgr)).BeginInit();
                    host.Child = tsWrapper.AxApplicationMgr;
                    ((System.ComponentModel.ISupportInitialize)(tsWrapper.AxApplicationMgr)).EndInit();
                }
                //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(testModelFile);
                executionWrapper.Init();
            }
            catch (Exception ex)
            {
                BurnInUI.Logger.Error("InitTestStandEngine Failed.", ex);
                throw new Exception($"{this.lang("Ts.InitTestStandEngineFail")}: {ex.Message}");
            }
        }
        public void SetOvenName()
        {
            foreach (var oven in tables)
            {
                executionWrapper?.SetStationGlobalValue<string>($"StationHMI.UUTHMI[{oven.index}].Name", 1, oven.Name);
            }
            executionWrapper?.CommitToDisk(false);
        }
        public void StartTestSequence()
        {
            try
            {
                if (string.IsNullOrEmpty(testSeqFile)) return;
                executionWrapper?.StartSequence(this.testSeqFile,
                    @"C:\Program Files\National Instruments\TestStand 2020\Components\Models\TestStandModels\ParallelModel.seq");
                executionWrapper.SetStationGlobalValue<bool>("StationHMI.AllStop", 0, false);
            }
            catch (Exception ex)
            {
                BurnInUI.Logger.Error("StartTestSequence Failed.", ex);
                throw new Exception($"{this.lang("Ts.StartTestSequenceFailed")}: {ex.Message}");
            }
        }
        public void StopTest()
        {
            try
            {
                executionWrapper?.SetStationGlobalValue<bool>("StationState.StopTest", 0, true, true);
                try
                {
                    executionWrapper?.WaitForExit();
                    executionWrapper?.ReleaseSeqFile();
                }
                catch (Exception ex)
                {
                    BurnInUI.Logger.Error("Wait For Exit error.", ex);
                    //if (ex is NullReferenceException ne)
                    //{
                    //}
                    //else
                    //{
                    //    throw ex;
                    //}
                }
            }
            catch (Exception ex)
            {
                BurnInUI.Logger.Error("StopTest Failed.", ex);
                throw new Exception($"{this.lang("Ts.StopTestSequenceFailed")}: {ex.Message}");
            }
        }
        [Obsolete]
        private void axApplicationMgr_ExitApplication(object sender, EventArgs e)
        {
            //Environment.ExitCode = testApp.ExitCode;
            //TSHelper.DoSynchronousGCForCOMObjectDestruction();
        }
        [Obsolete]
        private void axApplicationMgr_ExecutionClosed(object sender, _ApplicationMgrEvents_ExecutionClosedEvent e)
        {
            try
            {
                //this.ReleaseSequenceFile();
            }
            catch (Exception ex)
            {
                MsgBoxHelper.Error(Window.GetWindow(this),
                    $"{this.lang("Ts.ReleaseSequenceFileFail")}: {ex.Message}");
            }
        }
        private void HandleUIMessage(int EventCase, int MethodCode, string strData, object xData)
        {
            try
            {
                if (EventCase != 10014) return;
                if (MethodCode == 0)
                {
                    /// è®¾ç½®å¥æŸ„
                    executionWrapper.SetContext(xData);
                    return;
                }
                if (MethodCode == 1)
                {
                    /// æ›´æ–°ç‚‰å­çŠ¶æ€
                    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)
                {
                    /// æ›´æ–°ç‚‰å­æ•°é‡ä¿¡æ¯
                    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)
                {
                    /// æ›´æ–°ç‚‰å­æ—¶é—´ä¿¡æ¯
                    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].SetEndTime(planEndTime);
                    double? leftTime = null;
                    /// ä¸ºäº†å…¼å®¹æ—§ç‰ˆæœ¬ï¼Œå¦‚果有剩余时间就传,没有就传null
                    if (datas_10014.Length >= 5)
                    {
                        leftTime = double.Parse(datas_10014[4]);
                    }
                    tables[ovenIndex].UpdateConsumeTime(double.Parse(consumeTime), leftTime);
                    return;
                }
                if (MethodCode == 4)
                {
                    /// æ›´æ–°è€åŒ–炉底部状态栏
                    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)
                {
                    /// é‡‡é›†ç‚‰æ¸©åº¦
                    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)
                {
                    Dispatcher.Invoke(() =>
                    {/// æ›´æ–°ç©´ä½çŠ¶æ€
                        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);
                        if (status_10015 == "OK" || status_10015 == "ok")
                        {
                            tables[ovenIndex].RefreshCount(1, 0);
                        }
                        else if (status_10015 == "ng" || status_10015 == "NG")
                        {
                            tables[ovenIndex].RefreshCount(0, 1);
                        }
                    });
                    return;
                }
                if (MethodCode == 7)
                {
                    /// æ›´æ–°ç©´ä½å‚æ•°
                    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],
                        limitUp = values[2],
                        limitDown = values[3],
                        unit = values[4],
                        result = values[5],
                        updateTime = values[6]
                    });
                    return;
                }
                if (MethodCode == 10)
                {
                    /// æ¸…空炉子的温度图表
                    int ovenIndex = int.Parse(strData);
                    tables[ovenIndex].ClearChartData();
                    return;
                }
                if (MethodCode == 11)
                {
                    /// æ¸…除穴位参数
                    string[] datas_10015 = strData.Split(';');
                    int ovenIndex = int.Parse(datas_10015[0]);
                    int cellIndex = int.Parse(datas_10015[1]);
                    tables[ovenIndex].GetSingleCell(cellIndex).ClearInfo();
                    //GC.Collect();
                    return;
                }
                if (MethodCode == 12)
                {
                    /// ä¿å­˜ç‚‰ä½“温度文件
                    string[] datas = strData.Split(';');
                    int ovenIndex = int.Parse(datas[0]);
                    string folder = datas[1];
                    tables[ovenIndex].SaveTempFile(folder);
                    return;
                }
                if (MethodCode == 13)
                {
                    /// æ·»åŠ ç”µæµæ•°æ®
                    string[] datas = strData.Split(';');
                    int ovenIndex = int.Parse(datas[0]);
                    int cellIndex = int.Parse(datas[1]);
                    double eData = double.Parse(datas[2]);
                    tables[ovenIndex].GetSingleCell(cellIndex).AddElectricData(eData);
                    return;
                }
                if (MethodCode == 14)
                {
                    /// æ¸…空电流和电压数据
                    int ovenIndex = int.Parse(strData);
                    tables[ovenIndex].ClearCellData();
                    //tables[ovenIndex].ClearElectricData();
                    //tables[ovenIndex].ClearVData();
                    //tables[ovenIndex].ClearWaterData();
                    return;
                }
                if (MethodCode == 15)
                {
                    /// ä¿å­˜ç”µæµæ•°æ®
                    string[] datas = strData.Split(';');
                    int ovenIndex = int.Parse(datas[0]);
                    int cellIndex = int.Parse(datas[1]);
                    string filePath = datas[2];
                    tables[ovenIndex].GetSingleCell(cellIndex).SaveElectricDataToCSV(filePath);
                    return;
                }
                if (MethodCode == 16)
                {
                    /// æ·»åŠ ç”µåŽ‹å›¾è¡¨æ•°æ®
                    string[] datas = strData.Split(';');
                    int ovenIndex = int.Parse(datas[0]);
                    int cellIndex = int.Parse(datas[1]);
                    double eData = double.Parse(datas[2]);
                    tables[ovenIndex].GetSingleCell(cellIndex).AddVData(eData);
                    return;
                }
                if (MethodCode == 17)
                {
                    /// ä¿å­˜ç”µåŽ‹æ•°æ®
                    string[] datas = strData.Split(';');
                    int ovenIndex = int.Parse(datas[0]);
                    int cellIndex = int.Parse(datas[1]);
                    string filePath = datas[2];
                    tables[ovenIndex].GetSingleCell(cellIndex).SaveVDataToCSV(filePath);
                    return;
                }
                if (MethodCode == 18)
                {
                    /// ä¿å­˜ç©´ä½æµ‹è¯•数据到CSV
                    string[] datas = strData.Split(';');
                    int ovenIndex = int.Parse(datas[0]);
                    int cellIndex = int.Parse(datas[1]);
                    string filePath = datas[2];
                    tables[ovenIndex].GetSingleCell(cellIndex).SaveInfoToCSV(filePath);
                    return;
                }
                if (MethodCode == 180)
                {
                    /// ä¿å­˜ç©´ä½æµ‹è¯•数据到CSV
                    string[] datas = strData.Split(';');
                    int ovenIndex = int.Parse(datas[0]);
                    int cellIndex = int.Parse(datas[1]);
                    string filePath = datas[2];
                    tables[ovenIndex].GetSingleCell(cellIndex).NormalCSV(filePath);
                    return;
                }
                if (MethodCode == 19)
                {
                    /// ä¿å­˜ç©´ä½æµ‹è¯•数据到Sqlite
                    string[] datas = strData.Split(';');
                    int ovenIndex = int.Parse(datas[0]);
                    int cellIndex = int.Parse(datas[1]);
                    tables[ovenIndex].GetSingleCell(cellIndex).SaveInfoToDB();
                    return;
                }
                if (MethodCode == 20)
                {
                    /// æ¸…空条码
                    int ovenIndex = int.Parse(strData);
                    tables[ovenIndex].ClearAllSN();
                    return;
                }
                if (MethodCode == 21)
                {
                    /// æ·»åŠ æ°´æµå›¾è¡¨æ•°æ®
                    string[] datas = strData.Split(';');
                    int ovenIndex = int.Parse(datas[0]);
                    int cellIndex = int.Parse(datas[1]);
                    double waterTemp = double.Parse(datas[2]);
                    double backWaterTemp = double.Parse(datas[3]);
                    double waterPressure = double.Parse(datas[4]);
                    tables[ovenIndex].GetSingleCell(cellIndex).AddWaterData(waterTemp, backWaterTemp, waterPressure);
                    return;
                }
                if (MethodCode == 99)
                {
                    string[] datas = strData.Split(';');
                    int index = int.Parse(datas[0]);
                    if (MsgBoxHelper2.WarnChoose(datas[1], "提示") == MessageBoxResult.OK)
                    {
                        executionWrapper.SetStationGlobalValue<int>($"StationState.Pipe[{index}]", 1, 1);
                        //SequenceContext.StationGlobals.SetValNumber($"StationState.Pipe[{index}]", 1, 1);
                    }
                    else
                    {
                        executionWrapper.SetStationGlobalValue<int>($"StationState.Pipe[{index}]", 1, 0);
                        //SequenceContext.StationGlobals.SetValNumber($"StationState.Pipe[{index}]", 1, 0);
                    }
                    return;
                }
            }
            catch (Exception ex)
            {
                BurnInUI.Logger.Warn("TestStand Event Error.", ex);
            }
        }
        private void axApplicationMgr_UIMessageEvent(object sender, _ApplicationMgrEvents_UIMessageEventEvent e)
        {
            int EventCase = Convert.ToInt32(e.uiMsg.Event);
            int MethodCode = Convert.ToInt16(e.uiMsg.NumericData);
            string strData = e.uiMsg.StringData;
            HandleUIMessage(EventCase, MethodCode, strData, e.uiMsg.ActiveXData);
        }
        public void SetStartFlag(int index, bool data)
        {
            string name = tables[index].title;
            string key = string.Empty;
            if (data)
            {
                BurnInUI.Logger?.Info($"启动老化炉[{name}]");
                key = $"StationHMI.UUTHMI[{index}].Start";
            }
            else
            {
                BurnInUI.Logger?.Info($"停止老化炉[{name}]");
                key = $"StationHMI.UUTHMI[{index}].Stop";
            }
            executionWrapper?.SetStationGlobalValue<bool>(key, 1, true);
        }
        public void ReleaseResource()
        {
            executionWrapper.UIMessageEvent -= ExecutionWrapper_UIMessageEvent;
            foreach (var table in tables)
            {
                table.RelaseResource();
            }
        }
        /// <summary>
        /// è®¡æ•°æ¸…é›¶
        /// </summary>
        public void ClearNumber()
        {
            foreach (var table in tables)
            {
                table.ClearNumber();
            }
        }
        public void SetProductSource(List<string> productSource)
        {
            foreach (var table in tables)
            {
                table.SetProductSource(productSource);
            }
        }
        public void SetCurrentProduct(string currentProduct)
        {
            for (int i = 0; i < tables.Length; i++)
            {
                var table = tables[i];
                string LookupStr = $"StationHMI.UUTHMI[{i}].Model";
                string tableModel = executionWrapper?.GetStationGlobalValue<string>(LookupStr, 1);
                //engine?.Globals?.GetValString(LookupStr, 1);
                table.SetCurrentProduct(tableModel);
            }
        }
        public void SetOvenProduct(int index, string product)
        {
            string LookupStr = $"StationHMI.UUTHMI[{index}].Model";
            executionWrapper?.SetStationGlobalValue<string>(LookupStr, 1, product);
            executionWrapper?.CommitToDisk(false);
        }
        public void RefreshProducts()
        {
        }
    }
}
Plugin.BurnIn/UI/LiveChart.xaml
New file
@@ -0,0 +1,29 @@
<hb:NotifyControl x:Class="Plugin.BurnIn.LiveChart"
             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.BurnIn"
             mc:Ignorable="d"
             xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
             xmlns:lvc="clr-namespace:LiveChartsCore.SkiaSharpView.WPF;assembly=LiveChartsCore.SkiaSharpView.WPF"
             Loaded="UserControl_Loaded"
             Unloaded="UserControl_Unloaded"
             SizeChanged="UserControl_SizeChanged"
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <lvc:CartesianChart Series="{Binding Series}" BorderThickness="1" BorderBrush="Black"
                            Padding="0"
                            Margin="2"
                            SyncContext="{Binding Sync}"
                            XAxes="{Binding XAxes}"
                            YAxes="{Binding YAxes}"
                            TooltipTextPaint="{Binding TooltipTextPaint}"
                            TooltipTextSize="{Binding TooltipTextSize}"
                            DrawMarginFrame="{Binding DrawMarginFrame}"
                            x:Name="chart">
        </lvc:CartesianChart>
    </Grid>
</hb:NotifyControl>
Plugin.BurnIn/UI/LiveChart.xaml.cs
New file
@@ -0,0 +1,516 @@
using HMI.Base;
using LiveChartsCore;
using LiveChartsCore.Defaults;
using LiveChartsCore.Drawing;
using LiveChartsCore.Drawing.Layouts;
using LiveChartsCore.Kernel.Sketches;
using LiveChartsCore.SkiaSharpView;
using LiveChartsCore.SkiaSharpView.Drawing;
using LiveChartsCore.SkiaSharpView.Drawing.Geometries;
using LiveChartsCore.SkiaSharpView.Drawing.Layouts;
using LiveChartsCore.SkiaSharpView.Painting;
using LiveChartsCore.SkiaSharpView.SKCharts;
using SkiaSharp;
using System.Collections.ObjectModel;
namespace Plugin.BurnIn
{
    public class SeriesConfig
    {
        public static readonly float FontSize = 12;
        /// <summary>
        /// æ•°æ®å
        /// </summary>
        public string Name { get; set; }
        public string YAxesName { get; set; }
        public int YAxesIndex { get; set; }
    }
    public class CustomLegend : SKDefaultLegend
    {
        protected override Layout<SkiaSharpDrawingContext> GetLayout(LiveChartsCore.Chart chart)
        {
            var stackLayout = new StackLayout
            {
                Orientation = ContainerOrientation.Horizontal,
                Padding = new Padding(0, 10, 0, 0),
                HorizontalAlignment = Align.Start,
                VerticalAlignment = Align.Middle,
            };
            foreach (var series in chart.Series.Where(x => x.IsVisibleAtLegend))
            {
                stackLayout.Children.Add(new LegendItem(series));
            }
            return stackLayout;
        }
    }
    public class LegendItem : StackLayout
    {
        public LegendItem(ISeries series)
        {
            Orientation = ContainerOrientation.Horizontal;
            Padding = new Padding(0);
            VerticalAlignment = Align.Start;
            HorizontalAlignment = Align.Middle;
            Opacity = series.IsVisible ? 1 : 0.5f;
            var miniature = (IDrawnElement<SkiaSharpDrawingContext>)series.GetMiniatureGeometry(null);
            Children = new List<IDrawnElement<SkiaSharpDrawingContext>> {
                miniature,
            new LabelGeometry
            {
                Text = series.Name,
                TextSize = SeriesConfig.FontSize,
                Paint = new SolidColorPaint(SKColors.Black){
                    SKTypeface = SKFontManager.Default.MatchCharacter('汉')
                },
                Padding = new Padding(5,0,10,0),
                VerticalAlign = Align.Middle,
                HorizontalAlign = Align.Start
            }};
        }
    }
    public partial class LiveChart : NotifyControl
    {
        private static SKColor[] colors = new SKColor[] {
            //new SKColor(229, 84, 62),
            //new SKColor(72, 143, 239),
            //new SKColor(147, 196, 83),
            new SKColor(255, 0, 0),
            new SKColor(0, 0, 255),
            new SKColor(0, 255, 0),
            new SKColor(76, 184, 210),
            new SKColor(246, 198, 50),
        };
        private DateTime startTime;
        private string configText;
        private bool showLegend;
        private bool enableResize;
        private SolidColorPaint tooltipTextPaint = new SolidColorPaint(SKColors.Black)
        {
            SKTypeface = SKFontManager.Default.MatchCharacter('汉')
        };
        public SolidColorPaint TooltipTextPaint
        {
            get => tooltipTextPaint;
            set
            {
                Set(ref tooltipTextPaint, value);
            }
        }
        private double tooltipTextSize = SeriesConfig.FontSize;
        public double TooltipTextSize
        {
            get => tooltipTextSize;
            set
            {
                Set(ref tooltipTextSize, value);
            }
        }
        private Dictionary<string, SeriesConfig> configCache = new Dictionary<string, SeriesConfig>();
        private Dictionary<string, ISeries> seriesCache = new Dictionary<string, ISeries>();
        private Dictionary<string, List<DateTimePoint>> valueCache = new Dictionary<string, List<DateTimePoint>>();
        private Dictionary<string, double> minValueCache = new Dictionary<string, double>();
        private Dictionary<string, double> maxValueCache = new Dictionary<string, double>();
        private ObservableCollection<ISeries> series = new ObservableCollection<ISeries>();
        public ObservableCollection<ISeries> Series
        {
            get => series;
            set
            {
                Set(ref series, value);
            }
        }
        public ObservableCollection<ICartesianAxis> YAxes { get; set; } = new ObservableCollection<ICartesianAxis>();
        private Axis[] xAxes;
        public Axis[] XAxes
        {
            get => xAxes; set
            {
                Set(ref xAxes, value);
            }
        }
        public DrawMarginFrame DrawMarginFrame => new DrawMarginFrame()
        {
            Fill = new SolidColorPaint(new SKColor(223, 223, 223)),
            Stroke = new SolidColorPaint(new SKColor(180, 180, 180), 1)
        };
        public object Sync { get; } = new object();
        private int CreateYAxis(string name)
        {
            for (int i = 0; i < YAxes.Count; i++)
            {
                if (YAxes[i].Name == name)
                {
                    return i;
                }
            }
            if (YAxes.Count == 0)
            {
                YAxes.Add(new Axis
                {
                    Name = name,
                    MinStep = 1,
                    TextSize = SeriesConfig.FontSize,
                    NameTextSize = SeriesConfig.FontSize,
                    NamePaint = new SolidColorPaint(new SKColor(0, 0, 0))
                    {
                        SKTypeface = SKFontManager.Default.MatchCharacter('汉')
                    },
                    Padding = new Padding(0, 0, 5, 0),
                    LabelsPaint = new SolidColorPaint(new SKColor(0, 0, 0)),
                    SeparatorsPaint = new SolidColorPaint
                    {
                        Color = SKColors.Black.WithAlpha(30),
                        StrokeThickness = 0.5f
                    },
                    TicksPaint = new SolidColorPaint
                    {
                        Color = SKColor.Empty,
                        StrokeThickness = 0
                    },
                });
            }
            else
            {
                YAxes.Add(new Axis
                {
                    Name = name,
                    Position = LiveChartsCore.Measure.AxisPosition.End,
                    MinStep = 1,
                    TextSize = SeriesConfig.FontSize,
                    NameTextSize = SeriesConfig.FontSize,
                    NamePaint = new SolidColorPaint(new SKColor(0, 0, 0))
                    {
                        SKTypeface = SKFontManager.Default.MatchCharacter('汉')
                    },
                    Padding = new Padding(5, 0, 0, 0),
                    LabelsPaint = new SolidColorPaint(new SKColor(0, 0, 0)),
                    TicksPaint = new SolidColorPaint
                    {
                        Color = SKColor.Empty,
                        StrokeThickness = 0
                    },
                    ShowSeparatorLines = false,
                });
            }
            return YAxes.Count - 1;
        }
        public void RefreshConfig(string configStr)
        {
            configText = configStr;
            configCache.Clear();
            seriesCache.Clear();
            valueCache.Clear();
            minValueCache.Clear();
            maxValueCache.Clear();
            Series.Clear();
            YAxes.Clear();
            startTime = DateTime.MinValue;
            try
            {
                string[] arr = configStr.Split(';');
                for (int i = 0; i < arr.Length; i++)
                {
                    var config = arr[i];
                    string[] datas = config.Split(',');
                    var sConfig = new SeriesConfig();
                    if (datas.Length >= 2)
                    {
                        sConfig.YAxesName = datas[1];
                    }
                    else
                    {
                        sConfig.YAxesName = datas[0];
                    }
                    sConfig.Name = datas[0];
                    sConfig.YAxesIndex = CreateYAxis(sConfig.YAxesName);
                    if (!string.IsNullOrEmpty(sConfig.Name))
                    {
                        configCache[sConfig.Name] = sConfig;
                        var values = new List<DateTimePoint>();
                        valueCache[sConfig.Name] = values;
                        var ls = new LineSeries<DateTimePoint>
                        {
                            Name = sConfig.Name,
                            Values = values,
                            ScalesYAt = sConfig.YAxesIndex,
                            Fill = null,
                            GeometryFill = null,
                            GeometryStroke = null,
                            LineSmoothness = 0,
                            Stroke = new SolidColorPaint(GetColor(i)) { StrokeThickness = 0.8f },
                        };
                        seriesCache[sConfig.Name] = ls;
                        Series.Add(ls);
                    }
                }
                if (showLegend)
                {
                    chart.LegendPosition = LiveChartsCore.Measure.LegendPosition.Top;
                    chart.Legend = new CustomLegend();
                }
                chart.TooltipTextPaint = new SolidColorPaint(SKColors.Black)
                {
                    SKTypeface = SKFontManager.Default.MatchCharacter('汉')
                };
            }
            catch (Exception ex)
            {
            }
        }
        public LiveChart(string configStr, bool showLegend = true, bool enableResize = false)
        {
            DataContext = this;
            this.showLegend = showLegend;
            this.enableResize = enableResize;
            var xAxis = new DateTimeAxis(TimeSpan.FromSeconds(10), Formatter)
            {
                Padding = new Padding(0),
                TextSize = SeriesConfig.FontSize,
                NameTextSize = SeriesConfig.FontSize,
                CustomSeparators = GetSeparators(),
                AnimationsSpeed = TimeSpan.FromMilliseconds(0),
                SeparatorsPaint = new SolidColorPaint(SKColors.Black.WithAlpha(30)),
            };
            XAxes = new Axis[] { xAxis };
            InitializeComponent();
            RefreshConfig(configStr);
        }
        private double[] GetSeparators()
        {
            if (valueCache.Count <= 0)
            {
                return new double[] { DateTime.Now.Ticks };
            }
            if (valueCache.Values.First().Count <= 5)
            {
                //return new double[] { now.Ticks };
                return valueCache.Values.First().Select(e => (double)e.DateTime.Ticks).ToArray();
            }
            var now = valueCache.Values.First().Last().DateTime;
            var step = (now - startTime).TotalSeconds / 6;
            return
            new double[] {
                now.AddSeconds(-5*step).Ticks,
                now.AddSeconds(-4*step).Ticks,
                now.AddSeconds(-3*step).Ticks,
                now.AddSeconds(-2*step).Ticks,
                now.AddSeconds(-step).Ticks,
            };
        }
        private static string Formatter(DateTime date)
        {
            //var secsAgo = (DateTime.Now - date).TotalSeconds;
            //return secsAgo < 1
            //    ? "now"
            //    : $"{secsAgo:N0}s ago";
            return date.ToString("HH:mm:ss");
        }
        private void RefreshChart()
        {
            XAxes[0].CustomSeparators = GetSeparators();
        }
        private double GetMaxByName(string name)
        {
            return maxValueCache.ContainsKey(name) ? maxValueCache[name] : double.MinValue;
        }
        private double GetMinByName(string name)
        {
            return minValueCache.ContainsKey(name) ? minValueCache[name] : double.MaxValue;
        }
        //public void AddData(string name, double value)
        //{
        //    var now = DateTime.Now;
        //    if (valueCache.ContainsKey(name))
        //    {
        //        var config = configCache[name];
        //        valueCache[name].Add(new DateTimePoint(now, value));
        //        minValueCache[config.YAxesName] = Math.Min(GetMinByName(config.YAxesName), value);
        //        maxValueCache[config.YAxesName] = Math.Max(GetMaxByName(config.YAxesName), value);
        //        YAxes[config.YAxesIndex].MinLimit = minValueCache[config.YAxesName] - 1;
        //        YAxes[config.YAxesIndex].MaxLimit = maxValueCache[config.YAxesName] + 1;
        //        RefreshChart();
        //    }
        //    if (startTime == DateTime.MinValue)
        //    {
        //        startTime = now;
        //    }
        //}
        private SKColor GetColor(int index)
        {
            if (index >= colors.Length)
            {
                return colors[index % colors.Length];
            }
            return colors[index];
        }
        private void AddNewSeries(string name, string YName)
        {
            var config = new SeriesConfig
            {
                Name = name,
                YAxesName = YName,
                YAxesIndex = CreateYAxis(YName)
            };
            configCache[name] = config;
            var values = new List<DateTimePoint>();
            valueCache[name] = values;
            var ls = new LineSeries<DateTimePoint>
            {
                Name = name,
                Values = values,
                ScalesYAt = config.YAxesIndex,
                Fill = null,
                GeometryFill = null,
                GeometryStroke = null,
                LineSmoothness = 0,
                Stroke = new SolidColorPaint(GetColor(Series.Count)) { StrokeThickness = 0.8f },
            };
            seriesCache[Name] = ls;
            Series.Add(ls);
        }
        public void AddDynamicData(string name, string YName, DateTime now, double value)
        {
            if (!valueCache.ContainsKey(name))
            {
                AddNewSeries(name, YName);
            }
            var config = configCache[name];
            valueCache[name].Add(new DateTimePoint(now, value));
            minValueCache[config.YAxesName] = Math.Min(GetMinByName(config.YAxesName), value);
            maxValueCache[config.YAxesName] = Math.Max(GetMaxByName(config.YAxesName), value);
            YAxes[config.YAxesIndex].MinLimit = minValueCache[config.YAxesName] - 1;
            YAxes[config.YAxesIndex].MaxLimit = maxValueCache[config.YAxesName] + 1;
            RefreshChart();
            if (startTime == DateTime.MinValue)
            {
                startTime = now;
            }
        }
        public void AddData(string name, DateTime now, double value)
        {
            if (valueCache.ContainsKey(name))
            {
                var config = configCache[name];
                valueCache[name].Add(new DateTimePoint(now, value));
                minValueCache[config.YAxesName] = Math.Min(GetMinByName(config.YAxesName), value);
                maxValueCache[config.YAxesName] = Math.Max(GetMaxByName(config.YAxesName), value);
                YAxes[config.YAxesIndex].MinLimit = minValueCache[config.YAxesName] - 1;
                YAxes[config.YAxesIndex].MaxLimit = maxValueCache[config.YAxesName] + 1;
                RefreshChart();
            }
            if (startTime == DateTime.MinValue)
            {
                startTime = now;
            }
        }
        public void Clear()
        {
            RefreshConfig(configText);
        }
        public bool CanHandle(string name)
        {
            return configCache.ContainsKey(name);
        }
        public List<double> GetSingleData(string name)
        {
            if (valueCache.ContainsKey(name))
            {
                return valueCache[name].Select(d => d.Value.Value).ToList();
            }
            return null;
        }
        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 (enableResize)
            {
                var size = FontSizeConverter.CountFontSize(12, ActualHeight, 92);
                foreach (var y in YAxes)
                {
                    y.TextSize = size;
                    y.NameTextSize = size;
                }
                foreach (var x in XAxes)
                {
                    x.TextSize = size;
                    x.NameTextSize = size;
                }
                TooltipTextSize = size;
                //TooltipTextPaint = new SolidColorPaint(SKColors.Black)
                //{
                //    SKTypeface = SKFontManager.Default.MatchCharacter('汉')
                //};
            }
        }
    }
}
Plugin.BurnIn/Util/CommonCodeParser.cs
New file
@@ -0,0 +1,28 @@
using Plugin.BurnIn.Model;
namespace Plugin.BurnIn.Util
{
    public class CommonCodeParser : ICodeParser
    {
        public CellInfo GetCellInfoByCode(string code)
        {
            string[] arr = code.Split('-');
            if (arr == null || arr.Length != 3)
            {
                throw new Exception("扫码格式异常");
            }
            return new CellInfo()
            {
                OriginCode = code,
                MachineName = arr[0],
                OvenName = arr[1],
                Index = int.Parse(arr[2]) - 1,
            };
        }
        public CellInfo GetCellInfoByCode(string model, int columns, int rows, string code)
        {
            return GetCellInfoByCode(code);
        }
    }
}
Plugin.BurnIn/Util/DpUtil.cs
New file
@@ -0,0 +1,220 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
namespace Plugin.BurnIn
{
    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));
        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 = "None";
            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 = "None";
                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.BurnIn/Util/FontSizeConverter.cs
New file
@@ -0,0 +1,32 @@
using System.Globalization;
using System.Windows.Data;
namespace Plugin.BurnIn
{
    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.BurnIn/Util/ICodeParser.cs
New file
@@ -0,0 +1,10 @@
using Plugin.BurnIn.Model;
namespace Plugin.BurnIn.Util
{
    public interface ICodeParser
    {
        CellInfo GetCellInfoByCode(string code);
        CellInfo GetCellInfoByCode(string model,int columns,int rows, string code);
    }
}
Plugin.BurnIn/Util/JKCodeParser.cs
New file
@@ -0,0 +1,30 @@
using Plugin.BurnIn.Model;
namespace Plugin.BurnIn.Util
{
    public class JKCodeParser : ICodeParser
    {
        public CellInfo GetCellInfoByCode(string code)
        {
            string[] arr = code.Split('-');
            if (arr == null || arr.Length != 3)
            {
                throw new Exception("扫码格式异常");
            }
            int row = int.Parse(arr[1]);
            int index = int.Parse(arr[2]);
            return new CellInfo()
            {
                OriginCode = code,
                OvenName = arr[0],
                Index = (row - 1) * 4 + (index - 1),
            };
        }
        public CellInfo GetCellInfoByCode(string model, int columns, int rows, string code)
        {
            return GetCellInfoByCode(code);
        }
    }
}
Plugin.BurnIn/Util/ModelCodeParser.cs
New file
@@ -0,0 +1,97 @@
using HMI.Core.Entity;
using HMI.Core.Manager;
using Plugin.BurnIn.Model;
namespace Plugin.BurnIn.Util
{
    public class ModelCodeParser : ICodeParser
    {
        public CellInfo GetCellInfoByCode(string code)
        {
            string[] arr = code.Split('-');
            if (arr == null || arr.Length != 3)
            {
                throw new Exception("扫码格式异常");
            }
            return new CellInfo()
            {
                OriginCode = code,
                MachineName = arr[0],
                OvenName = arr[1],
                Index = int.Parse(arr[2]) - 1,
            };
        }
        public CellInfo GetCellInfoByCode(string model, int columns, int rows, string code)
        {
            var list = ProductManager.GetParametersByProductName(model);
            ParameterData config = null;
            if (list != null && list.Count > 0)
            {
                config = list.Where(e => e.Name == "PositionCodeConfig").FirstOrDefault();
            }
            if (string.IsNullOrEmpty(config?.Value))
            {
                // äº§å“æ²¡æœ‰å°±æŒ‰é»˜è®¤çš„去配
                config = ParameterManager.GetCommonParameterByName("PositionCodeConfig");
                if (string.IsNullOrEmpty(config?.Value))
                {
                    throw new Exception($"当前型号[{model}]未配置穴位规则");
                }
            }
            string value = config.Value;
            string[] arr = value?.Split(';');
            try
            {
                if (arr.Length == 2)
                {
                    string[] v1s = arr[0].Split(',');
                    string[] v2s = arr[1].Split(',');
                    return new CellInfo
                    {
                        OriginCode = code,
                        OvenName = code.Substring(int.Parse(v1s[0]), int.Parse(v1s[1])),
                        Index = int.Parse(code.Substring(int.Parse(v2s[0]), int.Parse(v2s[1]))) - 1
                    };
                }
                else if (arr.Length == 3)
                {
                    string[] v1s = arr[0].Split(',');
                    string[] v2s = arr[1].Split(',');
                    string[] v3s = arr[2].Split(',');
                    int row = int.Parse(code.Substring(int.Parse(v2s[0]), int.Parse(v2s[1])));
                    int index = int.Parse(code.Substring(int.Parse(v3s[0]), int.Parse(v3s[1])));
                    return new CellInfo()
                    {
                        OriginCode = code,
                        OvenName = code.Substring(int.Parse(v1s[0]), int.Parse(v1s[1])),
                        Index = (row - 1) * columns + (index - 1),
                    };
                }
                else if (arr.Length >= 4)
                {
                    string[] v1s = arr[0].Split(',');
                    string[] v2s = arr[1].Split(',');
                    string[] v3s = arr[2].Split(',');
                    columns = int.Parse(arr[3]);
                    int row = int.Parse(code.Substring(int.Parse(v2s[0]), int.Parse(v2s[1])));
                    int index = int.Parse(code.Substring(int.Parse(v3s[0]), int.Parse(v3s[1])));
                    return new CellInfo()
                    {
                        OriginCode = code,
                        OvenName = code.Substring(int.Parse(v1s[0]), int.Parse(v1s[1])),
                        Index = (row - 1) * columns + (index - 1),
                    };
                }
                throw new Exception($"当前型号[{model}]穴位规则配置异常");
            }
            catch (Exception ex)
            {
                throw new Exception($"当前型号[{model}]解析异常:{ex.Message}", ex);
            }
        }
    }
}
Plugin.BurnIn/Util/PNUtil.cs
New file
@@ -0,0 +1,106 @@
using HMI.Core.Util;
using log4net;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Net.Http;
using System.Text;
namespace Plugin.BurnIn.Util
{
    public class HttpResult
    {
        public int code { get; set; }
        public string msg { get; set; }
        public string ipn { get; set; }
    }
    public class PNUtil
    {
        public static bool GetPN(string mesAddress, string stationName, string sn, out string pn, out string msg)
        {
            pn = null;
            msg = string.Empty;
            try
            {
                //OP170Burn-In老化
                string json = $"{{\"csn\":\"{sn}\",\"station_name\":\"{stationName}\"}}";
                HttpClient val = new HttpClient();
                BurnInUI.Logger.Debug($"Get PN Send: [{json}]");
                try
                {
                    StringContent val2 = new StringContent(json, Encoding.UTF8, "application/json");
                    HttpResponseMessage result = val.PostAsync($"http://{mesAddress}/mes-module-system-2.1.1/lixun/api/getPn", (HttpContent)(object)val2).Result;
                    result.EnsureSuccessStatusCode();
                    string resJson = result.Content.ReadAsStringAsync().Result;
                    HttpResult httpResult = JsonUtil.Deserialize<HttpResult>(resJson);
                    BurnInUI.Logger.Debug($"Get PN Receive: [{resJson}]");
                    if (httpResult.code == 200)
                    {
                        pn = httpResult.ipn;
                        return true;
                    }
                    msg = httpResult.msg;
                    return false;
                }
                finally
                {
                    ((IDisposable)val)?.Dispose();
                }
            }
            catch (Exception ex)
            {
                BurnInUI.Logger.Debug($"Get PN Error.", ex);
                msg = $"请求PN异常!{ex.Message}";
                return false;
            }
        }
        public static bool CheckIn(ILog log, string mesAddress, string stationName, string sn, out bool needCheckIn, out string msg)
        {
            JObject data = new JObject();
            data.Add("cmd", "QUERY_RECORD");
            data.Add("p", "UOP");
            data.Add("sn", sn);
            data.Add("station_name", stationName);
            data.Add("op", "A0000001");
            needCheckIn = false;
            msg = string.Empty;
            string sendData = string.Empty;
            string resData = null;
            try
            {
                sendData = data.ToString(Formatting.None);
                using (HttpClient val = new HttpClient())
                {
                    StringContent val2 = new StringContent(sendData, Encoding.UTF8, "application/json");
                    HttpResponseMessage result = val.PostAsync("http://" + mesAddress + "/mes-module-system-2.1.1/lixun/api/checkIn", (HttpContent)(object)val2).Result;
                    result.EnsureSuccessStatusCode();
                    resData = result.Content.ReadAsStringAsync().Result;
                    JObject httpResult = JObject.Parse(resData);
                    log.Info("CheckIn Send: " + httpResult.Value<string>("sendStr"));
                    log.Info("CheckIn Receive: " + httpResult.Value<string>("receiveStr"));
                    log.Info("CheckIn Get: " + resData);
                    msg = httpResult.Value<string>("msg");
                    if (httpResult.Value<int>("code") == 200)
                    {
                        needCheckIn = httpResult.Value<bool>("needCheckIn");
                        return true;
                    }
                    log.Error("CheckIn Error: " + msg);
                    return false;
                }
            }
            catch (Exception ex)
            {
                msg = ex.Message + " in " + ex.StackTrace + ".SendStr: " + sendData + ",ResultStr: " + resData;
                log.Error("CheckIn Unknow Error: " + msg);
                return false;
            }
        }
    }
}
Plugin.BurnIn/Util/RunTimeVariablePool.cs
New file
@@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace Plugin.BurnIn.Util
{
    public class RunTimeVariablePool
    {
        public static readonly string DEFAULT_PLC_NAME = "BurnInControler";
        public static readonly Dictionary<string, string> CurrentOvenModel = new Dictionary<string, string>();
    }
}
Plugin.BurnIn/Util/SNValidator.cs
New file
@@ -0,0 +1,86 @@
using HMI.Core.Manager;
namespace Plugin.BurnIn.Util
{
    public class SNValidator
    {
        public static bool ValidateSN(string model, string sn, out string msg)
        {
            msg = string.Empty;
            string validate = string.Empty;
            try
            {
                validate = ProductManager.GetParameterByProductAndName(model, "SNRValidate")?.Value;
                if (string.IsNullOrEmpty(validate))
                {
                    msg = $"当前型号[{model}]未配置SNRValidate参数,无法校验SN合法性";
                    return true;
                }
                string[] arr = validate.Split(';');
                if (arr.Length == 1)
                {
                    if (!sn.Contains(arr[0]))
                    {
                        msg = $"条码不包含固定码[{arr[0]}]";
                        return false;
                    }
                    return true;
                }
                if (arr.Length == 2)
                {
                    int length = int.Parse(arr[1]);
                    if (!sn.Contains(arr[0]))
                    {
                        msg = $"条码不包含固定码[{arr[0]}]";
                        return false;
                    }
                    if (sn.Length != length)
                    {
                        msg = $"条码长度应为[{length}]";
                        return false;
                    }
                    return true;
                }
                if (arr.Length >= 3)
                {
                    int length = int.Parse(arr[1]);
                    int startIndex = int.Parse(arr[2]);
                    if (sn.Length != length)
                    {
                        msg = $"条码长度应为[{length}]";
                        return false;
                    }
                    if (startIndex >= sn.Length)
                    {
                        msg = $"条码长读小于起始下标[{validate}]";
                        return false;
                    }
                    if (arr[0].Length > sn.Length)
                    {
                        msg = $"条码长读小于固定码长度[{validate}]";
                        return false;
                    }
                    if (sn.Substring(startIndex, arr[0].Length) != arr[0])
                    {
                        msg = $"固定码检查失败: [{validate}]";
                        return false;
                    }
                    return true;
                }
                msg = $"当前型号[{model}]配置参数[{validate}]解析失败";
                return false;
            }
            catch (Exception ex)
            {
                msg = $"当前型号[{model}]配置参数[{validate}]解析失败";
                return false;
            }
        }
    }
}
Plugin.BurnIn/Util/ScannerParser.cs
New file
@@ -0,0 +1,30 @@
using Plugin.BurnIn.Model;
namespace Plugin.BurnIn.Util
{
    public class ScannerParser
    {
        public static ICodeParser parser;
        public static void Init(string code)
        {
            if (code == "JK01")
            {
                parser = new JKCodeParser();
            }
            else if (code == "Model")
            {
                parser = new ModelCodeParser();
            }
            else
            {
                parser = new CommonCodeParser();
            }
        }
        public static CellInfo GetCellInfoByCode(string model, int columns, int rows, string code)
        {
            return parser?.GetCellInfoByCode(model, columns, rows, code);
        }
    }
}
Plugin.DataBoard/DataBoard.xaml
@@ -11,6 +11,7 @@
         xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
                  xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
             mc:Ignorable="d" 
             xmlns:lvc="clr-namespace:LiveChartsCore.SkiaSharpView.WPF;assembly=LiveChartsCore.SkiaSharpView.WPF"
                  Loaded="PluginControl_Loaded"
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
@@ -130,10 +131,54 @@
        Content="{DynamicResource bd.btn}"/>
            </Grid>
            <wv2:WebView2 Grid.Row="3" Grid.ColumnSpan="4" Name = "webview2"
            <lvc:CartesianChart Grid.Row="3" Background="#FFF" BorderThickness="1" BorderBrush="{DynamicResource CommonBoarder}"
                    Title="{Binding Chart0Title}"
                    TooltipTextPaint="{Binding TooltipTextPaint}"
                    Padding="0"
                    Margin="2"
                    Series="{Binding Chart0_series}"
                    XAxes="{Binding Chart0_xAxes}"
                    YAxes="{Binding Chart0_yAxes}"
                    x:Name="chart0">
            </lvc:CartesianChart>
            <lvc:PieChart Grid.Row="3" Grid.Column="1" Background="#FFF" BorderThickness="1" BorderBrush="{DynamicResource CommonBoarder}"
        Title="{Binding Chart1Title}"
        TooltipTextPaint="{Binding TooltipTextPaint}"
        Padding="0"
        Margin="2"
        Series="{Binding Chart1_series}"
        LegendPosition="Top"
        x:Name="chart1">
            </lvc:PieChart>
            <lvc:CartesianChart Grid.Row="3" Grid.Column="2" Background="#FFF" BorderThickness="1" BorderBrush="{DynamicResource CommonBoarder}"
        Title="{Binding Chart2Title}"
        TooltipTextPaint="{Binding TooltipTextPaint}"
        Padding="0"
        Margin="2"
        Series="{Binding Chart2_series}"
        XAxes="{Binding Chart2_xAxes}"
        YAxes="{Binding Chart2_yAxes}"
        LegendPosition="Top"
        x:Name="chart2">
            </lvc:CartesianChart>
            <lvc:CartesianChart Grid.Row="3" Grid.Column="3" Background="#FFF" BorderThickness="1" BorderBrush="{DynamicResource CommonBoarder}"
Title="{Binding Chart3Title}"
TooltipTextPaint="{Binding TooltipTextPaint}"
Padding="0"
Margin="2"
Series="{Binding Chart3_series}"
XAxes="{Binding Chart3_xAxes}"
YAxes="{Binding Chart3_yAxes}"
x:Name="chart3">
            </lvc:CartesianChart>
            <!--<wv2:WebView2 Grid.Row="3" Grid.ColumnSpan="4" Name="webview2"
                      SizeChanged="webview2_SizeChanged"
                      NavigationCompleted="Webview2_NavigationCompleted"
                      Loaded="webview2_Loaded"/>
                      Loaded="webview2_Loaded"/>-->
        </Grid>
    </Border>
</hb:PluginControl>
Plugin.DataBoard/DataBoard.xaml.cs
@@ -5,12 +5,23 @@
using HMI.Core.Util;
using HMI.UI.Util;
using HMI.Util;
using LiveChartsCore;
using LiveChartsCore.Drawing;
using LiveChartsCore.Drawing.Layouts;
using LiveChartsCore.Kernel.Sketches;
using LiveChartsCore.SkiaSharpView;
using LiveChartsCore.SkiaSharpView.Drawing;
using LiveChartsCore.SkiaSharpView.Drawing.Geometries;
using LiveChartsCore.SkiaSharpView.Drawing.Layouts;
using LiveChartsCore.SkiaSharpView.Painting;
using LiveChartsCore.SkiaSharpView.SKCharts;
using LiveChartsCore.SkiaSharpView.VisualElements;
using Microsoft.Web.WebView2.Core;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@@ -19,6 +30,54 @@
namespace Plugin.DataBoard
{
    public class CustomLegend : SKDefaultLegend
    {
        protected override Layout<SkiaSharpDrawingContext> GetLayout(LiveChartsCore.Chart chart)
        {
            var stackLayout = new StackLayout
            {
                Orientation = ContainerOrientation.Horizontal,
                Padding = new Padding(0, 10, 0, 0),
                HorizontalAlignment = Align.Start,
                VerticalAlignment = Align.Middle,
            };
            foreach (var series in chart.Series.Where(x => x.IsVisibleAtLegend))
            {
                stackLayout.Children.Add(new LegendItem(series));
            }
            return stackLayout;
        }
    }
    public class LegendItem : StackLayout
    {
        public LegendItem(ISeries series)
        {
            Orientation = ContainerOrientation.Horizontal;
            Padding = new Padding(0);
            VerticalAlignment = Align.Start;
            HorizontalAlignment = Align.Middle;
            Opacity = series.IsVisible ? 1 : 0.5f;
            var miniature = (IDrawnElement<SkiaSharpDrawingContext>)series.GetMiniatureGeometry(null);
            Children = new List<IDrawnElement<SkiaSharpDrawingContext>> {
                miniature,
            new LabelGeometry
            {
                Text = series.Name,
                TextSize = 12,
                Paint = new SolidColorPaint(SKColors.Black){
                    SKTypeface = SKFontManager.Default.MatchCharacter('汉')
                },
                Padding = new Padding(5,0,10,0),
                VerticalAlign = Align.Middle,
                HorizontalAlign = Align.Start
            }};
        }
    }
    /// <summary>
    /// UserControl1.xaml çš„交互逻辑
    /// </summary>
@@ -28,8 +87,308 @@
        {
            DataContext = this;
            InitializeComponent();
            webview2.DefaultBackgroundColor = Color.Transparent;//Color.FromArgb(243, 249, 255);
            //webview2.DefaultBackgroundColor = Color.Transparent;//Color.FromArgb(243, 249, 255);
        }
        private SolidColorPaint tooltipTextPaint = new SolidColorPaint(SKColors.Black)
        {
            SKTypeface = SKFontManager.Default.MatchCharacter('汉')
        };
        public SolidColorPaint TooltipTextPaint
        {
            get => tooltipTextPaint;
            set
            {
                Set(ref tooltipTextPaint, value);
            }
        }
        #region chart0
        public LabelVisual Chart0Title { get; set; } = new LabelVisual
       {
           Text = "投入产出统计",
           TextSize = 20,
           Paint = new SolidColorPaint(new SKColor(0, 0, 0))
           {
               SKTypeface = SKFontManager.Default.MatchCharacter('汉')
           },
           Padding = new LiveChartsCore.Drawing.Padding(15)
       };
        private ISeries[] chart0_series = new ISeries[] {
            new ColumnSeries<int> {
                Fill= new SolidColorPaint(new SKColor(34, 68, 139)),
                DataLabelsPaint  = new SolidColorPaint(new SKColor(0, 0, 0)),
                DataLabelsPosition = LiveChartsCore.Measure.DataLabelsPosition.Top,
                Values = new int[]{ 0,0} }
        };
        public ISeries[] Chart0_series
        {
            get => chart0_series;
            set
            {
                Set(ref chart0_series, value);
            }
        }
        private ObservableCollection<Axis> chart0_yAxes = new ObservableCollection<Axis> {
            new Axis
            {
                MinStep = 1,
                MinLimit = 0,
                LabelsPaint = new SolidColorPaint(new SKColor(0, 0, 0)),
                SeparatorsPaint = new SolidColorPaint
                {
                    Color = SKColors.Black.WithAlpha(30),
                    StrokeThickness = 0.5f
                },
                TicksPaint = new SolidColorPaint
                {
                    Color = SKColor.Empty,
                    StrokeThickness = 0
                },
            }
        };
        public ObservableCollection<Axis> Chart0_yAxes
        {
            get => chart0_yAxes;
            set
            {
                Set(ref chart0_yAxes, value);
            }
        }
        private Axis[] chart0_xAxes = new Axis[] {
            new Axis
        {
            Labels = new string[]{"投入", "产出" },
            LabelsPaint=new SolidColorPaint(new SKColor(0, 0, 0))
            {
                SKTypeface = SKFontManager.Default.MatchCharacter('汉')
            },
            LabelsRotation = 0,
            SeparatorsAtCenter = false,
            TicksPaint = new SolidColorPaint(new SKColor(35, 35, 35)),
            TicksAtCenter = true,
            ForceStepToMin = true,
            MinStep = 1
        }
        };
        public Axis[] Chart0_xAxes
        {
            get => chart0_xAxes;
            set
            {
                Set(ref chart0_xAxes, value);
            }
        }
        #endregion
        #region chart1
        public LabelVisual Chart1Title { get; set; } = new LabelVisual
        {
            Text = "产品状态统计",
            TextSize = 20,
            Paint = new SolidColorPaint(new SKColor(0, 0, 0))
            {
                SKTypeface = SKFontManager.Default.MatchCharacter('汉')
            },
            Padding = new LiveChartsCore.Drawing.Padding(15)
        };
        public ISeries[] Chart1_series { get; set; } = new PieSeries<int>[]
        {
            new PieSeries<int> {
                Name="Passed",
                Values = new int[]{ 0},
            DataLabelsPosition = LiveChartsCore.Measure.PolarLabelsPosition.Outer,
             DataLabelsSize = 15,
             DataLabelsPaint = new SolidColorPaint(new SKColor(30, 30, 30)){
                 SKTypeface = SKFontManager.Default.MatchCharacter('汉')
             },
             Fill = new SolidColorPaint(new SKColor(0, 128, 0)),
             DataLabelsFormatter =
                point =>point.StackedValue.Total>0?
                    $"Passed\r\n计数:{point.Coordinate.PrimaryValue}\r\n占比:{point.StackedValue.Share:P2}":"",
             ToolTipLabelFormatter = point => $"{point.StackedValue.Share:P2}"
            },
            new PieSeries<int> { Values = new int[]{ 0} ,
                Name="Failed",
            DataLabelsPosition = LiveChartsCore.Measure.PolarLabelsPosition.Outer,
             DataLabelsSize = 15,
             DataLabelsPaint = new SolidColorPaint(new SKColor(30, 30, 30)){
                 SKTypeface = SKFontManager.Default.MatchCharacter('汉')
             },
             Fill = new SolidColorPaint(new SKColor(255, 0, 0)),
             DataLabelsFormatter =
                point =>point.StackedValue.Total>0?
                    $"Failed\r\n计数:{point.Coordinate.PrimaryValue}\r\n占比:{point.StackedValue.Share:P2}":"",
             ToolTipLabelFormatter = point => $"{point.StackedValue.Share:P2}"},
        };
        #endregion
        #region chart2
        public LabelVisual Chart2Title { get; set; } = new LabelVisual
        {
            Text = "良率统计",
            TextSize = 20,
            Paint = new SolidColorPaint(new SKColor(0, 0, 0))
            {
                SKTypeface = SKFontManager.Default.MatchCharacter('汉')
            },
            Padding = new LiveChartsCore.Drawing.Padding(15)
        };
        public ISeries[] Chart2_series { get; set; } = new ISeries[]
        {
            new LineSeries<double> {
                Name="直通率",
                Values = new double[]{ 0},
                DataLabelsPosition = LiveChartsCore.Measure.DataLabelsPosition.Top,
                DataLabelsFormatter =
                    point => $"直通率:{point.Coordinate.PrimaryValue:f2}%",
                DataLabelsSize = 15,
                DataLabelsPaint = new SolidColorPaint(new SKColor(30, 30, 30)){
                    SKTypeface = SKFontManager.Default.MatchCharacter('汉')
                },
                Stroke = new SolidColorPaint(new SKColor(255,0,0)){
                    StrokeThickness = 2
                },
                Fill = null,
                GeometryStroke = new SolidColorPaint(new SKColor(255,0,0)){
                    StrokeThickness = 2
                },
                GeometrySize=5
            },
            new LineSeries<double> {
                Name="良率",
                Values = new double[]{ 0},
            DataLabelsPosition = LiveChartsCore.Measure.DataLabelsPosition.Bottom,
            DataLabelsFormatter =
                point => $"良率:{point.Coordinate.PrimaryValue:f2}%",
             DataLabelsSize = 10,
             DataLabelsPaint = new SolidColorPaint(new SKColor(30, 30, 30)){
                 SKTypeface = SKFontManager.Default.MatchCharacter('汉')
             },
                Stroke = new SolidColorPaint(new SKColor(34, 68, 139)){
                    StrokeThickness = 2
                },
                GeometryStroke = new SolidColorPaint(new SKColor(34, 68, 139)){
                    StrokeThickness = 2
                },
             Fill = null,
            },
        };
        private ObservableCollection<Axis> chart2_yAxes = new ObservableCollection<Axis>  {
            new Axis
            {
                LabelsPaint = new SolidColorPaint(new SKColor(0, 0, 0)),
                SeparatorsPaint = new SolidColorPaint
                {
                    Color = SKColors.Black.WithAlpha(30),
                    StrokeThickness = 0.5f
                },
                TicksPaint = new SolidColorPaint
                {
                    Color = SKColor.Empty,
                    StrokeThickness = 0
                },
                MinLimit = 0
            }
        };
        public ObservableCollection<Axis> Chart2_yAxes
        {
            get => chart2_yAxes;
            set
            {
                Set(ref chart2_yAxes, value);
            }
        }
        private ObservableCollection<ICartesianAxis> chart2_xAxes = new ObservableCollection<ICartesianAxis> {
            new Axis
            {
            Labels = new string[]{"" },
            }
        };
        public ObservableCollection<ICartesianAxis> Chart2_xAxes {
            get => chart2_xAxes;
            set {
                Set(ref chart2_xAxes, value);
            }
        }
        #endregion
        #region chart3
        public LabelVisual Chart3Title { get; set; } = new LabelVisual
        {
            Text = "TOP10不良统计",
            TextSize = 20,
            Paint = new SolidColorPaint(new SKColor(0, 0, 0))
            {
                SKTypeface = SKFontManager.Default.MatchCharacter('汉')
            },
            Padding = new LiveChartsCore.Drawing.Padding(15)
        };
        public ISeries[] Chart3_series { get; set; } = new ISeries[]
        {
            new RowSeries<SimpleData> {
                Values = new SimpleData[]{},
                DataLabelsPosition = LiveChartsCore.Measure.DataLabelsPosition.Left,
                DataLabelsFormatter =
                    point => $"{point.Model.Name}",
                DataLabelsSize = 15,
                DataLabelsPadding = new Padding(30,0,0,0),
                DataLabelsTranslate = new LvcPoint(1, 0),
                // DataLabelsTranslate = new LvcPoint(-1, 0),
                DataLabelsPaint = new SolidColorPaint(new SKColor(255,0,0)){
                    SKTypeface = SKFontManager.Default.MatchCharacter('汉')
                },
                YToolTipLabelFormatter = point => $"{point.Model.Name} {point.Model.Value}"
            },
        };
        public ObservableCollection<Axis> chart3_yAxes = new ObservableCollection<Axis>  {
            new Axis
            {
                IsVisible = false
            }
        };
        public ObservableCollection<Axis> Chart3_yAxes
        {
            get => chart3_yAxes;
            set
            {
                Set(ref chart3_yAxes, value);
            }
        }
        private ObservableCollection<ICartesianAxis> chart3_xAxes = new ObservableCollection<ICartesianAxis> {
            new Axis { SeparatorsPaint = new SolidColorPaint(new SKColor(220, 220, 220)) }
        };
        public ObservableCollection<ICartesianAxis> Chart3_xAxes
        {
            get => chart3_xAxes;
            set
            {
                Set(ref chart3_xAxes, value);
            }
        }
        #endregion
        private ObservableCollection<OptionData> _options = new ObservableCollection<OptionData> {
            new OptionData { Name = LangUtil.Get("bd.today"), Value = "today" },
@@ -49,6 +408,7 @@
        private string DataFolder;
        public override void InitFinish()
        {
            chart2.Legend = new CustomLegend();
            iniFile = Path.Combine(BaseFolder, "Config.ini");
            if (!File.Exists(iniFile))
            {
@@ -60,7 +420,10 @@
        private void PluginControl_Loaded(object sender, RoutedEventArgs e)
        {
            cb0.SelectedIndex = 0;
            cb1.SelectedIndex = 0;
            cb2.SelectedIndex = 0;
            cb3.SelectedIndex = 0;
        }
        private void LoadProduct()
@@ -223,7 +586,7 @@
                segment = $"AND model = '{model}'";
            }
            string sql = $@"SELECT * FROM testResult WHERE time BETWEEN '{start:yyyy-MM-dd HH:mm:ss}' AND '{end:yyyy-MM-dd HH:mm:ss}' {segment};";
            string sql = $@"SELECT * FROM testResult WHERE time BETWEEN '{start:yyyy-MM-dd HH:mm:ss}' AND '{end:yyyy-MM-dd HH:mm:ss}' {segment} Order BY id DESC;";
            DataTable dt = new DataTable();
            foreach (string file in files)
@@ -240,22 +603,23 @@
        private void webview2_Loaded(object sender, RoutedEventArgs e)
        {
            string path = Path.Combine(PathHelper.AppFolder, "Libs", "Static", "test.html");
            webview2.Source = new Uri(path);
            //webview2.EnsureCoreWebView2Async();
            // webview2.Source = new Uri(path);
        }
        private void Webview2_NavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e)
        {
            cb0.SelectedIndex = 0;
            cb1.SelectedIndex = 0;
            cb2.SelectedIndex = 0;
            cb3.SelectedIndex = 0;
            //cb0.SelectedIndex = 0;
            //cb1.SelectedIndex = 0;
            //cb2.SelectedIndex = 0;
            //cb3.SelectedIndex = 0;
        }
        private void webview2_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            var width = this.webview2.ActualWidth - 100;
            var height = this.webview2.ActualHeight;
            webview2.CoreWebView2?.ExecuteScriptAsync($"resize({width},{height})");
            //var width = this.webview2.ActualWidth - 100;
            //var height = this.webview2.ActualHeight;
            //webview2.CoreWebView2?.ExecuteScriptAsync($"resize({width},{height})");
        }
        //private void RefreshChart0(DateTime start, DateTime end)
@@ -305,7 +669,7 @@
                {
                    Dispatcher.BeginInvoke(new Action(() =>
                    {
                        webview2.CoreWebView2?.ExecuteScriptAsync($"loading0('加载中,请稍后...')");
                        // webview2.CoreWebView2?.ExecuteScriptAsync($"loading0('加载中,请稍后...')");
                    }));
                    List<SerialData> datas = SelectResultDataByTime(model, start, end);
                    datas.OrderByDescending(e => e.time);
@@ -321,7 +685,8 @@
                    var fields = $"['{this.lang("bd.input")}','{this.lang("bd.output")}']";
                    Dispatcher.BeginInvoke(new Action(() =>
                    {
                        webview2.CoreWebView2?.ExecuteScriptAsync($"drawChart0({fields}, [{all},{output}])");
                        Chart0_series[0].Values = new int[] { all, output };
                        //webview2.CoreWebView2?.ExecuteScriptAsync($"drawChart0({fields}, [{all},{output}])");
                    }));
                }
                catch (Exception ex)
@@ -343,12 +708,12 @@
                {
                    Dispatcher.BeginInvoke(new Action(() =>
                    {
                        webview2.CoreWebView2?.ExecuteScriptAsync($"loading1('加载中,请稍后...')");
                        //webview2.CoreWebView2?.ExecuteScriptAsync($"loading1('加载中,请稍后...')");
                    }));
                    List<SerialData> datas = SelectResultDataByTime(model, start, end);
                    datas = datas.OrderByDescending(e => e.time).ToList();
                    Dictionary<string, int?> pieData = new Dictionary<string, int?> {
                    Dictionary<string, int> pieData = new Dictionary<string, int> {
                { "Passed",0},
                { "Failed",0},
                //{ "Action",0},
@@ -380,7 +745,9 @@
                    res += "]";
                    Dispatcher.BeginInvoke(new Action(() =>
                    {
                        webview2.CoreWebView2?.ExecuteScriptAsync($"drawChart1({res})");
                        Chart1_series[0].Values = new int[] { pieData["Passed"] };
                        Chart1_series[1].Values = new int[] { pieData["Failed"] };
                        // webview2.CoreWebView2?.ExecuteScriptAsync($"drawChart1({res})");
                    }));
                }
                catch (Exception ex)
@@ -468,7 +835,7 @@
                {
                    Dispatcher.BeginInvoke(new Action(() =>
                    {
                        webview2.CoreWebView2?.ExecuteScriptAsync($"loading2('加载中,请稍后...')");
                       // webview2.CoreWebView2?.ExecuteScriptAsync($"loading2('加载中,请稍后...')");
                    }));
                    List<SerialData> datas = SelectResultDataByTime(model, start, end);
@@ -513,20 +880,46 @@
                        });
                    }
                    string fields = "[", vals0 = "[", vals1 = "[";
                    foreach (var key in res.Keys)
                    {
                        fields += $"'{key}',";
                        vals0 += $"{res[key].onePassRate},";
                        vals1 += $"{res[key].passRate},";
                    }
                    fields += "]";
                    vals0 += "]";
                    vals1 += "]";
                    //string fields = "[", vals0 = "[", vals1 = "[";
                    //foreach (var key in res.Keys)
                    //{
                    //    fields += $"'{key}',";
                    //    vals0 += $"{res[key].onePassRate},";
                    //    vals1 += $"{res[key].passRate},";
                    //}
                    //fields += "]";
                    //vals0 += "]";
                    //vals1 += "]";
                    var names = $"['{this.lang("bd.onePassRate")}','{this.lang("bd.passRate")}']";
                    Dispatcher.BeginInvoke(new Action(() =>
                    {
                        webview2.CoreWebView2?.ExecuteScriptAsync($"drawChart2({fields},{names},{vals0},{vals1})");
                        if (res.Count == 0)
                        {
                            res[DateTime.Now.ToString("yyyy-MM-dd")] = new PassRateData
                            {
                                onePassRate = 0,
                                passRate = 0
                            };
                        }
                        string[] keys = new string[res.Count];
                        double[] val0 = new double[res.Count];
                        double[] val1 = new double[res.Count];
                        int i = 0;
                        foreach (var key in res.Keys)
                        {
                            keys[i] = key;
                            val0[i] = res[key].onePassRate;
                            val1[i] = res[key].passRate;
                            i++;
                        }
                        Chart2_xAxes[0] = new Axis
                        {
                            Labels = keys,
                        };
                        Chart2_series[0].Values = val0;
                        Chart2_series[1].Values = val1;
                        //webview2.CoreWebView2?.ExecuteScriptAsync($"drawChart2({fields},{names},{vals0},{vals1})");
                    }));
                }
                catch (Exception ex)
@@ -548,7 +941,7 @@
                {
                    Dispatcher.BeginInvoke(new Action(() =>
                    {
                        webview2.CoreWebView2?.ExecuteScriptAsync($"loading3('加载中,请稍后...')");
                        //webview2.CoreWebView2?.ExecuteScriptAsync($"loading3('加载中,请稍后...')");
                    }));
                    var st = DateTime.Now;
                    List<SerialData> datas = SelectSerialDataByTime(model, start, end);
@@ -585,15 +978,16 @@
                    var s3 = DateTime.Now;
                    log.Info($"RefreshChart3 Build {(s3 - s2).TotalMilliseconds}");
                    string names = string.Empty, vals = string.Empty;
                    foreach (var data in datasource)
                    {
                        names += $"'{data.Name}',";
                        vals += $"{data.Value},";
                    }
                    //string names = string.Empty, vals = string.Empty;
                    //foreach (var data in datasource)
                    //{
                    //    names += $"'{data.Name}',";
                    //    vals += $"{data.Value},";
                    //}
                    Dispatcher.BeginInvoke(new Action(() =>
                    {
                        webview2.CoreWebView2?.ExecuteScriptAsync($"drawChart3([{names}],[{vals}])");
                        Chart3_series[0].Values = datasource.OrderBy(x => x.Value).ToList(); ;
                        //webview2.CoreWebView2?.ExecuteScriptAsync($"drawChart3([{names}],[{vals}])");
                    }));
                }
                catch (Exception ex)
Plugin.DataBoard/Plugin.DataBoard.csproj
@@ -52,8 +52,23 @@
    <ErrorReport>prompt</ErrorReport>
  </PropertyGroup>
  <ItemGroup>
    <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="LiveChartsCore, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc92a3e13d40c573, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\LiveChartsCore.2.0.0-rc5.4\lib\net462\LiveChartsCore.dll</HintPath>
    </Reference>
    <Reference Include="LiveChartsCore.SkiaSharpView, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc92a3e13d40c573, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\LiveChartsCore.SkiaSharpView.2.0.0-rc5.4\lib\net462\LiveChartsCore.SkiaSharpView.dll</HintPath>
    </Reference>
    <Reference Include="LiveChartsCore.SkiaSharpView.WPF, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc92a3e13d40c573, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\LiveChartsCore.SkiaSharpView.WPF.2.0.0-rc5.4\lib\net462\LiveChartsCore.SkiaSharpView.WPF.dll</HintPath>
    </Reference>
    <Reference Include="log4net, Version=3.0.4.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\log4net.3.0.4\lib\net462\log4net.dll</HintPath>
    </Reference>
    <Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\Microsoft.Bcl.AsyncInterfaces.8.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
    </Reference>
    <Reference Include="Microsoft.Web.WebView2.Core, Version=1.0.3240.44, Culture=neutral, PublicKeyToken=2a8ab48044d2601e, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\Microsoft.Web.WebView2.1.0.3240.44\lib\net462\Microsoft.Web.WebView2.Core.dll</HintPath>
@@ -70,10 +85,50 @@
    <Reference Include="Panuon.WPF.UI, Version=1.3.0.2, Culture=neutral, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\Panuon.WPF.UI.1.3.0.2\lib\net472\Panuon.WPF.UI.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="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, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll</HintPath>
    </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=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
    </Reference>
    <Reference Include="System.Text.Encodings.Web, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\System.Text.Encodings.Web.8.0.0\lib\net462\System.Text.Encodings.Web.dll</HintPath>
    </Reference>
    <Reference Include="System.Text.Json, Version=8.0.0.5, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\System.Text.Json.8.0.5\lib\net462\System.Text.Json.dll</HintPath>
    </Reference>
    <Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.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.Web" />
    <Reference Include="System.Xml" />
    <Reference Include="Microsoft.CSharp" />
@@ -120,6 +175,7 @@
      <Generator>ResXFileCodeGenerator</Generator>
      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
    </EmbeddedResource>
    <None Include="app.config" />
    <None Include="packages.config" />
    <None Include="Properties\Settings.settings">
      <Generator>SettingsSingleFileGenerator</Generator>
@@ -162,5 +218,13 @@
      <ErrorText>这台计算机上缺少此项目引用的 NuGet ç¨‹åºåŒ…。使用“NuGet ç¨‹åºåŒ…还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
    </PropertyGroup>
    <Error Condition="!Exists('..\WPFProject\packages\Microsoft.Web.WebView2.1.0.3240.44\build\Microsoft.Web.WebView2.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\WPFProject\packages\Microsoft.Web.WebView2.1.0.3240.44\build\Microsoft.Web.WebView2.targets'))" />
    <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'))" />
  </Target>
  <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')" />
  <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')" />
</Project>
Plugin.DataBoard/SimpleData.cs
@@ -1,8 +1,9 @@
namespace Plugin.DataBoard
using LiveChartsCore.Defaults;
namespace Plugin.DataBoard
{
    public class SimpleData
    public class SimpleData: ObservableValue
    {
        public string Name { get; set; }
        public int Value { get; set; }
    }
}
Plugin.DataBoard/app.config
New file
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>
Plugin.DataBoard/packages.config
@@ -1,7 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="HarfBuzzSharp" 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="LiveChartsCore" version="2.0.0-rc5.4" targetFramework="net472" />
  <package id="LiveChartsCore.SkiaSharpView" version="2.0.0-rc5.4" targetFramework="net472" />
  <package id="LiveChartsCore.SkiaSharpView.WPF" version="2.0.0-rc5.4" targetFramework="net472" />
  <package id="log4net" version="3.0.4" targetFramework="net472" />
  <package id="Microsoft.Bcl.AsyncInterfaces" version="8.0.0" targetFramework="net472" />
  <package id="Microsoft.Web.WebView2" version="1.0.3240.44" targetFramework="net472" />
  <package id="Panuon.WPF" version="1.1.3" targetFramework="net472" />
  <package id="Panuon.WPF.UI" version="1.3.0.2" 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.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="6.0.0" targetFramework="net472" />
  <package id="System.Text.Encodings.Web" version="8.0.0" targetFramework="net472" />
  <package id="System.Text.Json" version="8.0.5" targetFramework="net472" />
  <package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net472" />
  <package id="System.ValueTuple" version="4.5.0" targetFramework="net472" />
</packages>
Plugin.QualityReport/AssemblyInfo.cs
New file
@@ -0,0 +1,13 @@
using HMI.Core.Annotation;
using System.Windows;
[assembly:ThemeInfo(
    ResourceDictionaryLocation.None,            //where theme specific resource dictionaries are located
                                                //(used if a resource is not found in the page,
                                                // or application resource dictionaries)
    ResourceDictionaryLocation.SourceAssembly   //where the generic resource dictionary is located
                                                //(used if a resource is not found in the page,
                                                // app, or any theme specific resource dictionaries)
)]
[assembly: PluginClassName("Plugin.QualityReport.QualityReport")]
Plugin.QualityReport/Libs/Static/CMKÄ£°å.xls
Binary files differ
Plugin.QualityReport/Libs/Static/MSAÄ£°å.xlsx
Binary files differ
Plugin.QualityReport/Libs/Static/ÖØ¸´ÐÔÄ£°å.xls
Binary files differ
Plugin.QualityReport/Plugin.QualityReport.csproj
New file
@@ -0,0 +1,46 @@
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Library</OutputType>
    <TargetFramework>net472</TargetFramework>
    <Nullable>enable</Nullable>
    <LangVersion>10.0</LangVersion>
    <ImplicitUsings>enable</ImplicitUsings>
    <UseWPF>true</UseWPF>
    <Platforms>AnyCPU;x64</Platforms>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
    <PackageReference Include="log4net" Version="3.0.4" />
    <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
    <PackageReference Include="NPOI" Version="2.4.1" />
    <PackageReference Include="Panuon.WPF" Version="1.1.3" />
    <PackageReference Include="Panuon.WPF.UI" Version="1.3.0.2" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\HMI.Base\HMI.Base.csproj" />
    <ProjectReference Include="..\HMI.Core\HMI.Core.csproj" />
    <ProjectReference Include="..\HMI.UI\HMI.UI.csproj" />
  </ItemGroup>
  <ItemGroup>
    <Reference Include="NPOI">
      <HintPath>..\HMI.ParameterManage\bin\x64\Debug\NPOI.dll</HintPath>
    </Reference>
  </ItemGroup>
  <ItemGroup>
    <None Update="Libs\Static\CMK模板.xls">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
    <None Update="Libs\Static\MSA模板.xlsx">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
    <None Update="Libs\Static\重复性模板.xls">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
  </ItemGroup>
</Project>
Plugin.QualityReport/QualityReport.xaml
New file
@@ -0,0 +1,249 @@
<hb:SimpleMaskControl x:Class="Plugin.QualityReport.QualityReport"
             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.QualityReport"
             mc:Ignorable="d"
             xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
             xmlns:pu="https://opensource.panuon.com/wpf-ui"
             xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
                      x:Name="control"
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <Style x:Key="ReportDataGridRow" TargetType="DataGridRow">
            <Setter Property="Background" Value="White"/>
            <Style.Triggers>
                <Trigger Property="AlternationIndex" Value="1">
                    <Setter Property="Background" Value="#eeeeee"/>
                </Trigger>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="Background" Value="{DynamicResource SecondPrimaryColor}"/>
                    <Setter Property="Foreground" Value="{DynamicResource SecondPrimaryColorContract}"/>
                </Trigger>
            </Style.Triggers>
        </Style>
        <Style x:Key="ReportDataGrid" TargetType="DataGrid">
            <Setter Property="FontSize" Value="16"/>
            <Setter Property="AlternationCount" Value="2"/>
            <Setter Property="AutoGenerateColumns" Value="False"/>
            <Setter Property="RowHeight" Value="30"/>
            <Setter Property="IsReadOnly" Value="True"/>
            <Setter Property="RowStyle" Value="{StaticResource ReportDataGridRow}"/>
        </Style>
        <Style TargetType="ListBox" x:Key="ListBoxItemStyle1">
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="Padding" Value="-1"/>
            <Setter Property="ItemContainerStyle">
                <Setter.Value>
                    <Style TargetType="ListBoxItem">
                        <Setter Property="Margin" Value="0"/>
                        <Setter Property="Padding" Value="0"/>
                        <Setter Property="Foreground" Value="Black"/>
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate TargetType="ListBoxItem">
                                    <Border x:Name="border" Padding="10,5" Cursor="Hand" Margin="5,2" Height="30"
                                                BorderThickness="1" BorderBrush="{DynamicResource CommonBoarder}">
                                        <DockPanel VerticalAlignment="Center">
                                            <Button Margin="10,0,0,0" DockPanel.Dock="Right"
                                                    Command="{Binding RemoveSNCommand,RelativeSource={RelativeSource AncestorType=local:QualityReport}}"
                                                    CommandParameter="{Binding }"
                                                    Tag="{Binding }">X</Button>
                                            <ContentPresenter x:Name="cp" Content="{TemplateBinding Content}"
ContentStringFormat="{TemplateBinding ContentStringFormat}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
                                        </DockPanel>
                                    </Border>
                                    <ControlTemplate.Triggers>
                                        <Trigger Property="IsMouseOver" Value="True">
                                            <Setter TargetName="border" Property="Background" Value="#7093d3" />
                                            <Setter Property="Foreground" Value="{StaticResource SecondPrimaryColorContract}" />
                                        </Trigger>
                                    </ControlTemplate.Triggers>
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                    </Style>
                </Setter.Value>
            </Setter>
            <Setter Property="ItemsPanel">
                <Setter.Value>
                    <ItemsPanelTemplate>
                        <VirtualizingStackPanel IsItemsHost="True" Orientation="Horizontal"/>
                    </ItemsPanelTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </UserControl.Resources>
    <Border Style="{StaticResource CardBoarder}" Padding="0">
        <TabControl Style="{StaticResource HeaderTabControl}" >
            <TabItem Header="CMK">
                <Grid dp:GridHelper.Space="10">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <StackPanel Orientation="Horizontal" VerticalAlignment="Center" dp:StackPanelHelper.ChildSpace="10">
                        <pu:FormGroup Header="型号" Style="{StaticResource InputFormGroupStyle}">
                            <ComboBox MinWidth="180" ItemsSource="{Binding Models}" Style="{StaticResource FormComboxStyle}" SelectedValue="{Binding Model}"
SelectedValuePath="Name" DisplayMemberPath="Name" />
                        </pu:FormGroup>
                        <pu:FormGroup Header="时间" Style="{StaticResource InputFormGroupStyle}">
                            <StackPanel Orientation="Horizontal">
                                <pu:DateTimePicker Style="{StaticResource TimePicker}" SelectedDateTime="{Binding StartTimeCMK}"></pu:DateTimePicker>
                                <TextBlock VerticalAlignment="Center" Text="   è‡³   "/>
                                <pu:DateTimePicker Style="{StaticResource TimePicker}" SelectedDateTime="{Binding EndTimeCMK}"></pu:DateTimePicker>
                            </StackPanel>
                        </pu:FormGroup>
                        <pu:FormGroup Header="测试项" Style="{StaticResource InputFormGroupStyle}">
                            <pu:SearchBox Text="{Binding TestItemCMK,Mode=TwoWay}" Tag="Step" SearchTextChanged="SearchTextChanged" MinWidth="180" />
                        </pu:FormGroup>
                        <Button Style="{StaticResource QueryBtn}" Command="{Binding QueryCMKCommand}"/>
                        <Button Style="{StaticResource ExportBtn}" Command="{Binding ExportCMKCommand}"/>
                    </StackPanel>
                    <DataGrid Grid.Row="2" Style="{StaticResource ReportDataGrid}"
                              ItemsSource="{Binding DatasourceCMK}">
                        <DataGrid.Columns>
                            <DataGridTextColumn Width="*" Header="数据1" Binding="{Binding d1}"/>
                            <DataGridTextColumn Width="*" Header="数据2" Binding="{Binding d2}"/>
                            <DataGridTextColumn Width="*" Header="数据3" Binding="{Binding d3}"/>
                            <DataGridTextColumn Width="*" Header="数据4" Binding="{Binding d4}"/>
                            <DataGridTextColumn Width="*" Header="数据5" Binding="{Binding d5}"/>
                        </DataGrid.Columns>
                    </DataGrid>
                </Grid>
            </TabItem>
            <TabItem Header="MSA">
                <Grid dp:GridHelper.Space="10">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <StackPanel Orientation="Horizontal" VerticalAlignment="Center" dp:StackPanelHelper.ChildSpace="10">
                        <pu:FormGroup Header="型号" Style="{StaticResource InputFormGroupStyle}">
                            <ComboBox MinWidth="180" ItemsSource="{Binding Models}" Style="{StaticResource FormComboxStyle}" SelectedValue="{Binding Model}"
              SelectedValuePath="Name" DisplayMemberPath="Name" />
                        </pu:FormGroup>
                        <pu:FormGroup Header="SN" Style="{StaticResource InputFormGroupStyle}">
                            <DockPanel>
                                <Button DockPanel.Dock="Right"
                                        Command="{Binding AddSnrCommand}"
                                        Style="{StaticResource PrimaryButton}"
                                        FontFamily="{StaticResource iconfont}"
                                        Content="&#xe61a;"/>
                                <pu:SearchBox Text="{Binding Snr,Mode=TwoWay}" Tag="SN" SearchTextChanged="SearchTextChanged" MinWidth="180" />
                            </DockPanel>
                        </pu:FormGroup>
                        <pu:FormGroup Header="时间" Style="{StaticResource InputFormGroupStyle}">
                            <StackPanel Orientation="Horizontal">
                                <pu:DateTimePicker Style="{StaticResource TimePicker}" SelectedDateTime="{Binding StartTimeCMK}"></pu:DateTimePicker>
                                <TextBlock VerticalAlignment="Center" Text="   è‡³   "/>
                                <pu:DateTimePicker Style="{StaticResource TimePicker}" SelectedDateTime="{Binding EndTimeCMK}"></pu:DateTimePicker>
                            </StackPanel>
                        </pu:FormGroup>
                        <Button Style="{StaticResource QueryBtn}" Command="{Binding QueryMSACommand}"/>
                        <Button Style="{StaticResource ExportBtn}" Command="{Binding ExportMSACommand}"/>
                    </StackPanel>
                    <pu:FormGroup Grid.Row="1" Header="已选SN" Style="{StaticResource InputFormGroupStyle}">
                        <ListBox Style="{StaticResource ListBoxItemStyle1}" ItemsSource="{Binding SelectedSnr}" Height="40">
                        </ListBox>
                    </pu:FormGroup>
                    <DataGrid Grid.Row="2" Style="{StaticResource ReportDataGrid}"
          ItemsSource="{Binding DatasourceMSA}">
                        <DataGrid.Columns>
                            <DataGridTextColumn Width="*" Header="条码1" Binding="{Binding d1}"/>
                            <DataGridTextColumn Width="*" Header="条码2" Binding="{Binding d2}"/>
                            <DataGridTextColumn Width="*" Header="条码3" Binding="{Binding d3}"/>
                            <DataGridTextColumn Width="*" Header="条码4" Binding="{Binding d4}"/>
                            <DataGridTextColumn Width="*" Header="条码5" Binding="{Binding d5}"/>
                            <DataGridTextColumn Width="*" Header="条码6" Binding="{Binding d5}"/>
                            <DataGridTextColumn Width="*" Header="条码7" Binding="{Binding d5}"/>
                            <DataGridTextColumn Width="*" Header="条码8" Binding="{Binding d5}"/>
                            <DataGridTextColumn Width="*" Header="条码9" Binding="{Binding d5}"/>
                        </DataGrid.Columns>
                    </DataGrid>
                    <!--<Grid Grid.Row="2" x:Name="msaGrid" dp:GridHelper.InnerBorder="1" dp:GridHelper.InnerBorderBrush="Black">
                    </Grid>-->
                </Grid>
            </TabItem>
            <TabItem Header="重复性">
                <Grid dp:GridHelper.Space="10">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <StackPanel Orientation="Horizontal" VerticalAlignment="Center" dp:StackPanelHelper.ChildSpace="10">
                        <pu:FormGroup Header="型号" Style="{StaticResource InputFormGroupStyle}">
                            <ComboBox MinWidth="180" ItemsSource="{Binding Models}" Style="{StaticResource FormComboxStyle}" SelectedValue="{Binding Model}"
    SelectedValuePath="Name" DisplayMemberPath="Name" />
                        </pu:FormGroup>
                        <pu:FormGroup Header="SN" Style="{StaticResource InputFormGroupStyle}">
                            <DockPanel>
                                <Button DockPanel.Dock="Right"
                              Command="{Binding AddSnr2RepeatCommand}"
                              Style="{StaticResource PrimaryButton}"
                              FontFamily="{StaticResource iconfont}"
                              Content="&#xe61a;"/>
                                <pu:SearchBox Text="{Binding Snr,Mode=TwoWay}" Tag="SN" SearchTextChanged="SearchTextChanged" MinWidth="180" />
                            </DockPanel>
                        </pu:FormGroup>
                        <pu:FormGroup Header="测试项" Style="{StaticResource InputFormGroupStyle}">
                            <pu:SearchBox Text="{Binding TestItemCMK,Mode=TwoWay}" Tag="Step" SearchTextChanged="SearchTextChanged" MinWidth="180" />
                        </pu:FormGroup>
                        <pu:FormGroup Header="时间" Style="{StaticResource InputFormGroupStyle}">
                            <StackPanel Orientation="Horizontal">
                                <pu:DateTimePicker Style="{StaticResource TimePicker}" SelectedDateTime="{Binding StartTimeCMK}"></pu:DateTimePicker>
                                <TextBlock VerticalAlignment="Center" Text="   è‡³   "/>
                                <pu:DateTimePicker Style="{StaticResource TimePicker}" SelectedDateTime="{Binding EndTimeCMK}"></pu:DateTimePicker>
                            </StackPanel>
                        </pu:FormGroup>
                        <Button Style="{StaticResource QueryBtn}" Command="{Binding QueryRepeatCommand}"/>
                        <Button Style="{StaticResource ExportBtn}" Command="{Binding ExportRepeatCommand}"/>
                    </StackPanel>
                    <pu:FormGroup Grid.Row="1" Header="已选SN" Style="{StaticResource InputFormGroupStyle}">
                        <ListBox Style="{StaticResource ListBoxItemStyle1}" ItemsSource="{Binding SelectedSnrRepeat}" Height="40">
                        </ListBox>
                    </pu:FormGroup>
                    <DataGrid Grid.Row="2" Style="{StaticResource ReportDataGrid}" ItemsSource="{Binding DatasourceRepeat}">
                        <DataGrid.Columns>
                            <DataGridTextColumn Width="*" Header="条码1" Binding="{Binding d1}"/>
                            <DataGridTextColumn Width="*" Header="条码2" Binding="{Binding d2}"/>
                            <DataGridTextColumn Width="*" Header="条码3" Binding="{Binding d3}"/>
                            <DataGridTextColumn Width="*" Header="条码4" Binding="{Binding d4}"/>
                            <DataGridTextColumn Width="*" Header="条码5" Binding="{Binding d5}"/>
                            <DataGridTextColumn Width="*" Header="条码6" Binding="{Binding d5}"/>
                            <DataGridTextColumn Width="*" Header="条码7" Binding="{Binding d5}"/>
                            <DataGridTextColumn Width="*" Header="条码8" Binding="{Binding d5}"/>
                            <DataGridTextColumn Width="*" Header="条码9" Binding="{Binding d5}"/>
                        </DataGrid.Columns>
                    </DataGrid>
                </Grid>
            </TabItem>
        </TabControl>
    </Border>
</hb:SimpleMaskControl>
Plugin.QualityReport/QualityReport.xaml.cs
New file
@@ -0,0 +1,705 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using HMI.Base;
using HMI.Core.Entity;
using HMI.Core.Manager;
using HMI.Core.Setting;
using HMI.Core.Util;
using HMI.UI.Util;
using Microsoft.Win32;
using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;
using NPOI.Util;
using NPOI.XSSF.UserModel;
using Panuon.WPF.UI;
using System.Collections.ObjectModel;
using System.Data;
using System.IO;
using System.Windows;
namespace Plugin.QualityReport
{
    public class CMKData
    {
        public object[] values = new object[5];
        public object d1 { get => values[0]; set => values[0] = value; }
        public object d2 { get => values[1]; set => values[1] = value; }
        public object d3 { get => values[2]; set => values[2] = value; }
        public object d4 { get => values[3]; set => values[3] = value; }
        public object d5 { get => values[4]; set => values[4] = value; }
    }
    public class MSAData
    {
        public object[] values = new object[9];
        public object d1 { get => values[0]; set => values[0] = value; }
        public object d2 { get => values[1]; set => values[1] = value; }
        public object d3 { get => values[2]; set => values[2] = value; }
        public object d4 { get => values[3]; set => values[3] = value; }
        public object d5 { get => values[4]; set => values[4] = value; }
        public object d6 { get => values[5]; set => values[5] = value; }
        public object d7 { get => values[6]; set => values[6] = value; }
        public object d8 { get => values[7]; set => values[7] = value; }
        public object d9 { get => values[8]; set => values[8] = value; }
    }
    public class RepeatData
    {
        public string Title { get; set; }
        public object[] values = new object[10];
        public object d1 { get => values[0]; set => values[0] = value; }
        public object d2 { get => values[1]; set => values[1] = value; }
        public object d3 { get => values[2]; set => values[2] = value; }
        public object d4 { get => values[3]; set => values[3] = value; }
        public object d5 { get => values[4]; set => values[4] = value; }
        public object d6 { get => values[5]; set => values[5] = value; }
        public object d7 { get => values[6]; set => values[6] = value; }
        public object d8 { get => values[7]; set => values[7] = value; }
        public object d9 { get => values[8]; set => values[8] = value; }
        public object d10 { get => values[9]; set => values[9] = value; }
    }
    /// <summary>
    /// QualityReport.xaml çš„交互逻辑
    /// </summary>
    [ObservableObject]
    public partial class QualityReport : SimpleMaskControl
    {
        [ObservableProperty]
        private string model;
        [ObservableProperty]
        private List<ProductData> models;
        [ObservableProperty]
        private DateTime startTimeCMK;
        [ObservableProperty]
        private DateTime endTimeCMK;
        [ObservableProperty]
        private string testItemCMK = "TestItem";
        [ObservableProperty]
        private List<CMKData> datasourceCMK = null;
        [ObservableProperty]
        private List<MSAData> datasourceMSA = null;
        [ObservableProperty]
        private List<RepeatData> datasourceRepeat = null;
        [ObservableProperty]
        private string snr;
        [ObservableProperty]
        private ObservableCollection<string> selectedSnr = new ObservableCollection<string> { };
        [ObservableProperty]
        private ObservableCollection<string> selectedSnrRepeat = new ObservableCollection<string> { };
        public QualityReport()
        {
            DataContext = this;
            InitializeComponent();
            StartTimeCMK = DateTime.Today;
            EndTimeCMK = DateTime.Today.AddDays(1);
        }
        public override void Active()
        {
            base.Active();
            Models = ProductManager.GetProducts<List<ProductData>>()
                ?? new List<ProductData>();
        }
        private string[] GetFiles(DateTime? startTime, DateTime? endTime)
        {
            string dir = ParamSetting.DataLogFolder;
            string startName = $"Data_{startTime?.ToString("yyyyMMdd")}";
            string endName = $"Data_{endTime?.ToString("yyyyMMdd")}";
            var oldFiles = Directory.GetFiles(dir, "Data_*.db");
            oldFiles = oldFiles.Where(file =>
            {
                var fileName = Path.GetFileNameWithoutExtension(file);
                bool flag = true;
                flag &= (string.Compare(fileName, startName) == 1 || fileName == startName);
                flag &= (string.Compare(endName, fileName) == 1 || fileName == endName);
                return flag;
            }).ToArray();
            return oldFiles;
        }
        public DataTable SelectByFiles(string[] files, Func<string, string, string> ConcatAction, SortParam sorter, PageParam page)
        {
            if (files == null || files.Length <= 0)
            {
                return new DataTable();
            }
            if (files.Length < Sqlite3Helper.MAX_ATTACHED)
            {
                using (var helper = new Sqlite3Helper(files[0]))
                {
                    string[] sqls = new string[files.Length];
                    for (int i = 0; i < files.Length; i++)
                    {
                        var attach = $"ATTACH DATABASE '{files[i]}' AS 'db{i}'";
                        helper.Execute(attach);
                        sqls[i] = ConcatAction(files[i], $"db{i}");
                    }
                    string sql = string.Join(" UNION ALL ", sqls);
                    sql = $"SELECT * FROM ({sql}) as _temp";
                    if (sorter != null)
                    {
                        sql = $"{sql} ORDER BY {sorter.field} {sorter.sort}";
                    }
                    if (page != null)
                    {
                        sql = $"{sql} LIMIT {page.limit} OFFSET {page.offset}";
                    }
                    return helper.GetDataTable(sql);
                }
            }
            else
            {
                DataTable dt = new DataTable();
                Sqlite3Helper helper = null;
                string[] sqls = new string[Sqlite3Helper.MAX_ATTACHED];
                var fff = () =>
                {
                    if (helper != null)
                    {
                        string sql = string.Join(" UNION ALL ", sqls.Where(s => s != null));
                        sql = $"SELECT * FROM ({sql}) as _temp";
                        dt.Merge(helper.GetDataTable(sql));
                        sqls = new string[Sqlite3Helper.MAX_ATTACHED];
                        helper.Dispose();
                        helper = null;
                    }
                };
                try
                {
                    for (int i = 0; i < files.Length; i++)
                    {
                        if (i % Sqlite3Helper.MAX_ATTACHED == 0)
                        {
                            fff();
                            helper = new Sqlite3Helper(files[i]);
                        }
                        var attach = $"ATTACH DATABASE '{files[i]}' AS 'db{i}'";
                        helper.Execute(attach);
                        sqls[i % Sqlite3Helper.MAX_ATTACHED] = ConcatAction(files[i], $"db{i}");
                    }
                    fff();
                    if (dt.Rows.Count > 0)
                    {
                        if (sorter != null)
                        {
                            dt.DefaultView.Sort = $"{sorter.field} {sorter.sort}";
                        }
                        if (page != null)
                        {
                            var rows = dt.Rows.Cast<DataRow>();
                            var curRows = rows.Skip(page.offset).Take(page.limit).ToArray();
                            var sb = dt.Clone();
                            if (dt.Rows.Count > page.offset)
                            {
                                if (dt.Rows.Count < page.limit)
                                {
                                    sb = dt;
                                }
                                else
                                {
                                    for (int i = page.offset; i < page.offset + page.limit; i++)
                                    {
                                        sb.ImportRow(dt.Rows[i]);
                                    }
                                }
                            }
                            dt = sb;
                            // dt = new DataTable(curRows);
                        }
                    }
                }
                finally
                {
                    helper?.Dispose();
                }
                return dt;
            }
        }
        [RelayCommand]
        public void QueryCMK()
        {
            try
            {
                if (string.IsNullOrEmpty(Model) || string.IsNullOrEmpty(TestItemCMK) || StartTimeCMK == null || EndTimeCMK == null)
                {
                    ToastHelper.Error("请输入型号、测试项、开始时间和结束时间!");
                    return;
                }
                string[] files = GetFiles(StartTimeCMK, EndTimeCMK);
                DataTable dt = SelectByFiles(files, (file, db) =>
                {
                    return $"SELECT value,time FROM {db}.serialData WHERE model='{Model}' AND step='{TestItemCMK}' AND time>='{StartTimeCMK:yyyy-MM-dd HH:mm:ss}' AND time<'{EndTimeCMK:yyyy-MM-dd HH:mm:ss}'";
                }, new SortParam { field = "time", sort = "DESC" }, new PageParam { limit = 50, offset = 0 });
                List<CMKData> list = new List<CMKData>();
                object[] values = null;
                for (int i = 0; i < dt.Rows.Count; i++)
                {
                    if (i % 5 == 0)
                    {
                        values = new object[5];
                        list.Add(new CMKData { values = values });
                    }
                    values[i % 5] = dt.Rows[i]["value"];
                }
                DatasourceCMK = list;
            }
            catch (Exception ex)
            {
            }
        }
        [RelayCommand]
        public void ExportCMK()
        {
            try
            {
                if (string.IsNullOrEmpty(Model) || string.IsNullOrEmpty(TestItemCMK) || StartTimeCMK == null || EndTimeCMK == null)
                {
                    ToastHelper.Error("请输入型号、测试项、开始时间和结束时间!");
                    return;
                }
                string[] files = GetFiles(StartTimeCMK, EndTimeCMK);
                DataTable dt = SelectByFiles(files, (file, db) =>
                {
                    return $"SELECT value,time FROM {db}.serialData WHERE model='{Model}' AND step='{TestItemCMK}' AND time>='{StartTimeCMK:yyyy-MM-dd HH:mm:ss}' AND time<'{EndTimeCMK:yyyy-MM-dd HH:mm:ss}'";
                }, new SortParam { field = "time", sort = "DESC" }, new PageParam { limit = 50, offset = 0 });
                List<CMKData> list = new List<CMKData>();
                object[] values = null;
                for (int i = 0; i < dt.Rows.Count; i++)
                {
                    if (i % 5 == 0)
                    {
                        values = new object[5];
                        list.Add(new CMKData { values = values });
                    }
                    values[i % 5] = dt.Rows[i]["value"];
                }
                using (FileStream fs = File.OpenRead(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Libs", "Static", "CMK模板.xls")))
                {
                    IWorkbook workbook = new HSSFWorkbook(fs);
                    ISheet sheet = workbook.GetSheet("CMK");
                    for (int i = 0; i < list.Count; i++)
                    {
                        int num = i + 9;
                        if (num > 24)
                        {
                            break;
                        }
                        IRow row = sheet.GetRow(num);
                        CMKData data = list[i];
                        for (int j = 0; j < 5; j++)
                        {
                            if (data.values[j] != null)
                            {
                                row.GetCell(j).SetCellValue(double.Parse(data.values[j].ToString()));
                            }
                        }
                    }
                    sheet.ForceFormulaRecalculation = true;//执行公式
                    //方法二
                    //workbook.GetCreationHelper().CreateFormulaEvaluator().EvaluateAll();
                    SaveFileDialog sfd = new SaveFileDialog
                    {
                        Filter = "Excel文件 (*.xls)|*.xls",
                    };
                    if (sfd.ShowDialog() == true)
                    {
                        using (FileStream sfs = new FileStream(sfd.FileName, FileMode.OpenOrCreate))
                        {
                            workbook.Write(sfs);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
            }
        }
        [RelayCommand]
        private void AddSnr()
        {
            if (SelectedSnr.Count >= 9)
            {
                ToastHelper.Warning("最多选取9个产品");
                return;
            }
            if (SelectedSnr.Contains(Snr))
            {
                ToastHelper.Warning($"条码:[{Snr}] å·²è¢«é€‰ä¸­");
                return;
            }
            SelectedSnr.Add(Snr);
        }
        [RelayCommand]
        private void AddSnr2Repeat()
        {
            if (SelectedSnrRepeat.Count >= 10)
            {
                ToastHelper.Warning("最多选取10个产品");
                return;
            }
            if (SelectedSnrRepeat.Contains(Snr))
            {
                ToastHelper.Warning($"条码:[{Snr}] å·²è¢«é€‰ä¸­");
                return;
            }
            SelectedSnrRepeat.Add(Snr);
        }
        [RelayCommand]
        public void RemoveSN(object obj)
        {
            SelectedSnr.Remove(obj.ToString());
        }
        [RelayCommand]
        public void QueryMSA()
        {
            try
            {
                if (string.IsNullOrEmpty(Model) || SelectedSnr.Count <= 0 || StartTimeCMK == null || EndTimeCMK == null)
                {
                    ToastHelper.Error("请输入型号、选中的条码、开始时间和结束时间!");
                    return;
                }
                string[] files = GetFiles(StartTimeCMK, EndTimeCMK);
                Dictionary<string, List<int>> map = new Dictionary<string, List<int>>();
                foreach (var sn in SelectedSnr)
                {
                    List<int> datas = new List<int>();
                    DataTable dt = SelectByFiles(files, (file, db) =>
                {
                    return $"SELECT result,time FROM {db}.testResult WHERE model='{Model}' AND sn='{sn}' AND time>='{StartTimeCMK:yyyy-MM-dd HH:mm:ss}' AND time<'{EndTimeCMK:yyyy-MM-dd HH:mm:ss}'";
                }, new SortParam { field = "time", sort = "DESC" }, new PageParam { limit = 10, offset = 0 });
                    datas = dt.Rows.Cast<DataRow>().Select(r =>
                    string.Equals(r["result"].ToString(), "passed", StringComparison.OrdinalIgnoreCase)
                    || string.Equals(r["result"].ToString(), "ok", StringComparison.OrdinalIgnoreCase) ? 1 : 0)
                        .ToList();
                    map[sn] = datas;
                }
                List<MSAData> temp = new List<MSAData>() { new MSAData() };
                int i = 0;
                foreach (var key in map.Keys)
                {
                    temp[0].values[i] = key;
                    var vals = map[key];
                    for (int j = 0; j < vals.Count; j++)
                    {
                        if (temp.Count < j + 2)
                        {
                            temp.Add(new MSAData());
                        }
                        temp[j + 1].values[i] = vals[j];
                    }
                    i++;
                }
                DatasourceMSA = temp;
                //RefreshMSAGrid(map);
            }
            catch (Exception ex)
            {
            }
        }
        [RelayCommand]
        public void ExportMSA()
        {
            try
            {
                if (string.IsNullOrEmpty(Model) || SelectedSnr.Count <= 0 || StartTimeCMK == null || EndTimeCMK == null)
                {
                    ToastHelper.Error("请输入型号、选中的条码、开始时间和结束时间!");
                    return;
                }
                string[] files = GetFiles(StartTimeCMK, EndTimeCMK);
                Dictionary<string, List<int>> map = new Dictionary<string, List<int>>();
                foreach (var sn in SelectedSnr)
                {
                    List<int> datas = new List<int>();
                    DataTable dt = SelectByFiles(files, (file, db) =>
                    {
                        return $"SELECT result,time FROM {db}.testResult WHERE model='{Model}' AND sn='{sn}' AND time>='{StartTimeCMK:yyyy-MM-dd HH:mm:ss}' AND time<'{EndTimeCMK:yyyy-MM-dd HH:mm:ss}'";
                    }, new SortParam { field = "time", sort = "DESC" }, new PageParam { limit = 10, offset = 0 });
                    datas = dt.Rows.Cast<DataRow>().Select(r =>
                    string.Equals(r["result"].ToString(), "passed", StringComparison.OrdinalIgnoreCase)
                    || string.Equals(r["result"].ToString(), "ok", StringComparison.OrdinalIgnoreCase) ? 1 : 0)
                        .ToList();
                    map[sn] = datas;
                }
                using (FileStream fs = File.OpenRead(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Libs", "Static", "MSA模板.xlsx")))
                {
                    IWorkbook workbook = new XSSFWorkbook(fs);
                    ISheet sheet = workbook.GetSheet("数据输入");
                    int column = 2;
                    foreach (string snr in map.Keys)
                    {
                        if (column > 10)
                        {
                            break;
                        }
                        var datas = map[snr];
                        sheet.GetRow(9).GetCell(column).SetCellValue(snr);
                        for (int i = 0; i < datas.Count; i++)
                        {
                            if (i > 49) break;
                            sheet.GetRow(i + 11).GetCell(column).SetCellValue(datas[i]);
                        }
                        column++;
                    }
                    sheet.ForceFormulaRecalculation = true;//执行公式
                    //方法二
                    //workbook.GetCreationHelper().CreateFormulaEvaluator().EvaluateAll();
                    SaveFileDialog sfd = new SaveFileDialog
                    {
                        Filter = "Excel文件 (*.xlsx)|*.xlsx",
                    };
                    if (sfd.ShowDialog() == true)
                    {
                        using (FileStream sfs = new FileStream(sfd.FileName, FileMode.OpenOrCreate))
                        {
                            workbook.Write(sfs);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
            }
        }
        [RelayCommand]
        public void QueryRepeat()
        {
            try
            {
                if (string.IsNullOrEmpty(Model) || SelectedSnrRepeat.Count <= 0 || string.IsNullOrEmpty(TestItemCMK) || StartTimeCMK == null || EndTimeCMK == null)
                {
                    ToastHelper.Error("请输入型号、选中的条码、测试项、开始时间和结束时间!");
                    return;
                }
                string[] files = GetFiles(StartTimeCMK, EndTimeCMK);
                Dictionary<string, List<double>> map = new Dictionary<string, List<double>>();
                foreach (var sn in SelectedSnrRepeat)
                {
                    DataTable dt = SelectByFiles(files, (file, db) =>
                    {
                        return $"SELECT value,time FROM {db}.serialData WHERE model='{Model}' And step='{TestItemCMK}' AND sn='{sn}' AND time>='{StartTimeCMK:yyyy-MM-dd HH:mm:ss}' AND time<'{EndTimeCMK:yyyy-MM-dd HH:mm:ss}'";
                    }, new SortParam { field = "time", sort = "DESC" }, new PageParam { limit = 9, offset = 0 });
                    var datas = dt.Rows.Cast<DataRow>().Select(r => Math.Round(double.Parse(r["value"].ToString()), 3, MidpointRounding.AwayFromZero)).ToList();
                    map[sn] = datas;
                }
                List<RepeatData> temp = new List<RepeatData>() { new RepeatData { Title = "条码" } };
                int i = 0;
                foreach (var key in map.Keys)
                {
                    temp[0].values[i] = key;
                    var vals = map[key];
                    for (int j = 0; j < vals.Count; j++)
                    {
                        if (temp.Count < j + 2)
                        {
                            temp.Add(new RepeatData());
                        }
                        temp[j + 1].values[i] = vals[j];
                    }
                    i++;
                }
                DatasourceRepeat = temp;
            }
            catch (Exception ex)
            {
                log.Error("重复性查询失败", ex);
                MsgBoxHelper2.Error($"重复性查询失败: {ex.Message}");
            }
        }
        [RelayCommand]
        public void ExportRepeat()
        {
            try
            {
                if (string.IsNullOrEmpty(Model) || SelectedSnrRepeat.Count <= 0 || string.IsNullOrEmpty(TestItemCMK) || StartTimeCMK == null || EndTimeCMK == null)
                {
                    ToastHelper.Error("请输入型号、选中的条码、测试项、开始时间和结束时间!");
                    return;
                }
                string[] files = GetFiles(StartTimeCMK, EndTimeCMK);
                Dictionary<string, List<double>> map = new Dictionary<string, List<double>>();
                foreach (var sn in SelectedSnrRepeat)
                {
                    DataTable dt = SelectByFiles(files, (file, db) =>
                    {
                        return $"SELECT value,time FROM {db}.serialData WHERE model='{Model}' And step='{TestItemCMK}' AND sn='{sn}' AND time>='{StartTimeCMK:yyyy-MM-dd HH:mm:ss}' AND time<'{EndTimeCMK:yyyy-MM-dd HH:mm:ss}'";
                    }, new SortParam { field = "time", sort = "DESC" }, new PageParam { limit = 9, offset = 0 });
                    var datas = dt.Rows.Cast<DataRow>().Select(r => Math.Round(double.Parse(r["value"].ToString()), 3, MidpointRounding.AwayFromZero)).ToList();
                    map[sn] = datas;
                }
                List<RepeatData> temp = new List<RepeatData>() { new RepeatData() };
                int i = 0;
                foreach (var key in map.Keys)
                {
                    temp[0].values[i] = key;
                    var vals = map[key];
                    for (int j = 0; j < vals.Count; j++)
                    {
                        if (temp.Count < j + 2)
                        {
                            temp.Add(new RepeatData());
                        }
                        temp[j + 1].values[i] = vals[j];
                    }
                    i++;
                }
                using (FileStream fs = File.OpenRead(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Libs", "Static", "重复性模板.xls")))
                {
                    IWorkbook workbook = new HSSFWorkbook(fs);
                    ISheet sheet = workbook.GetSheet("sheet1");
                    sheet.GetRow(10).GetCell(2).SetCellValue(TestItemCMK);
                    int column = 2;
                    foreach (string snr in map.Keys)
                    {
                        if (column > 11)
                        {
                            break;
                        }
                        var datas = map[snr];
                        sheet.GetRow(11).GetCell(column).SetCellValue(snr);
                        for (int j = 0; j < datas.Count; j++)
                        {
                            if (j > 8) break;
                            int row = j + 13;
                            if (j > 2 && j <= 5)
                            {
                                row = j + 15;
                            }
                            else if (j > 5)
                            {
                                row = j + 17;
                            }
                            sheet.GetRow(row).GetCell(column).SetCellValue(datas[j]);
                        }
                        column++;
                    }
                    sheet.ForceFormulaRecalculation = true;//执行公式
                    //方法二
                    //workbook.GetCreationHelper().CreateFormulaEvaluator().EvaluateAll();
                    SaveFileDialog sfd = new SaveFileDialog
                    {
                        Filter = "Excel文件 (*.xls)|*.xls",
                    };
                    if (sfd.ShowDialog() == true)
                    {
                        using (FileStream sfs = new FileStream(sfd.FileName, FileMode.OpenOrCreate))
                        {
                            workbook.Write(sfs);
                            ToastHelper.Success("导出成功");
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                log.Error("重复性导出失败", ex);
                MsgBoxHelper2.Error($"导出失败: {ex.Message}");
            }
        }
        private void SearchTextChanged(object sender, Panuon.WPF.SearchTextChangedRoutedEventArgs e)
        {
            SearchBox box = (SearchBox)sender;
            try
            {
                string[] files = GetFiles(StartTimeCMK, EndTimeCMK).OrderBy(s => s).Take(7).ToArray();
                if (box.Tag.ToString() == "Step")
                {
                    DataTable dt = SelectByFiles(files, (file, db) =>
                    {
                        return $"SELECT DISTINCT step FROM {db}.serialData WHERE model='{Model}' And step LIKE '%{TestItemCMK}%'";
                    }, null, null);
                    var datas = dt.Rows.Cast<DataRow>().Select(r => r["step"].ToString()).Distinct().ToList();
                    box.ItemsSource = datas;
                }
                else
                {
                    DataTable dt = SelectByFiles(files, (file, db) =>
                    {
                        return $"SELECT DISTINCT sn FROM {db}.testResult WHERE model='{Model}' And sn LIKE '%{Snr}%'";
                    }, null, null);
                    var datas = dt.Rows.Cast<DataRow>().Select(r => r["sn"].ToString()).Distinct().ToList();
                    box.ItemsSource = datas;
                }
            }
            catch
            {
                box.ItemsSource = new string[] { };
            }
        }
        private void Button_Click(object sender, RoutedEventArgs e)
        {
        }
    }
}
Plugin.TestResult/DataTable.xaml
@@ -135,6 +135,7 @@
                <ColumnDefinition/>
            </UniformGrid.ColumnDefinitions>-->
            <Button Click="ScanWindow_Click" Style="{StaticResource PrimaryButton}"
                    IsEnabled="{Binding IsTesting,Converter={StaticResource reverseBool}}"
        Content="{DynamicResource Tr.Scan}"/>
            <Button Grid.Column="1" Click="ClearAllSnr_Click" Style="{StaticResource DangerButton}"
                    IsEnabled="{Binding IsTesting,Converter={StaticResource reverseBool}}"
Plugin.TestResult/DataTable.xaml.cs
@@ -319,7 +319,7 @@
            else
            {
                /// æ£€æŸ¥æ˜¯å¦å…³é—¨
                if (parent.executionWrapper?.GetStationGlobalValue<bool>($"StationState.DoorClose[{this.index}]", 0) != true)
                if (!GetDoorClose(parent.executionWrapper))
                {
                    /// æ²¡å…³é—¨ä¸å…è®¸å¯åЍ
                    MsgBoxHelper2.Warn("请先关闭舱门!");
@@ -390,6 +390,11 @@
            scanner = null;
        }
        public bool GetDoorClose(ITestExecutionWrapper wrapper)
        {
            return wrapper?.GetStationGlobalValue<bool>($"StationState.DoorClose[{this.index}]", 0) == true;
        }
        private void Scanner_FinishBarcode(object sender, BarcodeEventArg e)
        {
            if (parent.executionWrapper == null) return;
@@ -399,19 +404,37 @@
            CellInfo cellInfo = ScannerParser.GetCellInfoByCode(CbProduct.SelectedItem?.ToString(), mainGrid.Columns, rows, e.positionCode);
            string positionCode = e.positionCode, snrCode = e.snrCode;
            //将读到的条码写入到SN中
            int dutNo = this.index * this.cellCount + cellInfo.Index;
            //if (cellInfo.OvenName != this.title)
            //{
            //    MsgBoxHelper2.Warn($"请切换至 [{cellInfo.OvenName}] åŒºæ‰«ç ï¼Œå½“前为 [{this.title}] åŒºï¼");
            //    return;
            //}
            var targetTable = TestResultView.nameTables[cellInfo.OvenName];
            //将读到的条码写入到SN中
            int dutNo = targetTable.index * targetTable.cellCount + cellInfo.Index;
            string LookupStr = $"StationState.SN[{dutNo}]";
            cellInfo.LookUpStr = LookupStr;
            ViewControl.Logger.Debug($"PositionCode: [{e.positionCode}], SNR: [{e.snrCode}], LookUpStr: [{LookupStr}]");
            ViewControl.Logger.Debug($"OvenName: [{cellInfo.OvenName}], CellIndex: [{cellInfo.Index}]");
            var cell = TestResultView.nameTables[cellInfo.OvenName].GetSingleCell(cellInfo.Index);
            ViewControl.Logger.Info($"PositionCode: [{e.positionCode}], SNR: [{e.snrCode}], LookUpStr: [{LookupStr}]");
            ViewControl.Logger.Info($"OvenName: [{cellInfo.OvenName}], CellIndex: [{cellInfo.Index}]");
            var cell = targetTable.GetSingleCell(cellInfo.Index);
            if (targetTable.GetDoorClose(parent.executionWrapper))
            {
                /// å…³é—¨äº†å°±ä¸å…è®¸æ¸…条码
                MsgBoxHelper2.Warn($"{targetTable.title}炉生产中,不允许操作条码");
                //MsgBoxHelper2.Warn(this.lang("Tr.CantClearSNR"));
                return;
            }
            if (snrCode == null)
            {
                var oldCellInfo = e.lastInfo;
                var oldCell = TestResultView.nameTables[oldCellInfo.OvenName].GetSingleCell(oldCellInfo.Index);
                var oldTable = TestResultView.nameTables[oldCellInfo.OvenName];
                var oldCell = oldTable.GetSingleCell(oldCellInfo.Index);
                if (oldCell.status != "hadCode")
                {
                    throw new Exception(this.lang("Tr.PositionCantClearSNR", oldCellInfo.OriginCode));
@@ -441,18 +464,10 @@
            //parent.engine.Globals.SetValString(LookupStr, 1, snrCode);
            //parent.engine.CommitGlobalsToDisk(promptOnSaveConflicts: false);
            TestResultView.nameTables[cellInfo.OvenName].GetSingleCell(cellInfo.Index).SetStatus("hadCode");
            TestResultView.nameTables[cellInfo.OvenName].GetSingleCell(cellInfo.Index).sn = snrCode;
            cell.SetStatus("hadCode");
            cell.sn = snrCode;
            Scanner.InsertNewCode(snrCode, cellInfo);
            var light = $"Oven{this.index + 1}_Cell{cell.index + 1}_Light_OK";
            if (cell.LightGreen(true, out string msg))
            {
                ViewControl.Logger.Debug($"Open Light [{light}] ON.");
            }
            else
            {
                ViewControl.Logger.Debug($"Open Light [{light}] Error.{msg}");
            }
            cell.LightGreen(true, out string msg);
        }
@@ -572,16 +587,18 @@
            string strs = datas_10015[2];
            string[] values = strs.Split(',');
            GetSingleCell(cellIndex).SetInfos(new Info
            Dispatcher.Invoke(() =>
            {
                step = values[0],
                value = values[1],
                limitUp = values[2],
                limitDown = values[3],
                unit = values[4],
                result = values[5],
                updateTime = values[6]
                GetSingleCell(cellIndex).SetInfos(new Info
                {
                    step = values[0],
                    value = values[1],
                    limitUp = values[2],
                    limitDown = values[3],
                    unit = values[4],
                    result = values[5],
                    updateTime = values[6]
                });
            });
            //session.Send("1");
        }
@@ -786,7 +803,7 @@
        private void ClearAllSnr_Click(object sender, RoutedEventArgs e)
        {
            if (parent.executionWrapper?.GetStationGlobalValue<bool>($"StationState.DoorClose[{this.index}]", 0) == true)
            if (GetDoorClose(parent.executionWrapper))
            {
                /// å…³é—¨äº†å°±ä¸å…è®¸æ¸…条码
                MsgBoxHelper2.Warn(this.lang("Tr.CantClearSNR"));
@@ -818,7 +835,7 @@
        public void TryClearCellSN(int cellIndex)
        {
            if (parent.executionWrapper?.GetStationGlobalValue<bool>($"StationState.DoorClose[{this.index}]", 0) == true)
            if (GetDoorClose(parent.executionWrapper))
            {
                MsgBoxHelper.Error(Window.GetWindow(this), this.lang("Tr.CantClearSNR"));
                return;
@@ -851,10 +868,7 @@
                return;
            }
            string key = $"StationState.Start[{this.index}]";
            //string key = $"StationState.Start[{this.index}]";
            //if (parent.executionWrapper?.GetStationGlobalValue<bool>(key, 0) == true)
            //{
            //    MsgBoxHelper2.Warn("测试启动中,不允许Abort");
@@ -873,9 +887,9 @@
            {
                return;
            }
            LogHelper.Info("UI", "Abort按钮按下", out _);
            ViewControl.Logger.Info("Abort按钮按下");
            key = $"StationState.Abort[{this.index}]";
            LogHelper.Info("UI", $"{title} Abort按钮按下", out _);
            ViewControl.Logger.Info($"{title} Abort按钮按下");
            string key = $"StationState.Abort[{this.index}]";
            parent.executionWrapper?.SetStationGlobalValue(key, 0, true);
        }
    }
Plugin.TestResult/Properties/AssemblyInfo.cs
@@ -50,6 +50,6 @@
//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
//通过使用 "*",如下所示:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyVersion("1.0.1.0")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: PluginClassName("Plugin.TestResult_WPF.ViewControl")]
Plugin.TestResult/Scanner.xaml.cs
@@ -158,12 +158,27 @@
                    {
                        try
                        {
                            this.targetCellInfo = ScannerParser.GetCellInfoByCode(this.model, this.columns, this.rows, barcode);
                            var targetCellInfo = ScannerParser.GetCellInfoByCode(this.model, this.columns, this.rows, barcode);
                            if (!TestResultView.nameTables.ContainsKey(targetCellInfo.OvenName))
                            {
                                MsgBoxHelper2.Error($"老化炉 [{targetCellInfo.OvenName}] ä¸å­˜åœ¨ï¼Œè¯·æ£€æŸ¥é…ç½®");
                                return;
                            }
                            var sourceTable = TestResultView.tables[this.ovenIndex];
                            if (sourceTable.title != targetCellInfo.OvenName)
                            {
                                MsgBoxHelper2.Error($"请切换至 [{targetCellInfo.OvenName}] åŒºæ‰«ç ï¼Œå½“前为 [{sourceTable.title}] åŒºï¼");
                                return;
                            }
                            var oven = TestResultView.nameTables[targetCellInfo.OvenName];
                            if (oven.cells.Length < targetCellInfo.Index + 1)
                            {
                                MsgBoxHelper2.Error($"穴位号 [{barcode}] è¶…出索引 {targetCellInfo.Index}");
                                return;
                            }
                            this.targetCellInfo = targetCellInfo;
                            positionText.Text = barcode;
                            //positionText.Focus();
                            //snrText.Text = "";
@@ -229,13 +244,14 @@
                    var EnableMes = ParameterManager.GetCommonParameterByName<bool>("EnableMes", false);
                    if (ParamSetting.CustomerCode == "LIXUN" && EnableMes)
                    {
                        var table = TestResultView.tables[this.ovenIndex];
                        var table = TestResultView.nameTables[targetCellInfo.OvenName];
                        // TestResultView.tables[this.ovenIndex]
                        string currentPn = table.CbProduct.Text;
                        /// æ£€æŸ¥æ ¼å¼
                        if (!SNValidator.ValidateSN(currentPn, snr, out string msg))
                        {
                            TestResultView.tables[this.ovenIndex].GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
                            table.GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
                            MsgBoxHelper2.Error($"检查产品条码异常:{msg}");
                            snrText.Text = string.Empty;
                            snrText.Focus();
@@ -246,7 +262,7 @@
                        string stationName = ProductManager.GetParameterByProductAndName(currentPn, "MES站位")?.Value;
                        if (string.IsNullOrEmpty(stationName))
                        {
                            TestResultView.tables[this.ovenIndex].GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
                            table.GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
                            MsgBoxHelper2.Error($"检查产品条码异常,当前产品[{currentPn}]未设置参数MES站位");
                            snrText.Text = string.Empty;
                            snrText.Focus();
@@ -256,10 +272,10 @@
                        /// æ£€æŸ¥PN
                        if (PNUtil.GetPN(ParamSetting.MesAddress, stationName, snr, out string pn, out msg))
                        {
                            if (pn != currentPn)
                            {
                                TestResultView.tables[this.ovenIndex].GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
                                table.GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
                                MsgBoxHelper2.Error($"当前测试料号为 [{currentPn}],扫码产品料号为 [{pn}] ä¸å…è®¸æµ‹è¯•");
                                snrText.Text = string.Empty;
                                snrText.Focus();
@@ -268,7 +284,7 @@
                        }
                        else
                        {
                            TestResultView.tables[this.ovenIndex].GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
                            table.GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
                            MsgBoxHelper2.Error($"检查产品条码异常:{msg}");
                            snrText.Text = string.Empty;
                            snrText.Focus();
@@ -280,7 +296,7 @@
                        {
                            if (needCheckIn != true)
                            {
                                TestResultView.tables[this.ovenIndex].GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
                                table.GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
                                MsgBoxHelper2.Error($"条码[{snr}]工序不在本站:{msg}");
                                snrText.Text = string.Empty;
                                snrText.Focus();
@@ -289,7 +305,7 @@
                        }
                        else
                        {
                            TestResultView.tables[this.ovenIndex].GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
                            table.GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
                            MsgBoxHelper2.Error($"条码[{snr}]CheckIn失败:{msg}");
                            snrText.Text = string.Empty;
                            snrText.Focus();
@@ -399,11 +415,11 @@
                    }
                    else
                    {
                        if (ParamSetting.CustomerCode != "LIXUN")
                        {
                            snrText.Focus();
                            return;
                        }
                        //if (ParamSetting.CustomerCode != "LIXUN")
                        //{
                        //    snrText.Focus();
                        //    return;
                        //}
                        string barcode = positionText.Text;
                        try
                        {
@@ -416,10 +432,21 @@
                                targetCellInfo = null;
                                return;
                            }
                            var sourceTable = TestResultView.tables[this.ovenIndex];
                            if (sourceTable.title != targetCellInfo.OvenName)
                            {
                                MsgBoxHelper2.Error($"请切换至 [{targetCellInfo.OvenName}] åŒºæ‰«ç ï¼Œå½“前为 [{sourceTable.title}] åŒºï¼");
                                positionText.Text = string.Empty;
                                positionText.Focus();
                                targetCellInfo = null;
                                return;
                            }
                            var oven = TestResultView.nameTables[targetCellInfo.OvenName];
                            if (oven.cells.Length < targetCellInfo.Index + 1)
                            {
                                MsgBoxHelper2.Error($"穴位下标 [{targetCellInfo.Index}] è¶…出索引");
                                MsgBoxHelper2.Error($"穴位号 [{barcode}] è¶…出索引 {targetCellInfo.Index}");
                                positionText.Text = string.Empty;
                                positionText.Focus();
                                targetCellInfo = null;
Plugin.TestResult/SingleCell.xaml.cs
@@ -297,6 +297,17 @@
        private string visualTestId;
        private Sqlite3Helper helper;
        private void InitCurrentTestHelper()
        {
            if (helper == null)
            {
                var now = DateTime.Now;
                string dateStr = now.ToString("yyyyMMdd");
                visualTestId = $"{title}_{now.ToString("yyyyMMddHHmmssfff")}";
                helper = CheckSqlHelper(dateStr);
            }
        }
        public void SetStatus(string status)
        {
            Dispatcher.Invoke(() =>
@@ -306,11 +317,8 @@
                if (oldStatus != "testing" && status == "testing")
                {
                    UpdateProbeCount(ProbeCount + 1);
                    var now = DateTime.Now;
                    string dateStr = now.ToString("yyyyMMdd");
                    visualTestId = $"{title}_{now.ToString("yyyyMMddHHmmssfff")}";
                    helper = CheckSqlHelper(dateStr);
                    InsertARecourd();
                    InitCurrentTestHelper();
                    InsertARecord();
                }
                if (oldStatus != "ok" && status == "ok")
@@ -325,7 +333,7 @@
            });
        }
        private void InsertARecourd()
        private void InsertARecord()
        {
            try
            {
@@ -336,7 +344,7 @@
            }
            catch (Exception ex)
            {
                ViewControl.Logger.Error($"Cell[{title}] SaveToDB Error.", ex);
                ViewControl.Logger.Error($"Cell[{title}] InsertARecord Error.", ex);
            }
        }
@@ -457,6 +465,7 @@
                    try
                    {
                        InitCurrentTestHelper();
                        string model = table?.CbProduct?.SelectedItem?.ToString();
                        sql = $@"INSERT INTO serialData (sn,model,station,position,step,limitDown,limitUp,unit,value,status,finalStatus,time,resultId) 
VALUES ('{sn}','{model}','','{this.title}','{info.step}','{info.limitDown}','{info.limitUp}','{info.unit}','{info.value}','{info.result}',
@@ -992,7 +1001,7 @@
        /// <param name="filePath"></param>
        public void SaveInfoToDB()
        {
            Dispatcher.BeginInvoke(new Action(() =>
            Dispatcher.Invoke(() =>
            {
                try
                {
@@ -1021,7 +1030,7 @@
                {
                    ViewControl.Logger.Error($"Cell[{title}] SaveToDB Error.", ex);
                }
            }));
            });
        }
        private void MenuItem_Click(object sender, RoutedEventArgs e)
@@ -1093,8 +1102,17 @@
        public bool LightGreen(bool on, out string msg)
        {
            return S7Context.WriteData(RunTimeVariablePool.DEFAULT_PLC_NAME,
                $"Oven{table.index + 1}_Cell{index + 1}_Light_OK", on, out msg);
            string key = $"Oven{table.index + 1}_Cell{index + 1}_Light_OK";
            bool res = S7Context.WriteData(RunTimeVariablePool.DEFAULT_PLC_NAME, key, on, out msg);
            if (res)
            {
                ViewControl.Logger.Debug($"Set Light [{key}] {on}.");
            }
            else
            {
                ViewControl.Logger.Debug($"Open Light [{key}] {on} Error.{msg}");
            }
            return res;
        }
        public bool LightRed(bool on, out string msg)
Plugin.TestResult/TestResultView.xaml.cs
@@ -2,13 +2,11 @@
using HMI.Core.Manager;
using HMI.Core.Util;
using HMI.UI.Util;
using NationalInstruments.TestStand.Interop.API;
using NationalInstruments.TestStand.Interop.UI.Ax;
using Plugin.TestResult_WPF.Model;
using Plugin.TsSupport;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
@@ -143,26 +141,19 @@
            InitializeComponent();
            executionWrapper = new TsExecutionWrapper(); //new TsExecutionWrapper();
            if (executionWrapper is TsExecutionWrapper tsWrapper)
        }
        private void ExecutionWrapper_UIMessageEvent(object sender, UIMessageEvent e)
        {
            try
            {
                tsWrapper.AxApplicationMgr.ExitApplication += this.axApplicationMgr_ExitApplication;
                tsWrapper.AxApplicationMgr.UIMessageEvent += this.axApplicationMgr_UIMessageEvent;
                tsWrapper.AxApplicationMgr.ExecutionClosed += this.axApplicationMgr_ExecutionClosed;
                ((System.ComponentModel.ISupportInitialize)(tsWrapper.AxApplicationMgr)).BeginInit();//myClient为第三方控件名
                host.Child = tsWrapper.AxApplicationMgr;
                ((System.ComponentModel.ISupportInitialize)(tsWrapper.AxApplicationMgr)).EndInit();
                HandleUIMessage(e.EventCode, Convert.ToInt16(e.NumberData), e.StrData, e.Data);
            }
            catch (Exception ex)
            {
            //testApp = TestAppManager.GetAxApplicationMgr();
            //testApp.ExitApplication += this.axApplicationMgr_ExitApplication;
            //testApp.UIMessageEvent += this.axApplicationMgr_UIMessageEvent;
            //testApp.ExecutionClosed += this.axApplicationMgr_ExecutionClosed;
            //((System.ComponentModel.ISupportInitialize)(this.testApp)).BeginInit();//myClient为第三方控件名
            //host.Child = testApp;
            //((System.ComponentModel.ISupportInitialize)(this.testApp)).EndInit();
            }
        }
        private void Refresh()
@@ -217,10 +208,26 @@
        /// <summary>
        /// å¯åŠ¨æµ‹è¯•åºåˆ—
        /// </summary>
        public void InitTestStandEngine()
        public void InitTestStandEngine(bool isTap)
        {
            try
            {
                if (isTap)
                {
                    executionWrapper = new TapWrapper();
                }
                else
                {
                    executionWrapper = new TsExecutionWrapper();
                }
                executionWrapper.UIMessageEvent += ExecutionWrapper_UIMessageEvent;
                if (executionWrapper is TsExecutionWrapper tsWrapper)
                {
                    ((System.ComponentModel.ISupportInitialize)(tsWrapper.AxApplicationMgr)).BeginInit();
                    host.Child = tsWrapper.AxApplicationMgr;
                    ((System.ComponentModel.ISupportInitialize)(tsWrapper.AxApplicationMgr)).EndInit();
                }
                //this.engine = this.testApp.GetEngine();
                //this.engine.PersistBreakpoints = false;
                //this.testApp.IsEditor = true;
@@ -258,7 +265,7 @@
            try
            {
                executionWrapper?.SetStationGlobalValue<bool>("StationState.StopTest", 0, true, true);
                try
                {
                    executionWrapper?.WaitForExit();
@@ -284,12 +291,14 @@
            }
        }
        [Obsolete]
        private void axApplicationMgr_ExitApplication(object sender, EventArgs e)
        {
            //Environment.ExitCode = testApp.ExitCode;
            //TSHelper.DoSynchronousGCForCOMObjectDestruction();
        }
        [Obsolete]
        private void axApplicationMgr_ExecutionClosed(object sender, _ApplicationMgrEvents_ExecutionClosedEvent e)
        {
            try
@@ -303,31 +312,23 @@
            }
        }
        private void axApplicationMgr_UIMessageEvent(object sender, _ApplicationMgrEvents_UIMessageEventEvent e)
        private void HandleUIMessage(int EventCase, int MethodCode, string strData, object xData)
        {
            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;
                    if (executionWrapper is TsExecutionWrapper tsWrapper)
                    {
                        tsWrapper.context = (SequenceContext)e.uiMsg.ActiveXData;
                    }
                    executionWrapper.SetContext(xData);
                    return;
                }
                if (MethodCode == 1)
                {
                    /// æ›´æ–°ç‚‰å­çŠ¶æ€
                    strData = e.uiMsg.StringData;
                    string[] datas_10014 = strData.Split(';');
                    int ovenIndex = int.Parse(datas_10014[0]);
@@ -338,7 +339,6 @@
                if (MethodCode == 2)
                {
                    /// æ›´æ–°ç‚‰å­æ•°é‡ä¿¡æ¯
                    strData = e.uiMsg.StringData;
                    string[] datas_10014 = strData.Split(';');
                    int ovenIndex = int.Parse(datas_10014[0]);
@@ -351,7 +351,6 @@
                if (MethodCode == 3)
                {
                    /// æ›´æ–°ç‚‰å­æ—¶é—´ä¿¡æ¯
                    strData = e.uiMsg.StringData;
                    string[] datas_10014 = strData.Split(';');
                    int ovenIndex = int.Parse(datas_10014[0]);
                    string startTime = datas_10014[1];
@@ -372,7 +371,6 @@
                if (MethodCode == 4)
                {
                    /// æ›´æ–°è€åŒ–炉底部状态栏
                    strData = e.uiMsg.StringData;
                    string[] datas_10014 = strData.Split(';');
                    int ovenIndex = int.Parse(datas_10014[0]);
@@ -383,7 +381,6 @@
                if (MethodCode == 5)
                {
                    /// é‡‡é›†ç‚‰æ¸©åº¦
                    strData = e.uiMsg.StringData;
                    string[] datas_10016 = strData.Split(';');
                    int ovenIndex = int.Parse(datas_10016[0]);
@@ -399,29 +396,28 @@
                }
                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);
                    Dispatcher.Invoke(() =>
                    {/// æ›´æ–°ç©´ä½çŠ¶æ€
                        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);
                    if (status_10015 == "OK" || status_10015 == "ok")
                    {
                        tables[ovenIndex].RefreshCount(1, 0);
                    }
                    else if (status_10015 == "ng" || status_10015 == "NG")
                    {
                        tables[ovenIndex].RefreshCount(0, 1);
                    }
                        if (status_10015 == "OK" || status_10015 == "ok")
                        {
                            tables[ovenIndex].RefreshCount(1, 0);
                        }
                        else if (status_10015 == "ng" || status_10015 == "NG")
                        {
                            tables[ovenIndex].RefreshCount(0, 1);
                        }
                    });
                    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]);
@@ -446,7 +442,6 @@
                if (MethodCode == 10)
                {
                    /// æ¸…空炉子的温度图表
                    strData = e.uiMsg.StringData;
                    int ovenIndex = int.Parse(strData);
                    tables[ovenIndex].ClearChartData();
                    return;
@@ -454,7 +449,6 @@
                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]);
@@ -465,7 +459,6 @@
                if (MethodCode == 12)
                {
                    /// ä¿å­˜ç‚‰ä½“温度文件
                    strData = e.uiMsg.StringData;
                    string[] datas = strData.Split(';');
                    int ovenIndex = int.Parse(datas[0]);
                    string folder = datas[1];
@@ -475,7 +468,6 @@
                if (MethodCode == 13)
                {
                    /// æ·»åŠ ç”µæµæ•°æ®
                    strData = e.uiMsg.StringData;
                    string[] datas = strData.Split(';');
                    int ovenIndex = int.Parse(datas[0]);
                    int cellIndex = int.Parse(datas[1]);
@@ -487,7 +479,6 @@
                if (MethodCode == 14)
                {
                    /// æ¸…空电流和电压数据
                    strData = e.uiMsg.StringData;
                    int ovenIndex = int.Parse(strData);
                    tables[ovenIndex].ClearCellData();
                    //tables[ovenIndex].ClearElectricData();
@@ -499,7 +490,6 @@
                if (MethodCode == 15)
                {
                    /// ä¿å­˜ç”µæµæ•°æ®
                    strData = e.uiMsg.StringData;
                    string[] datas = strData.Split(';');
                    int ovenIndex = int.Parse(datas[0]);
                    int cellIndex = int.Parse(datas[1]);
@@ -511,7 +501,6 @@
                if (MethodCode == 16)
                {
                    /// æ·»åŠ ç”µåŽ‹å›¾è¡¨æ•°æ®
                    strData = e.uiMsg.StringData;
                    string[] datas = strData.Split(';');
                    int ovenIndex = int.Parse(datas[0]);
                    int cellIndex = int.Parse(datas[1]);
@@ -523,7 +512,6 @@
                if (MethodCode == 17)
                {
                    /// ä¿å­˜ç”µåŽ‹æ•°æ®
                    strData = e.uiMsg.StringData;
                    string[] datas = strData.Split(';');
                    int ovenIndex = int.Parse(datas[0]);
                    int cellIndex = int.Parse(datas[1]);
@@ -535,7 +523,6 @@
                if (MethodCode == 18)
                {
                    /// ä¿å­˜ç©´ä½æµ‹è¯•数据到CSV
                    strData = e.uiMsg.StringData;
                    string[] datas = strData.Split(';');
                    int ovenIndex = int.Parse(datas[0]);
                    int cellIndex = int.Parse(datas[1]);
@@ -546,7 +533,6 @@
                if (MethodCode == 180)
                {
                    /// ä¿å­˜ç©´ä½æµ‹è¯•数据到CSV
                    strData = e.uiMsg.StringData;
                    string[] datas = strData.Split(';');
                    int ovenIndex = int.Parse(datas[0]);
                    int cellIndex = int.Parse(datas[1]);
@@ -558,7 +544,6 @@
                if (MethodCode == 19)
                {
                    /// ä¿å­˜ç©´ä½æµ‹è¯•数据到Sqlite
                    strData = e.uiMsg.StringData;
                    string[] datas = strData.Split(';');
                    int ovenIndex = int.Parse(datas[0]);
                    int cellIndex = int.Parse(datas[1]);
@@ -569,7 +554,6 @@
                if (MethodCode == 20)
                {
                    /// æ¸…空条码
                    strData = e.uiMsg.StringData;
                    int ovenIndex = int.Parse(strData);
                    tables[ovenIndex].ClearAllSN();
                    return;
@@ -578,7 +562,6 @@
                if (MethodCode == 21)
                {
                    /// æ·»åŠ æ°´æµå›¾è¡¨æ•°æ®
                    strData = e.uiMsg.StringData;
                    string[] datas = strData.Split(';');
                    int ovenIndex = int.Parse(datas[0]);
                    int cellIndex = int.Parse(datas[1]);
@@ -591,7 +574,6 @@
                if (MethodCode == 99)
                {
                    strData = e.uiMsg.StringData;
                    string[] datas = strData.Split(';');
                    int index = int.Parse(datas[0]);
                    if (MsgBoxHelper2.WarnChoose(datas[1], "提示") == MessageBoxResult.OK)
@@ -613,6 +595,14 @@
            }
        }
        private void axApplicationMgr_UIMessageEvent(object sender, _ApplicationMgrEvents_UIMessageEventEvent e)
        {
            int EventCase = Convert.ToInt32(e.uiMsg.Event);
            int MethodCode = Convert.ToInt16(e.uiMsg.NumericData);
            string strData = e.uiMsg.StringData;
            HandleUIMessage(EventCase, MethodCode, strData, e.uiMsg.ActiveXData);
        }
        public void SetStartFlag(int index, bool data)
        {
            string name = tables[index].title;
@@ -620,28 +610,11 @@
            string key = $"StationState.Start[{index}]";
            executionWrapper?.SetStationGlobalValue<bool>(key, 1, data);
            if (data == false)
            {
                key = $"StationState.Scanner[{index}]";
                executionWrapper?.SetStationGlobalValue<bool>(key, 1, false);
            }
        }
        public bool? GetStartFlag(int index)
        {
            string key = $"StationState.Start[{index}]";
            return executionWrapper.GetStationGlobalValue<bool>(key, 0);
        }
        public void ReleaseResource()
        {
            if (executionWrapper is TsExecutionWrapper tsWrapper)
            {
                tsWrapper.AxApplicationMgr.ExitApplication -= this.axApplicationMgr_ExitApplication;
                tsWrapper.AxApplicationMgr.UIMessageEvent -= this.axApplicationMgr_UIMessageEvent;
                tsWrapper.AxApplicationMgr.ExecutionClosed -= this.axApplicationMgr_ExecutionClosed;
            }
            executionWrapper.UIMessageEvent -= ExecutionWrapper_UIMessageEvent;
            foreach (var table in tables)
            {
Plugin.TestResult/ViewControl.xaml.cs
@@ -112,17 +112,62 @@
            timer.Elapsed += Timer_Elapsed;
            InitializeComponent();
            if (testResultView.executionWrapper is TsExecutionWrapper tsWrapper)
            {
                tsWrapper.AxApplicationMgr.ExitApplication += new EventHandler(this.axApplicationMgr_ExitApplication);
                tsWrapper.AxApplicationMgr.UIMessageEvent += new _ApplicationMgrEvents_UIMessageEventEventHandler(this.axApplicationMgr_UIMessageEvent);
                tsWrapper.AxApplicationMgr.ExecutionClosed += new _ApplicationMgrEvents_ExecutionClosedEventHandler(this.axApplicationMgr_ExecutionClosed);
            }
            //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 HandleUIMessage(int EventCase, int MethodCode, string strData, object xData)
        {
            try
            {
                try
                {
                    log.Debug($"EventCase: {EventCase}, MethodCode: {MethodCode}, StrData: {strData}");
                }
                catch
                {
                }
                if (EventCase == 8)
                {
                    IsRunning = false;
                    return;
                }
                if (EventCase != 10014) return;
                if (MethodCode == 0)
                {
                    /// è®¾ç½®å¥æŸ„
                    this.SequenceContext = (SequenceContext)xData;
                    return;
                }
                if (MethodCode == 8)
                {
                    /// æ›´æ–°MES状态
                    mesStatus = strData == "1";
                    return;
                }
                if (MethodCode == 9)
                {
                    /// æ›´æ–°ç”Ÿäº§çŠ¶æ€
                    produceStatus = strData;
                    return;
                }
            }
            catch
            { }
        }
        private void ExecutionWrapper_UIMessageEvent(object sender, UIMessageEvent e)
        {
            HandleUIMessage(e.EventCode, Convert.ToInt16(e.NumberData), e.StrData, e.Data);
        }
        private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
@@ -142,7 +187,6 @@
            string path = Path.Combine(BaseFolder, "Count.ini");
            testResultView.SetCountIniPath(path);
            testResultView.InitMainGrid();
            testResultView.InitTestStandEngine();
            path = Path.Combine(BaseFolder, "Scanner.ini");
            if (!File.Exists(path))
@@ -174,6 +218,12 @@
                testResultView.testSeqFile = string.Empty;
                FileName = string.Empty;
            }
            string type = Path.GetExtension(FileName);
            bool isTap = string.Equals(".tapplan", type, StringComparison.OrdinalIgnoreCase);
            testResultView.InitTestStandEngine(isTap);
            testResultView.executionWrapper.UIMessageEvent += ExecutionWrapper_UIMessageEvent;
            RefreshUIConfig();
        }
@@ -204,67 +254,6 @@
            {
                log.Error("RefreshUIConfig Error.", ex);
            }
        }
        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;
                try
                {
                    log.Debug($"EventCase: {EventCase}, MethodCode: {MethodCode}, StrData: {e.uiMsg.StringData}");
                }
                catch
                {
                }
                if (EventCase == 8)
                {
                    IsRunning = false;
                    return;
                }
                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 bool isLoaded;
Plugin.TsExecution/Plugin.TsExecution.csproj
@@ -88,6 +88,10 @@
      <SpecificVersion>False</SpecificVersion>
      <HintPath>..\Libs\NationalInstruments.TestStand.Interop.UI.AxControls.dll</HintPath>
    </Reference>
    <Reference Include="NPOI, Version=2.4.1.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1, processorArchitecture=MSIL">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>..\HMI.ParameterManage\bin\x64\Debug\NPOI.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>
@@ -264,6 +268,10 @@
      <Project>{f8b8b6b6-821b-4ff6-b556-9cab840ea57a}</Project>
      <Name>HMI.MQUtil</Name>
    </ProjectReference>
    <ProjectReference Include="..\SimpleUtil\HMI.Simple.Util\HMI.Simple.Util.csproj">
      <Project>{CF5BEBE9-2803-49C3-B773-73D082E0984A}</Project>
      <Name>HMI.Simple.Util</Name>
    </ProjectReference>
  </ItemGroup>
  <ItemGroup>
    <WCFMetadata Include="Connected Services\" />
Plugin.TsExecution/Properties/AssemblyInfo.cs
@@ -32,13 +32,13 @@
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
[assembly:ThemeInfo(
[assembly: ThemeInfo(
    ResourceDictionaryLocation.None, //主题特定资源词典所处位置
                             //(未在页面中找到资源时使用,
                             //或应用程序资源字典中找到时使用)
                                     //(未在页面中找到资源时使用,
                                     //或应用程序资源字典中找到时使用)
    ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置
                                      //(未在页面中找到资源时使用,
                                      //、应用程序或任何主题专用资源字典中找到时使用)
                                              //(未在页面中找到资源时使用,
                                              //、应用程序或任何主题专用资源字典中找到时使用)
)]
@@ -49,6 +49,6 @@
//      ç”Ÿæˆå·
//      ä¿®è®¢å·
//
[assembly: AssemblyVersion("1.0.0.3")]
[assembly: AssemblyFileVersion("1.0.0.3")]
[assembly: AssemblyVersion("1.0.0.7")]
[assembly: AssemblyFileVersion("1.0.0.7")]
[assembly: PluginClassName("Plugin.TsExecution.TsExecution")]
Plugin.TsExecution/ScreenTouch.xaml
@@ -8,7 +8,7 @@
             xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI" 
             xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UniformGrid x:Name="uniform" dp:UniformGridHelper.InnerBorder="0.3" Background="LightGray"
                 dp:UniformGridHelper.InnerBorderBrush="Transparent">
    <UniformGrid x:Name="uniform" dp:UniformGridHelper.InnerBorder="0.5"
                 dp:UniformGridHelper.InnerBorderBrush="#37375f">
    </UniformGrid>
</hb:NotifyControl>
Plugin.TsExecution/ScreenTouch.xaml.cs
@@ -1,4 +1,5 @@
using HMI.Base;
using HMI.UI.DpUtil;
using System;
using System.Collections.ObjectModel;
using System.Globalization;
@@ -108,6 +109,7 @@
                children[i] = border;
                uniform.Children.Add(border);
            }
            UniformGridHelper.RefreshGridInnerBorder(uniform);
        }
        public void Refresh(int rows, int columns)
@@ -140,6 +142,25 @@
            Values = temp;
        }
        public void DrawNewPoints(int[][] points)
        {
            var temp = Values == null ?
                new ObservableCollection<bool>(new bool[rows * columns]) :
                new ObservableCollection<bool>(Values);
            for (int i = 0; i < points.Length; i++)
            {
                int[] point = points[i];
                if (point == null)
                {
                    continue;
                }
                int index = point[1] * columns + point[0];
                //children[point[0]][point[1]].Background = Brushes.Green;
                temp[index] = true;
            }
            Values = temp;
        }
        private void PaintPoints()
        {
            for (int i = 0; i < Values.Count; i++)
Plugin.TsExecution/SingleControl.xaml
@@ -125,8 +125,7 @@
                </Label>
                <Label Content="{Binding Status}" VerticalAlignment="Center" VerticalContentAlignment="Center"
                       Padding="0"
                       Margin="0"
                       Padding="0" Margin="0"
                       HorizontalContentAlignment="Center"
                       Foreground="#FFF"
                       FontWeight="Bold"
Plugin.TsExecution/SingleControl.xaml.cs
@@ -1,14 +1,17 @@
using HMI.Core.Entity;
using HMI.Core.Manager;
using HMI.Core.Setting;
using HMI.Core.Util;
using HMI.Util;
using NationalInstruments.TestStand.Interop.API;
using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;
using Plugin.TsExecution.Entity;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Timers;
using System.Windows;
@@ -243,7 +246,7 @@
                Interval = 100
            };
            refreshTimer.Elapsed += RefreshTimer_Elapsed;
            ReadProbeCount();
            RefreshProbeCount(null);
            ReadCounter();
            InitializeComponent();
            try
@@ -397,37 +400,6 @@
            this.WriteCounter();
        }
        /// <summary>
        /// ä»Žini读取探针使用次数
        /// </summary>
        private void ReadProbeCount()
        {
            try
            {
                var num = IniFileHelper.ReadIniData("Socket" + this.index.ToString(), "ProbeCount", "0", this.CountIniPath);
                this.ProbeCount = int.Parse(num);
            }
            catch (Exception ex)
            {
                this.ProbeCount = 0;
            }
        }
        /// <summary>
        /// æ›´æ–°æŽ¢é’ˆä½¿ç”¨æ¬¡æ•°
        /// </summary>
        /// <param name="count"></param>
        private void UpdateProbeCount(int count)
        {
            /// å†™åˆ°é…ç½®æ–‡ä»¶
            IniFileHelper.WriteIniData("Socket" + this.index.ToString(), "ProbeCount", count.ToString(), this.CountIniPath);
            ProbeCount = count;
            //if (ProbeWarn <= 0) probeCountAlarm.Visibility = Visibility.Collapsed;
            //else if (count >= ProbeWarn) probeCountAlarm.Visibility = Visibility.Visible;
            //else probeCountAlarm.Visibility = Visibility.Collapsed;
        }
        private void WriteCounter()
        {
            IniFileHelper.WriteIniData("Socket" + this.index.ToString(), "Pass",
@@ -456,9 +428,48 @@
            this.UpdateTestCounter(null);
        }
        public void WriteToExcel(string path)
        {
            Dispatcher.Invoke(() =>
            {
                try
                {
                    string suffix = Path.GetExtension(path);
                    if (!string.Equals(".xlsx", suffix, StringComparison.OrdinalIgnoreCase) &&
                    !string.Equals(".xls", suffix, StringComparison.OrdinalIgnoreCase))
                    {
                        path = path.Replace(suffix, ".xls");
                    }
                    FileInfo fi = new FileInfo(path);
                    if (!fi.Directory.Exists)
                    {
                        fi.Directory.Create();  //文件不存在,创建文件
                    }
                    List<string[]> rows = new List<string[]>();
                    rows.Add(new string[] { "测试时间", "测试人", "测试设备", "条形码", "测试结果" });
                    rows.Add(new string[] { DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), UserInfo.User.Name, ParamSetting.StationName, Barcode, Status });
                    rows.Add(new string[] { "项目名称", "测试参数", "测试值", "上限", "下限", "单位", "结果" });
                    if (Datasource != null && Datasource.Count > 0)
                    {
                        foreach (var data in Datasource)
                        {
                            rows.Add(new string[] { data.Name, data.Step, data.Value, data.LimitUp, data.LimitDown, data.Unit, data.Result });
                        }
                    }
                    ExcelHelper.ExportExcel(path, rows);
                }
                catch (Exception ex)
                {
                    TsExecution.logger.Error("Write CSV File Failed.", ex);
                }
            });
        }
        public void WriteToCSV(string path)
        {
            Dispatcher.BeginInvoke(new Action(() =>
            Dispatcher.Invoke(() =>
            {
                try
                {
@@ -488,7 +499,7 @@
                {
                    TsExecution.logger.Error("Write CSV File Failed.", ex);
                }
            }));
            });
        }
        public void ShowPausePanel()
@@ -534,17 +545,7 @@
        private void SendSnr2Ts()
        {
            //try
            //{
            //    string snr = TbManualSnr.Text;
            //    SequenceFileContext?.StationGlobals.SetValString($"StationState.SN[{index}]", 1, snr);
            //    scannerPanel.Visibility = Visibility.Collapsed;
            //    TbManualSnr.Text = string.Empty;
            //}
            //catch (Exception ex)
            //{
            //    ToastHelper.Error(ex.Message);
            //}
        }
        private void pausePanel_Loaded(object sender, RoutedEventArgs e)
@@ -605,16 +606,59 @@
            screens[index].DrawPoint(x, y);
        }
        /// <summary>
        /// ç»˜å›¾ä¹‹å‰æ¸…空已有的
        /// </summary>
        /// <param name="index"></param>
        /// <param name="points"></param>
        public void DrawScreenPoints(int index, int[][] points)
        {
            screens[index].DrawPoints(points);
        }
        /// <summary>
        /// ç»˜å›¾ä¹‹å‰ä¸æ¸…空已有的
        /// </summary>
        /// <param name="index"></param>
        /// <param name="points"></param>
        public void DrawNewScreenPoints(int index, int[][] points)
        {
            screens[index].DrawNewPoints(points);
        }
        public void ClearScreenPoint(int index)
        {
            screens[index].Clear();
        }
        public void RefreshProbeCount(ProductData product)
        {
            try
            {
                var num = IniFileHelper.ReadIniData($"Socket{this.index.ToString()}", "ProbeCount", "0", this.CountIniPath);
                this.ProbeCount = int.Parse(num);
            }
            catch (Exception ex)
            {
                this.ProbeCount = 0;
            }
        }
        /// <summary>
        /// æ›´æ–°æŽ¢é’ˆä½¿ç”¨æ¬¡æ•°
        /// </summary>
        /// <param name="count"></param>
        private void UpdateProbeCount(int count)
        {
            /// å†™åˆ°é…ç½®æ–‡ä»¶
            IniFileHelper.WriteIniData("Socket" + this.index.ToString(), "ProbeCount", count.ToString(), this.CountIniPath);
            ProbeCount = count;
            //if (ProbeWarn <= 0) probeCountAlarm.Visibility = Visibility.Collapsed;
            //else if (count >= ProbeWarn) probeCountAlarm.Visibility = Visibility.Visible;
            //else probeCountAlarm.Visibility = Visibility.Collapsed;
        }
        public void RefreshProduct(ProductData product)
        {
            if (product == null)
Plugin.TsExecution/TsExecution.xaml.cs
@@ -6,11 +6,13 @@
using HMI.Core.Setting;
using HMI.Core.Util;
using HMI.MQUtil;
using HMI.Simple.Util;
using HMI.UI.Util;
using HMI.Util;
using log4net;
using NationalInstruments.TestStand.Interop.API;
using NationalInstruments.TestStand.Interop.UI.Ax;
using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;
using Plugin.TsSupport;
using ScottPlot;
using ScottPlot.Plottables;
@@ -20,8 +22,6 @@
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Forms.DataVisualization.Charting;
@@ -37,24 +37,23 @@
        /// <summary>
        /// TS å¼•擎
        /// </summary>
        private Engine engine = null;
        //private Engine engine = null;
        ///// <summary>
        ///// æµ‹è¯•序列
        ///// </summary>
        //private SequenceFile testSeq = null;
        ///// <summary>
        ///// æµ‹è¯•模型序列
        ///// </summary>
        //private SequenceFile modeSeq = null;
        //private Execution execution = null;
        private string testSeqFile = string.Empty;
        /// <summary>
        /// æµ‹è¯•序列
        /// </summary>
        private SequenceFile testSeq = null;
        /// <summary>
        /// æµ‹è¯•模型序列
        /// </summary>
        private SequenceFile modeSeq = null;
        public AxApplicationMgr AxApplicationMgr;
        private Execution execution = null;
        private SequenceContext sequenceContext = null;
        private Chart chart;
        private ITestExecutionWrapper testExecutionWrapper;
        #endregion
        public static ILog logger { get; private set; }
@@ -83,7 +82,6 @@
        private bool isLoadOK = false;
        private Timer GCTimer;
        private int PassAmount = 0;
        private int FailAmount = 0;
@@ -109,6 +107,7 @@
                    foreach (SingleControl socket in tsSockets)
                    {
                        socket.RefreshProduct(value);
                        // socket.RefreshProbeCount(value);
                    }
                }
                SetTestSeqFile(value);
@@ -182,9 +181,12 @@
            get => _enableCanLog;
            set
            {
                if (WriteConfig("EnableCanLog", value))
                if (RequirePassword() == true)
                {
                    Set(ref _enableCanLog, value);
                    if (WriteConfig("EnableCanLog", value))
                    {
                        Set(ref _enableCanLog, value);
                    }
                }
            }
        }
@@ -227,10 +229,13 @@
            get => _enableSimpleMode;
            set
            {
                if (WriteConfig("EnableSimpleMode", value))
                if (RequirePassword() == true)
                {
                    Set(ref _enableSimpleMode, value);
                    foreach (var cell in tsSockets) cell.SetSimpleUI(value);
                    if (WriteConfig("EnableSimpleMode", value))
                    {
                        Set(ref _enableSimpleMode, value);
                        foreach (var cell in tsSockets) cell.SetSimpleUI(value);
                    }
                }
            }
        }
@@ -241,10 +246,13 @@
            get => _ui_showInfoDock;
            set
            {
                if (WriteConfig("UI_ShowInfoDock", value))
                if (RequirePassword() == true)
                {
                    Set(ref _ui_showInfoDock, value);
                    detailPanel.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
                    if (WriteConfig("UI_ShowInfoDock", value))
                    {
                        Set(ref _ui_showInfoDock, value);
                        detailPanel.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
                    }
                }
            }
        }
@@ -268,8 +276,8 @@
                if (WriteIni("UI_CurrentWorkorder", value))
                {
                    Set(ref currentWorkorder, value);
                    engine?.Globals.SetValString("StationState.WorkOrder", 1, value);
                    engine?.CommitGlobalsToDisk(promptOnSaveConflicts: false);
                    testExecutionWrapper?.SetStationGlobalValue("StationState.WorkOrder", 1, value);
                    testExecutionWrapper?.CommitToDisk(false);
                }
            }
        }
@@ -280,9 +288,12 @@
            get => _DisableScanner;
            set
            {
                if (WriteConfig("DisableScanner", value))
                if (RequirePassword() == true)
                {
                    Set(ref _DisableScanner, value);
                    if (WriteConfig("DisableScanner", value))
                    {
                        Set(ref _DisableScanner, value);
                    }
                }
            }
        }
@@ -297,13 +308,10 @@
        public TsExecution()
        {
            DataContext = this;
            GCTimer = new Timer();
            InitializeComponent();
            InitChart();
            InitTsEngine();
            //初始化TestStand引擎
            this.InitTestStandEngine();
        }
        public override void SetBaseFolder(string folder)
@@ -373,8 +381,9 @@
            {
                UserInfo user = UserInfo.User;
                CurrentUserCode = user?.Code;
                engine?.Globals.SetValString("StationState.OPID", 1, CurrentUserCode);
                engine?.CommitGlobalsToDisk(promptOnSaveConflicts: false);
                testExecutionWrapper?.SetStationGlobalValue("StationState.OPID", 1, CurrentUserCode);
                testExecutionWrapper?.CommitToDisk(false);
            }
            catch (Exception ex)
            {
@@ -443,7 +452,7 @@
            Products = ProductManager.GetProducts<ObservableCollection<ProductData>>();
            if (Products != null && Products.Count > 0)
            {
                string lastModel = engine?.Globals.GetValString("StationState.ProductModel", 1);
                string lastModel = testExecutionWrapper?.GetStationGlobalValue<string>("StationState.ProductModel", 1);
                var tempProduct = Products[0];
                if (!string.IsNullOrEmpty(lastModel))
                {
@@ -487,25 +496,58 @@
        /// </summary>
        private void InitTsEngine()
        {
            AxApplicationMgr = TestAppManager.GetAxApplicationMgr();
            if (ParamSetting.EngineType == "Tap")
            {
                testExecutionWrapper = new TapWrapper();
            }
            else
            {
                testExecutionWrapper = new TeststandWrapper();
            }
            //AxApplicationMgr. += AxApplicationMgr_EndExecution;
            AxApplicationMgr.EndExecution += AxApplicationMgr_EndExecution;
            AxApplicationMgr.ExitApplication += this.AxApplicationMgr_ExitApplication;
            AxApplicationMgr.UIMessageEvent += this.AxApplicationMgr_UIMessageEvent;
            AxApplicationMgr.ExecutionClosed += this.AxApplicationMgr_ExecutionClosed;
            ((System.ComponentModel.ISupportInitialize)(AxApplicationMgr)).BeginInit();
            host.Child = AxApplicationMgr;
            ((System.ComponentModel.ISupportInitialize)(AxApplicationMgr)).EndInit();
            testExecutionWrapper.EndExecutionEvent += TestExecutionWrapper_EndExecutionEvent;
            testExecutionWrapper.UIMessageEvent += TestExecutionWrapper_UIMessageEvent;
            if (testExecutionWrapper is TeststandWrapper tsw)
            {
                var AxApplicationMgr = tsw.AxApplicationMgr;
                ((System.ComponentModel.ISupportInitialize)(AxApplicationMgr)).BeginInit();
                host.Child = AxApplicationMgr;
                ((System.ComponentModel.ISupportInitialize)(AxApplicationMgr)).EndInit();
            }
            testExecutionWrapper.Init();
        }
        private volatile bool canSwitchModel = false;
        private volatile Action switchAction = null;
        private void TestExecutionWrapper_UIMessageEvent(object sender, UIMessageEvent e)
        {
            try
            {
                int eventCode = Convert.ToInt32(e.EventCode);
                int index = Convert.ToInt16(e.NumberData);//SocketIndex
                string strData = e.StrData;
        private void AxApplicationMgr_EndExecution(object sender, _ApplicationMgrEvents_EndExecutionEvent e)
                try
                {
                    log.Debug($"Event: {eventCode}, NumericData: {index}, StringData: {strData}");
                }
                catch
                {
                }
                if (eventCode == 10001)
                {
                    testExecutionWrapper?.SetContext((SequenceContext)e.Data);
                    tsSockets[index].SequenceFileContext = (SequenceContext)e.Data;
                    return;
                }
                HandleUIMessage(eventCode, index, strData);
            }
            catch (Exception ex)
            {
                log.Warn("Ts Event Error.", ex);
            }
        }
        private void TestExecutionWrapper_EndExecutionEvent(object sender, ExecutionEndEvent e)
        {
            log.Info("AxApplicationMgr_EndExecution");
            if (ReleaseSequenceFile())
@@ -520,15 +562,8 @@
            }
        }
        /// <summary>
        /// è¿›ç¨‹ç»“束事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void AxApplicationMgr_ExecutionClosed(object sender, _ApplicationMgrEvents_ExecutionClosedEvent e)
        {
            //ReleaseSequenceFile();
        }
        private volatile bool canSwitchModel = false;
        private volatile Action switchAction = null;
        private void PluginControl_Loaded(object sender, RoutedEventArgs e)
        {
@@ -547,17 +582,6 @@
                {
                    log.Error("TsExecution Loaded Failed.", ex);
                    MsgBoxHelper.Error(this, $"{LangUtil.Get("Ts.UserControlLoadFailed")}: {ex.Message}");
                }
                try
                {
                    if (Environment.Version.ToString() != this.AxApplicationMgr.GetEngine().DotNetCLRVersion)
                        this.GCTimer.Enabled = true;
                }
                catch (Exception ex)
                {
                    log.Error("TsExecution GCTimer Start Failed", ex);
                    MsgBoxHelper.Error(this, $"{LangUtil.Get("Ts.LoadFailed")}: {ex.Message}");
                }
                Dispatcher.Invoke(() =>
@@ -653,26 +677,6 @@
        }
        /// <summary>
        /// åˆå§‹åŒ–TestStand引擎
        /// </summary>
        private void InitTestStandEngine()
        {
            try
            {
                this.engine = this.AxApplicationMgr.GetEngine();
                this.engine.PersistBreakpoints = false;
                this.AxApplicationMgr.IsEditor = true;
                this.AxApplicationMgr.LoginOnStart = false;
                this.AxApplicationMgr.Start();
                //this.modeSeq = this.engine.GetSequenceFileEx(@"C:\Program Files\National Instruments\TestStand 2020\Components\Models\TestStandModels\ParallelModel.seq");
            }
            catch (Exception ex)
            {
                throw new Exception(LangUtil.Get("Ts.InitTestStandEngineFail"), ex);
            }
        }
        /// <summary>
        /// è¯»å–Count配置文件的pass和fail数
        /// </summary>
        /// <param name="pass">合格数</param>
@@ -695,7 +699,7 @@
        /// <param name="fail"></param>
        private void ReadConfig()
        {
            ParameterData value;
            HMI.Core.Entity.ParameterData value;
            bool flag = false;
@@ -955,7 +959,7 @@
                    foreach (var ts in tsSockets)
                    {
                        ts?.SequenceFileContext?.StationGlobals.SetValBoolean($"SystemSetting.{name}", 1, value);
                        if (ts?.SequenceFileContext?.Sequence != null)
                        if (name == "EnableDebugMode" && ts?.SequenceFileContext?.Sequence != null)
                        {
                            ts.SequenceFileContext.Sequence.FailureAction =
                                value ? FailureActions.FailureAction_None : FailureActions.FailureAction_GotoCleanup;
@@ -1065,7 +1069,7 @@
            if (!isLoadOK) return;
            try
            {
                engine?.Globals.SetValBoolean("StationState.StopTest", 1, false);
                testExecutionWrapper?.SetStationGlobalValue("StationState.StopTest", 1, false);
            }
            catch (Exception ex)
            {
@@ -1085,9 +1089,10 @@
                        return;
                    }
                    this.testSeqFile = path;
                    this.modeSeq = this.engine.GetSequenceFileEx(tsModelPath);
                    this.testSeq = this.engine.GetSequenceFileEx(this.testSeqFile, 2, TypeConflictHandlerTypes.ConflictHandler_UseGlobalType);
                    this.execution = this.engine.NewExecution(this.testSeq, "Test UUTs", this.modeSeq, false, 0, null, null, null);
                    testExecutionWrapper?.StartSequence(this.testSeqFile, this.tsModelPath);
                    //this.modeSeq = this.engine.GetSequenceFileEx(tsModelPath);
                    //this.testSeq = this.engine.GetSequenceFileEx(this.testSeqFile, 2, TypeConflictHandlerTypes.ConflictHandler_UseGlobalType);
                    //this.execution = this.engine.NewExecution(this.testSeq, "Test UUTs", this.modeSeq, false, 0, null, null, null);
                    IsTesting = true;
                }
                catch (Exception ex)
@@ -1104,11 +1109,12 @@
                //MaskWindow mask = new MaskWindow();
                try
                {
                    engine?.Globals.SetValString("StationState.ProductModel", 1, CurrentProductModel);
                    engine?.CommitGlobalsToDisk(promptOnSaveConflicts: false);
                    this.modeSeq = this.engine.GetSequenceFileEx(tsModelPath);
                    this.testSeq = this.engine.GetSequenceFileEx(this.testSeqFile, 2, TypeConflictHandlerTypes.ConflictHandler_UseGlobalType);
                    this.execution = this.engine.NewExecution(this.testSeq, "Test UUTs", this.modeSeq, false, 0, null, null, null);
                    testExecutionWrapper?.SetStationGlobalValue("StationState.ProductModel", 1, CurrentProductModel);
                    testExecutionWrapper?.CommitToDisk(false);
                    testExecutionWrapper?.StartSequence(this.testSeqFile, this.tsModelPath);
                    //this.modeSeq = this.engine.GetSequenceFileEx(tsModelPath);
                    //this.testSeq = this.engine.GetSequenceFileEx(this.testSeqFile, 2, TypeConflictHandlerTypes.ConflictHandler_UseGlobalType);
                    //this.execution = this.engine.NewExecution(this.testSeq, "Test UUTs", this.modeSeq, false, 0, null, null, null);
                    IsTesting = true;
                }
                catch (Exception ex)
@@ -1143,21 +1149,14 @@
        {
            try
            {
                //if (this.tsSockets.Count > 0)
                //{
                //    for (int i = 0; i < tsSockets.Count; i++)
                //    {
                //        var context = this.tsSockets[i].SequenceFileContext;
                //        context?.StationGlobals.SetValBoolean("StationState.StopTest", 1, true);
                //    }
                //}
                engine.Globals.SetValBoolean("StationState.StopTest", 1, true);
                if (this.execution == null) return true;
                testExecutionWrapper.SetStationGlobalValue("StationState.StopTest", 1, true);
                //bool res = this.execution.WaitForEndEx(-1, true, null, null);
                bool res = this.execution?.WaitForEndEx(-1, true, Type.Missing, null) == true;
                Marshal.ReleaseComObject(this.execution);
                this.execution = null;
                bool res = this.testExecutionWrapper.WaitForExit();
                //if (this.execution != null)
                //    Marshal.ReleaseComObject(this.execution);
                //this.execution = null;
                return res;
            }
            catch (Exception ex)
@@ -1217,22 +1216,229 @@
            //    }).Info(LangUtil.Get("Ts.SequenceIsStarting"));
        }
        /// <summary>
        /// é€€å‡ºåº”用
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void AxApplicationMgr_ExitApplication(object sender, EventArgs e)
        {
            //Environment.ExitCode = this.AxApplicationMgr.ExitCode;
            //this.Close();
        }
        public void UpdatePositionStatus(int index, string status)
        {
            this.tsSockets[index].UpdateStatus(status);
            OnStatusPublished(index, status);
            this.UpdateTestCounter();
        }
        private void OnStatusPublished(int index, string status)
        {
            if (ParamSetting.CustomerCode != "SOUND")
            {
                return;
            }
            if (status == "Passed" || status == "Failed")
            {
                SaveSOUNDExcel(index, status);
            }
        }
        private string ConvertStatus(string status)
        {
            if (string.Equals(status, "passed", StringComparison.OrdinalIgnoreCase))
            {
                return "Pass";
            }
            else
            {
                return "Fail";
            }
        }
        #region æ¡‘德定制
        private ICell CreateCell(ISheet sheet, int r, int c, ICellStyle style)
        {
            var row = sheet.GetRow(r);
            if (row == null)
            {
                row = sheet.CreateRow(r);
            }
            var cell = row.GetCell(c);
            if (cell == null)
            {
                cell = row.CreateCell(c);
            }
            if (style != null)
            {
                cell.CellStyle = style;
            }
            return cell;
        }
        private string GetOutputFolder()
        {
            var value = ParameterManager.GetCommonParameterByName("SOUND_Output");
            if (value != null)
            {
                return value.Value;
            }
            return ParamSetting.DataLogFolder;
        }
        private void SaveSOUNDExcel(int index, string status)
        {
            lock (this)
            {
                try
                {
                    string outputFolder = GetOutputFolder();
                    var socket = this.tsSockets[index];
                    DateTime testTime = DateTime.Now;
                    string filePath = Path.Combine(outputFolder, $"{testTime.ToString("yy-MM-dd")}_{CurrentProductModel}.xls");
                    if (!File.Exists(filePath))
                    {
                        Dictionary<string, int> columnMap = new Dictionary<string, int>();
                        HSSFWorkbook workbook = new HSSFWorkbook();
                        ISheet sheet = workbook.CreateSheet("Sheet1");
                        ICellStyle border = workbook.CreateCellStyle();
                        border.BorderBottom = NPOI.SS.UserModel.BorderStyle.Thin;
                        border.BorderTop = NPOI.SS.UserModel.BorderStyle.Thin;
                        border.BorderLeft = NPOI.SS.UserModel.BorderStyle.Thin;
                        border.BorderRight = NPOI.SS.UserModel.BorderStyle.Thin;
                        border.BottomBorderColor = NPOI.HSSF.Util.HSSFColor.Black.Index;
                        border.TopBorderColor = NPOI.HSSF.Util.HSSFColor.Black.Index;
                        border.LeftBorderColor = NPOI.HSSF.Util.HSSFColor.Black.Index;
                        border.RightBorderColor = NPOI.HSSF.Util.HSSFColor.Black.Index;
                        ICellStyle red = workbook.CreateCellStyle();
                        red.CloneStyleFrom(border);
                        red.FillForegroundColor = NPOI.HSSF.Util.HSSFColor.Red.Index;
                        red.FillPattern = FillPattern.SolidForeground;
                        ICellStyle green = workbook.CreateCellStyle();
                        green.CloneStyleFrom(border);
                        green.FillForegroundColor = NPOI.HSSF.Util.HSSFColor.BrightGreen.Index;
                        green.FillPattern = FillPattern.SolidForeground;
                        CreateCell(sheet, 1, 0, border);
                        CreateCell(sheet, 1, 1, border);
                        CreateCell(sheet, 1, 2, border);
                        CreateCell(sheet, 1, 3, border);
                        CreateCell(sheet, 1, 4, border);
                        CreateCell(sheet, 2, 0, border);
                        CreateCell(sheet, 2, 1, border);
                        CreateCell(sheet, 2, 2, border);
                        CreateCell(sheet, 2, 3, border);
                        CreateCell(sheet, 2, 4, border).SetCellValue("上限");
                        CreateCell(sheet, 3, 0, border).SetCellValue("测试时间");
                        CreateCell(sheet, 3, 1, border).SetCellValue("测试人");
                        CreateCell(sheet, 3, 2, border).SetCellValue("测试设备");
                        CreateCell(sheet, 3, 3, border).SetCellValue("条形码");
                        CreateCell(sheet, 3, 4, border).SetCellValue("下限");
                        int column = 5;
                        int row = 4;
                        CreateCell(sheet, row, 0, border).SetCellValue(testTime.ToString("*HH:mm:ss*"));
                        CreateCell(sheet, row, 1, border).SetCellValue(UserInfo.User.Name);
                        CreateCell(sheet, row, 2, border).SetCellValue(ParamSetting.StationName);
                        CreateCell(sheet, row, 3, border).SetCellValue(socket.Barcode);
                        CreateCell(sheet, row, 4, status == "Passed" ? green : red).SetCellValue(ConvertStatus(status));
                        foreach (var data in socket.Datasource)
                        {
                            int col = column;
                            if (columnMap.ContainsKey(data.Name))
                            {
                                col = columnMap[data.Name];
                            }
                            else
                            {
                                columnMap[data.Name] = column++;
                            }
                            CreateCell(sheet, 1, col, border).SetCellValue(data.Name);
                            CreateCell(sheet, 2, col, border).SetCellValue(data.LimitUp);
                            CreateCell(sheet, 3, col, border).SetCellValue(data.LimitDown);
                            CreateCell(sheet, row, col, data.Result == "Failed" ? red : border).SetCellValue(data.Value);
                        }
                        using (FileStream fs = new FileStream(filePath, FileMode.OpenOrCreate))
                        {
                            workbook.Write(fs);
                        }
                    }
                    else
                    {
                        using (FileStream fs = File.OpenRead(filePath))
                        {
                            IWorkbook workbook = new HSSFWorkbook(fs);
                            ICellStyle border = workbook.CreateCellStyle();
                            border.BorderBottom = NPOI.SS.UserModel.BorderStyle.Thin;
                            border.BorderTop = NPOI.SS.UserModel.BorderStyle.Thin;
                            border.BorderLeft = NPOI.SS.UserModel.BorderStyle.Thin;
                            border.BorderRight = NPOI.SS.UserModel.BorderStyle.Thin;
                            border.BottomBorderColor = NPOI.HSSF.Util.HSSFColor.Black.Index;
                            border.TopBorderColor = NPOI.HSSF.Util.HSSFColor.Black.Index;
                            border.LeftBorderColor = NPOI.HSSF.Util.HSSFColor.Black.Index;
                            border.RightBorderColor = NPOI.HSSF.Util.HSSFColor.Black.Index;
                            ICellStyle red = workbook.CreateCellStyle();
                            red.CloneStyleFrom(border);
                            red.FillForegroundColor = NPOI.HSSF.Util.HSSFColor.Red.Index;
                            red.FillPattern = FillPattern.SolidForeground;
                            ICellStyle green = workbook.CreateCellStyle();
                            green.CloneStyleFrom(border);
                            green.FillForegroundColor = NPOI.HSSF.Util.HSSFColor.BrightGreen.Index;
                            green.FillPattern = FillPattern.SolidForeground;
                            ISheet sheet = workbook.GetSheet("Sheet1");
                            Dictionary<string, int> columnMap = new Dictionary<string, int>();
                            var titleRow = sheet.GetRow(1);
                            for (int i = 0; i < titleRow.LastCellNum; i++)
                            {
                                if (!string.IsNullOrEmpty(titleRow.GetCell(i).StringCellValue))
                                {
                                    columnMap[titleRow.GetCell(i).StringCellValue] = i;
                                }
                            }
                            int row = sheet.LastRowNum + 1;
                            CreateCell(sheet, row, 0, border).SetCellValue(testTime.ToString("*HH:mm:ss*"));
                            CreateCell(sheet, row, 1, border).SetCellValue(UserInfo.User.Name);
                            CreateCell(sheet, row, 2, border).SetCellValue(ParamSetting.StationName);
                            CreateCell(sheet, row, 3, border).SetCellValue(socket.Barcode);
                            CreateCell(sheet, row, 4, status == "Passed" ? green : red).SetCellValue(ConvertStatus(status));
                            foreach (var data in socket.Datasource)
                            {
                                int col = titleRow.LastCellNum;
                                if (columnMap.ContainsKey(data.Name))
                                {
                                    col = columnMap[data.Name];
                                }
                                else
                                {
                                    columnMap[data.Name] = col;
                                }
                                CreateCell(sheet, 1, col, border).SetCellValue(data.Name);
                                CreateCell(sheet, 2, col, border).SetCellValue(data.LimitUp);
                                CreateCell(sheet, 3, col, border).SetCellValue(data.LimitDown);
                                CreateCell(sheet, row, col, data.Result == "Failed" ? red : border).SetCellValue(data.Value);
                            }
                            using (FileStream nfs = new FileStream(filePath, FileMode.OpenOrCreate))
                            {
                                workbook.Write(nfs);
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    log.Error("SOUND Export Excel Data Error.", ex);
                }
            }
        }
        #endregion
        public void UpdatePositionProcessValue(int index, int value)
        {
@@ -1296,9 +1502,11 @@
                sqls.Add($@"INSERT INTO serialData (sn,model,station,step,position,limitDown,limitUp,unit,value,status,finalStatus,consumeTime,totalConsumeTime,time) VALUES
('{snr}','{model}','','{data.Name}','{index}','{data.LimitDown}','{data.LimitUp}','{data.Unit}','{data.Value}','{data.Result}','{socket.Status}','{data.ConsumeTime}','{socket.ElapseTime}','{time}')");
            }
            sqls.Add($@"INSERT INTO testResult (sn,model,station,result,time,position,status) VALUES
('{snr}','{model}','','{socket.Status}','{time}','{index}','{status}')");
            sqliteHelper.ExecuteBatchSql(sqls);
            var ssql = $@"INSERT INTO testResult (sn,model,station,result,time,position,status) VALUES
('{snr}','{model}','','{socket.Status}','{time}','{index}','{status}')";
            sqliteHelper.ExecuteSql(ssql);
        }
        private void HandleUIMessage(int eventCode, int index, string strData)
@@ -1385,6 +1593,26 @@
                    this.tsSockets[index].SetTestProcess(ProcessVal);
                    break;
                case 10031://导出CSV
                    if (ParamSetting.CustomerCode == "SOUND")
                    {
                        string[] datas = strData.Split(';');
                        this.tsSockets[index].WriteToExcel(datas[0]);
                        if (datas.Length >= 2)
                        {
                            SavePositionDB(index, datas[1]);
                        }
                        else
                        {
                            Dispatcher.Invoke(() =>
                            {
                                var sss = this.tsSockets[index].Status;
                                SavePositionDB(index, sss);
                            });
                        }
                        return;
                    }
                    if (string.IsNullOrEmpty(strData))
                    {
                        SaveTemplateCSV(index);
@@ -1400,8 +1628,11 @@
                        }
                        else
                        {
                            var sss = this.tsSockets[index].Status;
                            SavePositionDB(index, sss);
                            Dispatcher.Invoke(() =>
                            {
                                var sss = this.tsSockets[index].Status;
                                SavePositionDB(index, sss);
                            });
                        }
                    }
                    catch (Exception ex)
@@ -1435,6 +1666,22 @@
                        points[i - 1] = new int[2] { int.Parse(strs[0]), int.Parse(strs[1]) };
                    }
                    this.tsSockets[index].DrawScreenPoints(int.Parse(arr_10035[0]), points);
                    break;
                case 100350:
                    string[] arr_100350 = strData.Split(';');
                    int[][] points_0 = new int[arr_100350.Length - 1][];
                    for (int i = 1; i < arr_100350.Length; i++)
                    {
                        if (string.IsNullOrEmpty(arr_100350[i]))
                        {
                            continue;
                        }
                        string[] strs = arr_100350[i].Split(',');
                        points_0[i - 1] = new int[2] { int.Parse(strs[0]), int.Parse(strs[1]) };
                    }
                    this.tsSockets[index].DrawNewScreenPoints(int.Parse(arr_100350[0]), points_0);
                    break;
                case 10036:
                    this.tsSockets[index].ClearScreenPoint(int.Parse(strData));
@@ -1481,8 +1728,8 @@
                            {
                                log?.Info("开始自动换型");
                                SelectedProduct = target;
                                engine?.Globals.SetValString($"CacheSNR[{index}]", 1, snr);
                                engine?.CommitGlobalsToDisk(false);
                                testExecutionWrapper?.SetStationGlobalValue($"CacheSNR[{index}]", 1, snr);
                                testExecutionWrapper?.CommitToDisk(false);
                                StartClick();
                            }
                            catch (Exception ex)
@@ -1498,43 +1745,7 @@
        private void ShowAlarm(AlarmText alarm)
        {
            AlarmShowHelper.ShowAlarm(alarm);
            mSMQHelper.Send("Alarm", $"ATE测试 {alarm.AlarmInfo}");
        }
        /// <summary>
        /// TestStand用户交互事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void AxApplicationMgr_UIMessageEvent(object sender, _ApplicationMgrEvents_UIMessageEventEvent e)
        {
            try
            {
                int eventCode = Convert.ToInt32(e.uiMsg.Event);
                int index = Convert.ToInt16(e.uiMsg.NumericData);//SocketIndex
                string strData = e.uiMsg.StringData;
                try
                {
                    log.Debug($"Event: {eventCode}, NumericData: {index}, StringData: {strData}");
                }
                catch
                {
                }
                if (eventCode == 10001)
                {
                    sequenceContext = (SequenceContext)e.uiMsg.ActiveXData;
                    tsSockets[index].SequenceFileContext = (SequenceContext)e.uiMsg.ActiveXData;
                    return;
                }
                HandleUIMessage(eventCode, index, strData);
            }
            catch (Exception ex)
            {
                log.Warn("Ts Event Error.", ex);
            }
            mSMQHelper.Send("Alarm", "PC", $"ATE测试 {alarm.AlarmInfo}", 2);
        }
        private void UpdateTestCounter()
@@ -1557,7 +1768,7 @@
                return;
            }
            string name = product.Name;
            List<ParameterData> parameters = ProductManager.GetParametersByProductName(name);
            List<HMI.Core.Entity.ParameterData> parameters = ProductManager.GetParametersByProductName(name);
            testSeqFile = parameters.FirstOrDefault(p => p.Name == "TestSequence")?.Value?.ToString();
            if (AutoExecuteSeq)
            {
@@ -1607,32 +1818,13 @@
            {
                lock (this)
                {
                    if (this.testSeq == null)
                    {
                        return true;
                    }
                    //execution.Abort();
                    int count = 10;
                    while (count > 0)
                    {
                        try
                        {
                            if (this.testSeq == null)
                                return true;
                            if (this.engine?.ReleaseSequenceFileEx(this.testSeq, 4) == true)
                            {
                                Marshal.ReleaseComObject(this.testSeq);
                                this.testSeq = null;
                                // GC.Collect();
                            }
                            if (this.engine?.ReleaseSequenceFileEx(this.modeSeq, 4) == true)
                            {
                                Marshal.ReleaseComObject(this.modeSeq);
                                this.modeSeq = null;
                            }
                            this.engine?.UnloadAllModules();
                            return this.testSeq == null && this.modeSeq == null;
                            return testExecutionWrapper.ReleaseSeqFile();
                        }
                        catch (Exception ex)
                        {
Plugin.TsSupport/ITestExecutionWrapper.cs
@@ -1,5 +1,4 @@
using NationalInstruments.TestStand.Interop.API;
using System;
using System;
namespace Plugin.TsSupport
{
Plugin.TsSupport/Plugin.TsSupport.csproj
@@ -72,6 +72,11 @@
      <EmbedInteropTypes>True</EmbedInteropTypes>
      <HintPath>..\Libs\NationalInstruments.TestStand.Interop.API.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>
@@ -88,6 +93,7 @@
  </ItemGroup>
  <ItemGroup>
    <Compile Include="ITestExecutionWrapper.cs" />
    <Compile Include="TapWrapper.cs" />
    <Compile Include="TestAppManager.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
    <Compile Include="TeststandWrapper.cs" />
Plugin.TsSupport/TapWrapper.cs
New file
@@ -0,0 +1,238 @@
using System;
//using AddInPlugin;
//using AddInPlugin.Util;
//using Microsoft.Win32;
//using OpenTap;
//using OpenTap.Addin;
//using OpenTap.Addin.Util;
//using System.ComponentModel;
//using System.IO;
namespace Plugin.TsSupport
{
    public class TapWrapper : ITestExecutionWrapper
    {
        public event EventHandler<UIMessageEvent> UIMessageEvent;
        public event EventHandler<ExecutionEndEvent> EndExecutionEvent;
        public void CommitToDisk(bool option)
        {
        }
        public T GetFileGlobalValue<T>(string key, int option)
        {
            return default(T);
        }
        public T GetStationGlobalValue<T>(string key, int option)
        {
            return default(T);
        }
        public object Init()
        {
            return null;
        }
        public bool ReleaseSeqFile()
        {
            return true;
        }
        public void SetContext(object context)
        {
        }
        public void SetFileGlobalValue<T>(string key, int option, T value)
        {
        }
        public void SetStationGlobalValue<T>(string key, int option, T value, bool inContext = false)
        {
        }
        public object StartSequence(string filePath, string modelPath)
        {
            return null;
        }
        public bool WaitForExit()
        {
            return true;
        }
    }
    //public class Net47Getter : IRegGetter
    //{
    //    public string GetAppPath(string keyPath)
    //    {
    //        using (RegistryKey key = Registry.CurrentUser.OpenSubKey(keyPath))
    //        {
    //            if (key == null)
    //            {
    //                throw new Exception("注册表不存在,请先运行一次编辑器");
    //            }
    //            string value = key.GetValue("Location")?.ToString();
    //            if (string.IsNullOrEmpty(value))
    //            {
    //                throw new Exception("注册表不存在,请先运行一次编辑器");
    //            }
    //            return value;
    //        }
    //    }
    //}
    //public class TapWrapper : ITestExecutionWrapper, IResultListener
    //{
    //    TapThread testPlanThread;
    //    TestPlan Plan;
    //    TestPlanRun PlanRun;
    //    public event PropertyChangedEventHandler PropertyChanged;
    //    public event EventHandler<UIMessageEvent> UIMessageEvent;
    //    public event EventHandler<ExecutionEndEvent> EndExecutionEvent;
    //    public string Name { get; set; }
    //    public bool IsConnected => true;
    //    public object Init()
    //    {
    //        RegUtil.Register(new Net47Getter());
    //        StationGlobalsManager.Register(new DefaultStationGlobalsManager());
    //        VariableResolverFactory.Register(new DefaultVariableResolverFactory());
    //        ResultSettings.Current.Add(this);
    //        return null;
    //    }
    //    private void PostUIMsgHandler_OnPostUIMsg(object sender, PostUIMsg e)
    //    {
    //        UIMessageEvent?.Invoke(this, new UIMessageEvent()
    //        {
    //            Origin = e,
    //            EventCode = e.EventCode,
    //            BoolData = e.BoolData,
    //            NumberData = e.NumberData,
    //            StrData = e.StringData
    //        });
    //    }
    //    public object StartSequence(string filePath, string modelPath)
    //    {
    //        Plan = TestPlan.Load(filePath);
    //        testPlanThread = TapThread.Start(() =>
    //        {
    //            var res = ResultSettings.Current;
    //            Plan.Execute(ResultSettings.Current, null);
    //        });
    //        return Plan;
    //    }
    //    public T GetFileGlobalValue<T>(string key, int option)
    //    {
    //        return PlanRun.Resolve<T>(null, $"FileGlobals.{key}");
    //    }
    //    public T GetStationGlobalValue<T>(string key, int option)
    //    {
    //        if (PlanRun == null)
    //        {
    //            return StationGlobalsManager.GetValue<T>(key);
    //        }
    //        return PlanRun.Resolve<T>(null, $"StationGlobals.{key}");
    //    }
    //    public T GetValue<T>(string key, int option)
    //    {
    //        return default(T);
    //    }
    //    public void SetValue<T>(string key, T value, int option)
    //    {
    //    }
    //    public void SetFileGlobalValue<T>(string key, int option, T value)
    //    {
    //        PlanRun.Update(null, $"FileGlobals.{key}", value);
    //    }
    //    public void SetStationGlobalValue<T>(string key, int option, T value, bool inContext = false)
    //    {
    //        if (PlanRun == null)
    //        {
    //            StationGlobalsManager.SetValue(key, value);
    //        }
    //        else
    //        {
    //            PlanRun.Update(null, $"StationGlobals.{key}", value);
    //        }
    //    }
    //    public void CommitToDisk(bool option)
    //    {
    //        StationGlobalsManager.WriteToLocal(null);
    //    }
    //    public void OnTestPlanRunStart(TestPlanRun planRun)
    //    {
    //        PlanRun = planRun;
    //    }
    //    public void OnTestPlanRunCompleted(TestPlanRun planRun, Stream logStream)
    //    {
    //    }
    //    public void OnTestStepRunStart(TestStepRun stepRun)
    //    {
    //    }
    //    public void OnTestStepRunCompleted(TestStepRun stepRun)
    //    {
    //    }
    //    public void OnResultPublished(Guid stepRunID, ResultTable result)
    //    {
    //    }
    //    public void Open()
    //    {
    //        PostUIMsgHandler.OnPostUIMsg += PostUIMsgHandler_OnPostUIMsg;
    //    }
    //    public void Close()
    //    {
    //        PostUIMsgHandler.OnPostUIMsg -= PostUIMsgHandler_OnPostUIMsg;
    //        EndExecutionEvent?.Invoke(this, new ExecutionEndEvent());
    //        PlanRun = null;
    //    }
    //    public bool WaitForExit()
    //    {
    //        return true;
    //    }
    //    public bool ReleaseSeqFile()
    //    {
    //        return true;
    //    }
    //    public void SetContext(object context)
    //    {
    //    }
    //}
}
Plugin.TsSupport/TeststandWrapper.cs
@@ -1,7 +1,7 @@
using NationalInstruments.TestStand.Interop.API;
using NationalInstruments.TestStand.Interop.UI.Ax;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Plugin.TsSupport
{
@@ -213,11 +213,19 @@
        public bool ReleaseSeqFile()
        {
            if (testSeq != null)
            if (this.testSeq != null && this.engine?.ReleaseSequenceFileEx(this.testSeq, 4) == true)
            {
                return engine?.ReleaseSequenceFileEx(testSeq, 4) == true;
                Marshal.ReleaseComObject(this.testSeq);
                this.testSeq = null;
            }
            return true;
            if (this.modeSeq != null && this.engine?.ReleaseSequenceFileEx(this.modeSeq, 4) == true)
            {
                Marshal.ReleaseComObject(this.modeSeq);
                this.modeSeq = null;
            }
            this.engine?.UnloadAllModules();
            return this.testSeq == null && this.modeSeq == null;
        }
        public void SetContext(object context)
Plugin.TsSupport/TsExecutionWrapper.cs
@@ -40,23 +40,37 @@
        /// </summary>
        public AxApplicationMgr AxApplicationMgr { get; private set; }
        public event _ApplicationMgrEvents_EndExecutionEventHandler OnEndExecution;
        public event EventHandler<UIMessageEvent> UIMessageEvent;
        public event EventHandler<ExecutionEndEvent> EndExecutionEvent;
        public TsExecutionWrapper()
        {
            AxApplicationMgr = TestAppManager.GetAxApplicationMgr();
            //AxApplicationMgr.EndExecution += AxApplicationMgr_EndExecution;
            AxApplicationMgr.EndExecution += AxApplicationMgr_EndExecution;
            //AxApplicationMgr.ExitApplication += new EventHandler(this.AxApplicationMgr_ExitApplication);
            //AxApplicationMgr.UIMessageEvent +=
            //    new _ApplicationMgrEvents_UIMessageEventEventHandler(this.AxApplicationMgr_UIMessageEvent);
            AxApplicationMgr.UIMessageEvent += AxApplicationMgr_UIMessageEvent;
            //AxApplicationMgr.ExecutionClosed +=
            //    new _ApplicationMgrEvents_ExecutionClosedEventHandler(this.AxApplicationMgr_ExecutionClosed);
        }
        #endregion
        private void AxApplicationMgr_EndExecution(object sender, _ApplicationMgrEvents_EndExecutionEvent e)
        {
            EndExecutionEvent?.Invoke(this, new ExecutionEndEvent() { Origin = e });
        }
        private void AxApplicationMgr_UIMessageEvent(object sender, _ApplicationMgrEvents_UIMessageEventEvent e)
        {
            UIMessageEvent?.Invoke(this, new UIMessageEvent()
            {
                Origin = e,
                EventCode = Convert.ToInt32(e.uiMsg.Event),
                NumberData = e.uiMsg.NumericData,
                Data = e.uiMsg.ActiveXData,
                StrData = e.uiMsg.StringData
            });
        }
        public object Init()
        {
@@ -208,7 +222,7 @@
        public void SetContext(object context)
        {
            this.context = (SequenceContext)context;
        }
    }
}
S7Driver/PLCS7Driver.cs
@@ -61,60 +61,150 @@
            client?.Dispose();
        }
        public bool Simens_S7_ReadBool(int DB, int StartAdd, int Pos)
        public bool Simens_S7_ReadBool(int DB, int StartAdd, int Pos, out bool value, out string msg)
        {
            var or = client.ReadBool($"DB{DB}.DBX{StartAdd}.{Pos}");
            return or.Content;
            value = false;
            msg = null;
            try
            {
                var or = client.ReadBool($"DB{DB}.DBX{StartAdd}.{Pos}");
                if (!or.IsSuccess)
                {
                    msg = $"{or.ErrorCode} {or.Message}";
                    return false;
                }
                value = or.Content;
                return true;
            }
            catch (Exception ex)
            {
                msg = ex.Message;
                return false;
            }
        }
        public void Simens_S7_WriteBool(int DB, int StartAdd, int Pos, bool Val)
        public bool Simens_S7_WriteBool(int DB, int StartAdd, int Pos, bool Val, out string msg)
        {
            var or = client.Write($"DB{DB}.DBX{StartAdd}.{Pos}", Val);
            return;
            msg = null;
            try
            {
                var or = client.Write($"DB{DB}.DBX{StartAdd}.{Pos}", Val);
                if (!or.IsSuccess)
                {
                    msg = $"{or.ErrorCode} {or.Message}";
                    return false;
                }
                return true;
            }
            catch (Exception ex)
            {
                msg = ex.Message;
                return false;
            }
        }
        public int Simens_S7_ReadInt(int DB, int StartAdd, int Length)
        {
            var or = client.ReadInt16($"DB{DB}.DBW{StartAdd}");
            return or.Content;
        }
        public void Simens_S7_WriteInt(int DB, int StartAdd, short Val)
        {
            client.Write($"DB{DB}.DBW{StartAdd}", Val);
        }
        public float Simens_S7_ReadReal(int DB, int StartAdd, int Length)
        {
            var or = client.ReadFloat($"DB{DB}.DBW{StartAdd}");
            return or.Content;
        }
        public void Simens_S7_WriteReal(int DB, int StartAdd, float Val)
        {
            client.Write($"DB{DB}.DBW{StartAdd}", Val);
        }
        public bool ReadShort(string address, out short value, out string errMsg)
        public bool Simens_S7_ReadInt(int DB, int StartAdd, out int value, out string msg)
        {
            value = 0;
            errMsg = null;
            msg = null;
            try
            {
                var or = client.ReadInt16($"DB{DB}.DBW{StartAdd}");
                if (!or.IsSuccess)
                {
                    msg = $"{or.ErrorCode} {or.Message}";
                    return false;
                }
                value = or.Content;
                return true;
            }
            catch (Exception ex)
            {
                msg = ex.Message;
                return false;
            }
        }
        public bool Simens_S7_WriteInt(int DB, int StartAdd, short Val, out string msg)
        {
            msg = null;
            try
            {
                var or = client.Write($"DB{DB}.DBW{StartAdd}", Val);
                if (!or.IsSuccess)
                {
                    msg = $"{or.ErrorCode} {or.Message}";
                    return false;
                }
                return true;
            }
            catch (Exception ex)
            {
                msg = ex.Message;
                return false;
            }
        }
        public bool Simens_S7_ReadReal(int DB, int StartAdd, out float value, out string msg)
        {
            value = 0;
            msg = null;
            try
            {
                var or = client.ReadFloat($"DB{DB}.DBW{StartAdd}");
                if (!or.IsSuccess)
                {
                    msg = $"{or.ErrorCode} {or.Message}";
                    return false;
                }
                value = or.Content;
                return true;
            }
            catch (Exception ex)
            {
                msg = ex.Message;
                return false;
            }
        }
        public bool Simens_S7_WriteReal(int DB, int StartAdd, float Val, out string msg)
        {
            msg = null;
            try
            {
                var or = client.Write($"DB{DB}.DBW{StartAdd}", Val);
                if (!or.IsSuccess)
                {
                    msg = $"{or.ErrorCode} {or.Message}";
                    return false;
                }
                return true;
            }
            catch (Exception ex)
            {
                msg = ex.Message;
                return false;
            }
        }
        public bool ReadShort(string address, out short value, out string msg)
        {
            value = 0;
            msg = null;
            try
            {
                var or = client.ReadInt16(address);
                if (or.IsSuccess)
                if (!or.IsSuccess)
                {
                    value = or.Content;
                    msg = $"{or.ErrorCode} {or.Message}";
                    return false;
                }
                else
                {
                    errMsg = or.Message;
                }
                return or.IsSuccess;
                value = or.Content;
                return true;
            }
            catch (Exception e)
            {
                errMsg = e.Message;
                msg = e.Message;
                return false;
            }
        }
S7Driver/Properties/AssemblyInfo.cs
@@ -29,5 +29,5 @@
//      ç”Ÿæˆå·
//      ä¿®è®¢å·
//
[assembly: AssemblyVersion("1.0.0.4")]
[assembly: AssemblyFileVersion("1.0.0.4")]
[assembly: AssemblyVersion("1.0.0.5")]
[assembly: AssemblyFileVersion("1.0.0.5")]
SimpleUtil/HMI.Simple.Util/AlarmUtil.cs
@@ -4,19 +4,15 @@
{
    public class AlarmUtil
    {
        private static string ConcatAlarm(string source, string content)
        {
            return $"{source},{content}";
        }
        public static bool CreateAlarm(string source, string content, out string msg)
        public static bool CreateAlarm(string source, string content, int level, out string msg)
        {
            msg = string.Empty;
            try
            {
                using (var helper = new MSMQHelper("HMI_Alarm"))
                {
                    helper.Send("Alarm", ConcatAlarm(source, content));
                    helper.Send("Alarm", source, content, level);
                    return true;
                }
            }
@@ -34,16 +30,16 @@
        /// <param name="contents">报警内容,同一来源的报警可以多条同时创建</param>
        /// <param name="msg">创建失败时为异常信息</param>
        /// <returns>创建失败时为异常信息</returns>
        public static bool CreateAlarm(string source, string[] contents, out string msg)
        public static bool CreateAlarm(string source, string[] contents, int[] levels, out string msg)
        {
            msg = string.Empty;
            try
            {
                using (var helper = new MSMQHelper("HMI_Alarm"))
                {
                    foreach (string content in contents)
                    for (int i = 0; i < contents.Length; i++)
                    {
                        helper.Send("Alarm", ConcatAlarm(source, content));
                        helper.Send("Alarm", source, contents[i], levels[i]);
                    }
                    return true;
                }
@@ -54,10 +50,13 @@
                return false;
            }
        }
        public static bool CreateAlarm(string content, out string msg)
        {
            return CreateAlarm("PC", content, out msg);
        }
        //private static string ConcatAlarm(string source, string content)
        //{
        //    return $"{source},{content}";
        //}
        //public static bool CreateAlarm(string content, int level, out string msg)
        //{
        //    return CreateAlarm("PC", content, level, out msg);
        //}
    }
}
SimpleUtil/HMI.Simple.Util/MSMQHelper.cs
@@ -3,6 +3,15 @@
namespace HMI.Simple.Util
{
    public class MQAlarm
    {
        public string Source { get; set; }
        public string Content { get; set; }
        public int Level { get; set; }
    }
    public class MSMQHelper : IDisposable
    {
        /// <summary>
@@ -40,7 +49,7 @@
                    // å¦‚果消息队列不存在,则创建消息对垒
                    this.msgQueue = MessageQueue.Create(this.msmqPath);
                }
                this.msgQueue.Formatter = new XmlMessageFormatter(new Type[] { typeof(string) });
                this.msgQueue.Formatter = new XmlMessageFormatter(new Type[] { typeof(MQAlarm) });
            }
            catch (Exception ex)
            {
@@ -53,11 +62,11 @@
        /// </summary>
        /// <param name="MessageID">消息ID</param>
        /// <param name="MsgString">要发送的字符串</param>
        public void Send(string MessageID, string MsgString)
        public void Send(string MessageID, string source, string MsgString, int level)
        {
            try
            {
                this.msgQueue.Send(MsgString, MessageID);
                this.msgQueue.Send(new MQAlarm { Source = source, Content = MsgString, Level = level }, MessageID);
            }
            catch (Exception ex)
            {
@@ -71,12 +80,12 @@
        /// <param name="TimeOut">超时时间(ms)</param>
        /// <param name="MessageID">消息ID</param>
        /// <param name="MsgString">消息内容</param>
        public void Receive(long TimeOut, out string MessageID, out string MsgString)
        public void Receive(long TimeOut, out string MessageID, out MQAlarm alarm)
        {
            try
            {
                MessageID = string.Empty;
                MsgString = string.Empty;
                alarm = null;
                TimeSpan Time = new TimeSpan(TimeOut);
                Message MsgReceived = null;
                try
@@ -91,7 +100,7 @@
                if (MsgReceived != null)
                {
                    MessageID = MsgReceived.Label;
                    MsgString = MsgReceived.Body.ToString();
                    alarm = (MQAlarm)MsgReceived.Body;
                }
            }
            catch (Exception ex)
SimpleUtil/HMI.TapApi/HMI.TapApi.csproj
New file
@@ -0,0 +1,74 @@
<?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>{EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}</ProjectGuid>
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>HMI.TapApi</RootNamespace>
    <AssemblyName>HMI.TapApi</AssemblyName>
    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <Deterministic>true</Deterministic>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Xml.Linq" />
    <Reference Include="System.Data.DataSetExtensions" />
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System.Data" />
    <Reference Include="System.Net.Http" />
    <Reference Include="System.Xml" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="ProductUtil.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\..\..\..\Project\OpenTapEditor\OpenTap\BasicSteps\Tap.Plugins.BasicSteps.csproj">
      <Project>{4BCFADC0-54F9-7032-B918-E4DD407CAA76}</Project>
      <Name>Tap.Plugins.BasicSteps</Name>
    </ProjectReference>
    <ProjectReference Include="..\..\..\..\Project\OpenTapEditor\OpenTap\Engine\Tap.Engine.csproj">
      <Project>{efbd516d-3dc9-9d75-f082-48fbc76715f7}</Project>
      <Name>Tap.Engine</Name>
    </ProjectReference>
    <ProjectReference Include="..\..\..\..\Project\OpenTapEditor\Plugin\AddInPlugin\AddInPlugin.csproj">
      <Project>{0d150ca1-d6ea-3362-7330-028939e66e1c}</Project>
      <Name>AddInPlugin</Name>
    </ProjectReference>
    <ProjectReference Include="..\..\HMI.Core\HMI.Core.csproj">
      <Project>{3e3bf44a-0b69-45c5-9512-528cb48a7106}</Project>
      <Name>HMI.Core</Name>
    </ProjectReference>
    <ProjectReference Include="..\HMI.Simple.Util\HMI.Simple.Util.csproj">
      <Project>{cf5bebe9-2803-49c3-b773-73d082e0984a}</Project>
      <Name>HMI.Simple.Util</Name>
    </ProjectReference>
    <ProjectReference Include="..\HMI.Util.Base\HMI.Util.Base.csproj">
      <Project>{d59e25d1-9b27-46a1-8438-92661dd23fae}</Project>
      <Name>HMI.Util.Base</Name>
    </ProjectReference>
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
SimpleUtil/HMI.TapApi/ProductUtil.cs
New file
@@ -0,0 +1,164 @@
using HMI.Simple.Util;
using OpenTap;
using OpenTap.Addin.Util;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace HMI.TapApi
{
    public class ProductUtil
    {
        private static void WriteParameterData(object instance, ParameterData data)
        {
            TestPlanRun context = (TestPlanRun)instance;
            string name = $"DUTSetting.{data.Name}";
            if (data.IsArray)
            {
                Type type;
                if (data.Type == "Bool")
                {
                    type = typeof(bool);
                }
                else if (data.Type == "Int")
                {
                    type = typeof(int);
                }
                else if (data.Type == "Double")
                {
                    type = typeof(double);
                }
                else
                {
                    type = typeof(string);
                }
                string[] arr = data.Value?.Split(',') ?? new string[0];
                if (arr.Length > 0)
                {
                    //RuntimeVariable rv;
                    //if (data.Type == "Int")
                    //{
                    //    int[] values = arr.Select(a => int.Parse(a)).ToArray();
                    //    rv = new RuntimeVariable(context, name, values);
                    //}
                    //if (data.Type == "Double")
                    //{
                    //    double[] values = arr.Select(a => double.Parse(a)).ToArray();
                    //    rv = new RuntimeVariable(context, name, values);
                    //}
                    //if (data.Type == "Bool")
                    //{
                    //    bool[] values = arr.Select(a => bool.Parse(a)).ToArray();
                    //    rv = new RuntimeVariable(context, name, values);
                    //}
                    //else
                    //{
                    //    rv = new RuntimeVariable(context, name, arr);
                    //}
                    if (data.Type == "Int")
                    {
                        int[] values = arr.Select(a => int.Parse(a)).ToArray();
                        context.Update(null, $"{ConstNames.FileGlobalsName}.{name}", values);
                    }
                    if (data.Type == "Double")
                    {
                        double[] values = arr.Select(a => double.Parse(a)).ToArray();
                        context.Update(null, $"{ConstNames.FileGlobalsName}.{name}", values);
                    }
                    if (data.Type == "Bool")
                    {
                        bool[] values = arr.Select(a => bool.Parse(a)).ToArray();
                        context.Update(null, $"{ConstNames.FileGlobalsName}.{name}", values);
                    }
                    else
                    {
                        context.Update(null, $"{ConstNames.FileGlobalsName}.{name}", arr);
                    }
                }
            }
            else
            {
                if (data.Type == "Bool")
                {
                    context.Update(null, $"{ConstNames.FileGlobalsName}.{name}", bool.Parse(data.Value));
                }
                else if (data.Type == "Int")
                {
                    context.Update(null, $"{ConstNames.FileGlobalsName}.{name}", int.Parse(data.Value));
                }
                else if (data.Type == "Double")
                {
                    context.Update(null, $"{ConstNames.FileGlobalsName}.{name}", double.Parse(data.Value));
                }
                else
                {
                    context.Update(null, $"{ConstNames.FileGlobalsName}.{name}", data.Value);
                }
            }
        }
        private static string GetDataFilePath()
        {
            string path = ParameterUtil.GetCommonParameterValue("ShareConfig", out _);
            if (string.IsNullOrEmpty(path))
            {
                path = Path.Combine(HMI.Simple.Util.RegUtil.GetLocaltion(), "Config");
            }
            return path;
        }
        /// <summary>
        /// æ ¹æ®äº§å“åž‹å·å†™é…ç½®
        /// </summary>
        /// <param name="model">产品型号</param>
        /// <param name="instance">TestStand上下文</param>
        /// <param name="errMsg">异常信息</param>
        /// <returns>是否成功</returns>
        public static bool WriteConfigToContextByModel(string model, object instance, out string errMsg)
        {
            errMsg = string.Empty;
            try
            {
                string path = Path.Combine(GetDataFilePath(), "Product", "Parameter", $"Parameter_{model}.data");
                return WriteConfigToContextByPath(path, instance, out errMsg);
            }
            catch (Exception ex)
            {
                errMsg = ex.Message;
                return false;
            }
        }
        /// <summary>
        /// æ ¹æ®è·¯å¾„写配置
        /// </summary>
        /// <param name="path">绝对路径</param>
        /// <param name="instance">TestStand上下文</param>
        /// <param name="errMsg">异常信息</param>
        /// <returns>是否成功</returns>
        public static bool WriteConfigToContextByPath(string path, object instance, out string errMsg)
        {
            errMsg = string.Empty;
            try
            {
                if (!File.Exists(path))
                {
                    throw new Exception($"未找到该配置[{path}],请检查");
                }
                List<ParameterData> datas = XmlHelper.DeserializeFromXml<List<ParameterData>>(path);
                foreach (var data in datas)
                {
                    WriteParameterData(instance, data);
                }
                return true;
            }
            catch (Exception ex)
            {
                errMsg = ex.Message;
                return false;
            }
        }
    }
}
SimpleUtil/HMI.TapApi/Properties/AssemblyInfo.cs
New file
@@ -0,0 +1,33 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// æœ‰å…³ç¨‹åºé›†çš„一般信息由以下
// æŽ§åˆ¶ã€‚更改这些特性值可修改
// ä¸Žç¨‹åºé›†å…³è”的信息。
[assembly: AssemblyTitle("HMI.TapApi")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("HMI.TapApi")]
[assembly: AssemblyCopyright("Copyright Â©  2026")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// å°† ComVisible è®¾ç½®ä¸º false ä¼šä½¿æ­¤ç¨‹åºé›†ä¸­çš„类型
//对 COM ç»„件不可见。如果需要从 COM è®¿é—®æ­¤ç¨‹åºé›†ä¸­çš„类型
//请将此类型的 ComVisible ç‰¹æ€§è®¾ç½®ä¸º true。
[assembly: ComVisible(false)]
// å¦‚果此项目向 COM å…¬å¼€ï¼Œåˆ™ä¸‹åˆ— GUID ç”¨äºŽç±»åž‹åº“çš„ ID
[assembly: Guid("eefdf6fe-41ad-4eb0-9ce6-87a07b88f9d7")]
// ç¨‹åºé›†çš„版本信息由下列四个值组成:
//
//      ä¸»ç‰ˆæœ¬
//      æ¬¡ç‰ˆæœ¬
//      ç”Ÿæˆå·
//      ä¿®è®¢å·
//
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
StationGlobalsReader/App.xaml
New file
@@ -0,0 +1,9 @@
<Application x:Class="StationGlobalsReader.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:StationGlobalsReader"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
    </Application.Resources>
</Application>
StationGlobalsReader/App.xaml.cs
New file
@@ -0,0 +1,13 @@
using System.Configuration;
using System.Data;
using System.Windows;
namespace StationGlobalsReader;
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
StationGlobalsReader/AssemblyInfo.cs
New file
@@ -0,0 +1,10 @@
using System.Windows;
[assembly:ThemeInfo(
    ResourceDictionaryLocation.None,            //where theme specific resource dictionaries are located
                                                //(used if a resource is not found in the page,
                                                // or application resource dictionaries)
    ResourceDictionaryLocation.SourceAssembly   //where the generic resource dictionary is located
                                                //(used if a resource is not found in the page,
                                                // app, or any theme specific resource dictionaries)
)]
StationGlobalsReader/MainWindow.xaml
New file
@@ -0,0 +1,29 @@
<Window x:Class="StationGlobalsReader.MainWindow"
        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:StationGlobalsReader"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <WindowsFormsHost x:Name="host" Visibility="Collapsed"></WindowsFormsHost>
        <TextBlock Grid.Row="0" Grid.Column="0" Text="StationGlobals."/>
        <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding PropertyName}"/>
        <TextBlock Grid.Row="1" Grid.Column="0" Text="值"/>
        <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Tagert}"/>
        <Button Grid.Row="2" Grid.ColumnSpan="2" Click="Button_Click">读取</Button>
    </Grid>
</Window>
StationGlobalsReader/MainWindow.xaml.cs
New file
@@ -0,0 +1,50 @@
using CommunityToolkit.Mvvm.ComponentModel;
using NationalInstruments.TestStand.Interop.API;
using NationalInstruments.TestStand.Interop.UI.Ax;
using System.Windows;
using System.Windows.Forms.Integration;
namespace StationGlobalsReader;
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
[ObservableObject]
public partial class MainWindow : Window
{
    public MainWindow()
    {
        axApplicationMgr = new AxApplicationMgr();
        InitializeComponent();
        this.engine = axApplicationMgr.GetEngine();
        this.engine.PersistBreakpoints = false;
        this.axApplicationMgr.IsEditor = true;
        this.axApplicationMgr.LoginOnStart = false;
        this.axApplicationMgr.Start();
        ((System.ComponentModel.ISupportInitialize)(axApplicationMgr)).BeginInit();
        host.Child = axApplicationMgr;
        ((System.ComponentModel.ISupportInitialize)(axApplicationMgr)).EndInit();
    }
    private AxApplicationMgr axApplicationMgr;
    private Engine engine = null;
    [ObservableProperty]
    private string propertyName;
    [ObservableProperty]
    private string target;
    private void Button_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            Target = engine.Globals.GetValString($"StationGlobals.{PropertyName}", 0);
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }
}
StationGlobalsReader/StationGlobalsReader.csproj
New file
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net472</TargetFramework>
    <Nullable>enable</Nullable>
    <LangVersion>10.0</LangVersion>
    <ImplicitUsings>enable</ImplicitUsings>
    <UseWPF>true</UseWPF>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
    <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
  </ItemGroup>
  <ItemGroup>
    <Reference Include="NationalInstruments.TestStand.Interop.API">
      <HintPath>..\Libs\NationalInstruments.TestStand.Interop.API.dll</HintPath>
    </Reference>
    <Reference Include="NationalInstruments.TestStand.Interop.UI.AxControls">
      <HintPath>..\Libs\NationalInstruments.TestStand.Interop.UI.AxControls.dll</HintPath>
    </Reference>
    <Reference Include="System.Windows.Forms" />
    <Reference Include="WindowsFormsIntegration" />
  </ItemGroup>
</Project>
WPFProject/App.config
@@ -3,7 +3,8 @@
    <configSections>
        <section name="TestResultConfig" type="System.Configuration.SingleTagSectionHandler" />
    </configSections>
    <TestResultConfig Row="2" Column="2" PortStart="6660" PositionColumn="4" PositionCount="20" NameStr="A,B,C,D,E,F,G" CodeParserType="Model" />
    <TestResultConfig Row="2" Column="2" PortStart="6660" PositionColumn="4" PositionCount="20" NameStr="A,B,C,D,E,F,G"
                      CodeParserType="Model"/>
    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
    </startup>
WPFProject/BuildCmd.bat
@@ -1,15 +1,30 @@
IF "$(ConfigurationName)"=="Release" Confuser.CLI.exe $(TargeDir)..\ConfuserEx\$(TargetFileName).crproj
mkdir "$(TargetDir)OpenTap"
copy "$(TargeDir)HMI.Core.dll" "$(TargetDir)OpenTap"
copy "$(TargeDir)HMI.Simple.Util.dll" "$(TargetDir)OpenTap"
copy "$(TargeDir)HMI.Util.Base.dll" "$(TargetDir)OpenTap"
move /y "$(TargeDir)HMI.Core.dll" $(TargeDir)Libs\HMI.Core.dll"
move /y "$(TargeDir)HMI.Base.dll" $(TargeDir)Libs\HMI.Base.dll"
move /y "$(TargeDir)HMI.UI.dll" $(TargeDir)Libs\HMI.UI.dll"
move /y "$(TargeDir)HMI.Util.dll" $(TargeDir)Libs\HMI.Util.dll"
move /y "$(TargeDir)ConnectionUtil.dll" $(TargeDir)Libs\ConnectionUtil.dll"
move /y "$(TargeDir)HMI.StdControl.dll" $(TargeDir)Libs\HMI.StdControl.dll"
move /y "$(TargeDir)S7Driver.dll" $(TargeDir)Libs\S7Driver.dll"
move /y "$(TargeDir)HslCommunication.dll" $(TargeDir)Libs\HslCommunication.dll"
move /y "$(TargeDir)PLCHelper.dll" $(TargeDir)Libs\PLCHelper.dll"
move /y "$(TargeDir)ModBusTcpController.dll" $(TargeDir)Libs\ModBusTcpController.dll"
move /y "$(TargeDir)HMI.LogUtil.dll" $(TargeDir)Api\HMI.LogUtil.dll"
move /y "$(TargeDir)HMI.SerialData.Util.dll" $(TargeDir)Api\HMI.SerialData.Util.dll"
move /y "$(TargeDir)HMI.Simple.Util.dll" $(TargeDir)Api\HMI.Simple.Util.dll"
move /y "$(TargeDir)HMI.Util.Base.dll" $(TargeDir)Api\HMI.Util.Base.dll"
move /y "$(TargeDir)CellStepSender.dll" $(TargeDir)Api\CellStepSender.dll"
mkdir "$(TargetDir)Plugin"
move /y "$(TargeDir)HMI.AlarmInfoManage.dll" $(TargeDir)Plugin\HMI.AlarmInfoManage.dll"
move /y "$(TargeDir)HMI.DataViewer.dll" $(TargeDir)Plugin\HMI.DataViewer.dll"
@@ -30,6 +45,17 @@
move /y "$(TargeDir)Plugin.MaterialOpt.dll" $(TargeDir)Plugin\Plugin.MaterialOpt.dll"
move /y "$(TargeDir)Plugin.DODriverControl.dll" $(TargeDir)Plugin\Plugin.DODriverControl.dll"
move /y "$(TargeDir)Plugin.ModelManage.dll" $(TargeDir)Plugin\Plugin.ModelManage.dll"
move /y "$(TargeDir)Plugin.CurrentSensorControl.dll" $(TargeDir)Plugin\Plugin.CurrentSensorControl.dll"
move /y "$(TargeDir)Plugin.TsSupport.dll" $(TargeDir)Plugin\Plugin.TsSupport.dll"
move /y "$(TargeDir)Plugin.DataViewer.dll" $(TargeDir)Plugin\Plugin.DataViewer.dll"
move /y "$(TargeDir)Util.ManualDebug.dll" $(TargeDir)Plugin\Util.ManualDebug.dll"
move /y "$(TargeDir)Plugin.QualityReport.dll" $(TargeDir)Plugin\Plugin.QualityReport.dll"
move /y "$(TargeDir)Plugin.BurnIn.dll" $(TargeDir)Plugin\Plugin.BurnIn.dll"
move /y "$(TargeDir)AddInPlugin.dll" "$(TargetDir)OpenTap\AddInPlugin.dll"
move /y "$(TargeDir)OpenTap.dll" "$(TargetDir)OpenTap\OpenTap.dll"
move /y "$(TargeDir)OpenTap.Plugins.BasicSteps.dll" "$(TargetDir)OpenTap\OpenTap.Plugins.BasicSteps.dll"
move /y "$(TargeDir)HMI.TapApi.dll" "$(TargetDir)OpenTap\HMI.TapApi.dll"
setlocal enabledelayedexpansion
@@ -46,13 +72,13 @@
)
for /d %%D in ("%source_folder%\*") do (
    if /I not "%%~nxD"=="Api" if /I not "%%~nxD"=="Config" if /I not "%%~nxD"=="Icon" if /I not "%%~nxD"=="Lang" if /I not "%%~nxD"=="Libs" if /I not "%%~nxD"=="Log" if /I not "%%~nxD"=="Plugin" if /I not "%%~nxD"=="TestPlatform.exe.WebView2" (
    if /I not "%%~nxD"=="Api" if /I not "%%~nxD"=="Config" if /I not "%%~nxD"=="Icon" if /I not "%%~nxD"=="Lang" if /I not "%%~nxD"=="Libs" if /I not "%%~nxD"=="Log" if /I not "%%~nxD"=="Plugin" if /I not "%%~nxD"=="TestPlatform.exe.WebView2" if /I not "%%~nxD"=="OpenTap" (
        move /Y "%%D" "%destination_folder%"
    )
)
for %%F in ("%source_folder%\*") do (
    if /I not "%%~nxF"=="$(TargetFileName)" if /I not "%%~nxF"=="$(TargetFileName).config" if /I not "%%~nxF"=="$(TargetName).pdb" if /I not "%%~nxF"=="clear.bat" if /I not "%%~nxF"=="安装MSMQ.bat" (
    if /I not "%%~nxF"=="$(TargetFileName)" if /I not "%%~nxF"=="$(TargetFileName).config" if /I not "%%~nxF"=="$(TargetName).pdb" if /I not "%%~nxF"=="clear.bat" if /I not "%%~nxF"=="安装MSMQ.bat" if /I not "%%~nxF"=="Settings.ini" (
        move /Y "%%F" "%destination_folder%"
    )
)
WPFProject/Plugin/Here put your plugins.txt
File was deleted
WPFProject/ResourceListener.cs
@@ -1,10 +1,8 @@
using HMI.Simple.Util;
using log4net;
using log4net;
using System;
using System.Diagnostics;
using System.Management;
using System.Timers;
using System.Xml.Linq;
namespace WPFProject
{
@@ -97,11 +95,11 @@
                        totalCpuRate = totalCpuRate,
                        totalRemRate = usagePercent
                    });
                    log.Info(info);
                    //if (usagePercent > 80)
                    //{
                    //    AlarmUtil.CreateAlarm("App", "内存占用过高,超80%", out _);
                    //}
                    if (usagePercent > 80)
                    {
                        log.Info(info);
                        //AlarmUtil.CreateAlarm("App", "内存占用过高,超80%", out _);
                    }
                }
            }
            catch (Exception ex)
WPFProject/WPFProject.csproj
@@ -326,9 +326,6 @@
    <Resource Include="Icon\t2.png" />
    <Resource Include="Icon\t3.png" />
    <Resource Include="Icon\t4.png" />
    <Content Include="Plugin\Here put your plugins.txt">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
    <Resource Include="Resources\Background.png" />
    <Resource Include="Resources\JingCeLogo.png" />
    <EmbeddedResource Include="Controls\HolderForm.resx">
@@ -449,9 +446,17 @@
      <Project>{49db0c9b-b239-4608-a91e-f5ed43afaf96}</Project>
      <Name>ModBusTcpController</Name>
    </ProjectReference>
    <ProjectReference Include="..\Plugin.BurnIn\Plugin.BurnIn.csproj">
      <Project>{7964f444-d2c6-4c11-87f4-0df4faec10b6}</Project>
      <Name>Plugin.BurnIn</Name>
    </ProjectReference>
    <ProjectReference Include="..\Plugin.DataViewer\Plugin.DataViewer.csproj">
      <Project>{dbfe5040-963f-4bbf-93fb-26b3f18fda90}</Project>
      <Name>Plugin.DataViewer</Name>
    </ProjectReference>
    <ProjectReference Include="..\Plugin.QualityReport\Plugin.QualityReport.csproj">
      <Project>{0fd55872-b9f6-41ad-8d3f-6d7a7d3f1233}</Project>
      <Name>Plugin.QualityReport</Name>
    </ProjectReference>
    <ProjectReference Include="..\Plugin.SeqEditor\Plugin.SeqEditor.csproj">
      <Project>{1260f995-1ae9-17b6-feb5-c430c1416bec}</Project>
@@ -564,6 +569,13 @@
  <PropertyGroup>
    <PostBuildEvent>IF "$(ConfigurationName)"=="Release" Confuser.CLI.exe $(TargeDir)..\ConfuserEx\$(TargetFileName).crproj
mkdir "$(TargetDir)OpenTap"
copy "$(TargeDir)HMI.Core.dll" "$(TargetDir)OpenTap"
copy "$(TargeDir)HMI.Simple.Util.dll" "$(TargetDir)OpenTap"
copy "$(TargeDir)HMI.Util.Base.dll" "$(TargetDir)OpenTap"
move /y "$(TargeDir)HMI.Core.dll" $(TargeDir)Libs\HMI.Core.dll"
move /y "$(TargeDir)HMI.Base.dll" $(TargeDir)Libs\HMI.Base.dll"
move /y "$(TargeDir)HMI.UI.dll" $(TargeDir)Libs\HMI.UI.dll"
@@ -580,6 +592,8 @@
move /y "$(TargeDir)HMI.Simple.Util.dll" $(TargeDir)Api\HMI.Simple.Util.dll"
move /y "$(TargeDir)HMI.Util.Base.dll" $(TargeDir)Api\HMI.Util.Base.dll"
move /y "$(TargeDir)CellStepSender.dll" $(TargeDir)Api\CellStepSender.dll"
mkdir "$(TargetDir)Plugin"
move /y "$(TargeDir)HMI.AlarmInfoManage.dll" $(TargeDir)Plugin\HMI.AlarmInfoManage.dll"
move /y "$(TargeDir)HMI.DataViewer.dll" $(TargeDir)Plugin\HMI.DataViewer.dll"
@@ -604,6 +618,13 @@
move /y "$(TargeDir)Plugin.TsSupport.dll" $(TargeDir)Plugin\Plugin.TsSupport.dll"
move /y "$(TargeDir)Plugin.DataViewer.dll" $(TargeDir)Plugin\Plugin.DataViewer.dll"
move /y "$(TargeDir)Util.ManualDebug.dll" $(TargeDir)Plugin\Util.ManualDebug.dll"
move /y "$(TargeDir)Plugin.QualityReport.dll" $(TargeDir)Plugin\Plugin.QualityReport.dll"
move /y "$(TargeDir)Plugin.BurnIn.dll" $(TargeDir)Plugin\Plugin.BurnIn.dll"
move /y "$(TargeDir)AddInPlugin.dll" "$(TargetDir)OpenTap\AddInPlugin.dll"
move /y "$(TargeDir)OpenTap.dll" "$(TargetDir)OpenTap\OpenTap.dll"
move /y "$(TargeDir)OpenTap.Plugins.BasicSteps.dll" "$(TargetDir)OpenTap\OpenTap.Plugins.BasicSteps.dll"
move /y "$(TargeDir)HMI.TapApi.dll" "$(TargetDir)OpenTap\HMI.TapApi.dll"
setlocal enabledelayedexpansion
@@ -620,7 +641,7 @@
)
for /d %25%25D in ("%25source_folder%25\*") do (
    if /I not "%25%25~nxD"=="Api" if /I not "%25%25~nxD"=="Config" if /I not "%25%25~nxD"=="Icon" if /I not "%25%25~nxD"=="Lang" if /I not "%25%25~nxD"=="Libs" if /I not "%25%25~nxD"=="Log" if /I not "%25%25~nxD"=="Plugin" if /I not "%25%25~nxD"=="TestPlatform.exe.WebView2" (
    if /I not "%25%25~nxD"=="Api" if /I not "%25%25~nxD"=="Config" if /I not "%25%25~nxD"=="Icon" if /I not "%25%25~nxD"=="Lang" if /I not "%25%25~nxD"=="Libs" if /I not "%25%25~nxD"=="Log" if /I not "%25%25~nxD"=="Plugin" if /I not "%25%25~nxD"=="TestPlatform.exe.WebView2" if /I not "%25%25~nxD"=="OpenTap" (
        move /Y "%25%25D" "%25destination_folder%25"
    )
)
WPFProject/WPFProject.sln
@@ -85,6 +85,20 @@
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Util.ManualDebug", "..\Util.ManualDebug\Util.ManualDebug.csproj", "{D0944E62-EE52-4ABE-832A-A09D98814DB1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tap.Engine", "..\..\..\Project\OpenTapEditor\OpenTap\Engine\Tap.Engine.csproj", "{EFBD516D-3DC9-9D75-F082-48FBC76715F7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AddInPlugin", "..\..\..\Project\OpenTapEditor\Plugin\AddInPlugin\AddInPlugin.csproj", "{0D150CA1-D6EA-3362-7330-028939E66E1C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tap.Plugins.BasicSteps", "..\..\..\Project\OpenTapEditor\OpenTap\BasicSteps\Tap.Plugins.BasicSteps.csproj", "{4BCFADC0-54F9-7032-B918-E4DD407CAA76}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin.QualityReport", "..\Plugin.QualityReport\Plugin.QualityReport.csproj", "{0FD55872-B9F6-41AD-8D3F-6D7A7D3F1233}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HMI.TapApi", "..\SimpleUtil\HMI.TapApi\HMI.TapApi.csproj", "{EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StationGlobalsReader", "..\StationGlobalsReader\StationGlobalsReader.csproj", "{87F7AD77-45E9-4185-A4F7-13041796E7B6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin.BurnIn", "..\Plugin.BurnIn\Plugin.BurnIn.csproj", "{7964F444-D2C6-4C11-87F4-0DF4FAEC10B6}"
EndProject
Global
    GlobalSection(SolutionConfigurationPlatforms) = preSolution
        Debug|Any CPU = Debug|Any CPU
@@ -575,6 +589,90 @@
        {D0944E62-EE52-4ABE-832A-A09D98814DB1}.Release|x64.Build.0 = Release|Any CPU
        {D0944E62-EE52-4ABE-832A-A09D98814DB1}.Release|x86.ActiveCfg = Release|x86
        {D0944E62-EE52-4ABE-832A-A09D98814DB1}.Release|x86.Build.0 = Release|x86
        {EFBD516D-3DC9-9D75-F082-48FBC76715F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {EFBD516D-3DC9-9D75-F082-48FBC76715F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {EFBD516D-3DC9-9D75-F082-48FBC76715F7}.Debug|x64.ActiveCfg = Debug|x64
        {EFBD516D-3DC9-9D75-F082-48FBC76715F7}.Debug|x64.Build.0 = Debug|x64
        {EFBD516D-3DC9-9D75-F082-48FBC76715F7}.Debug|x86.ActiveCfg = Debug|Any CPU
        {EFBD516D-3DC9-9D75-F082-48FBC76715F7}.Debug|x86.Build.0 = Debug|Any CPU
        {EFBD516D-3DC9-9D75-F082-48FBC76715F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {EFBD516D-3DC9-9D75-F082-48FBC76715F7}.Release|Any CPU.Build.0 = Release|Any CPU
        {EFBD516D-3DC9-9D75-F082-48FBC76715F7}.Release|x64.ActiveCfg = Release|x64
        {EFBD516D-3DC9-9D75-F082-48FBC76715F7}.Release|x64.Build.0 = Release|x64
        {EFBD516D-3DC9-9D75-F082-48FBC76715F7}.Release|x86.ActiveCfg = Release|Any CPU
        {EFBD516D-3DC9-9D75-F082-48FBC76715F7}.Release|x86.Build.0 = Release|Any CPU
        {0D150CA1-D6EA-3362-7330-028939E66E1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {0D150CA1-D6EA-3362-7330-028939E66E1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {0D150CA1-D6EA-3362-7330-028939E66E1C}.Debug|x64.ActiveCfg = Debug|x64
        {0D150CA1-D6EA-3362-7330-028939E66E1C}.Debug|x64.Build.0 = Debug|x64
        {0D150CA1-D6EA-3362-7330-028939E66E1C}.Debug|x86.ActiveCfg = Debug|Any CPU
        {0D150CA1-D6EA-3362-7330-028939E66E1C}.Debug|x86.Build.0 = Debug|Any CPU
        {0D150CA1-D6EA-3362-7330-028939E66E1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {0D150CA1-D6EA-3362-7330-028939E66E1C}.Release|Any CPU.Build.0 = Release|Any CPU
        {0D150CA1-D6EA-3362-7330-028939E66E1C}.Release|x64.ActiveCfg = Release|x64
        {0D150CA1-D6EA-3362-7330-028939E66E1C}.Release|x64.Build.0 = Release|x64
        {0D150CA1-D6EA-3362-7330-028939E66E1C}.Release|x86.ActiveCfg = Release|Any CPU
        {0D150CA1-D6EA-3362-7330-028939E66E1C}.Release|x86.Build.0 = Release|Any CPU
        {4BCFADC0-54F9-7032-B918-E4DD407CAA76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {4BCFADC0-54F9-7032-B918-E4DD407CAA76}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {4BCFADC0-54F9-7032-B918-E4DD407CAA76}.Debug|x64.ActiveCfg = Debug|x64
        {4BCFADC0-54F9-7032-B918-E4DD407CAA76}.Debug|x64.Build.0 = Debug|x64
        {4BCFADC0-54F9-7032-B918-E4DD407CAA76}.Debug|x86.ActiveCfg = Debug|Any CPU
        {4BCFADC0-54F9-7032-B918-E4DD407CAA76}.Debug|x86.Build.0 = Debug|Any CPU
        {4BCFADC0-54F9-7032-B918-E4DD407CAA76}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {4BCFADC0-54F9-7032-B918-E4DD407CAA76}.Release|Any CPU.Build.0 = Release|Any CPU
        {4BCFADC0-54F9-7032-B918-E4DD407CAA76}.Release|x64.ActiveCfg = Release|x64
        {4BCFADC0-54F9-7032-B918-E4DD407CAA76}.Release|x64.Build.0 = Release|x64
        {4BCFADC0-54F9-7032-B918-E4DD407CAA76}.Release|x86.ActiveCfg = Release|Any CPU
        {4BCFADC0-54F9-7032-B918-E4DD407CAA76}.Release|x86.Build.0 = Release|Any CPU
        {0FD55872-B9F6-41AD-8D3F-6D7A7D3F1233}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {0FD55872-B9F6-41AD-8D3F-6D7A7D3F1233}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {0FD55872-B9F6-41AD-8D3F-6D7A7D3F1233}.Debug|x64.ActiveCfg = Debug|x64
        {0FD55872-B9F6-41AD-8D3F-6D7A7D3F1233}.Debug|x64.Build.0 = Debug|x64
        {0FD55872-B9F6-41AD-8D3F-6D7A7D3F1233}.Debug|x86.ActiveCfg = Debug|Any CPU
        {0FD55872-B9F6-41AD-8D3F-6D7A7D3F1233}.Debug|x86.Build.0 = Debug|Any CPU
        {0FD55872-B9F6-41AD-8D3F-6D7A7D3F1233}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {0FD55872-B9F6-41AD-8D3F-6D7A7D3F1233}.Release|Any CPU.Build.0 = Release|Any CPU
        {0FD55872-B9F6-41AD-8D3F-6D7A7D3F1233}.Release|x64.ActiveCfg = Release|x64
        {0FD55872-B9F6-41AD-8D3F-6D7A7D3F1233}.Release|x64.Build.0 = Release|x64
        {0FD55872-B9F6-41AD-8D3F-6D7A7D3F1233}.Release|x86.ActiveCfg = Release|Any CPU
        {0FD55872-B9F6-41AD-8D3F-6D7A7D3F1233}.Release|x86.Build.0 = Release|Any CPU
        {EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}.Debug|x64.ActiveCfg = Debug|Any CPU
        {EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}.Debug|x64.Build.0 = Debug|Any CPU
        {EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}.Debug|x86.ActiveCfg = Debug|Any CPU
        {EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}.Debug|x86.Build.0 = Debug|Any CPU
        {EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}.Release|Any CPU.Build.0 = Release|Any CPU
        {EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}.Release|x64.ActiveCfg = Release|Any CPU
        {EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}.Release|x64.Build.0 = Release|Any CPU
        {EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}.Release|x86.ActiveCfg = Release|Any CPU
        {EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}.Release|x86.Build.0 = Release|Any CPU
        {87F7AD77-45E9-4185-A4F7-13041796E7B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {87F7AD77-45E9-4185-A4F7-13041796E7B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {87F7AD77-45E9-4185-A4F7-13041796E7B6}.Debug|x64.ActiveCfg = Debug|Any CPU
        {87F7AD77-45E9-4185-A4F7-13041796E7B6}.Debug|x64.Build.0 = Debug|Any CPU
        {87F7AD77-45E9-4185-A4F7-13041796E7B6}.Debug|x86.ActiveCfg = Debug|Any CPU
        {87F7AD77-45E9-4185-A4F7-13041796E7B6}.Debug|x86.Build.0 = Debug|Any CPU
        {87F7AD77-45E9-4185-A4F7-13041796E7B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {87F7AD77-45E9-4185-A4F7-13041796E7B6}.Release|Any CPU.Build.0 = Release|Any CPU
        {87F7AD77-45E9-4185-A4F7-13041796E7B6}.Release|x64.ActiveCfg = Release|Any CPU
        {87F7AD77-45E9-4185-A4F7-13041796E7B6}.Release|x64.Build.0 = Release|Any CPU
        {87F7AD77-45E9-4185-A4F7-13041796E7B6}.Release|x86.ActiveCfg = Release|Any CPU
        {87F7AD77-45E9-4185-A4F7-13041796E7B6}.Release|x86.Build.0 = Release|Any CPU
        {7964F444-D2C6-4C11-87F4-0DF4FAEC10B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {7964F444-D2C6-4C11-87F4-0DF4FAEC10B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {7964F444-D2C6-4C11-87F4-0DF4FAEC10B6}.Debug|x64.ActiveCfg = Debug|x64
        {7964F444-D2C6-4C11-87F4-0DF4FAEC10B6}.Debug|x64.Build.0 = Debug|x64
        {7964F444-D2C6-4C11-87F4-0DF4FAEC10B6}.Debug|x86.ActiveCfg = Debug|Any CPU
        {7964F444-D2C6-4C11-87F4-0DF4FAEC10B6}.Debug|x86.Build.0 = Debug|Any CPU
        {7964F444-D2C6-4C11-87F4-0DF4FAEC10B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {7964F444-D2C6-4C11-87F4-0DF4FAEC10B6}.Release|Any CPU.Build.0 = Release|Any CPU
        {7964F444-D2C6-4C11-87F4-0DF4FAEC10B6}.Release|x64.ActiveCfg = Release|Any CPU
        {7964F444-D2C6-4C11-87F4-0DF4FAEC10B6}.Release|x64.Build.0 = Release|Any CPU
        {7964F444-D2C6-4C11-87F4-0DF4FAEC10B6}.Release|x86.ActiveCfg = Release|Any CPU
        {7964F444-D2C6-4C11-87F4-0DF4FAEC10B6}.Release|x86.Build.0 = Release|Any CPU
    EndGlobalSection
    GlobalSection(SolutionProperties) = preSolution
        HideSolutionNode = FALSE
@@ -586,6 +684,7 @@
        {D59E25D1-9B27-46A1-8438-92661DD23FAE} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
        {F8B8B6B6-821B-4FF6-B556-9CAB840EA57A} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
        {34FFBAFE-478F-4B6E-95E7-92A949011A9B} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
        {EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
    EndGlobalSection
    GlobalSection(ExtensibilityGlobals) = postSolution
        SolutionGuid = {784CEA1D-A890-4625-B0C1-72294A6B4803}