alielie
2025-03-26 4cac1b533e133fdc8b3def90a0f998820f51907a
优化UI,新增日志自动删除;
59个文件已修改
17个文件已添加
8 文件已重命名
6个文件已删除
3424 ■■■■■ 已修改文件
HMI.Base/PluginLoader.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Annotation/ExcelColumn.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Api/ILogWriter.cs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Api/IProgram.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Api/ISequnce.cs 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Api/IStep.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Api/IStepRunner.cs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Entity/NotifyBase.cs 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Entity/Param.cs 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Entity/ParameterData.cs 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Entity/ProductData.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Entity/TestItemData.cs 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/HMI.Core.csproj 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Manager/ParameterManager.cs 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Manager/ProductManager.cs 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Manager/TestItemManager.cs 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Setting/SystemSetting.cs 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Util/LoggerHolder.cs 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Util/PathHelper.cs 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.IOMonitor/HMI.IOMonitor.csproj 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.LogViewer/LogViewer.xaml 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.LogViewer/LogViewer.xaml.cs 141 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.MenuManage/Entity/MenuData.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.MenuManage/MenuManage.xaml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.MenuManage/Modal/MenuModal.xaml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.MenuManage/Modal/MenuModal.xaml.cs 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.MenuManage/Model/MenuManageModel.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/HMI.ParameterManage.csproj 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/Model/SqlModel.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/Model/XmlModel.cs 142 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/ParameterManage.xaml.cs 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/ParameterModal.xaml.cs 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/ParameterSetting.xaml 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/ParameterSetting.xaml.cs 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/ProductModal.xaml.cs 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/SettingModal.xaml 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/SettingModal.xaml.cs 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/TestItem.xaml.cs 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/TestItemModal.xaml.cs 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/Util/ParameterManager.cs 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.SettingControl/SystemConfig.xaml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Controls/KeyboradWindow.xaml 234 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Controls/KeyboradWindow.xaml.cs 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Controls/NumberInput.cs 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Controls/NumberPanel.xaml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Controls/NumberPanel.xaml.cs 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/HMI.UI.csproj 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Style/FormStyle.xaml 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Util/ExcelHelper.cs 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Util/HMI.Util.csproj 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DOControl/ControlList.xaml 120 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DOControl/ControlList.xaml.cs 224 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DOControl/DOControl.xaml 185 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DOControl/DOControl.xaml.cs 474 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DOControl/DataModal.xaml 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DOControl/DataModal.xaml.cs 157 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DOControl/Entity/DOControlData.cs 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DOControl/Plugin.DOControl.csproj 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.ServoManage/Plugin.ServoManage.csproj 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.ServoManage/ServoManage.xaml 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.ServoManage/ServoManage.xaml.cs 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/DataTable.xaml 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/DataTable.xaml.cs 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/InfoTable.xaml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/Scanner.xaml 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/SingleCell.xaml 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/TestResultView.xaml.cs 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/ViewControl.xaml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/ViewControl.xaml.cs 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsExecution/TsExecution.xaml.cs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/App.config 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/App.xaml.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Controls/MainTabControl.xaml.cs 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Lang/en-US/DOControl.json 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Lang/en-US/LogViewer.json 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Lang/en-US/TestResult.json 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Lang/en-US/app.json 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Lang/en-US/setting.json 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Lang/zh-CN/DOControl.json 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Lang/zh-CN/LogViewer.json 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Lang/zh-CN/TestResult.json 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Lang/zh-CN/app.json 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Lang/zh-CN/setting.json 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Models/Program.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Models/Step.cs 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/PanuonWindow.xaml 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/TextFile1.txt 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Utils/CommonLogger.cs 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Utils/LogCleanner.cs 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/WPFProject.csproj 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Base/PluginLoader.cs
@@ -167,7 +167,7 @@
                string sql = $@"SELECT m.* FROM menu AS m INNER JOIN role_menu_rel AS rrel ON m.code = rrel.menuCode
INNER JOIN role AS r ON rrel.roleCode = r.code
INNER JOIN user_role_rel AS urel ON r.code = urel.roleCode
AND urel.username = '{user.Username}' ORDER BY m.level, m.id;";
AND urel.username = '{user.Username}' ORDER BY m.level,m.sort, m.id;";
                DataTable dt = sqliteHelper.GetDataTable(sql);
                if (dt != null)
HMI.Core/Annotation/ExcelColumn.cs
File was renamed from HMI.Util/ExcelColumn.cs
@@ -1,10 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HMI.Util
namespace HMI.Core
{
    [AttributeUsage(AttributeTargets.Property)]
    public class ExcelColumnAttribute : Attribute
HMI.Core/Api/ILogWriter.cs
New file
@@ -0,0 +1,10 @@
namespace HMI.Core.Api
{
    public interface ILogWriter
    {
        void Debug(string source, string content);
        void Info(string source, string content);
        void Warn(string source, string content);
        void Error(string source, string content);
    }
}
HMI.Core/Api/IProgram.cs
File was deleted
HMI.Core/Api/ISequnce.cs
File was deleted
HMI.Core/Api/IStep.cs
File was deleted
HMI.Core/Api/IStepRunner.cs
File was deleted
HMI.Core/Entity/NotifyBase.cs
@@ -1,10 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace HMI.Core.Entity
HMI.Core/Entity/Param.cs
File was deleted
HMI.Core/Entity/ParameterData.cs
File was renamed from HMI.ParameterManage/Entity/ParameterData.cs
@@ -1,7 +1,4 @@
using HMI.Core.Entity;
using HMI.Util;
namespace HMI.ParameterManage.Entity
namespace HMI.Core.Entity
{
    public class ParameterData : SelectBase
    {
HMI.Core/Entity/ProductData.cs
File was renamed from HMI.ParameterManage/Entity/ProductData.cs
@@ -1,8 +1,6 @@

using HMI.Core.Entity;
using System.Xml.Serialization;
using System.Xml.Serialization;
namespace HMI.ParameterManage.Entity
namespace HMI.Core.Entity
{
    public class ProductData:SelectBase
    {
@@ -11,5 +9,6 @@
        public string Name { get; set; }
        public bool IsMaster { get; set; }
        public string ParentId { get; set; }
        public string dir;
    }
}
HMI.Core/Entity/TestItemData.cs
File was renamed from HMI.ParameterManage/Entity/TestItemData.cs
@@ -1,7 +1,4 @@
using HMI.Core.Entity;
using HMI.Util;
namespace HMI.ParameterManage.Entity
namespace HMI.Core.Entity
{
    public class TestItemData : SelectBase
    {
HMI.Core/HMI.Core.csproj
@@ -86,26 +86,27 @@
    <Reference Include="WindowsBase" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Annotation\ExcelColumn.cs" />
    <Compile Include="Api\IConfigManager.cs" />
    <Compile Include="Api\IConfigRefresh.cs" />
    <Compile Include="Api\IFolderOwner.cs" />
    <Compile Include="Api\ILogWriter.cs" />
    <Compile Include="Api\IPlugin.cs" />
    <Compile Include="Api\IPluginContainer.cs" />
    <Compile Include="Api\IProgram.cs" />
    <Compile Include="Api\ISequnce.cs" />
    <Compile Include="Api\IStep.cs" />
    <Compile Include="Api\IStepRunner.cs" />
    <Compile Include="Annotation\InitAttribute.cs" />
    <Compile Include="Api\PluginPackage.cs" />
    <Compile Include="Entity\LangInfo.cs" />
    <Compile Include="Entity\NotifyBase.cs" />
    <Compile Include="Entity\Param.cs" />
    <Compile Include="Entity\ParameterData.cs" />
    <Compile Include="Entity\ProductData.cs" />
    <Compile Include="Entity\SelectBase.cs" />
    <Compile Include="Entity\TestItemData.cs" />
    <Compile Include="Entity\UserInfo.cs" />
    <Compile Include="Event\TabChange.cs" />
    <Compile Include="Exceptions\FormValidateException.cs" />
    <Compile Include="FontSizeConverter.cs" />
    <Compile Include="LangConverter.cs" />
    <Compile Include="Manager\TestItemManager.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
    <Compile Include="Setting\SystemSetting.cs" />
    <Compile Include="Util\BeanUtil.cs" />
@@ -113,8 +114,11 @@
    <Compile Include="Util\HexUtil.cs" />
    <Compile Include="Util\JsonUtil.cs" />
    <Compile Include="Util\LangUtil.cs" />
    <Compile Include="Util\LoggerHolder.cs" />
    <Compile Include="Util\MSMQHelper.cs" />
    <Compile Include="Manager\ParameterManager.cs" />
    <Compile Include="Util\PathHelper.cs" />
    <Compile Include="Manager\ProductManager.cs" />
    <Compile Include="Util\SqliteHelper.cs" />
    <Compile Include="Util\UtilContainer.cs" />
    <Compile Include="Util\XmlHelper.cs" />
HMI.Core/Manager/ParameterManager.cs
New file
@@ -0,0 +1,44 @@
using HMI.Core.Entity;
using HMI.Core.Util;
using System;
using System.Collections.Generic;
using System.IO;
namespace HMI.Core.Manager
{
    /// <summary>
    /// TODO 优化数据源,需要支持sqlite和xml
    /// </summary>
    public class ParameterManager
    {
        /// <summary>
        /// 获取通用参数
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static T GetCommonParameter<T>() where T : IList<ParameterData>
        {
            string path = PathHelper.ParameterDataPath;
            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 = PathHelper.ParameterDataPath;
            XmlHelper.SerializeToXml(path, datas);
        }
    }
}
HMI.Core/Manager/ProductManager.cs
New file
@@ -0,0 +1,55 @@
using HMI.Core.Entity;
using HMI.Core.Util;
using System.Collections.Generic;
using System.IO;
namespace HMI.Core.Manager
{
    /// <summary>
    /// TODO 优化数据源,需要支持sqlite和xml
    /// </summary>
    public class ProductManager
    {
        /// <summary>
        /// 获取保存的产品
        /// </summary>
        /// <returns></returns>
        public static T GetProducts<T>() where T : IList<ProductData>
        {
            string path = PathHelper.ProductDataFile;
            if (!File.Exists(path)) return default(T);
            T datas = XmlHelper.DeserializeFromXml<T>(path);
            return datas;
        }
        /// <summary>
        /// 获取保存的产品
        /// </summary>
        /// <returns></returns>
        public static T GetParametersByProductName<T>(string productName) where T : IList<ParameterData>
        {
            string path = Path.Combine(PathHelper.ProductParameterFolder, $"Parameter_{productName}.data");
            if (!File.Exists(path)) return default(T);
            T datas = XmlHelper.DeserializeFromXml<T>(path);
            return datas;
        }
        public static List<ProductData> GetProducts()
        {
            List<ProductData> datas = GetProducts<List<ProductData>>();
            return datas ?? new List<ProductData>();
        }
        /// <summary>
        /// 根据产品名称获取产品参数
        /// </summary>
        /// <param name="productName"></param>
        /// <returns></returns>
        public static List<ParameterData> GetParametersByProductName(string productName)
        {
            List<ParameterData> datas = GetParametersByProductName<List<ParameterData>>(productName);
            return datas ?? new List<ParameterData>();
        }
    }
}
HMI.Core/Manager/TestItemManager.cs
File was renamed from HMI.ParameterManage/Util/TestItemManager.cs
@@ -1,11 +1,10 @@
using HMI.Core.Util;
using HMI.ParameterManage.Entity;
using HMI.Core.Entity;
using HMI.Core.Util;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
namespace HMI.ParameterManage.Util
namespace HMI.Core.Manager
{
    /// <summary>
    /// TODO 优化数据源,需要支持sqlite和xml
@@ -19,7 +18,7 @@
        /// <returns></returns>
        public static T GetTestItemByProduct<T>(string product) where T : IList<TestItemData>
        {
            string path = Path.Combine(TestItem.FileFolder, $"TestItem_{product}.setting");
            string path = Path.Combine(PathHelper.TestItemDataFolder, $"TestItem_{product}.setting");
            if (!File.Exists(path)) return default(T);
            T datas = XmlHelper.DeserializeFromXml<T>(path);
            return datas;
@@ -36,7 +35,7 @@
            {
                throw new Exception("不能为null");
            }
            string path = Path.Combine(TestItem.FileFolder, $"TestItem_{product}.setting");
            string path = Path.Combine(PathHelper.TestItemDataFolder, $"TestItem_{product}.setting");
            XmlHelper.SerializeToXml(path, datas);
        }
HMI.Core/Setting/SystemSetting.cs
@@ -30,6 +30,7 @@
        public string PlcIp { get; private set; }
        public bool PlcEnabled { get; private set; }
        public string CurrentLanguage { get; private set; } = "zh-CN";
        public int LogExpiredDays { get; private set; } = 7;
        private static void LoadFromXml()
        {
@@ -106,5 +107,12 @@
            get => currentLanguage;
            set { Set(ref currentLanguage, value); }
        }
        private int logExpiredDays;
        public int LogExpiredDays
        {
            get => logExpiredDays;
            set { Set(ref logExpiredDays, value); }
        }
    }
}
HMI.Core/Util/LoggerHolder.cs
New file
@@ -0,0 +1,37 @@
using HMI.Core.Api;
namespace HMI.Core.Util
{
    public class LoggerHelper
    {
        public static ILogWriter Logger { get; private set; }
        public static void Register(ILogWriter logger)
        {
            if (Logger == null)
            {
                Logger = logger;
            }
        }
        public static void Debug(string source, string content)
        {
            Logger?.Debug(source, content);
        }
        public static void Info(string source, string content)
        {
            Logger?.Info(source, content);
        }
        public static void Warn(string source, string content)
        {
            Logger?.Warn(source, content);
        }
        public static void Error(string source, string content)
        {
            Logger?.Error(source, content);
        }
    }
}
HMI.Core/Util/PathHelper.cs
@@ -5,13 +5,77 @@
{
    public class PathHelper
    {
        /// <summary>
        /// 程序目录
        /// </summary>
        public static string AppFolder { get; private set; } = RootPath();
        /// <summary>
        /// 图标目录
        /// </summary>
        public static string IconFolder { get; private set; }
        /// <summary>
        /// Config目录
        /// </summary>
        public static string ConfigFolder { get; private set; }
        /// <summary>
        /// Sqlite数据库目录
        /// </summary>
        public static string DatabaseFolder { get; private set; }
        /// <summary>
        /// Sqlite数据库Path
        /// </summary>
        public static string DatabasePath { get; private set; }
        /// <summary>
        /// 多语言目录
        /// </summary>
        public static string LangFolder { get; private set; }
        /// <summary>
        /// 系统设置目录
        /// </summary>
        public static string SettingFolder { get; private set; }
        /// <summary>
        /// 参数目录
        /// </summary>
        public static string ParameterFolder { get; private set; }
        /// <summary>
        /// 参数数据源文件
        /// </summary>
        public static string ParameterDataPath { get; private set; }
        /// <summary>
        /// 产品数据目录
        /// </summary>
        public static string ProductDataFolder { get; private set; }
        public static string ProductParameterFolder { get; private set; }
        /// <summary>
        /// 产品数据源文件
        /// </summary>
        public static string ProductDataFile { get; private set; }
        /// <summary>
        /// 测试项数据目录
        /// </summary>
        public static string TestItemDataFolder { get; private set; }
        /// <summary>
        /// 测试项数据源文件
        /// </summary>
        public static string TestItemDataFile { get; private set; }
        /// <summary>
        /// sqlite日志数据库文件夹
        /// </summary>
        public static string LogViewerDir { get; private set; }
        /// <summary>
        /// 应用程序的根目录
@@ -27,6 +91,17 @@
            DatabasePath = DatabaseFolder + "\\database.db";
            LangFolder = InitFolder(path + "\\Lang");
            SettingFolder = InitFolder(ConfigFolder + "\\SystemSetting");
            ParameterFolder = InitFolder(ConfigFolder + "\\Parameter");
            ParameterDataPath = ParameterFolder + "\\CommonParameter.data";
            ProductDataFolder = InitFolder(ConfigFolder + "\\Product");
            ProductParameterFolder = InitFolder(ProductDataFolder + "\\Parameter");
            ProductDataFile = ProductDataFolder + "\\Product.data";
            TestItemDataFolder = InitFolder(ProductDataFolder + "\\TestItem");
            TestItemDataFile = TestItemDataFolder + "\\TestItem.data";
            LogViewerDir = InitFolder(ConfigFolder + "\\LogViewer");
            return path;
        }
@@ -77,16 +152,6 @@
        public static string TestItemFolder()
        {
            string Path = ProductionFolder() + "\\TestItem";
            return Path;
        }
        /// <summary>
        /// 设置参数文件夹
        /// </summary>
        /// <returns>设置参数文件夹</returns>
        public static string ParameterFolder()
        {
            string Path = ProductionFolder() + "\\Parameter";
            return Path;
        }
HMI.IOMonitor/HMI.IOMonitor.csproj
@@ -68,9 +68,6 @@
    <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="OpcInterface">
      <HintPath>..\Libs\OpcInterface.dll</HintPath>
    </Reference>
    <Reference Include="Panuon.WPF, Version=1.1.2.2, Culture=neutral, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\Panuon.WPF.1.1.3\lib\net472\Panuon.WPF.dll</HintPath>
    </Reference>
@@ -142,6 +139,10 @@
    </None>
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\..\HMI.Old\OPCInterface\OpcInterface\OpcInterface.csproj">
      <Project>{e1464149-2c0a-499d-ab90-0c72e6ed1c53}</Project>
      <Name>OpcInterface</Name>
    </ProjectReference>
    <ProjectReference Include="..\HMI.Base\HMI.Base.csproj">
      <Project>{2a6f9720-a890-4036-9d73-41a0dae28341}</Project>
      <Name>HMI.Base</Name>
HMI.LogViewer/LogViewer.xaml
@@ -14,22 +14,7 @@
             Background="{StaticResource WorkspaceBackground}"
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonResource.xaml"/>
                <ResourceDictionary Source="/HMI.UI;component/Style/FormStyle.xaml"/>
                <ResourceDictionary Source="/HMI.UI;component/Style/Colors.xaml"/>
                <ResourceDictionary Source="/HMI.UI;component/Resources/Font/Font.xaml"/>
            </ResourceDictionary.MergedDictionaries>
            <uic:PathImageConverter x:Key="path2Image"/>
            <uic:DataGridRowNumberConverter x:Key="rowNumber"/>
        </ResourceDictionary>
    </UserControl.Resources>
    <Border Style="{StaticResource CardBoarder}">
        <Grid dp:GridHelper.Space="10" Cursor="">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
@@ -38,7 +23,7 @@
            </Grid.RowDefinitions>
            <TextBlock FontSize="{StaticResource TitleFontSize}" 
VerticalAlignment="Center" HorizontalAlignment="Left" Text="日志查询"/>
VerticalAlignment="Center" HorizontalAlignment="Left" Text="{DynamicResource log.title}"/>
            <DockPanel Grid.Row="1" Cursor="">
                <StackPanel DockPanel.Dock="Right" Style="{StaticResource ButtonGroup}">
@@ -51,42 +36,40 @@
                </StackPanel>
                <StackPanel Style="{StaticResource ButtonGroup}" HorizontalAlignment="Left">
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="级别"/>
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="{DynamicResource log.level}"/>
                    <ComboBox Style="{StaticResource FormComboxStyle}" x:Name="levelInput" 
                              Width="180"
                              SelectedValuePath="Content">
                        <ComboBoxItem Content="info"/>
                        <ComboBoxItem Content="warn"/>
                        <ComboBoxItem Content="error"/>
                        <ComboBoxItem Content="Debug"/>
                        <ComboBoxItem Content="Info"/>
                        <ComboBoxItem Content="Warn"/>
                        <ComboBoxItem Content="Error"/>
                    </ComboBox>
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="开始时间"/>
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="{DynamicResource App.StartTime}"/>
                    <pu:DateTimePicker Style="{StaticResource TimePicker}" x:Name="startTimeInput"/>
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="结束时间"/>
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="{DynamicResource App.EndTime}"/>
                    <pu:DateTimePicker Style="{StaticResource TimePicker}" x:Name="endTimeInput"/>
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="来源"/>
                    <!--<ComboBox Style="{StaticResource FormComboxStyle}" x:Name="sourceInput" Width="180"></ComboBox>-->
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="{DynamicResource log.source}"/>
                    <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="sourceInput" Width="180"/>
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="实时刷新"/>
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="{DynamicResource log.autoRefresh}"/>
                    <CheckBox x:Name="autoRfhInput" HorizontalAlignment="Center" VerticalAlignment="Center"
                              IsChecked="{Binding AutoRfh,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                              />
                              IsChecked="{Binding AutoRfh,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
                </StackPanel>
            </DockPanel>
            <DataGrid Grid.Row="2" x:Name="table" Style="{StaticResource DefaultDataGrid}">
                <DataGrid.RowStyle>
                    <Style TargetType="DataGridRow" BasedOn="{StaticResource {x:Static pu:StyleKeys.DataGridRowStyle}}">
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding type}" Value="warn">
                            <DataTrigger Binding="{Binding level}" Value="Warn">
                                <Setter Property="Background" Value="{StaticResource WarningColor}"/>
                                <Setter Property="Foreground" Value="Black"/>
                            </DataTrigger>
                            <DataTrigger Binding="{Binding type}" Value="error">
                            <DataTrigger Binding="{Binding level}" Value="Error">
                                <Setter Property="Background" Value="{StaticResource DangerColor}"/>
                                <Setter Property="Foreground" Value="Black"/>
                            </DataTrigger>
@@ -100,13 +83,6 @@
                    </Style>
                </DataGrid.RowStyle>
                <DataGrid.Columns>
                    <DataGridTemplateColumn Header="" Width="Auto" CanUserResize="False">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <CheckBox VerticalAlignment="Center" IsChecked="{Binding Selected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                    <DataGridTemplateColumn Header="#" CanUserResize="False">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
@@ -115,10 +91,26 @@
                            </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 level}">
                        <DataGridTextColumn.Header>
                            <TextBlock Text="{DynamicResource log.level}"/>
                        </DataGridTextColumn.Header>
                    </DataGridTextColumn>
                    <DataGridTextColumn Width="*" Binding="{Binding time}">
                        <DataGridTextColumn.Header>
                            <TextBlock Text="{DynamicResource log.time}"/>
                        </DataGridTextColumn.Header>
                    </DataGridTextColumn>
                    <DataGridTextColumn Width="*" Binding="{Binding source}">
                        <DataGridTextColumn.Header>
                            <TextBlock Text="{DynamicResource log.source}"/>
                        </DataGridTextColumn.Header>
                    </DataGridTextColumn>
                    <DataGridTextColumn Width="*" Binding="{Binding text}">
                        <DataGridTextColumn.Header>
                            <TextBlock Text="{DynamicResource log.text}"/>
                        </DataGridTextColumn.Header>
                    </DataGridTextColumn>
                </DataGrid.Columns>
            </DataGrid>
        </Grid>
HMI.LogViewer/LogViewer.xaml.cs
@@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
@@ -16,8 +17,6 @@
    /// </summary>
    public partial class LogViewer : PluginControl
    {
        private string logDir;
        private string todayLogFile;
        private bool autoRfh;
@@ -36,42 +35,34 @@
        public LogViewer()
        {
            string logDir = Path.Combine(PathHelper.AppFolder, "Log");
            todayLogFile = Path.Combine(logDir, $"log{DateTime.Now.ToString("yyyy-MM-dd")}.db");
            if (!Directory.Exists(logDir))
            {
                Directory.CreateDirectory(logDir);
            }
            this.logDir = logDir;
            DataContext = this;
            InitializeComponent();
        }
        public override void InitFinish()
        {
            base.InitFinish();
            string baseFile = Path.Combine(logDir, "log.db");
            //base.InitFinish();
            //string baseFile = Path.Combine(logDir, "log.db");
            try
            {
                if (!File.Exists(baseFile))
                {
                    SqliteHelper.CreateDatabase(baseFile);
                }
                SqliteHelper sqliteHelper = new SqliteHelper(baseFile);
                sqliteHelper.ExecuteSql(@"CREATE TABLE IF NOT EXISTS log (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    type TEXT NOT NULL,
                    text TEXT NOT NULL,
                    source TEXT,
                    time TIME
                    );");
            }
            catch (Exception ex)
            {
                log.Error("Create Log File Failed.", ex);
            }
            //try
            //{
            //    if (!File.Exists(baseFile))
            //    {
            //        SqliteHelper.CreateDatabase(baseFile);
            //    }
            //    SqliteHelper sqliteHelper = new SqliteHelper(baseFile);
            //    sqliteHelper.ExecuteSql(@"CREATE TABLE IF NOT EXISTS log (
            //        id INTEGER PRIMARY KEY AUTOINCREMENT,
            //        level TEXT NOT NULL,
            //        text TEXT NOT NULL,
            //        source TEXT,
            //        time TIME
            //        );");
            //}
            //catch (Exception ex)
            //{
            //    log.Error("Create Log File Failed.", ex);
            //}
        }
@@ -113,16 +104,32 @@
        {
            try
            {
                if (startTimeInput.SelectedDateTime == null && endTimeInput.SelectedDateTime == null)
                {
                    ToastHelper.Error(this.lang("log.timeCantBeNull"));
                    return;
                }
                if (endTimeInput.SelectedDateTime != null && startTimeInput.SelectedDateTime != null &&
                endTimeInput.SelectedDateTime < startTimeInput.SelectedDateTime)
                {
                    ToastHelper.Error(this.lang("log.timeRangeError"));
                    return;
                }
                queryBtn.IsEnabled = false;
                autoRfhInput.IsEnabled = false;
                Filtrate();
                queryBtn.IsEnabled = true;
                autoRfhInput.IsEnabled = true;
            }
            catch (Exception ex)
            {
                log.Error("Log Query Failed.", ex);
                this.Error($"查询失败!{ex.Message}");
            }
            finally
            {
                queryBtn.IsEnabled = true;
                autoRfhInput.IsEnabled = true;
            }
        }
@@ -139,16 +146,43 @@
        {
            string str = LoadSql();
            DataTable dt = new DataTable();
            var files = Directory.GetFiles(logDir, "*.db");
            var files = GetOldDbFiles(PathHelper.LogViewerDir, new DateTime?[] { startTimeInput.SelectedDateTime,
                endTimeInput.SelectedDateTime });
            foreach (string file in files)
            {
                SqliteHelper sql = new SqliteHelper(file);
                dt.Merge(sql.GetDataTable(str));
                sql.Close();
            }
            dt.DefaultView.Sort = "time Desc";
            table.ItemsSource = dt.DefaultView;
        }
        public static string[] GetOldDbFiles(string folderPath, DateTime?[] range)
        {
            if (!Directory.Exists(folderPath))
            {
                return new string[0];
            }
            // 获取所有.db文件并筛选出7天前更新的文件
            var oldFiles = Directory.GetFiles(folderPath, "*.db");
            try
            {
                oldFiles = oldFiles.Where(file =>
                {
                    var lastWriteTime = File.GetLastWriteTime(file);
                    bool flag = true;
                    if (range[0] != null) flag &= lastWriteTime >= range[0];
                    if (range[1] != null) flag &= lastWriteTime <= range[1];
                    return flag;
                }).ToArray();
                return oldFiles;
            }
            catch (Exception ex)
            {
                return new string[0];
            }
        }
        /// <summary>
@@ -157,12 +191,11 @@
        /// <returns> str数据库的语句 </returns>
        private string LoadSql()
        {
            string sql = " select type,time,source,text from log";
            string sql = " select level,time,source,text from log";
            List<string> datas = new List<string>();
            if (!string.IsNullOrEmpty(levelInput.Text))
            {
                datas.Add($"type='{levelInput.Text}'");
                datas.Add($"level='{levelInput.Text}'");
            }
            if (!string.IsNullOrEmpty(startTimeInput.Text))
            {
@@ -189,22 +222,22 @@
        /// </summary>
        private void Refresh100items()
        {
            try
            {
                if (!File.Exists(todayLogFile)) return;
                string str = "select type,time,source,text from log order by time desc limit 100";
                SqliteHelper sql = new SqliteHelper(todayLogFile);
                var dt = sql.GetDataTable(str);
                this.Dispatcher.BeginInvoke((Action)delegate ()
                {
                    table.ItemsSource = dt.DefaultView;
                });
            }
            catch (Exception ex)
            {
                log.Error("Log Auto Refresh Failed.", ex);
                this.Error($"自动刷新失败!{ex.Message}");
            }
            //try
            //{
            //    if (!File.Exists(todayLogFile)) return;
            //    string str = "select type,time,source,text from log order by time desc limit 100";
            //    SqliteHelper sql = new SqliteHelper(todayLogFile);
            //    var dt = sql.GetDataTable(str);
            //    this.Dispatcher.BeginInvoke((Action)delegate ()
            //    {
            //        table.ItemsSource = dt.DefaultView;
            //    });
            //}
            //catch (Exception ex)
            //{
            //    log.Error("Log Auto Refresh Failed.", ex);
            //    this.Error($"自动刷新失败!{ex.Message}");
            //}
        }
    }
}
HMI.MenuManage/Entity/MenuData.cs
@@ -22,6 +22,8 @@
        public string DllPath { get; set; }
        public string ClassName { get; set; }
        public double? Sort { get; set; }
        private ObservableCollection<MenuData> _Children = new ObservableCollection<MenuData>();
        public ObservableCollection<MenuData> Children
HMI.MenuManage/MenuManage.xaml
@@ -17,8 +17,8 @@
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="7*"/>
            <ColumnDefinition Width="3*"/>
            <ColumnDefinition Width="8*"/>
            <ColumnDefinition Width="2*"/>
        </Grid.ColumnDefinitions>
        <Border Style="{StaticResource CardBoarder}" Margin="0,0,2,0">
@@ -91,6 +91,11 @@
                                <TextBlock Text="{DynamicResource HMI.Menu.ClassName}"/>
                            </DataGridTextColumn.Header>
                        </DataGridTextColumn>
                        <DataGridTextColumn Width="50" Binding="{Binding Sort}">
                            <DataGridTextColumn.Header>
                                <TextBlock Text="{DynamicResource HMI.Menu.Sort}"/>
                            </DataGridTextColumn.Header>
                        </DataGridTextColumn>
                        <DataGridTemplateColumn Width="110" IsReadOnly="True" CanUserResize="False">
                            <DataGridTemplateColumn.Header>
                                <TextBlock Text="{DynamicResource App.Opt}"/>
HMI.MenuManage/Modal/MenuModal.xaml
@@ -30,7 +30,7 @@
dp:FormGroupHelper.ValidateMethod="ValidateName">
                <TextBox x:Name="TbName" Style="{StaticResource EditorTextBoxStyle}" />
            </pu:FormGroup>
            <pu:FormGroup x:Name="FgIcon" Style="{StaticResource InputFormGroupStyle}"
                          Header="{DynamicResource HMI.Menu.Icon}" GroupName="MyFormGroup">
                <Grid>
@@ -96,6 +96,11 @@
                          Header="{DynamicResource HMI.Menu.ClassName}" GroupName="MyFormGroup">
                <TextBox x:Name="TbClassName" Style="{StaticResource EditorTextBoxStyle}" />
            </pu:FormGroup>
            <pu:FormGroup x:Name="FgSort" Style="{StaticResource InputFormGroupStyle}"
              Header="{DynamicResource HMI.Menu.Sort}" GroupName="MyFormGroup">
                <pu:NumberInput x:Name="NiSort" Style="{StaticResource FormNumberInput}" />
            </pu:FormGroup>
        </StackPanel>
    </Grid>
</pu:WindowX>
HMI.MenuManage/Modal/MenuModal.xaml.cs
@@ -39,7 +39,6 @@
            set { Set(ref parentSource, value); }
        }
        protected override void OnClosing(CancelEventArgs e)
        {
            //Press Continue Button
@@ -118,6 +117,7 @@
                Data.ParentCode = TbParentCode.SelectedValue?.ToString();
                Data.DllPath = TbDllPath.Text;
                Data.ClassName = TbClassName.Text;
                Data.Sort = NiSort.Value;
            }
            return flag;
        }
@@ -154,6 +154,7 @@
            TbParentCode.SelectedValue = Data.ParentCode;
            TbDllPath.Text = Data.DllPath;
            TbClassName.Text = Data.ClassName;
            NiSort.Value = Data.Sort;
            return this.ShowDialog();
        }
@@ -171,10 +172,10 @@
        public void InsertMenu(MenuData data)
        {
            string sql = $@"INSERT INTO menu (code,name,icon,level,type,parentCode,dllPath,className)
            string sql = $@"INSERT INTO menu (code,name,icon,level,type,parentCode,dllPath,className,sort)
                VALUES
                ('{data.Code}','{data.Name}','{data.Icon}','{data.Level}','{data.Type}','{data.ParentCode}',
                '{data.DllPath}','{data.ClassName}');";
                '{data.DllPath}','{data.ClassName}',{data.Sort});";
            sqliteHelper.ExecuteSql(sql);
        }
@@ -182,7 +183,7 @@
        {
            string sql = $@"UPDATE menu SET code='{data.Code}',name='{data.Name}',
icon='{data.Icon}',level='{data.Level}',type='{data.Type}',parentCode='{data.ParentCode}',
dllPath='{data.DllPath}',className='{data.ClassName}' WHERE id='{data.Id}';";
dllPath='{data.DllPath}',className='{data.ClassName}',sort={data.Sort} WHERE id='{data.Id}';";
            sqliteHelper.ExecuteSql(sql);
        }
HMI.MenuManage/Model/MenuManageModel.cs
@@ -55,6 +55,7 @@
                    parentCode TEXT,
                    dllPath TEXT,
                    className TEXT,
                    sort REAL,
                    UNIQUE(code)
                    );");
        }
@@ -63,7 +64,7 @@
        {
            ObservableCollection<MenuData> data = new ObservableCollection<MenuData>();
            ObservableCollection<MenuData> treeData = new ObservableCollection<MenuData>();
            DataTable dt = sqliteHelper.GetDataTable("SELECT m1.*,m2.name AS parentName FROM menu AS m1 LEFT JOIN menu AS m2 ON m1.parentCode = m2.code Order By m1.id;");
            DataTable dt = sqliteHelper.GetDataTable("SELECT m1.*,m2.name AS parentName FROM menu AS m1 LEFT JOIN menu AS m2 ON m1.parentCode = m2.code Order By m1.sort,m1.id;");
            Dictionary<string, MenuData> cache = new Dictionary<string, MenuData>();
            if (dt != null)
            {
@@ -80,7 +81,8 @@
                        ParentCode = dr["parentCode"].ToString(),
                        ParentName = dr["parentName"].ToString(),
                        DllPath = dr["dllPath"].ToString(),
                        ClassName = dr["className"].ToString()
                        ClassName = dr["className"].ToString(),
                        Sort = dr["sort"] == DBNull.Value ? 0 : double.Parse(dr["sort"].ToString()),
                    };
                    data.Add(menu);
                    if (menu.Level == "1")
HMI.ParameterManage/HMI.ParameterManage.csproj
@@ -78,7 +78,6 @@
    <Reference Include="PresentationFramework" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Entity\TestItemData.cs" />
    <Compile Include="TestItemModal.xaml.cs">
      <DependentUpon>TestItemModal.xaml</DependentUpon>
    </Compile>
@@ -98,8 +97,6 @@
      <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>
@@ -113,8 +110,6 @@
      <SubType>Designer</SubType>
    </Page>
    <Compile Include="Model\SqlModel.cs" />
    <Compile Include="Entity\ParameterData.cs" />
    <Compile Include="Entity\ProductData.cs" />
    <Compile Include="ParameterManage.xaml.cs">
      <DependentUpon>ParameterManage.xaml</DependentUpon>
      <SubType>Code</SubType>
@@ -178,6 +173,10 @@
      <Name>HMI.Util</Name>
    </ProjectReference>
  </ItemGroup>
  <ItemGroup>
    <Folder Include="Entity\" />
    <Folder Include="Util\" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <PropertyGroup>
    <PostBuildEvent>
HMI.ParameterManage/Model/SqlModel.cs
@@ -1,9 +1,8 @@
using System.Collections.ObjectModel;
using HMI.Core.Entity;
using HMI.Core.Util;
using System.Collections.ObjectModel;
using System.Data;
using System.IO;
using HMI.Core.Entity;
using HMI.Core.Util;
using HMI.ParameterManage.Entity;
namespace HMI.ParameterManage.Model
{
HMI.ParameterManage/Model/XmlModel.cs
@@ -1,6 +1,7 @@
using HMI.Core.Entity;
using HMI.Core.Manager;
using HMI.Core.Util;
using HMI.ParameterManage.Entity;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
@@ -9,17 +10,8 @@
{
    public class XmlModel : NotifyBase
    {
        private string ProductSettingFile;
        private string ParameterSettingFolder;
        public XmlModel(string baseFolder)
        {
            ProductSettingFile = Path.Combine(baseFolder, "Product.setting");
            ParameterSettingFolder = Path.Combine(baseFolder, "Parameter");
            if (!Directory.Exists(ParameterSettingFolder))
            {
                Directory.CreateDirectory(ParameterSettingFolder);
            }
        }
        private ObservableCollection<ProductData> products;
@@ -91,30 +83,20 @@
        public void LoadProducts()
        {
            ObservableCollection<ProductData> products;
            if (File.Exists(ProductSettingFile))
            {
                products = XmlHelper.DeserializeFromXml<ObservableCollection<ProductData>>(ProductSettingFile);
            }
            else
            {
                products = new ObservableCollection<ProductData>();
            }
            ObservableCollection<ProductData> products = ProductManager.GetProducts<ObservableCollection<ProductData>>();
            products = products ?? new ObservableCollection<ProductData>();
            Products = products;
        }
        public void LoadParameters()
        {
            ObservableCollection<ParameterData> parameters = new ObservableCollection<ParameterData>();
            ObservableCollection<ParameterData> parameters = null;
            if (SelectedProduct != null)
            {
                string fileName = $"Parameter_{SelectedProduct.Name}.setting";
                string parameterSettingFile = Path.Combine(ParameterSettingFolder, fileName);
                if (File.Exists(parameterSettingFile))
                {
                    parameters = XmlHelper.DeserializeFromXml<ObservableCollection<ParameterData>>(parameterSettingFile);
                }
                parameters = ProductManager.GetParametersByProductName<ObservableCollection<ParameterData>>(SelectedProduct.Name);
            }
            parameters = parameters ?? new ObservableCollection<ParameterData>();
            Parameters = parameters;
        }
@@ -123,11 +105,11 @@
            int index = Products.IndexOf(data);
            if (index > -1)
            {
                UpdateData(index, data);
                UpdateProduct(index, data);
            }
            else
            {
                InsertData(data);
                InsertProduct(data);
            }
        }
@@ -144,67 +126,119 @@
            }
        }
        public void InsertData(ProductData data)
        #region 产品操作
        /// <summary>
        /// 新增产品
        /// </summary>
        /// <param name="data"></param>
        public void InsertProduct(ProductData data)
        {
            var temp = XmlHelper.DeepClone(Products);
            temp.Add(data);
            XmlHelper.SerializeToXml(ProductSettingFile, temp);
            SaveProduct(temp);
        }
        public void UpdateData(int index, ProductData data)
        /// <summary>
        /// 更新产品
        /// </summary>
        /// <param name="index"></param>
        /// <param name="data"></param>
        public void UpdateProduct(int index, ProductData data)
        {
            var temp = XmlHelper.DeepClone(Products);
            temp[index] = data;
            XmlHelper.SerializeToXml(ProductSettingFile, temp);
            SaveProduct(temp);
        }
        public void InsertParameter(ParameterData data)
        {
            var temp = XmlHelper.DeepClone(Parameters);
            temp.Add(data);
            string fileName = Path.Combine(ParameterSettingFolder, $"Parameter_{SelectedProduct.Name}.setting");
            XmlHelper.SerializeToXml(fileName, temp);
        }
        public void UpdateParameter(int index, ParameterData data)
        {
            var temp = XmlHelper.DeepClone(Parameters);
            temp[index] = data;
            string fileName = Path.Combine(ParameterSettingFolder, $"Parameter_{SelectedProduct.Name}.setting");
            XmlHelper.SerializeToXml(fileName, temp);
        }
        /// <summary>
        /// 删除产品
        /// </summary>
        /// <param name="data"></param>
        public void DeleteProduct(ProductData data)
        {
            int index = Products.IndexOf(data);
            if (index > -1)
            {
                var temp = Products.Where(p => p != data).ToList();
                XmlHelper.SerializeToXml(ProductSettingFile, temp);
                SaveProduct(temp);
            }
        }
        /// <summary>
        /// 批量删除产品
        /// </summary>
        public void DeleteProducts()
        {
            var temp = Products.Where(p => !p.IsSelected).ToList();
            XmlHelper.SerializeToXml(ProductSettingFile, temp);
            SaveProduct(temp);
        }
        /// <summary>
        /// 持久化产品数据
        /// </summary>
        /// <param name="datas"></param>
        private void SaveProduct(IList<ProductData> datas)
        {
            XmlHelper.SerializeToXml(PathHelper.ProductDataFile, datas);
        }
        #endregion
        #region 产品参数操作
        /// <summary>
        /// 新增参数
        /// </summary>
        /// <param name="data"></param>
        public void InsertParameter(ParameterData data)
        {
            var temp = XmlHelper.DeepClone(Parameters);
            temp.Add(data);
            SaveParameter(temp);
        }
        /// <summary>
        /// 更新参数
        /// </summary>
        /// <param name="index"></param>
        /// <param name="data"></param>
        public void UpdateParameter(int index, ParameterData data)
        {
            var temp = XmlHelper.DeepClone(Parameters);
            temp[index] = data;
            SaveParameter(temp);
        }
        /// <summary>
        /// 删除参数
        /// </summary>
        /// <param name="data"></param>
        public void DeleteParameter(ParameterData data)
        {
            int index = Parameters.IndexOf(data);
            if (index > -1)
            {
                var temp = Parameters.Where(p => p != data).ToList();
                string fileName = Path.Combine(ParameterSettingFolder, $"Parameter_{SelectedProduct.Name}.setting");
                XmlHelper.SerializeToXml(fileName, temp);
                SaveParameter(temp);
            }
        }
        /// <summary>
        /// 批量删除参数
        /// </summary>
        public void DeleteParameters()
        {
            var temp = Parameters.Where(p => !p.IsSelected).ToList();
            string fileName = Path.Combine(ParameterSettingFolder, $"Parameter_{SelectedProduct.Name}.setting");
            XmlHelper.SerializeToXml(fileName, temp);
            SaveParameter(temp);
        }
        /// <summary>
        /// 持久化参数数据
        /// </summary>
        /// <param name="datas"></param>
        private void SaveParameter(IList<ParameterData> datas)
        {
            string fileName = Path.Combine(PathHelper.ProductDataFolder, "Parameter", $"Parameter_{SelectedProduct.Name}.data");
            XmlHelper.SerializeToXml(fileName, datas);
        }
        #endregion
    }
}
HMI.ParameterManage/ParameterManage.xaml.cs
@@ -1,6 +1,6 @@
using HMI.Base;
using HMI.Core.Entity;
using HMI.Core.Util;
using HMI.ParameterManage.Entity;
using HMI.ParameterManage.Model;
using HMI.UI.Util;
using System.Linq;
@@ -14,7 +14,6 @@
    /// </summary>
    public partial class ParameterManage : PluginControl
    {
        public static string FileFolder { get; private set; }
        private XmlModel Model;
        public ParameterManage()
@@ -25,7 +24,6 @@
        public override void InitFinish()
        {
            Model = new XmlModel(BaseFolder);
            FileFolder = BaseFolder;
            DataContext = Model;
        }
HMI.ParameterManage/ParameterModal.xaml.cs
@@ -1,12 +1,10 @@
using HMI.Core.Util;
using HMI.ParameterManage.Entity;
using HMI.Core.Entity;
using HMI.ParameterManage.Model;
using HMI.UI.DpUtil;
using HMI.UI.Util;
using Panuon.WPF.UI;
using System;
using System.ComponentModel;
using System.Windows.Interop;
namespace HMI.ParameterManage
{
HMI.ParameterManage/ParameterSetting.xaml
@@ -10,8 +10,7 @@
             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"
             mc:Ignorable="d"
             Padding="10"
             mc:Ignorable="d"
             Loaded="UserControl_Loaded"
             Background="{StaticResource WorkspaceBackground}"
             d:DesignHeight="450" d:DesignWidth="800">
@@ -24,10 +23,7 @@
                <RowDefinition/>
            </Grid.RowDefinitions>
            <TextBlock FontSize="{StaticResource TitleFontSize}"
                       VerticalAlignment="Center" HorizontalAlignment="Left" Text="{DynamicResource Pm.Setting.Title}"/>
            <DockPanel Grid.Row="1" Cursor="">
            <DockPanel Grid.Row="1">
                <StackPanel DockPanel.Dock="Right" Style="{StaticResource ButtonGroup}">
                    <Button Style="{StaticResource AddBtn}"
                            x:Name="addBtn" Click="Button_Click"/>
@@ -40,17 +36,18 @@
                    <Button x:Name="importBtn" 
                            Style="{StaticResource ImportBtn}" Click="Button_Click"/>
                </StackPanel>
                <TextBlock FontSize="{StaticResource TitleFontSize}"
           VerticalAlignment="Center" HorizontalAlignment="Left" Text="{DynamicResource Pm.Setting.Title}"/>
                <StackPanel Style="{StaticResource ButtonGroup}" HorizontalAlignment="Left">
                <!--<StackPanel Style="{StaticResource ButtonGroup}" HorizontalAlignment="Left">
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="{DynamicResource HMI.Line}"/>
                    <ComboBox Style="{StaticResource FormComboxStyle}" Width="160" x:Name="CbLine"/>
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="{DynamicResource HMI.Station}"/>
                    <ComboBox Style="{StaticResource FormComboxStyle}" Width="160" x:Name="CbStation"/>
                </StackPanel>
                </StackPanel>-->
            </DockPanel>
            <DataGrid Grid.Row="2" x:Name="table" Style="{StaticResource DefaultDataGrid}"
                      ItemsSource="{Binding Datasource}">
HMI.ParameterManage/ParameterSetting.xaml.cs
@@ -1,7 +1,7 @@
using HMI.Base;
using HMI.Core.Entity;
using HMI.Core.Manager;
using HMI.Core.Util;
using HMI.ParameterManage.Entity;
using HMI.ParameterManage.Util;
using HMI.UI.Util;
using HMI.Util;
using Microsoft.Win32;
@@ -11,7 +11,6 @@
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
namespace HMI.ParameterManage
{
HMI.ParameterManage/ProductModal.xaml.cs
@@ -1,21 +1,10 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Windows;
using Microsoft.Win32;
using Panuon.WPF.UI;
using HMI.Core.Annotation;
using HMI.Core.Entity;
using HMI.Core.Exceptions;
using HMI.Core.Util;
using HMI.ParameterManage.Entity;
using HMI.UI;
using HMI.Core.Entity;
using HMI.ParameterManage.Model;
using HMI.UI.DpUtil;
using HMI.UI.Util;
using Panuon.WPF.UI;
using System;
using System.ComponentModel;
namespace HMI.ParameterManage
{
HMI.ParameterManage/SettingModal.xaml
@@ -58,23 +58,13 @@
            <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>
                <TextBox x:Name="TbGroup" Style="{StaticResource EditorTextBoxStyle}"/>
            </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>
                <TextBox x:Name="TbSource" Style="{StaticResource EditorTextBoxStyle}"/>
            </pu:FormGroup>
        </StackPanel>
    </Grid>
HMI.ParameterManage/SettingModal.xaml.cs
@@ -1,5 +1,5 @@
using HMI.Core.Util;
using HMI.ParameterManage.Entity;
using HMI.Core.Entity;
using HMI.Core.Util;
using HMI.UI.DpUtil;
using HMI.UI.Util;
using Panuon.WPF.UI;
@@ -90,8 +90,8 @@
                Data.Value = TbValue.Text;
                Data.Unit = TbUnit.Text;
                Data.Description = TbDescription.Text;
                Data.PGroup = CbGroup.Text;
                Data.Source = CbSource.Text;
                Data.PGroup = TbGroup.Text;
                Data.Source = TbSource.Text;
            }
            return flag;
        }
@@ -105,8 +105,8 @@
            TbValue.Text = Data.Value;
            TbUnit.Text = Data.Unit;
            TbDescription.Text = Data.Description;
            CbGroup.SelectedValue = Data.PGroup;
            CbSource.SelectedValue = Data.Source;
            TbGroup.Text = Data.PGroup;
            TbSource.Text = Data.Source;
            return this.ShowDialog();
        }
    }
HMI.ParameterManage/TestItem.xaml.cs
@@ -1,14 +1,13 @@
using HMI.Base;
using HMI.Core.Entity;
using HMI.Core.Manager;
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;
@@ -54,14 +53,7 @@
        public static string FileFolder { get; private set; }
        public override void Active()
        {
            if (!isActivated)
            {
                string folder = Path.Combine(ParameterManage.FileFolder, "TestItem");
                PathHelper.InitFolder(folder);
                base.SetBaseFolder(folder);
                FileFolder = folder;
                isActivated = true;
            }
        }
        private void Button_Click(object sender, RoutedEventArgs e)
@@ -305,7 +297,7 @@
            try
            {
                ObservableCollection<ProductData> products =
                ParameterManager.GetProducts<ObservableCollection<ProductData>>()
                ProductManager.GetProducts<ObservableCollection<ProductData>>()
                ?? new ObservableCollection<ProductData>();
                Products = products;
            }
HMI.ParameterManage/TestItemModal.xaml.cs
@@ -1,5 +1,5 @@
using HMI.Core.Util;
using HMI.ParameterManage.Entity;
using HMI.Core.Entity;
using HMI.Core.Util;
using HMI.UI.DpUtil;
using HMI.UI.Util;
using Panuon.WPF.UI;
HMI.ParameterManage/Util/ParameterManager.cs
File was deleted
HMI.SettingControl/SystemConfig.xaml
@@ -57,6 +57,11 @@
                    <ComboBox x:Name="CbLang" Style="{StaticResource FormComboxStyle}" 
                              SelectedValue="{Binding CurrentLanguage}" SelectedValuePath="Code" DisplayMemberPath="Name"/>
                </pu:FormGroup>
                <pu:FormGroup Header="{DynamicResource Setting.LogExpiredDays}" HeaderWidth="Auto" GroupName="MyFormGroup">
                    <pu:NumberInput Interval="1" x:Name="NiLogExpiredDays" Style="{StaticResource FormNumberInput}"
                                    Value="{Binding LogExpiredDays}"/>
                </pu:FormGroup>
            </StackPanel>
        </GroupBox>
HMI.UI/Controls/KeyboradWindow.xaml
New file
@@ -0,0 +1,234 @@
<pu:WindowX x:Class="HMI.UI.KeyboardWindow"
            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.UI"
            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"
            mc:Ignorable="d"
            Title="NumberPanel"
            SizeToContent="Height"
            FontSize="14"
            FontWeight="Bold"
            WindowStartupLocation="CenterOwner"
            pu:WindowXModalDialog.Buttons="{x:Null}"
            Style="{StaticResource ModalWindow}">
    <Window.Resources>
        <Style TargetType="Button" BasedOn="{StaticResource PrimaryButton}"/>
    </Window.Resources>
    <Grid Margin="10" Height="600">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <!-- 输入框 -->
        <TextBox x:Name="inputTextBox"
                 Style="{StaticResource EditorTextBoxStyle}"
                 Grid.Row="0"
                 Margin="0,0,0,10"
                 VerticalScrollBarVisibility="Auto"
                 FontSize="16"/>
        <!-- 键盘布局 -->
        <Grid Grid.Row="1">
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <Grid Grid.Row="0">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Button Grid.Column="0" Content="(" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="1" Content=")" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="2" Content="-" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="3" Content="+" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="4" Content="{}{" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="5" Content="}" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="6" Content="|" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="7" Content=":" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="8" Content="&quot;" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="9" Content="~" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="10" Content="@" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="11" Content="#" Click="KeyButton_Click" Margin="1"/>
            </Grid>
            <!-- 第一行:数字键和特殊字符 -->
            <Grid Grid.Row="1">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Button Grid.Column="0" Content="`" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="1" Content="1" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="2" Content="2" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="3" Content="3" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="4" Content="4" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="5" Content="5" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="6" Content="6" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="7" Content="7" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="8" Content="8" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="9" Content="9" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="10" Content="0" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="11" Content="Backspace" Click="BackspaceButton_Click" Grid.ColumnSpan="2" Margin="1"/>
            </Grid>
            <!-- 第二行:字母键 -->
            <Grid Grid.Row="2">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Button Grid.Column="0" Content="Q" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="1" Content="W" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="2" Content="E" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="3" Content="R" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="4" Content="T" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="5" Content="Y" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="6" Content="U" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="7" Content="I" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="8" Content="O" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="9" Content="P" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="10" Content="[" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="11" Content="]" Click="KeyButton_Click" Margin="1"/>
            </Grid>
            <!-- 第三行:字母键 -->
            <Grid Grid.Row="3">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Button Grid.Column="0" Content="A" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="1" Content="S" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="2" Content="D" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="3" Content="F" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="4" Content="G" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="5" Content="H" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="6" Content="J" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="7" Content="K" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="8" Content="L" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="9" Content=";" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="10" Content="'" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="11" Content="Enter" Click="EnterButton_Click" Grid.ColumnSpan="2" Margin="1"/>
            </Grid>
            <!-- 第四行:字母键 -->
            <Grid Grid.Row="4">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Button Grid.Column="0" Content="Z" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="1" Content="X" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="2" Content="C" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="3" Content="V" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="4" Content="B" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="5" Content="N" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="6" Content="M" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="7" Content="," Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="8" Content="." Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="9" Content="/" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="10" Content="=" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="11" Content="\" Click="KeyButton_Click" Margin="1"/>
            </Grid>
            <!-- 第五行:功能键和特殊字符 -->
            <Grid Grid.Row="5">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Button Grid.Column="0" Content="Caps" Click="CapsButton_Click" Grid.ColumnSpan="2" Margin="1">
                    <Button.Style>
                        <Style TargetType="Button" BasedOn="{StaticResource PrimaryButton}">
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding IsCapsLockOn}" Value="True">
                                    <Setter Property="Background" Value="{StaticResource GreenVerBar}"/>
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </Button.Style>
                </Button>
                <Button Grid.Column="2" Content="Space" Click="SpaceButton_Click" Grid.ColumnSpan="4" Margin="1"/>
                <Button Grid.Column="6" Content="&amp;" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="7" Content="*" Click="KeyButton_Click" Margin="1"/>
                <Button Grid.Column="8" Content="Clear" Click="ClearButton_Click" Grid.ColumnSpan="2" Margin="1"/>
                <Button Grid.Column="10" Content="Cancel" Style="{StaticResource DangerButton}" Click="Cancel_Click" Margin="1"/>
                <Button Grid.Column="11" Content="OK" Style="{StaticResource SuccessButton}" Click="Ok_Click" Margin="1"/>
            </Grid>
        </Grid>
    </Grid>
</pu:WindowX>
HMI.UI/Controls/KeyboradWindow.xaml.cs
New file
@@ -0,0 +1,110 @@
using Panuon.WPF.UI;
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace HMI.UI
{
    public partial class KeyboardWindow : WindowX
    {
        private bool _isCapsLockOn = false;
        public bool IsCapsLockOn
        {
            get => _isCapsLockOn;
            set
            {
                Set(ref _isCapsLockOn, value);
            }
        }
        public KeyboardWindow()
        {
            DataContext = this;
            InitializeComponent();
        }
        public bool? Open(string data)
        {
            Data = data;
            inputTextBox.Text = data;
            return this.ShowDialog();
        }
        public string Data { get; private set; }
        private void KeyButton_Click(object sender, RoutedEventArgs e)
        {
            if (sender is Button button)
            {
                string key = button.Content.ToString();
                if (_isCapsLockOn && IsLetter(key))
                {
                    key = key.ToUpper();
                }
                else
                {
                    key = key.ToLower();
                }
                InsertText(key);
            }
        }
        private bool IsLetter(string key)
        {
            return key.Length == 1 && char.IsLetter(key[0]);
        }
        private void InsertText(string text)
        {
            inputTextBox.Text += text;
            inputTextBox.Focus();
        }
        private void BackspaceButton_Click(object sender, RoutedEventArgs e)
        {
            if (inputTextBox.Text.Length > 0)
            {
                inputTextBox.Text = inputTextBox.Text.Substring(0, inputTextBox.Text.Length - 1);
            }
            inputTextBox.Focus();
        }
        private void ClearButton_Click(object sender, RoutedEventArgs e)
        {
            inputTextBox.Clear();
            inputTextBox.Focus();
        }
        private void EnterButton_Click(object sender, RoutedEventArgs e)
        {
            //InsertText(Environment.NewLine);
        }
        private void SpaceButton_Click(object sender, RoutedEventArgs e)
        {
            InsertText(" ");
        }
        private void CapsButton_Click(object sender, RoutedEventArgs e)
        {
            IsCapsLockOn = !IsCapsLockOn;
        }
        private void Cancel_Click(object sender, RoutedEventArgs e)
        {
            Data = string.Empty;
            DialogResult = false;
            this.Close();
        }
        private void Ok_Click(object sender, RoutedEventArgs e)
        {
            Data = inputTextBox.Text;
            DialogResult = true;
            this.Close();
        }
    }
}
HMI.UI/Controls/NumberInput.cs
HMI.UI/Controls/NumberPanel.xaml
File was renamed from Plugin.ServoManage/NumberPanel.xaml
@@ -1,16 +1,16 @@
<pu:WindowX x:Class="Plugin.ServoManage.NumberPanel"
<pu:WindowX x:Class="HMI.UI.NumberPanel"
            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.ServoManage"
            xmlns:local="clr-namespace:HMI.UI"
            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"
            xmlns:dp="clr-namespace:HMI.UI.DpUtil"
            mc:Ignorable="d"
            Title="Form"
            Title="NumberPanel"
            SizeToContent="Height"
            Width="500"
            FontSize="14"
@@ -40,7 +40,7 @@
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <ui:NumberInput Grid.ColumnSpan="4" BorderThickness="1"
        <local:NumberInput Grid.ColumnSpan="4" BorderThickness="1"
                        BorderBrush="Black"
                        Style="{StaticResource EditorTextBoxStyle}"
                        x:Name="NiNumber"/>
HMI.UI/Controls/NumberPanel.xaml.cs
File was renamed from Plugin.ServoManage/NumberPanel.xaml.cs
@@ -1,16 +1,9 @@
using HMI.Core.Util;
using HMI.UI.DpUtil;
using HMI.UI.Util;
using Panuon.WPF.UI;
using Plugin.ServoManage.Entity;
using Panuon.WPF.UI;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Windows.Controls;
namespace Plugin.ServoManage
namespace HMI.UI
{
    /// <summary>
    /// ModalWindow.xaml 的交互逻辑
HMI.UI/HMI.UI.csproj
@@ -80,11 +80,17 @@
    <Reference Include="PresentationFramework" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Controls\KeyboradWindow.xaml.cs">
      <DependentUpon>KeyboradWindow.xaml</DependentUpon>
    </Compile>
    <Compile Include="Controls\NumberPanel.xaml.cs">
      <DependentUpon>NumberPanel.xaml</DependentUpon>
    </Compile>
    <Compile Include="Converter\VisibilityBoolConverter.cs" />
    <Compile Include="Converter\DataGridRowFlagConverter.cs" />
    <Compile Include="Converter\DataGridRowNumberConverter.cs" />
    <Compile Include="Converter\PathImageConverter.cs" />
    <Compile Include="Custom\NumberInput.cs" />
    <Compile Include="Controls\NumberInput.cs" />
    <Compile Include="DpUtil\DataGridHelper.cs" />
    <Compile Include="DpUtil\DataGridTextColumnHelper.cs" />
    <Compile Include="DpUtil\FormGroupHelper.cs" />
@@ -108,6 +114,14 @@
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
    </Resource>
    <Page Include="Controls\KeyboradWindow.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
    </Page>
    <Page Include="Controls\NumberPanel.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
    </Page>
    <Page Include="MaskWindow.xaml">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
HMI.UI/Style/FormStyle.xaml
@@ -67,7 +67,7 @@
        <Setter Property="pu:TextBoxHelper.FocusedWatermarkForeground" Value="#AAAAAA" />
        <Setter Property="Height" Value="35" />
        <Setter Property="Padding" Value="5,0" />
        <Setter Property="Background" Value="Transparent" />
        <Setter Property="Background" Value="White" />
        <Setter Property="Foreground" Value="Black" />
        <Setter Property="VerticalContentAlignment" Value="Center"/>
        <Style.Triggers>
@@ -109,14 +109,24 @@
            </Style>
        </Style.Resources>
        <Setter Property="HeaderTemplate">
            <Setter.Value>
                <DataTemplate>
                    <Border Margin="5,0,5,0">
                        <ContentControl Content="{Binding}" />
                    </Border>
                </DataTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="HeaderWidth" Value="Auto" />
        <Setter Property="Orientation" Value="Horizontal" />
        <Setter Property="Margin" Value="0,10,0,0" />
    </Style>
    <DataTemplate x:Key="RequiredFormHeader">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="*" Foreground="Red" />
        <StackPanel Orientation="Horizontal" Margin="0,0,5,0">
            <TextBlock Text="*" Width="5" Foreground="Red" />
            <ContentControl Content="{Binding}" />
        </StackPanel>
    </DataTemplate>
HMI.Util/ExcelHelper.cs
@@ -1,4 +1,6 @@
using NPOI.HSSF.UserModel;
using HMI.Core;
using HMI.Core.Util;
using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using System;
@@ -33,7 +35,7 @@
            IRow row1 = SheetOne.CreateRow(0);
            for (int i = 0; i < properties.Count; i++)
            {
                row1.CreateCell(i).SetCellValue((attrs[i]).Name);
                row1.CreateCell(i).SetCellValue(LangUtil.Get((attrs[i]).Name));
            }
            for (int i = 0; i < datas.Count(); i++)
@@ -64,7 +66,7 @@
            Dictionary<string, PropertyInfo> dict = new Dictionary<string, PropertyInfo>();
            for (int i = 0; i < attrs.Count; i++)
            {
                dict.Add(attrs[i].Name, properties[i]);
                dict.Add(LangUtil.Get(attrs[i].Name), properties[i]);
            }
            using (FileStream fileStream1 = new FileStream(sourcePath, FileMode.Open, FileAccess.Read))
            {
@@ -105,23 +107,26 @@
                                    value = row.GetCell(j).BooleanCellValue;
                                var prop = dict[title];
                                if (prop.PropertyType is string)
                                if (prop.PropertyType == typeof(string))
                                {
                                    value = Convert.ToString(value);
                                }
                                else if (prop.PropertyType is double || prop.PropertyType is double?)
                                else if (prop.PropertyType == typeof(double) || prop.PropertyType == typeof(double?))
                                {
                                    value = Convert.ToDouble(value);
                                }
                                else if (prop.PropertyType is int || prop.PropertyType is int?)
                                else if (prop.PropertyType == typeof(int) || prop.PropertyType == typeof(int?))
                                {
                                    value = Convert.ToInt16(value);
                                }
                                else if (prop.PropertyType is bool || prop.PropertyType is bool?)
                                else if (prop.PropertyType == typeof(bool) || prop.PropertyType == typeof(bool?))
                                {
                                    value = Convert.ToBoolean(value);
                                }
                                else if (prop.PropertyType.IsEnum)
                                {
                                    value = Enum.Parse(prop.PropertyType, value.ToString());
                                }
                                prop.SetValue(obj, value);
                                isEmpty = false;
                            }
HMI.Util/HMI.Util.csproj
@@ -75,7 +75,6 @@
  </ItemGroup>
  <ItemGroup>
    <Compile Include="ExcelHelper.cs" />
    <Compile Include="ExcelColumn.cs" />
    <Compile Include="IniFileHelper.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
    <Compile Include="PropertyHelper.cs" />
@@ -83,5 +82,11 @@
  <ItemGroup>
    <None Include="packages.config" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\HMI.Core\HMI.Core.csproj">
      <Project>{3e3bf44a-0b69-45c5-9512-528cb48a7106}</Project>
      <Name>HMI.Core</Name>
    </ProjectReference>
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
Plugin.DOControl/ControlList.xaml
New file
@@ -0,0 +1,120 @@
<pu:WindowX x:Class="Plugin.DOControl.ControlList"
            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.DOControl"
            xmlns:pw="https://opensource.panuon.com/wpf"
            xmlns:pu="https://opensource.panuon.com/wpf-ui"
            xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
            mc:Ignorable="d"
            Title="Form"
            Height="800"
            SizeToContent="Manual"
            WindowStartupLocation="CenterOwner"
            Style="{StaticResource ModalWindow}">
    <Grid Margin="10" dp:GridHelper.Space="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <DockPanel Grid.Row="0">
            <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>
            <TextBlock FontSize="{StaticResource TitleFontSize}"
VerticalAlignment="Center" HorizontalAlignment="Left" Text="{DynamicResource doc.list}"/>
        </DockPanel>
        <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 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 Group}">
                    <DataGridTextColumn.Header>
                        <TextBlock Text="{DynamicResource doc.group}"/>
                    </DataGridTextColumn.Header>
                </DataGridTextColumn>
                <DataGridTextColumn Width="*" Binding="{Binding Name}">
                    <DataGridTextColumn.Header>
                        <TextBlock Text="{DynamicResource doc.name}"/>
                    </DataGridTextColumn.Header>
                </DataGridTextColumn>
                <DataGridTextColumn Width="*" Binding="{Binding Address}">
                    <DataGridTextColumn.Header>
                        <TextBlock Text="{DynamicResource doc.address}"/>
                    </DataGridTextColumn.Header>
                </DataGridTextColumn>
                <DataGridTextColumn Width="*" Binding="{Binding ReadOnly}">
                    <DataGridTextColumn.Header>
                        <TextBlock Text="{DynamicResource doc.readOnly}"/>
                    </DataGridTextColumn.Header>
                </DataGridTextColumn>
                <DataGridTextColumn Width="*" Binding="{Binding PressDownValue}">
                    <DataGridTextColumn.Header>
                        <TextBlock Text="{DynamicResource doc.pressDownValue}"/>
                    </DataGridTextColumn.Header>
                </DataGridTextColumn>
                <DataGridTextColumn Width="*" Binding="{Binding PressUpValue}">
                    <DataGridTextColumn.Header>
                        <TextBlock Text="{DynamicResource doc.pressUpValue}"/>
                    </DataGridTextColumn.Header>
                </DataGridTextColumn>
                <DataGridTextColumn Width="*" Binding="{Binding ControlType}">
                    <DataGridTextColumn.Header>
                        <TextBlock Text="{DynamicResource doc.controlType}"/>
                    </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>
</pu:WindowX>
Plugin.DOControl/ControlList.xaml.cs
New file
@@ -0,0 +1,224 @@
using HMI.Core.Entity;
using HMI.Core.Manager;
using HMI.Core.Util;
using HMI.UI.DpUtil;
using HMI.UI.Util;
using HMI.Util;
using Microsoft.Win32;
using Panuon.WPF.UI;
using Plugin.DOControl.Entity;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace Plugin.DOControl
{
    /// <summary>
    /// ModalWindow.xaml 的交互逻辑
    /// </summary>
    public partial class ControlList : WindowX
    {
        public ControlList()
        {
            this.DataContext = this;
            InitializeComponent();
        }
        private ObservableCollection<DOControlData> datasource;
        public ObservableCollection<DOControlData> Datasource
        {
            get { return datasource; }
            set { Set(ref datasource, value); }
        }
        private bool dataChanged;
        protected override void OnClosing(CancelEventArgs e)
        {
            DialogResult = dataChanged;
            base.OnClosing(e);
        }
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Button btn = sender as Button;
            DOControlData data = btn?.DataContext as DOControlData;
            switch (btn.Name)
            {
                case "addBtn":
                    OpenDataModal(null);
                    break;
                case "editBtn":
                    OpenDataModal(data);
                    break;
                case "delBtn":
                    TryDeleteData(data);
                    break;
                case "delBatchBtn":
                    TryDeleteBatch();
                    break;
                case "refreshBtn":
                    LoadData();
                    break;
                case "exportBtn":
                    TryExport();
                    break;
                case "importBtn":
                    TryImport();
                    break;
                default:
                    break;
            }
        }
        public void LoadData()
        {
            ObservableCollection<DOControlData> datas = new ObservableCollection<DOControlData>();
            try
            {
                if (File.Exists(DOControl.FilePath))
                {
                    datas = XmlHelper.DeserializeFromXml<ObservableCollection<DOControlData>>(DOControl.FilePath);
                }
            }
            catch (Exception ex)
            {
                DOControl.Logger?.Error("Data List Load Data Failed.", ex);
                ToastHelper.Error($"{this.lang("App.LoadFailed")}: {ex.Message}");
            }
            Datasource = datas;
        }
        private void OpenDataModal(DOControlData data)
        {
            try
            {
                DataModal modal = new DataModal
                {
                    Title = data == null ? this.lang("App.Add") : this.lang("App.Edit"),
                    Owner = Window.GetWindow(this)
                };
                if (modal.Open(data ?? new DOControlData(), Datasource) == true)
                {
                    dataChanged = true;
                    LoadData();
                }
            }
            catch (Exception ex)
            {
                DOControl.Logger?.Error($"Data List {(data == null ? "Add" : "Edit")} Data Failed.", ex);
                ToastHelper.Error($"{(data == null ? this.lang("App.AddFailed") : this.lang("App.EditFailed"))}: {ex.Message}");
            }
        }
        private void TryDeleteData(DOControlData data)
        {
            try
            {
                int index = Datasource.IndexOf(data);
                if (index < 0)
                {
                    ToastHelper.Error("Error");
                    return;
                }
                if (MsgBoxHelper.WarnMessage(this, this.lang("HMI.MakeSureDeleteSingle", data.Name)) == MessageBoxResult.OK)
                {
                    var temp = XmlHelper.DeepClone(Datasource);
                    temp.RemoveAt(index);
                    XmlHelper.SerializeToXml(DOControl.FilePath, temp);
                    dataChanged = true;
                    LoadData();
                }
            }
            catch (Exception ex)
            {
                DOControl.Logger?.Error($"Data List Try Delete Data Failed.", ex);
                ToastHelper.Error($"{this.lang("App.DeleteFailed")}: {ex.Message}");
            }
        }
        private void TryDeleteBatch()
        {
            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();
                    XmlHelper.SerializeToXml(DOControl.FilePath, temp);
                    dataChanged = true;
                    LoadData();
                }
            }
            catch (Exception ex)
            {
                DOControl.Logger?.Error($"Data List Try Delete Batch Data Failed.", ex);
                ToastHelper.Error($"{this.lang("App.DeleteFailed")}: {ex.Message}");
            }
        }
        public void TryExport()
        {
            try
            {
                SaveFileDialog sfd = new SaveFileDialog();
                sfd.Filter = "";
                sfd.DefaultExt = "xls";
                sfd.FileName = this.lang("doc.list");
                if (sfd.ShowDialog() == true)
                {
                    string targetPath = sfd.FileName;
                    ExcelHelper.ExportExcel(targetPath, Datasource);
                    ToastHelper.Success(Window.GetWindow(this), this.lang("App.ExportSuccess"));
                }
            }
            catch (Exception ex)
            {
                DOControl.Logger?.Error("Export Data Failed.", ex);
                MsgBoxHelper.Error(Window.GetWindow(this), $"{this.lang("App.ExportFailed")}: {ex.Message}");
            }
        }
        public void TryImport()
        {
            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<DOControlData> datas = ExcelHelper.ReadFromExcel<DOControlData>(openFileDialog.FileName);
                    XmlHelper.SerializeToXml(DOControl.FilePath, datas);
                    dataChanged = true;
                    LoadData();
                }
            }
            catch (Exception ex)
            {
                DOControl.Logger?.Error("Export Test Item Failed.", ex);
                MsgBoxHelper.Error(Window.GetWindow(this), $"{this.lang("App.ExportFailed")}: {ex.Message}");
            }
        }
        public bool? Open(ObservableCollection<DOControlData> datasource)
        {
            Datasource = datasource;
            return this.ShowDialog();
        }
    }
}
Plugin.DOControl/DOControl.xaml
@@ -1,10 +1,10 @@
<hb:PluginControl x:Class="HMI.DOControl.DOControl"
<hb:PluginControl x:Class="Plugin.DOControl.DOControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
             xmlns:local="clr-namespace:HMI.DOControl"
             xmlns:local="clr-namespace:Plugin.DOControl"
             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"
@@ -13,100 +13,123 @@
             Loaded="UserControl_Loaded"
             Background="{StaticResource WorkspaceBackground}"
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <pu:StyleDictionary Includes="GroupBox" />
            </ResourceDictionary.MergedDictionaries>
            <Style TargetType="GroupBox" BasedOn="{StaticResource {x:Static pu:StyleKeys.GroupBoxStyle}}">
                <Setter Property="pu:GroupBoxHelper.HeaderBackground" Value="{StaticResource ReHoriBar}"/>
                <Setter Property="pu:GroupBoxHelper.HeaderForeground" Value="{StaticResource PrimaryColorContract}"/>
                <Setter Property="pu:GroupBoxHelper.HeaderFontWeight" Value="Bold"/>
            </Style>
        </ResourceDictionary>
    </UserControl.Resources>
    <Border Style="{StaticResource CardBoarder}">
        <Grid dp:GridHelper.Space="10" Cursor="">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <TextBlock FontSize="{StaticResource TitleFontSize}"
VerticalAlignment="Center" HorizontalAlignment="Left" Text="日志查询"/>
            <DockPanel Grid.Row="1" Cursor="">
            <DockPanel Grid.Row="0" Cursor="">
                <StackPanel DockPanel.Dock="Right" Style="{StaticResource ButtonGroup}">
                    <Button Style="{StaticResource QueryBtn}"
                            x:Name="queryBtn" Click="Button_Click"/>
                    <Button Style="{StaticResource SettingBtn}"
x:Name="settingBtn" Click="Button_Click"/>
                    <!--<Button Style="{StaticResource AddBtn}"
                            x:Name="addBtn" Click="Button_Click"/>
                    <Button Style="{StaticResource RfhBtn}"
                            x:Name="resetBtn" Click="Button_Click"/>
                    <Button x:Name="exportBtn"
                            Style="{StaticResource ExportBtn}" Click="Button_Click"/>
                            Style="{StaticResource ExportBtn}" Click="Button_Click"/>-->
                </StackPanel>
                <StackPanel Style="{StaticResource ButtonGroup}" HorizontalAlignment="Left">
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="级别"/>
                    <ComboBox Style="{StaticResource FormComboxStyle}" x:Name="levelInput"
                              Width="180"
                              SelectedValuePath="Content">
                        <ComboBoxItem Content="info"/>
                        <ComboBoxItem Content="warn"/>
                        <ComboBoxItem Content="error"/>
                    </ComboBox>
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="开始时间"/>
                    <pu:DateTimePicker Style="{StaticResource TimePicker}" x:Name="startTimeInput"/>
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="结束时间"/>
                    <pu:DateTimePicker Style="{StaticResource TimePicker}" x:Name="endTimeInput"/>
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="来源"/>
                    <!--<ComboBox Style="{StaticResource FormComboxStyle}" x:Name="sourceInput" Width="180"></ComboBox>-->
                    <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="sourceInput" Width="180"/>
                    <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="实时刷新"/>
                    <CheckBox x:Name="autoRfhInput" HorizontalAlignment="Center" VerticalAlignment="Center"
                              IsChecked="{Binding AutoRfh,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                              />
                </StackPanel>
                <TextBlock FontSize="{StaticResource TitleFontSize}"
VerticalAlignment="Center" HorizontalAlignment="Left" Text="{DynamicResource doc.title}"/>
            </DockPanel>
            <Grid Grid.Row="1" >
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="0*"/>
                    <ColumnDefinition Width="10*"/>
                </Grid.ColumnDefinitions>
                <!--<ListBox ItemsSource="{Binding Groups}" SelectedItem="{Binding SelectedGroup}">
                    <ListBox.Style>
                        <Style TargetType="ListBox">
                            <Setter Property="Padding" Value="-1"/>
                            <Setter Property="Background" Value="Transparent"/>
                            <Setter Property="ItemContainerStyle">
                                <Setter.Value>
                                    <Style TargetType="ListBoxItem">
                                        <Setter Property="Margin" Value="0"/>
                                        <Setter Property="Cursor" Value="Hand"/>
                                        <Setter Property="FontWeight" Value="Bold"/>
            <DataGrid Grid.Row="2" x:Name="table" Style="{StaticResource DefaultDataGrid}">
                <DataGrid.RowStyle>
                    <Style TargetType="DataGridRow" BasedOn="{StaticResource {x:Static pu:StyleKeys.DataGridRowStyle}}">
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding type}" Value="warn">
                                <Setter Property="Background" Value="{StaticResource WarningColor}"/>
                                <Setter Property="Foreground" Value="Black"/>
                            </DataTrigger>
                            <DataTrigger Binding="{Binding type}" Value="error">
                                <Setter Property="Background" Value="{StaticResource DangerColor}"/>
                                <Setter Property="Foreground" Value="Black"/>
                            </DataTrigger>
                            <Trigger Property="IsSelected" Value="True">
                                <Setter Property="Background" Value="Transparent"/>
                            </Trigger>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="Background" Value="Transparent"/>
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </DataGrid.RowStyle>
                <DataGrid.Columns>
                    <DataGridTemplateColumn Header="" Width="Auto" CanUserResize="False">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <CheckBox VerticalAlignment="Center" IsChecked="{Binding Selected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                    <DataGridTemplateColumn Header="#" CanUserResize="False">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Converter={StaticResource rowNumber},
    RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGridRow},AncestorLevel=1}}"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                    <DataGridTextColumn Width="*" Binding="{Binding type}" Header="级别" />
                    <DataGridTextColumn Width="*" Binding="{Binding time}" Header="时间" />
                    <DataGridTextColumn Width="*" Binding="{Binding source}" Header="来源" />
                    <DataGridTextColumn Width="*" Binding="{Binding text}" Header="文本" />
                </DataGrid.Columns>
            </DataGrid>
                                        <Setter Property="Background" Value="{StaticResource PrimaryColor}"/>
                                        <Setter Property="Template">
                                            <Setter.Value>
                                                <ControlTemplate TargetType="ListBoxItem">
                                                    <Border x:Name="border" Padding="0,15">
                                                        <ContentPresenter x:Name="cp" Content="{TemplateBinding Content}"
ContentStringFormat="{TemplateBinding ContentStringFormat}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
                                                    </Border>
                                                    <ControlTemplate.Triggers>
                                                        <Trigger Property="IsSelected" Value="True">
                                                            <Setter Property="Background" TargetName="border"
                                                        Value="{StaticResource ReHoriBar}"/>
                                                            <Setter Property="Foreground" Value="White"/>
                                                        </Trigger>
                                                        <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"/>
                                    </ItemsPanelTemplate>
                                </Setter.Value>
                            </Setter>
                            <Setter Property="ItemTemplate">
                                <Setter.Value>
                                    <DataTemplate>
                                        <Label VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
                                               Content="{Binding Mode=OneWay}"/>
                                    </DataTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </ListBox.Style>
                </ListBox>-->
                <Border Grid.Column="1" BorderThickness="0" Padding="0" BorderBrush="#FFABADB3">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition/>
                            <ColumnDefinition/>
                        </Grid.ColumnDefinitions>
                        <StackPanel x:Name="panel0" Margin="0,0,10,0">
                        </StackPanel>
                        <StackPanel Grid.Column="1" x:Name="panel1" >
                        </StackPanel>
                    </Grid>
                </Border>
            </Grid>
        </Grid>
    </Border>
</hb:PluginControl>
Plugin.DOControl/DOControl.xaml.cs
@@ -1,29 +1,465 @@
using HMI.Base;
using HMI.Core.Setting;
using HMI.Core.Util;
using HMI.UI;
using HMI.UI.Util;
using log4net;
using Panuon.WPF.UI;
using Plugin.DOControl.Entity;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
namespace HMI.DOControl
namespace Plugin.DOControl
{
    /// <summary>
    /// UserControl1.xaml 的交互逻辑
    /// </summary>
    public partial class DOControl : PluginControl
    {
        /// <summary>
        /// 实例化服务器接口函数
        /// </summary>
        private OpcInterface.OpcInterface lTSCADAApi = new OpcInterface.OpcInterface();
        private ObservableCollection<string> groups;
        public ObservableCollection<string> Groups
        {
            get => groups;
            set
            {
                Set(ref groups, value);
            }
        }
        private string selectedGroup;
        public string SelectedGroup
        {
            get => selectedGroup;
            set
            {
                Set(ref selectedGroup, value);
                if (string.IsNullOrEmpty(value))
                {
                    selectedControls.Clear();
                }
                else if (GroupMap.ContainsKey(value))
                {
                    selectedControls = GroupMap[value];
                }
                RefreshControl();
            }
        }
        private ObservableCollection<DOControlData> datasource;
        public ObservableCollection<DOControlData> Datasource
        {
            get => datasource;
            set
            {
                Set(ref datasource, value);
            }
        }
        private Dictionary<string, List<DOControlData>> GroupMap = new Dictionary<string, List<DOControlData>>();
        private List<FrameworkElement> visiableControls = new List<FrameworkElement>();
        private List<DOControlData> selectedControls = new List<DOControlData>();
        private System.Timers.Timer motorMonitorTimer;
        public DOControl()
        {
            DataContext = this;
            motorMonitorTimer = new System.Timers.Timer();
            motorMonitorTimer.Interval = 200;
            motorMonitorTimer.AutoReset = false;
            motorMonitorTimer.Elapsed += MotorMonitorTimer_Elapsed;
            InitializeComponent();
        }
        public override void Active()
        {
            motorMonitorTimer.Enabled = true;
            base.Active();
        }
        public override void DeActive()
        {
            motorMonitorTimer.Enabled = false;
            base.DeActive();
        }
        public override void DoClose(object sender, EventArgs e)
        {
            motorMonitorTimer.Enabled = false;
            base.DoClose(sender, e);
        }
        public static string FilePath { get; private set; }
        public static ILog Logger { get; private set; }
        public override void SetBaseFolder(string folder)
        {
            FilePath = Path.Combine(folder, "DOControlData.data");
            base.SetBaseFolder(folder);
        }
        public override void SetLogger(ILog logger)
        {
            Logger = logger;
            base.SetLogger(logger);
        }
        public override void InitFinish()
        {
            base.InitFinish();
        }
        private void LoadData()
        {
            try
            {
                motorMonitorTimer.Enabled = false;
                ObservableCollection<DOControlData> datas = new ObservableCollection<DOControlData>();
                if (File.Exists(FilePath))
                {
                    datas = XmlHelper.DeserializeFromXml<ObservableCollection<DOControlData>>(FilePath);
                }
                Datasource = datas;
                GroupMap = datas.GroupBy(x => x.Group)
                   .ToDictionary(
                       group => group.Key,
                       group => group.ToList()
                   );
                Groups = new ObservableCollection<string>(GroupMap.Keys);
                if (string.IsNullOrEmpty(SelectedGroup) && Groups.Count > 0)
                {
                    SelectedGroup = Groups[0];
                }
                else
                {
                    RefreshControl();
                }
            }
            catch (Exception ex)
            {
                log.Error("Load Data Failed.", ex);
                ToastHelper.Error(this, ex.Message);
            }
            finally
            {
                motorMonitorTimer.Enabled = true;
            }
        }
        private void MotorMonitorTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            try
            {
                motorMonitorTimer.Enabled = false;
                for (int i = visiableControls.Count - 1; i >= 0; i--)
                {
                    var ui = visiableControls[i];
                    ui.Dispatcher.Invoke(() =>
                    {
                        DOControlData data = ui.Tag as DOControlData;
                        if (data == null) return;
                        string value = GetPlcValue(data.Address);
                        switch (data.ControlType)
                        {
                            case ControlType.Button:
                                var color = Application.Current.FindResource("VerBar") as Brush;
                                if (value == data.PressDownValue)
                                {
                                    color = Application.Current.FindResource("GreenVerBar") as Brush;
                                }
                            ((Button)ui).Background = color;
                                ((Button)ui).Content = value;
                                break;
                            case ControlType.Int16:
                            case ControlType.Int32:
                            case ControlType.Real:
                            case ControlType.LReal:
                            case ControlType.String:
                            case ControlType.WString:
                                ((Button)ui).Content = value;
                                break;
                            default:
                                break;
                        }
                    });
                }
            }
            finally
            {
                motorMonitorTimer.Enabled = true;
            }
        }
        private void RefreshControl()
        {
            try
            {
                motorMonitorTimer.Enabled = false;
                panel0.Children.Clear();
                panel1.Children.Clear();
                visiableControls.Clear();
                int index = 0;
                foreach (string group in GroupMap.Keys)
                {
                    int num = index % 2;
                    WrapPanel panel = new WrapPanel
                    {
                        Orientation = Orientation.Horizontal
                    };
                    GroupBox gb = new GroupBox
                    {
                        Header = group,
                        Content = panel,
                        Margin = new Thickness(0, 0, 0, 10)
                    };
                    var datas = GroupMap[group];
                    foreach (var data in datas)
                    {
                        FrameworkElement ui = null;
                        switch (data.ControlType)
                        {
                            case ControlType.Button:
                            case ControlType.ClickButton:
                                ui = CreateButton(data);
                                break;
                            case ControlType.Int16:
                            case ControlType.Int32:
                            case ControlType.Real:
                            case ControlType.LReal:
                            case ControlType.String:
                            case ControlType.WString:
                                ui = CreateNumberInput(data);
                                break;
                            default:
                                break;
                        }
                        if (ui != null)
                        {
                            FormGroup fg = new FormGroup
                            {
                                Header = data.Name,
                                Content = ui,
                                Orientation = Orientation.Vertical,
                                Margin = new Thickness(10, 0, 0, 10)
                            };
                            panel.Children.Add(fg);
                            visiableControls.Add(ui);
                        }
                    }
                    if (num == 0)
                    {
                        panel0.Children.Add(gb);
                    }
                    else
                    {
                        panel1.Children.Add(gb);
                    }
                    index++;
                }
            }
            catch
            {
            }
            finally
            {
                motorMonitorTimer.Enabled = true;
            }
        }
        private Button CreateButton(DOControlData data)
        {
            Style btnStyle = Application.Current.FindResource("PrimaryButton") as Style;
            var btn = new Button
            {
                Tag = data,
                Style = btnStyle,
                MinWidth = 180,
                MinHeight = 40
            };
            if (data.ControlType == ControlType.Button)
            {
                btn.Click += ControlButton_Click;
            }
            else if (data.ControlType == ControlType.ClickButton)
            {
                btn.Content = data.Address;
                btn.PreviewMouseLeftButtonDown += Btn_MouseLeftButtonDown;
                btn.PreviewMouseLeftButtonUp += Btn_MouseLeftButtonUp;
            }
            return btn;
        }
        private void Btn_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            Button btn = sender as Button;
            DOControlData data = btn.Tag as DOControlData;
            try
            {
                var color = Application.Current.FindResource("GreenVerBar") as Brush;
                btn.Background = color;
                SetPlcValue(data.Address, data.PressDownValue);
            }
            catch (Exception ex)
            {
                log.Error("PressDownClick", ex);
                ToastHelper.Error($"{data.Name}: {ex.Message}");
            }
        }
        private void Btn_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            Button btn = sender as Button;
            DOControlData data = btn.Tag as DOControlData;
            try
            {
                var color = Application.Current.FindResource("VerBar") as Brush;
                btn.Background = color;
                SetPlcValue(data.Address, data.PressUpValue);
            }
            catch (Exception ex)
            {
                log.Error("PressUpClick", ex);
                ToastHelper.Error($"{data.Name}: {ex.Message}");
            }
        }
        private Button CreateNumberInput(DOControlData data)
        {
            Style btnStyle = Application.Current.FindResource("PrimaryButton") as Style;
            var tb = new Button
            {
                Tag = data,
                Style = btnStyle,
                MinWidth = 180,
                MinHeight = 40,
                BorderBrush = Brushes.Black,
                BorderThickness = new Thickness(1),
                Background = Brushes.White,
                Foreground = Brushes.Black
            };
            if (data.ReadOnly)
            {
                tb.Background = Brushes.LightGray;
                return tb;
            }
            if (data.ControlType == ControlType.String || data.ControlType == ControlType.WString)
            {
                tb.Click += StringInput_Click;
            }
            else
            {
                tb.Click += NumberInput_Click;
            }
            return tb;
        }
        /// <summary>
        /// 设置plc值
        /// </summary>
        /// <param name="address">地址</param>
        /// <param name="valueType">类型</param>
        /// <param name="value">值</param>
        private void SetPlcValue(string key, string value)
        {
            if (!lTSCADAApi.DoFctWriteSingle(key, value, out string errMsg))
            {
                throw new Exception(errMsg);
            }
        }
        private string GetPlcValue(string key)
        {
            if (lTSCADAApi.DoFctReadSingle(key, out string Val, out string errMsg))
            {
                return Val;
            }
            else
            {
                return null;
            }
        }
        private void ControlButton_Click(object sender, RoutedEventArgs args)
        {
            Button btn = sender as Button;
            DOControlData data = btn.Tag as DOControlData;
            try
            {
                string value = btn.Content?.ToString() == data.PressDownValue ? data.PressUpValue : data.PressDownValue;
                SetPlcValue(data.Address, value);
            }
            catch (Exception ex)
            {
                log.Error("ControlButton_Click", ex);
                ToastHelper.Error($"{data.Name}: {ex.Message}");
            }
        }
        private void StringInput_Click(object sender, RoutedEventArgs args)
        {
            Button tb = sender as Button;
            DOControlData data = tb.Tag as DOControlData;
            KeyboardWindow kw = new KeyboardWindow
            {
                Title = data.Name,
                Owner = Window.GetWindow(this)
            };
            if (kw.Open(tb.Content?.ToString()) == true)
            {
                SetPlcValue(data.Address, kw.Data?.ToString());
            }
        }
        private void NumberInput_Click(object sender, RoutedEventArgs args)
        {
            Button tb = sender as Button;
            DOControlData data = tb.Tag as DOControlData;
            NumberPanel np = new NumberPanel
            {
                Title = data.Name,
                Owner = Window.GetWindow(this)
            };
            if (np.Open(tb.Content?.ToString()) == true)
            {
                SetPlcValue(data.Address, np.Data?.ToString());
            }
        }
        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            LoadData();
            if (SystemSetting.Instance.PlcEnabled)
            {
                if (lTSCADAApi.Connect2("127.0.0.1", 6543, 18888, out string errMsg))
                {
                    motorMonitorTimer.Enabled = true;
                    RefreshControl();
                }
                else
                {
                    MsgBoxHelper2.Error(errMsg);
                }
            }
            Loaded -= UserControl_Loaded;
        }
@@ -32,9 +468,14 @@
            Button btn = sender as Button;
            switch (btn.Name)
            {
                case "queryBtn":
                case "addBtn":
                    OpenDataModal(null);
                    break;
                case "settingBtn":
                    OpenDataList();
                    break;
                case "resetBtn":
                    LoadData();
                    break;
                case "exportBtn":
                    break;
@@ -42,5 +483,30 @@
                    break;
            }
        }
        private void OpenDataModal(DOControlData data)
        {
            DataModal modal = new DataModal
            {
                Title = data == null ? this.lang("App.Add") : this.lang("App.Edit"),
                Owner = Window.GetWindow(this)
            };
            if (modal.Open(data ?? new DOControlData(), Datasource) == true)
            {
                LoadData();
            }
        }
        private void OpenDataList()
        {
            ControlList list = new ControlList
            {
                Owner = Window.GetWindow(this)
            };
            if (list.Open(Datasource) == true)
            {
                LoadData();
            }
        }
    }
}
Plugin.DOControl/DataModal.xaml
New file
@@ -0,0 +1,85 @@
<pu:WindowX x:Class="Plugin.DOControl.DataModal"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:Plugin.DOControl"
            xmlns:pw="https://opensource.panuon.com/wpf"
            xmlns:pu="https://opensource.panuon.com/wpf-ui"
            xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
            mc:Ignorable="d"
            Title="Form"
            SizeToContent="Height"
            Width="600"
            WindowStartupLocation="CenterOwner"
            Style="{StaticResource ModalWindow}">
    <Grid Margin="10">
        <StackPanel x:Name="formPanel">
            <pu:FormGroup x:Name="FgGroup"
                          Header="{DynamicResource doc.group}"
                          Style="{StaticResource InputFormGroupStyle}"
                          dp:FormGroupHelper.IsRequired="True"
                          GroupName="MyFormGroup">
                <TextBox x:Name="TbGroup" Style="{StaticResource EditorTextBoxStyle}" />
            </pu:FormGroup>
            <pu:FormGroup x:Name="FgName"
              Header="{DynamicResource doc.name}"
              Style="{StaticResource InputFormGroupStyle}"
              dp:FormGroupHelper.IsRequired="True"
              dp:FormGroupHelper.ValidateMethod="ValidateName"
              GroupName="MyFormGroup">
                <TextBox x:Name="TbName" Style="{StaticResource EditorTextBoxStyle}" />
            </pu:FormGroup>
            <pu:FormGroup x:Name="FgAddress"
  Header="{DynamicResource doc.address}"
  Style="{StaticResource InputFormGroupStyle}"
  dp:FormGroupHelper.IsRequired="True"
  dp:FormGroupHelper.ValidateMethod="ValidateAddress"
  GroupName="MyFormGroup">
                <TextBox x:Name="TbAddress" Style="{StaticResource EditorTextBoxStyle}" />
            </pu:FormGroup>
            <pu:FormGroup x:Name="FgReadOnly"
Header="{DynamicResource doc.readOnly}"
Style="{StaticResource InputFormGroupStyle}"
GroupName="MyFormGroup">
                <CheckBox x:Name="CxReadOnly"/>
            </pu:FormGroup>
            <pu:FormGroup x:Name="FgPressDownValue"
  Header="{DynamicResource doc.pressDownValue}"
  Style="{StaticResource InputFormGroupStyle}"
  dp:FormGroupHelper.IsRequired="True"
  GroupName="MyFormGroup">
                <TextBox x:Name="TbPressDownValue" Style="{StaticResource EditorTextBoxStyle}" />
            </pu:FormGroup>
            <pu:FormGroup x:Name="FgPressUpValue"
Header="{DynamicResource doc.pressUpValue}"
Style="{StaticResource InputFormGroupStyle}"
dp:FormGroupHelper.IsRequired="True"
GroupName="MyFormGroup">
                <TextBox x:Name="TbPressUpValue" Style="{StaticResource EditorTextBoxStyle}" />
            </pu:FormGroup>
            <pu:FormGroup x:Name="FgControlType"
Header="{DynamicResource doc.controlType}"
Style="{StaticResource InputFormGroupStyle}"
dp:FormGroupHelper.IsRequired="True"
GroupName="MyFormGroup">
                <ComboBox x:Name="CbControlType" Style="{StaticResource FormComboxStyle}" SelectedValuePath="Content">
                    <ComboBoxItem>0</ComboBoxItem>
                    <ComboBoxItem>1</ComboBoxItem>
                    <ComboBoxItem>2</ComboBoxItem>
                    <ComboBoxItem>3</ComboBoxItem>
                    <ComboBoxItem>4</ComboBoxItem>
                    <ComboBoxItem>5</ComboBoxItem>
                    <ComboBoxItem>6</ComboBoxItem>
                    <ComboBoxItem>7</ComboBoxItem>
                </ComboBox>
            </pu:FormGroup>
        </StackPanel>
    </Grid>
</pu:WindowX>
Plugin.DOControl/DataModal.xaml.cs
New file
@@ -0,0 +1,157 @@
using HMI.Core.Util;
using HMI.UI.DpUtil;
using HMI.UI.Util;
using Panuon.WPF.UI;
using Plugin.DOControl.Entity;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
namespace Plugin.DOControl
{
    /// <summary>
    /// ModalWindow.xaml 的交互逻辑
    /// </summary>
    public partial class DataModal : WindowX
    {
        public DataModal()
        {
            this.DataContext = this;
            InitializeComponent();
        }
        public DOControlData Data { get; set; }
        private ObservableCollection<DOControlData> datasource;
        public ObservableCollection<DOControlData> Datasource
        {
            get { return datasource; }
            set { Set(ref datasource, value); }
        }
        private bool _isProcessing;
        protected override void OnClosing(CancelEventArgs e)
        {
            //Press Continue Button
            if (DialogResult == true)
            {
                if (_isProcessing)
                {
                    e.Cancel = true;
                    return;
                }
                var okButton = WindowXModalDialog.GetOKButton(this);
                try
                {
                    _isProcessing = true;
                    ButtonHelper.SetIsPending(okButton, true);
                    if (!ValidateForm())
                    {
                        e.Cancel = true;
                        return;
                    }
                    SaveOrUpdateData();
                }
                catch (Exception ex)
                {
                    ToastHelper.Error(this, $"Error!{ex.Message}");
                    e.Cancel = true;
                    return;
                }
                finally
                {
                    _isProcessing = false;
                    ButtonHelper.SetIsPending(okButton, false);
                }
            }
            base.OnClosing(e);
        }
        private bool ValidateName(object value, out string err)
        {
            err = null;
            //string group = TbGroup.Text;
            //var temp = Datasource.FirstOrDefault(d => d.Group == group && d.Name == value.ToString());
            //if (temp != null && temp != Data)
            //{
            //    err = this.lang("App.MustBeUnique", $"{this.lang("doc.group")},{this.lang("doc.name")}");
            //    return false;
            //}
            return true;
        }
        private bool ValidateAddress(object value, out string err)
        {
            err = null;
            //string group = TbGroup.Text;
            //var temp = Datasource.FirstOrDefault(d => d.Address == value.ToString());
            //if (temp != null && temp != Data)
            //{
            //    err = this.lang("App.MustBeUnique", this.lang("doc.Address"));
            //    return false;
            //}
            return true;
        }
        private bool ValidateForm()
        {
            bool flag = FormGroupHelper.ValidateForm(formPanel);
            if (flag)
            {
                Data.Group = TbGroup.Text;
                Data.Name = TbName.Text;
                Data.Address = TbAddress.Text;
                Data.ReadOnly = CxReadOnly.IsChecked == true;
                Data.PressDownValue = TbPressDownValue.Text;
                Data.PressUpValue = TbPressUpValue.Text;
                Data.ControlType = (ControlType)int.Parse(CbControlType.SelectedValue.ToString());
            }
            return flag;
        }
        public bool? Open(DOControlData data, ObservableCollection<DOControlData> datasource)
        {
            Datasource = datasource;
            Data = data;
            TbGroup.Text = Data.Group;
            TbName.Text = Data.Name;
            TbAddress.Text = Data.Address;
            CxReadOnly.IsChecked = Data.ReadOnly;
            TbPressDownValue.Text = Data.PressDownValue;
            TbPressUpValue.Text = Data.PressUpValue;
            CbControlType.SelectedValue = (int)Data.ControlType;
            return this.ShowDialog();
        }
        private void SaveOrUpdateData()
        {
            int index = Datasource.IndexOf(Data);
            if (index < 0)
            {
                InsertData(Data);
            }
            else
            {
                UpdateData(index, Data);
            }
        }
        public void InsertData(DOControlData data)
        {
            Datasource.Add(data);
            XmlHelper.SerializeToXml(DOControl.FilePath, Datasource);
        }
        public void UpdateData(int index, DOControlData data)
        {
            var temp = XmlHelper.DeepClone(Datasource);
            temp[index] = data;
            XmlHelper.SerializeToXml(DOControl.FilePath, temp);
        }
    }
}
Plugin.DOControl/Entity/DOControlData.cs
New file
@@ -0,0 +1,60 @@
using HMI.Core.Entity;
using HMI.Core;
namespace Plugin.DOControl.Entity
{
    public enum ControlType
    {
        Button, Int16, Int32, Real, LReal, String, WString, ClickButton
    }
    public class DOControlData : SelectBase
    {
        /// <summary>
        /// 分组名称
        /// </summary>
        [ExcelColumn("doc.group")]
        public string Group { get; set; }
        /// <summary>
        /// 控制点名称
        /// </summary>
        [ExcelColumn("doc.name")]
        public string Name { get; set; }
        /// <summary>
        /// 地址
        /// </summary>
        [ExcelColumn("doc.address")]
        public string Address { get; set; }
        [ExcelColumn("doc.readOnly")]
        public bool ReadOnly { get; set; }
        /// <summary>
        /// 按下值
        /// </summary>
        [ExcelColumn("doc.pressDownValue")]
        public string PressDownValue { get; set; }
        /// <summary>
        /// 抬起值
        /// </summary>
        [ExcelColumn("doc.pressUpValue")]
        public string PressUpValue { get; set; }
        /// <summary>
        /// 值格式
        /// </summary>
        public string ValueFormat { get; set; }
        /// <summary>
        /// 是否启用
        /// </summary>
        public bool Enable { get; set; }
        /// <summary>
        /// 控制类型
        /// </summary>
        [ExcelColumn("doc.controlType")]
        public ControlType ControlType { get; set; }
    }
}
Plugin.DOControl/Plugin.DOControl.csproj
@@ -59,6 +59,16 @@
    <Reference Include="PresentationFramework" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="ControlList.xaml.cs">
      <DependentUpon>ControlList.xaml</DependentUpon>
    </Compile>
    <Compile Include="DataModal.xaml.cs">
      <DependentUpon>DataModal.xaml</DependentUpon>
    </Compile>
    <Page Include="ControlList.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
    </Page>
    <Page Include="DOControl.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
@@ -66,8 +76,13 @@
    <Compile Include="DOControl.xaml.cs">
      <DependentUpon>DOControl.xaml</DependentUpon>
    </Compile>
    <Page Include="DataModal.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
    </Page>
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Entity\DOControlData.cs" />
    <Compile Include="Properties\AssemblyInfo.cs">
      <SubType>Code</SubType>
    </Compile>
@@ -92,6 +107,10 @@
    </None>
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\..\HMI.Old\OPCInterface\OpcInterface\OpcInterface.csproj">
      <Project>{e1464149-2c0a-499d-ab90-0c72e6ed1c53}</Project>
      <Name>OpcInterface</Name>
    </ProjectReference>
    <ProjectReference Include="..\HMI.Base\HMI.Base.csproj">
      <Project>{2a6f9720-a890-4036-9d73-41a0dae28341}</Project>
      <Name>HMI.Base</Name>
@@ -109,5 +128,6 @@
      <Name>HMI.Util</Name>
    </ProjectReference>
  </ItemGroup>
  <ItemGroup />
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
Plugin.ServoManage/Plugin.ServoManage.csproj
@@ -79,9 +79,6 @@
  <ItemGroup>
    <Compile Include="Entity\ServoData.cs" />
    <Compile Include="Entity\ServoProductData.cs" />
    <Compile Include="NumberPanel.xaml.cs">
      <DependentUpon>NumberPanel.xaml</DependentUpon>
    </Compile>
    <Compile Include="ServoProductModal.xaml.cs">
      <DependentUpon>ServoProductModal.xaml</DependentUpon>
    </Compile>
@@ -99,10 +96,6 @@
      <DependentUpon>ServoManage.xaml</DependentUpon>
      <SubType>Code</SubType>
    </Compile>
    <Page Include="NumberPanel.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
    </Page>
    <Page Include="ServoProductModal.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
Plugin.ServoManage/ServoManage.xaml
@@ -243,7 +243,7 @@
                            </StackPanel>
                        </DockPanel>
                        <Grid Grid.Row="1" dp:GridHelper.Space="10">
                        <Grid Grid.Row="1" dp:GridHelper.Space="10" x:Name="panel">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition/>
@@ -301,24 +301,34 @@
                                            </Button.Style>
                                        </Button>
                                        <Button Style="{StaticResource PrimaryButton}"
 Margin="5,0,0,0" Content="{DynamicResource svo.reset}" Tag="Reset" Click="Servo_Click"/>
 Margin="5,0,0,0" Content="{DynamicResource svo.reset}" Tag="Reset"
                                                PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown"
                                                PreviewMouseLeftButtonUp="Button_PreviewMouseLeftButtonUp" />
                                    </UniformGrid>
                                    <Button Grid.Row="3" Style="{StaticResource PrimaryButton}"
                                            Content="{DynamicResource svo.init}"
                                            Tag="Initialization" Click="Servo_Click"/>
                                            Tag="Initialization"
                                            PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown"
                                            PreviewMouseLeftButtonUp="Button_PreviewMouseLeftButtonUp"/>
                                    <Button Grid.Row="4" Style="{StaticResource PrimaryButton}"
                                            Content="{DynamicResource svo.stop}"
                                            Tag="Stop" Click="Servo_Click"/>
                                            Tag="Stop"
                                            PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown"
                                            PreviewMouseLeftButtonUp="Button_PreviewMouseLeftButtonUp"/>
                                    <UniformGrid Grid.Row="5" Columns="3">
                                        <Button Style="{StaticResource PrimaryButton}"
                                                Content="{DynamicResource svo.JOG+}"
                                                Tag="JoGadd" Click="Servo_Click"/>
                                                Tag="JoGadd"
                                            PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown"
                                            PreviewMouseLeftButtonUp="Button_PreviewMouseLeftButtonUp"/>
                                        <Label Style="{StaticResource LabelText}" MinWidth="90"
Margin="10,0,10,0"
BorderThickness="1" MouseLeftButtonUp="Label_MouseLeftButtonUp" Tag="JogVelocity" Content="{Binding JogVelocity}"/>
                                        <Button Style="{StaticResource PrimaryButton}"
                                                Content="{DynamicResource svo.JOG-}"
                                                Tag="JOGsub" Click="Servo_Click"/>
                                                Tag="JOGsub"
                                            PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown"
                                            PreviewMouseLeftButtonUp="Button_PreviewMouseLeftButtonUp"/>
                                    </UniformGrid>
                                </Grid>
                            </GroupBox>
@@ -384,7 +394,8 @@
                                            Grid.ColumnSpan="2" 
                                            Style="{StaticResource PrimaryButton}"
                                            Tag="AbsExecute"
                                            Click="Servo_Click"
                                            PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown"
                                            PreviewMouseLeftButtonUp="Button_PreviewMouseLeftButtonUp"
                                            Content="{DynamicResource svo.absExecute}"/>
                                    </Grid>
                                </GroupBox>
@@ -394,7 +405,9 @@
                                        <Button DockPanel.Dock="Left" 
                                                Margin="0,0,10,0"
                                                Style="{StaticResource PrimaryButton}"
                                                Tag="Inch" Click="Servo_Click"
                                                Tag="Inch"
                                            PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown"
                                            PreviewMouseLeftButtonUp="Button_PreviewMouseLeftButtonUp"
                                                Content="{DynamicResource svo.inching}"/>
                                        <Button DockPanel.Dock="Right"
                                                Tag="Dir" Click="Servo_Click">
Plugin.ServoManage/ServoManage.xaml.cs
@@ -1,8 +1,9 @@
using HMI.Base;
using HMI.Core.Entity;
using HMI.Core.Manager;
using HMI.Core.Setting;
using HMI.Core.Util;
using HMI.ParameterManage.Entity;
using HMI.ParameterManage.Util;
using HMI.UI;
using HMI.UI.Util;
using Plugin.ServoManage.Entity;
using System;
@@ -15,6 +16,7 @@
using System.Timers;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
namespace Plugin.ServoManage
{
@@ -93,6 +95,7 @@
        private bool isActived = false;
        private System.Timers.Timer motorMonitorTimer;
        #region ServoData
        private string enable;
        [IsStatus]
        public string Enable
@@ -345,28 +348,20 @@
                Set(ref posCode, value);
            }
        }
        #endregion
        public ServoManage()
        {
            DataContext = this;
            motorMonitorTimer = new System.Timers.Timer();
            motorMonitorTimer.Elapsed += MotorMonitorTimer_Elapsed;
            motorMonitorTimer.Interval = 100;
            InitializeComponent();
        }
        private void MotorMonitorTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            this.motorMonitorTimer.Enabled = false;
            try
            {
                RefreshServoStatus();
                this.motorMonitorTimer.Enabled = true;
            }
            catch (Exception ex)
            {
                this.motorMonitorTimer.Enabled = true;
                return;
            }
            RefreshServoStatus();
        }
        public override void SetBaseFolder(string folder)
@@ -387,8 +382,7 @@
                    {
                        if (lTSCADAApi.Connect2("127.0.0.1", 6543, 18888, out string errMsg))
                        {
                            this.motorMonitorTimer.Interval = 100;
                            this.motorMonitorTimer.Start();
                            motorMonitorTimer.Enabled = true;
                            isActived = true;
                        }
                        else
@@ -408,6 +402,15 @@
                    MsgBoxHelper2.Error(ex.Message);
                }
            }
            else {
                motorMonitorTimer.Enabled = true;
            }
        }
        public override void DeActive()
        {
            motorMonitorTimer.Enabled = false;
            base.DeActive();
        }
        private void UserControl_Loaded(object sender, RoutedEventArgs e)
@@ -435,7 +438,6 @@
                case "rfhServoBtn":
                    LoadData();
                    break;
                case "addProductBtn":
                    OpenProductModal(null);
                    break;
@@ -515,12 +517,12 @@
        public ObservableCollection<ServoProductData> ReadCommonProducts()
        {
            ObservableCollection<ServoProductData> datas = new ObservableCollection<ServoProductData>();
            List<ProductData> products = ParameterManager.GetProducts<List<ProductData>>();
            List<ProductData> products = ProductManager.GetProducts<List<ProductData>>();
            if (products != null && products.Count > 0)
            {
                foreach (var product in products)
                {
                    var parameter = ParameterManager.GetParametersByProductName(product.Name)
                    var parameter = ProductManager.GetParametersByProductName(product.Name)
                         ?.FirstOrDefault(p => p.Name == "ProgramCode");
                    if (parameter != null)
                    {
@@ -580,7 +582,8 @@
        private void RefreshServoStatus()
        {
            Dispatcher.Invoke(() =>
            this.motorMonitorTimer.Enabled = false;
            try
            {
                if (SelectedServo == null) return;
                PropertyInfo[] properties = this.GetType().GetProperties();
@@ -590,19 +593,29 @@
                    {
                        continue;
                    }
                    if (prop.Name == "SelectedProduct")
                    Dispatcher.InvokeAsync(new Action(() =>
                    {
                        var data = GetBitValue($"{SelectedServo.Name}_sRecipeCode");
                        SelectedProduct = data;
                    }
                    else
                    {
                        var data = GetBitValue($"{SelectedServo.Name}_{prop.Name}");
                        prop.SetValue(this, data);
                    }
                        if (prop.Name == "SelectedProduct")
                        {
                            var data = GetBitValue($"{SelectedServo.Name}_sRecipeCode");
                            SelectedProduct = data;
                        }
                        else
                        {
                            var data = GetBitValue($"{SelectedServo.Name}_{prop.Name}");
                            prop.SetValue(this, data);
                        }
                    }), DispatcherPriority.SystemIdle);
                }
            });
            }
            catch (Exception ex)
            {
            }
            finally
            {
                this.motorMonitorTimer.Enabled = true;
            }
        }
        /// <summary>
@@ -647,7 +660,6 @@
            Button btn = sender as Button;
            try
            {
                motorMonitorTimer.Enabled = false;
                switch (btn.Tag)
                {
                    case "Enable":
@@ -657,7 +669,7 @@
                        EnableDoClick(btn.Tag.ToString());
                        break;
                    default:
                        DoSignClick(btn.Tag?.ToString());
                        //DoSignClick(btn.Tag?.ToString());
                        break;
                }
            }
@@ -668,7 +680,6 @@
            }
            finally
            {
                motorMonitorTimer.Enabled = true;
            }
        }
@@ -720,5 +731,35 @@
                SetBitValue($"{SelectedServo?.Name}_{tag}", np.Data?.ToString());
            }
        }
        private void Button_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            Button btn = sender as Button;
            var tag = btn.Tag.ToString();
            try
            {
                motorMonitorTimer.Enabled = false;
                SetBitValue($"{SelectedServo?.Name}_{tag}", "true");
            }
            finally
            {
                motorMonitorTimer.Enabled = true;
            }
        }
        private void Button_PreviewMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            Button btn = sender as Button;
            var tag = btn.Tag.ToString();
            try
            {
                motorMonitorTimer.Enabled = false;
                SetBitValue($"{SelectedServo?.Name}_{tag}", "false");
            }
            finally
            {
                motorMonitorTimer.Enabled = true;
            }
        }
    }
}
Plugin.TestResult/DataTable.xaml
@@ -28,17 +28,37 @@
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid x:Name="infoGrid" Grid.Row="0"
             Background="LightBlue">
        <Grid x:Name="infoGrid" Grid.Row="0" Background="LightBlue">
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Label Grid.Row="0" Grid.RowSpan="1" FontSize="20" FontWeight="Bold"
                   Padding="0,0,20,0"
                   BorderThickness="0"
                   VerticalContentAlignment="Stretch" Background="Transparent" Content="{Binding title}"/>
            <TextBlock FontSize="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl},
Converter={StaticResource FontSizeConverter}, ConverterParameter='30,940'}"
                   FontWeight="Bold"
                   Margin="0"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center" Background="Transparent" Text="{Binding title}"/>
            <ComboBox Grid.Row="1" FontSize="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl},
Converter={StaticResource FontSizeConverter}, ConverterParameter='15,940'}"
                      Style="{StaticResource FormComboxStyle}"
                      pu:ComboBoxHelper.ClearButtonVisibility="Collapsed"
                      BorderBrush="Black"
                      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='15,940'}"></local:InfoTable>
        </Grid>
@@ -83,8 +103,16 @@
            </StatusBarItem>
        </StatusBar>
        <Button Grid.Row="4" Click="SetStationGlobal_Click" Style="{StaticResource SuccessButton}"
                Content="{DynamicResource App.Start}"
                />
        <Grid Grid.Row="4" >
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Button Click="ScanWindow_Click" Style="{StaticResource PrimaryButton}"
        Content="{DynamicResource Tr.Scan}"/>
            <Button Grid.Column="1" Click="SetStationGlobal_Click" Style="{StaticResource SuccessButton}"
        Content="{DynamicResource App.Start}"/>
        </Grid>
    </Grid>
</UserControl>
Plugin.TestResult/DataTable.xaml.cs
@@ -1,8 +1,10 @@
using HMI.Core.Util;
using HMI.Util;
using Plugin.TestResult_WPF.Model;
using Plugin.TestResult_WPF.Util;
using SuperSocket.SocketBase.Protocol;
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
@@ -64,7 +66,7 @@
            set { RaiseAndSetIfchanged(ref tcpServerInfo, value); }
        }
        private static CommonCodeParser codeParser = new CommonCodeParser();
        private readonly int index;
        private readonly int cellCount;
        private readonly string chartTitle;
@@ -202,6 +204,8 @@
            }
        }
        private Scanner scanner;
        private void Window_Closed(object sender, EventArgs e)
        {
            window = null;
@@ -212,6 +216,49 @@
            ClearChartData();
            parent.SetStartFlag(this.index, true);
        }
        private void ScanWindow_Click(object sender, RoutedEventArgs e)
        {
            if (scanner != null)
            {
                scanner.Activate();
            }
            else
            {
                scanner = new Scanner(this.lang("Tr.ScanTitle", title))
                {
                    Tag = title
                };
                scanner.FinishBarcode += Scanner_FinishBarcode;
                scanner.Closed += Scanner_Closed;
                scanner.Show();
            }
        }
        private void Scanner_Closed(object sender, EventArgs e)
        {
            scanner = null;
        }
        private void Scanner_FinishBarcode(object sender, BarcodeEventArg e)
        {
            if (parent.SequenceContext == null) return;
            CellInfo cellInfo = codeParser.GetCellInfoByCode(e.positionCode);
            string paramName = $"StationState.StartScanner_{title}";
            parent.engine.Globals.SetValBoolean(paramName, 0, true);
            string positionCode = e.positionCode, snrCode = e.snrCode;
            //TODO 需要增加条码校验功能
            //将读到的条码写入到SN中
            int dutNo = Convert.ToInt16(positionCode.Split('-')[2]) - 1;
            string LookupStr = e.name == "Zone1" ? $"StationState.SN[{dutNo}]" : $"StationState.SN[{(dutNo + 54)}]";
            parent.engine.Globals.SetValString(LookupStr, 0, snrCode);
            string code = parent.engine.Globals.GetValString(LookupStr, 0);
            parent.nameTables[cellInfo.OvenName].GetSingleCell(cellInfo.Index).SetStatus("hadCode");
        }
        /// <summary>
        /// 清空两个图表数据
@@ -426,5 +473,28 @@
                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, "");
            }
        }
    }
}
Plugin.TestResult/InfoTable.xaml
@@ -31,11 +31,11 @@
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="2*"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="1.5*"/>
            <ColumnDefinition Width="2*"/>
            <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}"/>
Plugin.TestResult/Scanner.xaml
@@ -16,9 +16,10 @@
             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}" Height="300" Width="600">
        Title="{Binding title}" Width="600">
    <Window.Resources>
        <local:FontSizeConverter x:Key="FontSizeConverter"/>
        <Style TargetType="Label">
@@ -32,42 +33,47 @@
        </Style>
        <Style TargetType="Button" BasedOn="{StaticResource PrimaryButton}">
            <Setter Property="FontSize" Value="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=Window}, 
Converter={StaticResource FontSizeConverter}, ConverterParameter='100,1920'}" />
Converter={StaticResource FontSizeConverter}, ConverterParameter='20,1920'}" />
        </Style>
    </Window.Resources>
    <Grid Margin="20">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition />
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" HorizontalAlignment="Stretch" Padding="0"
        <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"
               />
        <StackPanel Grid.Row="1">
            <pu:FormGroup
                dp:FormGroupHelper.IsRequired="True"
                Header="{DynamicResource Tr.PositionSnr}">
                <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="positionText" TextWrapping="NoWrap"/>
            </pu:FormGroup>
               />-->
        <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"/>
                </pu:FormGroup>
            <pu:FormGroup
                dp:FormGroupHelper.IsRequired="True"
                Header="{DynamicResource Tr.ProductSnr}">
                <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="snrText" TextWrapping="NoWrap"/>
            </pu:FormGroup>
        </StackPanel>
                <pu:FormGroup
         Header="{DynamicResource Tr.ProductSnr}"
         dp:FormGroupHelper.IsRequired="True" Style="{StaticResource InputFormGroupStyle}">
                    <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="snrText" TextWrapping="NoWrap"/>
                </pu:FormGroup>
            </StackPanel>
        <StackPanel Style="{StaticResource ButtonGroup}"
                    dp:StackPanelHelper.ChildSpace="50"
                    HorizontalAlignment="Center" Grid.Row="2">
            <Button Click="Con_Click"
                    Content="{DynamicResource Tr.ContinueScan}"/>
            <Button Click="Ok_Click"
                    Content="{DynamicResource Tr.FinishScan}"/>
        </StackPanel>
        </GroupBox>
        <Button Grid.Row="2" Click="Con_Click" Style="{StaticResource PrimaryButton}"
        Content="{DynamicResource Tr.ContinueScan}"/>
        <Button Grid.Row="2" Grid.Column="1" Click="Ok_Click" Style="{StaticResource SuccessButton}"
        Content="{DynamicResource Tr.FinishScan}"/>
    </Grid>
</pu:WindowX>
Plugin.TestResult/SingleCell.xaml
@@ -51,20 +51,22 @@
        </Style>
    </UserControl.Style>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <!--<TextBox Grid.Row="0" IsReadOnly="True" TextAlignment="Center" Margin="0,0,0,3" FontSize="15" 
                 Background="Transparent" BorderThickness="0" Text="{Binding title}"/>-->
        <Label Grid.Row="0" HorizontalContentAlignment="Left" VerticalContentAlignment="Center" Padding="0" Margin="0,0,0,3"
        <Label Grid.Column="0" HorizontalContentAlignment="Left" VerticalContentAlignment="Top" Padding="0" Margin="0"
               FontSize="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl}, 
            Converter={StaticResource FontSizeConverter}, ConverterParameter='15,103'}" 
               FontWeight="Bold"
         Background="Transparent" BorderThickness="0" Content="{Binding title}"/>
        <Image Grid.Row="1">
        <Image Grid.Column="1">
            <Image.Style>
                <Style TargetType="Image">
                    <Setter Property="HorizontalAlignment" Value="Center"/>
                    <Setter Property="VerticalAlignment" Value="Bottom"/>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding status}" Value="ok">
                            <Setter Property="Source" Value="Resource/ok2.png"/>
Plugin.TestResult/TestResultView.xaml.cs
@@ -35,7 +35,7 @@
        /// <summary>
        /// 测试序列操作句柄
        /// </summary>
        private SequenceContext SequenceContext = null;
        public SequenceContext SequenceContext { get; private set; } = null;
        /// <summary>
        /// 测试模型序列
        /// </summary>
@@ -430,5 +430,25 @@
                table.ClearNumber();
            }
        }
        public void SetProductSource(List<string> productSource)
        {
            foreach (var table in tables)
            {
                table.SetProductSource(productSource);
            }
        }
        public void SetCurrentProduct(string currentProduct)
        {
            foreach (var table in tables)
            {
                table.SetCurrentProduct(currentProduct);
            }
        }
        public void SetOvenProduct(int index, string product) {
        }
    }
}
Plugin.TestResult/ViewControl.xaml
@@ -41,6 +41,7 @@
        <StackPanel Grid.Column="1" Background="#FFF">
            <GroupBox Header="{DynamicResource Tr.ProductInfo}">
                <ComboBox x:Name="modelSelect" Style="{StaticResource FormComboxStyle}"
                          pu:ComboBoxHelper.ClearButtonVisibility="Collapsed"
                          SelectionChanged="modelSelect_SelectionChanged"></ComboBox>
            </GroupBox>
            <GroupBox Header="{DynamicResource Tr.Status}">
@@ -103,11 +104,11 @@
                    <Button x:Name="btnClear" Click="BtnClear_Click" Style="{StaticResource PrimaryButton}"
                            Content="{DynamicResource Ts.BtnClearNum}"/>
                    <Button x:Name="btnScan1" Click="Scan_Click_1" Style="{StaticResource PrimaryButton}"
                    <!--<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}"/>
                            Content="{DynamicResource Tr.Zone2Scan}"/>-->
                </StackPanel>
            </GroupBox>
        </StackPanel>
Plugin.TestResult/ViewControl.xaml.cs
@@ -1,7 +1,7 @@
using HMI.Base;
using HMI.Core.Entity;
using HMI.Core.Manager;
using HMI.Core.Util;
using HMI.ParameterManage.Entity;
using HMI.ParameterManage.Util;
using HMI.UI;
using HMI.UI.Util;
using log4net;
@@ -11,7 +11,6 @@
using Plugin.TestResult_WPF.Util;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Windows;
@@ -157,16 +156,21 @@
        public void LoadProduct()
        {
            List<ProductData> products = ParameterManager.GetProducts<List<ProductData>>()
            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);
            if (products.Count > 0)
            {
                this.modelSelect.SelectedIndex = 0;
                testResultView.SetCurrentProduct(products[0].Name);
            }
        }
@@ -174,7 +178,7 @@
        {
            try
            {
                List<ParameterData> parameters = ParameterManager.GetParametersByProductName(this.modelSelect.SelectedItem.ToString());
                List<ParameterData> parameters = ProductManager.GetParametersByProductName(this.modelSelect.SelectedItem.ToString());
                //MessageBox.Show("ParaFile:" + ParaFile + "ParaCount:" + this.parameterManager.Parameters.Count.ToString());
                //this.TestTemperature = Convert.ToDouble(this.parameterManager.Find("TestTemperature").ParmValue);
                //this.TestTime = Convert.ToInt32(this.parameterManager.Find("TestTime").ParmValue);
Plugin.TsExecution/TsExecution.xaml.cs
@@ -1,8 +1,8 @@
using HMI.Base;
using HMI.Core;
using HMI.Core.Entity;
using HMI.Core.Manager;
using HMI.Core.Util;
using HMI.ParameterManage.Entity;
using HMI.ParameterManage.Util;
using HMI.UI;
using HMI.UI.Util;
using HMI.Util;
@@ -10,13 +10,11 @@
using NationalInstruments.TestStand.Interop.UI.Ax;
using Panuon.WPF.UI;
using ScottPlot;
using ScottPlot.AxisLimitManagers;
using ScottPlot.Plottables;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Timers;
@@ -173,7 +171,7 @@
        private void InitProducts()
        {
            Products = ParameterManager.GetProducts<ObservableCollection<ProductData>>();
            Products = ProductManager.GetProducts<ObservableCollection<ProductData>>();
            if (Products != null && Products.Count > 0)
            {
                List<ParameterData> parameters =
@@ -641,7 +639,7 @@
        private void SetTestSeqFile(ProductData product)
        {
            string name = product.Name;
            List<ParameterData> parameters = ParameterManager.GetParametersByProductName(name);
            List<ParameterData> parameters = ProductManager.GetParametersByProductName(name);
            testSeqFile = parameters.FirstOrDefault(p => p.Name == "TestSequence")?.Value?.ToString();
        }
WPFProject/App.config
@@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <configSections>
        <section name="TestResultConfig" type="System.Configuration.SingleTagSectionHandler" />
    </configSections>
    <TestResultConfig Row="1" Column="2" PortStart="6660" />
    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
    </startup>
WPFProject/App.xaml.cs
@@ -18,6 +18,7 @@
    public partial class App : Application
    {
        private ILog log;
        private LogCleanner logCleanner;
        public App()
        {
            log = LogManager.GetLogger("APP");
@@ -28,7 +29,7 @@
        {
            var ex = e.ExceptionObject as Exception;
            log.Error("UnhandledException.", ex);
            MsgBoxHelper.ErrorMessage(Current.MainWindow, this.lang("App.AppUnhandledException",ex.Message),
            MsgBoxHelper.ErrorMessage(Current.MainWindow, this.lang("App.AppUnhandledException", ex.Message),
                    this.lang("App.Error"), MessageBoxButton.OK,
                    MessageBoxIcon.Error, DefaultButton.YesOK, 5);
        }
@@ -39,8 +40,9 @@
            mutex = new Mutex(true, "TestPlatform_OnlyRun_CRNS");
            if (mutex.WaitOne(0, false))
            {
                logCleanner = new LogCleanner();
                AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
                LoggerHelper.Register(new CommonLogger());
                LangUtil.Load();
                SystemSetting.SystemSettingChanged += SystemSetting_SystemSettingChanged;
                ToastHelper.Register(new MainToastHandler());
WPFProject/Controls/MainTabControl.xaml.cs
@@ -2,6 +2,7 @@
using HMI.Core.Api;
using Microsoft.DwayneNeed.Interop;
using Panuon.WPF.UI;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
@@ -94,6 +95,7 @@
            }
        }
        private Dictionary<PluginInfo, PluginInfo> selectedCache = new Dictionary<PluginInfo, PluginInfo>();
        private ObservableCollection<PluginInfo> _SubMenuItems;
@@ -138,20 +140,27 @@
            {
                var data = (PluginInfo)e.AddedItems[0];
                SubMenuItems = data?.Children;
                if (selectedCache.ContainsKey(data))
                {
                    SelectedSubMenu = selectedCache[data];
                }
                else if (SubMenuItems.Count > 0)
                {
                    SelectedSubMenu = SubMenuItems[0];
                }
            }
        }
        private void SwithMenu(PluginInfo menu)
        {
        }
        private void SubMenu_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (e.AddedItems?.Count > 0)
            {
                var data = (PluginInfo)e.AddedItems[0];
                if (SelectedMenu != null)
                {
                    selectedCache[SelectedMenu] = data;
                }
                var content = data.Content;
                var last = page.Child;
                PluginPackage.Get(last)?.DeActive();
@@ -186,6 +195,11 @@
                UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
            };
            winFormHolder.SetBinding(WinFormHolder.MaskVisiableProperty, binding);
            if (MenuItems != null && MenuItems.Count > 0)
            {
                SelectedMenu = MenuItems[0];
            }
            Loaded -= UserControl_Loaded;
        }
    }
}
WPFProject/Lang/en-US/DOControl.json
New file
@@ -0,0 +1,11 @@
{
  "doc.title": "DO Control",
  "doc.list": "Data List",
  "doc.group": "Group",
  "doc.name": "Name",
  "doc.address": "Address",
  "doc.readOnly": "Read Only",
  "doc.pressDownValue": "Press Down Value",
  "doc.pressUpValue": "Press Up Value",
  "doc.controlType": "Control Type"
}
WPFProject/Lang/en-US/LogViewer.json
New file
@@ -0,0 +1,10 @@
{
  "log.title": "Log Viewer",
  "log.level": "Level",
  "log.time": "Time",
  "log.source": "Source",
  "log.text": "Content",
  "log.autoRefresh": "Auto Refresh",
  "log.timeCantBeNull": "Please select start time or end time",
  "log.timeRangeError": "End time must after start time",
}
WPFProject/Lang/en-US/TestResult.json
@@ -21,9 +21,11 @@
  "Tr.GetGunData": "Receive scanner failed",
  "Tr.PleaseInputSnrs": "Please scan serials",
  "Tr.ProductInfo": "Product info",
  "Tr.ProductInfo": "Select Product",
  "Tr.MES_CLOSE": "MES is closed",
  "Tr.MES_OPEN": "MES is open",
  "Tr.Scan": "Scan SNR",
  "Tr.ScanTitle": "Zone {0} Scan SNR",
  "Tr.Zone1Scan": "Scan to zone1",
  "Tr.Zone2Scan": "Scan to zone2"
}
WPFProject/Lang/en-US/app.json
@@ -123,6 +123,7 @@
  "HMI.Menu.ParentName": "Previous menu",
  "HMI.Menu.DllPath": "Dll path",
  "HMI.Menu.ClassName": "Class name",
  "HMI.Menu.Sort": "Sort Num",
  "HMI.User": "User",
  "HMI.User.Title": "User list",
WPFProject/Lang/en-US/setting.json
@@ -2,6 +2,7 @@
  "Setting.Logo": "Logo",
  "Setting.Title": "Title",
  "Setting.Lang": "Language",
  "Setting.LogExpiredDays": "Log Expired",
  "Setting.PlcIp": "PLC IP",
  "Setting.PlcEnabled": "Enabled",
  "Setting.BasicSetting": "Basic setting",
WPFProject/Lang/zh-CN/DOControl.json
New file
@@ -0,0 +1,11 @@
{
  "doc.title": "DO控制",
  "doc.list": "数据列表",
  "doc.group": "组",
  "doc.name": "名称",
  "doc.address": "地址",
  "doc.readOnly": "只读",
  "doc.pressDownValue": "按下值",
  "doc.pressUpValue": "弹起值",
  "doc.controlType": "控件类型"
}
WPFProject/Lang/zh-CN/LogViewer.json
New file
@@ -0,0 +1,10 @@
{
  "log.title": "日志查询",
  "log.level": "级别",
  "log.time": "时间",
  "log.source": "来源",
  "log.text": "内容",
  "log.autoRefresh": "自动刷新",
  "log.timeCantBeNull": "请选择开始时间或结束时间",
  "log.timeRangeError": "结束时间不能小于开始时间",
}
WPFProject/Lang/zh-CN/TestResult.json
@@ -24,6 +24,8 @@
  "Tr.ProductInfo": "产品信息",
  "Tr.MES_CLOSE": "MES关",
  "Tr.MES_OPEN": "MES开",
  "Tr.Scan": "扫码",
  "Tr.ScanTitle": "{0}区扫码",
  "Tr.Zone1Scan": "一区扫码",
  "Tr.Zone2Scan": "二区扫码"
}
WPFProject/Lang/zh-CN/app.json
@@ -123,6 +123,7 @@
  "HMI.Menu.ParentName": "上级菜单",
  "HMI.Menu.DllPath": "Dll路径",
  "HMI.Menu.ClassName": "类名",
  "HMI.Menu.Sort": "排序",
  "HMI.User": "用户",
  "HMI.User.Title": "用户列表",
WPFProject/Lang/zh-CN/setting.json
@@ -2,6 +2,7 @@
  "Setting.Logo": "Logo",
  "Setting.Title": "标题",
  "Setting.Lang": "语言",
  "Setting.LogExpiredDays": "日志保存时间",
  "Setting.PlcIp": "PLC IP",
  "Setting.PlcEnabled": "是否启用",
  "Setting.BasicSetting": "基础设置",
WPFProject/Models/Program.cs
@@ -7,7 +7,7 @@
namespace WPFProject.Models
{
    public class Program : NotifyBase, IProgram
    public class Program : NotifyBase
    {
        /// <summary>
        /// 程序名
@@ -53,9 +53,9 @@
        /// <summary>
        /// 计算临时参数
        /// </summary>
        private Dictionary<string, Param> _TempVars = new Dictionary<string, Param>();
        private Dictionary<string, object> _TempVars = new Dictionary<string, object>();
        [XmlIgnore]
        public Dictionary<string, Param> TempVars
        public Dictionary<string, object> TempVars
        {
            get { return _TempVars; }
            set { _TempVars = value; }
WPFProject/Models/Step.cs
@@ -8,7 +8,7 @@
{
    public class Step : NotifyBase
    {
        private static readonly Type InterfaceType = typeof(IStepRunner);
        //private static readonly Type InterfaceType = typeof(IStepRunner);
        private static readonly Dictionary<string, Assembly> Cache = new Dictionary<string, Assembly>();
        #region 字段
        [XmlIgnore]
@@ -63,13 +63,13 @@
        public void RunStep()
        {
            Assembly assembly = Assembly.LoadFrom(DllPath);
            Type type = assembly.GetType(ClassName);
            if (type != null && InterfaceType.IsAssignableFrom(type))
            {
                IStepRunner stepRunner = (IStepRunner)assembly.CreateInstance(ClassName);
                stepRunner.RunStep(Sequence.Program.TempVars);
            }
            //Assembly assembly = Assembly.LoadFrom(DllPath);
            //Type type = assembly.GetType(ClassName);
            //if (type != null && InterfaceType.IsAssignableFrom(type))
            //{
            //    IStepRunner stepRunner = (IStepRunner)assembly.CreateInstance(ClassName);
            //    stepRunner.RunStep(Sequence.Program.TempVars);
            //}
        }
    }
}
WPFProject/PanuonWindow.xaml
@@ -111,7 +111,7 @@
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <ctl:MainTabControl Grid.Row="0" x:Name="menuTab">
@@ -121,6 +121,6 @@
        <!--<ctl:MenuTabControl Grid.Row="1" x:Name="menuTab" OnTabChange="menuTab_OnTabChange"> </ctl:MenuTabControl>-->
        <StatusBar Grid.Row="1" Background="{StaticResource HoriBar}"></StatusBar>
        <!--<StatusBar Grid.Row="1" Background="{StaticResource HoriBar}"></StatusBar>-->
    </Grid>
</pu:WindowX>
WPFProject/TextFile1.txt
@@ -12,6 +12,7 @@
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.ServoManage.dll" $(TargeDir)Plugin\Plugin.ServoManage.dll"
move /y "$(TargeDir)Plugin.DOControl.dll" $(TargeDir)Plugin\Plugin.DOControl.dll"
setlocal enabledelayedexpansion
WPFProject/Utils/CommonLogger.cs
New file
@@ -0,0 +1,103 @@
using HMI.Core.Api;
using HMI.Core.Util;
using log4net;
using System;
using System.IO;
namespace WPFProject.Utils
{
    class CommonLogger : ILogWriter
    {
        private static ILog log = LogManager.GetLogger("CommonLogger");
        private SqliteHelper sqliteHelper;
        private DateTime currentDate;
        public CommonLogger()
        {
            RefreshConnection();
        }
        private SqliteHelper InitHelper(DateTime date)
        {
            string file = Path.Combine(PathHelper.LogViewerDir, $"log_{date.ToString("yyyy-MM-dd")}.db");
            if (!File.Exists(file))
            {
                SqliteHelper.CreateDatabase(file);
            }
            var sqliteHelper = new SqliteHelper(file);
            sqliteHelper.ExecuteSql(@"CREATE TABLE IF NOT EXISTS log (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    level TEXT NOT NULL,
                    text TEXT NOT NULL,
                    source TEXT,
                    time TIME
                    );");
            return sqliteHelper;
        }
        private void RefreshConnection()
        {
            if (sqliteHelper == null)
            {
                currentDate = DateTime.Today;
                sqliteHelper = InitHelper(currentDate);
                return;
            }
            else
            {
                lock (this)
                {
                    try
                    {
                        var current = DateTime.Today;
                        if (current == currentDate) return;
                        sqliteHelper?.Close();
                        sqliteHelper = null;
                        currentDate = current;
                        sqliteHelper = InitHelper(currentDate);
                    }
                    catch (Exception ex)
                    {
                        log.Error("RefreshConnection Failed.", ex);
                        sqliteHelper = null;
                    }
                }
            }
        }
        public void Debug(string source, string content)
        {
            DoLog("Debug", source, content);
        }
        public void Info(string source, string content)
        {
            DoLog("Info", source, content);
        }
        public void Warn(string source, string content)
        {
            DoLog("Warn", source, content);
        }
        public void Error(string source, string content)
        {
            DoLog("Error", source, content);
        }
        private void DoLog(string level, string source, string content)
        {
            if (currentDate != DateTime.Today || sqliteHelper == null)
            {
                RefreshConnection();
            }
            if (sqliteHelper == null)
            {
                log.Warn($"Level:{level} Source:{source} {content}");
            }
            else
            {
                sqliteHelper.ExecuteSql($"INSERT INTO log (level, text, source, time) VALUES ('{level}', '{content}', '{source}', '{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}');");
            }
        }
    }
}
WPFProject/Utils/LogCleanner.cs
New file
@@ -0,0 +1,109 @@
using HMI.Core.Setting;
using HMI.Core.Util;
using log4net;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Timers;
namespace WPFProject.Utils
{
    public class LogCleanner
    {
        private ILog log = LogManager.GetLogger("LogCleanner");
        private long ms = 24 * 60 * 60 * 1000;
        private string[] _fileExtensions = new string[] { ".log", ".db" };
        private string _filePrefix = "log";
        private Timer timer;
        public LogCleanner()
        {
            timer = new Timer();
            timer.Interval = ms;
            timer.Elapsed += Timer_Elapsed;
            timer.Enabled = true;
            Task.Run(() => {
                Clean();
            });
        }
        private void Clean()
        {
            int days = SystemSetting.Instance.LogExpiredDays;
            if (days == null || days <= 0) return;
            // 计算截止日期
            DateTime cutoffDate = DateTime.Now.AddDays(-days);
            string folder1 = PathHelper.LogViewerDir;
            // 检查目录是否存在
            if (Directory.Exists(folder1))
            {
                // 获取所有符合条件的文件
                var files = Directory.GetFiles(folder1)
                    .Where(file =>
                        Path.GetFileName(file).StartsWith(_filePrefix, StringComparison.OrdinalIgnoreCase) &&
                        _fileExtensions.Any(ext => file.EndsWith(ext, StringComparison.OrdinalIgnoreCase)) &&
                        File.GetLastWriteTime(file) < cutoffDate)
                    .ToList();
                foreach (var file in files)
                {
                    try
                    {
                        File.Delete(file);
                    }
                    catch (Exception ex)
                    {
                        log.Warn($"LogFile:{file} Delete Failed.", ex);
                    }
                }
            }
            folder1 = Path.Combine(PathHelper.AppFolder, "Log");
            // 检查目录是否存在
            if (Directory.Exists(folder1))
            {
                // 获取所有符合条件的文件
                var files = Directory.GetFiles(folder1)
                    .Where(file =>
                        Path.GetFileName(file).StartsWith(_filePrefix, StringComparison.OrdinalIgnoreCase) &&
                        _fileExtensions.Any(ext => file.EndsWith(ext, StringComparison.OrdinalIgnoreCase)) &&
                        File.GetLastWriteTime(file) < cutoffDate)
                    .ToList();
                foreach (var file in files)
                {
                    try
                    {
                        File.Delete(file);
                    }
                    catch (Exception ex)
                    {
                        log.Warn($"LogFile:{file} Delete Failed.", ex);
                    }
                }
            }
        }
        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            try
            {
                timer.Enabled = false;
                Clean();
                log.Info("Clean Log File Success.");
            }
            catch (System.Exception ex)
            {
                log.Error("Clean Log File Failed.", ex);
            }
            finally
            {
                timer.Enabled = true;
            }
        }
    }
}
WPFProject/WPFProject.csproj
@@ -273,9 +273,11 @@
    </Compile>
    <Compile Include="Utils\BinUtil.cs" />
    <Compile Include="Utils\CanvasHelper.cs" />
    <Compile Include="Utils\CommonLogger.cs" />
    <Compile Include="Utils\DdDragHandler.cs" />
    <Compile Include="Utils\DdDropHandler.cs" />
    <Compile Include="Utils\Encryptor.cs" />
    <Compile Include="Utils\LogCleanner.cs" />
    <Compile Include="Utils\MainToastHandler.cs" />
    <Compile Include="Utils\Resize.cs" />
    <Compile Include="Utils\Win32Helper.cs" />
@@ -497,6 +499,18 @@
    <Content Include="Lang\en-US\ServoManage.json">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
    <Content Include="Lang\zh-CN\DOControl.json">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
    <Content Include="Lang\zh-CN\LogViewer.json">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
    <Content Include="Lang\en-US\DOControl.json">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
    <Content Include="Lang\en-US\LogViewer.json">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
    <None Include="packages.config" />
    <None Include="Properties\app.manifest" />
    <None Include="Properties\Settings.settings">
@@ -548,6 +562,10 @@
      <Project>{de4205c6-5923-46fd-a4b9-65169bf61c75}</Project>
      <Name>HMI.MenuManage</Name>
    </ProjectReference>
    <ProjectReference Include="..\Plugin.DOControl\Plugin.DOControl.csproj">
      <Project>{306a83dc-a7db-4057-baa7-477d8756f5d9}</Project>
      <Name>Plugin.DOControl</Name>
    </ProjectReference>
    <ProjectReference Include="..\Plugin.ServoManage\Plugin.ServoManage.csproj">
      <Project>{1853e00f-6865-4bc6-974b-6a5e1c00bafe}</Project>
      <Name>Plugin.ServoManage</Name>
@@ -594,6 +612,7 @@
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.ServoManage.dll" $(TargeDir)Plugin\Plugin.ServoManage.dll"
move /y "$(TargeDir)Plugin.DOControl.dll" $(TargeDir)Plugin\Plugin.DOControl.dll"
setlocal enabledelayedexpansion