alielie
2025-03-13 a3ba25607612818c28d421bfa8eb2f92339d71ac
优化项目;
20个文件已修改
15个文件已添加
1个文件已删除
1892 ■■■■■ 已修改文件
HMI.Core/Util/PathHelper.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.DataViewer/DataViewer.xaml.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.MenuManage/Modal/IconSelector.xaml.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.MenuManage/Modal/MenuModal.xaml.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/Entity/ParameterData.cs 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/Entity/TestItemData.cs 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/HMI.ParameterManage.csproj 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/ParameterSetting.xaml 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/ParameterSetting.xaml.cs 215 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/SettingModal.xaml 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/SettingModal.xaml.cs 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/TestItem.xaml 168 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/TestItem.xaml.cs 327 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/TestItemModal.xaml 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/TestItemModal.xaml.cs 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/Util/ParameterManager.cs 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/Util/TestItemManager.cs 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Style/FormStyle.xaml 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Style/PanuonResource.xaml 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Util/MsgBoxHelper.cs 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Util/ToastHelper.cs 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Util/ExcelColumn.cs 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Util/ExcelHelper.cs 137 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Util/HMI.Util.csproj 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Util/IniFileHelper.cs 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Util/Properties/AssemblyInfo.cs 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Util/PropertyHelper.cs 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Util/packages.config 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/ViewControl.xaml.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsExecution/TsExecution.xaml.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/3rds/3rds Libs.txt 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Lang/zh-CN/ParamManage.json 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Lang/zh-CN/app.json 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/TextFile1.txt 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/WPFProject.csproj 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/WPFProject.sln 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Util/PathHelper.cs
@@ -100,7 +100,7 @@
            return Path;
        }
        private static string InitFolder(string path)
        public static string InitFolder(string path)
        {
            try
            {
HMI.DataViewer/DataViewer.xaml.cs
@@ -431,7 +431,7 @@
                workbook.CreateSheet("sheet1");////新建一个工作表
                XSSFSheet SheetOne = (XSSFSheet)workbook.GetSheet("Sheet1"); ////获取名称为Sheet1的工作表,创建标题头
                IRow row1 = SheetOne.CreateRow(0);////获取Sheet1工作表的首行,设置表头,获取列
                XSSFCell[] SheetCell = new XSSFCell[currentColumns.Count];
                //XSSFCell[] SheetCell = new XSSFCell[currentColumns.Count];
                for (int i = 0; i < currentColumns.Count; i++)
                {
                    row1.CreateCell(i).SetCellValue(currentColumns[i].Header.ToString());
HMI.MenuManage/Modal/IconSelector.xaml.cs
@@ -71,7 +71,7 @@
        public bool? Open(string currentValue)
        {
            list.ItemsSource = icons.Select(i => i.Code).ToList();
            list.ItemsSource = icons?.Select(i => i.Code).ToList();
            Data = currentValue;
            return this.ShowDialog();
        }
HMI.MenuManage/Modal/MenuModal.xaml.cs
@@ -85,7 +85,7 @@
            string sql = $"SELECT 1 FROM menu WHERE code='{TbCode.Text}'{segment};";
            if (sqliteHelper.HasData(sql))
            {
                err = $"{LangUtil.Get("HMI.Menu.Code")}{LangUtil.Get("HMI.Menu.CodeMustBeUnique")}";
                err = this.lang("App.MustBeUnique", LangUtil.Get("HMI.Menu.Code")); ;
                return false;
            }
            return true;
HMI.ParameterManage/Entity/ParameterData.cs
@@ -1,15 +1,26 @@
using HMI.Core.Entity;
using HMI.Util;
namespace HMI.ParameterManage.Entity
{
    public class ParameterData : SelectBase
    {
        public string Id { get; set; }
        [ExcelColumn("设置项名称")]
        public string Name { get; set; }
        [ExcelColumn("设置项类型")]
        public string Type { get; set; }
        [ExcelColumn("值")]
        public string Value { get; set; }
        [ExcelColumn("单位")]
        public string Unit { get; set; }
        [ExcelColumn("描述")]
        public string Description { get; set; }
        [ExcelColumn("分组")]
        public string PGroup { get; set; }
        [ExcelColumn("来源")]
        public string Source { get; set; }
        public string ProductName { get; set; }
    }
}
HMI.ParameterManage/Entity/TestItemData.cs
New file
@@ -0,0 +1,67 @@
using HMI.Core.Entity;
using HMI.Util;
namespace HMI.ParameterManage.Entity
{
    public class TestItemData : SelectBase
    {
        /// <summary>
        /// 参数ID
        /// </summary>
        public string ID { get; set; } = string.Empty;
        /// <summary>
        /// 测试项名称
        /// </summary>
        [ExcelColumn("测试项名称")]
        public string Name { get; set; }
        /// <summary>
        /// 测试项类型
        /// </summary>
        [ExcelColumn("测试项类型")]
        public string Type { get; set; } = "NumberTest";
        /// <summary>
        /// 字符串型数据
        /// </summary>
        [ExcelColumn("字符串")]
        public string StrValue { get; set; }
        /// <summary>
        /// 测试项下限
        /// </summary>
        [ExcelColumn("下限")]
        public double LimitDown { get; set; }
        /// <summary>
        /// 测试项上限
        /// </summary>
        [ExcelColumn("上限")]
        public double LimitUp { get; set; }
        /// <summary>
        /// 使能
        /// </summary>
        [ExcelColumn("使能")]
        public bool Enable { get; set; }
        /// <summary>
        /// 补偿值
        /// </summary>
        [ExcelColumn("补偿值")]
        public double Offset { get; set; }
        /// <summary>
        /// 来源
        /// </summary>
        [ExcelColumn("来源")]
        public string Source { get; set; } = string.Empty;
        /// <summary>
        /// 分组
        /// </summary>
        public string Group { get; set; } = string.Empty;
    }
}
HMI.ParameterManage/HMI.ParameterManage.csproj
@@ -53,6 +53,7 @@
    <Reference Include="log4net">
      <HintPath>..\WPFProject\packages\log4net.3.0.3\lib\net462\log4net.dll</HintPath>
    </Reference>
    <Reference Include="NPOI, Version=2.4.1.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1" />
    <Reference Include="Panuon.WPF">
      <HintPath>..\WPFProject\packages\Panuon.WPF.1.1.2.2\lib\net472\Panuon.WPF.dll</HintPath>
    </Reference>
@@ -75,14 +76,36 @@
    <Reference Include="PresentationFramework" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Entity\TestItemData.cs" />
    <Compile Include="TestItemModal.xaml.cs">
      <DependentUpon>TestItemModal.xaml</DependentUpon>
    </Compile>
    <Compile Include="TestItem.xaml.cs">
      <DependentUpon>TestItem.xaml</DependentUpon>
    </Compile>
    <Compile Include="SettingModal.xaml.cs">
      <DependentUpon>SettingModal.xaml</DependentUpon>
    </Compile>
    <Compile Include="ParameterModal.xaml.cs">
      <DependentUpon>ParameterModal.xaml</DependentUpon>
    </Compile>
    <Compile Include="ParameterSetting.xaml.cs">
      <DependentUpon>ParameterSetting.xaml</DependentUpon>
    </Compile>
    <Compile Include="ProductModal.xaml.cs">
      <DependentUpon>ProductModal.xaml</DependentUpon>
    </Compile>
    <Compile Include="Model\XmlModel.cs" />
    <Compile Include="Util\TestItemManager.cs" />
    <Compile Include="Util\ParameterManager.cs" />
    <Page Include="TestItemModal.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
    </Page>
    <Page Include="TestItem.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
    </Page>
    <Page Include="ParameterManage.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
@@ -94,7 +117,15 @@
      <DependentUpon>ParameterManage.xaml</DependentUpon>
      <SubType>Code</SubType>
    </Compile>
    <Page Include="SettingModal.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
    </Page>
    <Page Include="ParameterModal.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
    </Page>
    <Page Include="ParameterSetting.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
    </Page>
@@ -139,6 +170,10 @@
      <Project>{f17de6b5-af1d-48c6-94a0-6b763a6546a6}</Project>
      <Name>HMI.UI</Name>
    </ProjectReference>
    <ProjectReference Include="..\HMI.Util\HMI.Util.csproj">
      <Project>{afea100a-9223-448d-ab10-3015d93dd98c}</Project>
      <Name>HMI.Util</Name>
    </ProjectReference>
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <PropertyGroup>
HMI.ParameterManage/ParameterSetting.xaml
@@ -15,7 +15,7 @@
             Loaded="UserControl_Loaded"
             Background="{StaticResource WorkspaceBackground}"
             d:DesignHeight="450" d:DesignWidth="800">
    <Border Style="{StaticResource CardBoarder}">
        <Grid dp:GridHelper.Space="10" Cursor="">
            <Grid.RowDefinitions>
@@ -33,6 +33,8 @@
                            x:Name="addBtn" Click="Button_Click"/>
                    <Button Style="{StaticResource RfhBtn}"
                            x:Name="refreshBtn" Click="Button_Click"/>
                    <Button Style="{StaticResource DelBtn}"
                            x:Name="delBatchBtn" Click="Button_Click"/>
                    <Button x:Name="exportBtn"
                            Style="{StaticResource ExportBtn}" Click="Button_Click"/>
                    <Button x:Name="importBtn" 
@@ -42,40 +44,22 @@
                <StackPanel Style="{StaticResource ButtonGroup}" HorizontalAlignment="Left">
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="{DynamicResource HMI.Line}"/>
                    <ComboBox Style="{StaticResource FormComboxStyle}" x:Name="CbLine"/>
                    <ComboBox Style="{StaticResource FormComboxStyle}" Width="160" x:Name="CbLine"/>
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="{DynamicResource HMI.Station}"/>
                    <ComboBox Style="{StaticResource FormComboxStyle}" x:Name="CbStation"/>
                    <ComboBox Style="{StaticResource FormComboxStyle}" Width="160" x:Name="CbStation"/>
                </StackPanel>
            </DockPanel>
            <DataGrid Grid.Row="2" x:Name="table" Style="{StaticResource DefaultDataGrid}">
                <DataGrid.RowStyle>
                    <Style TargetType="DataGridRow" BasedOn="{StaticResource {x:Static pu:StyleKeys.DataGridRowStyle}}">
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding type}" Value="warn">
                                <Setter Property="Background" Value="{StaticResource WarningColor}"/>
                                <Setter Property="Foreground" Value="Black"/>
                            </DataTrigger>
                            <DataTrigger Binding="{Binding type}" Value="error">
                                <Setter Property="Background" Value="{StaticResource DangerColor}"/>
                                <Setter Property="Foreground" Value="Black"/>
                            </DataTrigger>
                            <Trigger Property="IsSelected" Value="True">
                                <Setter Property="Background" Value="Transparent"/>
                            </Trigger>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="Background" Value="Transparent"/>
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </DataGrid.RowStyle>
            <DataGrid Grid.Row="2" x:Name="table" Style="{StaticResource DefaultDataGrid}"
                      ItemsSource="{Binding Datasource}">
                <DataGrid.Columns>
                    <DataGridTemplateColumn Header="" Width="Auto" CanUserResize="False">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <CheckBox VerticalAlignment="Center" IsChecked="{Binding Selected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
                                <CheckBox VerticalAlignment="Center"
                                          IsChecked="{Binding IsSelected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
@@ -87,10 +71,58 @@
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                    <DataGridTextColumn Width="*" Binding="{Binding type}" Header="级别" />
                    <DataGridTextColumn Width="*" Binding="{Binding time}" Header="时间" />
                    <DataGridTextColumn Width="*" Binding="{Binding source}" Header="来源" />
                    <DataGridTextColumn Width="*" Binding="{Binding text}" Header="文本" />
                    <DataGridTextColumn Width="*" Binding="{Binding Name}">
                        <DataGridTextColumn.Header>
                            <TextBlock Text="{DynamicResource Pm.Parameter.Name}"/>
                        </DataGridTextColumn.Header>
                    </DataGridTextColumn>
                    <DataGridTextColumn Width="*" Binding="{Binding Type}">
                        <DataGridTextColumn.Header>
                            <TextBlock Text="{DynamicResource Pm.Parameter.Type}"/>
                        </DataGridTextColumn.Header>
                    </DataGridTextColumn>
                    <DataGridTextColumn Width="*" Binding="{Binding Value}">
                        <DataGridTextColumn.Header>
                            <TextBlock Text="{DynamicResource Pm.Parameter.Value}"/>
                        </DataGridTextColumn.Header>
                    </DataGridTextColumn>
                    <DataGridTextColumn Width="*" Binding="{Binding Description}">
                        <DataGridTextColumn.Header>
                            <TextBlock Text="{DynamicResource Pm.Parameter.Desc}"/>
                        </DataGridTextColumn.Header>
                    </DataGridTextColumn>
                    <DataGridTextColumn Width="*" Binding="{Binding Unit}">
                        <DataGridTextColumn.Header>
                            <TextBlock Text="{DynamicResource Pm.Parameter.Unit}"/>
                        </DataGridTextColumn.Header>
                    </DataGridTextColumn>
                    <DataGridTextColumn Width="*" Binding="{Binding PGroup}">
                        <DataGridTextColumn.Header>
                            <TextBlock Text="{DynamicResource Pm.Parameter.Group}"/>
                        </DataGridTextColumn.Header>
                    </DataGridTextColumn>
                    <DataGridTextColumn Width="*" Binding="{Binding Source}">
                        <DataGridTextColumn.Header>
                            <TextBlock Text="{DynamicResource Pm.Parameter.Source}"/>
                        </DataGridTextColumn.Header>
                    </DataGridTextColumn>
                    <DataGridTemplateColumn Width="110" IsReadOnly="True" CanUserResize="False">
                        <DataGridTemplateColumn.Header>
                            <TextBlock Text="{DynamicResource App.Opt}"/>
                        </DataGridTemplateColumn.Header>
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <StackPanel Orientation="Horizontal">
                                    <Button x:Name="editBtn" Style="{StaticResource EditLink}"
                                            Click="Button_Click" Margin="0,0,10,0"
                                            DataContext="{Binding }"/>
                                    <Button x:Name="delBtn" Style="{StaticResource DeleteLink}"
                                            DataContext="{Binding }"
                                            Click="Button_Click"/>
                                </StackPanel>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                </DataGrid.Columns>
            </DataGrid>
        </Grid>
HMI.ParameterManage/ParameterSetting.xaml.cs
@@ -1,11 +1,17 @@
using HMI.Base;
using HMI.Core.Util;
using HMI.ParameterManage.Entity;
using HMI.ParameterManage.Util;
using HMI.UI.Util;
using HMI.Util;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
namespace HMI.ParameterManage
{
@@ -15,9 +21,18 @@
    public partial class ParameterSetting : PluginControl
    {
        private ObservableCollection<ParameterData> datasource;
        public ObservableCollection<ParameterData> Datasource
        {
            get => datasource;
            set
            {
                Set(ref datasource, value);
            }
        }
        public ParameterSetting()
        {
            this.DataContext = this;
            InitializeComponent();
        }
@@ -25,11 +40,207 @@
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var btn = ((Button)sender);
            ParameterData data = btn.DataContext as ParameterData;
            switch (btn.Name)
            {
                case "addBtn":
                    OpenDataModal(null, false);
                    break;
                case "editBtn":
                    OpenDataModal(data, true);
                    break;
                case "refreshBtn":
                    LoadData();
                    break;
                case "delBatchBtn":
                    DeleteBatch();
                    break;
                case "exportBtn":
                    ExportData();
                    break;
                case "importBtn":
                    ImportData();
                    break;
                case "delBtn":
                    DeleteData(data);
                    break;
                default:
                    break;
            }
        }
        /// <summary>
        /// 加载数据
        /// </summary>
        public void LoadData()
        {
            try
            {
                Datasource = ParameterManager.GetCommonParameter<ObservableCollection<ParameterData>>()
                ?? new ObservableCollection<ParameterData>();
            }
            catch (Exception ex)
            {
                Datasource = new ObservableCollection<ParameterData>();
                log.Error("Refresh Data Failed.", ex);
                ToastHelper.Error(Window.GetWindow(this), $"{this.lang("App.RefreshFailed")}: {ex.Message}");
            }
        }
        /// <summary>
        /// 打开新增或编辑窗口
        /// </summary>
        /// <param name="data"></param>
        public void OpenDataModal(ParameterData data, bool isUpdate)
        {
            try
            {
                int index = isUpdate ? Datasource.IndexOf(data) : -1;
                string title = isUpdate ? $"{this.lang("App.Edit")}" : $"{this.lang("App.Add")}";
                SettingModal modal = new SettingModal()
                {
                    Title = title,
                    Owner = Window.GetWindow(this)
                };
                if (modal.Open(data ?? new ParameterData(), Datasource) == true)
                {
                    if (index == -1)
                    {
                        Datasource.Add(modal.Data);
                        ParameterManager.SaveCommonParameter(Datasource);
                    }
                    else
                    {
                        ParameterManager.SaveCommonParameter(Datasource);
                    }
                    LoadData();
                }
            }
            catch (Exception ex)
            {
                if (isUpdate)
                {
                    log.Error("Edit Data Failed.", ex);
                    MsgBoxHelper.Error(Window.GetWindow(this), $"{this.lang("App.EditFailed")}: {ex.Message}");
                }
                else
                {
                    log.Error("Add Data Failed.", ex);
                    MsgBoxHelper.Error(Window.GetWindow(this), $"{this.lang("App.AddFailed")}: {ex.Message}");
                }
            }
        }
        /// <summary>
        /// 批量删除
        /// </summary>
        public void DeleteBatch()
        {
            try
            {
                var count = Datasource.Where(d => d.IsSelected).Count();
                if (count <= 0)
                {
                    ToastHelper.Warning(this, this.lang("HMI.NoDeleteData"));
                    return;
                }
                if (MsgBoxHelper.WarnMessage(this, this.lang("HMI.MakeSureDeletes", count))
                    == MessageBoxResult.OK)
                {
                    var temp = Datasource.Where(d => !d.IsSelected).ToList();
                    ParameterManager.SaveCommonParameter(temp);
                    LoadData();
                }
            }
            catch (Exception ex)
            {
                log.Error("Delete Batch Data Failed.", ex);
                ToastHelper.Error(Window.GetWindow(this), $"{this.lang("App.DeleteFailed")}: {ex.Message}");
            }
        }
        /// <summary>
        /// 删除参数
        /// </summary>
        /// <param name="data"></param>
        private void DeleteData(ParameterData data)
        {
            try
            {
                if (MsgBoxHelper.WarnMessage(this, this.lang("HMI.MakeSureDeleteSingle", data.Name))
                    == MessageBoxResult.OK)
                {
                    int index = Datasource.IndexOf(data);
                    if (index < 0)
                    {
                        ToastHelper.Error(this, this.lang("App.ParameterException"));
                        return;
                    }
                    var temp = XmlHelper.DeepClone(Datasource);
                    temp.RemoveAt(index);
                    ParameterManager.SaveCommonParameter(temp);
                    LoadData();
                }
            }
            catch (Exception ex)
            {
                log.Error("Delete Parameter Failed.", ex);
                ToastHelper.Error(this, $"{this.lang("App.DeleteFailed")}: {ex.Message}");
                return;
            }
        }
        /// <summary>
        /// 导出数据
        /// </summary>
        private void ExportData()
        {
            try
            {
                SaveFileDialog sfd = new SaveFileDialog();
                sfd.Filter = "";
                sfd.DefaultExt = "xls";
                sfd.FileName = this.lang("Pm.Setting.Title");
                if (sfd.ShowDialog() == true)
                {
                    string targetPath = sfd.FileName;
                    ExcelHelper.ExportExcel(targetPath, Datasource);
                    ToastHelper.Success(Window.GetWindow(this), this.lang("App.ExportSuccess"));
                }
            }
            catch (Exception ex)
            {
                log.Error("Export Data Failed.", ex);
                MsgBoxHelper.Error(Window.GetWindow(this), $"{this.lang("App.ExportFailed")}: {ex.Message}");
            }
        }
        private void ImportData()
        {
            try
            {
                OpenFileDialog openFileDialog = new OpenFileDialog();
                openFileDialog.Filter = "execl(*.xlsx;*.xls)|*.xlsx;*.xls";
                openFileDialog.Title = this.lang("App.SelectFile");
                openFileDialog.Multiselect = false;
                if (openFileDialog.ShowDialog() == true)
                {
                    List<ParameterData> datas = ExcelHelper.ReadFromExcel<ParameterData>(openFileDialog.FileName);
                    ParameterManager.SaveCommonParameter(datas);
                    LoadData();
                }
            }
            catch (Exception ex)
            {
                log.Error("Export Data Failed.", ex);
                MsgBoxHelper.Error(Window.GetWindow(this), $"{this.lang("App.ExportFailed")}: {ex.Message}");
            }
        }
        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            LoadData();
            Loaded -= UserControl_Loaded;
        }
HMI.ParameterManage/SettingModal.xaml
New file
@@ -0,0 +1,81 @@
<pu:WindowX x:Class="HMI.ParameterManage.SettingModal"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:HMI.ParameterManage"
            xmlns:pw="https://opensource.panuon.com/wpf"
            xmlns:pu="https://opensource.panuon.com/wpf-ui"
            xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
            xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI"
            xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
            mc:Ignorable="d"
            Title="Form"
            SizeToContent="Height"
            Width="600"
            WindowStartupLocation="CenterOwner"
            Style="{StaticResource ModalWindow}"
            pu:WindowXModalDialog.OKButtonContent="{DynamicResource App.Save}">
    <Grid Margin="10">
        <StackPanel x:Name="formPanel">
            <pu:FormGroup x:Name="FgName"
                          dp:FormGroupHelper.ValidateMethod="ValidateName"
                          Header="{DynamicResource Pm.Parameter.Name}"
                          dp:FormGroupHelper.IsRequired="True"  GroupName="MyFormGroup">
                <TextBox x:Name="TbName" Style="{StaticResource EditorTextBoxStyle}" />
            </pu:FormGroup>
            <pu:FormGroup x:Name="FgType"
              Header="{DynamicResource Pm.Parameter.Type}"
              dp:FormGroupHelper.IsRequired="True" GroupName="MyFormGroup">
                <ComboBox x:Name="CbType" Style="{StaticResource FormComboxStyle}" SelectedValuePath="Content">
                    <ComboBoxItem>Int</ComboBoxItem>
                    <ComboBoxItem>Double</ComboBoxItem>
                    <ComboBoxItem>Bool</ComboBoxItem>
                    <ComboBoxItem>String</ComboBoxItem>
                </ComboBox>
            </pu:FormGroup>
            <pu:FormGroup x:Name="FgValue" Style="{StaticResource RequiredInputFormGroupStyle}"
                          Header="{DynamicResource Pm.Parameter.Value}"
dp:FormGroupHelper.IsRequired="True" GroupName="MyFormGroup">
                <TextBox x:Name="TbValue" Style="{StaticResource EditorTextBoxStyle}" />
            </pu:FormGroup>
            <pu:FormGroup x:Name="FgUnit" Style="{StaticResource InputFormGroupStyle}"
      Header="{DynamicResource Pm.Parameter.Unit}"
dp:FormGroupHelper.IsRequired="False" GroupName="MyFormGroup">
                <TextBox x:Name="TbUnit" Style="{StaticResource EditorTextBoxStyle}" />
            </pu:FormGroup>
            <pu:FormGroup x:Name="FgDescription" Style="{StaticResource InputFormGroupStyle}"
Header="{DynamicResource Pm.Parameter.Desc}"
dp:FormGroupHelper.IsRequired="False" GroupName="MyFormGroup">
                <TextBox x:Name="TbDescription" Style="{StaticResource MultilineEditorTextBoxStyle}" />
            </pu:FormGroup>
            <pu:FormGroup x:Name="FgGroup" Style="{StaticResource InputFormGroupStyle}"
                          Header="{DynamicResource Pm.Parameter.Group}"
                          GroupName="MyFormGroup">
                <ComboBox x:Name="CbGroup" Style="{StaticResource FormComboxStyle}" SelectedValuePath="Content">
                    <ComboBoxItem>Int</ComboBoxItem>
                    <ComboBoxItem>Double</ComboBoxItem>
                    <ComboBoxItem>Bool</ComboBoxItem>
                    <ComboBoxItem>String</ComboBoxItem>
                </ComboBox>
            </pu:FormGroup>
            <pu:FormGroup x:Name="FgSource" Style="{StaticResource InputFormGroupStyle}"
Header="{DynamicResource Pm.Parameter.Source}"
dp:FormGroupHelper.IsRequired="False" GroupName="MyFormGroup">
                <ComboBox x:Name="CbSource" Style="{StaticResource FormComboxStyle}" SelectedValuePath="Content">
                    <ComboBoxItem>Int</ComboBoxItem>
                    <ComboBoxItem>Double</ComboBoxItem>
                    <ComboBoxItem>Bool</ComboBoxItem>
                    <ComboBoxItem>String</ComboBoxItem>
                </ComboBox>
            </pu:FormGroup>
        </StackPanel>
    </Grid>
</pu:WindowX>
HMI.ParameterManage/SettingModal.xaml.cs
New file
@@ -0,0 +1,112 @@
using HMI.Core.Util;
using HMI.ParameterManage.Entity;
using HMI.UI.DpUtil;
using HMI.UI.Util;
using Panuon.WPF.UI;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
namespace HMI.ParameterManage
{
    /// <summary>
    /// ModalWindow.xaml 的交互逻辑
    /// </summary>
    public partial class SettingModal : WindowX
    {
        public SettingModal()
        {
            this.DataContext = this;
            InitializeComponent();
        }
        public ParameterData Data { get; set; }
        private ObservableCollection<ParameterData> Datasource;
        private bool _isProcessing;
        protected override void OnClosing(CancelEventArgs e)
        {
            //Press Continue Button
            if (DialogResult == true)
            {
                if (_isProcessing)
                {
                    e.Cancel = true;
                    return;
                }
                var okButton = WindowXModalDialog.GetOKButton(this);
                try
                {
                    _isProcessing = true;
                    ButtonHelper.SetIsPending(okButton, true);
                    if (!ValidateForm())
                    {
                        e.Cancel = true;
                        return;
                    }
                    SaveOrUpdateParameter(Data);
                }
                catch (Exception ex)
                {
                    ToastHelper.Error(this, $"Error: {ex.Message}");
                    e.Cancel = true;
                    return;
                }
                finally
                {
                    _isProcessing = false;
                    ButtonHelper.SetIsPending(okButton, false);
                }
            }
            base.OnClosing(e);
        }
        public bool ValidateName(object value, out string err)
        {
            err = null;
            if (Datasource?.FirstOrDefault(e => e.Name == value.ToString()) != null)
            {
                err = this.lang("App.MustBeUnique", this.lang("Pm.Parameter.Name"));
                return false;
            }
            return true;
        }
        private void SaveOrUpdateParameter(ParameterData data)
        {
        }
        private bool ValidateForm()
        {
            bool flag = FormGroupHelper.ValidateForm(formPanel);
            if (flag)
            {
                Data.Name = TbName.Text;
                Data.Type = CbType.Text;
                Data.Value = TbValue.Text;
                Data.Unit = TbUnit.Text;
                Data.Description = TbDescription.Text;
                Data.PGroup = CbGroup.Text;
                Data.Source = CbSource.Text;
            }
            return flag;
        }
        public bool? Open(ParameterData data, ObservableCollection<ParameterData> datasource)
        {
            Datasource = datasource;
            Data = data;
            TbName.Text = Data.Name;
            CbType.SelectedValue = Data.Type;
            TbValue.Text = Data.Value;
            TbUnit.Text = Data.Unit;
            TbDescription.Text = Data.Description;
            CbGroup.SelectedValue = Data.PGroup;
            CbSource.SelectedValue = Data.Source;
            return this.ShowDialog();
        }
    }
}
HMI.ParameterManage/TestItem.xaml
New file
@@ -0,0 +1,168 @@
<hb:PluginControl x:Class="HMI.ParameterManage.TestItem"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
             xmlns:local="clr-namespace:HMI.ParameterManage"
             mc:Ignorable="d"
             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"
             Background="{StaticResource WorkspaceBackground}"
             Loaded="PluginControl_Loaded"
             d:DesignHeight="450" d:DesignWidth="800">
    <Border Style="{StaticResource CardBoarder}">
        <Grid dp:GridHelper.Space="10">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <DockPanel Grid.ColumnSpan="2">
                <TextBlock Style="{StaticResource TitleBox}" Text="{DynamicResource Pm.Ti.Title}"/>
            </DockPanel>
            <DockPanel Grid.Row="1" Cursor="">
                <StackPanel DockPanel.Dock="Right" Style="{StaticResource ButtonGroup}">
                    <Button Style="{StaticResource AddBtn}"
                x:Name="addBtn" Click="Button_Click"/>
                    <Button Style="{StaticResource RfhBtn}"
                x:Name="refreshBtn" Click="Button_Click"/>
                    <Button Style="{StaticResource DelBtn}"
                x:Name="delBatchBtn" Click="Button_Click"/>
                    <Button x:Name="exportBtn"
                Style="{StaticResource ExportBtn}" Click="Button_Click"/>
                    <Button x:Name="importBtn"
                Style="{StaticResource ImportBtn}" Click="Button_Click"/>
                </StackPanel>
                <StackPanel Style="{StaticResource ButtonGroup}" HorizontalAlignment="Left">
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="{StaticResource ContentFontSize2}"
            Text="{DynamicResource HMI.Line}"/>
                    <ComboBox x:Name="CbLine" Style="{StaticResource FormComboxStyle}" SelectedValuePath="Content" MinWidth="180"/>
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="{StaticResource ContentFontSize2}"
            Text="{DynamicResource HMI.Station}"/>
                    <ComboBox x:Name="CbStation" Style="{StaticResource FormComboxStyle}" SelectedValuePath="Content" MinWidth="180"/>
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="{StaticResource ContentFontSize2}"
            Text="{DynamicResource App.EndTime}"/>
                    <ComboBox x:Name="CbShineng" Style="{StaticResource FormComboxStyle}" SelectedValuePath="Content" MinWidth="180"/>
                </StackPanel>
            </DockPanel>
            <Grid Grid.Row="2">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="2*"/>
                    <ColumnDefinition Width="8*"/>
                </Grid.ColumnDefinitions>
                <Border Grid.Column="0" BorderThickness="2,2,0,2" BorderBrush="{StaticResource CommonBoarder}">
                    <DataGrid Style="{StaticResource DefaultDataGrid}"
                          ItemsSource="{Binding Products}"
                          SelectedItem="{Binding SelectedProduct}">
                        <DataGrid.Columns>
                            <DataGridTemplateColumn Header="#" CanUserResize="False">
                                <DataGridTemplateColumn.CellTemplate>
                                    <DataTemplate>
                                        <TextBlock Text="{Binding Converter={StaticResource rowNumber},
RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGridRow},AncestorLevel=1}}"/>
                                    </DataTemplate>
                                </DataGridTemplateColumn.CellTemplate>
                            </DataGridTemplateColumn>
                            <DataGridTextColumn Width="*" Binding="{Binding Name}">
                                <DataGridTextColumn.Header>
                                    <TextBlock Text="{DynamicResource Pm.Product.Name}"/>
                                </DataGridTextColumn.Header>
                            </DataGridTextColumn>
                        </DataGrid.Columns>
                    </DataGrid>
                </Border>
                <Border Grid.Column="1" BorderThickness="2" BorderBrush="{StaticResource CommonBoarder}">
                    <DataGrid Style="{StaticResource DefaultDataGrid}" ItemsSource="{Binding TestItems}">
                        <DataGrid.Columns>
                            <DataGridTemplateColumn Header="" Width="Auto" CanUserResize="False">
                                <DataGridTemplateColumn.CellTemplate>
                                    <DataTemplate>
                                        <CheckBox VerticalAlignment="Center"
                      IsChecked="{Binding IsSelected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
                                    </DataTemplate>
                                </DataGridTemplateColumn.CellTemplate>
                            </DataGridTemplateColumn>
                            <DataGridTemplateColumn Header="#" CanUserResize="False">
                                <DataGridTemplateColumn.CellTemplate>
                                    <DataTemplate>
                                        <TextBlock Text="{Binding Converter={StaticResource rowNumber},
RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGridRow},AncestorLevel=1}}"/>
                                    </DataTemplate>
                                </DataGridTemplateColumn.CellTemplate>
                            </DataGridTemplateColumn>
                            <DataGridTextColumn Width="*" Binding="{Binding Name}">
                                <DataGridTextColumn.Header>
                                    <TextBlock Text="{DynamicResource Pm.Ti.Name}"/>
                                </DataGridTextColumn.Header>
                            </DataGridTextColumn>
                            <DataGridTextColumn Width="*" Binding="{Binding Type}">
                                <DataGridTextColumn.Header>
                                    <TextBlock Text="{DynamicResource Pm.Ti.Type}"/>
                                </DataGridTextColumn.Header>
                            </DataGridTextColumn>
                            <DataGridTextColumn Width="*" Binding="{Binding Enable}">
                                <DataGridTextColumn.Header>
                                    <TextBlock Text="{DynamicResource Pm.Ti.Shineng}"/>
                                </DataGridTextColumn.Header>
                            </DataGridTextColumn>
                            <DataGridTextColumn Width="*" Binding="{Binding LimitDown}">
                                <DataGridTextColumn.Header>
                                    <TextBlock Text="{DynamicResource Pm.Ti.LimitDown}"/>
                                </DataGridTextColumn.Header>
                            </DataGridTextColumn>
                            <DataGridTextColumn Width="*" Binding="{Binding LimitUp}">
                                <DataGridTextColumn.Header>
                                    <TextBlock Text="{DynamicResource Pm.Ti.LimitUp}"/>
                                </DataGridTextColumn.Header>
                            </DataGridTextColumn>
                            <DataGridTextColumn Width="*" Binding="{Binding StrValue}">
                                <DataGridTextColumn.Header>
                                    <TextBlock Text="{DynamicResource Pm.Ti.StrValue}"/>
                                </DataGridTextColumn.Header>
                            </DataGridTextColumn>
                            <DataGridTextColumn Width="*" Binding="{Binding Offset}">
                                <DataGridTextColumn.Header>
                                    <TextBlock Text="{DynamicResource Pm.Ti.Offset}"/>
                                </DataGridTextColumn.Header>
                            </DataGridTextColumn>
                            <DataGridTextColumn Width="*" Binding="{Binding Source}">
                                <DataGridTextColumn.Header>
                                    <TextBlock Text="{DynamicResource Pm.Ti.Source}"/>
                                </DataGridTextColumn.Header>
                            </DataGridTextColumn>
                            <DataGridTemplateColumn Width="100" IsReadOnly="True" CanUserResize="False">
                                <DataGridTemplateColumn.Header>
                                    <TextBlock Text="{DynamicResource App.Opt}"/>
                                </DataGridTemplateColumn.Header>
                                <DataGridTemplateColumn.CellTemplate>
                                    <DataTemplate>
                                        <StackPanel Orientation="Horizontal">
                                            <Button x:Name="editBtn"
                                                Style="{StaticResource EditLink}" Margin="0,0,10,0"
          DataContext="{Binding }"
          Click="Button_Click"/>
                                            <Button x:Name="delBtn" Style="{StaticResource DeleteLink}"
          DataContext="{Binding }"
          Click="Button_Click"/>
                                        </StackPanel>
                                    </DataTemplate>
                                </DataGridTemplateColumn.CellTemplate>
                            </DataGridTemplateColumn>
                        </DataGrid.Columns>
                    </DataGrid>
                </Border>
            </Grid>
        </Grid>
    </Border>
</hb:PluginControl>
HMI.ParameterManage/TestItem.xaml.cs
New file
@@ -0,0 +1,327 @@
using HMI.Base;
using HMI.Core.Util;
using HMI.ParameterManage.Entity;
using HMI.ParameterManage.Util;
using HMI.UI.Util;
using HMI.Util;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Windows;
namespace HMI.ParameterManage
{
    /// <summary>
    /// UserControl1.xaml 的交互逻辑
    /// </summary>
    public partial class TestItem : PluginControl
    {
        public TestItem()
        {
            DataContext = this;
            InitializeComponent();
        }
        private ObservableCollection<ProductData> products;
        public ObservableCollection<ProductData> Products
        {
            get => products;
            set { Set(ref products, value); }
        }
        private ObservableCollection<TestItemData> testItems;
        public ObservableCollection<TestItemData> TestItems
        {
            get => testItems;
            set { Set(ref testItems, value); }
        }
        private ProductData selectedProduct;
        public ProductData SelectedProduct
        {
            get => selectedProduct;
            set
            {
                Set(ref selectedProduct, value);
                LoadTestItems();
            }
        }
        bool isActivated;
        public static string FileFolder { get; private set; }
        public override object Active()
        {
            if (!isActivated)
            {
                string folder = Path.Combine(ParameterManage.FileFolder, "TestItem");
                PathHelper.InitFolder(folder);
                base.SetBaseFolder(folder);
                FileFolder = folder;
                isActivated = true;
            }
            return null;
        }
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            System.Windows.Controls.Button btn = sender as System.Windows.Controls.Button;
            TestItemData data = btn.DataContext as TestItemData;
            switch (btn.Name)
            {
                case "addBtn":
                    OpenTestItemModal(null, false);
                    break;
                case "editBtn":
                    OpenTestItemModal(data, true);
                    break;
                case "refreshBtn":
                    LoadProducts();
                    break;
                case "delBtn":
                    DeleteData(data);
                    break;
                case "delBatchBtn":
                    DeleteBatch();
                    break;
                case "exportBtn":
                    ExportData();
                    break;
                case "importBtn":
                    ImportData();
                    break;
                default:
                    break;
            }
        }
        /// <summary>
        /// 加载数据
        /// </summary>
        public void LoadTestItems()
        {
            if (SelectedProduct == null)
            {
                TestItems = new ObservableCollection<TestItemData>();
                return;
            }
            try
            {
                TestItems =
                    TestItemManager.GetTestItemByProduct<ObservableCollection<TestItemData>>(SelectedProduct.Name)
                ?? new ObservableCollection<TestItemData>();
            }
            catch (Exception ex)
            {
                TestItems = new ObservableCollection<TestItemData>();
                log.Error("Refresh Data Failed.", ex);
                ToastHelper.Error(Window.GetWindow(this), $"{this.lang("App.RefreshFailed")}: {ex.Message}");
            }
        }
        /// <summary>
        /// 打开新增或编辑窗口
        /// </summary>
        /// <param name="data"></param>
        public void OpenTestItemModal(TestItemData data, bool isUpdate)
        {
            try
            {
                if (SelectedProduct == null)
                {
                    ToastHelper.Warning(this, this.lang("Pm.Ti.PleaseSelectProduct"));
                    return;
                }
                int index = isUpdate ? TestItems.IndexOf(data) : -1;
                string title = isUpdate ? $"{this.lang("App.Edit")}" : $"{this.lang("App.Add")}";
                TestItemModal modal = new TestItemModal()
                {
                    Title = title,
                    Owner = Window.GetWindow(this)
                };
                if (modal.Open(data ?? new TestItemData(), TestItems) == true)
                {
                    if (index == -1)
                    {
                        TestItems.Add(modal.Data);
                        TestItemManager.SaveTestItemByProduct(SelectedProduct.Name, TestItems);
                    }
                    else
                    {
                        TestItemManager.SaveTestItemByProduct(SelectedProduct.Name, TestItems);
                    }
                    LoadTestItems();
                }
            }
            catch (Exception ex)
            {
                if (isUpdate)
                {
                    log.Error("Edit Test Item Failed.", ex);
                    MsgBoxHelper.Error(Window.GetWindow(this), $"{this.lang("App.EditFailed")}: {ex.Message}");
                }
                else
                {
                    log.Error("Add Test Item Failed.", ex);
                    MsgBoxHelper.Error(Window.GetWindow(this), $"{this.lang("App.AddFailed")}: {ex.Message}");
                }
            }
        }
        /// <summary>
        /// 批量删除
        /// </summary>
        public void DeleteBatch()
        {
            try
            {
                if (SelectedProduct == null)
                {
                    ToastHelper.Warning(this, this.lang("Pm.Ti.PleaseSelectProduct"));
                    return;
                }
                var count = TestItems.Where(d => d.IsSelected).Count();
                if (count <= 0)
                {
                    ToastHelper.Warning(this, this.lang("HMI.NoDeleteData"));
                    return;
                }
                if (MsgBoxHelper.WarnMessage(this, this.lang("HMI.MakeSureDeletes", count))
                    == MessageBoxResult.OK)
                {
                    var temp = TestItems.Where(d => !d.IsSelected).ToList();
                    TestItemManager.SaveTestItemByProduct(SelectedProduct.Name, temp);
                    LoadTestItems();
                }
            }
            catch (Exception ex)
            {
                log.Error("Delete Batch Data Failed.", ex);
                ToastHelper.Error(Window.GetWindow(this), $"{this.lang("App.DeleteFailed")}: {ex.Message}");
            }
        }
        /// <summary>
        /// 删除参数
        /// </summary>
        /// <param name="data"></param>
        private void DeleteData(TestItemData data)
        {
            try
            {
                if (SelectedProduct == null)
                {
                    ToastHelper.Warning(this, this.lang("Pm.Ti.PleaseSelectProduct"));
                    return;
                }
                if (MsgBoxHelper.WarnMessage(this, this.lang("HMI.MakeSureDeleteSingle", data.Name))
                    == MessageBoxResult.OK)
                {
                    int index = TestItems.IndexOf(data);
                    if (index < 0)
                    {
                        ToastHelper.Error(this, this.lang("App.ParameterException"));
                        return;
                    }
                    var temp = XmlHelper.DeepClone(TestItems);
                    temp.RemoveAt(index);
                    TestItemManager.SaveTestItemByProduct(SelectedProduct.Name, temp);
                    LoadTestItems();
                }
            }
            catch (Exception ex)
            {
                log.Error("Delete Test Item Data Failed.", ex);
                ToastHelper.Error(this, $"{this.lang("App.DeleteFailed")}: {ex.Message}");
                return;
            }
        }
        /// <summary>
        /// 导出数据
        /// </summary>
        private void ExportData()
        {
            try
            {
                if (SelectedProduct == null)
                {
                    ToastHelper.Warning(this, this.lang("Pm.Ti.PleaseSelectProduct"));
                    return;
                }
                SaveFileDialog sfd = new SaveFileDialog();
                sfd.Filter = "";
                sfd.DefaultExt = "xls";
                sfd.FileName = $"{SelectedProduct.Name}{this.lang("Pm.Ti.Title")}";
                if (sfd.ShowDialog() == true)
                {
                    string targetPath = sfd.FileName;
                    ExcelHelper.ExportExcel(targetPath, TestItems);
                    ToastHelper.Success(Window.GetWindow(this), this.lang("App.ExportSuccess"));
                }
            }
            catch (Exception ex)
            {
                log.Error("Export Test Item Failed.", ex);
                MsgBoxHelper.Error(Window.GetWindow(this), $"{this.lang("App.ExportFailed")}: {ex.Message}");
            }
        }
        private void ImportData()
        {
            try
            {
                if (SelectedProduct == null)
                {
                    ToastHelper.Warning(this, this.lang("Pm.Ti.PleaseSelectProduct"));
                    return;
                }
                OpenFileDialog openFileDialog = new OpenFileDialog();
                openFileDialog.Filter = "execl(*.xlsx;*.xls)|*.xlsx;*.xls";
                openFileDialog.Title = this.lang("App.SelectFile");
                openFileDialog.Multiselect = false;
                if (openFileDialog.ShowDialog() == true)
                {
                    List<TestItemData> datas = ExcelHelper.ReadFromExcel<TestItemData>(openFileDialog.FileName);
                    TestItemManager.SaveTestItemByProduct(SelectedProduct.Name, TestItems);
                    LoadTestItems();
                }
            }
            catch (Exception ex)
            {
                log.Error("Export Test Item Failed.", ex);
                MsgBoxHelper.Error(Window.GetWindow(this), $"{this.lang("App.ExportFailed")}: {ex.Message}");
            }
        }
        private void LoadProducts()
        {
            try
            {
                ObservableCollection<ProductData> products =
                ParameterManager.GetProducts<ObservableCollection<ProductData>>()
                ?? new ObservableCollection<ProductData>();
                Products = products;
            }
            catch (Exception ex)
            {
                Products = new ObservableCollection<ProductData>();
                log.Error("Load Products Failed.", ex);
                ToastHelper.Error(this, this.lang("App.RefreshFailed"));
            }
        }
        private void PluginControl_Loaded(object sender, RoutedEventArgs e)
        {
            LoadProducts();
            Loaded -= PluginControl_Loaded;
        }
    }
}
HMI.ParameterManage/TestItemModal.xaml
New file
@@ -0,0 +1,71 @@
<pu:WindowX x:Class="HMI.ParameterManage.TestItemModal"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:HMI.ParameterManage"
            xmlns:pw="https://opensource.panuon.com/wpf"
            xmlns:pu="https://opensource.panuon.com/wpf-ui"
            xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
            xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
            mc:Ignorable="d"
            Title="Form"
            SizeToContent="Height"
            Width="600"
            WindowStartupLocation="CenterOwner"
            BorderBrush="#3E3E45"
            BorderThickness="1"
            Foreground="Black"
            Style="{StaticResource ModalWindow}">
    <Grid Margin="10">
        <StackPanel x:Name="formPanel">
            <pu:FormGroup x:Name="FgName" Style="{StaticResource InputFormGroupStyle}"
                          Header="{DynamicResource Pm.Ti.Name}" GroupName="MyFormGroup"
                          dp:FormGroupHelper.IsRequired="True"
                          dp:FormGroupHelper.ValidateMethod="ValidateName">
                <TextBox x:Name="TbName" Style="{StaticResource EditorTextBoxStyle}" />
            </pu:FormGroup>
            <pu:FormGroup x:Name="FgType" Style="{StaticResource InputFormGroupStyle}"
                          Header="{DynamicResource Pm.Ti.Type}"
                          dp:FormGroupHelper.IsRequired="True" GroupName="MyFormGroup">
                <ComboBox x:Name="CbType" Style="{StaticResource FormComboxStyle}" SelectedValuePath="Content">
                    <ComboBoxItem>PassFail</ComboBoxItem>
                    <ComboBoxItem>Int</ComboBoxItem>
                    <ComboBoxItem>Double</ComboBoxItem>
                    <ComboBoxItem>String</ComboBoxItem>
                </ComboBox>
            </pu:FormGroup>
            <pu:FormGroup x:Name="FgEnable" Style="{StaticResource InputFormGroupStyle}"
                  Header="{DynamicResource Pm.Ti.Shineng}" GroupName="MyFormGroup">
                <CheckBox x:Name="CoEnable" />
            </pu:FormGroup>
            <pu:FormGroup x:Name="FgLimitDown" Style="{StaticResource InputFormGroupStyle}"
      Header="{DynamicResource Pm.Ti.LimitDown}" GroupName="MyFormGroup">
                <pu:NumberInput x:Name="NiLimitDown" Style="{StaticResource FormNumberInput}" />
            </pu:FormGroup>
            <pu:FormGroup x:Name="FgLimitUp" Style="{StaticResource InputFormGroupStyle}"
Header="{DynamicResource Pm.Ti.LimitUp}" GroupName="MyFormGroup">
                <pu:NumberInput x:Name="NiLimitUp" Style="{StaticResource FormNumberInput}" />
            </pu:FormGroup>
            <pu:FormGroup x:Name="FgStrValue" Style="{StaticResource InputFormGroupStyle}"
Header="{DynamicResource Pm.Ti.StrValue}" GroupName="MyFormGroup">
                <TextBox x:Name="TbStrValue" Style="{StaticResource EditorTextBoxStyle}" />
            </pu:FormGroup>
            <pu:FormGroup x:Name="FgOffset" Style="{StaticResource InputFormGroupStyle}"
Header="{DynamicResource Pm.Ti.Offset}" GroupName="MyFormGroup">
                <pu:NumberInput x:Name="NiOffset" Style="{StaticResource FormNumberInput}" />
            </pu:FormGroup>
            <pu:FormGroup x:Name="FgSource" Style="{StaticResource InputFormGroupStyle}"
Header="{DynamicResource Pm.Ti.Source}" GroupName="MyFormGroup">
                <ComboBox x:Name="CbSource" SelectedValuePath="Content" Style="{StaticResource FormComboxStyle}" />
            </pu:FormGroup>
        </StackPanel>
    </Grid>
</pu:WindowX>
HMI.ParameterManage/TestItemModal.xaml.cs
New file
@@ -0,0 +1,114 @@
using HMI.Core.Util;
using HMI.ParameterManage.Entity;
using HMI.UI.DpUtil;
using HMI.UI.Util;
using Panuon.WPF.UI;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
namespace HMI.ParameterManage
{
    /// <summary>
    /// ModalWindow.xaml 的交互逻辑
    /// </summary>
    public partial class TestItemModal : WindowX
    {
        public TestItemModal()
        {
            this.DataContext = this;
            InitializeComponent();
        }
        public TestItemData Data { get; set; }
        public ObservableCollection<TestItemData> Datasource { 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;
                    }
                    SaveOrUpdateParameter(Data);
                }
                catch (Exception ex)
                {
                    ToastHelper.Error(this, $"Error: {ex.Message}");
                    e.Cancel = true;
                    return;
                }
                finally
                {
                    _isProcessing = false;
                    ButtonHelper.SetIsPending(okButton, false);
                }
            }
            base.OnClosing(e);
        }
        public bool ValidateName(object value, out string err)
        {
            err = null;
            if (Datasource?.FirstOrDefault(e => e.Name == value.ToString()) != null)
            {
                err = this.lang("App.MustBeUnique", this.lang("Pm.Parameter.Name"));
                return false;
            }
            return true;
        }
        private void SaveOrUpdateParameter(TestItemData data)
        {
        }
        private bool ValidateForm()
        {
            bool flag = FormGroupHelper.ValidateForm(formPanel);
            if (flag)
            {
                Data.Name = TbName.Text;
                Data.Type = CbType.Text;
                Data.Enable = CoEnable.IsChecked == true;
                Data.LimitDown = NiLimitDown.Value ?? 0;
                Data.LimitUp = NiLimitUp.Value ?? 0;
                Data.StrValue = TbStrValue.Text;
                Data.Offset = NiOffset.Value ?? 0;
                Data.Source = CbSource.Text;
            }
            return flag;
        }
        public bool? Open(TestItemData data, ObservableCollection<TestItemData> datasource)
        {
            Datasource = datasource;
            Data = data;
            TbName.Text = Data.Name;
            CbType.Text = Data.Type;
            CoEnable.IsChecked = Data.Enable;
            NiLimitDown.Value = Data.LimitDown;
            NiLimitUp.Value = Data.LimitUp;
            TbStrValue.Text = Data.StrValue;
            NiOffset.Value = Data.Offset;
            CbSource.Text = Data.Source;
            return this.ShowDialog();
        }
    }
}
HMI.ParameterManage/Util/ParameterManager.cs
@@ -1,20 +1,34 @@
using HMI.Core.Util;
using HMI.ParameterManage.Entity;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
namespace HMI.ParameterManage.Util
{
    /// <summary>
    /// TODO 优化数据源,需要支持sqlite和xml
    /// </summary>
    public class ParameterManager
    {
        public static List<ProductData> GetProducts()
        /// <summary>
        /// 获取保存的产品
        /// </summary>
        /// <returns></returns>
        public static T GetProducts<T>() where T : IList<ProductData>
        {
            string path = Path.Combine(ParameterManage.FileFolder, "Product.setting");
            if (!File.Exists(path)) return new List<ProductData>();
            List<ProductData> datas = XmlHelper.DeserializeFromXml<List<ProductData>>(path);
            if (!File.Exists(path)) return default(T);
            T datas = XmlHelper.DeserializeFromXml<T>(path);
            return datas;
        }
        /// <summary>
        /// 根据产品名称获取产品参数
        /// </summary>
        /// <param name="productName"></param>
        /// <returns></returns>
        public static List<ParameterData> GetParametersByProductName(string productName)
        {
            string path = Path.Combine(ParameterManage.FileFolder, "Parameter", $"Parameter_{productName}.setting");
@@ -22,5 +36,34 @@
            List<ParameterData> datas = XmlHelper.DeserializeFromXml<List<ParameterData>>(path);
            return datas;
        }
        /// <summary>
        /// 获取通用参数
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static T GetCommonParameter<T>() where T : IList<ParameterData>
        {
            string path = Path.Combine(ParameterManage.FileFolder, "CommonParameter.setting");
            if (!File.Exists(path)) return default(T);
            T datas = XmlHelper.DeserializeFromXml<T>(path);
            return datas;
        }
        /// <summary>
        /// 保存通用参数
        /// </summary>
        /// <param name="datas"></param>
        /// <exception cref="Exception"></exception>
        public static void SaveCommonParameter(IList<ParameterData> datas)
        {
            if (datas == null)
            {
                throw new Exception("不能为null");
            }
            string path = Path.Combine(ParameterManage.FileFolder, "CommonParameter.setting");
            XmlHelper.SerializeToXml(path, datas);
        }
    }
}
HMI.ParameterManage/Util/TestItemManager.cs
New file
@@ -0,0 +1,44 @@
using HMI.Core.Util;
using HMI.ParameterManage.Entity;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
namespace HMI.ParameterManage.Util
{
    /// <summary>
    /// TODO 优化数据源,需要支持sqlite和xml
    /// </summary>
    public class TestItemManager
    {
        /// <summary>
        /// 根据产品获取测试项
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static T GetTestItemByProduct<T>(string product) where T : IList<TestItemData>
        {
            string path = Path.Combine(TestItem.FileFolder, $"TestItem_{product}.setting");
            if (!File.Exists(path)) return default(T);
            T datas = XmlHelper.DeserializeFromXml<T>(path);
            return datas;
        }
        /// <summary>
        /// 根据产品保存测试项
        /// </summary>
        /// <param name="datas"></param>
        /// <exception cref="Exception"></exception>
        public static void SaveTestItemByProduct(string product, IList<TestItemData> datas)
        {
            if (datas == null)
            {
                throw new Exception("不能为null");
            }
            string path = Path.Combine(TestItem.FileFolder, $"TestItem_{product}.setting");
            XmlHelper.SerializeToXml(path, datas);
        }
    }
}
HMI.UI/Style/FormStyle.xaml
@@ -132,6 +132,7 @@
        <Setter Property="pu:ComboBoxHelper.ItemsSelectedForeground" Value="Black"/>
        <Setter Property="pu:ComboBoxHelper.HoverBorderBrush" Value="#007ACC" />
        <Setter Property="pu:ComboBoxHelper.FocusedBorderBrush" Value="#007ACC" />
        <Setter Property="pu:ComboBoxHelper.ClearButtonVisibility" Value="VisibleOnNonnull" />
        <Setter Property="Height" Value="35" />
        <Setter Property="Margin" Value="0" />
        <Setter Property="Background" Value="#FFF" />
HMI.UI/Style/PanuonResource.xaml
@@ -175,7 +175,6 @@
    <Style x:Key="DefaultDataGrid" TargetType="DataGrid" BasedOn="{StaticResource {x:Static pu:StyleKeys.DataGridStyle}}">
        <Style.Resources>
            <Style TargetType="DataGridCell" 
                   BasedOn="{StaticResource {x:Static pu:StyleKeys.DataGridCellStyle}}">
                <Setter Property="Background" Value="Transparent"/>
@@ -188,13 +187,11 @@
                <Setter Property="VerticalAlignment" Value="Center"/>
                <Setter Property="Padding" Value="5"/>
            </Style>-->
            <Style TargetType="DataGridColumnHeader" 
       BasedOn="{StaticResource {x:Static pu:StyleKeys.DataGridColumnHeaderStyle}}">
                <Setter Property="Padding" Value="10"/>
                <Setter Property="BorderThickness" Value="0"/>
            </Style>
        </Style.Resources>
        <Setter Property="pu:DataGridHelper.ColumnHeaderPadding" Value="0"/>
        <Setter Property="RowHeight" Value="30"/>
@@ -231,7 +228,6 @@
        <Setter Property="pu:DataGridHelper.RowSelectedBackground" Value="{StaticResource PrimaryColor}"/>
        <Setter Property="pu:DataGridHelper.RowSelectedForeground" Value="White"/>
        <Setter Property="pu:DataGridHelper.RowHeaderBorderThickness" Value="10,0,0,0"/>
        <Setter Property="pu:DataGridHelper.RowHeaderBackground" Value="Red"/>
        <Setter Property="pu:DataGridHelper.ColumnHeaderBorderThickness" Value="0"/>
        <Setter Property="pu:DataGridHelper.ColumnHeaderPadding" Value="0"/>
    </Style>
HMI.UI/Util/MsgBoxHelper.cs
@@ -8,7 +8,7 @@
    public class MsgBoxHelper
    {
        #region Warn
        public static MessageBoxResult WarnMessage(Window owner,
        public static MessageBoxResult WarnMessage(DependencyObject owner,
            string message,
            string caption = null,
            MessageBoxButton button = MessageBoxButton.OKCancel,
@@ -48,7 +48,7 @@
        #endregion
        #region Info
        public static MessageBoxResult InfoMessage(Window owner,
        public static MessageBoxResult InfoMessage(DependencyObject owner,
            string message,
            string caption = null,
            MessageBoxButton button = MessageBoxButton.OKCancel,
HMI.UI/Util/ToastHelper.cs
@@ -40,39 +40,55 @@
            if (Instance == null) { Instance = helper; }
        }
        public static void Success(Window window,
        public static void Success(DependencyObject dp,
            string message,
            MessageBoxIcon icon = MessageBoxIcon.Success,
            ToastPosition position = ToastPosition.Top, double offset = 0, int durationMs = 3000,
            ToastWindow targetWindow = ToastWindow.ActiveWindow)
        {
            if (!(dp is Window window))
            {
                window = Window.GetWindow(dp);
            }
            Instance.Success(window, message, icon, position, offset, durationMs, targetWindow);
        }
        public static void Warning(Window window,
        public static void Warning(DependencyObject dp,
            string message,
            MessageBoxIcon icon = MessageBoxIcon.Warning,
            ToastPosition position = ToastPosition.Top, double offset = 0, int durationMs = 3000,
            ToastWindow targetWindow = ToastWindow.ActiveWindow)
        {
            if (!(dp is Window window))
            {
                window = Window.GetWindow(dp);
            }
            Instance.Warning(window, message, icon, position, offset, durationMs, targetWindow);
        }
        public static void Error(Window window,
        public static void Error(DependencyObject dp,
            string message,
            MessageBoxIcon icon = MessageBoxIcon.Error,
            ToastPosition position = ToastPosition.Top, double offset = 0, int durationMs = 3000,
            ToastWindow targetWindow = ToastWindow.ActiveWindow)
        {
            if (!(dp is Window window))
            {
                window = Window.GetWindow(dp);
            }
            Instance.Error(window, message, icon, position, offset, durationMs, targetWindow);
        }
        public static void Info(Window window,
        public static void Info(DependencyObject dp,
            string message,
            MessageBoxIcon icon = MessageBoxIcon.Info,
            ToastPosition position = ToastPosition.Top, double offset = 0, int durationMs = 3000,
            ToastWindow targetWindow = ToastWindow.ActiveWindow)
        {
            if (!(dp is Window window))
            {
                window = Window.GetWindow(dp);
            }
            Instance.Info(window, message, icon, position, offset, durationMs, targetWindow);
        }
    }
HMI.Util/ExcelColumn.cs
New file
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HMI.Util
{
    [AttributeUsage(AttributeTargets.Property)]
    public class ExcelColumnAttribute : Attribute
    {
        public string Name { get; private set; }
        public ExcelColumnAttribute(string name)
        {
            this.Name = name;
        }
    }
}
HMI.Util/ExcelHelper.cs
New file
@@ -0,0 +1,137 @@
using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
namespace HMI.Util
{
    public class ExcelHelper
    {
        public static void ExportExcel<T>(string targetPath, IList<T> datas)
        {
            Type type = typeof(T);
            var properties = PropertyHelper.GetAtterProperty<T, ExcelColumnAttribute>(out var attrs);
            if (properties == null || properties.Count <= 0)
            {
                throw new Exception("No Attribute");
            }
            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");
            IRow row1 = SheetOne.CreateRow(0);
            for (int i = 0; i < properties.Count; i++)
            {
                row1.CreateCell(i).SetCellValue((attrs[i]).Name);
            }
            for (int i = 0; i < datas.Count(); i++)
            {
                IRow rowTemp = SheetOne.CreateRow(i + 1);
                for (int j = 0; j < properties.Count; j++)
                {
                    var value = properties[j].GetValue(datas[i]);
                    rowTemp.CreateCell(j).SetCellValue(value.ToString());
                }
            }
            using (FileStream fs = new FileStream(targetPath, FileMode.OpenOrCreate))
            {
                workbook.Write(fs);
            }
        }
        public static List<T> ReadFromExcel<T>(string sourcePath)
        {
            Type type = typeof(T);
            var properties = PropertyHelper.GetAtterProperty<T, ExcelColumnAttribute>(out var attrs);
            if (properties == null || properties.Count <= 0)
            {
                throw new Exception("No Attribute");
            }
            Dictionary<string, PropertyInfo> dict = new Dictionary<string, PropertyInfo>();
            for (int i = 0; i < attrs.Count; i++)
            {
                dict.Add(attrs[i].Name, properties[i]);
            }
            using (FileStream fileStream1 = new FileStream(sourcePath, FileMode.Open, FileAccess.Read))
            {
                IWorkbook workbook = null;
                if (sourcePath.EndsWith(".xlsx"))
                {
                    workbook = new XSSFWorkbook(fileStream1);
                }
                else if (sourcePath.EndsWith(".xls"))
                {
                    workbook = new HSSFWorkbook(fileStream1);
                }
                ISheet sheet = workbook.GetSheetAt(0);
                IRow titleRow = sheet.GetRow(0);
                List<T> result = new List<T>();
                IRow row;
                for (int i = 1; i < (sheet.LastRowNum + 1); i++)
                {
                    row = sheet.GetRow(i);
                    if (row != null)
                    {
                        T obj = (T)Activator.CreateInstance(type);
                        bool isEmpty = true;
                        for (int j = 0; j < row.Count(); j++)
                        {
                            string title = titleRow.GetCell(j).StringCellValue;
                            if (dict.ContainsKey(title))
                            {
                                CellType ct = row.GetCell(j).CellType;
                                object value = null;
                                if (ct == CellType.String)
                                    value = row.GetCell(j).StringCellValue;
                                else if (ct == CellType.Numeric)
                                    value = row.GetCell(j).NumericCellValue;
                                else if (ct == CellType.Boolean)
                                    value = row.GetCell(j).BooleanCellValue;
                                var prop = dict[title];
                                if (prop.PropertyType is string)
                                {
                                    value = Convert.ToString(value);
                                }
                                else if (prop.PropertyType is double || prop.PropertyType is double?)
                                {
                                    value = Convert.ToDouble(value);
                                }
                                else if (prop.PropertyType is int || prop.PropertyType is int?)
                                {
                                    value = Convert.ToInt16(value);
                                }
                                else if (prop.PropertyType is bool || prop.PropertyType is bool?)
                                {
                                    value = Convert.ToBoolean(value);
                                }
                                prop.SetValue(obj, value);
                                isEmpty = false;
                            }
                        }
                        if (!isEmpty) result.Add(obj);
                    }
                }
                workbook.Close();
                return result;
            }
        }
    }
}
HMI.Util/HMI.Util.csproj
New file
@@ -0,0 +1,69 @@
<?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>{AFEA100A-9223-448D-AB10-3015D93DD98C}</ProjectGuid>
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>HMI.Util</RootNamespace>
    <AssemblyName>HMI.Util</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="ICSharpCode.SharpZipLib, Version=1.0.0.999, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\SharpZipLib.1.0.0\lib\net45\ICSharpCode.SharpZipLib.dll</HintPath>
    </Reference>
    <Reference Include="NPOI, Version=2.4.1.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\NPOI.2.4.1\lib\net45\NPOI.dll</HintPath>
    </Reference>
    <Reference Include="NPOI.OOXML, Version=2.4.1.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\NPOI.2.4.1\lib\net45\NPOI.OOXML.dll</HintPath>
    </Reference>
    <Reference Include="NPOI.OpenXml4Net, Version=2.4.1.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\NPOI.2.4.1\lib\net45\NPOI.OpenXml4Net.dll</HintPath>
    </Reference>
    <Reference Include="NPOI.OpenXmlFormats, Version=2.4.1.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\NPOI.2.4.1\lib\net45\NPOI.OpenXmlFormats.dll</HintPath>
    </Reference>
    <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="ExcelHelper.cs" />
    <Compile Include="ExcelColumn.cs" />
    <Compile Include="IniFileHelper.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
    <Compile Include="PropertyHelper.cs" />
  </ItemGroup>
  <ItemGroup>
    <None Include="packages.config" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
HMI.Util/IniFileHelper.cs
New file
@@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
namespace HMI.Util
{
    public class IniFileHelper
    {
        private static String filePath = "./default.ini";
        static List<string> sections = new List<string>();
        static List<string> keys = new List<string>();
        #region API函数声明
        [DllImport("kernel32")]
        private static extern long WritePrivateProfileString(string section, string key,
            string val, string filePath);
        //需要调用GetPrivateProfileString的重载
        [DllImport("kernel32", EntryPoint = "GetPrivateProfileString")]
        private static extern long GetPrivateProfileString(string section, string key,
            string def, StringBuilder retVal, int size, string filePath);
        [DllImport("kernel32", EntryPoint = "GetPrivateProfileString")]
        private static extern uint GetPrivateProfileStringA(string section, string key,
            string def, Byte[] retVal, int size, string filePath);
        #endregion
        public static List<string> ReadSections()
        {
            return ReadSections(filePath);
        }
        public static List<string> ReadSections(string iniFilename)
        {
            List<string> result = new List<string>();
            Byte[] buf = new Byte[65536];
            uint len = GetPrivateProfileStringA(null, null, null, buf, buf.Length, iniFilename);
            int j = 0;
            for (int i = 0; i < len; i++)
                if (buf[i] == 0)
                {
                    result.Add(Encoding.Default.GetString(buf, j, i - j));
                    j = i + 1;
                }
            return result;
        }
        public static List<string> ReadKeys(String SectionName)
        {
            return ReadKeys(SectionName, filePath);
        }
        public static List<string> ReadKeys(string SectionName, string iniFilename)
        {
            List<string> result = new List<string>();
            Byte[] buf = new Byte[65536];
            uint len = GetPrivateProfileStringA(SectionName, null, null, buf, buf.Length, iniFilename);
            int j = 0;
            for (int i = 0; i < len; i++)
                if (buf[i] == 0)
                {
                    result.Add(Encoding.Default.GetString(buf, j, i - j));
                    j = i + 1;
                }
            return result;
        }
        public static void SetFilePath(String filepath)
        {
            filePath = filepath;
        }
        #region 读Ini文件
        public static string ReadIniData(string Section, string Key, string NoText)
        {
            return ReadIniData(Section, Key, NoText, filePath);
        }
        public static string ReadIniData(string Section, string Key, string NoText, string iniFilePath)
        {
            if (File.Exists(iniFilePath))
            {
                StringBuilder temp = new StringBuilder(1024);
                GetPrivateProfileString(Section, Key, NoText, temp, 1024, iniFilePath);
                return temp.ToString();
            }
            else
                return String.Empty;
        }
        #endregion
        #region 写Ini文件
        public static bool WriteIniData(string Section, string Key, string Value, string iniFilePath)
        {
            if (File.Exists(iniFilePath) == false)
            {
                FileStream FS = new FileStream(iniFilePath, FileMode.CreateNew);
            }
            long OpStation = WritePrivateProfileString(Section, Key, Value, iniFilePath);
            if (OpStation == 0)
                return false;
            else
                return true;
        }
        #endregion
    }
}
HMI.Util/Properties/AssemblyInfo.cs
New file
@@ -0,0 +1,33 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// 有关程序集的一般信息由以下
// 控制。更改这些特性值可修改
// 与程序集关联的信息。
[assembly: AssemblyTitle("HMI.Util")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("HMI.Util")]
[assembly: AssemblyCopyright("Copyright ©  2025")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// 将 ComVisible 设置为 false 会使此程序集中的类型
//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
//请将此类型的 ComVisible 特性设置为 true。
[assembly: ComVisible(false)]
// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
[assembly: Guid("afea100a-9223-448d-ab10-3015d93dd98c")]
// 程序集的版本信息由下列四个值组成:
//
//      主版本
//      次版本
//      生成号
//      修订号
//
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
HMI.Util/PropertyHelper.cs
New file
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Reflection;
namespace HMI.Util
{
    class PropertyHelper
    {
        public static List<PropertyInfo> GetAtterProperty<T, A>(out List<A> attrs) where A : Attribute
        {
            var result = new List<PropertyInfo>();
            attrs = new List<A>();
            var type = typeof(T);
            var attrType = typeof(A);
            PropertyInfo[] properties = type.GetProperties();
            foreach (var prop in properties)
            {
                if (Attribute.IsDefined(prop, attrType))
                {
                    result.Add(prop);
                    attrs.Add((A)Attribute.GetCustomAttribute(prop, attrType));
                }
            }
            return result;
        }
    }
}
HMI.Util/packages.config
New file
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="NPOI" version="2.4.1" targetFramework="net472" />
  <package id="SharpZipLib" version="1.0.0" targetFramework="net472" />
</packages>
Plugin.TestResult/ViewControl.xaml.cs
@@ -126,7 +126,7 @@
        public void LoadProduct()
        {
            List<ProductData> products = ParameterManager.GetProducts();
            List<ProductData> products = ParameterManager.GetProducts<List<ProductData>>();
            this.modelSelect.Items.Clear();
            foreach (var prd in products)
            {
Plugin.TsExecution/TsExecution.xaml.cs
@@ -193,12 +193,12 @@
        private void InitProducts()
        {
            Products = new ObservableCollection<ProductData>(ParameterManager.GetProducts());
            Products = ParameterManager.GetProducts<ObservableCollection<ProductData>>();
            if (Products != null && Products.Count > 0)
            {
                List<ParameterData> parameters =
                    ParameterManager.GetParametersByProductName(Products[0].Name);
                string tmpNum = parameters.FirstOrDefault(p => p.Name == "SocketNum")?.Value?.ToString();
                    ParameterManager.GetCommonParameter<List<ParameterData>>();
                string tmpNum = parameters?.FirstOrDefault(p => p.Name == "SocketNum")?.Value?.ToString();
                if (!string.IsNullOrEmpty(tmpNum))
                {
                    this.socketNum = Convert.ToInt32(tmpNum);
WPFProject/3rds/3rds Libs.txt
File was deleted
WPFProject/Lang/zh-CN/ParamManage.json
@@ -10,12 +10,25 @@
  "Pm.Parameter.Name": "名称",
  "Pm.Parameter.Value": "值",
  "Pm.Parameter.Unit": "单位",
  "Pm.Parameter.Type": "类型",
  "Pm.Parameter.Desc": "描述",
  "Pm.Parameter.Group": "分组",
  "Pm.Parameter.Source": "来源",
  "Pm.Parameter.Add": "新增参数",
  "Pm.Parameter.Edit": "编辑参数",
  "Pm.PleaseSelectProduct": "请先选择一项产品",
  "Pm.Setting.Title":"参数管理"
  "Pm.Setting.Title": "参数管理",
  "Pm.Ti.Title": "测试项管理",
  "Pm.Ti.Name": "测试项名称",
  "Pm.Ti.Type": "测试类型",
  "Pm.Ti.Shineng": "使能",
  "Pm.Ti.LimitDown": "下限",
  "Pm.Ti.LimitUp": "上限",
  "Pm.Ti.StrValue": "字符串",
  "Pm.Ti.Offset": "补偿值",
  "Pm.Ti.Source": "来源",
  "Pm.Ti.PleaseSelectProduct": "请先选择产品"
}
WPFProject/Lang/zh-CN/app.json
@@ -137,6 +137,6 @@
  "HMI.Role.Add": "新增角色",
  "HMI.Role.Edit": "编辑角色",
  "HMI.Line": "产线",
  "HMI.Station": "工站",
  "HMI.Line": "产线",
  "HMI.Station": "工站"
}
WPFProject/TextFile1.txt
@@ -10,4 +10,23 @@
move /y "$(TargeDir)HMI.ParameterManage.dll" $(TargeDir)Plugin\HMI.ParameterManage.dll"
move /y "$(TargeDir)HMI.SettingControl.dll" $(TargeDir)Plugin\HMI.SettingControl.dll"
move /y "$(TargeDir)Plugin.TestResult.dll" $(TargeDir)Plugin\Plugin.TestResult.dll"
move /y "$(TargeDir)Plugin.TsExecution.dll" $(TargeDir)Plugin\Plugin.TsExecution.dll"
move /y "$(TargeDir)Plugin.TsExecution.dll" $(TargeDir)Plugin\Plugin.TsExecution.dll"
setlocal enabledelayedexpansion
set "source_folder=$(TargetDir)"
set "destination_folder=$(TargetDir)3rds"
if not exist "%destination_folder%" mkdir "%destination_folder%"
for /d %%D in ("%source_folder%\*") do (
    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" (
        move "%%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" (
        move "%%F" "%destination_folder%"
    )
)
WPFProject/WPFProject.csproj
@@ -372,9 +372,6 @@
      <DependentUpon>Settings.settings</DependentUpon>
      <DesignTimeSharedInput>True</DesignTimeSharedInput>
    </Compile>
    <Content Include="3rds\3rds Libs.txt">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
    <Content Include="Config\Put Your Config Here.txt">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
WPFProject/WPFProject.sln
@@ -37,6 +37,8 @@
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin.TsExecution", "..\Plugin.TsExecution\Plugin.TsExecution.csproj", "{202D9A7B-96B3-4AD4-9977-FC4263207A02}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HMI.Util", "..\HMI.Util\HMI.Util.csproj", "{AFEA100A-9223-448D-AB10-3015D93DD98C}"
EndProject
Global
    GlobalSection(SolutionConfigurationPlatforms) = preSolution
        Debug|Any CPU = Debug|Any CPU
@@ -181,6 +183,14 @@
        {202D9A7B-96B3-4AD4-9977-FC4263207A02}.Release|Any CPU.Build.0 = Release|Any CPU
        {202D9A7B-96B3-4AD4-9977-FC4263207A02}.Release|x64.ActiveCfg = Release|Any CPU
        {202D9A7B-96B3-4AD4-9977-FC4263207A02}.Release|x64.Build.0 = Release|Any CPU
        {AFEA100A-9223-448D-AB10-3015D93DD98C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {AFEA100A-9223-448D-AB10-3015D93DD98C}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {AFEA100A-9223-448D-AB10-3015D93DD98C}.Debug|x64.ActiveCfg = Debug|Any CPU
        {AFEA100A-9223-448D-AB10-3015D93DD98C}.Debug|x64.Build.0 = Debug|Any CPU
        {AFEA100A-9223-448D-AB10-3015D93DD98C}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {AFEA100A-9223-448D-AB10-3015D93DD98C}.Release|Any CPU.Build.0 = Release|Any CPU
        {AFEA100A-9223-448D-AB10-3015D93DD98C}.Release|x64.ActiveCfg = Release|Any CPU
        {AFEA100A-9223-448D-AB10-3015D93DD98C}.Release|x64.Build.0 = Release|Any CPU
    EndGlobalSection
    GlobalSection(SolutionProperties) = preSolution
        HideSolutionNode = FALSE