添加项目文件。
This commit is contained in:
116
ADP.sln
Normal file
116
ADP.sln
Normal file
@@ -0,0 +1,116 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.12.35527.113 d17.12
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ADP", "ADP\ADP.csproj", "{8B48B0CD-55F9-4623-9A10-BFE25B21EBD6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Common\Common.csproj", "{520D7017-1A2F-427D-B8A4-54E3D0575EFD}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Model", "Model\Model.csproj", "{42623437-0ADD-4149-B33F-DB30E7B13D69}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Service", "Service\Service.csproj", "{F9A522A1-869E-462E-9782-57ED3DE5566E}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ORM", "ORM\ORM.csproj", "{19ED286B-4E83-4EA0-9BD4-37D2AB796F22}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logger", "Logger\Logger.csproj", "{84F26384-D1DC-4175-98A3-A64A4FB961D2}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceCommand", "DeviceCommand\DeviceCommand.csproj", "{1F4E6A3D-B273-4F76-8D59-37F7C8E858BB}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Command", "Command\Command.csproj", "{FC353140-A722-4E8E-97BA-FB866E8188A9}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UIShare", "UIShare\UIShare.csproj", "{53198B54-BDF9-446D-B1FD-EC0A52D794E5}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{1E92D601-68FC-45F6-8251-BD0F39226E5B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoginModule", "LoginModule\LoginModule.csproj", "{DF1DE1F7-B570-41D7-A2B5-AB2CDCD4BD72}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SettingModule", "SettingModule\SettingModule.csproj", "{51384EB4-1940-44FB-8E18-56F7201642CD}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestingModule", "TestingModule\TestingModule.csproj", "{BD74E7F3-BBB5-4375-A37E-F24E8507A436}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MainModule", "MainModule\MainModule.csproj", "{DFC084AE-FE45-4492-A2DA-EAE2941F26E9}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UpdateInfoMoudle", "UpdateInfoMoudle\UpdateInfoMoudle.csproj", "{3456C3AC-E7FF-45F8-A68F-BEED9969812C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonitorModule", "MonitorModule\MonitorModule.csproj", "{850A5BE8-0BC1-4D6A-8351-DD812C9C4427}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{8B48B0CD-55F9-4623-9A10-BFE25B21EBD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8B48B0CD-55F9-4623-9A10-BFE25B21EBD6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8B48B0CD-55F9-4623-9A10-BFE25B21EBD6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8B48B0CD-55F9-4623-9A10-BFE25B21EBD6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{520D7017-1A2F-427D-B8A4-54E3D0575EFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{520D7017-1A2F-427D-B8A4-54E3D0575EFD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{520D7017-1A2F-427D-B8A4-54E3D0575EFD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{520D7017-1A2F-427D-B8A4-54E3D0575EFD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{42623437-0ADD-4149-B33F-DB30E7B13D69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{42623437-0ADD-4149-B33F-DB30E7B13D69}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{42623437-0ADD-4149-B33F-DB30E7B13D69}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{42623437-0ADD-4149-B33F-DB30E7B13D69}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F9A522A1-869E-462E-9782-57ED3DE5566E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F9A522A1-869E-462E-9782-57ED3DE5566E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F9A522A1-869E-462E-9782-57ED3DE5566E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F9A522A1-869E-462E-9782-57ED3DE5566E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{19ED286B-4E83-4EA0-9BD4-37D2AB796F22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{19ED286B-4E83-4EA0-9BD4-37D2AB796F22}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{19ED286B-4E83-4EA0-9BD4-37D2AB796F22}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{19ED286B-4E83-4EA0-9BD4-37D2AB796F22}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{84F26384-D1DC-4175-98A3-A64A4FB961D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{84F26384-D1DC-4175-98A3-A64A4FB961D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{84F26384-D1DC-4175-98A3-A64A4FB961D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{84F26384-D1DC-4175-98A3-A64A4FB961D2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1F4E6A3D-B273-4F76-8D59-37F7C8E858BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1F4E6A3D-B273-4F76-8D59-37F7C8E858BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1F4E6A3D-B273-4F76-8D59-37F7C8E858BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1F4E6A3D-B273-4F76-8D59-37F7C8E858BB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FC353140-A722-4E8E-97BA-FB866E8188A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FC353140-A722-4E8E-97BA-FB866E8188A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FC353140-A722-4E8E-97BA-FB866E8188A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FC353140-A722-4E8E-97BA-FB866E8188A9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{53198B54-BDF9-446D-B1FD-EC0A52D794E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{53198B54-BDF9-446D-B1FD-EC0A52D794E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{53198B54-BDF9-446D-B1FD-EC0A52D794E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{53198B54-BDF9-446D-B1FD-EC0A52D794E5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DF1DE1F7-B570-41D7-A2B5-AB2CDCD4BD72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DF1DE1F7-B570-41D7-A2B5-AB2CDCD4BD72}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DF1DE1F7-B570-41D7-A2B5-AB2CDCD4BD72}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DF1DE1F7-B570-41D7-A2B5-AB2CDCD4BD72}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{51384EB4-1940-44FB-8E18-56F7201642CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{51384EB4-1940-44FB-8E18-56F7201642CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{51384EB4-1940-44FB-8E18-56F7201642CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{51384EB4-1940-44FB-8E18-56F7201642CD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BD74E7F3-BBB5-4375-A37E-F24E8507A436}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BD74E7F3-BBB5-4375-A37E-F24E8507A436}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BD74E7F3-BBB5-4375-A37E-F24E8507A436}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BD74E7F3-BBB5-4375-A37E-F24E8507A436}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DFC084AE-FE45-4492-A2DA-EAE2941F26E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DFC084AE-FE45-4492-A2DA-EAE2941F26E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DFC084AE-FE45-4492-A2DA-EAE2941F26E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DFC084AE-FE45-4492-A2DA-EAE2941F26E9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3456C3AC-E7FF-45F8-A68F-BEED9969812C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3456C3AC-E7FF-45F8-A68F-BEED9969812C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3456C3AC-E7FF-45F8-A68F-BEED9969812C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3456C3AC-E7FF-45F8-A68F-BEED9969812C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{850A5BE8-0BC1-4D6A-8351-DD812C9C4427}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{850A5BE8-0BC1-4D6A-8351-DD812C9C4427}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{850A5BE8-0BC1-4D6A-8351-DD812C9C4427}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{850A5BE8-0BC1-4D6A-8351-DD812C9C4427}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{DF1DE1F7-B570-41D7-A2B5-AB2CDCD4BD72} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
||||
{51384EB4-1940-44FB-8E18-56F7201642CD} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
||||
{BD74E7F3-BBB5-4375-A37E-F24E8507A436} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
||||
{DFC084AE-FE45-4492-A2DA-EAE2941F26E9} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
||||
{3456C3AC-E7FF-45F8-A68F-BEED9969812C} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
||||
{850A5BE8-0BC1-4D6A-8351-DD812C9C4427} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
24
ADP/ADP.csproj
Normal file
24
ADP/ADP.csproj
Normal file
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Command\Command.csproj" />
|
||||
<ProjectReference Include="..\DeviceCommand\DeviceCommand.csproj" />
|
||||
<ProjectReference Include="..\LoginModule\LoginModule.csproj" />
|
||||
<ProjectReference Include="..\MainModule\MainModule.csproj" />
|
||||
<ProjectReference Include="..\MonitorModule\MonitorModule.csproj" />
|
||||
<ProjectReference Include="..\Service\Service.csproj" />
|
||||
<ProjectReference Include="..\SettingModule\SettingModule.csproj" />
|
||||
<ProjectReference Include="..\TestingModule\TestingModule.csproj" />
|
||||
<ProjectReference Include="..\UIShare\UIShare.csproj" />
|
||||
<ProjectReference Include="..\UpdateInfoMoudle\UpdateInfoMoudle.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
15
ADP/App.xaml
Normal file
15
ADP/App.xaml
Normal file
@@ -0,0 +1,15 @@
|
||||
<prism:PrismApplication x:Class="ADP.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:ADP"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:prism="http://prismlibrary.com/">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<!--自定义style-->
|
||||
<ResourceDictionary Source="/UIShare;component/Styles/CommonStyle.xaml"></ResourceDictionary>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</prism:PrismApplication>
|
||||
88
ADP/App.xaml.cs
Normal file
88
ADP/App.xaml.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using Common;
|
||||
using ADP.ViewModels;
|
||||
using ADP.ViewModels.Dialogs;
|
||||
using ADP.Views;
|
||||
using ADP.Views;
|
||||
using ADP.Views.Dialogs;
|
||||
using Logger;
|
||||
using Notifications.Wpf.Core;
|
||||
using ORM;
|
||||
using Service.Implement;
|
||||
using Service.Interface;
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
using UIShare.PubEvent;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
using UIShare.GlobalVariable;
|
||||
using UIShare;
|
||||
|
||||
namespace ADP
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : PrismApplication
|
||||
{
|
||||
protected override Window CreateShell()
|
||||
{
|
||||
//UI线程未捕获异常处理事件
|
||||
this.DispatcherUnhandledException += OnDispatcherUnhandledException;
|
||||
//Task线程内未捕获异常处理事件
|
||||
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
|
||||
////多线程异常
|
||||
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
||||
return Container.Resolve<ShellView>();
|
||||
}
|
||||
private void OnDispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
|
||||
{
|
||||
LoggerHelper.Error(e.Exception.Message, e.Exception.StackTrace);
|
||||
}
|
||||
|
||||
private void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
|
||||
{
|
||||
LoggerHelper.Error(e.Exception.Message, e.Exception.StackTrace);
|
||||
}
|
||||
|
||||
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
//记录dump文件
|
||||
Exception ex = e.ExceptionObject as Exception;
|
||||
MiniDump.TryDump($"dumps\\Error_{DateTime.Now:yyyy-MM-dd HH-mm-ss-ms}.dmp", MiniDump.Option.WithFullMemory, ex);
|
||||
}
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
//初始化数据库
|
||||
//DatabaseConfig.SetTenant(10001);
|
||||
//DatabaseConfig.InitMySql("127.0.0.1",3306,"ADP","root","123456");
|
||||
//DatabaseConfig.CreateDatabaseAndCheckConnection(createDatabase: true, checkConnection: true);
|
||||
//SqlSugarContext.InitDatabase();
|
||||
//显示登录窗口
|
||||
var login = Container.Resolve<LoginModuleView>();
|
||||
var re = Container.Resolve<IRegionManager>();
|
||||
RegionManager.SetRegionManager(login, re);
|
||||
RegionManager.SetRegionManager(Application.Current.MainWindow, re);
|
||||
login.Show();
|
||||
}
|
||||
protected override void RegisterTypes(IContainerRegistry containerRegistry)
|
||||
{
|
||||
//注册弹窗
|
||||
containerRegistry.RegisterDialog<MessageBoxView, MessageBoxViewModel>("MessageBox");
|
||||
// 注册通知管理器
|
||||
INotificationManager NotificationManager = new NotificationManager();
|
||||
containerRegistry.RegisterInstance<INotificationManager>(NotificationManager);
|
||||
//注册全局变量
|
||||
containerRegistry.RegisterScoped<SystemConfig>();
|
||||
containerRegistry.RegisterScoped<StepRunning>();
|
||||
containerRegistry.RegisterScoped<ScopedContext>();
|
||||
containerRegistry.RegisterSingleton<GlobalInfo>();
|
||||
}
|
||||
//指定模块加载方式(需要手动将模块生成的dll放入Modules文件夹中)
|
||||
protected override IModuleCatalog CreateModuleCatalog()
|
||||
{
|
||||
//指定模块加载方式为从文件夹中以反射发现并加载module(推荐用法)
|
||||
return new DirectoryModuleCatalog() { ModulePath = @".\Modules" };
|
||||
}
|
||||
}
|
||||
}
|
||||
10
ADP/AssemblyInfo.cs
Normal file
10
ADP/AssemblyInfo.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
BIN
ADP/Resources/Images/error.png
Normal file
BIN
ADP/Resources/Images/error.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.0 KiB |
BIN
ADP/Resources/Images/info.png
Normal file
BIN
ADP/Resources/Images/info.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.4 KiB |
BIN
ADP/Resources/Images/warning.png
Normal file
BIN
ADP/Resources/Images/warning.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
120
ADP/ViewModels/Dialogs/MessageBoxViewModel.cs
Normal file
120
ADP/ViewModels/Dialogs/MessageBoxViewModel.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
using UIShare.PubEvent;
|
||||
using UIShare.ViewModelBase;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace ADP.ViewModels.Dialogs
|
||||
{
|
||||
public class MessageBoxViewModel : DialogViewModelBase
|
||||
{
|
||||
#region 属性
|
||||
|
||||
private string _Title;
|
||||
public string Title
|
||||
{
|
||||
get => _Title;
|
||||
set => SetProperty(ref _Title, value);
|
||||
}
|
||||
|
||||
private string _Message = "";
|
||||
public string Message
|
||||
{
|
||||
get => _Message;
|
||||
set => SetProperty(ref _Message, value);
|
||||
}
|
||||
|
||||
private string _Icon= $"pack://siteoforigin:,,,/Resources/Images/info.png";
|
||||
public string Icon
|
||||
{
|
||||
get => _Icon;
|
||||
set => SetProperty(ref _Icon, value);
|
||||
}
|
||||
|
||||
private bool _ShowYes;
|
||||
public bool ShowYes
|
||||
{
|
||||
get => _ShowYes;
|
||||
set => SetProperty(ref _ShowYes, value);
|
||||
}
|
||||
|
||||
private bool _ShowNo;
|
||||
public bool ShowNo
|
||||
{
|
||||
get => _ShowNo;
|
||||
set => SetProperty(ref _ShowNo, value);
|
||||
}
|
||||
|
||||
private bool _ShowOk;
|
||||
public bool ShowOk
|
||||
{
|
||||
get => _ShowOk;
|
||||
set => SetProperty(ref _ShowOk, value);
|
||||
}
|
||||
|
||||
private bool _ShowCancel;
|
||||
public bool ShowCancel
|
||||
{
|
||||
get => _ShowCancel;
|
||||
set => SetProperty(ref _ShowCancel, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 命令
|
||||
public ICommand YesCommand { get; set; }
|
||||
public ICommand NoCommand { get; set; }
|
||||
public ICommand OkCommand { get; set; }
|
||||
public ICommand CancelCommand { get; set; }
|
||||
#endregion
|
||||
|
||||
public DialogCloseListener RequestClose { get; set; }
|
||||
|
||||
public MessageBoxViewModel(IContainerProvider containerProvider):base(containerProvider)
|
||||
{
|
||||
YesCommand = new DelegateCommand(OnYes);
|
||||
NoCommand = new DelegateCommand(OnNo);
|
||||
OkCommand = new DelegateCommand(OnOk);
|
||||
CancelCommand = new DelegateCommand(OnCancel);
|
||||
}
|
||||
|
||||
private void CloseDialog(ButtonResult result)
|
||||
{
|
||||
var parameters = new DialogParameters();
|
||||
RequestClose.Invoke(new DialogResult(result));
|
||||
}
|
||||
|
||||
private void OnYes() => CloseDialog(ButtonResult.Yes);
|
||||
private void OnNo() => CloseDialog(ButtonResult.No);
|
||||
private void OnOk() => CloseDialog(ButtonResult.OK);
|
||||
private void OnCancel() => CloseDialog(ButtonResult.Cancel);
|
||||
|
||||
#region Prism Dialog 规范
|
||||
public bool CanCloseDialog() => true;
|
||||
|
||||
public override void OnDialogClosed()
|
||||
{
|
||||
_eventAggregator.GetEvent<OverlayEvent>().Publish(false);
|
||||
}
|
||||
|
||||
public override void OnDialogOpened(IDialogParameters parameters)
|
||||
{
|
||||
_eventAggregator.GetEvent<OverlayEvent>().Publish(true);
|
||||
Title = parameters.GetValue<string>("Title");
|
||||
Message = parameters.GetValue<string>("Message");
|
||||
var iconKey = parameters.GetValue<string>("Icon"); // info / error / warn
|
||||
Icon = iconKey switch
|
||||
{
|
||||
"info" => $"pack://siteoforigin:,,,/Resources/Images/info.png",
|
||||
"error" => $"pack://siteoforigin:,,,/Resources/Images/error.png",
|
||||
"warn" => $"pack://siteoforigin:,,,/Resources/Images/warning.png",
|
||||
_ => $"pack://siteoforigin:,,,/Resources/Images/info.png" // 默认
|
||||
};
|
||||
|
||||
|
||||
ShowYes = parameters.GetValue<bool>("ShowYes");
|
||||
ShowNo = parameters.GetValue<bool>("ShowNo");
|
||||
ShowOk = parameters.GetValue<bool>("ShowOk");
|
||||
ShowCancel = parameters.GetValue<bool>("ShowCancel");
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
772
ADP/ViewModels/ShellViewModel.cs
Normal file
772
ADP/ViewModels/ShellViewModel.cs
Normal file
@@ -0,0 +1,772 @@
|
||||
|
||||
using Logger;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using Notifications.Wpf.Core;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading; // 💡 新增:引入定时器命名空间
|
||||
using UIShare;
|
||||
using UIShare.GlobalVariable;
|
||||
using UIShare.PubEvent;
|
||||
using UIShare.UIViewModel;
|
||||
|
||||
namespace ADP.ViewModels
|
||||
{
|
||||
public class ShellViewModel : BindableBase
|
||||
{
|
||||
#region 私有字段
|
||||
private string _Title = "";
|
||||
private bool _IsLeftDrawerOpen;
|
||||
|
||||
private readonly ConcurrentDictionary<string, Task> _executionTasks = new();
|
||||
private readonly ConcurrentDictionary<string, Task> _errorExecutionTasks = new();
|
||||
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IRegionManager _regionManager;
|
||||
private readonly IContainerProvider _containerProvider;
|
||||
private readonly INotificationManager _notificationManager;
|
||||
private readonly IModuleManager _moduleManager;
|
||||
private readonly GlobalInfo _globalInfo;
|
||||
|
||||
// 💡 新增:UI 刷新定时器,用于高频让前端重新拉取当前工位的 SW 累计时间
|
||||
private readonly DispatcherTimer _uiRefreshTimer;
|
||||
#endregion
|
||||
|
||||
#region 属性
|
||||
public string Title
|
||||
{
|
||||
get => _Title;
|
||||
set => SetProperty(ref _Title, value);
|
||||
}
|
||||
public bool IsLeftDrawerOpen
|
||||
{
|
||||
get => _IsLeftDrawerOpen;
|
||||
set => SetProperty(ref _IsLeftDrawerOpen, value);
|
||||
}
|
||||
|
||||
// 代理属性:动态反映当前被激活的 Scope 状态
|
||||
public TimeSpan RunningTime
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CurrentContext == null) return TimeSpan.Zero;
|
||||
// 💡 核心:如果当前工位的计时器正在跑,动态累加 SW 当前的时间,否则返回其保存的固定时间值
|
||||
return CurrentContext.SW.IsRunning ? CurrentContext.SW.Elapsed : CurrentContext.RunningTime;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (CurrentContext != null && CurrentContext.RunningTime != value)
|
||||
{
|
||||
CurrentContext.RunningTime = value;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsTerminate
|
||||
{
|
||||
get => CurrentContext?.IsTerminate ?? false;
|
||||
set
|
||||
{
|
||||
if (CurrentContext != null && CurrentContext.IsTerminate != value)
|
||||
{
|
||||
CurrentContext.IsTerminate = value;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ScopedContext? CurrentContext =>
|
||||
_globalInfo.ContextDic.TryGetValue(_globalInfo.CurrentScope, out var ctx) ? ctx : null;
|
||||
private StepRunning? CurrentRunner =>
|
||||
_globalInfo.StepRunningDic.TryGetValue(_globalInfo.CurrentScope, out var runner) ? runner : null;
|
||||
|
||||
public string RunState
|
||||
{
|
||||
get => CurrentContext?.RunState ?? "运行";
|
||||
set
|
||||
{
|
||||
if (CurrentContext != null && CurrentContext.RunState != value)
|
||||
{
|
||||
CurrentContext.RunState = value;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool SingleStep
|
||||
{
|
||||
get => CurrentContext?.SingleStep ?? false;
|
||||
set
|
||||
{
|
||||
if (CurrentContext != null && CurrentContext.SingleStep != value)
|
||||
{
|
||||
CurrentContext.SingleStep = value;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public PackIconKind RunIcon
|
||||
{
|
||||
get => CurrentContext?.RunIcon ?? PackIconKind.Play;
|
||||
set
|
||||
{
|
||||
if (CurrentContext != null && CurrentContext.RunIcon != value)
|
||||
{
|
||||
CurrentContext.RunIcon = value;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 命令
|
||||
public ICommand LeftDrawerOpenCommand { get; set; }
|
||||
public ICommand MinimizeCommand { get; set; }
|
||||
public ICommand MaximizeCommand { get; set; }
|
||||
public ICommand CloseCommand { get; set; }
|
||||
public ICommand NavigateCommand { get; set; }
|
||||
public ICommand LoadCommand { get; set; }
|
||||
public ICommand RefreshCommand { get; set; }
|
||||
public ICommand DestroyCommand { get; set; }
|
||||
public ICommand RunningCommand { get; set; }
|
||||
public ICommand RunSingleCommand { get; set; }
|
||||
public ICommand RestorationCommand { get; set; }
|
||||
public ICommand RunAbnormalStepsCommand { get; set; }
|
||||
public ICommand SaveAsCommand { get; set; }
|
||||
public ICommand SaveCommand { get; set; }
|
||||
public ICommand OpenCommand { get; set; }
|
||||
public ICommand NewCommand { get; set; }
|
||||
public ICommand SetDefaultCommand { get; set; }
|
||||
#endregion
|
||||
|
||||
public ShellViewModel(IContainerProvider containerProvider)
|
||||
{
|
||||
_containerProvider = containerProvider;
|
||||
_globalInfo = containerProvider.Resolve<GlobalInfo>();
|
||||
_eventAggregator = containerProvider.Resolve<IEventAggregator>();
|
||||
_regionManager = containerProvider.Resolve<IRegionManager>();
|
||||
_notificationManager = containerProvider.Resolve<INotificationManager>();
|
||||
_moduleManager = containerProvider.Resolve<IModuleManager>();
|
||||
|
||||
LeftDrawerOpenCommand = new DelegateCommand(LeftDrawerOpen);
|
||||
MinimizeCommand = new DelegateCommand<Window>(MinimizeWindow);
|
||||
MaximizeCommand = new DelegateCommand<Window>(MaximizeWindow);
|
||||
CloseCommand = new DelegateCommand<Window>(CloseWindow);
|
||||
NavigateCommand = new DelegateCommand<string>(Navigate);
|
||||
LoadCommand = new DelegateCommand(Load);
|
||||
RefreshCommand = new DelegateCommand(OnRefresh);
|
||||
DestroyCommand = new DelegateCommand(OnDestroy);
|
||||
RunningCommand = new DelegateCommand(OnRunning);
|
||||
RunSingleCommand = new DelegateCommand(RunSingle);
|
||||
RestorationCommand = new AsyncDelegateCommand(OnRestoration);
|
||||
RunAbnormalStepsCommand = new AsyncDelegateCommand(OnRunAbnormalSteps);
|
||||
NewCommand = new DelegateCommand(New);
|
||||
OpenCommand = new AsyncDelegateCommand<string>(Open);
|
||||
SaveAsCommand = new DelegateCommand(SaveAs);
|
||||
SaveCommand = new DelegateCommand(Save);
|
||||
SetDefaultCommand = new DelegateCommand(SetDefault);
|
||||
|
||||
_globalInfo.ContextDic.Add("default", new ScopedContext());
|
||||
|
||||
_eventAggregator.GetEvent<LoginSuccessEvent>().Subscribe(() =>
|
||||
{
|
||||
Application.Current.MainWindow.Show();
|
||||
_regionManager.RequestNavigate("ShellViewManager", "MainView");
|
||||
});
|
||||
|
||||
_eventAggregator.GetEvent<RunSingalCompletedEvent>().Subscribe(UpdateRunIcon);
|
||||
|
||||
_globalInfo.ScopeChanged += (s, e) =>
|
||||
{
|
||||
RefreshAllContextProperties();
|
||||
};
|
||||
|
||||
// 💡 初始化轻量级 UI 定时器:每 100 毫秒刷新一次当前可见工位的界面时间
|
||||
_uiRefreshTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) };
|
||||
_uiRefreshTimer.Tick += (s, e) => RaisePropertyChanged(nameof(RunningTime));
|
||||
_uiRefreshTimer.Start();
|
||||
}
|
||||
|
||||
private void RefreshAllContextProperties()
|
||||
{
|
||||
RaisePropertyChanged(nameof(RunState));
|
||||
RaisePropertyChanged(nameof(SingleStep));
|
||||
RaisePropertyChanged(nameof(RunIcon));
|
||||
RaisePropertyChanged(nameof(RunningTime));
|
||||
RaisePropertyChanged(nameof(IsTerminate));
|
||||
}
|
||||
|
||||
#region 命令处理与事件
|
||||
private async void OnRunning()
|
||||
{
|
||||
string runningScope = _globalInfo.CurrentScope;
|
||||
ScopedContext? targetContext = CurrentContext;
|
||||
StepRunning? targetRunner = CurrentRunner;
|
||||
|
||||
if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null || targetRunner == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetContext.RunState == "运行")
|
||||
{
|
||||
targetContext.SingleStep = false;
|
||||
LoggerHelper.InfoWithNotify($"{_globalInfo.UserName} 执行工位 [{runningScope}] 运行命令");
|
||||
|
||||
targetContext.RunState = "暂停";
|
||||
targetContext.RunIcon = PackIconKind.Pause;
|
||||
|
||||
if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
|
||||
|
||||
if (targetContext.IsStop == null)
|
||||
{
|
||||
targetContext.IsStop = false;
|
||||
|
||||
// 💡 启动或恢复:启动属于该快照工位独立的 SW 计时器
|
||||
targetContext.SW.Start();
|
||||
|
||||
if (!_executionTasks.TryGetValue(runningScope, out var existingTask) || existingTask == null)
|
||||
{
|
||||
existingTask = targetRunner.ExecuteSteps(targetContext.Program, cancellationToken: targetRunner.stepCTS.Token);
|
||||
_executionTasks[runningScope] = existingTask;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await existingTask;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.ErrorWithNotify($"工位 [{runningScope}] 异常中止: {ex.Message}");
|
||||
}
|
||||
|
||||
// 💡 测试正常完毕或取消:停止当前工位的 SW,并将最终时间固化到 RunningTime 字段中
|
||||
targetContext.SW.Stop();
|
||||
targetContext.RunningTime = targetContext.SW.Elapsed;
|
||||
|
||||
targetContext.RunState = "运行";
|
||||
targetContext.RunIcon = PackIconKind.Play;
|
||||
targetContext.IsStop = null;
|
||||
|
||||
if (targetRunner.stepCTS.IsCancellationRequested)
|
||||
{
|
||||
targetRunner.stepCTS = new CancellationTokenSource();
|
||||
_executionTasks.TryRemove(runningScope, out _);
|
||||
}
|
||||
else if (_executionTasks.TryGetValue(runningScope, out var currentTask) && currentTask != null && currentTask.IsCompleted)
|
||||
{
|
||||
targetContext.IsTerminate = true;
|
||||
_executionTasks.TryRemove(runningScope, out _);
|
||||
}
|
||||
|
||||
if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
|
||||
}
|
||||
else if (targetContext.IsStop == true)
|
||||
{
|
||||
targetContext.IsStop = false;
|
||||
// 💡 从暂停中恢复,继续累加当前工位计时
|
||||
targetContext.SW.Start();
|
||||
}
|
||||
}
|
||||
else // 用户点击了暂停
|
||||
{
|
||||
LoggerHelper.InfoWithNotify($"{_globalInfo.UserName} 点击工位 [{runningScope}] 暂停命令");
|
||||
targetContext.SingleStep = true;
|
||||
targetContext.IsStop = true;
|
||||
targetContext.RunState = "运行";
|
||||
targetContext.RunIcon = PackIconKind.Play;
|
||||
if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
|
||||
}
|
||||
}
|
||||
|
||||
private async void RunSingle()
|
||||
{
|
||||
string runningScope = _globalInfo.CurrentScope;
|
||||
ScopedContext? targetContext = CurrentContext;
|
||||
StepRunning? targetRunner = CurrentRunner;
|
||||
|
||||
if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null || targetRunner == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetContext.RunState == "运行")
|
||||
{
|
||||
targetContext.SingleStep = true;
|
||||
LoggerHelper.InfoWithNotify($"{_globalInfo.UserName} 执行工位 [{runningScope}] 单步执行命令");
|
||||
|
||||
targetContext.RunState = "暂停";
|
||||
targetContext.RunIcon = PackIconKind.Pause;
|
||||
|
||||
if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
|
||||
|
||||
if (targetContext.IsStop == null)
|
||||
{
|
||||
targetContext.IsStop = false;
|
||||
|
||||
// 💡 单步启动:让专属工位的 SW 跑起来
|
||||
targetContext.SW.Start();
|
||||
|
||||
if (!_executionTasks.TryGetValue(runningScope, out var existingTask) || existingTask == null)
|
||||
{
|
||||
existingTask = targetRunner.ExecuteSteps(targetContext.Program, cancellationToken: targetRunner.stepCTS.Token);
|
||||
_executionTasks[runningScope] = existingTask;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await existingTask;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.ErrorWithNotify($"工位 [{runningScope}] 单步执行异常: {ex.Message}");
|
||||
}
|
||||
|
||||
// 💡 单步单步完成后会被 StepRunning 挂起,在此暂时停止 SW
|
||||
targetContext.SW.Stop();
|
||||
targetContext.RunningTime = targetContext.SW.Elapsed;
|
||||
|
||||
targetContext.RunState = "运行";
|
||||
targetContext.RunIcon = PackIconKind.Play;
|
||||
targetContext.IsStop = null;
|
||||
|
||||
if (targetRunner.stepCTS.IsCancellationRequested)
|
||||
{
|
||||
targetRunner.stepCTS = new CancellationTokenSource();
|
||||
_executionTasks.TryRemove(runningScope, out _);
|
||||
}
|
||||
else if (_executionTasks.TryGetValue(runningScope, out var currentTask) && currentTask != null && currentTask.IsCompleted)
|
||||
{
|
||||
targetContext.IsTerminate = true;
|
||||
_executionTasks.TryRemove(runningScope, out _);
|
||||
}
|
||||
|
||||
if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
|
||||
}
|
||||
else if (targetContext.IsStop == true)
|
||||
{
|
||||
targetContext.IsStop = false;
|
||||
// 💡 继续单步,继续计时
|
||||
targetContext.SW.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnRestoration()
|
||||
{
|
||||
string runningScope = _globalInfo.CurrentScope;
|
||||
ScopedContext? targetContext = CurrentContext;
|
||||
StepRunning? targetRunner = CurrentRunner;
|
||||
|
||||
if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null || targetRunner == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LoggerHelper.InfoWithNotify($"{_globalInfo.UserName} 执行工位 [{runningScope}] 复位命令");
|
||||
|
||||
_executionTasks.TryGetValue(runningScope, out var currentTask);
|
||||
_errorExecutionTasks.TryGetValue(runningScope, out var errorTask);
|
||||
|
||||
if (currentTask != null || errorTask != null)
|
||||
{
|
||||
targetRunner.stepCTS.Cancel();
|
||||
targetRunner.errorStepCTS.Cancel();
|
||||
|
||||
await Task.Delay(200);
|
||||
|
||||
// 💡 彻底复位:停止当前工位的计时器,并且完全归零(Reset)
|
||||
targetContext.SW.Reset();
|
||||
targetContext.RunningTime = TimeSpan.Zero;
|
||||
|
||||
targetContext.IsStop = null;
|
||||
targetRunner.ResetAllStepStatus(targetContext.Program.StepCollection);
|
||||
targetRunner.ResetAllStepStatus(targetContext.Program.ErrorStepCollection);
|
||||
targetContext.IsTerminate = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetRunner.stepCTS = new CancellationTokenSource();
|
||||
targetRunner.errorStepCTS = new CancellationTokenSource();
|
||||
|
||||
// 💡 无任务状态下的直接归零
|
||||
targetContext.SW.Reset();
|
||||
targetContext.RunningTime = TimeSpan.Zero;
|
||||
|
||||
targetContext.IsStop = null;
|
||||
targetRunner.ResetAllStepStatus(targetContext.Program.StepCollection);
|
||||
targetRunner.ResetAllStepStatus(targetContext.Program.ErrorStepCollection);
|
||||
targetContext.IsTerminate = false;
|
||||
}
|
||||
|
||||
if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
|
||||
}
|
||||
|
||||
private async Task OnRunAbnormalSteps()
|
||||
{
|
||||
string runningScope = _globalInfo.CurrentScope;
|
||||
ScopedContext? targetContext = CurrentContext;
|
||||
StepRunning? targetRunner = CurrentRunner;
|
||||
|
||||
if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null || targetRunner == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LoggerHelper.InfoWithNotify($"{_globalInfo.UserName} 执行工位 [{runningScope}] 异常流程命令");
|
||||
|
||||
// 💡 异常测试启动计时
|
||||
targetContext.SW.Start();
|
||||
|
||||
if (!_errorExecutionTasks.TryGetValue(runningScope, out var existingErrorTask) || existingErrorTask == null)
|
||||
{
|
||||
existingErrorTask = targetRunner.ExecuteErrorSteps(targetContext.Program, cancellationToken: targetRunner.errorStepCTS.Token);
|
||||
_errorExecutionTasks[runningScope] = existingErrorTask;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await existingErrorTask;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.ErrorWithNotify($"工位 [{runningScope}] 异常流程执行出错: {ex.Message}");
|
||||
}
|
||||
|
||||
// 💡 异常测试结束停止计时
|
||||
targetContext.SW.Stop();
|
||||
targetContext.RunningTime = targetContext.SW.Elapsed;
|
||||
|
||||
if (targetRunner.errorStepCTS.IsCancellationRequested)
|
||||
{
|
||||
targetRunner.errorStepCTS = new CancellationTokenSource();
|
||||
_errorExecutionTasks.TryRemove(runningScope, out _);
|
||||
}
|
||||
else if (_errorExecutionTasks.TryGetValue(runningScope, out var currentErrorTask) && currentErrorTask != null && currentErrorTask.IsCompleted)
|
||||
{
|
||||
targetContext.IsTerminate = true;
|
||||
_errorExecutionTasks.TryRemove(runningScope, out _);
|
||||
}
|
||||
|
||||
if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
|
||||
}
|
||||
|
||||
private void SetDefault()
|
||||
{
|
||||
// 💡 1. 抓取触发瞬间的 Scope 与 Context 快照
|
||||
string runningScope = _globalInfo.CurrentScope;
|
||||
ScopedContext? targetContext = CurrentContext;
|
||||
|
||||
if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return;
|
||||
|
||||
if (targetContext.CurrentFilePath != null)
|
||||
{
|
||||
//SystemConfig.DefaultProgramFilePath = targetContext.CurrentFilePath;
|
||||
//ConfigService.Save();
|
||||
LoggerHelper.SuccessWithNotify($"工位 [{runningScope}] 已成功将当前程序设为默认启动程序");
|
||||
}
|
||||
}
|
||||
|
||||
private void New()
|
||||
{
|
||||
// 💡 1. 抓取快照
|
||||
string runningScope = _globalInfo.CurrentScope;
|
||||
ScopedContext? targetContext = CurrentContext;
|
||||
|
||||
if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return;
|
||||
|
||||
// 💡 2. 严格对快照隔离的 Context 数据进行清理,不影响其他工位
|
||||
targetContext.CurrentFilePath = null;
|
||||
targetContext.Program.Parameters.Clear();
|
||||
targetContext.Program.StepCollection.Clear();
|
||||
targetContext.Program.ErrorStepCollection.Clear();
|
||||
|
||||
LoggerHelper.InfoWithNotify($"工位 [{runningScope}] 创建了空程序文件");
|
||||
|
||||
// 如果当前正看着该工位,刷新 UI 显示
|
||||
if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
|
||||
}
|
||||
|
||||
private async Task Open(string filePath = null)
|
||||
{
|
||||
// 💡 1. 抓取进方法瞬间的快照,锁死上下文
|
||||
string runningScope = _globalInfo.CurrentScope;
|
||||
ScopedContext? targetContext = CurrentContext;
|
||||
|
||||
if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
// 如果没有传路径,弹出文件选择对话框(WPF 对话框是模态的,会阻塞当前 UI 线程)
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
{
|
||||
var openFileDialog = new Microsoft.Win32.OpenFileDialog
|
||||
{
|
||||
Filter = "ADP程序文件|*.ADP|所有文件|*.*",
|
||||
Title = $"工位 [{runningScope}] 打开程序",
|
||||
};
|
||||
|
||||
if (openFileDialog.ShowDialog() != true)
|
||||
return; // 用户取消选择
|
||||
|
||||
filePath = openFileDialog.FileName;
|
||||
}
|
||||
|
||||
// 确认文件存在
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
LoggerHelper.ErrorWithNotify($"文件不存在: {filePath}");
|
||||
return;
|
||||
}
|
||||
|
||||
// 读取 JSON 文件
|
||||
string json = File.ReadAllText(filePath);
|
||||
|
||||
// 反序列化为 ProgramModel
|
||||
var program = Newtonsoft.Json.JsonConvert.DeserializeObject<ProgramModel>(json);
|
||||
|
||||
if (program == null)
|
||||
{
|
||||
LoggerHelper.WarnWithNotify($"文件格式不正确或为空: {filePath}");
|
||||
return;
|
||||
}
|
||||
|
||||
// 💡 2. 严格赋值给快照锁定下的当前工位上下文,实现数据完全隔离
|
||||
targetContext.Program.Parameters = program.Parameters;
|
||||
targetContext.Program.StepCollection = program.StepCollection;
|
||||
targetContext.Program.ErrorStepCollection = program.ErrorStepCollection;
|
||||
targetContext.CurrentFilePath = filePath;
|
||||
|
||||
LoggerHelper.SuccessWithNotify($"工位 [{runningScope}] 成功打开文件: {filePath}");
|
||||
|
||||
// 💡 3. 安全调用异步复位,确保重置的是对应工位的数据
|
||||
// 注意:由于在 OnRestoration 内部第一行也做了快照拦截,
|
||||
// 如果用户此时正好在看这个工位,它会自动完美执行
|
||||
await OnRestoration();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.ErrorWithNotify($"工位 [{runningScope}] 打开文件失败: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 💡 4. 异步回归:不管中途用户切去了哪里,回到该工位时及时刷新显示
|
||||
if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveAs()
|
||||
{
|
||||
if (!_globalInfo.IsAdmin) return;
|
||||
|
||||
// 💡 1. 抓取快照
|
||||
string runningScope = _globalInfo.CurrentScope;
|
||||
ScopedContext? targetContext = CurrentContext;
|
||||
|
||||
if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return;
|
||||
|
||||
string defaultPath = @"D:\ADP\子程序";
|
||||
if (!Directory.Exists(defaultPath))
|
||||
Directory.CreateDirectory(defaultPath);
|
||||
|
||||
var saveFileDialog = new Microsoft.Win32.SaveFileDialog
|
||||
{
|
||||
Filter = "ADP程序文件|*.adp|所有文件|*.*",
|
||||
Title = $"工位 [{runningScope}] 程序另存为",
|
||||
FileName = "NewProgram.ADP",
|
||||
InitialDirectory = defaultPath
|
||||
};
|
||||
|
||||
if (saveFileDialog.ShowDialog() == true)
|
||||
{
|
||||
// 💡 2. 将新路径安全写入快照工位
|
||||
targetContext.CurrentFilePath = saveFileDialog.FileName;
|
||||
|
||||
// 💡 3. 调用你的底层文件保存逻辑,传入快照工位的 Program 模型
|
||||
SaveProgramToFile(targetContext.CurrentFilePath, targetContext.Program);
|
||||
|
||||
LoggerHelper.InfoWithNotify($"{_globalInfo.UserName} 另存为文件成功: {saveFileDialog.FileName}");
|
||||
|
||||
if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
|
||||
}
|
||||
}
|
||||
|
||||
private void Save()
|
||||
{
|
||||
if (!_globalInfo.IsAdmin) return;
|
||||
|
||||
// 💡 1. 抓取快照
|
||||
string runningScope = _globalInfo.CurrentScope;
|
||||
ScopedContext? targetContext = CurrentContext;
|
||||
|
||||
if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return;
|
||||
|
||||
// 💡 2. 判断当前工位是否有历史路径
|
||||
if (targetContext.CurrentFilePath == null)
|
||||
{
|
||||
SaveAs();
|
||||
return;
|
||||
}
|
||||
|
||||
// 💡 3. 持久化当前快照工位的 Program
|
||||
SaveProgramToFile(targetContext.CurrentFilePath, targetContext.Program);
|
||||
LoggerHelper.SuccessWithNotify($"工位 [{runningScope}] 程序保存成功!");
|
||||
}
|
||||
|
||||
// 💡 辅助方法:建议将你的通用序列化落盘代码调整为接收 ProgramModel 参数,提高复用性
|
||||
private void SaveProgramToFile(string filePath, ProgramModel programModel)
|
||||
{
|
||||
try
|
||||
{
|
||||
string json = Newtonsoft.Json.JsonConvert.SerializeObject(programModel, Newtonsoft.Json.Formatting.Indented);
|
||||
File.WriteAllText(filePath, json);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.ErrorWithNotify($"写入程序文件失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateRunIcon(string obj)
|
||||
{
|
||||
CurrentContext?.SW.Stop();
|
||||
CurrentContext.RunningTime = CurrentContext.SW.Elapsed;
|
||||
RunIcon = obj switch
|
||||
{
|
||||
"Play" => PackIconKind.Play,
|
||||
"Pause" => PackIconKind.Pause,
|
||||
_ => RunIcon
|
||||
};
|
||||
}
|
||||
|
||||
private void OnRefresh()
|
||||
{
|
||||
_eventAggregator.GetEvent<ExpandViewEvent>().Publish("");
|
||||
_globalInfo.CurrentScope = "default";
|
||||
}
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_globalInfo.CurrentScope == "default" || string.IsNullOrEmpty(_globalInfo.CurrentScope)) return;
|
||||
|
||||
string targetRegionName = _globalInfo.CurrentScope;
|
||||
|
||||
// 💡 物理销毁工位时,顺便清理任务字典,防止内存泄漏
|
||||
_executionTasks.TryRemove(targetRegionName, out _);
|
||||
_errorExecutionTasks.TryRemove(targetRegionName, out _);
|
||||
|
||||
if (_regionManager.Regions.ContainsRegionWithName(targetRegionName))
|
||||
{
|
||||
var region = _regionManager.Regions[targetRegionName];
|
||||
var viewsToDestroy = region.Views.ToList();
|
||||
|
||||
foreach (var view in viewsToDestroy)
|
||||
{
|
||||
if (view is FrameworkElement element)
|
||||
{
|
||||
var viewModel = element.DataContext;
|
||||
region.Remove(view);
|
||||
if (viewModel is IDisposable disposableVM) disposableVM.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_globalInfo.ContextDic.Remove(targetRegionName);
|
||||
_globalInfo.StepRunningDic.Remove(targetRegionName);
|
||||
|
||||
var parameters = new NavigationParameters { { "Name", targetRegionName } };
|
||||
_regionManager.RequestNavigate(targetRegionName, "ProtocolStartView", parameters);
|
||||
_eventAggregator.GetEvent<ExpandViewEvent>().Publish("");
|
||||
}
|
||||
private void Load()
|
||||
{
|
||||
_notificationManager.ShowAsync(new NotificationContent { Title = "登录成功", Message = "", Type = NotificationType.Success });
|
||||
//默认导航到主界面
|
||||
Type moduleAType = typeof(MainModule.MainModule);
|
||||
_moduleManager.LoadModule(moduleAType.Name);
|
||||
}
|
||||
private void Navigate(string content)
|
||||
{
|
||||
switch (content)
|
||||
{
|
||||
case "主界面":
|
||||
if (_globalInfo.CurrentScope == "default" || string.IsNullOrEmpty(_globalInfo.CurrentScope))
|
||||
{
|
||||
_regionManager.RequestNavigate("ShellViewManager", "MainView");
|
||||
break;
|
||||
}
|
||||
var TestingRegion = _globalInfo.CurrentScope;
|
||||
if (!_regionManager.Regions.ContainsRegionWithName(TestingRegion)) break;
|
||||
_regionManager.RequestNavigate(TestingRegion, "AutomatedTestingView");
|
||||
break;
|
||||
case "监控界面":
|
||||
// 仅当某个工位被选中(九宫格已展开/选择)时才跳转,默认 default 跳过。
|
||||
if (_globalInfo.CurrentScope == "default" || string.IsNullOrEmpty(_globalInfo.CurrentScope)) break;
|
||||
|
||||
// 记录界面不是填到全局的 ShellViewManager,而是填到该工位专属的 region。
|
||||
var monitorRegion = _globalInfo.CurrentScope;
|
||||
if (!_regionManager.Regions.ContainsRegionWithName(monitorRegion)) break;
|
||||
|
||||
var monitorParameters = new NavigationParameters { { "Name", monitorRegion } };
|
||||
_regionManager.RequestNavigate(monitorRegion, "MonitorView", monitorParameters);
|
||||
IsLeftDrawerOpen = false;
|
||||
break;
|
||||
case "记录界面":
|
||||
if (_globalInfo.CurrentScope == "default" || string.IsNullOrEmpty(_globalInfo.CurrentScope)) break;
|
||||
|
||||
// 记录界面不是填到全局的 ShellViewManager,而是填到该工位专属的 region。
|
||||
var recordRegion = _globalInfo.CurrentScope;
|
||||
if (!_regionManager.Regions.ContainsRegionWithName(recordRegion)) break;
|
||||
|
||||
var recordParameters = new NavigationParameters { { "Name", recordRegion } };
|
||||
_regionManager.RequestNavigate(recordRegion, "RecordView", recordParameters);
|
||||
IsLeftDrawerOpen = false;
|
||||
break;
|
||||
case "设置界面":
|
||||
if (_globalInfo.CurrentScope == "default" || string.IsNullOrEmpty(_globalInfo.CurrentScope)) break;
|
||||
|
||||
// 记录界面不是填到全局的 ShellViewManager,而是填到该工位专属的 region。
|
||||
var settingRegion = _globalInfo.CurrentScope;
|
||||
if (!_regionManager.Regions.ContainsRegionWithName(settingRegion)) break;
|
||||
|
||||
var settingParameters = new NavigationParameters { { "Name", settingRegion } };
|
||||
_regionManager.RequestNavigate(settingRegion, "SettingView", settingParameters);
|
||||
IsLeftDrawerOpen = false;
|
||||
break;
|
||||
case "更新界面":
|
||||
_regionManager.RequestNavigate("ShellViewManager", "UpdateInfoView");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void LeftDrawerOpen()
|
||||
{
|
||||
IsLeftDrawerOpen = true;
|
||||
}
|
||||
|
||||
private void MinimizeWindow(Window window)
|
||||
{
|
||||
if (window != null)
|
||||
window.WindowState = WindowState.Minimized;
|
||||
}
|
||||
|
||||
private void MaximizeWindow(Window window)
|
||||
{
|
||||
if (window != null)
|
||||
{
|
||||
window.WindowState = window.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseWindow(Window window)
|
||||
{
|
||||
window?.Close();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
87
ADP/Views/Dialogs/MessageBoxView.xaml
Normal file
87
ADP/Views/Dialogs/MessageBoxView.xaml
Normal file
@@ -0,0 +1,87 @@
|
||||
<UserControl x:Class="ADP.Views.Dialogs.MessageBoxView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:ADP.Views.Dialogs"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
mc:Ignorable="d"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
Background="Transparent"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
Height="250"
|
||||
Width="300">
|
||||
<prism:Dialog.WindowStyle>
|
||||
<Style BasedOn="{StaticResource DialogUserManageStyle}"
|
||||
TargetType="Window" />
|
||||
</prism:Dialog.WindowStyle>
|
||||
<Border CornerRadius="20"
|
||||
Background="white"
|
||||
MouseLeftButtonDown="Border_MouseLeftButtonDown">
|
||||
<Grid Background="Transparent">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
|
||||
<!-- Title -->
|
||||
<TextBlock Grid.Row="0"
|
||||
Margin="15 5 5 5"
|
||||
FontSize="18"
|
||||
FontWeight="Bold"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Title}"
|
||||
Foreground="#333" />
|
||||
|
||||
<StackPanel HorizontalAlignment="Center"
|
||||
Grid.Row="1">
|
||||
<!-- Icon -->
|
||||
<Image
|
||||
Width="100"
|
||||
Height="100"
|
||||
VerticalAlignment="Top"
|
||||
Margin="5 15 0 0"
|
||||
Source="{Binding Icon}" />
|
||||
<!-- Message -->
|
||||
<TextBlock
|
||||
FontSize="20"
|
||||
Margin="15 0 0 0"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
Text="{Binding Message}" />
|
||||
</StackPanel>
|
||||
<!-- Buttons -->
|
||||
<StackPanel Grid.Row="2"
|
||||
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right">
|
||||
|
||||
<Button Content="Yes"
|
||||
Width="80"
|
||||
Margin="10 10"
|
||||
Visibility="{Binding ShowYes, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Command="{Binding YesCommand}" />
|
||||
|
||||
<Button Content="No"
|
||||
Width="80"
|
||||
Margin="10 10"
|
||||
Visibility="{Binding ShowNo, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Command="{Binding NoCommand}" />
|
||||
|
||||
<Button Content="OK"
|
||||
Width="80"
|
||||
Margin="10 10"
|
||||
Visibility="{Binding ShowOk, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Command="{Binding OkCommand}" />
|
||||
|
||||
<Button Content="Cancel"
|
||||
Width="80"
|
||||
Margin="10 10"
|
||||
Visibility="{Binding ShowCancel, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Command="{Binding CancelCommand}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
36
ADP/Views/Dialogs/MessageBoxView.xaml.cs
Normal file
36
ADP/Views/Dialogs/MessageBoxView.xaml.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace ADP.Views.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// MessageBoxView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class MessageBoxView : UserControl
|
||||
{
|
||||
public MessageBoxView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
Window.GetWindow(this)?.DragMove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
ADP/Views/LoginModuleView.xaml
Normal file
21
ADP/Views/LoginModuleView.xaml
Normal file
@@ -0,0 +1,21 @@
|
||||
<mah:MetroWindow xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:helpers="clr-namespace:UIShare.Helpers;assembly=UIShare"
|
||||
x:Class="ADP.Views.LoginModuleView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:local="clr-namespace:ADP.Views"
|
||||
mc:Ignorable="d"
|
||||
Title="ADP"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Height="315"
|
||||
Width="420"
|
||||
ResizeMode="NoResize">
|
||||
<Grid>
|
||||
<ContentControl prism:RegionManager.RegionName="LoginRegion" />
|
||||
</Grid>
|
||||
</mah:MetroWindow>
|
||||
38
ADP/Views/LoginModuleView.xaml.cs
Normal file
38
ADP/Views/LoginModuleView.xaml.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using UIShare.PubEvent;
|
||||
using MahApps.Metro.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
namespace ADP.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// Login.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class LoginModuleView : MetroWindow
|
||||
{
|
||||
public LoginModuleView(IEventAggregator eventAggregator)
|
||||
{
|
||||
InitializeComponent();
|
||||
//订阅登录成功事件
|
||||
eventAggregator.GetEvent<LoginSuccessEvent>().Subscribe(() =>
|
||||
{
|
||||
this.Close();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
339
ADP/Views/ShellView.xaml
Normal file
339
ADP/Views/ShellView.xaml
Normal file
@@ -0,0 +1,339 @@
|
||||
<Window x:Class="ADP.Views.ShellView"
|
||||
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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:converter="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Topmost="false"
|
||||
mc:Ignorable="d"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
WindowStyle="None"
|
||||
WindowState="Maximized"
|
||||
Title="ShellView"
|
||||
d:DesignHeight="1080"
|
||||
d:DesignWidth="1920">
|
||||
<WindowChrome.WindowChrome>
|
||||
<WindowChrome GlassFrameThickness="-1" />
|
||||
</WindowChrome.WindowChrome>
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="Loaded">
|
||||
<i:InvokeCommandAction Command="{Binding LoadCommand}" />
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
<Window.Resources>
|
||||
<converter:InverseBooleanConverter x:Key="InverseBooleanConverter" />
|
||||
<converter:TimeSpanToStringConverter x:Key="TimeSpanConverter" />
|
||||
</Window.Resources>
|
||||
<materialDesign:DrawerHost x:Name="MainDrawerHost"
|
||||
IsLeftDrawerOpen="{Binding IsLeftDrawerOpen, Mode=TwoWay}">
|
||||
|
||||
<!-- ✅ 左侧抽屉内容 -->
|
||||
<materialDesign:DrawerHost.LeftDrawerContent>
|
||||
<StackPanel Width="220"
|
||||
Background="{DynamicResource MaterialDesignPaper}">
|
||||
<TextBlock Text="导航菜单"
|
||||
FontSize="18"
|
||||
Margin="16"
|
||||
Foreground="{DynamicResource PrimaryHueMidBrush}" />
|
||||
<Separator Margin="0,0,0,8" />
|
||||
<Button Content="主界面"
|
||||
Command="{Binding NavigateCommand}"
|
||||
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
|
||||
Style="{StaticResource MaterialDesignFlatButton}"
|
||||
Margin="8" />
|
||||
<Button Content="监控界面"
|
||||
Command="{Binding NavigateCommand}"
|
||||
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
|
||||
Style="{StaticResource MaterialDesignFlatButton}"
|
||||
Margin="8" />
|
||||
<Button Content="记录界面"
|
||||
Command="{Binding NavigateCommand}"
|
||||
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
|
||||
Style="{StaticResource MaterialDesignFlatButton}"
|
||||
Margin="8" />
|
||||
<Button Content="设置界面"
|
||||
Command="{Binding NavigateCommand}"
|
||||
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
|
||||
Style="{StaticResource MaterialDesignFlatButton}"
|
||||
Margin="8" />
|
||||
<Button Content="更新界面"
|
||||
Command="{Binding NavigateCommand}"
|
||||
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
|
||||
Style="{StaticResource MaterialDesignFlatButton}"
|
||||
Margin="8" />
|
||||
</StackPanel>
|
||||
</materialDesign:DrawerHost.LeftDrawerContent>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<!-- 顶部工具栏 -->
|
||||
<materialDesign:ColorZone Mode="PrimaryMid"
|
||||
MouseLeftButtonDown="ColorZone_MouseLeftButtonDown">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Menu Grid.Column="0"
|
||||
Background="Transparent"
|
||||
Foreground="White"
|
||||
VerticalAlignment="Center">
|
||||
<!-- 文件菜单 -->
|
||||
<MenuItem FontSize="13"
|
||||
Height="50"
|
||||
Header="菜单"
|
||||
Foreground="White"
|
||||
Command="{Binding DataContext.LeftDrawerOpenCommand, RelativeSource={RelativeSource AncestorType=Window}}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Menu"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
|
||||
|
||||
<!-- 工具菜单 -->
|
||||
<MenuItem Header="工具"
|
||||
FontSize="13"
|
||||
Height="50"
|
||||
Foreground="White">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Tools"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
|
||||
<!-- 新建 -->
|
||||
<MenuItem Header="新建"
|
||||
Foreground="Black"
|
||||
Command="{Binding NewCommand}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="FilePlus"
|
||||
Foreground="Black" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
|
||||
<!-- 打开 -->
|
||||
<MenuItem Header="打开"
|
||||
Foreground="Black"
|
||||
Command="{Binding OpenCommand}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="FolderOpen"
|
||||
Foreground="Black" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
|
||||
<!-- 保存 -->
|
||||
<MenuItem Header="保存"
|
||||
Foreground="Black"
|
||||
Command="{Binding SaveCommand}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="ContentSave"
|
||||
Foreground="Black" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
|
||||
<!-- 另存为 -->
|
||||
<MenuItem Header="另存为"
|
||||
Foreground="Black"
|
||||
Command="{Binding SaveAsCommand}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="ContentSaveEdit"
|
||||
Foreground="Black" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
|
||||
<!-- 设置默认程序 -->
|
||||
<MenuItem Header="设置默认程序"
|
||||
Foreground="Black"
|
||||
Command="{Binding SetDefaultCommand}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Cog"
|
||||
Foreground="Black" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
|
||||
<!-- 蜂鸣器消音 -->
|
||||
<MenuItem Header="蜂鸣器消音"
|
||||
FontSize="14"
|
||||
Height="50"
|
||||
Foreground="Black"
|
||||
Command="{Binding SilenceBuzzerCommand}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="BellOff"
|
||||
Foreground="Black" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
<MenuItem Header="切换九宫格"
|
||||
FontSize="13"
|
||||
Height="50"
|
||||
Foreground="White"
|
||||
Command="{Binding RefreshCommand}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Refresh"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="销毁作用域"
|
||||
FontSize="13"
|
||||
Height="50"
|
||||
Foreground="White"
|
||||
Command="{Binding DestroyCommand}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Death"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem FontSize="14"
|
||||
Height="50"
|
||||
Header="运行"
|
||||
IsEnabled="{Binding IsTerminate, Converter={StaticResource InverseBooleanConverter}}"
|
||||
Command="{Binding RunningCommand}"
|
||||
Foreground="White">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="{Binding RunIcon}"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem FontSize="14"
|
||||
Height="50"
|
||||
Header="单步执行"
|
||||
IsEnabled="{Binding IsTerminate, Converter={StaticResource InverseBooleanConverter}}"
|
||||
Command="{Binding RunSingleCommand}"
|
||||
Foreground="White">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="ArrowRight"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem FontSize="14"
|
||||
Height="50"
|
||||
Header="异常流程"
|
||||
Command="{Binding RunAbnormalStepsCommand}"
|
||||
IsEnabled="{Binding IsTerminate, Converter={StaticResource InverseBooleanConverter}}"
|
||||
Foreground="White">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="AlertCircle"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem FontSize="14"
|
||||
Height="50"
|
||||
Header="复位"
|
||||
Command="{Binding RestorationCommand}"
|
||||
Foreground="White">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Restart"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem FontSize="14"
|
||||
Height="50"
|
||||
Foreground="White">
|
||||
<MenuItem.Header>
|
||||
<TextBlock>
|
||||
<Run Text="运行时间:" />
|
||||
<TextBlock Text="{Binding RunningTime, Converter={StaticResource TimeSpanConverter}}" />
|
||||
</TextBlock>
|
||||
</MenuItem.Header>
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Clock"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
<Menu Grid.Column="2"
|
||||
Margin="0 0 20 0">
|
||||
<MenuItem FontSize="13"
|
||||
Height="50"
|
||||
Header="最小化"
|
||||
Foreground="White"
|
||||
Command="{Binding MinimizeCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Minimize"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
|
||||
<!--<MenuItem FontSize="13"
|
||||
Height="50"
|
||||
Header="最大化"
|
||||
Foreground="White"
|
||||
Command="{Binding MaximizeCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Maximize"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>-->
|
||||
|
||||
<MenuItem FontSize="13"
|
||||
Height="50"
|
||||
Header="关闭"
|
||||
Foreground="White"
|
||||
Command="{Binding CloseCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Close"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
</Grid>
|
||||
<!-- 左侧菜单 -->
|
||||
|
||||
|
||||
|
||||
</materialDesign:ColorZone>
|
||||
|
||||
<materialDesign:DialogHost Grid.Row="1"
|
||||
x:Name="DialogHost"
|
||||
DialogBackground="Transparent"
|
||||
Background="Transparent"
|
||||
Identifier="Root">
|
||||
<!-- 主内容区 -->
|
||||
<Grid>
|
||||
<ContentControl prism:RegionManager.RegionName="ShellViewManager" />
|
||||
<Border x:Name="Overlay"
|
||||
Background="#40000000"
|
||||
Visibility="Collapsed"
|
||||
Panel.ZIndex="1">
|
||||
<StackPanel Width="150"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0 0 0 100">
|
||||
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border x:Name="Waitinglay"
|
||||
Background="#40000000"
|
||||
Visibility="Collapsed"
|
||||
Panel.ZIndex="1">
|
||||
<StackPanel Width="150"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0 0 0 100">
|
||||
<ProgressBar Width="80"
|
||||
Height="80"
|
||||
Margin="20"
|
||||
IsIndeterminate="True"
|
||||
Style="{StaticResource MaterialDesignCircularProgressBar}" />
|
||||
<TextBlock FontSize="30"
|
||||
Text="加载中......"
|
||||
HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</materialDesign:DialogHost>
|
||||
</Grid>
|
||||
</materialDesign:DrawerHost>
|
||||
|
||||
</Window>
|
||||
51
ADP/Views/ShellView.xaml.cs
Normal file
51
ADP/Views/ShellView.xaml.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using UIShare.PubEvent;
|
||||
using Prism.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace ADP.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// ShellView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class ShellView : Window
|
||||
{
|
||||
public ShellView(IEventAggregator eventAggregator)
|
||||
{
|
||||
InitializeComponent();
|
||||
//注册灰度遮罩层
|
||||
eventAggregator.GetEvent<OverlayEvent>().Subscribe(ShowOverlay);
|
||||
eventAggregator.GetEvent<WaitingEvent>().Subscribe(ShowWaitinglay);
|
||||
}
|
||||
|
||||
private void ShowWaitinglay(bool arg)
|
||||
{
|
||||
Waitinglay.Visibility = arg ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void ShowOverlay(bool arg)
|
||||
{
|
||||
Overlay.Visibility = arg ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
|
||||
private void ColorZone_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
Window.GetWindow(this)?.DragMove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Command/Command.csproj
Normal file
13
Command/Command.csproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
268
Command/CommandApplication.cs
Normal file
268
Command/CommandApplication.cs
Normal file
@@ -0,0 +1,268 @@
|
||||
using Common.Attributes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Command
|
||||
{
|
||||
|
||||
[ADPCommand]
|
||||
public static class CommandApplication
|
||||
{
|
||||
/// <summary>
|
||||
/// 打开外部应用程序
|
||||
/// </summary>
|
||||
/// <param name="route">程序路径</param>
|
||||
/// <param name="waitShutdown">是否等待关闭</param>
|
||||
/// <param name="AdminRun">是否请求管理员运行</param>
|
||||
public static void OpenApplication(string route, bool waitShutdown, bool AdminRun)
|
||||
{
|
||||
|
||||
// 创建一个新的进程实例
|
||||
Process process = new Process();
|
||||
|
||||
try
|
||||
{
|
||||
// 指定要启动的程序路径
|
||||
process.StartInfo.FileName = route;
|
||||
|
||||
// 指定是否等待程序关闭
|
||||
process.StartInfo.UseShellExecute = !waitShutdown;
|
||||
if (AdminRun) process.StartInfo.Verb = "runas";
|
||||
// 启动程序
|
||||
process.Start();
|
||||
|
||||
// 如果需要等待程序关闭,则等待程序退出
|
||||
if (waitShutdown)
|
||||
{
|
||||
process.WaitForExit();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("错误!: " + ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 确保进程对象被释放
|
||||
process.Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 使用默认方式打开一个文件
|
||||
/// </summary>
|
||||
/// <param name="route">文件路径</param>
|
||||
public static void OpenFile(string route)
|
||||
{
|
||||
// 创建一个新的进程实例
|
||||
Process process = new Process();
|
||||
|
||||
try
|
||||
{
|
||||
// 指定要启动的程序路径
|
||||
process.StartInfo.FileName = route;
|
||||
process.StartInfo.UseShellExecute = true;
|
||||
// 启动程序
|
||||
process.Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("错误!: " + ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 确保进程对象被释放
|
||||
process.Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Python引擎相关字段
|
||||
private static Process _pythonProcess;
|
||||
private static string _pythonPath = string.Empty;
|
||||
private static bool _isPythonEngineInitialized = false;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化python引擎
|
||||
/// </summary>
|
||||
/// <param name="py路径">Python解释器路径</param>
|
||||
public static void Initialization_Python(string py路径)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 验证Python路径是否存在
|
||||
if (string.IsNullOrEmpty(py路径) || !File.Exists(py路径))
|
||||
{
|
||||
throw new FileNotFoundException($"Python解释器路径不存在: {py路径}");
|
||||
}
|
||||
|
||||
// 验证是否为Python可执行文件
|
||||
var fileInfo = new FileInfo(py路径);
|
||||
if (!fileInfo.Name.ToLower().Contains("python"))
|
||||
{
|
||||
Console.WriteLine("警告: 指定的文件可能不是Python解释器");
|
||||
}
|
||||
|
||||
// 测试Python是否能正常运行
|
||||
var testProcess = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = py路径,
|
||||
Arguments = "--version",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true
|
||||
}
|
||||
};
|
||||
|
||||
testProcess.Start();
|
||||
testProcess.WaitForExit(5000); // 5秒超时
|
||||
|
||||
if (testProcess.ExitCode != 0)
|
||||
{
|
||||
throw new InvalidOperationException($"Python解释器验证失败,退出码: {testProcess.ExitCode}");
|
||||
}
|
||||
|
||||
_pythonPath = py路径;
|
||||
_isPythonEngineInitialized = true;
|
||||
|
||||
Console.WriteLine($"Python引擎初始化成功,版本: {testProcess.StandardOutput.ReadToEnd().Trim()}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_isPythonEngineInitialized = false;
|
||||
_pythonPath = string.Empty;
|
||||
Console.WriteLine($"初始化Python引擎失败: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行python脚本
|
||||
/// </summary>
|
||||
/// <param name="路径">Python脚本路径</param>
|
||||
/// <returns>脚本执行结果</returns>
|
||||
public static string Run_Python_Script(string 路径)
|
||||
{
|
||||
if (!_isPythonEngineInitialized)
|
||||
{
|
||||
throw new InvalidOperationException("Python引擎未初始化,请先调用初始化python引擎");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(路径) || !File.Exists(路径))
|
||||
{
|
||||
throw new FileNotFoundException($"Python脚本路径不存在: {路径}");
|
||||
}
|
||||
|
||||
Process process = null;
|
||||
try
|
||||
{
|
||||
process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = _pythonPath,
|
||||
Arguments = $"\"{路径}\"",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true
|
||||
}
|
||||
};
|
||||
|
||||
var output = new StringBuilder();
|
||||
var error = new StringBuilder();
|
||||
|
||||
// 设置输出和错误处理
|
||||
process.OutputDataReceived += (sender, e) =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(e.Data))
|
||||
output.AppendLine(e.Data);
|
||||
};
|
||||
|
||||
process.ErrorDataReceived += (sender, e) =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(e.Data))
|
||||
error.AppendLine(e.Data);
|
||||
};
|
||||
|
||||
process.Start();
|
||||
|
||||
// 异步读取输出
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
|
||||
process.WaitForExit();
|
||||
|
||||
var outputResult = output.ToString().Trim();
|
||||
var errorResult = error.ToString().Trim();
|
||||
|
||||
if (process.ExitCode != 0)
|
||||
{
|
||||
throw new InvalidOperationException($"Python脚本执行失败,退出码: {process.ExitCode}, 错误: {errorResult}");
|
||||
}
|
||||
|
||||
return string.IsNullOrEmpty(outputResult) ? "执行成功" : outputResult;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"运行Python脚本失败: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
process?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放python引擎
|
||||
/// </summary>
|
||||
/// <param name="py">Python解释器路径(可选,用于验证)</param>
|
||||
public static void Release_Python(string py = "")
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(py) && py != _pythonPath)
|
||||
{
|
||||
Console.WriteLine($"警告: 释放的Python路径与当前初始化路径不匹配");
|
||||
}
|
||||
|
||||
// 如果有正在运行的Python进程,尝试终止它
|
||||
if (_pythonProcess != null && !_pythonProcess.HasExited)
|
||||
{
|
||||
try
|
||||
{
|
||||
_pythonProcess.Kill();
|
||||
_pythonProcess.WaitForExit(1000);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"终止Python进程时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
_pythonPath = string.Empty;
|
||||
_isPythonEngineInitialized = false;
|
||||
_pythonProcess = null;
|
||||
|
||||
Console.WriteLine("Python引擎已释放");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"释放Python引擎失败: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
261
Command/CommandArray.cs
Normal file
261
Command/CommandArray.cs
Normal file
@@ -0,0 +1,261 @@
|
||||
using Common.Attributes;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Command
|
||||
{
|
||||
|
||||
[ADPCommand]
|
||||
public static class CommandArray
|
||||
{
|
||||
#region 数组操作
|
||||
/// <summary>
|
||||
/// 从数组中获取指定位置的元素。
|
||||
/// </summary>
|
||||
/// <param name="Array">要索引的数组。</param>
|
||||
/// <param name="Index">要获取的元素的位置。</param>
|
||||
/// <returns>指定位置的元素。</returns>
|
||||
public static object IndexArray(object Array, int Index)
|
||||
{
|
||||
return ((dynamic)Array)[Index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置数组中指定位置的元素为给定值。
|
||||
/// </summary>
|
||||
/// <param name="Array">要设置元素的数组。</param>
|
||||
/// <param name="Location">要设置的元素的位置。</param>
|
||||
/// <param name="Value">要设置的值。</param>
|
||||
/// <returns>修改后的数组。</returns>
|
||||
public static object SetArrayElement(object Array, int Location, object Value)
|
||||
{
|
||||
((dynamic)Array)[Location] = (dynamic)Value;
|
||||
return Array;
|
||||
}
|
||||
/// <summary>
|
||||
/// 将给定数组的长度设置为指定值,并返回一个新的数组。
|
||||
/// 如果指定的长度小于原始数组的长度,则截取原始数组的一部分作为新数组。
|
||||
/// </summary>
|
||||
/// <param name="Arr">要设置长度的数组。</param>
|
||||
/// <param name="Length">新数组的长度。</param>
|
||||
/// <returns>新的数组,或者原始数组(如果输入参数不是数组类型)。</returns>
|
||||
public static object SetArrayLength(object Arr, int Length)
|
||||
{
|
||||
if (Arr is Array)
|
||||
{
|
||||
// 创建一个新的数组实例来存储结果
|
||||
Array temp = Array.CreateInstance(Arr.GetType()!.GetElementType()!, Length);
|
||||
// 确定复制的长度,取原始数组长度和目标长度的最小值
|
||||
int CopyLength = Math.Min(((Array)Arr).Length, Length);
|
||||
// 复制原始数组的元素到新数组中
|
||||
Array.Copy((Array)Arr, temp, CopyLength);
|
||||
return temp;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果输入参数不是数组类型,则返回原始数组
|
||||
return Arr;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在指定位置插入元素到数组中。
|
||||
/// </summary>
|
||||
/// <param name="Arr">要插入元素的数组。</param>
|
||||
/// <param name="Location">要插入元素的位置。</param>
|
||||
/// <param name="InsertElement">要插入的元素。</param>
|
||||
/// <returns>插入元素后的数组。</returns>
|
||||
public static object SetArrayInsertElement(object Arr, int Location, object InsertElement)
|
||||
{
|
||||
// 将数组转换为可编辑列表
|
||||
var temp = Enumerable.ToList((dynamic)Arr);
|
||||
|
||||
// 在指定位置插入元素
|
||||
temp.Insert(Location, (dynamic)InsertElement);
|
||||
|
||||
// 如果原数组是数组类型,则将可编辑列表转换回数组并返回
|
||||
if (Arr is Array)
|
||||
{
|
||||
return Enumerable.ToArray(temp);
|
||||
}
|
||||
|
||||
// 返回插入元素后的可编辑列表
|
||||
return temp;
|
||||
}
|
||||
/// <summary>
|
||||
/// 从数组中删除指定的元素。
|
||||
/// </summary>
|
||||
/// <param name="Arr">要删除元素的数组。</param>
|
||||
/// <param name="DeleteElement">要从数组中删除的元素。</param>
|
||||
/// <returns>删除元素后的数组。</returns>
|
||||
public static object SetArrayDeleteElement(object Arr, object DeleteElement)
|
||||
{
|
||||
// 将数组转换为可编辑列表
|
||||
var temp = Enumerable.ToList((dynamic)Arr);
|
||||
|
||||
// 从列表中移除指定元素
|
||||
temp.Remove((dynamic)DeleteElement);
|
||||
|
||||
// 如果原数组是数组类型,则将可编辑列表转换回数组并返回
|
||||
if (Arr is Array)
|
||||
{
|
||||
return Enumerable.ToArray(temp);
|
||||
}
|
||||
|
||||
// 返回删除元素后的可编辑列表
|
||||
return temp;
|
||||
}
|
||||
/// <summary>
|
||||
/// 从数组中删除指定位置的元素。
|
||||
/// </summary>
|
||||
/// <param name="Arr">要删除元素的数组。</param>
|
||||
/// <param name="DeleteElementLocation">要从数组中删除的元素的位置。</param>
|
||||
/// <returns>删除元素后的数组。</returns>
|
||||
public static object SetArrayDeleteLocationElement(object Arr, int DeleteElementLocation)
|
||||
{
|
||||
var temp = Enumerable.ToList((dynamic)Arr);
|
||||
temp.RemoveAt(DeleteElementLocation);
|
||||
if (Arr is Array)
|
||||
{
|
||||
return Enumerable.ToArray(temp);
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
/// <summary>
|
||||
/// 在数组中寻找第一个匹配的元素,并返回其索引位置。
|
||||
/// </summary>
|
||||
/// <param name="Arr">要搜索的数组。</param>
|
||||
/// <param name="QueryElement">要寻找的元素。</param>
|
||||
/// <returns>要寻找的元素在数组中的第一个匹配项的索引;如果未找到匹配项,则为 -1。</returns>
|
||||
public static int ArrayQueryFirstMatchElement(object Arr, object QueryElement)
|
||||
{
|
||||
if (Arr is Array)
|
||||
{
|
||||
return Array.IndexOf((dynamic)Arr, (dynamic)QueryElement);
|
||||
}
|
||||
return ((dynamic)Arr).IndexOf((dynamic)QueryElement);
|
||||
}
|
||||
/// <summary>
|
||||
/// 替换数组中第一个匹配的元素为指定的新元素。
|
||||
/// </summary>
|
||||
/// <param name="Arr">要进行替换操作的数组。</param>
|
||||
/// <param name="FirstMatchElement">要替换的元素。</param>
|
||||
/// <param name="ReplaceElement">要替换为的新元素。</param>
|
||||
/// <returns>替换后的数组。</returns>
|
||||
public static object ArrayReplaceFirstMatchElement(object Arr, object FirstMatchElement, object ReplaceElement)
|
||||
{
|
||||
var temp = Enumerable.ToList((dynamic)Arr);
|
||||
int Location = ArrayQueryFirstMatchElement(Arr, FirstMatchElement);
|
||||
temp[Location] = (dynamic)ReplaceElement;
|
||||
return temp;
|
||||
}
|
||||
/// <summary>
|
||||
/// 将数组中所有匹配的元素替换为指定的新元素。
|
||||
/// </summary>
|
||||
/// <param name="Arr">要进行替换操作的数组。</param>
|
||||
/// <param name="MatchElement">要替换的元素。</param>
|
||||
/// <param name="ReplaceElement">要替换为的新元素。</param>
|
||||
/// <returns>替换后的数组。</returns>
|
||||
public static object ArrayReplaceAllMatchElement(object Arr, object MatchElement, object ReplaceElement)
|
||||
{
|
||||
var temp = Enumerable.ToList((dynamic)Arr);
|
||||
int 长度 = temp.Count;
|
||||
|
||||
for (int i = 0; i < 长度; i++)
|
||||
{
|
||||
if (((dynamic)Arr)[i] == (dynamic)MatchElement)
|
||||
{
|
||||
((dynamic)Arr)[i] = (dynamic)ReplaceElement;
|
||||
}
|
||||
}
|
||||
return Arr;
|
||||
}
|
||||
/// <summary>
|
||||
/// 反转数组中元素的顺序。
|
||||
/// </summary>
|
||||
/// <param name="Arr">要反转的数组。</param>
|
||||
/// <returns>反转后的数组。</returns>
|
||||
public static object ArrayReverse(object Arr)
|
||||
{
|
||||
var temp = Enumerable.ToList((dynamic)Arr);
|
||||
temp.Reverse();
|
||||
if (Arr is Array)
|
||||
{
|
||||
return Enumerable.ToArray(temp);
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
/// <summary>
|
||||
/// 对数组进行排序。
|
||||
/// </summary>
|
||||
/// <param name="Arr">要排序的数组。</param>
|
||||
/// <returns>排序后的数组。</returns>
|
||||
public static object ArraySorting(object Arr)
|
||||
{
|
||||
var temp = Enumerable.ToList((dynamic)Arr);
|
||||
temp = Enumerable.ToList(Enumerable.Order(temp));
|
||||
if (Arr is Array)
|
||||
{
|
||||
return Enumerable.ToArray(temp);
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取数组的长度。
|
||||
/// </summary>
|
||||
/// <param name="Arr">要获取长度的数组。</param>
|
||||
/// <returns>数组的长度。</returns>
|
||||
public static int GetArrayLength(object Arr)
|
||||
{
|
||||
if (Arr is Array)
|
||||
{
|
||||
return ((Array)Arr).Length;
|
||||
}
|
||||
return ((ICollection)Arr).Count;
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 将对象转换为数组。
|
||||
/// </summary>
|
||||
/// <param name="Arr">要转换的对象。</param>
|
||||
/// <returns>转换后的数组。</returns>
|
||||
public static object objectConvertToArray(object Arr)
|
||||
{
|
||||
return Enumerable.ToArray((dynamic)Arr);
|
||||
}
|
||||
/// <summary>
|
||||
/// 将对象转换为列表。
|
||||
/// </summary>
|
||||
/// <param name="Arr">要转换的对象。</param>
|
||||
/// <returns>转换后的列表。</returns>
|
||||
public static object objectConvertToList(object Arr)
|
||||
{
|
||||
return Enumerable.ToList((dynamic)Arr);
|
||||
}
|
||||
/// <summary>
|
||||
/// 从数组中截取指定长度的子数组。
|
||||
/// </summary>
|
||||
/// <param name="Arr">要截取的数组。</param>
|
||||
/// <param name="StartLocation">截取开始的位置。</param>
|
||||
/// <param name="Length">要截取的长度。</param>
|
||||
/// <returns>截取得到的子数组。</returns>
|
||||
public static object ArrayCapture(object Arr, int StartLocation, int Length)
|
||||
{
|
||||
var temp = Enumerable.ToList((dynamic)Arr);
|
||||
temp = Enumerable.ToList(Enumerable.Take(Enumerable.Skip(temp, StartLocation), Length));
|
||||
if (Arr is Array)
|
||||
{
|
||||
return Enumerable.ToArray(temp);
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
473
Command/CommandMath.cs
Normal file
473
Command/CommandMath.cs
Normal file
@@ -0,0 +1,473 @@
|
||||
using Common.Attributes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Command
|
||||
{
|
||||
public enum ComparisonFuncEnum
|
||||
{
|
||||
Big,
|
||||
BigOrEqual,
|
||||
Small,
|
||||
SmallOrEqual,
|
||||
Equal,
|
||||
NotEqual
|
||||
}
|
||||
|
||||
[ADPCommand]
|
||||
public static class CommandMath
|
||||
{
|
||||
#region 返回值是Double类型数据
|
||||
/// <summary>
|
||||
/// 两数相加(Param1 + Param2)
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数1</param>
|
||||
/// <param name="Param2">传入值:实数2</param>
|
||||
/// <example>Param1:9 Param2:2 返回值:11</example>
|
||||
/// <returns></returns>
|
||||
public static double ParamAdd(double Param1, double Param2)
|
||||
{
|
||||
return Param1 + Param2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 两数相减(Param1 - Param2)
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数1</param>
|
||||
/// <param name="Param2">传入值:实数2</param>
|
||||
/// <example>Param1:9 Param2:2 返回值:7</example>
|
||||
/// <returns></returns>
|
||||
public static double ParamReduce(double Param1, double Param2)
|
||||
{
|
||||
return Param1 - Param2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 两数相乘(Param1 * Param2)
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数1</param>
|
||||
/// <param name="Param2">传入值:实数2</param>
|
||||
/// <example>Param1:9 Param2:2 返回值:18</example>
|
||||
/// <returns></returns>
|
||||
public static double ParamMult(double Param1, double Param2)
|
||||
{
|
||||
return Param1 * Param2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 两数相除(Param1 / Param2)
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数1</param>
|
||||
/// <param name="Param2">传入值:实数2</param>
|
||||
/// <param name="Param3">传入值:保留几位小数</param>
|
||||
/// <example>Param1:10 Param2:3 Param3:3 返回值:3.333</example>
|
||||
/// <example>Param1:11 Param2:3 Param3:2 返回值:3.67</example>
|
||||
/// <returns></returns>
|
||||
public static double ParamDivide(double Param1, double Param2, int Param3)
|
||||
{
|
||||
return Math.Round((Param1 / Param2), Param3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 两数相除求余(Param1 % Param2)
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数1</param>
|
||||
/// <param name="Param2">传入值:实数2</param>
|
||||
/// <example>Param1:9 Param2:2 返回值:1</example>
|
||||
/// <returns></returns>
|
||||
public static double ParamRemainder(double Param1, double Param2)
|
||||
{
|
||||
return Param1 % Param2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实数开根号
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数1</param>
|
||||
/// <example>Param1:16 返回值:4</example>
|
||||
/// <returns></returns>
|
||||
public static double ParamSquareRoot(double Param1)
|
||||
{
|
||||
return Math.Sqrt(Param1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实数的幂运算
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数1</param>
|
||||
/// <param name="Param2">传入值:实数1需要做几次方</param>
|
||||
/// <example>Param1:3 Param2:2 返回值:9</example>
|
||||
/// <example>Param1:2 Param2:3 返回值:8</example>
|
||||
/// <example>Param1:5 Param2:4 返回值:625</example>
|
||||
/// <returns></returns>
|
||||
public static double ParamPow(double Param1, double Param2)
|
||||
{
|
||||
return Math.Pow(Param1, Param2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实数的绝对值
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数1</param>
|
||||
/// <example>Param1:-3.7 返回值:3.7</example>
|
||||
/// <returns></returns>
|
||||
public static double ParamAbs(double Param1)
|
||||
{
|
||||
return Math.Abs(Param1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实数的向下取整
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数1</param>
|
||||
/// <example>Param1:3.7 返回值:3</example>
|
||||
/// <returns></returns>
|
||||
public static double ParamFloor(double Param1)
|
||||
{
|
||||
return Math.Floor(Param1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实数的向上取整
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数1</param>
|
||||
/// <example>Param1:3.7 返回值:4</example>
|
||||
/// <returns></returns>
|
||||
public static double ParamCeiling(double Param1)
|
||||
{
|
||||
return Math.Ceiling(Param1);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region 返回值是String类型的数据
|
||||
/// <summary>
|
||||
/// A与B转换为double后比较,返回A比较B的结果
|
||||
/// </summary>
|
||||
/// <param name="a">比较数1</param>
|
||||
/// <param name="b">比较数2</param>
|
||||
/// <param name="比较方法">比较方法</param>
|
||||
/// <returns>返回A比较B</returns>
|
||||
public static bool Comparison(string a, string b, ComparisonFuncEnum 比较方法)
|
||||
{
|
||||
var aa = Convert.ToDouble(a);
|
||||
var bb = Convert.ToDouble(b);
|
||||
|
||||
switch (比较方法)
|
||||
{
|
||||
case ComparisonFuncEnum.Big:
|
||||
return aa > bb;
|
||||
case ComparisonFuncEnum.BigOrEqual:
|
||||
return aa >= bb;
|
||||
case ComparisonFuncEnum.Small:
|
||||
return aa < bb;
|
||||
case ComparisonFuncEnum.SmallOrEqual:
|
||||
return aa <= bb;
|
||||
case ComparisonFuncEnum.Equal:
|
||||
return aa == bb;
|
||||
case ComparisonFuncEnum.NotEqual:
|
||||
return aa != bb;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加法指令,转换为double后返回o1+o2
|
||||
/// </summary>
|
||||
/// <param name="o1">运算数1</param>
|
||||
/// <param name="o2">运算数2</param>
|
||||
/// <returns>返回o1+o2</returns>
|
||||
public static string TwoNumberAdd(string o1, string o2)
|
||||
{
|
||||
return (Convert.ToDecimal(o1) + Convert.ToDecimal(o2)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 减法指令,转换为double后返回o1-o2
|
||||
/// </summary>
|
||||
/// <param name="o1">运算数1</param>
|
||||
/// <param name="o2">运算数2</param>
|
||||
/// <returns>返回o1-o2</returns>
|
||||
public static string TwoNumberReduce(string o1, string o2)
|
||||
{
|
||||
return (Convert.ToDecimal(o1) - Convert.ToDecimal(o2)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 乘法指令,转换为double后返回o1*o2
|
||||
/// </summary>
|
||||
/// <param name="o1">运算数1</param>
|
||||
/// <param name="o2">运算数2</param>
|
||||
/// <returns>返回o1*o2</returns>
|
||||
public static string TwoNumberMult(string o1, string o2)
|
||||
{
|
||||
return (Convert.ToDecimal(o1) * Convert.ToDecimal(o2)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 除法指令,转换为double后返回o1/o2
|
||||
/// </summary>
|
||||
/// <param name="o1">运算数1</param>
|
||||
/// <param name="o2">运算数2</param>
|
||||
/// <returns>返回o1/o2</returns>
|
||||
public static string TwoNumberDivide1(string o1, string o2)
|
||||
{
|
||||
return (Convert.ToDecimal(o1) / Convert.ToDecimal(o2)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 转换为double后返回o1/o2
|
||||
/// </summary>
|
||||
/// <param name="o1">运算数1</param>
|
||||
/// <param name="o2">运算数2</param>
|
||||
/// <param name="o3">保留几位小数</param>
|
||||
/// <returns>返回o1/o2</returns>
|
||||
public static string TwoNumberDivide2(string o1, string o2, int o3)
|
||||
{
|
||||
return (Math.Round((Convert.ToDecimal(o1) / Convert.ToDecimal(o2)), o3)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 返回o1%o2
|
||||
/// </summary>
|
||||
/// <param name="o1">运算数1</param>
|
||||
/// <param name="o2">运算数2</param>
|
||||
/// <returns>返回o1%o2</returns>
|
||||
public static string TwoNumberRemainder(string o1, string o2)
|
||||
{
|
||||
return (Convert.ToDecimal(o1) % (Convert.ToDecimal(o2))).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算两个数字的平方。
|
||||
/// </summary>
|
||||
/// <param name="o1">第一个数字的字符串表示。</param>
|
||||
/// <param name="o2">第二个数字的字符串表示。</param>
|
||||
/// <example>Param1:3 Param2:2 返回值:9</example>
|
||||
/// <example>Param1:2 Param2:3 返回值:8</example>
|
||||
/// <example>Param1:5 Param2:4 返回值:625</example>
|
||||
/// <returns>返回两个数字的平方的字符串表示。</returns>
|
||||
public static string NumberPow(string o1, string o2)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并计算它们的平方
|
||||
// 最后将结果转换为字符串并返回
|
||||
return Math.Pow(Convert.ToDouble(o1), Convert.ToDouble(o2)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算一个数的平方根。
|
||||
/// </summary>
|
||||
/// <param name="o1">要计算平方根的数字的字符串表示。</param>
|
||||
/// <returns>返回输入数字的平方根的字符串表示。</returns>
|
||||
public static string NumberSqrt(string o1)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并计算其平方根。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Sqrt(Convert.ToDouble(o1)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算一个数的绝对值。
|
||||
/// </summary>
|
||||
/// <param name="o1">要计算绝对值的数字的字符串表示。</param>
|
||||
/// <returns>返回输入数字的绝对值的字符串表示。</returns>
|
||||
public static string NumberAbs(string o1)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并计算其绝对值。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Abs(Convert.ToDouble(o1)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算一个角度的正弦值。
|
||||
/// </summary>
|
||||
/// <param name="o1">要计算正弦值的角度的字符串表示(单位为弧度)。</param>
|
||||
/// <returns>返回输入角度的正弦值的字符串表示。</returns>
|
||||
public static string NumberSin(string o1)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并计算其正弦值。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Sin(Convert.ToDouble(o1)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算一个角度的余弦值。
|
||||
/// </summary>
|
||||
/// <param name="o1">要计算余弦值的角度的字符串表示(单位为弧度)。</param>
|
||||
/// <returns>返回输入角度的余弦值的字符串表示。</returns>
|
||||
public static string NumberCos(string o1)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并计算其余弦值。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Cos(Convert.ToDouble(o1)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算一个角度的正切值。
|
||||
/// </summary>
|
||||
/// <param name="o1">要计算正切值的角度的字符串表示(单位为弧度)。</param>
|
||||
/// <returns>返回输入角度的正切值的字符串表示。</returns>
|
||||
public static string NumberTan(string o1)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并计算其正切值。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Tan(Convert.ToDouble(o1)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算一个数的反正弦值。
|
||||
/// </summary>
|
||||
/// <param name="o1">要计算反正弦值的数的字符串表示。</param>
|
||||
/// <returns>返回输入数的反正弦值的字符串表示(单位为弧度)。</returns>
|
||||
public static string NumberASin(string o1)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并计算其反正弦值。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Asin(Convert.ToDouble(o1)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算一个数的反余弦值。
|
||||
/// </summary>
|
||||
/// <param name="o1">要计算反余弦值的数的字符串表示。</param>
|
||||
/// <returns>返回输入数的反余弦值的字符串表示(单位为弧度)。</returns>
|
||||
public static string NumberACos(string o1)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并计算其反余弦值。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Acos(Convert.ToDouble(o1)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算一个数的反正切值。
|
||||
/// </summary>
|
||||
/// <param name="o1">要计算反正切值的数的字符串表示。</param>
|
||||
/// <returns>返回输入数的反正切值的字符串表示(单位为弧度)。</returns>
|
||||
public static string NumberATan(string o1)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并计算其反正切值。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Atan(Convert.ToDouble(o1)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算 e 的给定次幂。
|
||||
/// </summary>
|
||||
/// <param name="o1">e 的指数的字符串表示。</param>
|
||||
/// <returns>返回 e 的给定次幂的字符串表示。</returns>
|
||||
public static string eIndex(string o1)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并计算 e 的给定次幂。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Exp(Convert.ToDouble(o1)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算一个数的自然对数。
|
||||
/// </summary>
|
||||
/// <param name="o1">要计算自然对数的数的字符串表示。</param>
|
||||
/// <returns>返回输入数的自然对数的字符串表示。</returns>
|
||||
public static string NumberNaturalLogarithm(string o1)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并计算其自然对数。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Log(Convert.ToDouble(o1)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算一个数在指定基数下的对数。
|
||||
/// </summary>
|
||||
/// <param name="o1">要计算对数的数的字符串表示。</param>
|
||||
/// <param name="Base">对数的基数的字符串表示。</param>
|
||||
/// <returns>返回输入数在指定基数下的对数的字符串表示。</returns>
|
||||
public static string NumberLogarithm(string o1, string Base)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并计算其在指定基数下的对数。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Log(Convert.ToDouble(o1), Convert.ToDouble(Base)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 对输入数字进行四舍五入取整。
|
||||
/// </summary>
|
||||
/// <param name="n1">要进行取整操作的数字的字符串表示。</param>
|
||||
/// <returns>返回经过四舍五入取整后的结果的字符串表示。</returns>
|
||||
public static string NumberRoundToNearest(string n1)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并进行四舍五入取整。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Round(Convert.ToDouble(n1)).ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对输入数字进行向上舍入取整。
|
||||
/// </summary>
|
||||
/// <param name="n1">要进行取整操作的数字的字符串表示。</param>
|
||||
/// <returns>返回经过向上舍入取整后的结果的字符串表示。</returns>
|
||||
public static string NumberRoundUp(string n1)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并进行向上舍入取整。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Ceiling(Convert.ToDouble(n1)).ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对输入数字进行向下舍入取整。
|
||||
/// </summary>
|
||||
/// <param name="n1">要进行取整操作的数字的字符串表示。</param>
|
||||
/// <returns>返回经过向下舍入取整后的结果的字符串表示。</returns>
|
||||
public static string NumberRoundDown(string n1)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并进行向下舍入取整。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Floor(Convert.ToDouble(n1)).ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取圆周率π的字符串表示。
|
||||
/// </summary>
|
||||
/// <returns>返回圆周率π的字符串表示。</returns>
|
||||
public static string PI()
|
||||
{
|
||||
// 返回圆周率π的字符串表示。
|
||||
return Math.PI.ToString();
|
||||
}
|
||||
|
||||
//public static Random random1 = new Random();
|
||||
/// <summary>
|
||||
/// 生成一个随机整数。
|
||||
/// </summary>
|
||||
/// <returns>返回生成的随机整数的字符串表示。</returns>
|
||||
public static string RandomNumber_Int()
|
||||
{
|
||||
Random random = new Random();
|
||||
// 使用随机数生成器生成一个随机整数,并将其转换为字符串表示。
|
||||
return random.Next().ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成一个指定范围内的随机整数。
|
||||
/// </summary>
|
||||
/// <param name="最小">随机数生成范围的最小值。</param>
|
||||
/// <param name="最大">随机数生成范围的最大值(不包含)。</param>
|
||||
/// <returns>返回生成的指定范围内的随机整数的字符串表示。</returns>
|
||||
public static string RandomNumber_RangeInt(int 最小, int 最大)
|
||||
{
|
||||
Random random = new Random();
|
||||
// 使用随机数生成器生成一个指定范围内的随机整数,并将其转换为字符串表示。
|
||||
return random.Next(最小, 最大).ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成一个随机小数。
|
||||
/// </summary>
|
||||
/// <returns>返回生成的随机小数的字符串表示。</returns>
|
||||
public static string RandomNumber_Decimal()
|
||||
{
|
||||
Random random = new Random();
|
||||
// 使用随机数生成器生成一个随机小数,并将其转换为字符串表示。
|
||||
return random.NextDouble().ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算给定算式的结果。
|
||||
/// </summary>
|
||||
/// <param name="s">要计算的算式的字符串表示。</param>
|
||||
/// <returns>返回计算结果的字符串表示。</returns>
|
||||
public static string FormulaCalculation(string s)
|
||||
{
|
||||
// 创建一个 DataTable 实例来进行算式的计算。
|
||||
DataTable dataTable = new DataTable();
|
||||
// 使用 DataTable 的 Compute 方法计算给定的算式,并将结果转换为字符串表示。
|
||||
return dataTable.Compute(s, "").ToString();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
153
Command/CommandRadixChange.cs
Normal file
153
Command/CommandRadixChange.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
using Common.Attributes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Command
|
||||
{
|
||||
|
||||
[ADPCommand]
|
||||
public static class CommandRadixChange
|
||||
{
|
||||
#region 进制转换
|
||||
/// <summary>
|
||||
/// 二进制 转换成 八进制
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符类型</param>
|
||||
/// <example>Param1:"101010" 返回值:"52"</example>
|
||||
/// <returns></returns>
|
||||
public static string TwoRadix_ConvertTo_EightRadix(string Param1)
|
||||
{
|
||||
return Convert.ToString(Convert.ToInt32(Param1, 2),8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 二进制 转换成 十进制
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符类型</param>
|
||||
/// <example>Param1:"101010" 返回值:"42"</example>
|
||||
/// <returns></returns>
|
||||
public static string TwoRadix_ConvertTo_TenRadix(string Param1)
|
||||
{
|
||||
return Convert.ToString(Convert.ToInt32(Param1, 2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 二进制 转换成 十六进制
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符类型</param>
|
||||
/// <example>Param1:"101010" 返回值:"2A"</example>
|
||||
/// <returns></returns>
|
||||
public static string TwoRadix_ConvertTo_SixteenRadix(string Param1)
|
||||
{
|
||||
return Convert.ToString(Convert.ToInt32(Param1, 2), 16);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 八进制 转换成 二进制
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符类型</param>
|
||||
/// <example>Param1:"88" 返回值:"1001000"</example>
|
||||
/// <returns></returns>
|
||||
public static string EightRadix_ConvertTo_TwoRadix(string Param1)
|
||||
{
|
||||
return Convert.ToString(Convert.ToInt32(Param1, 8), 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 八进制 转换成 十进制
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符类型</param>
|
||||
/// <example>Param1:"88" 返回值:"72"</example>
|
||||
/// <returns></returns>
|
||||
public static double EightRadix_ConvertTo_TenRadix(string Param1)
|
||||
{
|
||||
double Value = Convert.ToInt32(Param1, 8);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 八进制 转换成 十六进制
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符类型</param>
|
||||
/// <example>Param1:"88" 返回值:"48"</example>
|
||||
/// <returns></returns>
|
||||
public static string EightRadix_ConvertTo_SixteenRadix(string Param1)
|
||||
{
|
||||
return Convert.ToString(Convert.ToInt32(Param1, 8), 16);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 十进制 转换成 二进制
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:整型数值</param>
|
||||
/// <example>Param1:60 返回值:"111100"</example>
|
||||
/// <returns></returns>
|
||||
public static string TenRadix_ConvertTo_TwoRadix(int Param1)
|
||||
{
|
||||
return Convert.ToString(Param1, 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 十进制 转换成 八进制
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:整型数值</param>
|
||||
/// <example>Param1:60 返回值:"74"</example>
|
||||
/// <returns></returns>
|
||||
public static string TenRadix_ConvertTo_EightRadix(int Param1)
|
||||
{
|
||||
return Convert.ToString(Param1, 8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 十进制 转换成 十六进制
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:整型数值</param>
|
||||
/// <example>Param1:60 返回值:"3C"</example>
|
||||
/// <returns></returns>
|
||||
public static string TenRadix_ConvertTo_SixteenRadix(int Param1)
|
||||
{
|
||||
return Convert.ToString(Param1, 16);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 十六进制 转换成 二进制
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符类型</param>
|
||||
/// <example>Param1:"6F" 返回值:"1101111"</example>
|
||||
/// <returns></returns>
|
||||
public static string SixteenRadix_ConvertTo_Radix2(string Param1)
|
||||
{
|
||||
return Convert.ToString(Convert.ToInt32(Param1, 16), 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 十六进制 转换成 八进制
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符类型</param>
|
||||
/// <example>Param1:"6F" 返回值:"157"</example>
|
||||
/// <returns></returns>
|
||||
public static string SixteenRadix_ConvertTo_EightRadix8(string Param1)
|
||||
{
|
||||
return Convert.ToString(Convert.ToInt32(Param1, 16), 8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 十六进制 转换成 十进制
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符类型</param>
|
||||
/// <example>Param1:"6F" 返回值:"111"</example>
|
||||
/// <returns></returns>
|
||||
public static string SixteenRadix_ConvertTo_TenRadix(string Param1)
|
||||
{
|
||||
return Convert.ToString(Convert.ToInt32(Param1, 16));
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
196
Command/CommandStringProcessing.cs
Normal file
196
Command/CommandStringProcessing.cs
Normal file
@@ -0,0 +1,196 @@
|
||||
using Common.Attributes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Command
|
||||
{
|
||||
|
||||
[ADPCommand]
|
||||
public static class CommandStringProcessing
|
||||
{
|
||||
///<summary>
|
||||
///获取值
|
||||
///</summary>
|
||||
///<param name="obj">要获取的值。</param>
|
||||
///returns>获取值(字符串)。</returns>
|
||||
public static string GetValue(object obj)
|
||||
{
|
||||
return Convert.ToString(obj) ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在原字符串中查找指定字符的位置。
|
||||
/// </summary>
|
||||
/// <param name="str">要查找的字符串。</param>
|
||||
/// <param name="findStr">要查找的字符。</param>
|
||||
/// <returns>字符在字符串中的位置,如果未找到则返回 -1。</returns>
|
||||
public static int FindStr(string str, string findStr)
|
||||
{
|
||||
return str.IndexOf(findStr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将原字符串中的指定字符替换为新字符。
|
||||
/// </summary>
|
||||
/// <param name="str">要替换的字符串。</param>
|
||||
/// <param name="ReplaceStr">要被替换的字符。</param>
|
||||
/// <param name="ReplacedStr">替换后的新字符。</param>
|
||||
/// <returns>替换后的字符串。</returns>
|
||||
public static string ReplaceStr(string str, string ReplaceStr, string ReplacedStr)
|
||||
{
|
||||
return str.Replace(ReplaceStr, ReplacedStr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从原字符串中移除指定位置开始的指定个数的字符。
|
||||
/// </summary>
|
||||
/// <param name="str">要操作的字符串。</param>
|
||||
/// <param name="startLocation">开始移除字符的位置。</param>
|
||||
/// <param name="removeQty">要移除的字符个数。</param>
|
||||
/// <returns>移除后的字符串。</returns>
|
||||
public static string RemoveStr(string str, int startLocation, int removeQty)
|
||||
{
|
||||
return str.Remove(startLocation, removeQty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在原字符串的指定位置插入新字符串。
|
||||
/// </summary>
|
||||
/// <param name="str">要操作的字符串。</param>
|
||||
/// <param name="startLocation">要插入字符串的位置。</param>
|
||||
/// <param name="newStr">要插入的字符串。</param>
|
||||
/// <returns>插入后的字符串。</returns>
|
||||
public static string InsertStr(string str, int startLocation, string newStr)
|
||||
{
|
||||
return str.Insert(startLocation, newStr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从原字符串中截取指定位置开始的指定个数的字符。
|
||||
/// </summary>
|
||||
/// <param name="str">要操作的字符串。</param>
|
||||
/// <param name="startLocation">要截取的起始位置。</param>
|
||||
/// <param name="qty">要截取的字符个数。</param>
|
||||
/// <returns>截取后的字符串。</returns>
|
||||
public static string CaptureStr(string str, int startLocation, int qty)
|
||||
{
|
||||
return str.Substring(startLocation, qty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字符串转换为小写形式。
|
||||
/// </summary>
|
||||
/// <param name="str">要转换的字符串。</param>
|
||||
/// <returns>转换为小写后的字符串。</returns>
|
||||
public static string StrConvertToLower(string str)
|
||||
{
|
||||
return str.ToLower();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字符串转换为大写形式。
|
||||
/// </summary>
|
||||
/// <param name="str">要转换的字符串。</param>
|
||||
/// <returns>转换为大写后的字符串。</returns>
|
||||
public static string StrConvertToUpper(string str)
|
||||
{
|
||||
return str.ToUpper();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取字符串的长度。
|
||||
/// </summary>
|
||||
/// <param name="str">要获取长度的字符串。</param>
|
||||
/// <returns>字符串的长度。</returns>
|
||||
public static int GetStrLength(string str)
|
||||
{
|
||||
return str.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 去除字符串两端的空白字符。
|
||||
/// </summary>
|
||||
/// <param name="str">要去除空白字符的字符串。</param>
|
||||
/// <returns>去除空白字符后的字符串。</returns>
|
||||
public static string StrRemoveLeadingAndTrailingWhitespaces(string str)
|
||||
{
|
||||
return str.Trim();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节数组转换为字符串,使用 UTF-8 编码。
|
||||
/// </summary>
|
||||
/// <param name="arr">要转换的字节数组。</param>
|
||||
/// <returns>转换后的字符串。</returns>
|
||||
public static string ByteArrayToStr_UTF8(byte[] arr)
|
||||
{
|
||||
return Encoding.UTF8.GetString(arr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节数组转换为字符串,使用 GB18030 编码。
|
||||
/// </summary>
|
||||
/// <param name="arr">要转换的字节数组。</param>
|
||||
/// <returns>转换后的字符串。</returns>
|
||||
public static string ByteArrayToStr_GB18030(byte[] arr)
|
||||
{
|
||||
return Encoding.GetEncoding("gb18030").GetString(arr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节数组转换为字符串,使用自选的编码类型。
|
||||
/// </summary>
|
||||
/// <param name="arr">要转换的字节数组。</param>
|
||||
/// <param name="encodingType">要使用的编码类型。</param>
|
||||
/// <returns>转换后的字符串。</returns>
|
||||
public static string ByteArrayToStr_SelfSelectedEncoding(byte[] arr, string encodingType)
|
||||
{
|
||||
return Encoding.GetEncoding(encodingType).GetString(arr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节数组转换为16进制字符串
|
||||
/// </summary>
|
||||
/// <param name="str">要转换的字节数组。</param>
|
||||
/// <returns>转换后的字符串。</returns>
|
||||
public static string ByteArrayTo_HexStr(byte[] str)
|
||||
{
|
||||
return Convert.ToInt32(str.ToString(),2).ToString("X2");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字符串转换为字节数组,使用 UTF-8 编码。
|
||||
/// </summary>
|
||||
/// <param name="str">要转换的字符串。</param>
|
||||
/// <returns>转换后的字节数组。</returns>
|
||||
public static byte[] StrToByteArray_UTF8(string str)
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(str);
|
||||
}
|
||||
/// <summary>
|
||||
/// 将字符串转换为字节数组,使用 GB18030 编码。
|
||||
/// </summary>
|
||||
/// <param name="str">要转换的字符串。</param>
|
||||
/// <returns>转换后的字节数组。</returns>
|
||||
public static byte[] StrToByteArray_GB18030(string str)
|
||||
{
|
||||
return Encoding.GetEncoding("gb18030").GetBytes(str);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字符串转换为字节数组,使用自选的编码类型。
|
||||
/// </summary>
|
||||
/// <param name="str">要转换的字符串。</param>
|
||||
/// <param name="encodingType">要使用的编码类型。</param>
|
||||
/// <returns>转换后的字节数组。</returns>
|
||||
public static byte[] StrToByteArray_SelfSelectedEncoding(string str, string encodingType)
|
||||
{
|
||||
return Encoding.GetEncoding(encodingType).GetBytes(str);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
463
Command/CommandSystem.cs
Normal file
463
Command/CommandSystem.cs
Normal file
@@ -0,0 +1,463 @@
|
||||
using Common.Attributes;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Command
|
||||
{
|
||||
[ADPCommand]
|
||||
public static class CommandSystem
|
||||
{
|
||||
#region 类型转换
|
||||
/// <summary>
|
||||
/// int转化为double
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:整型数值</param>
|
||||
/// <example>Param1:40 返回值:40</example>
|
||||
/// <returns></returns>
|
||||
public static double Int_ConvertTo_Double(int Param1)
|
||||
{
|
||||
return Convert.ToDouble(Param1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// double转化为Int16
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数数值</param>
|
||||
/// <example>Param1:4 返回值:4</example>
|
||||
/// <example>Param1:4.67 返回值:4</example>
|
||||
/// <returns></returns>
|
||||
public static Int16 Double_ConvertTo_Int16(double Param1)
|
||||
{
|
||||
Int16 Value = Convert.ToInt16(Param1);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// double转化为int,即Int32
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数数值</param>
|
||||
/// <example>Param1:4 返回值:4</example>
|
||||
/// <example>Param1:4.67 返回值:4</example>
|
||||
/// <returns></returns>
|
||||
public static int Double_ConvertTo_Int(double Param1)
|
||||
{
|
||||
int Value = Convert.ToInt32(Param1);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// double转化为Int64
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数数值</param>
|
||||
/// <example>Param1:4 返回值:4</example>
|
||||
/// <example>Param1:4.67 返回值:4</example>
|
||||
/// <returns></returns>
|
||||
public static Int64 Double_ConvertTo_Int64(double Param1)
|
||||
{
|
||||
Int64 Value = Convert.ToInt64(Param1);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// float转化为double
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:浮点数值</param>
|
||||
/// <example>Param1:4.0 返回值:4</example>
|
||||
/// <example>Param1:4.2 返回值:4.2</example>
|
||||
/// <returns></returns>
|
||||
public static double Float_ConvertTo_Double(float Param1)
|
||||
{
|
||||
double Value = Convert.ToDouble(Param1);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断string转化为int,转换成功返回 true,失败返回 false
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符串</param>
|
||||
/// <example>Param1:"2" 返回值:true</example>
|
||||
/// <example>Param1:"abc" 返回值:false</example>
|
||||
/// <returns></returns>
|
||||
public static bool BoolString_ConvertTo_Int(string Param1)
|
||||
{
|
||||
bool Value = int.TryParse(Param1, out int intValue);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// string转化为int
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符串</param>
|
||||
/// <example>Param1:"2" 返回值:2</example>
|
||||
/// <returns></returns>
|
||||
public static int String_ConvertTo_Int(string Param1)
|
||||
{
|
||||
int Value = Convert.ToInt32(Param1);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// int转化为string
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:整型数值</param>
|
||||
/// <example>Param1:2 返回值:"2"</example>
|
||||
/// <returns></returns>
|
||||
public static string Int_ConvertTo_String(int Param1)
|
||||
{
|
||||
string Value = Convert.ToString(Param1);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断string转化为double,转换成功返回 true,失败返回 false
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符串</param>
|
||||
/// <example>Param1:"2" 返回值:true</example>
|
||||
/// <example>Param1:"2.1" 返回值:true</example>
|
||||
/// <example>Param1:"abc" 返回值:false</example>
|
||||
/// <returns></returns>
|
||||
public static bool BoolString_ConvertTo_Double(string Param1)
|
||||
{
|
||||
bool Value = double.TryParse(Param1, out double intValue);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// string转化为double
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符串</param>
|
||||
/// <example>Param1:"2" 返回值:2</example>
|
||||
/// <example>Param1:"2.1" 返回值:2.1</example>
|
||||
/// <returns></returns>
|
||||
public static double String_ConvertTo_Double(string Param1)
|
||||
{
|
||||
double Value = Convert.ToDouble(Param1);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// double转化为string
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数数值</param>
|
||||
/// <example>Param1:2 返回值:"2"</example>
|
||||
/// <example>Param1:2.1 返回值:"2.1"</example>
|
||||
/// <returns></returns>
|
||||
public static string Double_ConvertTo_String(double Param1)
|
||||
{
|
||||
string Value = Convert.ToString(Param1);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// float转化为string
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:浮点数值</param>
|
||||
/// <example>Param1:2.0 返回值:"2.0"</example>
|
||||
/// <example>Param1:2.1 返回值:"2.1"</example>
|
||||
/// <returns></returns>
|
||||
public static string Float_ConvertTo_String(float Param1)
|
||||
{
|
||||
string Value = Convert.ToString(Param1);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断string转化为datetime,转换成功返回 true,失败返回 false
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:时间字符串</param>
|
||||
/// <example>Param1:"2025-08-14" 返回值:true</example>
|
||||
/// <example>Param1:"2025-08-14 13:14:15" 返回值:true</example>
|
||||
/// <example>Param1:"abc" 返回值:false</example>
|
||||
/// <returns></returns>
|
||||
public static bool BoolString_ConvertTo_Datetime(string Param1)
|
||||
{
|
||||
bool Value = DateTime.TryParse(Param1, out DateTime DateTimeValue);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// string转化为datetime
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:时间字符串</param>
|
||||
/// <example>Param1:"2025-08-14" 返回值:2025/8/14 0:00:00</example>
|
||||
/// <example>Param1:"2025-08-14 13:14:15" 返回值:2025/8/14 13:14:15</example>
|
||||
/// <returns></returns>
|
||||
public static DateTime String_ConvertTo_Datetime(string Param1)
|
||||
{
|
||||
DateTime Value = Convert.ToDateTime(Param1);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// datetime转化为string
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:时间</param>
|
||||
/// <example>Param1:2025/8/14 13:14:15 返回值:"2025/08/14 13:14:15"</example>
|
||||
/// <returns></returns>
|
||||
public static string Datetime_ConvertTo_String1(DateTime Param1)
|
||||
{
|
||||
string Value = Convert.ToString(Param1);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// datetime转化为string
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:时间</param>
|
||||
/// <param name="DateTimeFormat">传入值:字符串时间格式</param>
|
||||
/// <example>Param1:2025/8/14 13:14:15 DateTimeFormat:"yyyy-MM-dd" 返回值:"2025-08-14"</example>
|
||||
/// <example>Param1:2025/8/14 13:14:15 DateTimeFormat:"yyyy-MM-dd hh:mm:ss" 返回值:"2025-08-14 01:14:15"</example>
|
||||
/// <example>Param1:2025/8/14 13:14:15 DateTimeFormat:"yyyy-MM-dd HH:mm:ss" 返回值:"2025-08-14 13:14:15"</example>
|
||||
/// <example>Param1:2025/8/14 13:14:15 DateTimeFormat:"yyyyMMdd" 返回值:"20250814"</example>
|
||||
/// <returns></returns>
|
||||
public static string Datetime_ConvertTo_String2(DateTime Param1,string DateTimeFormat)
|
||||
{
|
||||
string Value = Param1.ToString(DateTimeFormat);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// bool转化为string
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:布尔值</param>
|
||||
/// <example>Param1:true 返回值:"true"</example>
|
||||
/// <example>Param1:false 返回值:"false"</example>
|
||||
/// <returns></returns>
|
||||
public static string Bool_ConvertTo_String(bool Param1)
|
||||
{
|
||||
string Value = Convert.ToString(Param1);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// string转化为bool
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符串</param>
|
||||
/// <example>Param1:"true" 返回值:true</example>
|
||||
/// <example>Param1:"false" 返回值:false</example>
|
||||
/// <example>Param1:"abc" 返回值:false</example>
|
||||
/// <returns></returns>
|
||||
public static bool String_ConvertTo_Bool(string Param1)
|
||||
{
|
||||
bool Value = bool.TryParse(Param1, out bool boolValue);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将无符号整型转换为整型
|
||||
/// </summary>
|
||||
/// <param name="u"></param>
|
||||
/// <returns></returns>
|
||||
public static int Uint_ConvertTo_Int(uint u)
|
||||
{
|
||||
return (int)u;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将对象转换为字符串
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static string Object_ConvertTo_String(object obj)
|
||||
{
|
||||
return Convert.ToString(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字符串内容转换为byte
|
||||
/// </summary>
|
||||
/// <param name="s">要转换的内容</param>
|
||||
/// <returns></returns>
|
||||
public static byte String_ConvertTo_Byte(string s)
|
||||
{
|
||||
return Convert.ToByte(s);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为short(int16)
|
||||
/// </summary>
|
||||
/// <param name="s">要转换的内容</param>
|
||||
/// <returns></returns>
|
||||
public static short String_ConvertTo_Short(string s)
|
||||
{
|
||||
return Convert.ToInt16(s);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字符串转换为无符号整数。
|
||||
/// </summary>
|
||||
/// <param name="s">要转换的字符串。</param>
|
||||
/// <returns>转换后的无符号整数。</returns>
|
||||
public static uint String_ConvertTo_Uint(string s)
|
||||
{
|
||||
return Convert.ToUInt32(s);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定进制的字符串转换为整数。
|
||||
/// </summary>
|
||||
/// <param name="s">要转换的字符串。</param>
|
||||
/// <param name="XRadix">源字符串的进制。只能为2,8,10,16</param>
|
||||
/// <returns>转换后的整数。</returns>
|
||||
public static int XRadixString_ConvertTo_Int(string s, int XRadix)
|
||||
{
|
||||
return Convert.ToInt32(s, XRadix);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region 逻辑操作
|
||||
/// <summary>
|
||||
/// 执行整数的异或操作。
|
||||
/// </summary>
|
||||
/// <param name="a">第一个整数。</param>
|
||||
/// <param name="b">第二个整数。</param>
|
||||
/// <returns>异或操作结果。</returns>
|
||||
public static int XOR(int a, int b)
|
||||
{
|
||||
return a ^ b;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行整数的位或操作。
|
||||
/// </summary>
|
||||
/// <param name="a">第一个整数。</param>
|
||||
/// <param name="b">第二个整数。</param>
|
||||
/// <returns>位或操作结果。</returns>
|
||||
public static int BitwiseOR(int a, int b)
|
||||
{
|
||||
return a | b;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行整数的位与操作。
|
||||
/// </summary>
|
||||
/// <param name="a">第一个整数。</param>
|
||||
/// <param name="b">第二个整数。</param>
|
||||
/// <returns>位与操作结果。</returns>
|
||||
public static int BitwiseAND(int a, int b)
|
||||
{
|
||||
return a & b;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将整数向左移动指定位数。
|
||||
/// </summary>
|
||||
/// <param name="a">要移动的整数。</param>
|
||||
/// <param name="BitwiseLeftShift">左移的位数。</param>
|
||||
/// <returns>移动后的结果。</returns>
|
||||
public static int LeftShift(int a, int BitwiseLeftShift)
|
||||
{
|
||||
return a << BitwiseLeftShift;
|
||||
}
|
||||
/// <summary>
|
||||
/// 将整数向右移动指定位数。
|
||||
/// </summary>
|
||||
/// <param name="a">要移动的整数。</param>
|
||||
/// <param name="BitwiseRightShift">右移的位数。</param>
|
||||
/// <returns>移动后的结果。</returns>
|
||||
public static int RightShift(int a, int BitwiseRightShift)
|
||||
{
|
||||
return a >> BitwiseRightShift;
|
||||
}
|
||||
/// <summary>
|
||||
/// 执行逻辑或操作。
|
||||
/// </summary>
|
||||
/// <param name="a">第一个逻辑值。</param>
|
||||
/// <param name="b">第二个逻辑值。</param>
|
||||
/// <returns>逻辑或操作结果。</returns>
|
||||
public static bool LogicalOR(bool a, bool b)
|
||||
{
|
||||
return a || b;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行逻辑与操作。
|
||||
/// </summary>
|
||||
/// <param name="a">第一个逻辑值。</param>
|
||||
/// <param name="b">第二个逻辑值。</param>
|
||||
/// <returns>逻辑与操作结果。</returns>
|
||||
public static bool LogicalAND(bool a, bool b)
|
||||
{
|
||||
return a && b;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节的高低位对调。
|
||||
/// </summary>
|
||||
/// <param name="b">要对调的字节。</param>
|
||||
/// <returns>对调后的字节。</returns>
|
||||
public static byte ByteHighAndLowBitSwap(byte b)
|
||||
{
|
||||
return (byte)(((b & 0x0F) << 4) | ((b & 0xF0) >> 4));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 翻转字节数组中的元素顺序。
|
||||
/// </summary>
|
||||
/// <param name="b">要翻转的字节数组。</param>
|
||||
/// <returns>翻转后的字节数组。</returns>
|
||||
public static byte[] ByteArrayReverse(byte[] b)
|
||||
{
|
||||
return b.Reverse().ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将当前线程挂起指定的时间。
|
||||
/// </summary>
|
||||
/// <param name="time">要延时的时间,以毫秒为单位。</param>
|
||||
public static void Delay_ms(int time)
|
||||
{
|
||||
Thread.Sleep(time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将当前线程挂起指定的时间。
|
||||
/// </summary>
|
||||
/// <param name="time">要延时的时间,以秒为单位。</param>
|
||||
public static void Delay_s(int time)
|
||||
{
|
||||
Thread.Sleep(time * 1000);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将当前线程挂起指定的时间。
|
||||
/// </summary>
|
||||
/// <param name="time">要延时的时间,以分钟为单位。</param>
|
||||
public static void Delay_min(int time)
|
||||
{
|
||||
Thread.Sleep(time * 1000 * 60);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将当前线程挂起指定的时间。
|
||||
/// </summary>
|
||||
/// <param name="time">要延时的时间,以小时为单位。</param>
|
||||
public static void Delay_hour(int time)
|
||||
{
|
||||
Thread.Sleep(time * 1000 * 60 * 60);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将当前线程挂起指定的时间。
|
||||
/// </summary>
|
||||
/// <param name="time">要延时的时间,以天为单位。</param>
|
||||
public static void Delay_day(int time)
|
||||
{
|
||||
Thread.Sleep(time * 1000 * 60 * 60 * 24);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
95
Command/CommandTime.cs
Normal file
95
Command/CommandTime.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using Common.Attributes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Command
|
||||
{
|
||||
|
||||
[ADPCommand]
|
||||
public static class CommandTime
|
||||
{
|
||||
#region 时间处理
|
||||
/// <summary>
|
||||
/// 获取给定时间段的总毫秒数。
|
||||
/// </summary>
|
||||
/// <param name="TimePeriod">要获取总毫秒数的时间段。</param>
|
||||
/// <returns>返回时间段的总毫秒数。</returns>
|
||||
public static double GetTimePeriodMilliseconds(TimeSpan TimePeriod)
|
||||
{
|
||||
// 返回时间段的总毫秒数。
|
||||
return TimePeriod.TotalMilliseconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取给定时间段的总秒数。
|
||||
/// </summary>
|
||||
/// <param name="TimePeriod">要获取总秒数的时间段。</param>
|
||||
/// <returns>返回时间段的总秒数。</returns>
|
||||
public static double GetTimePeriodSeconds(TimeSpan TimePeriod)
|
||||
{
|
||||
// 返回时间段的总秒数。
|
||||
return TimePeriod.TotalSeconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取给定时间段的总分钟数。
|
||||
/// </summary>
|
||||
/// <param name="TimePeriod">要获取总分钟数的时间段。</param>
|
||||
/// <returns>返回时间段的总分钟数。</returns>
|
||||
public static double GetTimePeriodMinutes(TimeSpan TimePeriod)
|
||||
{
|
||||
// 返回时间段的总分钟数。
|
||||
return TimePeriod.TotalMinutes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取给定时间段的总小时数。
|
||||
/// </summary>
|
||||
/// <param name="TimePeriod">要获取总小时数的时间段。</param>
|
||||
/// <returns>返回时间段的总小时数。</returns>
|
||||
public static double GetTimePeriodHours(TimeSpan TimePeriod)
|
||||
{
|
||||
// 返回时间段的总小时数。
|
||||
return TimePeriod.TotalHours;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取给定时间段的总天数。
|
||||
/// </summary>
|
||||
/// <param name="TimePeriod">要获取总天数的时间段。</param>
|
||||
/// <returns>返回时间段的总天数。</returns>
|
||||
public static double GetTimePeriodDays(TimeSpan TimePeriod)
|
||||
{
|
||||
// 返回时间段的总天数。
|
||||
return TimePeriod.TotalDays;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算两个日期时间之间的时间差。
|
||||
/// </summary>
|
||||
/// <param name="date1">第一个日期时间。</param>
|
||||
/// <param name="date2">第二个日期时间。</param>
|
||||
/// <returns>返回两个日期时间之间的时间差。</returns>
|
||||
public static TimeSpan GetDateReduce(DateTime date1, DateTime date2)
|
||||
{
|
||||
// 返回第一个日期时间减去第二个日期时间的时间差。
|
||||
return date1 - date2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前系统时间。
|
||||
/// </summary>
|
||||
/// <returns>返回当前系统时间。</returns>
|
||||
public static DateTime GetNowTime()
|
||||
{
|
||||
// 返回当前系统时间。
|
||||
return DateTime.Now;
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
58
Command/Delay.cs
Normal file
58
Command/Delay.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using Common.Attributes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Command
|
||||
{
|
||||
[ADPCommand]
|
||||
public static class Delay
|
||||
{
|
||||
/// <summary>
|
||||
/// 等待_毫秒
|
||||
/// </summary>
|
||||
/// <param name="millisecond"></param>
|
||||
/// <param name="ct"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task Delay_ms(int millisecond, CancellationToken ct)
|
||||
{
|
||||
await Task.Delay(millisecond, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 等待_秒
|
||||
/// </summary>
|
||||
/// <param name="second"></param>
|
||||
/// <param name="ct"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task Delay_s(float second, CancellationToken ct)
|
||||
{
|
||||
await Task.Delay((int)second * 1000, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 等待_分钟
|
||||
/// </summary>
|
||||
/// <param name="minnute"></param>
|
||||
/// <param name="ct"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task Delay_m(float minnute, CancellationToken ct)
|
||||
{
|
||||
await Task.Delay((int)minnute * 60 * 1000, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 等待_小时
|
||||
/// </summary>
|
||||
/// <param name="minnute"></param>
|
||||
/// <param name="ct"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task Delay_h(float minnute, CancellationToken ct)
|
||||
{
|
||||
await Task.Delay((int)minnute * 60 * 1000, ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
32
Common/Attributes/ADPCommandAttribute.cs
Normal file
32
Common/Attributes/ADPCommandAttribute.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Common.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// 标记可加载到指令集中的类或方法
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class ADPCommandAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 指令描述(用于工具提示)
|
||||
/// </summary>
|
||||
public string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 指令分类(用于树形视图分组)
|
||||
/// </summary>
|
||||
public string Category { get; set; } = "默认分类";
|
||||
|
||||
public ADPCommandAttribute() { }
|
||||
|
||||
public ADPCommandAttribute(string description)
|
||||
{
|
||||
Description = description;
|
||||
}
|
||||
}
|
||||
}
|
||||
15
Common/Common.csproj
Normal file
15
Common/Common.csproj
Normal file
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ncalc" Version="1.3.8" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.5-beta1" />
|
||||
<PackageReference Include="System.IO.Ports" Version="11.0.0-preview.4.26230.115" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
112
Common/MiniDump.cs
Normal file
112
Common/MiniDump.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Common
|
||||
{
|
||||
public static class MiniDump
|
||||
{
|
||||
[Flags]
|
||||
public enum Option : uint
|
||||
{
|
||||
Normal = 0x00000000,
|
||||
WithDataSegs = 0x00000001,
|
||||
WithFullMemory = 0x00000002,
|
||||
WithHandleData = 0x00000004,
|
||||
FilterMemory = 0x00000008,
|
||||
ScanMemory = 0x00000010,
|
||||
WithUnloadedModules = 0x00000020,
|
||||
WithIndirectlyReferencedMemory = 0x00000040,
|
||||
FilterModulePaths = 0x00000080,
|
||||
WithProcessThreadData = 0x00000100,
|
||||
WithPrivateReadWriteMemory = 0x00000200,
|
||||
WithoutOptionalData = 0x00000400,
|
||||
WithFullMemoryInfo = 0x00000800,
|
||||
WithThreadInfo = 0x00001000,
|
||||
WithCodeSegs = 0x00002000,
|
||||
WithoutAuxiliaryState = 0x00004000,
|
||||
WithFullAuxiliaryState = 0x00008000,
|
||||
WithPrivateWriteCopyMemory = 0x00010000,
|
||||
IgnoreInaccessibleMemory = 0x00020000,
|
||||
ValidTypeFlags = 0x0003ffff,
|
||||
}
|
||||
|
||||
enum ExceptionInfo
|
||||
{
|
||||
None,
|
||||
Present
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)] // Pack=4 is important! So it works also for x64!
|
||||
struct MiniDumpExceptionInformation
|
||||
{
|
||||
public uint ThreadId;
|
||||
public IntPtr ExceptionPointers;
|
||||
[MarshalAs(UnmanagedType.Bool)]
|
||||
public bool ClientPointers;
|
||||
}
|
||||
|
||||
// MiniDumpWriteDump function declarations
|
||||
[DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
|
||||
static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeHandle hFile, uint dumpType, ref MiniDumpExceptionInformation expParam, IntPtr userStreamParam, IntPtr callbackParam);
|
||||
|
||||
[DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
|
||||
static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeHandle hFile, uint dumpType, IntPtr expParam, IntPtr userStreamParam, IntPtr callbackParam);
|
||||
|
||||
[DllImport("kernel32.dll", EntryPoint = "GetCurrentThreadId", ExactSpelling = true)]
|
||||
static extern uint GetCurrentThreadId();
|
||||
|
||||
static bool Write(SafeHandle fileHandle, Option options, ExceptionInfo exceptionInfo, Exception exception = null)
|
||||
{
|
||||
Process currentProcess = Process.GetCurrentProcess();
|
||||
IntPtr currentProcessHandle = currentProcess.Handle;
|
||||
uint currentProcessId = (uint)currentProcess.Id;
|
||||
|
||||
MiniDumpExceptionInformation exp;
|
||||
exp.ThreadId = GetCurrentThreadId();
|
||||
exp.ClientPointers = false;
|
||||
exp.ExceptionPointers = IntPtr.Zero;
|
||||
|
||||
if (exceptionInfo == ExceptionInfo.Present && exception != null)
|
||||
{
|
||||
// Get exception pointers
|
||||
exp.ExceptionPointers = Marshal.GetExceptionPointers();
|
||||
}
|
||||
|
||||
// Write dump
|
||||
bool result = exp.ExceptionPointers == IntPtr.Zero
|
||||
? MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint)options, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero)
|
||||
: MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint)options, ref exp, IntPtr.Zero, IntPtr.Zero);
|
||||
|
||||
if (exception != null)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static Boolean TryDump(String dmpPath, Option dmpType = Option.Normal, Exception exception = null)
|
||||
{
|
||||
var path = Path.Combine(Environment.CurrentDirectory, dmpPath);
|
||||
var dir = Path.GetDirectoryName(path);
|
||||
if (dir != null && !Directory.Exists(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
|
||||
using (var fs = new FileStream(path, FileMode.Create))
|
||||
{
|
||||
return Write(fs.SafeFileHandle, dmpType, exception == null ? ExceptionInfo.None : ExceptionInfo.Present, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
104
Common/Tool/ExpressionEvaluator.cs
Normal file
104
Common/Tool/ExpressionEvaluator.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using NCalc;
|
||||
|
||||
namespace Common.Tools
|
||||
{
|
||||
public class ExpressionEvaluator
|
||||
{
|
||||
public static bool EvaluateExpression(string expression, Dictionary<string, object>? variables = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 预处理:替换中文变量名为英文别名
|
||||
var (processedExpression, processedVariables) = PreprocessExpression(expression, variables);
|
||||
|
||||
var expr = new Expression(processedExpression, EvaluateOptions.IgnoreCase);
|
||||
|
||||
if (processedVariables != null)
|
||||
{
|
||||
foreach (var kvp in processedVariables)
|
||||
{
|
||||
expr.Parameters[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
if (expr.HasErrors())
|
||||
{
|
||||
throw new ArgumentException($"条件表达式格式错误: {expr.Error}");
|
||||
}
|
||||
|
||||
var result = expr.Evaluate();
|
||||
return Convert.ToBoolean(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new ArgumentException($"条件表达式异常: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static (string, Dictionary<string, object>) PreprocessExpression(
|
||||
string expression,
|
||||
Dictionary<string, object>? variables)
|
||||
{
|
||||
if (variables == null || variables.Count == 0)
|
||||
return (expression, variables ?? new Dictionary<string, object>());
|
||||
|
||||
// 生成变量名映射 (中文 -> 英文别名)
|
||||
var chineseToAlias = new Dictionary<string, string>();
|
||||
var aliasToValue = new Dictionary<string, object>();
|
||||
int counter = 1;
|
||||
|
||||
foreach (var key in variables.Keys)
|
||||
{
|
||||
if (ContainsChinese(key))
|
||||
{
|
||||
string alias = $"var_{counter}";
|
||||
counter++;
|
||||
chineseToAlias[key] = alias;
|
||||
aliasToValue[alias] = variables[key];
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有中文变量名,直接返回原始数据
|
||||
if (chineseToAlias.Count == 0)
|
||||
return (expression, variables);
|
||||
|
||||
// 替换表达式中的中文变量名
|
||||
string processedExpression = expression;
|
||||
foreach (var pair in chineseToAlias)
|
||||
{
|
||||
// 使用正则确保完整匹配变量名
|
||||
string pattern = $@"\b{Regex.Escape(pair.Key)}\b";
|
||||
processedExpression = Regex.Replace(
|
||||
processedExpression,
|
||||
pattern,
|
||||
pair.Value,
|
||||
RegexOptions.Compiled
|
||||
);
|
||||
}
|
||||
|
||||
// 创建新变量字典(英文别名 + 原始英文变量)
|
||||
var newVariables = new Dictionary<string, object>(aliasToValue);
|
||||
foreach (var key in variables.Keys)
|
||||
{
|
||||
if (!ContainsChinese(key))
|
||||
{
|
||||
newVariables[key] = variables[key];
|
||||
}
|
||||
}
|
||||
|
||||
return (processedExpression, newVariables);
|
||||
}
|
||||
|
||||
// 检查字符串是否包含中文字符
|
||||
private static bool ContainsChinese(string text)
|
||||
{
|
||||
return text.Any(c => c >= 0x4E00 && c <= 0x9FFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
16
DeviceCommand/Base/IBaseInterface.cs
Normal file
16
DeviceCommand/Base/IBaseInterface.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using NModbus;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Base
|
||||
{
|
||||
public interface IBaseInterface
|
||||
{
|
||||
public bool IsConnected { get; }
|
||||
public Task<bool> ConnectAsync(CancellationToken ct = default);
|
||||
public void Close();
|
||||
}
|
||||
}
|
||||
19
DeviceCommand/Base/IModbusDevice.cs
Normal file
19
DeviceCommand/Base/IModbusDevice.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using NModbus;
|
||||
|
||||
|
||||
namespace DeviceCommand.Base
|
||||
{
|
||||
public interface IModbusDevice : IBaseInterface,IDisposable
|
||||
{
|
||||
IModbusMaster Modbus { get; }
|
||||
|
||||
Task WriteSingleRegisterAsync(byte slaveAddress, ushort registerAddress, ushort value, CancellationToken ct = default);
|
||||
Task WriteMultipleRegistersAsync(byte slaveAddress, ushort startAddress, ushort[] values, CancellationToken ct = default);
|
||||
Task<ushort[]> ReadHoldingRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default);
|
||||
|
||||
Task WriteSingleCoilAsync(byte slaveAddress, ushort coilAddress, bool value, CancellationToken ct = default);
|
||||
Task<bool[]> ReadCoilsAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default);
|
||||
|
||||
Task<ushort[]> ReadInputRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default);
|
||||
}
|
||||
}
|
||||
13
DeviceCommand/Base/ISerialPort.cs
Normal file
13
DeviceCommand/Base/ISerialPort.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Base
|
||||
{
|
||||
public interface ISerialPort : IBaseInterface,IDisposable
|
||||
{
|
||||
Task SendAsync(string data, CancellationToken ct = default);
|
||||
Task<string> ReadAsync(string delimiter = "\n", CancellationToken ct = default);
|
||||
Task<string> WriteReadAsync(string command, string delimiter = "\n", CancellationToken ct = default);
|
||||
}
|
||||
}
|
||||
15
DeviceCommand/Base/ITcp.cs
Normal file
15
DeviceCommand/Base/ITcp.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Base
|
||||
{
|
||||
public interface ITcp : IBaseInterface,IDisposable
|
||||
{
|
||||
Task SendAsync(byte[] buffer, CancellationToken ct = default);
|
||||
Task SendAsync(string str, CancellationToken ct = default);
|
||||
Task<byte[]> ReadAsync(int length, CancellationToken ct = default);
|
||||
Task<string> ReadAsync(string delimiter = "\n", CancellationToken ct = default);
|
||||
Task<string> WriteReadAsync(string command, string delimiter = "\n", CancellationToken ct = default);
|
||||
}
|
||||
}
|
||||
164
DeviceCommand/Base/ModbusRtu.cs
Normal file
164
DeviceCommand/Base/ModbusRtu.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
using NModbus;
|
||||
using NModbus.Serial;
|
||||
using System;
|
||||
using System.IO.Ports;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Base
|
||||
{
|
||||
public class ModbusRtu : IModbusDevice
|
||||
{
|
||||
public string PortName { get; private set; } = "COM1";
|
||||
public int BaudRate { get; private set; } = 9600;
|
||||
public int DataBits { get; private set; } = 8;
|
||||
public StopBits StopBits { get; private set; } = StopBits.One;
|
||||
public Parity Parity { get; private set; } = Parity.None;
|
||||
public int ReadTimeout { get; private set; } = 3000;
|
||||
public int WriteTimeout { get; private set; } = 3000;
|
||||
|
||||
private SerialPort _serialPort;
|
||||
public IModbusMaster Modbus { get; private set; }
|
||||
public bool IsConnected => _serialPort?.IsOpen ?? false;
|
||||
|
||||
protected readonly SemaphoreSlim _commLock = new(1, 1);
|
||||
|
||||
public ModbusRtu()
|
||||
{
|
||||
_serialPort = new SerialPort();
|
||||
}
|
||||
|
||||
public void ConfigureDevice(string portName, int baudRate, int dataBits = 8, StopBits stopBits = StopBits.One, Parity parity = Parity.None, int readTimeout = 3000, int writeTimeout = 3000)
|
||||
{
|
||||
PortName = portName;
|
||||
BaudRate = baudRate;
|
||||
DataBits = dataBits;
|
||||
StopBits = stopBits;
|
||||
Parity = parity;
|
||||
ReadTimeout = readTimeout;
|
||||
WriteTimeout = writeTimeout;
|
||||
}
|
||||
|
||||
public virtual async Task<bool> ConnectAsync(CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
if (_serialPort.IsOpen)
|
||||
_serialPort.Close();
|
||||
|
||||
_serialPort.PortName = PortName;
|
||||
_serialPort.BaudRate = BaudRate;
|
||||
_serialPort.DataBits = DataBits;
|
||||
_serialPort.StopBits = StopBits;
|
||||
_serialPort.Parity = Parity;
|
||||
_serialPort.ReadTimeout = ReadTimeout;
|
||||
_serialPort.WriteTimeout = WriteTimeout;
|
||||
_serialPort.Open();
|
||||
|
||||
Modbus = new ModbusFactory().CreateRtuMaster(_serialPort);
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Close()
|
||||
{
|
||||
if (_serialPort.IsOpen)
|
||||
_serialPort.Close();
|
||||
}
|
||||
|
||||
public async Task WriteSingleRegisterAsync(byte slaveAddress, ushort registerAddress, ushort value, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
await Modbus.WriteSingleRegisterAsync(slaveAddress, registerAddress, value)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(WriteTimeout), ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task WriteMultipleRegistersAsync(byte slaveAddress, ushort startAddress, ushort[] values, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
await Modbus.WriteMultipleRegistersAsync(slaveAddress, startAddress, values)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(WriteTimeout), ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ushort[]> ReadHoldingRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
return await Modbus.ReadHoldingRegistersAsync(slaveAddress, startAddress, numberOfPoints)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(ReadTimeout), ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task WriteSingleCoilAsync(byte slaveAddress, ushort coilAddress, bool value, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
await Modbus.WriteSingleCoilAsync(slaveAddress, coilAddress, value)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(WriteTimeout), ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool[]> ReadCoilsAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
return await Modbus.ReadCoilsAsync(slaveAddress, startAddress, numberOfPoints)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(ReadTimeout), ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ushort[]> ReadInputRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
return await Modbus.ReadInputRegistersAsync(slaveAddress, startAddress, numberOfPoints)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(ReadTimeout), ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_serialPort?.Dispose();
|
||||
_commLock?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
158
DeviceCommand/Base/ModbusTcp.cs
Normal file
158
DeviceCommand/Base/ModbusTcp.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
using NModbus;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Base
|
||||
{
|
||||
public class ModbusTcp : IModbusDevice
|
||||
{
|
||||
public string IPAddress { get; private set; } = "127.0.0.1";
|
||||
public int Port { get; private set; } = 502;
|
||||
public int SendTimeout { get; private set; } = 3000;
|
||||
public int ReceiveTimeout { get; private set; } = 3000;
|
||||
|
||||
private TcpClient _tcpClient;
|
||||
public IModbusMaster Modbus { get; private set; }
|
||||
public bool IsConnected => _tcpClient?.Connected ?? false;
|
||||
|
||||
protected readonly SemaphoreSlim _commLock = new(1, 1);
|
||||
|
||||
public ModbusTcp()
|
||||
{
|
||||
_tcpClient = new TcpClient();
|
||||
}
|
||||
|
||||
public void ConfigureDevice(string ipAddress, int port, int sendTimeout = 3000, int receiveTimeout = 3000)
|
||||
{
|
||||
IPAddress = ipAddress;
|
||||
Port = port;
|
||||
SendTimeout = sendTimeout;
|
||||
ReceiveTimeout = receiveTimeout;
|
||||
}
|
||||
|
||||
public virtual async Task<bool> ConnectAsync(CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
if (_tcpClient.Connected)
|
||||
{
|
||||
var remoteEndPoint = (IPEndPoint)_tcpClient.Client.RemoteEndPoint!;
|
||||
if (remoteEndPoint.Address.MapToIPv4().ToString() == IPAddress && remoteEndPoint.Port == Port)
|
||||
return true;
|
||||
}
|
||||
|
||||
_tcpClient.Close();
|
||||
_tcpClient.Dispose();
|
||||
_tcpClient = new TcpClient();
|
||||
|
||||
await _tcpClient.ConnectAsync(IPAddress, Port, ct);
|
||||
Modbus = new ModbusFactory().CreateMaster(_tcpClient);
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Close()
|
||||
{
|
||||
if (_tcpClient.Connected) _tcpClient.Close();
|
||||
}
|
||||
|
||||
public async Task WriteSingleRegisterAsync(byte slaveAddress, ushort registerAddress, ushort value, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
// 修复:FromMinutes 改为 FromMilliseconds
|
||||
await Modbus.WriteSingleRegisterAsync(slaveAddress, registerAddress, value)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(SendTimeout), ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task WriteMultipleRegistersAsync(byte slaveAddress, ushort startAddress, ushort[] values, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
await Modbus.WriteMultipleRegistersAsync(slaveAddress, startAddress, values)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(SendTimeout), ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ushort[]> ReadHoldingRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
return await Modbus.ReadHoldingRegistersAsync(slaveAddress, startAddress, numberOfPoints)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(ReceiveTimeout), ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task WriteSingleCoilAsync(byte slaveAddress, ushort coilAddress, bool value, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
await Modbus.WriteSingleCoilAsync(slaveAddress, coilAddress, value)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(SendTimeout), ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool[]> ReadCoilsAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
return await Modbus.ReadCoilsAsync(slaveAddress, startAddress, numberOfPoints)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(ReceiveTimeout), ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ushort[]> ReadInputRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
return await Modbus.ReadInputRegistersAsync(slaveAddress, startAddress, numberOfPoints)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(ReceiveTimeout), ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_tcpClient?.Dispose();
|
||||
_commLock?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
156
DeviceCommand/Base/Serial_Port.cs
Normal file
156
DeviceCommand/Base/Serial_Port.cs
Normal file
@@ -0,0 +1,156 @@
|
||||
using System;
|
||||
using System.IO.Ports;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Base
|
||||
{
|
||||
public class Serial_Port : ISerialPort
|
||||
{
|
||||
public string PortName { get; set; } = "COM1";
|
||||
public int BaudRate { get; set; } = 9600;
|
||||
public int DataBits { get; set; } = 8;
|
||||
public StopBits StopBits { get; set; } = StopBits.One;
|
||||
public Parity Parity { get; set; } = Parity.None;
|
||||
public int ReadTimeout { get; set; } = 3000;
|
||||
public int WriteTimeout { get; set; } = 3000;
|
||||
|
||||
private SerialPort _serialPort;
|
||||
public bool IsConnected => _serialPort?.IsOpen ?? false;
|
||||
protected readonly SemaphoreSlim commLock = new(1, 1);
|
||||
|
||||
public Serial_Port()
|
||||
{
|
||||
_serialPort = new SerialPort();
|
||||
}
|
||||
|
||||
public void ConfigureDevice(string portName, int baudRate, int dataBits = 8, StopBits stopBits = StopBits.One, Parity parity = Parity.None, int readTimeout = 3000, int writeTimeout = 3000)
|
||||
{
|
||||
PortName = portName;
|
||||
BaudRate = baudRate;
|
||||
DataBits = dataBits;
|
||||
StopBits = stopBits;
|
||||
Parity = parity;
|
||||
ReadTimeout = readTimeout;
|
||||
WriteTimeout = writeTimeout;
|
||||
}
|
||||
|
||||
public virtual async Task<bool> ConnectAsync(CancellationToken ct = default)
|
||||
{
|
||||
await commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
if (_serialPort.IsOpen) _serialPort.Close();
|
||||
|
||||
_serialPort.PortName = PortName;
|
||||
_serialPort.BaudRate = BaudRate;
|
||||
_serialPort.DataBits = DataBits;
|
||||
_serialPort.StopBits = StopBits;
|
||||
_serialPort.Parity = Parity;
|
||||
_serialPort.ReadTimeout = ReadTimeout;
|
||||
_serialPort.WriteTimeout = WriteTimeout;
|
||||
|
||||
_serialPort.Open();
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Close()
|
||||
{
|
||||
if (_serialPort.IsOpen) _serialPort.Close();
|
||||
}
|
||||
|
||||
// 内部无锁发送方法,供原子组合操作调用
|
||||
private async Task LoglessSendAsync(string data, CancellationToken ct)
|
||||
{
|
||||
if (!_serialPort.IsOpen) throw new InvalidOperationException("串口未打开。");
|
||||
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(data);
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
if (WriteTimeout > 0) cts.CancelAfter(WriteTimeout);
|
||||
|
||||
await _serialPort.BaseStream.WriteAsync(bytes, 0, bytes.Length, cts.Token);
|
||||
}
|
||||
|
||||
public async Task SendAsync(string data, CancellationToken ct = default)
|
||||
{
|
||||
await commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
await LoglessSendAsync(data, ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
// 内部无锁读取方法,利用 BaseStream 挂起线程,高性能不吃 CPU
|
||||
private async Task<string> LoglessReadAsync(string delimiter, CancellationToken ct)
|
||||
{
|
||||
if (!_serialPort.IsOpen) throw new InvalidOperationException("串口未打开。");
|
||||
|
||||
delimiter ??= "\n";
|
||||
var sb = new StringBuilder();
|
||||
byte[] buffer = new byte[1024];
|
||||
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
if (ReadTimeout > 0) cts.CancelAfter(ReadTimeout);
|
||||
|
||||
while (!cts.Token.IsCancellationRequested)
|
||||
{
|
||||
// 核心优化:利用流异步挂起,替代原先的 BytesToRead 循环延时
|
||||
int bytesRead = await _serialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length, cts.Token);
|
||||
if (bytesRead == 0) continue;
|
||||
|
||||
sb.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
|
||||
|
||||
int index = sb.ToString().IndexOf(delimiter, StringComparison.Ordinal);
|
||||
if (index >= 0)
|
||||
{
|
||||
return sb.ToString(0, index).Trim();
|
||||
}
|
||||
}
|
||||
throw new TimeoutException("读取数据超时");
|
||||
}
|
||||
|
||||
public async Task<string> ReadAsync(string delimiter = "\n", CancellationToken ct = default)
|
||||
{
|
||||
await commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
return await LoglessReadAsync(delimiter, ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
// 核心优化:保证多线程环境下发送和等待回包是一个原子过程
|
||||
public async Task<string> WriteReadAsync(string command, string delimiter = "\n", CancellationToken ct = default)
|
||||
{
|
||||
await commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
await LoglessSendAsync(command, ct);
|
||||
return await LoglessReadAsync(delimiter, ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_serialPort?.Dispose();
|
||||
commLock?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
179
DeviceCommand/Base/TCP.cs
Normal file
179
DeviceCommand/Base/TCP.cs
Normal file
@@ -0,0 +1,179 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Base
|
||||
{
|
||||
public class Tcp : ITcp
|
||||
{
|
||||
public string IPAddress { get; set; } = "127.0.0.1";
|
||||
public int Port { get; set; } = 502;
|
||||
public int SendTimeout { get; set; } = 3000;
|
||||
public int ReceiveTimeout { get; set; } = 3000;
|
||||
|
||||
private TcpClient _tcpClient;
|
||||
public bool IsConnected => _tcpClient?.Connected ?? false;
|
||||
protected readonly SemaphoreSlim _commLock = new(1, 1);
|
||||
|
||||
public Tcp()
|
||||
{
|
||||
_tcpClient = new TcpClient();
|
||||
}
|
||||
|
||||
public void ConfigureDevice(string ipAddress, int port, int sendTimeout = 3000, int receiveTimeout = 3000)
|
||||
{
|
||||
IPAddress = ipAddress;
|
||||
Port = port;
|
||||
SendTimeout = sendTimeout;
|
||||
ReceiveTimeout = receiveTimeout;
|
||||
}
|
||||
|
||||
public virtual async Task<bool> ConnectAsync(CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
if (_tcpClient.Connected) return true;
|
||||
|
||||
// 修复:释放并彻底清空旧的连接实例,否则复用引发异常
|
||||
_tcpClient.Close();
|
||||
_tcpClient.Dispose();
|
||||
|
||||
_tcpClient = new TcpClient();
|
||||
await _tcpClient.ConnectAsync(IPAddress, Port, ct);
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Close()
|
||||
{
|
||||
if (_tcpClient.Connected) _tcpClient.Close();
|
||||
}
|
||||
|
||||
private async Task LoglessSendAsync(byte[] buffer, CancellationToken ct)
|
||||
{
|
||||
if (!IsConnected) throw new InvalidOperationException("TCP未连接。");
|
||||
|
||||
NetworkStream stream = _tcpClient.GetStream();
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
if (SendTimeout > 0) cts.CancelAfter(SendTimeout);
|
||||
|
||||
await stream.WriteAsync(buffer, 0, buffer.Length, cts.Token);
|
||||
}
|
||||
|
||||
public async Task SendAsync(byte[] buffer, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
await LoglessSendAsync(buffer, ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SendAsync(string str, CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync(Encoding.UTF8.GetBytes(str), ct);
|
||||
}
|
||||
|
||||
public async Task<byte[]> ReadAsync(int length, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
if (!IsConnected) throw new InvalidOperationException("TCP未连接。");
|
||||
|
||||
NetworkStream stream = _tcpClient.GetStream();
|
||||
byte[] buffer = new byte[length];
|
||||
int offset = 0;
|
||||
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
if (ReceiveTimeout > 0) cts.CancelAfter(ReceiveTimeout);
|
||||
|
||||
while (offset < length)
|
||||
{
|
||||
int read = await stream.ReadAsync(buffer, offset, length - offset, cts.Token);
|
||||
if (read == 0) break;
|
||||
offset += read;
|
||||
}
|
||||
|
||||
return offset == 0 ? Array.Empty<byte>() : buffer[..offset];
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> LoglessReadAsync(string delimiter, CancellationToken ct)
|
||||
{
|
||||
if (!IsConnected) throw new InvalidOperationException("TCP未连接。");
|
||||
|
||||
delimiter ??= "\n";
|
||||
var sb = new StringBuilder();
|
||||
byte[] buffer = new byte[1024];
|
||||
NetworkStream stream = _tcpClient.GetStream();
|
||||
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
if (ReceiveTimeout > 0) cts.CancelAfter(ReceiveTimeout);
|
||||
|
||||
while (!cts.Token.IsCancellationRequested)
|
||||
{
|
||||
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cts.Token);
|
||||
if (bytesRead == 0) throw new IOException("远程主机已关闭连接");
|
||||
|
||||
sb.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
|
||||
|
||||
int index = sb.ToString().IndexOf(delimiter, StringComparison.Ordinal);
|
||||
if (index >= 0)
|
||||
return sb.ToString(0, index).Trim();
|
||||
}
|
||||
|
||||
throw new TimeoutException("读取数据超时");
|
||||
}
|
||||
|
||||
public async Task<string> ReadAsync(string delimiter = "\n", CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
return await LoglessReadAsync(delimiter, ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
// 核心优化:确保发送与读取在同一组锁生命周期内
|
||||
public async Task<string> WriteReadAsync(string command, string delimiter = "\n", CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
await LoglessSendAsync(Encoding.UTF8.GetBytes(command), ct);
|
||||
return await LoglessReadAsync(delimiter, ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_tcpClient?.Dispose();
|
||||
_commLock?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
18
DeviceCommand/DeviceCommand.csproj
Normal file
18
DeviceCommand/DeviceCommand.csproj
Normal file
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NModbus" Version="3.0.83" />
|
||||
<PackageReference Include="NModbus.Serial" Version="3.0.83" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
249
DeviceCommand/Devices/IT7800E.cs
Normal file
249
DeviceCommand/Devices/IT7800E.cs
Normal file
@@ -0,0 +1,249 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Device
|
||||
{
|
||||
[ADPCommand]
|
||||
public class IT7800E : Tcp
|
||||
{
|
||||
// 根据通用 SCPI 指令规范,使用换行符 (ASCII 字符 LF,即 \n) 作为标准结束符
|
||||
private const string ScpiDelimiter = "\n";
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数:初始化 IT7800E 交直流电源通信参数
|
||||
/// </summary>
|
||||
public IT7800E(string ipAddress, int port, int sendTimeout, int receiveTimeout)
|
||||
{
|
||||
ConfigureDevice(ipAddress, port, sendTimeout, receiveTimeout);
|
||||
}
|
||||
|
||||
#region 1. IEEE 488.2 公共命令
|
||||
|
||||
/// <summary>
|
||||
/// 清除状态命令。清除标准事件状态寄存器和错误队列
|
||||
/// </summary>
|
||||
public virtual async Task 清除错误队列和状态字节(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*CLS{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取电源识别字符串(制造商、产品型号、系统 SN、软件版本号)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 复位命令。使电源恢复到出厂默认预定义安全值
|
||||
/// </summary>
|
||||
public virtual async Task 重置设备(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*RST{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取状态字节寄存器
|
||||
/// </summary>
|
||||
public virtual async Task<string> 读取状态字节(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*STB?{ScpiDelimiter}", ScpiDelimiter, ct); //
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 2. 耦合模式与拉载开关控制
|
||||
|
||||
/// <summary>
|
||||
/// 设置电源输出源的电气模式/工作耦合模式
|
||||
/// (AC: 纯交流, DC: 纯直流, ACDC: 交直流混合模式)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电源模式(string 模式, CancellationToken ct = default)
|
||||
{
|
||||
string modeUpper = 模式.ToUpper();
|
||||
if (modeUpper != "AC" && modeUpper != "DC" && modeUpper != "ACDC")
|
||||
throw new ArgumentException("工作模式只能为 AC, DC, 或 ACDC");
|
||||
|
||||
await SendAsync($":SOURce:MODE {modeUpper}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询电源当前的工作模式
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询电源模式(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":SOURce:MODE?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 控制电源的主输出开关 (True: 开启输出, False: 关闭输出)
|
||||
/// </summary>
|
||||
public virtual async Task 设置DC输出(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
// 注意:虽然方法名叫设置DC输出,但对交直流电源它代表通用的主输出控制(Main Output)
|
||||
string 参数 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($":OUTPut {参数}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询电源当前主输出开关状态 (返回 ON 或 OFF)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询DC输出状态(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":OUTPut?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3. 设定输出参数设置 (AC 电压/频率/DC 电压)
|
||||
|
||||
/// <summary>
|
||||
/// 设定交流(AC)模式下的电压有效值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置交流电压(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage:AC {0:F2}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设定直流(DC)模式或交直流(AC+DC)模式下的直流偏置电压值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置直流电压(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage:DC {0:F2}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设定电源交流输出的频率值 (单位: Hz)
|
||||
/// </summary>
|
||||
public virtual async Task 设置频率(double 频率, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:FREQuency {0:F2}{1}", 频率, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置硬件交流输出的有效值限流门限 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电流(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:CURRent {0:F3}{1}", 电流, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 4. 测量与数据回测(高频数据轮询核心)
|
||||
|
||||
/// <summary>
|
||||
/// 查询通道实时测得的电压有效值(RMS,支持 AC 或 DC 模式下的回测)(单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际电压(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询通道实时测得的电流有效值(RMS)(单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际电流(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询通道实时测得的有功功率值 (单位: W)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际功率(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:POWer?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询通道实时测得的视在功率 (单位: VA)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询视在功率(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:POWer:APParent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询通道实时测得的输出频率 (单位: Hz)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际频率(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:FREQuency?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询当前的功率因数 (PF)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询功率因数(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:PF?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 5. 保护参数设置与系统监控
|
||||
|
||||
/// <summary>
|
||||
/// 设置电源的过流保护(OCP)值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过流保护_OCP(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:CURRent:PROTection {0:F3}{1}", 电流, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置电源的过压保护(OVP)值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过压保护_OVP(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage:PROTection {0:F2}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取系统的出错记录信息(获取上一条未处理的错误码)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询错误信息(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":SYSTem:ERRor?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换仪器至计算机控制的远程控制模式 (锁定前面板按键)
|
||||
/// </summary>
|
||||
public virtual async Task 切换远程控制模式(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:REMote{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换仪器至前面板操作的本地模式 (前面板解锁)
|
||||
/// </summary>
|
||||
public virtual async Task 切换本地控制模式(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:LOCal{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除仪器当前的保护状态(例如触发 OCP/OVP 被锁死后进行软件解锁)
|
||||
/// </summary>
|
||||
public virtual async Task 清除保护告警(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":PROTection:CLEar{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
382
DeviceCommand/Devices/N36200.cs
Normal file
382
DeviceCommand/Devices/N36200.cs
Normal file
@@ -0,0 +1,382 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Device
|
||||
{
|
||||
[ADPCommand]
|
||||
public class N36200 : Tcp
|
||||
{
|
||||
// 手册第 9 页 2.2.4 明确规定:命令结束符为换行符 (ASCII 字符 LF,即 \n)
|
||||
private const string ScpiDelimiter = "\n";
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数:初始化 N36200/N36300 设备通信参数
|
||||
/// </summary>
|
||||
public N36200(string ipAddress, int port, int sendTimeout, int receiveTimeout)
|
||||
{
|
||||
ConfigureDevice(ipAddress, port, sendTimeout, receiveTimeout);
|
||||
}
|
||||
|
||||
#region 3.1. IEEE 488.2 公共命令
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.1. 清除标准事件状态寄存器和错误队列
|
||||
/// </summary>
|
||||
public virtual async Task 清除错误队列和状态字节(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*CLS{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.4. 读取直流电源相关信息(制造商、产品型号、系统 SN、软件版本号)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.7. 恢复出厂设置 (注意:设备重置保存数据大约需要 10 秒)
|
||||
/// </summary>
|
||||
public virtual async Task 重置设备(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*RST{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.8. 读取状态字节寄存器(只读寄存器,读取时不会清除位)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 读取状态字节(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*STB?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.2. 设定输出电压与限流值
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.1. 设定输出电压值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电压(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "SOURce:VOLTage {0:F3}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.1. 查询输出电压设定值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询电压设定(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"SOURce:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.2. 设置输出限流值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电流(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "SOURce:CURRent {0:F3}{1}", 电流, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.2. 查询输出限流值设定值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询电流设定(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"SOURce:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.3. 设置输出模拟内阻值 (单位: mΩ)
|
||||
/// </summary>
|
||||
public virtual async Task 设置内阻(double 内阻, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "SOURce:INTErnalres {0:F1}{1}", 内阻, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.4. 保存当前测试参数到指定存储组 (范围: 1-10)
|
||||
/// </summary>
|
||||
public virtual async Task 保存测试参数(int 组别, CancellationToken ct = default)
|
||||
{
|
||||
if (组别 < 1 || 组别 > 10) throw new ArgumentOutOfRangeException(nameof(组别), "组别有效范围为 1~10");
|
||||
await SendAsync($"SOURce:FUNCtion:SAVe {组别}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.5. 调用指定存储组的测试参数 (范围: 1-10)
|
||||
/// </summary>
|
||||
public virtual async Task 调用测试参数(int 组别, CancellationToken ct = default)
|
||||
{
|
||||
if (组别 < 1 || 组别 > 10) throw new ArgumentOutOfRangeException(nameof(组别), "组别有效范围为 1~10");
|
||||
await SendAsync($"SOURce:FUNCtion:RECAll {组别}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.3. 输出控制及状态
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.1. 控制电源输出开关 (True: 开启, False: 关闭)
|
||||
/// </summary>
|
||||
public virtual async Task 设置DC输出(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
string 参数 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($"OUTPut:ONOFF {参数}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.1. 查询电源输出开关状态 (返回 "ON" 或 "OFF")
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询DC输出开关状态(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"OUTPut:ONOFF?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.2. 设定电源工作模式
|
||||
/// (NORMal: 普通模式, CHARge: 电池充电, SEQuence: 序列模式, CPOWer: 恒功率模式, CARWave: 汽车测试, APG: 外部编程)
|
||||
/// </summary>
|
||||
public virtual async Task 设置运行模式(string 模式, CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"OUTPut:MODE {模式}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.2. 查询电源当前运行工作模式
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询运行模式(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"OUTPut:MODE?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.11. 设置电源输出 CV/CC 优先权 (CV 或 CC)
|
||||
/// </summary>
|
||||
public virtual async Task 设置CVCC优先权(string 优先模式, CancellationToken ct = default)
|
||||
{
|
||||
if (优先模式 != "CV" && 优先模式 != "CC") throw new ArgumentException("优先模式只能为 'CV' 或 'CC'");
|
||||
await SendAsync($"OUTPut:PRIority {优先模式}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.12. 获取电源状态字(通过解析返回整数的二进制 Bit 位获取全状态环路及告警)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备状态字(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"OUTPut:STATe?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.13. 获取电源事件告警状态值 (返回整数通过位定义标识 UVP/OVP/OCP/OPP/OTP)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询事件告警状态(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"OUTPut:EVENT?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.13. 清除当前的告警状态
|
||||
/// </summary>
|
||||
public virtual async Task 清除告警(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"OUTPut:EVENT 0{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.16. 打开/关闭设备定时关机功能
|
||||
/// </summary>
|
||||
public virtual async Task 设置定时关机开关(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
string 参数 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($"OUTPut:TIMing:SWITch {参数}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.17. 设定设备定时关机倒计时时间 (单位: s)
|
||||
/// </summary>
|
||||
public virtual async Task 设置定时关机时间(double 秒数, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "OUTPut:TIMing:DWELI {0:F1}{1}", 秒数, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.18. 控制泄放电路(Bleeder)开关状态
|
||||
/// </summary>
|
||||
public virtual async Task 设置泄放电路开关(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
string 参数 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($"OUTPut:DISRes {参数}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.4. 读取输出电压电流及功率值 (实时轮询核心)
|
||||
|
||||
/// <summary>
|
||||
/// 3.4.1. 回读通道输出端子上的实时测得电压值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际电压(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.4.2. 回读通道输出端子上的实时测得电流值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际电流(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.4.3. 回读通道输出端子上的实时测得功率值 (单位: W)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际功率(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:POWer?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.4.4. 回读电池充电模式下当前累计已充入的容量值 (单位: mAh)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询已充电容量MAH(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:MAH?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.4.5. 获取当前电源的硬件额定电压上限值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 获取设备额定电压(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:VOLTage:MAXimum?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.4.6. 获取当前电源的硬件额定电流上限值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 获取设备额定电流(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:CURRent:MAXimum?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.5. 保护功能
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.1. 设置欠压保护门限值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过欠压保护_UVP(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "PROTect:LESS:VOLTage {0:F3}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.4. 设置过压保护门限值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过压保护_OVP(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "PROTect:OVER:VOLTage {0:F3}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.6. 设置硬件硬件过流保护硬指标参数 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过流保护_OCP(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "PROTect:CURRent {0:F3}{1}", 电流, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.8. 设置硬件过功率保护门限值 (单位: W)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过功率保护_OPP(double 功率, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "PROTect:POWer {0:F3}{1}", 功率, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.12. 设置可调节的用户软输出电压下限值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电压下限(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "SOURce:VOLTage:LEVel:LIMit:LOW {0:F3}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.13. 设置可调节的用户软输出电压上限值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电压上限(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "SOURce:VOLTage:LEVel:LIMit {0:F3}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.14. 设置可调节的用户软输出电流下限值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电流下限(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "SOURce:CURRent:LEVel:LIMit:LOW {0:F3}{1}", 电流, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.15. 设置可调节的用户软输出电流上限值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电流上限(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "SOURce:CURRent:LEVel:LIMit {0:F3}{1}", 电流, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.6. 恒功率模式配置 (CPOWer)
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.1. 设定恒功率工作模式下的限定电压值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒功率模式电压(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "CPOWer:VOLTage {0:F3}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.2. 设定恒功率工作模式下的限定电流值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒功率模式电流(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "CPOWer:CURRent {0:F3}{1}", 电流, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.3. 设定恒功率工作模式下的运行功率目标值 (单位: W)
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒功率模式功率(double 功率, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "CPOWer:POWer {0:F3}{1}", 功率, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
459
DeviceCommand/Devices/N36600.cs
Normal file
459
DeviceCommand/Devices/N36600.cs
Normal file
@@ -0,0 +1,459 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Device
|
||||
{
|
||||
[ADPCommand]
|
||||
public class N36600 : Tcp
|
||||
{
|
||||
private CancellationTokenSource? _heartbeatCts;
|
||||
private Task? _heartbeatTask;
|
||||
private const int HeartbeatInterval = 3000; // 心跳间隔 3 秒
|
||||
|
||||
public bool IsActive { get; private set; } = false;
|
||||
public int ReConnectionAttempts { get; private set; } = 0;
|
||||
public const int MaxReconnectAttempts = 10;
|
||||
|
||||
// 手册第 4 页明确规定:每条命令后面都要加结束符 0x0A (\n)
|
||||
private const string SCPIDelimiter = "\n";
|
||||
|
||||
public N36600(string ipAddress, int port, int sendTimeout, int receiveTimeout)
|
||||
{
|
||||
ConfigureDevice(ipAddress, port, sendTimeout, receiveTimeout);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 建立 TCP 连接,成功后自动激活心跳
|
||||
/// </summary>
|
||||
public new async Task<bool> ConnectAsync(CancellationToken ct = default)
|
||||
{
|
||||
bool isConnected = await base.ConnectAsync(ct);
|
||||
if (isConnected)
|
||||
{
|
||||
IsActive = true;
|
||||
ReConnectionAttempts = 0;
|
||||
StartHeartbeat();
|
||||
}
|
||||
return isConnected;
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
StopHeartbeat();
|
||||
base.Close();
|
||||
}
|
||||
|
||||
#region 心跳与断线重连逻辑
|
||||
|
||||
[Browsable(false)]
|
||||
public void StartHeartbeat()
|
||||
{
|
||||
if (_heartbeatTask != null && !_heartbeatTask.IsCompleted)
|
||||
return;
|
||||
|
||||
_heartbeatCts?.Cancel();
|
||||
_heartbeatCts?.Dispose();
|
||||
_heartbeatCts = new CancellationTokenSource();
|
||||
|
||||
_heartbeatTask = Task.Run(() => HeartbeatLoop(_heartbeatCts.Token));
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
public void StopHeartbeat()
|
||||
{
|
||||
IsActive = false;
|
||||
if (_heartbeatCts != null && !_heartbeatCts.IsCancellationRequested)
|
||||
{
|
||||
_heartbeatCts.Cancel();
|
||||
}
|
||||
_heartbeatTask = null;
|
||||
}
|
||||
|
||||
private async Task HeartbeatLoop(CancellationToken ct)
|
||||
{
|
||||
while (!ct.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(HeartbeatInterval, ct);
|
||||
if (ct.IsCancellationRequested) break;
|
||||
|
||||
// 使用公共查询命令发送心跳,确保通道连接正常,且不破坏远程或本地锁定状态
|
||||
await SendAsync($"*IDN?{SCPIDelimiter}", ct);
|
||||
IsActive = true;
|
||||
ReConnectionAttempts = 0;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
IsActive = false;
|
||||
ReConnectionAttempts++;
|
||||
|
||||
if (ReConnectionAttempts > MaxReconnectAttempts)
|
||||
{
|
||||
StopHeartbeat();
|
||||
base.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
await ReconnectDeviceAsync(ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReconnectDeviceAsync(CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(ct);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// 静默处理,等待下一轮心跳重试
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.1 IEEE 488.2 公用命令
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.1 读取电源的相关信息(制造商, 产品标号, 产品序列号, 软件版本号)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*IDN?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.2 从指定的单位中恢复 *SAV 命令保存的设定值 (参数:1~99)
|
||||
/// </summary>
|
||||
public virtual async Task 调用存储状态(int 组别, CancellationToken ct = default)
|
||||
{
|
||||
if (组别 < 1 || 组别 > 99) throw new ArgumentOutOfRangeException(nameof(组别), "组别有效范围为 1~99");
|
||||
await SendAsync($"*RCL {组别}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.3 将仪器当前系统状态保存到非易失性内存中 (参数:1~99)
|
||||
/// </summary>
|
||||
public virtual async Task 保存当前状态(int 组别, CancellationToken ct = default)
|
||||
{
|
||||
if (组别 < 1 || 组别 > 99) throw new ArgumentOutOfRangeException(nameof(组别), "组别有效范围为 1~99");
|
||||
await SendAsync($"*SAV {组别}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.4 远程控制触发一次
|
||||
/// </summary>
|
||||
public virtual async Task 远程触发(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*TRG{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.5 返回状态、采集电压、采集电流 (格式: 状态码,电压值,电流值)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询全部状态及采样(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*ALL?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.2 APPLy 命令子系统
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.1 设置输出电压和输出电流值
|
||||
/// </summary>
|
||||
public virtual async Task 快捷设置电压电流(double 电压, double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":APPLY {0:F3},{1:F3}{2}", 电压, 电流, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.1 查询当前设定的输出电压和电流值
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询快捷电压电流设定(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":APPLY?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.5 MEASure 命令子系统
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.1 查询通道输出端子上测得的直流电流值 (A)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际电流(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:CURRent?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.2 查询通道输出端子上测得的直流功率值 (W)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际功率(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:POWer?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.3 查询通道输出端子上测得的直流电压值 (V)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际电压(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:VOLTage?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.4 查询通道输出端子上测得的电压、电流和功率的组合值
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询电压电流功率数组(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:VAP?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.5 查询通道输出端子上测得的电池容量
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询测得电池容量(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:CAPAcity?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.6 查询通道输出端子上测得的恒流输出时间长度 (单位: ms)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询恒流输出时间长度(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:TIMEr:CC?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.7 查询通道输出端子上测得的输出时间长度
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询总输出时间长度(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:TIME:OUTPut?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.6 OUTPut 命令子系统
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.1 设置电源输出开关
|
||||
/// </summary>
|
||||
public virtual async Task 设置DC输出(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
string 状态 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($":OUTPut {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.1 查询通道输出状态 (返回 ON 或 OFF)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询DC输出状态(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":OUTPut?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.2 控制电源输出定时器的开关状态
|
||||
/// </summary>
|
||||
public virtual async Task 设置定时器状态(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
string 状态 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($":OUTPut:TIMEr {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.3 设定电源输出定时器的时间 (单位: s, 范围: 0.1 ~ 999999.9)
|
||||
/// </summary>
|
||||
public virtual async Task 设置定时器时间(double 秒数, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":OUTPut:TIMEr:DATA {0:F1}{1}", 秒数, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.4 设定电源输出的模式优先权 (CV优先 或 CC优先)
|
||||
/// </summary>
|
||||
public virtual async Task 设置输出模式优先(bool 是CV优先, CancellationToken ct = default)
|
||||
{
|
||||
string 模式 = 是CV优先 ? "CV" : "CC";
|
||||
await SendAsync($":OUTPut:MODE {模式}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.7 SOURce 命令子系统
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.1.1 清除输出电流保护(OCP)电路状态
|
||||
/// </summary>
|
||||
public virtual async Task 清除电流保护状态(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":CURRent:PROTection:CLEar{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.1.2 使能或禁止当前输出电流保护(OCP)电路
|
||||
/// </summary>
|
||||
public virtual async Task 设置电流保护使能(bool 启用, CancellationToken ct = default)
|
||||
{
|
||||
string 状态 = 启用 ? "ON" : "OFF";
|
||||
await SendAsync($":CURRent:PROTection:STATE {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.1.4 设置输出电流保护(OCP)阀值 (A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过流保护值(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":CURRent:PROTection {0:F3}{1}", 电流, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.1.7 设置电源的输出电流值 (A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电流(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":CURRent {0:F3}{1}", 电流, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.2.1 清除输出电压保护(OVP)电路状态
|
||||
/// </summary>
|
||||
public virtual async Task 清除电压保护状态(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":VOLTage:PROTection:CLEar{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.2.2 使能或禁止当前输出电压保护(OVP)电路
|
||||
/// </summary>
|
||||
public virtual async Task 设置电压保护使能(bool 启用, CancellationToken ct = default)
|
||||
{
|
||||
string 状态 = 启用 ? "ON" : "OFF";
|
||||
await SendAsync($":VOLTage:PROTection:STATE {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.2.4 设置输出电压保护(OVP)阀值 (V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过压保护值(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":VOLTage:PROTection {0:F3}{1}", 电压, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.2.7 设置电源的输出电压值 (V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电压(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":VOLTage {0:F3}{1}", 电压, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.2.8 设定电压输出范围的上限电压值 (V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电压上限限制(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":VOLTage:LIMIT {0:F3}{1}", 电压, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.3.1 设置输出功率值 (W)
|
||||
/// </summary>
|
||||
public virtual async Task 设置功率(double 功率, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":POWer {0:F3}{1}", 功率, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.3.2 禁止或使能当前恒功率(CP)输出
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒功率输出使能(bool 启用, CancellationToken ct = default)
|
||||
{
|
||||
string 状态 = 启用 ? "ON" : "OFF";
|
||||
await SendAsync($":POWer:STATE {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.9 SYSTem 命令子系统
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.1 控制蜂鸣器开关
|
||||
/// </summary>
|
||||
public virtual async Task 设置蜂鸣器状态(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
string 状态 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($":SYSTem:BEEPer:STATE {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.2 使蜂鸣器强制鸣叫一声
|
||||
/// </summary>
|
||||
public virtual async Task 蜂鸣器鸣叫(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:BEEPer{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.4 查询仪器当前出错记录数量 (最大 18 组)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询错误记录数量(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":SYSTem:ERRor:COUNT?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.5 读取仪器的出错信息 (成功返回 0,"No error")
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询错误信息(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":SYSTem:ERRor?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.7 设置电源为面板控制模式 (本地 Local 状态,前面板按键可用)
|
||||
/// </summary>
|
||||
public virtual async Task 切换本地控制模式(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:LOCal{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.8 设置电源为远程控制模式 (Remote 状态)
|
||||
/// </summary>
|
||||
public virtual async Task 切换远程控制模式(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:REMote{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.9 通过通信接口设置电源为远程控制锁定模式
|
||||
/// </summary>
|
||||
public virtual async Task 远程控制模式锁定(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:RWLock{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
294
DeviceCommand/Devices/N69200.cs
Normal file
294
DeviceCommand/Devices/N69200.cs
Normal file
@@ -0,0 +1,294 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Device
|
||||
{
|
||||
[ADPCommand]
|
||||
public class N69200 : Tcp
|
||||
{
|
||||
// 手册第 15 页 2.2.4 明确规定:命令结束符为换行符 (ASCII 字符 LF,即 \n)
|
||||
private const string ScpiDelimiter = "\n";
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数:初始化 N69200 电子负载通信参数
|
||||
/// </summary>
|
||||
public N69200(string ipAddress, int port, int sendTimeout, int receiveTimeout)
|
||||
{
|
||||
ConfigureDevice(ipAddress, port, sendTimeout, receiveTimeout);
|
||||
}
|
||||
|
||||
#region 3.1. IEEE 488.2 公共命令
|
||||
|
||||
/// <summary>
|
||||
/// 清除状态命令。清除标准事件状态寄存器和错误队列
|
||||
/// </summary>
|
||||
public virtual async Task 清除错误队列和状态字节(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*CLS{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取电子负载设备识别字符串(制造商、产品型号、系统 SN、软件版本号)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 复位命令。使电子负载恢复到出厂默认配置状态
|
||||
/// </summary>
|
||||
public virtual async Task 重置设备(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*RST{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取标准状态字节寄存器
|
||||
/// </summary>
|
||||
public virtual async Task<string> 读取状态字节(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*STB?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存当前系统参数到指定的存储单元 (单元范围依型号而定)
|
||||
/// </summary>
|
||||
public virtual async Task 保存当前状态(int 单元, CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*SAV {单元}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调用指定存储单元中保存的系统参数
|
||||
/// </summary>
|
||||
public virtual async Task 调用存储状态(int 单元, CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*RCL {单元}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.2. 电子负载模式切换及拉载开关
|
||||
|
||||
/// <summary>
|
||||
/// 控制电子负载的输入控制开关(True: 开启拉载输入, False: 关闭拉载输入)
|
||||
/// </summary>
|
||||
public virtual async Task 设置DC输入(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
string 参数 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($":INPut {参数}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询电子负载当前的拉载开关状态 (返回 ON 或 OFF)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询DC输入状态(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":INPut?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置电子负载的基本工作模式
|
||||
/// (CC: 恒电流, CV: 恒电压, CP: 恒功率, CR: 恒电阻)
|
||||
/// </summary>
|
||||
public virtual async Task 设置负载模式(string 模式, CancellationToken ct = default)
|
||||
{
|
||||
// 转换为大写以确保设备命令解析兼容
|
||||
string modeUpper = 模式.ToUpper();
|
||||
if (modeUpper != "CC" && modeUpper != "CV" && modeUpper != "CP" && modeUpper != "CR")
|
||||
throw new ArgumentException("工作模式只能为 CC, CV, CP, 或 CR");
|
||||
|
||||
await SendAsync($":MODE {modeUpper}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询电子负载当前处于何种工作模式
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询负载模式(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MODE?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.3. 负载参数设置 (CC / CV / CP / CR)
|
||||
|
||||
/// <summary>
|
||||
/// 设定恒电流模式(CC)下的拉载电流目标值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒电流CC(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":CURRent {0:F4}{1}", 电流, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询恒电流模式下设定的拉载电流值
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询恒电流CC设定(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设定恒电压模式(CV)下的拉载电压目标值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒电压CV(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":VOLTage {0:F3}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询恒电压模式下设定的拉载电压值
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询恒电压CV设定(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设定恒功率模式(CP)下的拉载功率目标值 (单位: W)
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒功率CP(double 功率, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":POWer {0:F3}{1}", 功率, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询恒功率模式下设定的拉载功率值
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询恒功率CP设定(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":POWer?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设定恒电阻模式(CR)下的等效拉载电阻阻值 (单位: Ω)
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒电阻CR(double 电阻, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":RESistance {0:F4}{1}", 电阻, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询恒电阻模式下设定的拉载电阻值
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询恒电阻CR设定(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":RESistance?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.4. 测量与数据回测(高频数据轮询核心)
|
||||
|
||||
/// <summary>
|
||||
/// 回读电子负载输入端子上的实时测得电压值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际电压(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回读电子负载输入端子上的实时测得电流值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际电流(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回读电子负载输入端子上的实时测得功率值 (单位: W)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际功率(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:POWer?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一键回读当前负载测得的电压、电流和功率的组合数组数据
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询电压电流功率数组(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:VAP?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.5. 保护限制参数设置
|
||||
|
||||
/// <summary>
|
||||
/// 设置电子负载的过流保护(OCP)报警值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过流保护值_OCP(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":PROTection:CURRent {0:F3}{1}", 电流, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置电子负载的过功率保护(OPP)报警值 (单位: W)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过功率保护值_OPP(double 功率, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":PROTection:POWer {0:F3}{1}", 功率, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置电子负载的过压保护(OVP)报警值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过压保护值_OVP(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":PROTection:VOLTage {0:F3}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.6. 系统控制及报警清除
|
||||
|
||||
/// <summary>
|
||||
/// 读取仪器的出错记录记录 (当系统产生告警或指令解析异常时读取)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询错误信息(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":SYSTem:ERRor?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换电子负载到本地面板操作状态 (前面板按键解锁)
|
||||
/// </summary>
|
||||
public virtual async Task 切换本地控制模式(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:LOCal{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换电子负载到远程计算机操作状态 (前面板按键锁定)
|
||||
/// </summary>
|
||||
public virtual async Task 切换远程控制模式(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:REMote{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除负载当前的软件过流/过功率等保护(Protection)锁定触发状态
|
||||
/// </summary>
|
||||
public virtual async Task 清除保护告警(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":PROTection:CLEar{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
213
DeviceCommand/Devices/SDS2000X_HD.cs
Normal file
213
DeviceCommand/Devices/SDS2000X_HD.cs
Normal file
@@ -0,0 +1,213 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Device
|
||||
{
|
||||
[ADPCommand]
|
||||
public class SDS2000X_HD : Tcp
|
||||
{
|
||||
// 示波器底层 Socket 字符串命令通常以换行符 \n 结束
|
||||
private const string ScpiDelimiter = "\n";
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数:初始化示波器通信参数 (鼎阳示波器网口 Socket 默认端口通常为 5025)
|
||||
/// </summary>
|
||||
public SDS2000X_HD(string ipAddress, int port = 5025, int sendTimeout = 3000, int receiveTimeout = 3000)
|
||||
{
|
||||
ConfigureDevice(ipAddress, port, sendTimeout, receiveTimeout);
|
||||
}
|
||||
|
||||
#region 1. IEEE 488.2 公共命令
|
||||
|
||||
/// <summary>
|
||||
/// 清除标准事件状态寄存器和错误队列
|
||||
/// </summary>
|
||||
public virtual async Task 清除状态(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*CLS{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取示波器识别字符串(制造商、型号、序列号、固件版本)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 复位命令。使示波器恢复到默认的出厂设置
|
||||
/// </summary>
|
||||
public virtual async Task 重置设备(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*RST{ScpiDelimiter}", ct); //
|
||||
}
|
||||
#endregion
|
||||
#region 2. 运行与捕获控制控制 (Run / Stop / Single)
|
||||
|
||||
/// <summary>
|
||||
/// 控制示波器开始捕获波形 (等同于按下前端面板的 Run 键)
|
||||
/// </summary>
|
||||
public virtual async Task 启动捕获_RUN(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"RUN{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止捕获波形 (等同于按下前端面板的 Stop 键)
|
||||
/// </summary>
|
||||
public virtual async Task 停止捕获_STOP(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"STOP{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 强制示波器进入单次触发捕获模式
|
||||
/// </summary>
|
||||
public virtual async Task 单次触发_SINGLE(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"SINGle{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发一次波形采样 (当触发源设为 Manual 时使用)
|
||||
/// </summary>
|
||||
public virtual async Task 强制触发(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*TRG{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3. Channel 垂直控制子系统 (C1 ~ C4)
|
||||
|
||||
/// <summary>
|
||||
/// 开启或关闭指定的模拟通道 (例如: channel=1 代表 C1)
|
||||
/// </summary>
|
||||
public virtual async Task 设置通道开关(int channel, bool enable, CancellationToken ct = default)
|
||||
{
|
||||
string state = enable ? "ON" : "OFF";
|
||||
await SendAsync($"C{channel}:TRAce {state}{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置指定通道的垂直电压档位 (Volts/Div,单位: V,例如 0.05 代表 50mV/div)
|
||||
/// </summary>
|
||||
public virtual async Task 设置通道电压档位(int channel, double volts, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "C{0}:VDIV {1:F4}{2}", channel, volts, ScpiDelimiter); //
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置指定通道的垂直偏移量 (Offset,单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置通道垂直偏移(int channel, double offset, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "C{0}:OFST {1:F4}{2}", channel, offset, ScpiDelimiter); //
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置通道的输入阻抗 (1MΩ 或 50Ω)
|
||||
/// </summary>
|
||||
/// <param name="is50Ohm">True: 50欧姆, False: 1M欧姆</param>
|
||||
public virtual async Task 设置通道输入阻抗(int channel, bool is50Ohm, CancellationToken ct = default)
|
||||
{
|
||||
string value = is50Ohm ? "50" : "1M";
|
||||
await SendAsync($"C{channel}:COUPling {value}{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 4. Timebase 水平时基子系统
|
||||
|
||||
/// <summary>
|
||||
/// 设置示波器的水平时基档位 (Time/Div,单位: s,例如 0.001 代表 1ms/div)
|
||||
/// </summary>
|
||||
public virtual async Task 设置水平时基(double scale, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "TIME_DIV {0:E6}{1}", scale, ScpiDelimiter); //
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置示波器的触发水平延迟位置 (Horizontal Delay,单位: s)
|
||||
/// </summary>
|
||||
public virtual async Task 设置水平延迟(double delay, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "TRIGger:DELay {0:E6}{1}", delay, ScpiDelimiter); //
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 5. Trigger 触发子系统
|
||||
|
||||
/// <summary>
|
||||
/// 设置边沿触发的电平值 (Trigger Level,单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置触发电平(double level, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "TRIGger:LEVel {0:F3}{1}", level, ScpiDelimiter); //
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置边沿触发的触发源 (例如: C1, C2, C3, C4, EX, LINE)
|
||||
/// </summary>
|
||||
public virtual async Task 设置触发源(string source, CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"TRIGger:SOURce {source.ToUpper()}{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 6. Measure 自动测量参数回读 (高频轮询核心)
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道自动测量项的当前实时测量数值
|
||||
/// </summary>
|
||||
/// <param name="channel">通道号 (1-4)</param>
|
||||
/// <param name="paramName">参数名称助记符:
|
||||
/// PKPK(峰峰值), MAX(最大值), MIN(最小值), AMPL(振幅值),
|
||||
/// FREQ(频率), PER(周期), MEAN(平均值), RMS(均方根) 等</param>
|
||||
/// <returns>设备返回的科学计数法或自定义字符串数值</returns>
|
||||
public virtual async Task<string> 查询通道测量项参数(int channel, string paramName, CancellationToken ct = default)
|
||||
{
|
||||
// 语法格式示例:C1:PAVA? FREQ
|
||||
string query = string.Format(CultureInfo.InvariantCulture, "C{0}:PAVA? {1}{2}", channel, paramName.ToUpper(), ScpiDelimiter); //
|
||||
return await WriteReadAsync(query, ScpiDelimiter, ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轮询便捷接口:查询指定通道的电压峰峰值 (Vpp)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际电压峰峰值(int channel, CancellationToken ct = default)
|
||||
{
|
||||
return await 查询通道测量项参数(channel, "PKPK", ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轮询便捷接口:查询指定通道的频率值 (Frequency)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际频率(int channel, CancellationToken ct = default)
|
||||
{
|
||||
return await 查询通道测量项参数(channel, "FREQ", ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轮询便捷接口:查询指定通道的真均方根电压值 (Vrms)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际电压均方根(int channel, CancellationToken ct = default)
|
||||
{
|
||||
return await 查询通道测量项参数(channel, "RMS", ct); //
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
217
DeviceCommand/Devices/SPAW7000.cs
Normal file
217
DeviceCommand/Devices/SPAW7000.cs
Normal file
@@ -0,0 +1,217 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Device
|
||||
{
|
||||
[ADPCommand]
|
||||
public class SPAW7000 : Tcp
|
||||
{
|
||||
// 根据通用 SCPI 与远宽指令规范,使用换行符 (ASCII 字符 LF,即 \n) 作为标准结束符
|
||||
private const string ScpiDelimiter = "\n";
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数:初始化 SPAW7000 功率分析记录仪通信参数
|
||||
/// </summary>
|
||||
public SPAW7000(string ipAddress, int port, int sendTimeout, int receiveTimeout)
|
||||
{
|
||||
ConfigureDevice(ipAddress, port, sendTimeout, receiveTimeout);
|
||||
}
|
||||
|
||||
#region 1. IEEE 488.2 公共命令
|
||||
|
||||
/// <summary>
|
||||
/// 清除状态命令。清除标准事件状态寄存器和错误队列
|
||||
/// </summary>
|
||||
public virtual async Task 清除错误队列和状态字节(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*CLS{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取功率分析仪识别字符串(制造商、产品型号、系统 SN、软件版本号)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 复位命令。使功率分析仪恢复到出厂默认配置状态
|
||||
/// </summary>
|
||||
public virtual async Task 重置设备(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*RST{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取标准状态字节寄存器
|
||||
/// </summary>
|
||||
public virtual async Task<string> 读取状态字节(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*STB?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 2. MEASure / NUMeric 核心数据测量与轮询 (高频轮询核心)
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道的实时 RMS 电压值 (单位: V)
|
||||
/// </summary>
|
||||
/// <param name="channel">通道号 (例如: 1, 2, 3...)</param>
|
||||
public virtual async Task<string> 查询实际电压(int channel, CancellationToken ct = default)
|
||||
{
|
||||
string query = string.Format(CultureInfo.InvariantCulture, ":MEASure:NUMeric:VALue? U,{0}{1}", channel, ScpiDelimiter);
|
||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道的实时 RMS 电流值 (单位: A)
|
||||
/// </summary>
|
||||
/// <param name="channel">通道号 (例如: 1, 2, 3...)</param>
|
||||
public virtual async Task<string> 查询实际电流(int channel, CancellationToken ct = default)
|
||||
{
|
||||
string query = string.Format(CultureInfo.InvariantCulture, ":MEASure:NUMeric:VALue? I,{0}{1}", channel, ScpiDelimiter);
|
||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道的实时有功功率值 (单位: W)
|
||||
/// </summary>
|
||||
/// <param name="channel">通道号 (例如: 1, 2, 3...)</param>
|
||||
public virtual async Task<string> 查询实际功率(int channel, CancellationToken ct = default)
|
||||
{
|
||||
string query = string.Format(CultureInfo.InvariantCulture, ":MEASure:NUMeric:VALue? P,{0}{1}", channel, ScpiDelimiter);
|
||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道的实时频率值 (单位: Hz)
|
||||
/// </summary>
|
||||
/// <param name="channel">通道号 (例如: 1, 2, 3...)</param>
|
||||
public virtual async Task<string> 查询频率(int channel, CancellationToken ct = default)
|
||||
{
|
||||
string query = string.Format(CultureInfo.InvariantCulture, ":MEASure:NUMeric:VALue? FREQuency,{0}{1}", channel, ScpiDelimiter);
|
||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道的功率因数 (Power Factor)
|
||||
/// </summary>
|
||||
/// <param name="channel">通道号 (例如: 1, 2, 3...)</param>
|
||||
public virtual async Task<string> 查询功率因数(int channel, CancellationToken ct = default)
|
||||
{
|
||||
string query = string.Format(CultureInfo.InvariantCulture, ":MEASure:NUMeric:VALue? PF,{0}{1}", channel, ScpiDelimiter);
|
||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自定义组合参数批量读取接口
|
||||
/// </summary>
|
||||
/// <param name="parameter">参数助记符 (如 "U,I,P" 或 "S,Q,LAMBda")</param>
|
||||
/// <param name="channel">通道号</param>
|
||||
public virtual async Task<string> 查询自定义测量组合(string parameter, int channel, CancellationToken ct = default)
|
||||
{
|
||||
string query = string.Format(CultureInfo.InvariantCulture, ":MEASure:NUMeric:VALue? {0},{1}{2}", parameter, channel, ScpiDelimiter);
|
||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3. INPut 通道电气参数配置
|
||||
|
||||
/// <summary>
|
||||
/// 设置指定通道的电压量程 (例如: 15, 30, 60, 150, 300, 600, 1000)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电压量程(int channel, double range, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":INPut:VOLTage:RANGe {0},{1:F1}{2}", channel, range, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置指定通道的电流量程 (取决于接线单元或传感器输入类型)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电流量程(int channel, double range, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":INPut:CURRent:RANGe {0},{1:F3}{2}", channel, range, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置通道的耦合模式 (AC, DC, ACDC)
|
||||
/// </summary>
|
||||
public virtual async Task 设置通道耦合模式(int channel, string mode, CancellationToken ct = default)
|
||||
{
|
||||
string modeUpper = mode.ToUpper();
|
||||
if (modeUpper != "AC" && modeUpper != "DC" && modeUpper != "ACDC")
|
||||
throw new ArgumentException("耦合模式只能为 AC, DC, 或 ACDC");
|
||||
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":INPut:COUPling {0},{1}{2}", channel, modeUpper, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 4. SYSTem 系统设置与状态查询
|
||||
|
||||
/// <summary>
|
||||
/// 14. 查询仪器型号名称
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备型号(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":SYSTem:MODel?{ScpiDelimiter}", ScpiDelimiter, ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 16. 查询仪器唯一序列号
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备序列号(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":SYSTem:SERial?{ScpiDelimiter}", ScpiDelimiter, ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 15. 设置数值数据显示的分辨率 (5位或6位)
|
||||
/// </summary>
|
||||
/// <param name="resolution">有效值只能为 5 或 6</param>
|
||||
public virtual async Task 设置显示分辨率(int resolution, CancellationToken ct = default)
|
||||
{
|
||||
if (resolution != 5 && resolution != 6) throw new ArgumentException("分辨率只能设置为 5 或 6 位");
|
||||
await SendAsync($":SYSTem:RESolution {resolution}{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 13. 设置或查询屏幕 LCD 的亮度级别 (1-10)
|
||||
/// </summary>
|
||||
public virtual async Task 设置显示亮度(int brightness, CancellationToken ct = default)
|
||||
{
|
||||
if (brightness < 1 || brightness > 10) throw new ArgumentOutOfRangeException(nameof(brightness), "亮度范围必须在 1~10 之间");
|
||||
await SendAsync($":SYSTem:LCD:BRIGhtness {brightness}{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 18. 设置或查询触摸锁的开/关状态 (锁定时防止人工误触触控屏)
|
||||
/// </summary>
|
||||
public virtual async Task 设置屏幕触摸锁定(bool isLocked, CancellationToken ct = default)
|
||||
{
|
||||
string state = isLocked ? "ON" : "OFF";
|
||||
await SendAsync($":SYSTem:TLOCK {state}{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 17. 设置或读取分析仪当前的系统内部时间
|
||||
/// </summary>
|
||||
/// <param name="timeStr">格式必须为 "HH:MM:SS"</param>
|
||||
public virtual async Task 设置系统时间(string timeStr, CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:TIME \"{timeStr}\"{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
13
Logger/Logger.csproj
Normal file
13
Logger/Logger.csproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NLog" Version="6.1.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
114
Logger/LoggerHelper.cs
Normal file
114
Logger/LoggerHelper.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using NLog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Logger
|
||||
{
|
||||
public static class LoggerHelper
|
||||
{
|
||||
public static readonly ILogger Logger = LogManager.GetLogger("InfoLogger");
|
||||
public static readonly ILogger sqlLogger = LogManager.GetLogger("SqlLogger");
|
||||
public static IProgress<(string message, string color, int depth)> Progress { get; set; }
|
||||
static LoggerHelper()
|
||||
{
|
||||
Progress = new Progress<(string message, string color, int depth)>();
|
||||
}
|
||||
public static void InfoWithNotify(string message, int depth = 0)
|
||||
{
|
||||
Logger.Info(message); // 写入 NLog
|
||||
NotifyUI(message, "blue", depth); // 触发UI显示
|
||||
}
|
||||
|
||||
public static void SuccessWithNotify(string message, int depth = 0)
|
||||
{
|
||||
Logger.Info(message);
|
||||
NotifyUI(message, "lightgreen", depth);
|
||||
}
|
||||
public static void WarnWithNotify(string message, string stackTrace = null, int depth = 0)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(stackTrace))
|
||||
{
|
||||
string location = GetProjectStackLine(stackTrace);
|
||||
message = $"{message} ({location})";
|
||||
}
|
||||
|
||||
Logger.Warn(message);
|
||||
NotifyUI(message, "orange", depth);
|
||||
}
|
||||
|
||||
public static void ErrorWithNotify(string message, string stackTrace = null, int depth = 0)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(stackTrace))
|
||||
{
|
||||
string location = GetProjectStackLine(stackTrace);
|
||||
message = $"{message} ({location})";
|
||||
}
|
||||
|
||||
Logger.Error(message);
|
||||
NotifyUI(message, "red", depth);
|
||||
}
|
||||
private static void NotifyUI(string message, string color, int depth)
|
||||
{
|
||||
Progress.Report((message, color, depth));
|
||||
}
|
||||
|
||||
public static void Info(string message, int depth = 0)
|
||||
{
|
||||
Logger.Info(message);
|
||||
}
|
||||
public static void Success(string message, int depth = 0)
|
||||
{
|
||||
Logger.Info(message);
|
||||
}
|
||||
public static void Warn(string message, string stackTrace = null, int depth = 0)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(stackTrace))
|
||||
{
|
||||
string location = GetProjectStackLine(stackTrace);
|
||||
message = $"{message} ({location})";
|
||||
}
|
||||
|
||||
Logger.Warn(message);
|
||||
}
|
||||
|
||||
public static void Error(string message, string stackTrace = null, int depth = 0)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(stackTrace))
|
||||
{
|
||||
string location = GetProjectStackLine(stackTrace);
|
||||
message = $"{message} ({location})";
|
||||
}
|
||||
|
||||
Logger.Error(message);
|
||||
}
|
||||
|
||||
public static string GetProjectStackLine(string stackTrace)
|
||||
{
|
||||
if (string.IsNullOrEmpty(stackTrace))
|
||||
return "未知位置";
|
||||
|
||||
var lines = stackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
// 匹配你项目的命名空间路径
|
||||
if (line.Contains("ADP"))
|
||||
{
|
||||
// 提取 "in 文件路径:line 行号"
|
||||
var match = Regex.Match(line, @"in (.+?):line (\d+)");
|
||||
if (match.Success)
|
||||
{
|
||||
return match.Value; // 返回类似 C:\...\MainViewModel.cs:line 37
|
||||
}
|
||||
return line.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
return lines[0].Trim(); // 如果找不到就返回第一条
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Logger/Nlog.config
Normal file
37
Logger/Nlog.config
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
|
||||
<!-- 定义目标 -->
|
||||
<targets>
|
||||
<!-- SQL 日志 -->
|
||||
<target name="sqlLog" xsi:type="File"
|
||||
fileName="Logs/${shortdate}_sql.txt"
|
||||
layout="${longdate} [${level}] [ThreadId:${threadid}] ${message} ${exception}"
|
||||
encoding="utf-8" />
|
||||
|
||||
<!-- Info 日志 -->
|
||||
<target name="infoLog" xsi:type="File"
|
||||
fileName="Logs/${shortdate}_info.txt"
|
||||
layout="${longdate} [${level}] [ThreadId:${threadid}] ${message} ${exception}"
|
||||
encoding="utf-8" />
|
||||
|
||||
<!-- Warn 日志 -->
|
||||
<target name="warnLog" xsi:type="File"
|
||||
fileName="Logs/${shortdate}_warn.txt"
|
||||
layout="${longdate} [${level}] [ThreadId:${threadid}] ${message} ${exception}"
|
||||
encoding="utf-8" />
|
||||
</targets>
|
||||
|
||||
<!-- 定义日志规则 -->
|
||||
<rules>
|
||||
<!-- SQL 日志规则 -->
|
||||
<logger name="SqlLogger" minlevel="Info" writeTo="sqlLog" />
|
||||
|
||||
<!-- Info 日志规则 -->
|
||||
<logger name="InfoLogger" minlevel="Info" maxlevel="Info" writeTo="infoLog" />
|
||||
|
||||
<!-- Warn 日志规则 -->
|
||||
<logger name="*" minlevel="Warn" writeTo="warnLog" />
|
||||
</rules>
|
||||
</nlog>
|
||||
21
LoginModule/LoginModule.cs
Normal file
21
LoginModule/LoginModule.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using LoginModule.Views;
|
||||
using Prism.Modularity;
|
||||
using System.Reflection;
|
||||
namespace LoginModule
|
||||
{
|
||||
public class LoginModule : IModule
|
||||
{
|
||||
public void OnInitialized(IContainerProvider containerProvider)
|
||||
{
|
||||
IRegionManager regionManager = containerProvider.Resolve<IRegionManager>();
|
||||
regionManager.RegisterViewWithRegion("LoginRegion", typeof(LoginView));
|
||||
regionManager.RegisterViewWithRegion("LoginRegion", typeof(RegisterView));
|
||||
}
|
||||
|
||||
public void RegisterTypes(IContainerRegistry containerRegistry)
|
||||
{
|
||||
containerRegistry.RegisterForNavigation<LoginView>("LoginView");
|
||||
containerRegistry.RegisterForNavigation<RegisterView>("RegisterView");
|
||||
}
|
||||
}
|
||||
}
|
||||
14
LoginModule/LoginModule.csproj
Normal file
14
LoginModule/LoginModule.csproj
Normal file
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\UIShare\UIShare.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
48
LoginModule/ViewModels/LoginViewModel.cs
Normal file
48
LoginModule/ViewModels/LoginViewModel.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using UIShare.PubEvent;
|
||||
using UIShare.ViewModelBase;
|
||||
|
||||
namespace LoginModule.ViewModels
|
||||
{
|
||||
public class LoginViewModel: NavigateViewModelBase
|
||||
{
|
||||
#region 属性
|
||||
private string _Password;
|
||||
public string Password
|
||||
{
|
||||
get => _Password;
|
||||
set => SetProperty(ref _Password, value);
|
||||
}
|
||||
private string _Account;
|
||||
public string Account
|
||||
{
|
||||
get => _Account;
|
||||
set => SetProperty(ref _Account, value);
|
||||
}
|
||||
#endregion
|
||||
public ICommand LoginCommand { get; set; }
|
||||
public ICommand RegisterCommand { get; set; }
|
||||
private IEventAggregator _eventAggregator;
|
||||
public LoginViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
_eventAggregator = containerProvider.Resolve<IEventAggregator>();
|
||||
LoginCommand = new AsyncDelegateCommand(OnLogin);
|
||||
RegisterCommand = new AsyncDelegateCommand(OnRegister);
|
||||
}
|
||||
|
||||
private async Task OnRegister()
|
||||
{
|
||||
_regionManager.RequestNavigate("LoginRegion", "RegisterView");
|
||||
}
|
||||
|
||||
private async Task OnLogin()
|
||||
{
|
||||
_eventAggregator.GetEvent<LoginSuccessEvent>().Publish();
|
||||
}
|
||||
}
|
||||
}
|
||||
51
LoginModule/ViewModels/RegisterViewModel.cs
Normal file
51
LoginModule/ViewModels/RegisterViewModel.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using UIShare.ViewModelBase;
|
||||
|
||||
namespace LoginModule.ViewModels
|
||||
{
|
||||
public class RegisterViewModel : NavigateViewModelBase
|
||||
{
|
||||
#region 属性
|
||||
private string _SecondPassword;
|
||||
public string SecondPassword
|
||||
{
|
||||
get => _SecondPassword;
|
||||
set => SetProperty(ref _SecondPassword, value);
|
||||
}
|
||||
private string _Password;
|
||||
public string Password
|
||||
{
|
||||
get => _Password;
|
||||
set => SetProperty(ref _Password, value);
|
||||
}
|
||||
private string _Account;
|
||||
public string Account
|
||||
{
|
||||
get => _Account;
|
||||
set => SetProperty(ref _Account, value);
|
||||
}
|
||||
#endregion
|
||||
public ICommand BackCommand { get; set; }
|
||||
public ICommand RegisterCommand { get; set; }
|
||||
public RegisterViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
BackCommand = new DelegateCommand(OnBack);
|
||||
RegisterCommand = new AsyncDelegateCommand(OnRegister);
|
||||
}
|
||||
|
||||
private void OnBack()
|
||||
{
|
||||
_regionManager.RequestNavigate("LoginRegion", "LoginView");
|
||||
}
|
||||
|
||||
private async Task OnRegister()
|
||||
{
|
||||
_regionManager.RequestNavigate("LoginRegion", "LoginView");
|
||||
}
|
||||
}
|
||||
}
|
||||
163
LoginModule/Views/LoginView.xaml
Normal file
163
LoginModule/Views/LoginView.xaml
Normal file
@@ -0,0 +1,163 @@
|
||||
<UserControl x:Class="LoginModule.Views.LoginView"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:helpers="clr-namespace:UIShare.Helpers;assembly=UIShare"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
xmlns:local="clr-namespace:LoginModule.Views"
|
||||
mc:Ignorable="d"
|
||||
Height="315"
|
||||
Width="420">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/UIShare;component/Styles/CommonStyle.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!-- 这里是你的额外样式 -->
|
||||
<Style TargetType="TextBox"
|
||||
BasedOn="{StaticResource MahApps.Styles.TextBox}">
|
||||
<Setter Property="FontSize"
|
||||
Value="14" />
|
||||
<Setter Property="BorderThickness"
|
||||
Value="0,0,0,2" />
|
||||
<Setter Property="BorderBrush"
|
||||
Value="#E0E0E0" />
|
||||
<Setter Property="Padding"
|
||||
Value="5,8" />
|
||||
<Setter Property="Background"
|
||||
Value="Transparent" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="PasswordBox"
|
||||
BasedOn="{StaticResource MahApps.Styles.PasswordBox}">
|
||||
<Setter Property="FontSize"
|
||||
Value="14" />
|
||||
<Setter Property="BorderThickness"
|
||||
Value="0,0,0,2" />
|
||||
<Setter Property="BorderBrush"
|
||||
Value="#E0E0E0" />
|
||||
<Setter Property="Padding"
|
||||
Value="5,8" />
|
||||
<Setter Property="Background"
|
||||
Value="Transparent" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Button"
|
||||
BasedOn="{StaticResource MahApps.Styles.Button.Flat}">
|
||||
<Setter Property="FontSize"
|
||||
Value="15" />
|
||||
<Setter Property="FontWeight"
|
||||
Value="SemiBold" />
|
||||
<Setter Property="Foreground"
|
||||
Value="White" />
|
||||
<Setter Property="Background"
|
||||
Value="#2196F3" />
|
||||
<Setter Property="BorderThickness"
|
||||
Value="0" />
|
||||
<Setter Property="Margin"
|
||||
Value="0,20,0,0" />
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="40" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 表单区域 -->
|
||||
<Border Grid.Row="0"
|
||||
Background="White"
|
||||
Margin="20,20,20,0"
|
||||
CornerRadius="5"
|
||||
BorderThickness="1"
|
||||
BorderBrush="#E0E0E0"
|
||||
Padding="30,20">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
|
||||
<Label Content="用户登录"
|
||||
HorizontalAlignment="Center"
|
||||
FontSize="15"
|
||||
Padding="3" />
|
||||
<StackPanel Grid.Row="1">
|
||||
|
||||
<!-- 用户名输入 -->
|
||||
<StackPanel Orientation="Vertical"
|
||||
HorizontalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Margin="17">
|
||||
<materialDesign:PackIcon Kind="Account"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding Account}"
|
||||
mah:TextBoxHelper.Watermark="请输入账号"
|
||||
mah:TextBoxHelper.ClearTextButton="True"
|
||||
VerticalContentAlignment="Center"
|
||||
Width="180"
|
||||
Height="30"
|
||||
Padding="0" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 密码输入 -->
|
||||
<StackPanel Orientation="Vertical"
|
||||
HorizontalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Margin="7">
|
||||
<materialDesign:PackIcon Kind="Lock"
|
||||
VerticalAlignment="Center" />
|
||||
<PasswordBox helpers:PasswordBoxHelper.Password="{Binding Password, Mode=TwoWay}"
|
||||
mah:TextBoxHelper.Watermark="请输入密码"
|
||||
mah:TextBoxHelper.ClearTextButton="True"
|
||||
VerticalContentAlignment="Center"
|
||||
Width="180"
|
||||
Height="30"
|
||||
Padding="0" />
|
||||
</StackPanel>
|
||||
<Label Content="注册账户"
|
||||
HorizontalAlignment="Right"
|
||||
Cursor="Hand">
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="MouseLeftButtonUp">
|
||||
<i:InvokeCommandAction Command="{Binding RegisterCommand}" />
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
</Label>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 登录按钮 -->
|
||||
<Button Grid.Row="2"
|
||||
Command="{Binding LoginCommand}"
|
||||
Content="登 录"
|
||||
Width="120"
|
||||
Height="33"
|
||||
mah:ControlsHelper.CornerRadius="5">
|
||||
<Button.Effect>
|
||||
<DropShadowEffect BlurRadius="8"
|
||||
ShadowDepth="3"
|
||||
Opacity="0.5" />
|
||||
</Button.Effect>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- 底部版权信息 -->
|
||||
<TextBlock Grid.Row="2"
|
||||
Text="© 2026 ADP"
|
||||
Foreground="#777"
|
||||
FontSize="12"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,10" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
28
LoginModule/Views/LoginView.xaml.cs
Normal file
28
LoginModule/Views/LoginView.xaml.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace LoginModule.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// LoginView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class LoginView : UserControl
|
||||
{
|
||||
public LoginView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
175
LoginModule/Views/RegisterView.xaml
Normal file
175
LoginModule/Views/RegisterView.xaml
Normal file
@@ -0,0 +1,175 @@
|
||||
<UserControl x:Class="LoginModule.Views.RegisterView"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:helpers="clr-namespace:UIShare.Helpers;assembly=UIShare"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
xmlns:local="clr-namespace:LoginModule.Views"
|
||||
mc:Ignorable="d"
|
||||
Height="315"
|
||||
Width="420">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/UIShare;component/Styles/CommonStyle.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!-- 这里是你的额外样式 -->
|
||||
<Style TargetType="TextBox"
|
||||
BasedOn="{StaticResource MahApps.Styles.TextBox}">
|
||||
<Setter Property="FontSize"
|
||||
Value="14" />
|
||||
<Setter Property="BorderThickness"
|
||||
Value="0,0,0,2" />
|
||||
<Setter Property="BorderBrush"
|
||||
Value="#E0E0E0" />
|
||||
<Setter Property="Padding"
|
||||
Value="5,8" />
|
||||
<Setter Property="Background"
|
||||
Value="Transparent" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="PasswordBox"
|
||||
BasedOn="{StaticResource MahApps.Styles.PasswordBox}">
|
||||
<Setter Property="FontSize"
|
||||
Value="14" />
|
||||
<Setter Property="BorderThickness"
|
||||
Value="0,0,0,2" />
|
||||
<Setter Property="BorderBrush"
|
||||
Value="#E0E0E0" />
|
||||
<Setter Property="Padding"
|
||||
Value="5,8" />
|
||||
<Setter Property="Background"
|
||||
Value="Transparent" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Button"
|
||||
BasedOn="{StaticResource MahApps.Styles.Button.Flat}">
|
||||
<Setter Property="FontSize"
|
||||
Value="15" />
|
||||
<Setter Property="FontWeight"
|
||||
Value="SemiBold" />
|
||||
<Setter Property="Foreground"
|
||||
Value="White" />
|
||||
<Setter Property="Background"
|
||||
Value="#2196F3" />
|
||||
<Setter Property="BorderThickness"
|
||||
Value="0" />
|
||||
<Setter Property="Margin"
|
||||
Value="0,20,0,0" />
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="40" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 表单区域 -->
|
||||
<Border Grid.Row="0"
|
||||
Background="White"
|
||||
Margin="20,20,20,0"
|
||||
CornerRadius="5"
|
||||
BorderThickness="1"
|
||||
BorderBrush="#E0E0E0"
|
||||
Padding="30,20">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
|
||||
<Label Content="用户注册"
|
||||
HorizontalAlignment="Center"
|
||||
FontSize="15"
|
||||
Padding="3" />
|
||||
<StackPanel Grid.Row="1">
|
||||
|
||||
<!-- 用户名输入 -->
|
||||
<StackPanel Orientation="Vertical"
|
||||
HorizontalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Margin="7">
|
||||
<materialDesign:PackIcon Kind="Account"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding Account}"
|
||||
mah:TextBoxHelper.Watermark="请输入账号"
|
||||
mah:TextBoxHelper.ClearTextButton="True"
|
||||
VerticalContentAlignment="Center"
|
||||
Width="180"
|
||||
Height="30"
|
||||
Padding="0" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 密码输入 -->
|
||||
<StackPanel Orientation="Vertical"
|
||||
HorizontalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Margin="7">
|
||||
<materialDesign:PackIcon Kind="Lock"
|
||||
VerticalAlignment="Center" />
|
||||
<PasswordBox helpers:PasswordBoxHelper.Password="{Binding Password, Mode=TwoWay}"
|
||||
mah:TextBoxHelper.Watermark="请输入密码"
|
||||
mah:TextBoxHelper.ClearTextButton="True"
|
||||
VerticalContentAlignment="Center"
|
||||
Width="180"
|
||||
Height="30"
|
||||
Padding="0" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Margin="7">
|
||||
<materialDesign:PackIcon Kind="Lock"
|
||||
VerticalAlignment="Center" />
|
||||
<PasswordBox helpers:PasswordBoxHelper.Password="{Binding SecondPassword, Mode=TwoWay}"
|
||||
mah:TextBoxHelper.Watermark="请输入二次密码"
|
||||
mah:TextBoxHelper.ClearTextButton="True"
|
||||
VerticalContentAlignment="Center"
|
||||
Width="180"
|
||||
Height="30"
|
||||
Padding="0" />
|
||||
</StackPanel>
|
||||
<Label Content="返回"
|
||||
HorizontalAlignment="Right"
|
||||
Cursor="Hand">
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="MouseLeftButtonUp">
|
||||
<i:InvokeCommandAction Command="{Binding BackCommand}" />
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
</Label>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 登录按钮 -->
|
||||
<Button Grid.Row="2"
|
||||
Command="{Binding RegisterCommand}"
|
||||
Content="注 册"
|
||||
Width="120"
|
||||
Height="33"
|
||||
mah:ControlsHelper.CornerRadius="5">
|
||||
<Button.Effect>
|
||||
<DropShadowEffect BlurRadius="8"
|
||||
ShadowDepth="3"
|
||||
Opacity="0.5" />
|
||||
</Button.Effect>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- 底部版权信息 -->
|
||||
<TextBlock Grid.Row="2"
|
||||
Text="© 2025 ADP"
|
||||
Foreground="#777"
|
||||
FontSize="12"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,10" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
28
LoginModule/Views/RegisterView.xaml.cs
Normal file
28
LoginModule/Views/RegisterView.xaml.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace LoginModule.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// RegisterView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class RegisterView : UserControl
|
||||
{
|
||||
public RegisterView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
28
MainModule/MainModule.cs
Normal file
28
MainModule/MainModule.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using MainModule.Views;
|
||||
using System.Reflection;
|
||||
using UIShare.GlobalVariable;
|
||||
|
||||
namespace MainModule
|
||||
{
|
||||
[Module(OnDemand=true)]
|
||||
public class MainModule : IModule
|
||||
{
|
||||
public void OnInitialized(IContainerProvider containerProvider)
|
||||
{
|
||||
IRegionManager regionManager = containerProvider.Resolve<IRegionManager>();
|
||||
regionManager.RegisterViewWithRegion("ShellViewManager", typeof(MainView));
|
||||
}
|
||||
|
||||
public void RegisterTypes(IContainerRegistry containerRegistry)
|
||||
{
|
||||
containerRegistry.RegisterForNavigation<MainView>("MainView");
|
||||
containerRegistry.RegisterForNavigation<AutomatedTestingView>("AutomatedTestingView");
|
||||
containerRegistry.RegisterForNavigation<ProtocolStartView>("ProtocolStartView");
|
||||
// Scoped: one ScopedContext per container scope.
|
||||
// AutomatedTestingViewModel creates its own scope (IContainerExtension.CreateScope)
|
||||
// and resolves the 5 child VMs from it, so all siblings inside one parent share
|
||||
// the same ScopedContext, while different parents get isolated instances.
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
15
MainModule/MainModule.csproj
Normal file
15
MainModule/MainModule.csproj
Normal file
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\TestingModule\TestingModule.csproj" />
|
||||
<ProjectReference Include="..\UIShare\UIShare.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
114
MainModule/ViewModels/AutomatedTestingViewModel.cs
Normal file
114
MainModule/ViewModels/AutomatedTestingViewModel.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
using Prism.Ioc;
|
||||
using TestingModule.ViewModels;
|
||||
using UIShare;
|
||||
using UIShare.GlobalVariable;
|
||||
using UIShare.PubEvent;
|
||||
using UIShare.ViewModelBase;
|
||||
|
||||
namespace MainModule.ViewModels
|
||||
{
|
||||
public class AutomatedTestingViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
|
||||
{
|
||||
#region 私有字段
|
||||
private string _testStatus;
|
||||
private readonly IScopedProvider _scope;
|
||||
#endregion
|
||||
|
||||
#region 属性
|
||||
public bool KeepAlive => true; // 保持存活
|
||||
|
||||
public string TestStatus
|
||||
{
|
||||
get => _testStatus;
|
||||
set => SetProperty(ref _testStatus, value);
|
||||
}
|
||||
|
||||
// 该 AutomatedTestingView 实例独占的 ScopedContext,5 个子面板共享
|
||||
public ScopedContext _scopedContext { get; }
|
||||
public StepRunning _stepRunning { get; }
|
||||
public GlobalInfo _globalInfo { get; }
|
||||
public SystemConfig _systemConfig { get; }
|
||||
|
||||
// 5 个子 ViewModel 全部从同一个 scope 解析,自动注入同一个 ScopedContext
|
||||
public CommandTreeViewModel CommandTreeVM { get; }
|
||||
public StepsManagerViewModel StepsManagerVM { get; }
|
||||
public SingleStepEditViewModel SingleStepEditVM { get; }
|
||||
public LogAreaViewModel LogAreaVM { get; }
|
||||
public ParametersManagerViewModel ParametersManagerVM { get; }
|
||||
#endregion
|
||||
|
||||
public ICommand RefreshCommand { get; set; }
|
||||
public ICommand BackToProtocolCommand { get; set; }
|
||||
|
||||
public AutomatedTestingViewModel(IContainerExtension container) : base(container)
|
||||
{
|
||||
// 每个 AutomatedTestingViewModel 实例创建独立的容器作用域
|
||||
_scope = container.CreateScope();
|
||||
_globalInfo=container.Resolve<GlobalInfo>();
|
||||
// 在该作用域内解析 ScopedContext —— 当前作用域唯一
|
||||
_scopedContext = _scope.Resolve<ScopedContext>();
|
||||
_stepRunning = _scope.Resolve<StepRunning>();
|
||||
_systemConfig=_scope.Resolve<SystemConfig>();
|
||||
// 关键:从同一个 _scope 解析 5 个子 VM,DI 会把同一个 ScopedContext 注入它们
|
||||
CommandTreeVM = _scope.Resolve<CommandTreeViewModel>();
|
||||
StepsManagerVM = _scope.Resolve<StepsManagerViewModel>();
|
||||
SingleStepEditVM = _scope.Resolve<SingleStepEditViewModel>();
|
||||
LogAreaVM = _scope.Resolve<LogAreaViewModel>();
|
||||
ParametersManagerVM = _scope.Resolve<ParametersManagerViewModel>();
|
||||
RefreshCommand = new DelegateCommand(OnRefresh);
|
||||
BackToProtocolCommand = new DelegateCommand(OnBackToProtocol);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (string.IsNullOrEmpty(TestStatus))
|
||||
{
|
||||
_scope?.Dispose();
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
_globalInfo.ContextDic?.Remove(TestStatus);
|
||||
_globalInfo.StepRunningDic?.Remove(TestStatus);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LoggerHelper.ErrorWithNotify($"卸载机台 [{TestStatus}] 全局引用失败: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_scope?.Dispose();
|
||||
}
|
||||
}
|
||||
#region 命令处理与事件
|
||||
private void OnRefresh()
|
||||
{
|
||||
// 双击:把自己的名字扔出去
|
||||
_eventAggregator.GetEvent<ExpandViewEvent>().Publish(TestStatus);
|
||||
_globalInfo.CurrentScope = TestStatus;
|
||||
}
|
||||
|
||||
private void OnBackToProtocol()
|
||||
{
|
||||
// 返回:扔个空字符串出去
|
||||
_eventAggregator.GetEvent<ExpandViewEvent>().Publish("");
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 重写
|
||||
public override void OnNavigatedTo(NavigationContext navigationContext)
|
||||
{
|
||||
base.OnNavigatedTo(navigationContext);
|
||||
if (navigationContext.Parameters.ContainsKey("Name"))
|
||||
{
|
||||
TestStatus = navigationContext.Parameters.GetValue<string>("Name");
|
||||
_globalInfo.ContextDic.Add(TestStatus, _scopedContext);
|
||||
_globalInfo.StepRunningDic.Add(TestStatus, _stepRunning);
|
||||
}
|
||||
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
67
MainModule/ViewModels/MainViewModel.cs
Normal file
67
MainModule/ViewModels/MainViewModel.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using UIShare.PubEvent;
|
||||
using MainModule.ViewModels;
|
||||
using UIShare.ViewModelBase;
|
||||
|
||||
|
||||
namespace MainModule.ViewModels
|
||||
{
|
||||
public class MainViewModel : NavigateViewModelBase, IRegionMemberLifetime
|
||||
{
|
||||
#region 私有字段
|
||||
private bool IsInitialized = false;
|
||||
private string _expandedCellName = string.Empty;
|
||||
#endregion
|
||||
|
||||
#region 属性
|
||||
public bool KeepAlive => true; // 保持存活
|
||||
|
||||
public string ExpandedCellName
|
||||
{
|
||||
get => _expandedCellName;
|
||||
set => SetProperty(ref _expandedCellName, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 命令
|
||||
public ICommand LoadedCommand { get; set; }
|
||||
#endregion
|
||||
|
||||
public MainViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
LoadedCommand = new DelegateCommand(OnLoaded);
|
||||
_eventAggregator.GetEvent<ExpandViewEvent>().Subscribe(OnCellExpandRequested);
|
||||
}
|
||||
|
||||
#region 命令处理与事件
|
||||
private void OnLoaded()
|
||||
{
|
||||
if (IsInitialized) return;
|
||||
for (int i = 1; i <= 9; i++)
|
||||
{
|
||||
var parameters = new NavigationParameters { { "Name", $"TestCell{i}" } };
|
||||
_regionManager.RequestNavigate($"TestCell{i}", "ProtocolStartView", parameters);
|
||||
}
|
||||
IsInitialized = true;
|
||||
}
|
||||
|
||||
// 不再物理搬迁视图,只需修改一个字符串属性 ExpandedCellName,
|
||||
// XAML 里每个单元的 Style.Triggers 会根据该值处理隐藏 / 跨越 3x3。
|
||||
private void OnCellExpandRequested(string cellName)
|
||||
{
|
||||
ExpandedCellName = cellName ?? string.Empty;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 重写
|
||||
public override bool IsNavigationTarget(NavigationContext navigationContext)
|
||||
{
|
||||
// 之前帮你改好的单例复用逻辑(防止重复 new 和初始化视图)
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
109
MainModule/ViewModels/ProtocolStartViewModel.cs
Normal file
109
MainModule/ViewModels/ProtocolStartViewModel.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using Prism.Navigation.Regions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using UIShare.ViewModelBase;
|
||||
|
||||
namespace MainModule.ViewModels
|
||||
{
|
||||
public class ProtocolStartViewModel : NavigateViewModelBase
|
||||
{
|
||||
#region 私有字段
|
||||
private string _testStatus;
|
||||
private string _moduleColor;
|
||||
#endregion
|
||||
|
||||
#region 属性
|
||||
public string TestStatus
|
||||
{
|
||||
get => _testStatus;
|
||||
set => SetProperty(ref _testStatus, value);
|
||||
}
|
||||
|
||||
public string ModuleColor
|
||||
{
|
||||
get => _moduleColor;
|
||||
set => SetProperty(ref _moduleColor, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 命令
|
||||
public DelegateCommand StartProtocolCommand { get; }
|
||||
#endregion
|
||||
|
||||
public ProtocolStartViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
StartProtocolCommand = new DelegateCommand(OnStart);
|
||||
}
|
||||
|
||||
#region 命令处理与事件
|
||||
private void OnStart()
|
||||
{
|
||||
// 只在当前格子所属的 Cell Region 内完成切换,不会影响其他格子位置
|
||||
SwitchNavigate("AutomatedTestingView");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 定位当前视图所在的 Cell Region(TestCell1..TestCell9),
|
||||
/// 在同一个 Region 内完成跳转。Region 位置由 XAML Grid 锁定,永远不会错位。
|
||||
/// </summary>
|
||||
public void SwitchNavigate(string viewName)
|
||||
{
|
||||
// 1. 反向查找:哪个 Cell Region 当前托着“我”这个 ProtocolStartView
|
||||
for (int i = 1; i <= 9; i++)
|
||||
{
|
||||
var regionName = $"TestCell{i}";
|
||||
if (!_regionManager.Regions.ContainsRegionWithName(regionName)) continue;
|
||||
|
||||
var region = _regionManager.Regions[regionName];
|
||||
var myView = region.Views
|
||||
.OfType<FrameworkElement>()
|
||||
.FirstOrDefault(v => v.DataContext == this);
|
||||
|
||||
if (myView == null) continue;
|
||||
|
||||
// 2. 透传名称与颜色参数,使 AutomatedTestingViewModel 能正确初始化
|
||||
var parameters = new NavigationParameters();
|
||||
parameters.Add("Name", TestStatus);
|
||||
parameters.Add("Color", ModuleColor);
|
||||
|
||||
// 3. 在本格子 Region 内请求导航,导航成功后再移除旧的 ProtocolStartView 以释放资源
|
||||
_regionManager.RequestNavigate(regionName, viewName, navResult =>
|
||||
{
|
||||
if (navResult.Success == true)
|
||||
{
|
||||
region.Remove(myView);
|
||||
}
|
||||
}, parameters);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 重写
|
||||
public override bool IsNavigationTarget(NavigationContext navigationContext)
|
||||
{
|
||||
if (navigationContext.Parameters.ContainsKey("Name"))
|
||||
return TestStatus == navigationContext.Parameters.GetValue<string>("Name");
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void OnNavigatedTo(NavigationContext navigationContext)
|
||||
{
|
||||
base.OnNavigatedTo(navigationContext);
|
||||
|
||||
// 接收导航传参:名称与颜色
|
||||
if (navigationContext.Parameters.ContainsKey("Name"))
|
||||
TestStatus = navigationContext.Parameters.GetValue<string>("Name");
|
||||
|
||||
if (navigationContext.Parameters.ContainsKey("Color"))
|
||||
ModuleColor = navigationContext.Parameters.GetValue<string>("Color");
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
128
MainModule/Views/AutomatedTestingView.xaml
Normal file
128
MainModule/Views/AutomatedTestingView.xaml
Normal file
@@ -0,0 +1,128 @@
|
||||
<UserControl x:Class="MainModule.Views.AutomatedTestingView"
|
||||
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:prism="http://prismlibrary.com/"
|
||||
xmlns:local="clr-namespace:MainModule.Views"
|
||||
xmlns:b="clr-namespace:UIShare.Behaviors;assembly=UIShare"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:vs="clr-namespace:TestingModule.Views;assembly=TestingModule"
|
||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||
mc:Ignorable="d"
|
||||
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<UserControl.Resources>
|
||||
<converters:LessThanConverter x:Key="LessThanConverter" />
|
||||
</UserControl.Resources>
|
||||
<Border >
|
||||
<i:Interaction.Behaviors>
|
||||
<b:MouseDoubleClickBehavior
|
||||
Command="{Binding DataContext.RefreshCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
|
||||
</i:Interaction.Behaviors>
|
||||
|
||||
<!-- 给最外层 Grid 命名,方便子控件通过 Binding 找到它的 ActualWidth -->
|
||||
<Grid x:Name="RootGrid">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="1.5*"/>
|
||||
<!-- 给第二行取个名字 Row1,方便触发器控制它 -->
|
||||
<RowDefinition x:Name="Row1">
|
||||
<RowDefinition.Style>
|
||||
<Style TargetType="RowDefinition">
|
||||
<!-- 正常情况下是占 1* 比例 -->
|
||||
<Setter Property="Height" Value="1*" />
|
||||
<Style.Triggers>
|
||||
<!-- 当宽度小于 600 时,把第二行的高度死死锁在 0 像素 -->
|
||||
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
|
||||
<Setter Property="Height" Value="0" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</RowDefinition.Style>
|
||||
</RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- ==================== 1. 左侧:命令树 ==================== -->
|
||||
<vs:CommandTree DataContext="{Binding CommandTreeVM}"
|
||||
Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Margin="5">
|
||||
<vs:CommandTree.Style>
|
||||
<Style TargetType="UserControl">
|
||||
<Style.Triggers>
|
||||
<!-- 核心逻辑:当最外层 Grid 宽度小于 600 时,隐藏自己 -->
|
||||
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</vs:CommandTree.Style>
|
||||
</vs:CommandTree>
|
||||
|
||||
<!-- ==================== 2. 中上:步骤管理(核心显示) ==================== -->
|
||||
<!-- 1. 注意:标签上不要再写任何 Grid.Row、Grid.Column、Grid.ColumnSpan 属性了! -->
|
||||
<vs:StepsManager DataContext="{Binding StepsManagerVM}"
|
||||
Margin="5">
|
||||
<vs:StepsManager.Style>
|
||||
<Style TargetType="UserControl">
|
||||
<!-- 2. 【核心】把默认的网格位置写在这里(低优先级) -->
|
||||
<Setter Property="Grid.Row" Value="0"/>
|
||||
<Setter Property="Grid.Column" Value="1"/>
|
||||
<Setter Property="Grid.RowSpan" Value="1"/>
|
||||
<Setter Property="Grid.ColumnSpan" Value="2"/>
|
||||
|
||||
<Style.Triggers>
|
||||
<!-- 3. 当宽度小于 600 时,触发器就会顺理成章地覆盖上面的默认值,变成全屏 -->
|
||||
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
|
||||
<Setter Property="Grid.Row" Value="0" />
|
||||
<Setter Property="Grid.Column" Value="0" />
|
||||
<!-- 附加属性在 Trigger 里的标准全称写法就是这样,只要上面没有本地值冲突,它就能完美生效 -->
|
||||
<Setter Property="Grid.RowSpan" Value="2" />
|
||||
<Setter Property="Grid.ColumnSpan" Value="4" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</vs:StepsManager.Style>
|
||||
</vs:StepsManager>
|
||||
|
||||
<!-- ==================== 3. 右上:单步编辑 ==================== -->
|
||||
<vs:SingleStepEdit DataContext="{Binding SingleStepEditVM}"
|
||||
Grid.Row="0" Grid.Column="3" Margin="5">
|
||||
<vs:SingleStepEdit.Style>
|
||||
<Style TargetType="UserControl">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</vs:SingleStepEdit.Style>
|
||||
</vs:SingleStepEdit>
|
||||
|
||||
<!-- ==================== 4. 中下:日志显示 ==================== -->
|
||||
<vs:LogArea DataContext="{Binding LogAreaVM}"
|
||||
Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Margin="5">
|
||||
<!-- 这里不需要写 Visibility 触发器了,保持默认即可 -->
|
||||
</vs:LogArea>
|
||||
|
||||
<!-- ==================== 5. 右下:参数管理 ==================== -->
|
||||
<vs:ParametersManager DataContext="{Binding ParametersManagerVM}"
|
||||
Grid.Row="1" Grid.Column="3" Margin="5">
|
||||
<vs:ParametersManager.Style>
|
||||
<Style TargetType="UserControl">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</vs:ParametersManager.Style>
|
||||
</vs:ParametersManager>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
28
MainModule/Views/AutomatedTestingView.xaml.cs
Normal file
28
MainModule/Views/AutomatedTestingView.xaml.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace MainModule.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// AutomatedTestingView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class AutomatedTestingView : UserControl
|
||||
{
|
||||
public AutomatedTestingView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
258
MainModule/Views/MainView.xaml
Normal file
258
MainModule/Views/MainView.xaml
Normal file
@@ -0,0 +1,258 @@
|
||||
<UserControl x:Class="MainModule.Views.MainView"
|
||||
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:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
mc:Ignorable="d" d:DesignHeight="1080" d:DesignWidth="1920">
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="Loaded">
|
||||
<i:InvokeCommandAction Command="{Binding LoadedCommand}"/>
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
|
||||
<UserControl.Resources>
|
||||
<converters:StringToVisibilityConverter x:Key="StringToVisibilityConverter"/>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 单元 1:TestCell1,默认 (0,0) -->
|
||||
<ContentControl prism:RegionManager.RegionName="TestCell1" Margin="2">
|
||||
<ContentControl.Style>
|
||||
<Style TargetType="ContentControl">
|
||||
<Setter Property="Grid.Row" Value="0"/>
|
||||
<Setter Property="Grid.Column" Value="0"/>
|
||||
<Setter Property="Grid.RowSpan" Value="1"/>
|
||||
<Setter Property="Grid.ColumnSpan" Value="1"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ExpandedCellName, Converter={StaticResource StringToVisibilityConverter}}" Value="Visible">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding ExpandedCellName}" Value="TestCell1">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
<Setter Property="Grid.Row" Value="0"/>
|
||||
<Setter Property="Grid.Column" Value="0"/>
|
||||
<Setter Property="Grid.RowSpan" Value="3"/>
|
||||
<Setter Property="Grid.ColumnSpan" Value="3"/>
|
||||
<Setter Property="Panel.ZIndex" Value="99"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ContentControl.Style>
|
||||
</ContentControl>
|
||||
|
||||
<!-- 单元 2:TestCell2,默认 (0,1) -->
|
||||
<ContentControl prism:RegionManager.RegionName="TestCell2" Margin="2">
|
||||
<ContentControl.Style>
|
||||
<Style TargetType="ContentControl">
|
||||
<Setter Property="Grid.Row" Value="0"/>
|
||||
<Setter Property="Grid.Column" Value="1"/>
|
||||
<Setter Property="Grid.RowSpan" Value="1"/>
|
||||
<Setter Property="Grid.ColumnSpan" Value="1"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ExpandedCellName, Converter={StaticResource StringToVisibilityConverter}}" Value="Visible">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding ExpandedCellName}" Value="TestCell2">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
<Setter Property="Grid.Row" Value="0"/>
|
||||
<Setter Property="Grid.Column" Value="0"/>
|
||||
<Setter Property="Grid.RowSpan" Value="3"/>
|
||||
<Setter Property="Grid.ColumnSpan" Value="3"/>
|
||||
<Setter Property="Panel.ZIndex" Value="99"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ContentControl.Style>
|
||||
</ContentControl>
|
||||
|
||||
<!-- 单元 3:TestCell3,默认 (0,2) -->
|
||||
<ContentControl prism:RegionManager.RegionName="TestCell3" Margin="2">
|
||||
<ContentControl.Style>
|
||||
<Style TargetType="ContentControl">
|
||||
<Setter Property="Grid.Row" Value="0"/>
|
||||
<Setter Property="Grid.Column" Value="2"/>
|
||||
<Setter Property="Grid.RowSpan" Value="1"/>
|
||||
<Setter Property="Grid.ColumnSpan" Value="1"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ExpandedCellName, Converter={StaticResource StringToVisibilityConverter}}" Value="Visible">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding ExpandedCellName}" Value="TestCell3">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
<Setter Property="Grid.Row" Value="0"/>
|
||||
<Setter Property="Grid.Column" Value="0"/>
|
||||
<Setter Property="Grid.RowSpan" Value="3"/>
|
||||
<Setter Property="Grid.ColumnSpan" Value="3"/>
|
||||
<Setter Property="Panel.ZIndex" Value="99"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ContentControl.Style>
|
||||
</ContentControl>
|
||||
|
||||
<!-- 单元 4:TestCell4,默认 (1,0) -->
|
||||
<ContentControl prism:RegionManager.RegionName="TestCell4" Margin="2">
|
||||
<ContentControl.Style>
|
||||
<Style TargetType="ContentControl">
|
||||
<Setter Property="Grid.Row" Value="1"/>
|
||||
<Setter Property="Grid.Column" Value="0"/>
|
||||
<Setter Property="Grid.RowSpan" Value="1"/>
|
||||
<Setter Property="Grid.ColumnSpan" Value="1"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ExpandedCellName, Converter={StaticResource StringToVisibilityConverter}}" Value="Visible">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding ExpandedCellName}" Value="TestCell4">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
<Setter Property="Grid.Row" Value="0"/>
|
||||
<Setter Property="Grid.Column" Value="0"/>
|
||||
<Setter Property="Grid.RowSpan" Value="3"/>
|
||||
<Setter Property="Grid.ColumnSpan" Value="3"/>
|
||||
<Setter Property="Panel.ZIndex" Value="99"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ContentControl.Style>
|
||||
</ContentControl>
|
||||
|
||||
<!-- 单元 5:TestCell5,默认 (1,1) -->
|
||||
<ContentControl prism:RegionManager.RegionName="TestCell5" Margin="2">
|
||||
<ContentControl.Style>
|
||||
<Style TargetType="ContentControl">
|
||||
<Setter Property="Grid.Row" Value="1"/>
|
||||
<Setter Property="Grid.Column" Value="1"/>
|
||||
<Setter Property="Grid.RowSpan" Value="1"/>
|
||||
<Setter Property="Grid.ColumnSpan" Value="1"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ExpandedCellName, Converter={StaticResource StringToVisibilityConverter}}" Value="Visible">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding ExpandedCellName}" Value="TestCell5">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
<Setter Property="Grid.Row" Value="0"/>
|
||||
<Setter Property="Grid.Column" Value="0"/>
|
||||
<Setter Property="Grid.RowSpan" Value="3"/>
|
||||
<Setter Property="Grid.ColumnSpan" Value="3"/>
|
||||
<Setter Property="Panel.ZIndex" Value="99"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ContentControl.Style>
|
||||
</ContentControl>
|
||||
|
||||
<!-- 单元 6:TestCell6,默认 (1,2) -->
|
||||
<ContentControl prism:RegionManager.RegionName="TestCell6" Margin="2">
|
||||
<ContentControl.Style>
|
||||
<Style TargetType="ContentControl">
|
||||
<Setter Property="Grid.Row" Value="1"/>
|
||||
<Setter Property="Grid.Column" Value="2"/>
|
||||
<Setter Property="Grid.RowSpan" Value="1"/>
|
||||
<Setter Property="Grid.ColumnSpan" Value="1"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ExpandedCellName, Converter={StaticResource StringToVisibilityConverter}}" Value="Visible">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding ExpandedCellName}" Value="TestCell6">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
<Setter Property="Grid.Row" Value="0"/>
|
||||
<Setter Property="Grid.Column" Value="0"/>
|
||||
<Setter Property="Grid.RowSpan" Value="3"/>
|
||||
<Setter Property="Grid.ColumnSpan" Value="3"/>
|
||||
<Setter Property="Panel.ZIndex" Value="99"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ContentControl.Style>
|
||||
</ContentControl>
|
||||
|
||||
<!-- 单元 7:TestCell7,默认 (2,0) -->
|
||||
<ContentControl prism:RegionManager.RegionName="TestCell7" Margin="2">
|
||||
<ContentControl.Style>
|
||||
<Style TargetType="ContentControl">
|
||||
<Setter Property="Grid.Row" Value="2"/>
|
||||
<Setter Property="Grid.Column" Value="0"/>
|
||||
<Setter Property="Grid.RowSpan" Value="1"/>
|
||||
<Setter Property="Grid.ColumnSpan" Value="1"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ExpandedCellName, Converter={StaticResource StringToVisibilityConverter}}" Value="Visible">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding ExpandedCellName}" Value="TestCell7">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
<Setter Property="Grid.Row" Value="0"/>
|
||||
<Setter Property="Grid.Column" Value="0"/>
|
||||
<Setter Property="Grid.RowSpan" Value="3"/>
|
||||
<Setter Property="Grid.ColumnSpan" Value="3"/>
|
||||
<Setter Property="Panel.ZIndex" Value="99"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ContentControl.Style>
|
||||
</ContentControl>
|
||||
|
||||
<!-- 单元 8:TestCell8,默认 (2,1) -->
|
||||
<ContentControl prism:RegionManager.RegionName="TestCell8" Margin="2">
|
||||
<ContentControl.Style>
|
||||
<Style TargetType="ContentControl">
|
||||
<Setter Property="Grid.Row" Value="2"/>
|
||||
<Setter Property="Grid.Column" Value="1"/>
|
||||
<Setter Property="Grid.RowSpan" Value="1"/>
|
||||
<Setter Property="Grid.ColumnSpan" Value="1"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ExpandedCellName, Converter={StaticResource StringToVisibilityConverter}}" Value="Visible">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding ExpandedCellName}" Value="TestCell8">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
<Setter Property="Grid.Row" Value="0"/>
|
||||
<Setter Property="Grid.Column" Value="0"/>
|
||||
<Setter Property="Grid.RowSpan" Value="3"/>
|
||||
<Setter Property="Grid.ColumnSpan" Value="3"/>
|
||||
<Setter Property="Panel.ZIndex" Value="99"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ContentControl.Style>
|
||||
</ContentControl>
|
||||
|
||||
<!-- 单元 9:TestCell9,默认 (2,2) -->
|
||||
<ContentControl prism:RegionManager.RegionName="TestCell9" Margin="2">
|
||||
<ContentControl.Style>
|
||||
<Style TargetType="ContentControl">
|
||||
<Setter Property="Grid.Row" Value="2"/>
|
||||
<Setter Property="Grid.Column" Value="2"/>
|
||||
<Setter Property="Grid.RowSpan" Value="1"/>
|
||||
<Setter Property="Grid.ColumnSpan" Value="1"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ExpandedCellName, Converter={StaticResource StringToVisibilityConverter}}" Value="Visible">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding ExpandedCellName}" Value="TestCell9">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
<Setter Property="Grid.Row" Value="0"/>
|
||||
<Setter Property="Grid.Column" Value="0"/>
|
||||
<Setter Property="Grid.RowSpan" Value="3"/>
|
||||
<Setter Property="Grid.ColumnSpan" Value="3"/>
|
||||
<Setter Property="Panel.ZIndex" Value="99"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ContentControl.Style>
|
||||
</ContentControl>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
19
MainModule/Views/MainView.xaml.cs
Normal file
19
MainModule/Views/MainView.xaml.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using MainModule.ViewModels;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace MainModule.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// MainView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class MainView : UserControl
|
||||
{
|
||||
public MainView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
85
MainModule/Views/ProtocolStartView.xaml
Normal file
85
MainModule/Views/ProtocolStartView.xaml
Normal file
@@ -0,0 +1,85 @@
|
||||
<UserControl x:Class="MainModule.Views.ProtocolStartView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True">
|
||||
|
||||
<Grid>
|
||||
<Grid.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
|
||||
<GradientStop Color="#F8FAFC" Offset="0"/>
|
||||
<GradientStop Color="#E2E8F0" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</Grid.Background>
|
||||
|
||||
<materialDesign:Card Width="320" Height="380"
|
||||
UniformCornerRadius="16"
|
||||
Background="#FFFFFF"
|
||||
materialDesign:ElevationAssist.Elevation="Dp4"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
|
||||
<Grid Margin="30">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Grid.Row="0" HorizontalAlignment="Center" Margin="0,8,0,0">
|
||||
<materialDesign:PackIcon Kind="Chip"
|
||||
Width="24" Height="24"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="#94A3B8"/>
|
||||
<TextBlock Text="{Binding TestStatus}"
|
||||
FontSize="15"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="#64748B"
|
||||
Margin="0,6,0,0"
|
||||
HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
|
||||
<Grid Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<Ellipse Width="150" Height="150" Fill="#F1F5F9"/>
|
||||
<Ellipse Width="130" Height="130" Fill="#E2E8F0"/>
|
||||
|
||||
<Button Command="{Binding StartProtocolCommand}"
|
||||
Width="110" Height="110"
|
||||
Style="{StaticResource MaterialDesignFloatingActionDarkButton}"
|
||||
Background="#1E293B"
|
||||
BorderBrush="#334155"
|
||||
BorderThickness="2"
|
||||
materialDesign:ElevationAssist.Elevation="Dp6"
|
||||
materialDesign:RippleAssist.Feedback="#38BDF8">
|
||||
<Button.Resources>
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="CornerRadius" Value="55"/>
|
||||
</Style>
|
||||
</Button.Resources>
|
||||
|
||||
<materialDesign:PackIcon Kind="Play"
|
||||
Width="48" Height="48"
|
||||
Foreground="#38BDF8"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Margin="4,0,0,0"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Grid.Row="2" HorizontalAlignment="Center" Margin="0,0,0,8">
|
||||
<TextBlock Text="READY"
|
||||
FontSize="13"
|
||||
FontWeight="Black"
|
||||
Foreground="#10B981"
|
||||
HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="点击按钮加载测试序列"
|
||||
FontSize="12"
|
||||
Foreground="#94A3B8"
|
||||
Margin="0,4,0,0"
|
||||
HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</materialDesign:Card>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
28
MainModule/Views/ProtocolStartView.xaml.cs
Normal file
28
MainModule/Views/ProtocolStartView.xaml.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace MainModule.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// ProtocolStartView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class ProtocolStartView : UserControl
|
||||
{
|
||||
public ProtocolStartView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Model/Entity/BaseEntity.cs
Normal file
27
Model/Entity/BaseEntity.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using SqlSugar;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Model.Entity
|
||||
{
|
||||
public abstract class BaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 主键Id
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnName = "id", ColumnDescription = "主键Id", IsPrimaryKey = true, CreateTableFieldSort = 0)]
|
||||
public virtual long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 删除状态
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnName = "IsDel", ColumnDescription = "删除状态(0、未删除;1、已删除)", ColumnDataType = "tinyint", DefaultValue = "0", CreateTableFieldSort = 106)]
|
||||
public virtual byte IsDel { get; set; }
|
||||
|
||||
[SugarColumn(ColumnName = "CreateTime")]
|
||||
public DateTime CreateTime { get; set; }
|
||||
}
|
||||
}
|
||||
13
Model/Model.csproj
Normal file
13
Model/Model.csproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SqlSugarCore" Version="5.1.4.215-preview13" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
180
Model/Result.cs
Normal file
180
Model/Result.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Model
|
||||
{
|
||||
public class Result
|
||||
{
|
||||
public Result()
|
||||
{
|
||||
|
||||
}
|
||||
private int _code;
|
||||
private readonly string _msg;
|
||||
|
||||
/// <summary>
|
||||
/// 结果是否成功
|
||||
/// </summary>
|
||||
public bool IsSuccess => _code == 0;
|
||||
|
||||
/// <summary>
|
||||
/// 结果代码
|
||||
/// </summary>
|
||||
public int Code { set => _code = value; get => _code; }
|
||||
|
||||
/// <summary>
|
||||
/// 结果消息
|
||||
/// </summary>
|
||||
public string Msg => _msg;
|
||||
|
||||
/// <summary>
|
||||
/// 构造方法
|
||||
/// </summary>
|
||||
/// <param name="code"></param>
|
||||
/// <param name="msg"></param>
|
||||
/// <param name="exception"></param>
|
||||
protected Result(int code, string msg, Exception? exception = null)
|
||||
{
|
||||
if (exception != null)
|
||||
{
|
||||
var listMoreMsg = new List<string>();
|
||||
if (exception is ResultException resultException)
|
||||
{
|
||||
listMoreMsg.AddRange(resultException.MessageList);
|
||||
}
|
||||
//else if (exception is DeviceControlException deviceControlException)
|
||||
//{
|
||||
// listMoreMsg.Add(deviceControlException.ErrorInfo);
|
||||
//}
|
||||
else
|
||||
{
|
||||
listMoreMsg.Add(exception.Message);
|
||||
}
|
||||
var strMoreMsg = string.Join("、", listMoreMsg.Where(it => !string.IsNullOrWhiteSpace(it)));
|
||||
if (!string.IsNullOrWhiteSpace(strMoreMsg))
|
||||
{
|
||||
msg += $"({strMoreMsg})";
|
||||
}
|
||||
}
|
||||
_code = code;
|
||||
_msg = msg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回成功结果
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public static Result Success()
|
||||
{
|
||||
return new Result(0, "");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回错误结果
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
/// <param name="exception"></param>
|
||||
/// <returns></returns>
|
||||
public static Result Error(string msg, Exception? exception = null)
|
||||
{
|
||||
return new Result(-1, msg, exception);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public class Result<T> : Result
|
||||
{
|
||||
public Result()
|
||||
{
|
||||
|
||||
}
|
||||
private T? _data;
|
||||
|
||||
/// <summary>
|
||||
/// 结果数据
|
||||
/// </summary>
|
||||
public T? Data { set => _data = value; get => _data; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造方法
|
||||
/// </summary>
|
||||
/// <param name="code"></param>
|
||||
/// <param name="msg"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="exception"></param>
|
||||
private Result(int code, string msg, T? data, Exception? exception = null) : base(code, msg, exception)
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回成功数据
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public static Result<T> Success(T data)
|
||||
{
|
||||
return new Result<T>(0, "", data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回失败信息
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
/// <param name="exception"></param>
|
||||
/// <returns></returns>
|
||||
public new static Result<T> Error(string msg, Exception? exception = null)
|
||||
{
|
||||
return new Result<T>(-1, msg, default, exception);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回失败信息和数据
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="exception"></param>
|
||||
/// <returns></returns>
|
||||
public static Result<T> Error(string msg, T data, Exception? exception = null)
|
||||
{
|
||||
return new Result<T>(-1, msg, data, exception);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public class ResultException : Exception
|
||||
{
|
||||
private List<string>? _messageList;
|
||||
|
||||
public List<string> MessageList
|
||||
{
|
||||
get
|
||||
{
|
||||
List<string> messages = new();
|
||||
if (_messageList != null)
|
||||
{
|
||||
messages.AddRange(_messageList.Where(it => !string.IsNullOrWhiteSpace(it)));
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
|
||||
public override string Message => string.Join(", ", MessageList);
|
||||
|
||||
public ResultException() : base(null)
|
||||
{
|
||||
}
|
||||
|
||||
public void AddAdditionMessage(string message)
|
||||
{
|
||||
_messageList ??= new();
|
||||
_messageList.Add(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
0
MonitorModule.txt
Normal file
0
MonitorModule.txt
Normal file
21
MonitorModule/MonitorModule.cs
Normal file
21
MonitorModule/MonitorModule.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
using MonitorModule.Views;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MonitorModule
|
||||
{
|
||||
public class MonitorModule: IModule
|
||||
{
|
||||
public void OnInitialized(IContainerProvider containerProvider)
|
||||
{
|
||||
IRegionManager regionManager = containerProvider.Resolve<IRegionManager>();
|
||||
}
|
||||
|
||||
public void RegisterTypes(IContainerRegistry containerRegistry)
|
||||
{
|
||||
containerRegistry.RegisterForNavigation<MonitorView>("MonitorView");
|
||||
containerRegistry.RegisterForNavigation<RecordView>("RecordView");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
18
MonitorModule/MonitorModule.csproj
Normal file
18
MonitorModule/MonitorModule.csproj
Normal file
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Service\Service.csproj" />
|
||||
<ProjectReference Include="..\UIShare\UIShare.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OxyPlot.Wpf" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
330
MonitorModule/ViewModels/MonitorViewModel.cs
Normal file
330
MonitorModule/ViewModels/MonitorViewModel.cs
Normal file
@@ -0,0 +1,330 @@
|
||||
using OxyPlot;
|
||||
using OxyPlot.Axes;
|
||||
using OxyPlot.Legends;
|
||||
using OxyPlot.Series;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
using UIShare.GlobalVariable;
|
||||
using UIShare.PubEvent;
|
||||
using UIShare.ViewModelBase;
|
||||
|
||||
namespace MonitorModule.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 监控界面 VM —— 基于 OxyPlot 的曲线监控壳。
|
||||
/// 当前阶段不接入真实数据源,仅搭好框架:
|
||||
/// - 添加信号 / 删除信号
|
||||
/// - 重置视图(按数据范围复原)
|
||||
/// - 刷新(重绘 PlotView)
|
||||
/// 仍延续 ScopedContext 隔离 + 双击展开 的范式,每个工位独立一份图表与信号列表。
|
||||
/// </summary>
|
||||
public class MonitorViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
|
||||
{
|
||||
#region 私有字段
|
||||
private readonly IScopedProvider _scope;
|
||||
// 颜色轮转池,给新加的信号自动分配区分色
|
||||
private static readonly OxyColor[] _palette =
|
||||
{
|
||||
OxyColors.SteelBlue, OxyColors.IndianRed, OxyColors.SeaGreen,
|
||||
OxyColors.DarkOrange, OxyColors.MediumPurple, OxyColors.Goldenrod,
|
||||
OxyColors.Teal, OxyColors.Crimson, OxyColors.OliveDrab
|
||||
};
|
||||
private int _signalCounter;
|
||||
|
||||
// ===== 模拟数据相关 =====
|
||||
// 模拟定时器:周期性给每条信号追加新点,形成滚动效果
|
||||
private readonly DispatcherTimer _simTimer;
|
||||
// 全局采样时间(秒),每 tick 自增 _simInterval
|
||||
private double _simT;
|
||||
// 采样间隔(秒),与 DispatcherTimer.Interval 保持一致
|
||||
private const double _simInterval = 0.1;
|
||||
// 滚动窗口大小:每条曲线最多保留 200 个点(约 20 秒)
|
||||
private const int _maxPoints = 200;
|
||||
// 用于 random walk 的随机源
|
||||
private static readonly Random _rng = new();
|
||||
#endregion
|
||||
|
||||
#region 隔离 / 标题
|
||||
public bool KeepAlive => true;
|
||||
public ScopedContext _scopedContext { get; }
|
||||
public GlobalInfo GlobalInfoRef { get; }
|
||||
|
||||
private string _testStatus = string.Empty;
|
||||
public string TestStatus
|
||||
{
|
||||
get => _testStatus;
|
||||
set => SetProperty(ref _testStatus, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region OxyPlot
|
||||
/// <summary>PlotView 直接绑这个 PlotModel</summary>
|
||||
public PlotModel Plot { get; }
|
||||
|
||||
/// <summary>当前已添加的信号集合(左侧列表展示)</summary>
|
||||
public ObservableCollection<SignalItem> Signals { get; } = new();
|
||||
|
||||
private SignalItem? _selectedSignal;
|
||||
public SignalItem? SelectedSignal
|
||||
{
|
||||
get => _selectedSignal;
|
||||
set => SetProperty(ref _selectedSignal, value);
|
||||
}
|
||||
|
||||
private string _statusMessage = "图表已就绪,暂无信号";
|
||||
public string StatusMessage
|
||||
{
|
||||
get => _statusMessage;
|
||||
set => SetProperty(ref _statusMessage, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 命令
|
||||
public ICommand AddSignalCommand { get; }
|
||||
public ICommand DeleteSignalCommand { get; }
|
||||
public ICommand ResetViewCommand { get; }
|
||||
public ICommand RefreshDataCommand { get; }
|
||||
// 双击展开/折叠:与 RecordView/AutomatedTestingView 共用同一套 ExpandViewEvent
|
||||
public ICommand RefreshCommand { get; }
|
||||
#endregion
|
||||
|
||||
public MonitorViewModel(IContainerExtension container) : base(container)
|
||||
{
|
||||
_scope = container.CreateScope();
|
||||
GlobalInfoRef = container.Resolve<GlobalInfo>();
|
||||
_scopedContext = _scope.Resolve<ScopedContext>();
|
||||
|
||||
Plot = BuildEmptyPlot();
|
||||
|
||||
AddSignalCommand = new DelegateCommand(OnAddSignal);
|
||||
DeleteSignalCommand = new DelegateCommand(OnDeleteSignal);
|
||||
ResetViewCommand = new DelegateCommand(OnResetView);
|
||||
RefreshDataCommand = new DelegateCommand(OnRefreshData);
|
||||
RefreshCommand = new DelegateCommand(OnExpand);
|
||||
|
||||
// 启动模拟数据定时器:100ms 一帧,给每条信号喂一个新点
|
||||
_simTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(_simInterval) };
|
||||
_simTimer.Tick += OnSimTick;
|
||||
_simTimer.Start();
|
||||
|
||||
// 默认预置 3 条模拟信号,便于直接看到滚动效果
|
||||
OnAddSignal(); // sin
|
||||
OnAddSignal(); // cos
|
||||
OnAddSignal(); // random walk
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_simTimer?.Stop();
|
||||
_scope?.Dispose();
|
||||
}
|
||||
|
||||
#region PlotModel 构建
|
||||
private static PlotModel BuildEmptyPlot()
|
||||
{
|
||||
var pm = new PlotModel
|
||||
{
|
||||
Title = string.Empty,
|
||||
PlotAreaBorderColor = OxyColors.LightGray,
|
||||
Background = OxyColors.White
|
||||
};
|
||||
pm.Axes.Add(new LinearAxis
|
||||
{
|
||||
Position = AxisPosition.Bottom,
|
||||
Title = "X",
|
||||
MajorGridlineStyle = LineStyle.Dot,
|
||||
MinorGridlineStyle = LineStyle.None
|
||||
});
|
||||
pm.Axes.Add(new LinearAxis
|
||||
{
|
||||
Position = AxisPosition.Left,
|
||||
Title = "Y",
|
||||
MajorGridlineStyle = LineStyle.Dot,
|
||||
MinorGridlineStyle = LineStyle.None
|
||||
});
|
||||
pm.Legends.Add(new Legend
|
||||
{
|
||||
LegendPosition = LegendPosition.RightTop,
|
||||
LegendBackground = OxyColor.FromAColor(200, OxyColors.White),
|
||||
LegendBorder = OxyColors.LightGray
|
||||
});
|
||||
return pm;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 命令处理
|
||||
/// <summary>
|
||||
/// 添加一条信号:按 _signalCounter 轮转选择不同波形 generator,
|
||||
/// 后续 _simTimer 每帧会调用 generator(t) 给曲线追加新点形成滚动效果。
|
||||
/// </summary>
|
||||
private void OnAddSignal()
|
||||
{
|
||||
_signalCounter++;
|
||||
var color = _palette[(_signalCounter - 1) % _palette.Length];
|
||||
|
||||
// 6 种波形轮转:sin / cos / 锯齿 / 方波 / 衰减正弦 / random walk
|
||||
var (waveName, generator) = BuildGenerator(_signalCounter);
|
||||
var name = $"{waveName}_{_signalCounter}";
|
||||
|
||||
var series = new LineSeries
|
||||
{
|
||||
Title = name,
|
||||
Color = color,
|
||||
StrokeThickness = 1.5
|
||||
};
|
||||
|
||||
var item = new SignalItem(name, series, generator);
|
||||
Signals.Add(item);
|
||||
SelectedSignal = item;
|
||||
|
||||
Plot.Series.Add(series);
|
||||
Plot.InvalidatePlot(true);
|
||||
|
||||
StatusMessage = $"已添加信号 [{name}],当前共 {Signals.Count} 条";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按索引返回一种模拟波形发生器。
|
||||
/// 振幅 / 频率 / 相位都做了区分,让多条曲线视觉上分开。
|
||||
/// </summary>
|
||||
private static (string waveName, Func<double, double> generator) BuildGenerator(int index)
|
||||
{
|
||||
// 给同种波形不同实例一些随机偏移,避免完全重叠
|
||||
double phase = (index * 0.7) % (2 * Math.PI);
|
||||
double amp = 1.0 + (index % 3) * 0.3;
|
||||
double freq = 0.5 + (index % 4) * 0.2;
|
||||
|
||||
return (index % 6) switch
|
||||
{
|
||||
0 => ("Sin", t => amp * Math.Sin(2 * Math.PI * freq * t + phase)),
|
||||
1 => ("Cos", t => amp * Math.Cos(2 * Math.PI * freq * t + phase)),
|
||||
2 => ("Saw", t => amp * (2 * ((t * freq) - Math.Floor(t * freq + 0.5)))),
|
||||
3 => ("Square", t => amp * Math.Sign(Math.Sin(2 * Math.PI * freq * t + phase))),
|
||||
4 => ("Decay", t => amp * Math.Exp(-0.05 * t) * Math.Sin(2 * Math.PI * freq * t + phase)),
|
||||
_ => RandomWalkGenerator(amp),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>构造一个随机游走 generator:在前一次值基础上叠加高斯噪声。</summary>
|
||||
private static (string, Func<double, double>) RandomWalkGenerator(double amp)
|
||||
{
|
||||
double last = 0;
|
||||
return ("Walk", _ =>
|
||||
{
|
||||
last += (_rng.NextDouble() - 0.5) * 0.2 * amp;
|
||||
// 软约束在 [-amp*3, amp*3],避免一直跑偏
|
||||
if (last > amp * 3) last = amp * 3;
|
||||
if (last < -amp * 3) last = -amp * 3;
|
||||
return last;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>删除当前选中信号;若未选中则删除最后一条。</summary>
|
||||
private void OnDeleteSignal()
|
||||
{
|
||||
var target = SelectedSignal ?? Signals.LastOrDefault();
|
||||
if (target == null)
|
||||
{
|
||||
StatusMessage = "无可删除的信号";
|
||||
return;
|
||||
}
|
||||
|
||||
Plot.Series.Remove(target.Series);
|
||||
Signals.Remove(target);
|
||||
SelectedSignal = Signals.LastOrDefault();
|
||||
|
||||
Plot.InvalidatePlot(true);
|
||||
StatusMessage = $"已删除信号 [{target.Name}],剩余 {Signals.Count} 条";
|
||||
}
|
||||
|
||||
/// <summary>按数据范围复原视图(重置所有坐标轴的缩放/平移)。</summary>
|
||||
private void OnResetView()
|
||||
{
|
||||
Plot.ResetAllAxes();
|
||||
Plot.InvalidatePlot(false);
|
||||
StatusMessage = "视图已按数据范围复原";
|
||||
}
|
||||
|
||||
/// <summary>刷新:触发 PlotView 重绘;后续接数据源时可在此重拉数据。</summary>
|
||||
private void OnRefreshData()
|
||||
{
|
||||
Plot.InvalidatePlot(true);
|
||||
StatusMessage = $"已刷新({DateTime.Now:HH:mm:ss})";
|
||||
}
|
||||
|
||||
/// <summary>双击展开 / 折叠九宫格。</summary>
|
||||
private void OnExpand()
|
||||
{
|
||||
if (string.IsNullOrEmpty(TestStatus)) return;
|
||||
_eventAggregator.GetEvent<ExpandViewEvent>().Publish(TestStatus);
|
||||
GlobalInfoRef.CurrentScope = TestStatus;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 模拟数据 tick:每条信号按当前 _simT 算出新点并追加。
|
||||
/// 超过 _maxPoints 时丢弃最旧的点形成滚动窗口。
|
||||
/// </summary>
|
||||
private void OnSimTick(object? sender, EventArgs e)
|
||||
{
|
||||
_simT += _simInterval;
|
||||
|
||||
if (Signals.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var item in Signals)
|
||||
{
|
||||
double y = item.Generator(_simT);
|
||||
item.Series.Points.Add(new DataPoint(_simT, y));
|
||||
if (item.Series.Points.Count > _maxPoints)
|
||||
{
|
||||
item.Series.Points.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 让 X 轴跟着最新数据滚动
|
||||
var xAxis = Plot.Axes.FirstOrDefault(a => a.Position == AxisPosition.Bottom);
|
||||
if (xAxis != null)
|
||||
{
|
||||
double window = _maxPoints * _simInterval;
|
||||
xAxis.Minimum = Math.Max(0, _simT - window);
|
||||
xAxis.Maximum = _simT + 0.5;
|
||||
}
|
||||
|
||||
Plot.InvalidatePlot(true);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 导航
|
||||
public override void OnNavigatedTo(NavigationContext navigationContext)
|
||||
{
|
||||
base.OnNavigatedTo(navigationContext);
|
||||
if (navigationContext.Parameters.ContainsKey("Name"))
|
||||
{
|
||||
TestStatus = navigationContext.Parameters.GetValue<string>("Name");
|
||||
Plot.Title = $"监控 - {TestStatus}";
|
||||
Plot.InvalidatePlot(false);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 信号列表项:UI 显示名 + 对应的 OxyPlot LineSeries 引用 + 模拟数据 generator。
|
||||
/// generator(t) 接收当前模拟时间,返回该时刻 y 值。
|
||||
/// </summary>
|
||||
public class SignalItem
|
||||
{
|
||||
public string Name { get; }
|
||||
public LineSeries Series { get; }
|
||||
public Func<double, double> Generator { get; }
|
||||
public SignalItem(string name, LineSeries series, Func<double, double> generator)
|
||||
{
|
||||
Name = name;
|
||||
Series = series;
|
||||
Generator = generator;
|
||||
}
|
||||
}
|
||||
}
|
||||
362
MonitorModule/ViewModels/RecordViewModel.cs
Normal file
362
MonitorModule/ViewModels/RecordViewModel.cs
Normal file
@@ -0,0 +1,362 @@
|
||||
using Logger;
|
||||
using SqlSugar;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Windows.Input;
|
||||
using UIShare.GlobalVariable;
|
||||
using UIShare.PubEvent;
|
||||
using UIShare.ViewModelBase;
|
||||
|
||||
namespace MonitorModule.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 记录界面:用于查看运行目录下 SQL/ADP.db 中的数据。
|
||||
/// 功能:表选择 / 关键字 WHERE 查询 / 分页 / 导出 CSV。
|
||||
/// 仍延续 ScopedContext 隔离 + 双击展开 的范式,每个工位独立一份查询状态。
|
||||
/// </summary>
|
||||
public class RecordViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
|
||||
{
|
||||
#region 私有字段
|
||||
// 数据库相对路径:运行目录\SQL\ADP.db(数据库未就绪时容错处理,不抛异常)
|
||||
private static readonly string DbFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SQL");
|
||||
private static readonly string DbPath = Path.Combine(DbFolder, "ADP.db");
|
||||
private static readonly string ConnStr = $"Data Source={DbPath};Version=3;";
|
||||
|
||||
private readonly IScopedProvider _scope;
|
||||
#endregion
|
||||
|
||||
#region 隔离演示属性(保留 ScopedContext 验证用)
|
||||
public bool KeepAlive => true;
|
||||
public ScopedContext _scopedContext { get; }
|
||||
// 公开一份 GlobalInfo 供双击展开逻辑使用(基类的 _globalInfo 是 private)
|
||||
public GlobalInfo GlobalInfoRef { get; }
|
||||
#endregion
|
||||
|
||||
#region 顶部工位标题
|
||||
private string _testStatus = string.Empty;
|
||||
public string TestStatus
|
||||
{
|
||||
get => _testStatus;
|
||||
set => SetProperty(ref _testStatus, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 数据库 / 表 / 查询条件
|
||||
private ObservableCollection<string> _tableNames = new();
|
||||
public ObservableCollection<string> TableNames
|
||||
{
|
||||
get => _tableNames;
|
||||
set => SetProperty(ref _tableNames, value);
|
||||
}
|
||||
|
||||
private string? _selectedTable;
|
||||
public string? SelectedTable
|
||||
{
|
||||
get => _selectedTable;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedTable, value))
|
||||
{
|
||||
PageIndex = 1;
|
||||
Query();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 用户填写的 WHERE 子句(不带 WHERE 关键字),例如:Status='OK' AND Id>10
|
||||
private string _whereClause = string.Empty;
|
||||
public string WhereClause
|
||||
{
|
||||
get => _whereClause;
|
||||
set => SetProperty(ref _whereClause, value);
|
||||
}
|
||||
|
||||
private DataTable _resultTable = new();
|
||||
public DataTable ResultTable
|
||||
{
|
||||
get => _resultTable;
|
||||
set => SetProperty(ref _resultTable, value);
|
||||
}
|
||||
|
||||
private string _statusMessage = "未连接";
|
||||
public string StatusMessage
|
||||
{
|
||||
get => _statusMessage;
|
||||
set => SetProperty(ref _statusMessage, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 分页
|
||||
private int _pageIndex = 1;
|
||||
public int PageIndex
|
||||
{
|
||||
get => _pageIndex;
|
||||
set => SetProperty(ref _pageIndex, value);
|
||||
}
|
||||
|
||||
private int _pageSize = 50;
|
||||
public int PageSize
|
||||
{
|
||||
get => _pageSize;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _pageSize, value <= 0 ? 50 : value))
|
||||
{
|
||||
RaisePropertyChanged(nameof(TotalPages));
|
||||
PageIndex = 1;
|
||||
Query();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long _totalCount;
|
||||
public long TotalCount
|
||||
{
|
||||
get => _totalCount;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _totalCount, value);
|
||||
RaisePropertyChanged(nameof(TotalPages));
|
||||
}
|
||||
}
|
||||
|
||||
public int TotalPages
|
||||
{
|
||||
get
|
||||
{
|
||||
if (PageSize <= 0) return 1;
|
||||
var pages = (int)((TotalCount + PageSize - 1) / PageSize);
|
||||
return pages <= 0 ? 1 : pages;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 命令
|
||||
public ICommand LoadedCommand { get; }
|
||||
public ICommand RefreshTablesCommand { get; }
|
||||
public ICommand QueryCommand { get; }
|
||||
public ICommand FirstPageCommand { get; }
|
||||
public ICommand PrevPageCommand { get; }
|
||||
public ICommand NextPageCommand { get; }
|
||||
public ICommand LastPageCommand { get; }
|
||||
public ICommand ExportCsvCommand { get; }
|
||||
// 双击展开/折叠:与 MonitorView/AutomatedTestingView 共用
|
||||
public ICommand RefreshCommand { get; }
|
||||
#endregion
|
||||
|
||||
public RecordViewModel(IContainerExtension container) : base(container)
|
||||
{
|
||||
_scope = container.CreateScope();
|
||||
GlobalInfoRef = container.Resolve<GlobalInfo>();
|
||||
_scopedContext = _scope.Resolve<ScopedContext>();
|
||||
|
||||
LoadedCommand = new DelegateCommand(LoadTables);
|
||||
RefreshTablesCommand = new DelegateCommand(LoadTables);
|
||||
QueryCommand = new DelegateCommand(() => { PageIndex = 1; Query(); });
|
||||
FirstPageCommand = new DelegateCommand(() => { if (PageIndex > 1) { PageIndex = 1; Query(); } });
|
||||
PrevPageCommand = new DelegateCommand(() => { if (PageIndex > 1) { PageIndex--; Query(); } });
|
||||
NextPageCommand = new DelegateCommand(() => { if (PageIndex < TotalPages) { PageIndex++; Query(); } });
|
||||
LastPageCommand = new DelegateCommand(() => { if (PageIndex < TotalPages) { PageIndex = TotalPages; Query(); } });
|
||||
ExportCsvCommand = new DelegateCommand(ExportCsv);
|
||||
RefreshCommand = new DelegateCommand(OnExpand);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_scope?.Dispose();
|
||||
}
|
||||
|
||||
#region 数据库操作
|
||||
private SqlSugarClient CreateClient()
|
||||
{
|
||||
return new SqlSugarClient(new ConnectionConfig
|
||||
{
|
||||
DbType = SqlSugar.DbType.Sqlite,
|
||||
ConnectionString = ConnStr,
|
||||
IsAutoCloseConnection = true,
|
||||
InitKeyType = InitKeyType.Attribute
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取数据库中的所有用户表。数据库不存在时不报错,仅在状态栏提示。
|
||||
/// </summary>
|
||||
private void LoadTables()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(DbFolder)) Directory.CreateDirectory(DbFolder);
|
||||
if (!File.Exists(DbPath))
|
||||
{
|
||||
TableNames = new ObservableCollection<string>();
|
||||
SelectedTable = null;
|
||||
ResultTable = new DataTable();
|
||||
TotalCount = 0;
|
||||
StatusMessage = $"数据库尚未创建:{DbPath}";
|
||||
return;
|
||||
}
|
||||
|
||||
using var db = CreateClient();
|
||||
var tables = db.DbMaintenance.GetTableInfoList(false)
|
||||
.Select(t => t.Name)
|
||||
.OrderBy(n => n)
|
||||
.ToList();
|
||||
|
||||
TableNames = new ObservableCollection<string>(tables);
|
||||
if (tables.Count == 0)
|
||||
{
|
||||
SelectedTable = null;
|
||||
ResultTable = new DataTable();
|
||||
TotalCount = 0;
|
||||
StatusMessage = "数据库已连接,但暂无数据表";
|
||||
}
|
||||
else
|
||||
{
|
||||
StatusMessage = $"已连接:{DbPath}({tables.Count} 张表)";
|
||||
if (string.IsNullOrEmpty(SelectedTable) || !tables.Contains(SelectedTable))
|
||||
{
|
||||
SelectedTable = tables[0]; // setter 会触发 Query
|
||||
}
|
||||
else
|
||||
{
|
||||
Query();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.ErrorWithNotify($"加载数据表失败:{ex.Message}");
|
||||
StatusMessage = $"加载失败:{ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行分页查询。WHERE 为空则查询全部。
|
||||
/// </summary>
|
||||
private void Query()
|
||||
{
|
||||
if (string.IsNullOrEmpty(SelectedTable) || !File.Exists(DbPath))
|
||||
{
|
||||
ResultTable = new DataTable();
|
||||
TotalCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var db = CreateClient();
|
||||
string table = $"[{SelectedTable}]";
|
||||
string where = string.IsNullOrWhiteSpace(WhereClause) ? "1=1" : WhereClause;
|
||||
|
||||
// COUNT 总数
|
||||
var countSql = $"SELECT COUNT(*) FROM {table} WHERE {where}";
|
||||
TotalCount = db.Ado.GetLong(countSql);
|
||||
|
||||
// 修正越界
|
||||
if (PageIndex > TotalPages) PageIndex = TotalPages;
|
||||
if (PageIndex < 1) PageIndex = 1;
|
||||
|
||||
int offset = (PageIndex - 1) * PageSize;
|
||||
var dataSql = $"SELECT * FROM {table} WHERE {where} LIMIT {PageSize} OFFSET {offset}";
|
||||
ResultTable = db.Ado.GetDataTable(dataSql);
|
||||
|
||||
StatusMessage = $"表 [{SelectedTable}] 共 {TotalCount} 行 第 {PageIndex}/{TotalPages} 页";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.ErrorWithNotify($"查询失败:{ex.Message}");
|
||||
StatusMessage = $"查询失败:{ex.Message}";
|
||||
ResultTable = new DataTable();
|
||||
TotalCount = 0;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 导出 CSV
|
||||
/// <summary>
|
||||
/// 按当前 WHERE 条件导出全部结果到 CSV(不分页)。
|
||||
/// </summary>
|
||||
private void ExportCsv()
|
||||
{
|
||||
if (string.IsNullOrEmpty(SelectedTable) || !File.Exists(DbPath))
|
||||
{
|
||||
StatusMessage = "无可导出数据";
|
||||
return;
|
||||
}
|
||||
|
||||
var dlg = new Microsoft.Win32.SaveFileDialog
|
||||
{
|
||||
Filter = "CSV 文件 (*.csv)|*.csv|所有文件|*.*",
|
||||
FileName = $"{SelectedTable}_{DateTime.Now:yyyyMMdd_HHmmss}.csv",
|
||||
Title = $"导出 [{SelectedTable}] 为 CSV"
|
||||
};
|
||||
if (dlg.ShowDialog() != true) return;
|
||||
|
||||
try
|
||||
{
|
||||
using var db = CreateClient();
|
||||
string table = $"[{SelectedTable}]";
|
||||
string where = string.IsNullOrWhiteSpace(WhereClause) ? "1=1" : WhereClause;
|
||||
var sql = $"SELECT * FROM {table} WHERE {where}";
|
||||
var dt = db.Ado.GetDataTable(sql);
|
||||
|
||||
WriteCsv(dlg.FileName, dt);
|
||||
|
||||
StatusMessage = $"导出完成:{dlg.FileName}({dt.Rows.Count} 行)";
|
||||
LoggerHelper.InfoWithNotify($"工位 [{TestStatus}] 导出 [{SelectedTable}] 至 {dlg.FileName},共 {dt.Rows.Count} 行");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.ErrorWithNotify($"导出失败:{ex.Message}");
|
||||
StatusMessage = $"导出失败:{ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteCsv(string path, DataTable dt)
|
||||
{
|
||||
// 写 UTF-8 BOM 让 Excel 直接识别中文
|
||||
using var sw = new StreamWriter(path, false, new UTF8Encoding(true));
|
||||
// 表头
|
||||
sw.WriteLine(string.Join(",", dt.Columns.Cast<DataColumn>().Select(c => Escape(c.ColumnName))));
|
||||
// 行
|
||||
foreach (DataRow row in dt.Rows)
|
||||
{
|
||||
sw.WriteLine(string.Join(",", row.ItemArray.Select(v => Escape(v?.ToString() ?? string.Empty))));
|
||||
}
|
||||
}
|
||||
|
||||
private static string Escape(string field)
|
||||
{
|
||||
if (field.Contains('"') || field.Contains(',') || field.Contains('\r') || field.Contains('\n'))
|
||||
{
|
||||
return "\"" + field.Replace("\"", "\"\"") + "\"";
|
||||
}
|
||||
return field;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 双击展开
|
||||
private void OnExpand()
|
||||
{
|
||||
if (string.IsNullOrEmpty(TestStatus)) return;
|
||||
_eventAggregator.GetEvent<ExpandViewEvent>().Publish(TestStatus);
|
||||
GlobalInfoRef.CurrentScope = TestStatus;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 导航
|
||||
public override void OnNavigatedTo(NavigationContext navigationContext)
|
||||
{
|
||||
base.OnNavigatedTo(navigationContext);
|
||||
if (navigationContext.Parameters.ContainsKey("Name"))
|
||||
{
|
||||
TestStatus = navigationContext.Parameters.GetValue<string>("Name");
|
||||
}
|
||||
// 进入界面时自动加载表
|
||||
LoadTables();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
236
MonitorModule/Views/MonitorViewView.xaml
Normal file
236
MonitorModule/Views/MonitorViewView.xaml
Normal file
@@ -0,0 +1,236 @@
|
||||
<UserControl x:Class="MonitorModule.Views.MonitorView"
|
||||
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:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:b="clr-namespace:UIShare.Behaviors;assembly=UIShare"
|
||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||
xmlns:oxy="http://oxyplot.org/wpf"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="700"
|
||||
d:DesignWidth="1200">
|
||||
<UserControl.Resources>
|
||||
<converters:LessThanConverter x:Key="LessThanConverter"/>
|
||||
</UserControl.Resources>
|
||||
|
||||
<!-- 外层 Border:装 MouseDoubleClickBehavior 实现九宫格双击展开 -->
|
||||
<Border Background="#F5F7FA">
|
||||
<i:Interaction.Behaviors>
|
||||
<b:MouseDoubleClickBehavior
|
||||
Command="{Binding DataContext.RefreshCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
|
||||
</i:Interaction.Behaviors>
|
||||
|
||||
<!-- 给最外层 Grid 命名,方便子控件 DataTrigger 监听 ActualWidth -->
|
||||
<Grid x:Name="RootGrid" Margin="8">
|
||||
<Grid.RowDefinitions>
|
||||
<!-- Row0 标题:窄宽时折叠为 0 高度 -->
|
||||
<RowDefinition>
|
||||
<RowDefinition.Style>
|
||||
<Style TargetType="RowDefinition">
|
||||
<Setter Property="Height" Value="Auto"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
|
||||
<Setter Property="Height" Value="0"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</RowDefinition.Style>
|
||||
</RowDefinition>
|
||||
<!-- Row1 工具栏:窄宽时折叠为 0 -->
|
||||
<RowDefinition>
|
||||
<RowDefinition.Style>
|
||||
<Style TargetType="RowDefinition">
|
||||
<Setter Property="Height" Value="Auto"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
|
||||
<Setter Property="Height" Value="0"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</RowDefinition.Style>
|
||||
</RowDefinition>
|
||||
<!-- Row2 主体(图表区) -->
|
||||
<RowDefinition Height="*"/>
|
||||
<!-- Row3 状态栏:窄宽时折叠为 0 -->
|
||||
<RowDefinition>
|
||||
<RowDefinition.Style>
|
||||
<Style TargetType="RowDefinition">
|
||||
<Setter Property="Height" Value="Auto"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
|
||||
<Setter Property="Height" Value="0"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</RowDefinition.Style>
|
||||
</RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- ====== Row0:标题 ====== -->
|
||||
<TextBlock Grid.Row="0"
|
||||
Text="{Binding TestStatus, StringFormat=监控界面 - {0}}"
|
||||
FontSize="20" FontWeight="Bold"
|
||||
Margin="4,0,0,8">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
|
||||
<!-- ====== Row1:工具栏 ====== -->
|
||||
<Border Grid.Row="1"
|
||||
Background="White"
|
||||
BorderBrush="#DDD" BorderThickness="1"
|
||||
CornerRadius="4" Padding="8" Margin="0,0,0,6">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Content="+ 添加信号"
|
||||
Command="{Binding AddSignalCommand}"
|
||||
Padding="12,4"/>
|
||||
<Button Content="− 删除信号"
|
||||
Command="{Binding DeleteSignalCommand}"
|
||||
Padding="12,4" Margin="6,0,0,0"/>
|
||||
<Button Content="↺ 复原视图"
|
||||
Command="{Binding ResetViewCommand}"
|
||||
Padding="12,4" Margin="20,0,0,0"
|
||||
ToolTip="按数据范围重置坐标轴缩放/平移"/>
|
||||
<Button Content="⟳ 刷新"
|
||||
Command="{Binding RefreshDataCommand}"
|
||||
Padding="12,4" Margin="6,0,0,0"
|
||||
ToolTip="重新绘制图表"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- ====== Row2:主体(左信号列表 + 右图表)====== -->
|
||||
<Grid Grid.Row="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<!-- Col0 信号列表:窄宽时折叠为 0 宽 -->
|
||||
<ColumnDefinition>
|
||||
<ColumnDefinition.Style>
|
||||
<Style TargetType="ColumnDefinition">
|
||||
<Setter Property="Width" Value="220"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
|
||||
<Setter Property="Width" Value="0"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ColumnDefinition.Style>
|
||||
</ColumnDefinition>
|
||||
<!-- Col1 拖拽条:窄宽时折叠为 0 宽 -->
|
||||
<ColumnDefinition>
|
||||
<ColumnDefinition.Style>
|
||||
<Style TargetType="ColumnDefinition">
|
||||
<Setter Property="Width" Value="6"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
|
||||
<Setter Property="Width" Value="0"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ColumnDefinition.Style>
|
||||
</ColumnDefinition>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 左:信号列表(窄宽时整体隐藏) -->
|
||||
<Border Grid.Column="0"
|
||||
Background="White"
|
||||
BorderBrush="#DDD" BorderThickness="1"
|
||||
CornerRadius="4">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
<DockPanel>
|
||||
<Border DockPanel.Dock="Top"
|
||||
Background="#ECEFF4"
|
||||
Padding="8,4">
|
||||
<TextBlock Text="信号列表" FontWeight="Bold"/>
|
||||
</Border>
|
||||
<ListBox ItemsSource="{Binding Signals}"
|
||||
SelectedItem="{Binding SelectedSignal}"
|
||||
BorderThickness="0">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal" Margin="2">
|
||||
<Border Width="12" Height="12"
|
||||
CornerRadius="2"
|
||||
Background="#888"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock Text="{Binding Name}"
|
||||
Margin="6,0,0,0"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
<GridSplitter Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="Transparent">
|
||||
<GridSplitter.Style>
|
||||
<Style TargetType="GridSplitter">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</GridSplitter.Style>
|
||||
</GridSplitter>
|
||||
|
||||
<!-- 右:OxyPlot 图表(核心,始终显示) -->
|
||||
<Border Grid.Column="2"
|
||||
Background="White"
|
||||
BorderBrush="#DDD" BorderThickness="1"
|
||||
CornerRadius="4">
|
||||
<oxy:PlotView Model="{Binding Plot}"
|
||||
Background="Transparent"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- ====== Row3:状态栏 ====== -->
|
||||
<Border Grid.Row="3"
|
||||
Background="#ECEFF4"
|
||||
Padding="8,4" Margin="0,6,0,0"
|
||||
CornerRadius="2">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
<TextBlock Text="{Binding StatusMessage}"
|
||||
Foreground="#444"
|
||||
FontSize="12"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
28
MonitorModule/Views/MonitorViewView.xaml.cs
Normal file
28
MonitorModule/Views/MonitorViewView.xaml.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace MonitorModule.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// MonitorView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class MonitorView : UserControl
|
||||
{
|
||||
public MonitorView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
170
MonitorModule/Views/RecordView.xaml
Normal file
170
MonitorModule/Views/RecordView.xaml
Normal file
@@ -0,0 +1,170 @@
|
||||
<UserControl x:Class="MonitorModule.Views.RecordView"
|
||||
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:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:b="clr-namespace:UIShare.Behaviors;assembly=UIShare"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="700"
|
||||
d:DesignWidth="1200">
|
||||
<!-- 外层 Border:1) 装 MouseDoubleClickBehavior 实现九宫格双击展开;
|
||||
2) 给整个记录界面一个浅色背景 -->
|
||||
<Border Background="#F5F7FA">
|
||||
<i:Interaction.Behaviors>
|
||||
<b:MouseDoubleClickBehavior
|
||||
Command="{Binding DataContext.RefreshCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
|
||||
</i:Interaction.Behaviors>
|
||||
|
||||
<Grid Margin="8">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<!-- 标题 -->
|
||||
<RowDefinition Height="Auto"/>
|
||||
<!-- 工具栏 -->
|
||||
<RowDefinition Height="*"/>
|
||||
<!-- 数据表 -->
|
||||
<RowDefinition Height="Auto"/>
|
||||
<!-- 分页 -->
|
||||
<RowDefinition Height="Auto"/>
|
||||
<!-- 状态栏 -->
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 顶部:工位名称(区分九宫格) -->
|
||||
<TextBlock Grid.Row="0"
|
||||
Text="{Binding TestStatus, StringFormat=记录界面 - {0}}"
|
||||
FontSize="20" FontWeight="Bold"
|
||||
Margin="4,0,0,8"/>
|
||||
|
||||
<!-- 工具栏:表选择 / WHERE 条件 / 查询 / 刷新表 / 导出 -->
|
||||
<Border Grid.Row="1"
|
||||
Background="White"
|
||||
BorderBrush="#DDD" BorderThickness="1"
|
||||
CornerRadius="4" Padding="8" Margin="0,0,0,6">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="180"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Column="0"
|
||||
Text="表:"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,4,0"/>
|
||||
<ComboBox Grid.Column="1"
|
||||
materialDesign:HintAssist.Hint="数据表"
|
||||
ItemsSource="{Binding TableNames}"
|
||||
SelectedItem="{Binding SelectedTable}"
|
||||
VerticalAlignment="Center"/>
|
||||
|
||||
<TextBlock Grid.Column="2"
|
||||
Text="WHERE:"
|
||||
VerticalAlignment="Center"
|
||||
Margin="12,0,4,0"/>
|
||||
<TextBox Grid.Column="3"
|
||||
Text="{Binding WhereClause, UpdateSourceTrigger=PropertyChanged}"
|
||||
VerticalAlignment="Center"
|
||||
materialDesign:HintAssist.Hint="条件筛选"
|
||||
ToolTip="例如:Status='OK' AND Id>10。留空表示查询全部">
|
||||
<TextBox.InputBindings>
|
||||
<KeyBinding Key="Enter" Command="{Binding QueryCommand}"/>
|
||||
</TextBox.InputBindings>
|
||||
</TextBox>
|
||||
|
||||
<Button Grid.Column="4"
|
||||
Content="查询"
|
||||
Command="{Binding QueryCommand}"
|
||||
Padding="12,4" Margin="8,0,0,0"/>
|
||||
<Button Grid.Column="5"
|
||||
Content="刷新表"
|
||||
Command="{Binding RefreshTablesCommand}"
|
||||
Padding="12,4" Margin="6,0,0,0"/>
|
||||
<Button Grid.Column="6"
|
||||
Content="导出 CSV"
|
||||
Command="{Binding ExportCsvCommand}"
|
||||
Padding="12,4" Margin="6,0,0,0"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- DataGrid:自动生成列,绑 DataTable.DefaultView 直显示 -->
|
||||
<DataGrid Grid.Row="2"
|
||||
ItemsSource="{Binding ResultTable}"
|
||||
AutoGenerateColumns="True"
|
||||
IsReadOnly="True"
|
||||
CanUserAddRows="False"
|
||||
CanUserDeleteRows="False"
|
||||
GridLinesVisibility="All"
|
||||
HeadersVisibility="All"
|
||||
AlternatingRowBackground="#F9FAFB"
|
||||
RowBackground="White"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto"/>
|
||||
|
||||
<!-- 分页控件 -->
|
||||
<Border Grid.Row="3"
|
||||
Background="White"
|
||||
BorderBrush="#DDD" BorderThickness="1"
|
||||
CornerRadius="4" Padding="8" Margin="0,6,0,0">
|
||||
<Grid>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock Text="每页:" VerticalAlignment="Center"/>
|
||||
<ComboBox Text="{Binding PageSize, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
|
||||
IsEditable="True"
|
||||
materialDesign:HintAssist.Hint="页码"
|
||||
Width="80" VerticalAlignment="Center" Margin="4,0,0,0">
|
||||
<ComboBoxItem Content="20"/>
|
||||
<ComboBoxItem Content="50"/>
|
||||
<ComboBoxItem Content="100"/>
|
||||
<ComboBoxItem Content="200"/>
|
||||
<ComboBoxItem Content="500"/>
|
||||
</ComboBox>
|
||||
<TextBlock Text="{Binding TotalCount, StringFormat=共 {0} 行}"
|
||||
VerticalAlignment="Center" Margin="16,0,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Right">
|
||||
<Button Content="« 首页"
|
||||
Command="{Binding FirstPageCommand}"
|
||||
Padding="10,3"/>
|
||||
<Button Content="‹ 上一页"
|
||||
Command="{Binding PrevPageCommand}"
|
||||
Padding="10,3" Margin="6,0,0,0"/>
|
||||
<TextBlock VerticalAlignment="Center" Margin="12,0">
|
||||
<Run Text="第 "/>
|
||||
<Run Text="{Binding PageIndex}" FontWeight="Bold"/>
|
||||
<Run Text=" / "/>
|
||||
<Run Text="{Binding TotalPages, Mode=OneWay}" FontWeight="Bold"/>
|
||||
<Run Text=" 页"/>
|
||||
</TextBlock>
|
||||
<Button Content="下一页 ›"
|
||||
Command="{Binding NextPageCommand}"
|
||||
Padding="10,3"/>
|
||||
<Button Content="末页 »"
|
||||
Command="{Binding LastPageCommand}"
|
||||
Padding="10,3" Margin="6,0,0,0"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- 底部状态栏 -->
|
||||
<Border Grid.Row="4"
|
||||
Background="#ECEFF4"
|
||||
Padding="8,4" Margin="0,6,0,0"
|
||||
CornerRadius="2">
|
||||
<TextBlock Text="{Binding StatusMessage}"
|
||||
Foreground="#444"
|
||||
FontSize="12"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
28
MonitorModule/Views/RecordView.xaml.cs
Normal file
28
MonitorModule/Views/RecordView.xaml.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace MonitorModule.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// RecordView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class RecordView : UserControl
|
||||
{
|
||||
public RecordView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
132
ORM/DatabaseConfig.cs
Normal file
132
ORM/DatabaseConfig.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
using SqlSugar;
|
||||
using System;
|
||||
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace ORM
|
||||
{
|
||||
public class DatabaseConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据库连接类型
|
||||
/// </summary>
|
||||
public static DbType DbConnectionType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// SQLite 数据库文件路径
|
||||
/// </summary>
|
||||
public static string DbConnectionString { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据租户Id
|
||||
/// </summary>
|
||||
public static int TenantId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 雪花算法DatacenterId,值范围:0至31
|
||||
/// </summary>
|
||||
public static int SnowFlakeDatacenterId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 雪花算法WorkID,值范围:0至31
|
||||
/// </summary>
|
||||
public static int SnowFlakeWorkId { get; private set; }
|
||||
|
||||
#region 不同数据库初始化方法
|
||||
public static void InitSqlite()
|
||||
{
|
||||
DbConnectionType = DbType.Sqlite;
|
||||
// 获取程序运行目录
|
||||
string baseDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||
// 确保 SQLDB 文件夹存在
|
||||
string folder = Path.Combine(baseDir, "SQLDB");
|
||||
if (!Directory.Exists(folder))
|
||||
Directory.CreateDirectory(folder);
|
||||
|
||||
// 拼接数据库文件路径
|
||||
string DBPath = Path.Combine(folder, "SQL.db");
|
||||
DbConnectionString = $"Data Source={DBPath};Version=3;";
|
||||
}
|
||||
public static void InitMySql(string Server, int Port, string Database, string Uid,string Pwd)
|
||||
{
|
||||
DbConnectionType = DbType.MySql;
|
||||
DbConnectionString =
|
||||
$"Server={Server};Port={Port};Database={Database};Uid={Uid};Pwd={Pwd};";
|
||||
}
|
||||
public static void InitSqlServer(string server,int port,string database,string user,string password)
|
||||
{
|
||||
DbConnectionType = DbType.SqlServer;
|
||||
|
||||
DbConnectionString =
|
||||
$"Data Source={server},{port};Initial Catalog={database};user={user};Password={password};";
|
||||
}
|
||||
public static void InitSqlServerLocalDb()
|
||||
{
|
||||
DbConnectionType = DbType.SqlServer;
|
||||
string baseDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||
// 确保 SQLDB 文件夹存在
|
||||
string folder = Path.Combine(baseDir, "SQLDB");
|
||||
if (!Directory.Exists(folder))
|
||||
Directory.CreateDirectory(folder);
|
||||
|
||||
// 拼接数据库文件路径
|
||||
string DBPath = Path.Combine(folder, "SQL.db");
|
||||
DbConnectionString =$@"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog={DBPath};Integrated Security=True;";
|
||||
}
|
||||
#endregion
|
||||
/// <summary>
|
||||
/// 设置数据库连接字符串(可手动覆盖)
|
||||
/// </summary>
|
||||
public static void SetDbConnection(string strConnection)
|
||||
{
|
||||
if (string.IsNullOrEmpty(strConnection))
|
||||
{
|
||||
throw new Exception("数据库连接字符串为空");
|
||||
}
|
||||
DbConnectionString = strConnection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置数据租户Id
|
||||
/// </summary>
|
||||
public static void SetTenant(int nTenantId)
|
||||
{
|
||||
if (nTenantId <= 10000)
|
||||
{
|
||||
throw new Exception("数据租户Id值需大于10000");
|
||||
}
|
||||
TenantId = nTenantId;
|
||||
SetSnowFlake((nTenantId - 10000) % 1024);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定数字转换为雪花算法DatacenterId和WorkID
|
||||
/// </summary>
|
||||
private static void SetSnowFlake(int nSnowFlakeId)
|
||||
{
|
||||
if (nSnowFlakeId > 1023 || nSnowFlakeId < 0)
|
||||
{
|
||||
throw new Exception("雪花算法机器码值范围0至1023");
|
||||
}
|
||||
SnowFlakeDatacenterId = nSnowFlakeId >> 5;
|
||||
SnowFlakeWorkId = nSnowFlakeId & 31;
|
||||
}
|
||||
/// <summary>
|
||||
/// 检测数据库连接
|
||||
/// </summary>
|
||||
public static void CreateDatabaseAndCheckConnection(bool createDatabase = false, bool checkConnection = false)
|
||||
{
|
||||
//数据库不存在则创建数据库
|
||||
if (createDatabase)
|
||||
{
|
||||
SqlSugarContext.DbContext.DbMaintenance.CreateDatabase();
|
||||
}
|
||||
// 检查数据库连接
|
||||
if (checkConnection && !SqlSugarContext.DbContext.Ado.IsValidConnection())
|
||||
{
|
||||
throw new Exception("连接数据库失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
ORM/ORM.csproj
Normal file
14
ORM/ORM.csproj
Normal file
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Logger\Logger.csproj" />
|
||||
<ProjectReference Include="..\Model\Model.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
123
ORM/SqlSugarContext.cs
Normal file
123
ORM/SqlSugarContext.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using Logger;
|
||||
using SqlSugar;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ORM
|
||||
{
|
||||
public class SqlSugarContext
|
||||
{
|
||||
/// <summary>
|
||||
/// SqlSugarScope单例模式
|
||||
/// </summary>
|
||||
public static readonly SqlSugarScope DbContext = new SqlSugarScope(
|
||||
new ConnectionConfig()
|
||||
{
|
||||
DbType = DatabaseConfig.DbConnectionType,
|
||||
ConnectionString = DatabaseConfig.DbConnectionString, // 连接符字串
|
||||
//ConfigId = "Db1",//管理多个数据库
|
||||
IsAutoCloseConnection = true, // 自动关闭连接
|
||||
InitKeyType = InitKeyType.Attribute // 通过实体类上的特性初始化
|
||||
},
|
||||
db =>
|
||||
{
|
||||
// 执行超时时间,单位秒
|
||||
db.Ado.CommandTimeOut = 30;
|
||||
|
||||
// 每次SQL执行前事件
|
||||
db.Aop.OnLogExecuting = (sql, pars) =>
|
||||
{
|
||||
#if DEBUG
|
||||
// 确保 SQL 只通过 sqlLogger 记录
|
||||
LoggerHelper.sqlLogger.Info("Executing SQL: {0} with parameters: {1}", sql, pars); // 使用 NLog 记录 SQL
|
||||
#endif
|
||||
};
|
||||
|
||||
// SQL执行完
|
||||
db.Aop.OnLogExecuted = (sql, pars) =>
|
||||
{
|
||||
// 执行时间超过1秒记录慢日志
|
||||
if (db.Ado.SqlExecutionTime.TotalSeconds > 1)
|
||||
{
|
||||
LoggerHelper.sqlLogger.Warn("SQL Slow Execution. Time: {0}, File: {1}, Line: {2}, Method: {3}, SQL: {4}",
|
||||
db.Ado.SqlExecutionTime.ToString(), // SQL 执行时间
|
||||
db.Ado.SqlStackTrace.FirstFileName, // 代码 CS 文件名
|
||||
db.Ado.SqlStackTrace.FirstLine, // 代码行数
|
||||
db.Ado.SqlStackTrace.FirstMethodName, // 代码方法名
|
||||
sql); // 使用 NLog 记录慢 SQL 日志
|
||||
}
|
||||
};
|
||||
|
||||
// SQL 报错
|
||||
db.Aop.OnError = (exp) =>
|
||||
{
|
||||
// 确保 SQL 错误日志仅通过 sqlLogger 记录
|
||||
LoggerHelper.sqlLogger.Error(exp, "SQL Error: {0}", exp.Sql); // 使用 NLog 记录 SQL 错误日志
|
||||
};
|
||||
|
||||
// 数据过滤器:例如在新增数据时生成雪花 Id
|
||||
db.Aop.DataExecuting = (oldValue, entityInfo) =>
|
||||
{
|
||||
// 新增操作
|
||||
if (entityInfo.OperationType == DataFilterType.InsertByObject)
|
||||
{
|
||||
// 主键(long)赋值雪花 Id
|
||||
if (entityInfo.EntityColumnInfo.IsPrimarykey && entityInfo.EntityColumnInfo.PropertyInfo.PropertyType == typeof(long))
|
||||
{
|
||||
var id = ((dynamic)entityInfo.EntityValue).Id;
|
||||
if (id == null || id == 0)
|
||||
{
|
||||
SnowFlakeSingle.WorkId = DatabaseConfig.SnowFlakeWorkId;
|
||||
SnowFlakeSingle.DatacenterId = DatabaseConfig.SnowFlakeDatacenterId;
|
||||
entityInfo.SetValue(SnowFlakeSingle.Instance.NextId());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// 初始化数据库,创建所有后缀为 Entity 的表
|
||||
/// </summary>
|
||||
public static void InitDatabase()
|
||||
{
|
||||
// 加载 Model.dll 文件
|
||||
string modelDllPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Model.dll");
|
||||
|
||||
if (!File.Exists(modelDllPath))
|
||||
{
|
||||
throw new FileNotFoundException($"未找到 Model.dll 文件: {modelDllPath}");
|
||||
}
|
||||
|
||||
// 加载指定的程序集(Model.dll)
|
||||
var modelAssembly = Assembly.LoadFrom(modelDllPath);
|
||||
|
||||
// 获取所有的 Model层下的Entity 类(后缀为 Entity)
|
||||
var entityTypes = modelAssembly.GetTypes()
|
||||
.Where(t => t.Name.EndsWith("Entity") && t.IsClass && !t.IsInterface && !t.IsAbstract)
|
||||
.ToList();
|
||||
|
||||
|
||||
// 使用 SqlSugar 自动创建表
|
||||
using (var db = DbContext)
|
||||
{
|
||||
foreach (var entityType in entityTypes)
|
||||
{
|
||||
// 判断该类型是否为类且符合条件
|
||||
if (entityType.IsClass)
|
||||
{
|
||||
// 自动创建表
|
||||
db.CodeFirst.InitTables(entityType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
396
ORM/SqlSugarRepository.cs
Normal file
396
ORM/SqlSugarRepository.cs
Normal file
@@ -0,0 +1,396 @@
|
||||
using SqlSugar;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ORM
|
||||
{
|
||||
public partial class SqlSugarRepository<TEntity> : SimpleClient<TEntity> where TEntity : class, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// 实体集合
|
||||
/// </summary>
|
||||
public ISugarQueryable<TEntity> Entities => Context.Queryable<TEntity>();
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public SqlSugarRepository(ISqlSugarClient context = null) : base(context)
|
||||
{
|
||||
// 绑定数据库操作对象
|
||||
Context = SqlSugarContext.DbContext;
|
||||
// 备忘:GetConnectionScopeWithAttr会导致不能触发Aop.OnLogExecuting、Aop.OnLogExecuted等事件
|
||||
// 详见:https://www.donet5.com/Home/Doc?typeId=2405(之2.1、方法说明)
|
||||
// 详见:https://www.donet5.com/Home/Doc?typeId=2246(之2.2、根据特性获取)和(之4、多租户设置AOP)
|
||||
// base.Context = SqlSugarContext.DbContext.GetConnectionScopeWithAttr<TEntity>();
|
||||
// 根据特性获取,适合一个实体和库是一对一的情况
|
||||
}
|
||||
|
||||
|
||||
#region 查询
|
||||
/// <summary>
|
||||
/// 检查是否存在
|
||||
/// </summary>
|
||||
/// <param name="whereExpression"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsExists(Expression<Func<TEntity, bool>> whereExpression)
|
||||
{
|
||||
return Entities.Any(whereExpression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否存在
|
||||
/// </summary>
|
||||
/// <param name="whereExpression"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> IsExistsAsync(Expression<Func<TEntity, bool>> whereExpression)
|
||||
{
|
||||
return await Entities.AnyAsync(whereExpression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过主键获取实体
|
||||
/// </summary>
|
||||
/// <param name="Id"></param>
|
||||
/// <returns></returns>
|
||||
public TEntity Single(dynamic Id)
|
||||
{
|
||||
return Entities.InSingle(Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个实体
|
||||
/// </summary>
|
||||
/// <param name="whereExpression"></param>
|
||||
/// <returns></returns>
|
||||
public TEntity Single(Expression<Func<TEntity, bool>> whereExpression)
|
||||
{
|
||||
return Entities.Single(whereExpression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个实体
|
||||
/// </summary>
|
||||
/// <param name="whereExpression"></param>
|
||||
/// <returns></returns>
|
||||
public Task<TEntity> SingleAsync(Expression<Func<TEntity, bool>> whereExpression)
|
||||
{
|
||||
return Entities.SingleAsync(whereExpression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个实体
|
||||
/// </summary>
|
||||
/// <param name="whereExpression"></param>
|
||||
/// <returns></returns>
|
||||
public TEntity FirstOrDefault(Expression<Func<TEntity, bool>> whereExpression)
|
||||
{
|
||||
return Entities.First(whereExpression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个实体
|
||||
/// </summary>
|
||||
/// <param name="whereExpression"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> whereExpression)
|
||||
{
|
||||
return await Entities.FirstAsync(whereExpression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取列表
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<TEntity> ToList()
|
||||
{
|
||||
return Entities.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取列表
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Task<List<TEntity>> ToListAsync()
|
||||
{
|
||||
return Entities.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取列表
|
||||
/// </summary>
|
||||
/// <param name="whereExpression"></param>
|
||||
/// <returns></returns>
|
||||
public List<TEntity> ToList(Expression<Func<TEntity, bool>> whereExpression)
|
||||
{
|
||||
return Entities.Where(whereExpression).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取列表
|
||||
/// </summary>
|
||||
/// <param name="whereExpression"></param>
|
||||
/// <returns></returns>
|
||||
public Task<List<TEntity>> ToListAsync(Expression<Func<TEntity, bool>> whereExpression)
|
||||
{
|
||||
return Entities.Where(whereExpression).ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取列表
|
||||
/// </summary>
|
||||
/// <param name="whereExpression"></param>
|
||||
/// <param name="orderByExpression"></param>
|
||||
/// <param name="orderByType"></param>
|
||||
/// <returns></returns>
|
||||
public List<TEntity> ToList(Expression<Func<TEntity, bool>> whereExpression, Expression<Func<TEntity, object>> orderByExpression = null, OrderByType orderByType = OrderByType.Asc)
|
||||
{
|
||||
return Entities.OrderByIF(orderByExpression != null, orderByExpression, orderByType).Where(whereExpression).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取列表
|
||||
/// </summary>
|
||||
/// <param name="whereExpression"></param>
|
||||
/// <param name="orderByExpression"></param>
|
||||
/// <param name="orderByType"></param>
|
||||
/// <returns></returns>
|
||||
public Task<List<TEntity>> ToListAsync(Expression<Func<TEntity, bool>> whereExpression, Expression<Func<TEntity, object>> orderByExpression = null, OrderByType orderByType = OrderByType.Asc)
|
||||
{
|
||||
return Entities.OrderByIF(orderByExpression != null, orderByExpression, orderByType).Where(whereExpression).ToListAsync();
|
||||
}
|
||||
#endregion 查询
|
||||
|
||||
|
||||
#region 新增
|
||||
/// <summary>
|
||||
/// 新增多条记录
|
||||
/// </summary>
|
||||
/// <param name="entities"></param>
|
||||
/// <returns></returns>
|
||||
public int Insert(TEntity[] entities)
|
||||
{
|
||||
return Context.Insertable(entities).ExecuteCommand();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增多条记录
|
||||
/// </summary>
|
||||
/// <param name="entities"></param>
|
||||
/// <returns></returns>
|
||||
public Task<int> InsertAsync(TEntity[] entities)
|
||||
{
|
||||
return Context.Insertable(entities).ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增多条记录
|
||||
/// </summary>
|
||||
/// <param name="entities"></param>
|
||||
/// <returns></returns>
|
||||
public int Insert(IEnumerable<TEntity> entities)
|
||||
{
|
||||
return Context.Insertable(entities.ToArray()).ExecuteCommand();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增多条记录
|
||||
/// </summary>
|
||||
/// <param name="entities"></param>
|
||||
/// <returns></returns>
|
||||
public Task<int> InsertAsync(IEnumerable<TEntity> entities)
|
||||
{
|
||||
if (entities != null && entities.Any())
|
||||
{
|
||||
return Context.Insertable(entities.ToArray()).ExecuteCommandAsync();
|
||||
}
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
#endregion 新增
|
||||
|
||||
|
||||
#region 更新
|
||||
/// <summary>
|
||||
/// 更新单条记录指定列
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <param name="updateColumn"></param>
|
||||
/// <returns></returns>
|
||||
public int Update(TEntity entity, object updateColumn)
|
||||
{
|
||||
return Context.Updateable(entity).UpdateColumns(MergeUpdateColumns(updateColumn)).ExecuteCommand();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新单条记录指定列
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <param name="updateColumn"></param>
|
||||
/// <returns></returns>
|
||||
public Task<int> UpdateAsync(TEntity entity, object updateColumn)
|
||||
{
|
||||
return Context.Updateable(entity).UpdateColumns(MergeUpdateColumns(updateColumn)).ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新单条记录指定列
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <param name="updateColumn"></param>
|
||||
/// <param name="whereExpression"></param>
|
||||
/// <returns></returns>
|
||||
public int Update(TEntity entity, object updateColumn, Expression<Func<TEntity, bool>> whereExpression)
|
||||
{
|
||||
return Context.Updateable(entity).UpdateColumns(MergeUpdateColumns(updateColumn)).Where(whereExpression).ExecuteCommand();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新单条记录指定列
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <param name="updateColumn"></param>
|
||||
/// <param name="whereExpression"></param>
|
||||
/// <returns></returns>
|
||||
public Task<int> UpdateAsync(TEntity entity, object updateColumn, Expression<Func<TEntity, bool>> whereExpression)
|
||||
{
|
||||
return Context.Updateable(entity).UpdateColumns(MergeUpdateColumns(updateColumn)).Where(whereExpression).ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新多条记录
|
||||
/// </summary>
|
||||
/// <param name="entities"></param>
|
||||
/// <returns></returns>
|
||||
public int Update(TEntity[] entities)
|
||||
{
|
||||
return Context.Updateable(entities).ExecuteCommand();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新多条记录
|
||||
/// </summary>
|
||||
/// <param name="entities"></param>
|
||||
/// <returns></returns>
|
||||
public Task<int> UpdateAsync(TEntity[] entities)
|
||||
{
|
||||
return Context.Updateable(entities).ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新多条记录指定列
|
||||
/// </summary>
|
||||
/// <param name="entities"></param>
|
||||
/// <param name="updateColumn"></param>
|
||||
/// <returns></returns>
|
||||
public int Update(TEntity[] entities, object updateColumn)
|
||||
{
|
||||
return Context.Updateable(entities).UpdateColumns(MergeUpdateColumns(updateColumn)).ExecuteCommand();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新多条记录指定列
|
||||
/// </summary>
|
||||
/// <param name="entities"></param>
|
||||
/// <param name="updateColumn"></param>
|
||||
/// <returns></returns>
|
||||
public Task<int> UpdateAsync(TEntity[] entities, object updateColumn)
|
||||
{
|
||||
return Context.Updateable(entities).UpdateColumns(MergeUpdateColumns(updateColumn)).ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 合并指定更新列和附加更新列
|
||||
/// </summary>
|
||||
/// <param name="updateColumn"></param>
|
||||
/// <returns></returns>
|
||||
private string[] MergeUpdateColumns(object updateColumn)
|
||||
{
|
||||
List<string> columnList = new List<string>();
|
||||
if (updateColumn.GetType() == typeof(string))
|
||||
{
|
||||
columnList.Add((string)updateColumn);
|
||||
}
|
||||
else if (updateColumn.GetType() == typeof(string[]))
|
||||
{
|
||||
columnList.AddRange((string[])updateColumn);
|
||||
}
|
||||
return columnList.ToArray();
|
||||
}
|
||||
#endregion 更新
|
||||
|
||||
|
||||
#region 删除
|
||||
/// <summary>
|
||||
/// 删除一条记录
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
public int Delete(object key)
|
||||
{
|
||||
return Context.Deleteable<TEntity>().In(key).ExecuteCommand();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除一条记录
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
public Task<int> DeleteAsync(object key)
|
||||
{
|
||||
return Context.Deleteable<TEntity>().In(key).ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除多条记录
|
||||
/// </summary>
|
||||
/// <param name="keys"></param>
|
||||
/// <returns></returns>
|
||||
public int Delete(object[] keys)
|
||||
{
|
||||
return Context.Deleteable<TEntity>().In(keys).ExecuteCommand();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除多条记录
|
||||
/// </summary>
|
||||
/// <param name="keys"></param>
|
||||
/// <returns></returns>
|
||||
public Task<int> DeleteAsync(object[] keys)
|
||||
{
|
||||
return Context.Deleteable<TEntity>().In(keys).ExecuteCommandAsync();
|
||||
}
|
||||
#endregion 删除
|
||||
|
||||
|
||||
#region 事务
|
||||
/// <summary>
|
||||
/// 开启事务
|
||||
/// </summary>
|
||||
public void BeginTran()
|
||||
{
|
||||
Context.Ado.BeginTran();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 提交事务
|
||||
/// </summary>
|
||||
public void CommitTran()
|
||||
{
|
||||
Context.Ado.CommitTran();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回滚事务
|
||||
/// </summary>
|
||||
public void RollbackTran()
|
||||
{
|
||||
Context.Ado.RollbackTran();
|
||||
}
|
||||
#endregion 事务
|
||||
|
||||
}
|
||||
}
|
||||
132
Service/Implement/BaseService.cs
Normal file
132
Service/Implement/BaseService.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
using Model;
|
||||
using Model.Entity;
|
||||
using ORM;
|
||||
using Service.Interface;
|
||||
using SqlSugar;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Service.Implement
|
||||
{
|
||||
public class BaseService<TEntity> : IBaseService<TEntity> where TEntity : BaseEntity, new()
|
||||
{
|
||||
protected readonly SqlSugarRepository<TEntity> _repository;
|
||||
|
||||
public BaseService(SqlSugarRepository<TEntity> repository)
|
||||
{
|
||||
_repository = repository;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询全部
|
||||
/// </summary>
|
||||
public virtual async Task<Result<List<TEntity>>> GetAllAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var list = await _repository.Entities.ToListAsync();
|
||||
return Result<List<TEntity>>.Success(list);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<List<TEntity>>.Error("查询所有数据失败", ex);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 根据日期查询全部
|
||||
/// </summary>
|
||||
public virtual async Task<Result<List<TEntity>>> GetAllAsyncByDate(DateTime? startDate, DateTime? endDate)
|
||||
{
|
||||
try
|
||||
{
|
||||
var list = await _repository.Entities.Where(x => x.CreateTime >= startDate && x.CreateTime <= endDate).ToListAsync();
|
||||
return Result<List<TEntity>>.Success(list);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<List<TEntity>>.Error("查询所有数据失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询
|
||||
/// </summary>
|
||||
public virtual async Task<Result<List<TEntity>>> GetPagedAsync(int pageIndex, int pageSize, RefAsync<int> total)
|
||||
{
|
||||
try
|
||||
{
|
||||
var list = await _repository.Entities
|
||||
.OrderBy(d => d.Id)
|
||||
.ToPageListAsync(pageIndex, pageSize, total);
|
||||
total.Value = (int)Math.Ceiling((double)total.Value / pageSize);
|
||||
return Result<List<TEntity>>.Success(list);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<List<TEntity>>.Error("分页查询数据失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询,并可以根据日期范围进行过滤
|
||||
/// </summary>
|
||||
public virtual async Task<Result<List<TEntity>>> GetPagedAsync(int pageIndex, int pageSize, RefAsync<int> total, DateTime? startDate, DateTime? endDate)
|
||||
{
|
||||
try
|
||||
{
|
||||
var list = await _repository.Entities
|
||||
.Where(x=>x.CreateTime>=startDate&&x.CreateTime <= endDate)
|
||||
.OrderBy(d => d.Id)
|
||||
.ToPageListAsync(pageIndex, pageSize, total);
|
||||
total.Value = (int)Math.Ceiling((double)total.Value / pageSize);
|
||||
return Result<List<TEntity>>.Success(list);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<List<TEntity>>.Error("分页查询数据失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插入单条数据
|
||||
/// </summary>
|
||||
public virtual async Task<Result<bool>> InsertAsync(TEntity entity)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _repository.Context.Insertable(entity).ExecuteCommandAsync();
|
||||
return Result<bool>.Success(result > 0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<bool>.Error("插入数据失败", ex);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 删除单条数据(根据 Id)
|
||||
/// </summary>
|
||||
/// <param name="id">主键 Id</param>
|
||||
public virtual async Task<Result<bool>> DeleteAsync(long id)
|
||||
{
|
||||
if (id <= 0)
|
||||
return Result<bool>.Error("主键 Id 无效,无法删除");
|
||||
|
||||
try
|
||||
{
|
||||
// 删除实体
|
||||
var result = await _repository.Context
|
||||
.Deleteable<TEntity>()
|
||||
.Where(x => x.Id == id)
|
||||
.ExecuteCommandAsync();
|
||||
|
||||
return Result<bool>.Success(result > 0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<bool>.Error("删除数据失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
55
Service/Interface/IBaseService.cs
Normal file
55
Service/Interface/IBaseService.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using Model;
|
||||
using SqlSugar;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Service.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// 泛型基础服务接口(返回 Result 封装)
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型</typeparam>
|
||||
public interface IBaseService<TEntity> where TEntity : class, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// 查询全部
|
||||
/// </summary>
|
||||
/// <returns>返回包含数据的 Result</returns>
|
||||
Task<Result<List<TEntity>>> GetAllAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 根据日期查询全部
|
||||
/// </summary>
|
||||
/// <param name="startDate">开始日期</param>
|
||||
/// <param name="endDate">结束日期</param>
|
||||
/// <returns>返回包含日期范内的数据的 Result</returns>
|
||||
Task<Result<List<TEntity>>> GetAllAsyncByDate(DateTime? startDate, DateTime? endDate);
|
||||
|
||||
/// <summary>
|
||||
/// 插入单条数据
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns>返回操作是否成功的 Result</returns>
|
||||
Task<Result<bool>> InsertAsync(TEntity entity);
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询
|
||||
/// </summary>
|
||||
/// <param name="pageIndex">页码(从1开始)</param>
|
||||
/// <param name="pageSize">每页数量</param>
|
||||
/// <param name="total">总条数(输出参数)</param>
|
||||
/// <returns>返回包含分页数据的 Result</returns>
|
||||
Task<Result<List<TEntity>>> GetPagedAsync(int pageIndex, int pageSize, RefAsync<int> total);
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询,并根据日期范围进行过滤
|
||||
/// </summary>
|
||||
/// <param name="pageIndex">页码(从1开始)</param>
|
||||
/// <param name="pageSize">每页数量</param>
|
||||
/// <param name="total">总条数(输出参数)</param>
|
||||
/// <param name="startDate">开始日期</param>
|
||||
/// <param name="endDate">结束日期</param>
|
||||
/// <returns>返回包含分页数据的 Result</returns>
|
||||
Task<Result<List<TEntity>>> GetPagedAsync(int pageIndex, int pageSize, RefAsync<int> total, DateTime? startDate, DateTime? endDate);
|
||||
}
|
||||
}
|
||||
13
Service/Service.csproj
Normal file
13
Service/Service.csproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ORM\ORM.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
14
SettingModule/SettingModule.csproj
Normal file
14
SettingModule/SettingModule.csproj
Normal file
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\UIShare\UIShare.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
25
TestingModule/TestingModule.cs
Normal file
25
TestingModule/TestingModule.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using TestingModule.ViewModels;
|
||||
using System.Reflection;
|
||||
using TestingModule.Views;
|
||||
using TestingModule.Views.Dialogs;
|
||||
|
||||
namespace TestingModule
|
||||
{
|
||||
public class TestingModule : IModule
|
||||
{
|
||||
public void OnInitialized(IContainerProvider containerProvider)
|
||||
{
|
||||
IRegionManager regionManager = containerProvider.Resolve<IRegionManager>();
|
||||
}
|
||||
|
||||
public void RegisterTypes(IContainerRegistry containerRegistry)
|
||||
{
|
||||
containerRegistry.RegisterForNavigation<CommandTree>("CommandTree");
|
||||
containerRegistry.RegisterForNavigation<LogArea>("LogArea");
|
||||
containerRegistry.RegisterForNavigation<SingleStepEdit>("SingleStepEdit");
|
||||
containerRegistry.RegisterForNavigation<StepsManager>("StepsManager");
|
||||
containerRegistry.RegisterForNavigation<ParametersManager>("ParametersManager");
|
||||
containerRegistry.RegisterDialog<ParameterSetting>("ParameterSetting");
|
||||
}
|
||||
}
|
||||
}
|
||||
18
TestingModule/TestingModule.csproj
Normal file
18
TestingModule/TestingModule.csproj
Normal file
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Service\Service.csproj" />
|
||||
<ProjectReference Include="..\UIShare\UIShare.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="gong-wpf-dragdrop" Version="4.0.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
590
TestingModule/ViewModels/CommandTreeViewModel.cs
Normal file
590
TestingModule/ViewModels/CommandTreeViewModel.cs
Normal file
@@ -0,0 +1,590 @@
|
||||
using UIShare.UIViewModel;
|
||||
using UIShare.GlobalVariable;
|
||||
using Common.Attributes;
|
||||
using Logger;
|
||||
using Microsoft.IdentityModel.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Xml;
|
||||
using Model;
|
||||
using static UIShare.UIViewModel.ParameterModel;
|
||||
using UIShare.ViewModelBase;
|
||||
using NLog;
|
||||
|
||||
namespace TestingModule.ViewModels
|
||||
{
|
||||
public class CommandTreeViewModel:NavigateViewModelBase
|
||||
{
|
||||
#region 属性
|
||||
private string _SearchText;
|
||||
|
||||
public string SearchText
|
||||
{
|
||||
get => _SearchText;
|
||||
set => SetProperty(ref _SearchText, value);
|
||||
}
|
||||
private ObservableCollection<InstructionNode> _instructionTree = new();
|
||||
public ObservableCollection<InstructionNode> InstructionTree
|
||||
{
|
||||
get => _instructionTree;
|
||||
set => SetProperty(ref _instructionTree, value);
|
||||
}
|
||||
|
||||
public ProgramModel Program
|
||||
{
|
||||
get => _ScopedContext.Program;
|
||||
set
|
||||
{
|
||||
if (_ScopedContext.Program != value)
|
||||
{
|
||||
_ScopedContext.Program = value;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<Assembly> Assemblies
|
||||
{
|
||||
get => _ScopedContext.Assemblies;
|
||||
set
|
||||
{
|
||||
if (_ScopedContext.Assemblies != value)
|
||||
{
|
||||
_ScopedContext.Assemblies = value;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ObservableCollection<SubProgramItem> _subPrograms = new();
|
||||
public ObservableCollection<SubProgramItem> SubPrograms
|
||||
{
|
||||
get => _subPrograms;
|
||||
set => SetProperty(ref _subPrograms, value);
|
||||
}
|
||||
|
||||
private Dictionary<object, InstructionNode> _treeNodeMap = new();
|
||||
public Dictionary<object, InstructionNode> TreeNodeMap
|
||||
{
|
||||
get => _treeNodeMap;
|
||||
set => SetProperty(ref _treeNodeMap, value);
|
||||
}
|
||||
|
||||
private Dictionary<string, XmlDocument> _xmlDocumentCache = new();
|
||||
|
||||
public Dictionary<string, XmlDocument> XmlDocumentCache
|
||||
{
|
||||
get => _xmlDocumentCache;
|
||||
set => SetProperty(ref _xmlDocumentCache, value);
|
||||
}
|
||||
#endregion
|
||||
public ICommand LoadedCommand { get; set; }
|
||||
public ICommand SearchEnterCommand { get; set; }
|
||||
public ICommand TreeDoubleClickCommand { get; set; }
|
||||
public ICommand ReloadCommand { get; set; }
|
||||
private ScopedContext _ScopedContext { get; set; }
|
||||
private readonly SystemConfig _systemConfig;
|
||||
private readonly GlobalInfo _globalInfo;
|
||||
|
||||
public CommandTreeViewModel(IContainerProvider containerProvider, ScopedContext scopedContext, SystemConfig systemConfig, GlobalInfo globalInfo) : base(containerProvider)
|
||||
{
|
||||
_ScopedContext = scopedContext;
|
||||
_systemConfig = systemConfig;
|
||||
_globalInfo = globalInfo;
|
||||
LoadedCommand = new DelegateCommand(Loaded);
|
||||
SearchEnterCommand = new DelegateCommand(Search);
|
||||
TreeDoubleClickCommand = new DelegateCommand<object>(TreeDoubleClick);
|
||||
ReloadCommand = new DelegateCommand(Reload);
|
||||
}
|
||||
|
||||
|
||||
#region 委托命令
|
||||
private void Reload()
|
||||
{
|
||||
LoadAllAssemblies();
|
||||
LoadSubPrograms();
|
||||
LoadInstructionsToTreeView();
|
||||
}
|
||||
private void TreeDoubleClick(object obj)
|
||||
{
|
||||
if (!_globalInfo.IsAdmin) return;
|
||||
if(obj is InstructionNode Node)
|
||||
{
|
||||
if(Node.Children.Count == 0)
|
||||
{
|
||||
int index = _ScopedContext.SelectedStep?.Index >= 0 ? _ScopedContext.SelectedStep.Index : -1;
|
||||
if (Node.Tag is MethodInfo method)
|
||||
{
|
||||
AddMethodToProgram(method, index);
|
||||
}
|
||||
else if(Node.Tag is string tag)
|
||||
{
|
||||
switch (tag)
|
||||
{
|
||||
case "循环开始":
|
||||
AddLoopStartStep(index);
|
||||
break;
|
||||
case "循环结束":
|
||||
AddLoopEndStep(index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if(Node.Tag is SubProgramItem subProgram)
|
||||
{
|
||||
AddSubProgramToProgram(subProgram, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private void Search()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void Loaded()
|
||||
{
|
||||
LoadAllAssemblies();
|
||||
LoadSubPrograms();
|
||||
LoadInstructionsToTreeView();
|
||||
}
|
||||
#endregion
|
||||
#region 加载指令
|
||||
/// <summary>
|
||||
/// 加载指定目录下的所有DLL
|
||||
/// </summary>
|
||||
private void LoadAllAssemblies()
|
||||
{
|
||||
Assemblies.Clear();
|
||||
|
||||
foreach (var dllPath in Directory.GetFiles(_systemConfig.DLLFilePath, "*.dll"))
|
||||
{
|
||||
try
|
||||
{
|
||||
var assembly = Assembly.LoadFile(dllPath);
|
||||
Assemblies.Add(assembly);
|
||||
|
||||
// 加载对应的XML注释文件 (项目没有用到)
|
||||
//string xmlPath = Path.ChangeExtension(dllPath, ".xml");
|
||||
//if (File.Exists(xmlPath))
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// XmlDocument xmlDoc = new XmlDocument();
|
||||
// xmlDoc.Load(xmlPath);
|
||||
// _xmlDocumentCache[assembly.FullName!] = xmlDoc;
|
||||
// }
|
||||
// catch (Exception xmlEx)
|
||||
// {
|
||||
// LoggerHelper.WarnWithNotify($"加载XML注释失败: {Path.GetFileName(xmlPath)} - {xmlEx.Message}");
|
||||
// }
|
||||
//}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.WarnWithNotify($"无法加载程序集 {Path.GetFileName(dllPath)}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
// 子程序加载方法
|
||||
private void LoadSubPrograms()
|
||||
{
|
||||
SubPrograms.Clear();
|
||||
|
||||
if (!Directory.Exists(_systemConfig.SubProgramFilePath))
|
||||
{
|
||||
Directory.CreateDirectory(_systemConfig.SubProgramFilePath);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var filePath in Directory.GetFiles(_systemConfig.SubProgramFilePath, "*.adp"))
|
||||
{
|
||||
try
|
||||
{
|
||||
SubPrograms.Add(new SubProgramItem
|
||||
{
|
||||
Name = Path.GetFileNameWithoutExtension(filePath),
|
||||
FilePath = filePath
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.WarnWithNotify($"加载子程序错误: {filePath} - {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 加载指令集到TreeView
|
||||
/// </summary>
|
||||
private void LoadInstructionsToTreeView()
|
||||
{
|
||||
InstructionTree.Clear();
|
||||
var controlRootNode = new InstructionNode
|
||||
{
|
||||
Name = "系统指令",
|
||||
Tag = "ControlRoot"
|
||||
};
|
||||
InstructionTree.Add(controlRootNode);
|
||||
|
||||
// 循环开始
|
||||
controlRootNode.Children.Add(new InstructionNode
|
||||
{
|
||||
Name = "循环开始",
|
||||
Tag = "循环开始"
|
||||
});
|
||||
|
||||
// 循环结束
|
||||
controlRootNode.Children.Add(new InstructionNode
|
||||
{
|
||||
Name = "循环结束",
|
||||
Tag = "循环结束"
|
||||
});
|
||||
|
||||
// ----------------------
|
||||
// 子程序 根节点
|
||||
// ----------------------
|
||||
var subProgramRoot = new InstructionNode
|
||||
{
|
||||
Name = "子程序",
|
||||
Tag = "SubProgramRoot"
|
||||
};
|
||||
InstructionTree.Add(subProgramRoot);
|
||||
|
||||
foreach (var subProgram in SubPrograms)
|
||||
{
|
||||
subProgramRoot.Children.Add(new InstructionNode
|
||||
{
|
||||
Name = subProgram.Name,
|
||||
Tag = subProgram,
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------
|
||||
// 动态 DLL 指令
|
||||
// ----------------------
|
||||
foreach (var assembly in Assemblies)
|
||||
{
|
||||
List<Type> validTypes = new List<Type>();
|
||||
try
|
||||
{
|
||||
var types = assembly.GetTypes().Where(t =>
|
||||
t.IsPublic &&
|
||||
!t.IsNested &&
|
||||
|
||||
(t.IsClass || t.IsValueType) &&
|
||||
(!t.IsAbstract || t.IsSealed) &&
|
||||
t.GetCustomAttribute<ADPCommandAttribute>() != null);
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
if (type.GetCustomAttribute<BrowsableAttribute>()?.Browsable == false)
|
||||
continue;
|
||||
|
||||
var allMethods = new HashSet<MethodInfo>();
|
||||
GetPublicMethods(type, allMethods);
|
||||
|
||||
if (allMethods.Count > 0)
|
||||
validTypes.Add(type);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.ErrorWithNotify($"加载类型错误: {assembly.FullName} - {ex.Message}");
|
||||
}
|
||||
|
||||
if (validTypes.Count > 0)
|
||||
{
|
||||
var assemblyNode = new InstructionNode
|
||||
{
|
||||
Name = assembly.GetName().Name,
|
||||
Tag = assembly
|
||||
};
|
||||
InstructionTree.Add(assemblyNode);
|
||||
TreeNodeMap[assembly] = assemblyNode;
|
||||
|
||||
foreach (var type in validTypes)
|
||||
{
|
||||
//拦截没有在设备列表中的设备类型
|
||||
//if (SystemConfig.Instance.DeviceList.Where(x=>x.Remark==type.Name).ToList().Count==0 && type.FullName.Contains("DeviceCommand.Device"))
|
||||
//{
|
||||
// continue;
|
||||
//}
|
||||
|
||||
var typeNode = new InstructionNode
|
||||
{
|
||||
Name = type.Name,
|
||||
Tag = type,
|
||||
};
|
||||
assemblyNode.Children.Add(typeNode);
|
||||
TreeNodeMap[type] = typeNode;
|
||||
|
||||
var allMethods = new HashSet<MethodInfo>();
|
||||
GetPublicMethods(type, allMethods);
|
||||
|
||||
foreach (var method in allMethods)
|
||||
{
|
||||
if (method.IsSpecialName) continue;
|
||||
if (method.DeclaringType == typeof(object)) continue;
|
||||
|
||||
string[] ignoreMethods = { "GetType", "ToString", "Equals", "GetHashCode" };
|
||||
if (ignoreMethods.Contains(method.Name)) continue;
|
||||
|
||||
if (method.GetCustomAttribute<BrowsableAttribute>()?.Browsable == false)
|
||||
continue;
|
||||
if (type.IsAbstract && type.IsSealed && !method.IsStatic) continue;
|
||||
|
||||
var parameters = method.GetParameters();
|
||||
var paramText = string.Join(", ", parameters.Select(p => $"{p.ParameterType.Name} {p.Name}"));
|
||||
|
||||
var methodNode = new InstructionNode
|
||||
{
|
||||
Name = $"{method.Name}({paramText})",
|
||||
Tag = method,
|
||||
};
|
||||
|
||||
typeNode.Children.Add(methodNode);
|
||||
TreeNodeMap[method] = methodNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region 辅助方法
|
||||
/// <summary>
|
||||
/// 递归获取类型的所有公共方法(包括继承的方法),但跳过被重写的方法
|
||||
/// </summary>
|
||||
/// <param name="type">要处理的目标类型</param>
|
||||
/// <param name="methods">存储方法的集合</param>
|
||||
private void GetPublicMethods(Type type, HashSet<MethodInfo> methods)
|
||||
{
|
||||
// 获取当前类型的所有公共方法(包括继承的)
|
||||
var allMethods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)
|
||||
.Where(m => !m.IsSpecialName &&
|
||||
m.DeclaringType != typeof(object))
|
||||
.ToList();
|
||||
|
||||
// 按方法签名分组
|
||||
var groupedMethods = allMethods
|
||||
.GroupBy(m => new { m.Name, Parameters = string.Join(",", m.GetParameters().Select(p => p.ParameterType.FullName)) });
|
||||
|
||||
foreach (var group in groupedMethods)
|
||||
{
|
||||
// 从组中选择声明类型最接近当前类型(即继承层次最深)的方法
|
||||
MethodInfo? selectedMethod = null;
|
||||
int minDepth = int.MaxValue;
|
||||
|
||||
foreach (var method in group)
|
||||
{
|
||||
// 计算声明类型的深度
|
||||
int depth = 0;
|
||||
Type? current = type;
|
||||
Type declaringType = method.DeclaringType!;
|
||||
|
||||
while (current != null && current != declaringType)
|
||||
{
|
||||
depth++;
|
||||
current = current.BaseType;
|
||||
}
|
||||
|
||||
// 如果找到声明类型且在继承链上
|
||||
if (current == declaringType)
|
||||
{
|
||||
if (selectedMethod == null || depth < minDepth)
|
||||
{
|
||||
selectedMethod = method;
|
||||
minDepth = depth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedMethod != null)
|
||||
{
|
||||
methods.Add(selectedMethod);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
#region 指令添加
|
||||
|
||||
private void AddMethodToProgram(MethodInfo method, int insertIndex = -1)
|
||||
{
|
||||
try
|
||||
{
|
||||
var newStep = new StepModel
|
||||
{
|
||||
Name = method.Name,
|
||||
StepType = "方法",
|
||||
Method = new MethodModel
|
||||
{
|
||||
FullName = method.DeclaringType?.FullName,
|
||||
Name = method.Name
|
||||
}
|
||||
};
|
||||
|
||||
// 添加输入参数
|
||||
foreach (var param in method.GetParameters())
|
||||
{
|
||||
newStep.Method.Parameters.Add(new ParameterModel
|
||||
{
|
||||
Name = param.Name!,
|
||||
Type = param.ParameterType,
|
||||
Category = ParameterCategory.Input
|
||||
});
|
||||
}
|
||||
|
||||
// 添加输出参数(返回值)
|
||||
Type returnType = method.ReturnType;
|
||||
if (returnType == typeof(Task))
|
||||
{
|
||||
// 不添加输出参数(无返回值)
|
||||
}
|
||||
else if (returnType.IsGenericType &&
|
||||
returnType.GetGenericTypeDefinition() == typeof(Task<>))
|
||||
{
|
||||
// 提取实际返回类型(如 Task<bool> -> bool)
|
||||
Type actualType = returnType.GetGenericArguments()[0];
|
||||
newStep.Method.Parameters.Add(new ParameterModel
|
||||
{
|
||||
Name = "Result",
|
||||
Type = actualType, // 使用实际类型
|
||||
Category = ParameterCategory.Output
|
||||
});
|
||||
}
|
||||
else if (returnType != typeof(void))
|
||||
{
|
||||
// 同步方法正常添加
|
||||
newStep.Method.Parameters.Add(new ParameterModel
|
||||
{
|
||||
Name = "Result",
|
||||
Type = returnType,
|
||||
Category = ParameterCategory.Output
|
||||
});
|
||||
}
|
||||
|
||||
// 添加到程序
|
||||
if(_ScopedContext.SelectedStepList == "主程序")
|
||||
{
|
||||
if(insertIndex >= 0 && insertIndex <= Program.StepCollection.Count) Program.StepCollection.Insert(insertIndex, newStep);
|
||||
else Program.StepCollection.Add(newStep); }
|
||||
else
|
||||
{
|
||||
if (insertIndex >= 0 && insertIndex <= Program.ErrorStepCollection.Count) Program.ErrorStepCollection.Insert(insertIndex, newStep);
|
||||
else Program.ErrorStepCollection.Add(newStep);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.ErrorWithNotify($"添加方法失败: {method.Name} - {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void AddSubProgramToProgram(SubProgramItem subProgram, int insertIndex = -1)
|
||||
{
|
||||
try
|
||||
{
|
||||
var newStep = new StepModel
|
||||
{
|
||||
Name = subProgram.Name,
|
||||
StepType = "子程序"
|
||||
};
|
||||
var jsonstr = File.ReadAllText($"{subProgram.FilePath}");
|
||||
var tmp = JsonConvert.DeserializeObject<ProgramModel>(jsonstr);
|
||||
if (tmp != null)
|
||||
{
|
||||
newStep.SubProgram = tmp;
|
||||
}
|
||||
// 添加到程序
|
||||
if (_ScopedContext.SelectedStepList == "主程序")
|
||||
{
|
||||
if (insertIndex >= 0 && insertIndex <= Program.StepCollection.Count) Program.StepCollection.Insert(insertIndex, newStep);
|
||||
else Program.StepCollection.Add(newStep);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (insertIndex >= 0 && insertIndex <= Program.ErrorStepCollection.Count) Program.ErrorStepCollection.Insert(insertIndex, newStep);
|
||||
else Program.ErrorStepCollection.Add(newStep);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.ErrorWithNotify($"添加子程序失败: {subProgram.Name} - {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void AddLoopStartStep(int insertIndex = -1)
|
||||
{
|
||||
var newStep = new StepModel
|
||||
{
|
||||
Name = "循环开始",
|
||||
StepType = "循环开始",
|
||||
LoopCount = 1,
|
||||
Method = new() { Parameters = [new() { Name = "循环次数", Type = typeof(int), Category = ParameterCategory.Input }] }
|
||||
};
|
||||
|
||||
// 添加到程序
|
||||
if (_ScopedContext.SelectedStepList == "主程序")
|
||||
{
|
||||
if (insertIndex >= 0) Program.StepCollection.Insert(insertIndex, newStep);
|
||||
else Program.StepCollection.Add(newStep);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (insertIndex >= 0) Program.ErrorStepCollection.Insert(insertIndex, newStep);
|
||||
else Program.ErrorStepCollection.Add(newStep);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddLoopEndStep(int insertIndex = -1)
|
||||
{
|
||||
// 查找最近的未匹配循环开始
|
||||
StepModel? lastUnmatchedLoopStart = null;
|
||||
for (int i = Program.StepCollection.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (Program.StepCollection[i].StepType == "循环开始")
|
||||
{
|
||||
bool isMatched = Program.StepCollection.Any(s => s.StepType == "循环结束" && s.LoopStartStepId == Program.StepCollection[i].ID);
|
||||
|
||||
if (!isMatched)
|
||||
{
|
||||
lastUnmatchedLoopStart = Program.StepCollection[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var newStep = new StepModel
|
||||
{
|
||||
Name = "循环结束",
|
||||
StepType = "循环结束",
|
||||
LoopStartStepId = lastUnmatchedLoopStart?.ID
|
||||
};
|
||||
|
||||
// 添加到程序
|
||||
if (_ScopedContext.SelectedStepList == "主程序")
|
||||
{
|
||||
if (insertIndex >= 0) Program.StepCollection.Insert(insertIndex, newStep);
|
||||
else Program.StepCollection.Add(newStep);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (insertIndex >= 0) Program.ErrorStepCollection.Insert(insertIndex, newStep);
|
||||
else Program.ErrorStepCollection.Add(newStep);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
135
TestingModule/ViewModels/Dialogs/ParameterSettingViewModel.cs
Normal file
135
TestingModule/ViewModels/Dialogs/ParameterSettingViewModel.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
using UIShare.UIViewModel;
|
||||
using UIShare.PubEvent;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using UIShare.GlobalVariable;
|
||||
using static UIShare.UIViewModel.ParameterModel;
|
||||
using UIShare.ViewModelBase;
|
||||
using Prism.Ioc;
|
||||
using Prism.Navigation.Regions;
|
||||
|
||||
namespace TestingModule.ViewModels.Dialogs
|
||||
{
|
||||
public class ParameterSettingViewModel : DialogViewModelBase
|
||||
{
|
||||
#region 属性
|
||||
private string _title = "参数设置界面";
|
||||
public string Title
|
||||
{
|
||||
get => _title;
|
||||
set => SetProperty(ref _title, value);
|
||||
}
|
||||
private Array _EnumValues;
|
||||
public Array EnumValues
|
||||
{
|
||||
get => _EnumValues;
|
||||
set => SetProperty(ref _EnumValues, value);
|
||||
}
|
||||
private ObservableCollection<Type> _types =
|
||||
[
|
||||
typeof(string), typeof(bool),
|
||||
typeof(short), typeof(int), typeof(long), typeof(float), typeof(double),
|
||||
typeof(byte[]), typeof(short[]), typeof(ushort[]), typeof(int[]), typeof(long[]), typeof(float[]), typeof(double[]),
|
||||
typeof(object)
|
||||
];
|
||||
public ObservableCollection<Type> Types
|
||||
{
|
||||
get => _types;
|
||||
set => SetProperty(ref _types, value);
|
||||
}
|
||||
|
||||
private ObservableCollection<string> _categories = new ObservableCollection<string>(
|
||||
Enum.GetNames(typeof(ParameterCategory))
|
||||
);
|
||||
public ObservableCollection<string> Categories
|
||||
{
|
||||
get => _categories;
|
||||
set => SetProperty(ref _categories, value);
|
||||
}
|
||||
private string _Mode;
|
||||
public string Mode
|
||||
{
|
||||
get => _Mode;
|
||||
set => SetProperty(ref _Mode, value);
|
||||
}
|
||||
|
||||
private ProgramModel _program;
|
||||
public ProgramModel Program
|
||||
{
|
||||
get => _program;
|
||||
set => SetProperty(ref _program, value);
|
||||
}
|
||||
private ParameterModel _Parameter;
|
||||
public ParameterModel Parameter
|
||||
{
|
||||
get => _Parameter;
|
||||
set => SetProperty(ref _Parameter, value);
|
||||
}
|
||||
#endregion
|
||||
public DialogCloseListener RequestClose{get;set;}
|
||||
private ScopedContext _ScopedContext;
|
||||
public ICommand CancelCommand { get; set; }
|
||||
public ICommand SaveCommand { get; set; }
|
||||
public ParameterSettingViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
CancelCommand = new DelegateCommand(Cancel);
|
||||
SaveCommand = new DelegateCommand(Save);
|
||||
}
|
||||
|
||||
private void Save()
|
||||
{
|
||||
if (Mode == "ADD")
|
||||
{
|
||||
Program.Parameters.Add(Parameter);
|
||||
_ScopedContext.SelectedParameter = Parameter;
|
||||
}
|
||||
else
|
||||
{
|
||||
var index = Program.Parameters
|
||||
.Select((x, i) => new { x, i })
|
||||
.FirstOrDefault(p => p.x.ID == _ScopedContext.SelectedParameter.ID)?.i;
|
||||
|
||||
if (index.HasValue)
|
||||
{
|
||||
Program.Parameters[index.Value] = Parameter;
|
||||
}
|
||||
}
|
||||
|
||||
RequestClose.Invoke(ButtonResult.OK);
|
||||
}
|
||||
|
||||
private void Cancel()
|
||||
{
|
||||
RequestClose.Invoke(ButtonResult.No);
|
||||
}
|
||||
#region Prism Dialog 规范
|
||||
|
||||
public override void OnDialogClosed()
|
||||
{
|
||||
_eventAggregator.GetEvent<OverlayEvent>().Publish(false);
|
||||
}
|
||||
|
||||
public override void OnDialogOpened(IDialogParameters parameters)
|
||||
{
|
||||
if (parameters.ContainsKey("ScopedContext"))
|
||||
_ScopedContext = parameters.GetValue<ScopedContext>("ScopedContext");
|
||||
_eventAggregator.GetEvent<OverlayEvent>().Publish(true);
|
||||
Program =_ScopedContext.Program;
|
||||
Mode = parameters.GetValue<string>("Mode");
|
||||
if (Mode == "ADD")
|
||||
{
|
||||
Parameter = new();
|
||||
}
|
||||
else
|
||||
{
|
||||
Parameter = new ParameterModel(_ScopedContext.SelectedParameter);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
58
TestingModule/ViewModels/LogAreaViewModel.cs
Normal file
58
TestingModule/ViewModels/LogAreaViewModel.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using UIShare.GlobalVariable;
|
||||
using Logger;
|
||||
using Prism.Mvvm;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using UIShare.ViewModelBase;
|
||||
|
||||
namespace TestingModule.ViewModels
|
||||
{
|
||||
public class LogAreaViewModel : NavigateViewModelBase
|
||||
{
|
||||
// 日志集合
|
||||
private ObservableCollection<LogItem> _logs = new();
|
||||
|
||||
|
||||
public ObservableCollection<LogItem> Logs
|
||||
{
|
||||
get => _logs;
|
||||
set => SetProperty(ref _logs, value);
|
||||
}
|
||||
public ICommand ClearLogCommand { get; set; }
|
||||
|
||||
public LogAreaViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
ClearLogCommand = new DelegateCommand(ClearLog);
|
||||
LoggerHelper.Progress = new System.Progress<(string message, string color, int depth)>(
|
||||
log =>
|
||||
{
|
||||
var brush = (Brush)new BrushConverter().ConvertFromString(log.color);
|
||||
Logs.Add(new LogItem(log.message, brush, log.depth));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void ClearLog()
|
||||
{
|
||||
Logs.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
// 日志条目类
|
||||
public class LogItem
|
||||
{
|
||||
public string Message { get; set; }
|
||||
public Brush Color { get; set; } = Brushes.Black;
|
||||
public int Depth { get; set; }
|
||||
public LogItem(string message, Brush color, int depth = 0)
|
||||
{
|
||||
Message = new string(' ', depth * 20) + message;
|
||||
Color = color;
|
||||
Depth = depth;
|
||||
}
|
||||
}
|
||||
}
|
||||
165
TestingModule/ViewModels/ParametersManagerViewModel.cs
Normal file
165
TestingModule/ViewModels/ParametersManagerViewModel.cs
Normal file
@@ -0,0 +1,165 @@
|
||||
using UIShare.UIViewModel;
|
||||
using UIShare.GlobalVariable;
|
||||
using UIShare.PubEvent;
|
||||
using Logger;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows.Input;
|
||||
using System.Xml;
|
||||
using UIShare.ViewModelBase;
|
||||
using Prism.Events;
|
||||
|
||||
namespace TestingModule.ViewModels
|
||||
{
|
||||
public class ParametersManagerViewModel:NavigateViewModelBase
|
||||
{
|
||||
#region 属性
|
||||
//private ObservableCollection<DeviceConfigModel> _DeviceList;
|
||||
|
||||
//public ObservableCollection<DeviceConfigModel> DeviceList
|
||||
//{
|
||||
// get { return _DeviceList; }
|
||||
// set { SetProperty(ref _DeviceList,value); }
|
||||
//}
|
||||
private ObservableCollection<DeviceInfoModel> _DeviceInfoModel;
|
||||
|
||||
public ObservableCollection<DeviceInfoModel> DeviceInfoModel
|
||||
{
|
||||
get { return _DeviceInfoModel; }
|
||||
set { SetProperty(ref _DeviceInfoModel, value); }
|
||||
}
|
||||
|
||||
public ProgramModel Program
|
||||
{
|
||||
get => _ScopedContext.Program;
|
||||
set
|
||||
{
|
||||
if (_ScopedContext.Program != value)
|
||||
{
|
||||
_ScopedContext.Program = value;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
private ParameterModel _SelectedParameter;
|
||||
public ParameterModel SelectedParameter
|
||||
{
|
||||
get => _SelectedParameter;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _SelectedParameter, value))
|
||||
{
|
||||
_ScopedContext.SelectedParameter = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
private DeviceInfoModel _SelectedDevice;
|
||||
|
||||
|
||||
|
||||
public DeviceInfoModel SelectedDevice
|
||||
{
|
||||
get { return _SelectedDevice; }
|
||||
set { SetProperty(ref _SelectedDevice, value); }
|
||||
}
|
||||
#endregion
|
||||
private ScopedContext _ScopedContext { get; set; }
|
||||
private readonly SystemConfig _systemConfig;
|
||||
private readonly GlobalInfo _globalInfo;
|
||||
#region 命令
|
||||
public ICommand ParameterAddCommand { get; set; }
|
||||
public ICommand ParameterEditCommand { get; set; }
|
||||
public ICommand ParameterDeleteCommand { get; set; }
|
||||
public ICommand DeviceEditCommand { get; set; }
|
||||
public ICommand ReconnnectCommand { get; set; }
|
||||
public ICommand CloseCommand { get; set; }
|
||||
#endregion
|
||||
|
||||
public ParametersManagerViewModel(IContainerProvider containerProvider, ScopedContext scopedContext, SystemConfig systemConfig, GlobalInfo globalInfo) : base(containerProvider)
|
||||
{
|
||||
_ScopedContext = scopedContext;
|
||||
_systemConfig = systemConfig;
|
||||
_globalInfo = globalInfo;
|
||||
Program = _ScopedContext.Program;
|
||||
ParameterAddCommand = new DelegateCommand(ParameterAdd);
|
||||
ParameterEditCommand = new DelegateCommand(ParameterEdit);
|
||||
ParameterDeleteCommand = new DelegateCommand(ParameterDelete);
|
||||
DeviceEditCommand = new DelegateCommand(DeviceEdit);
|
||||
}
|
||||
|
||||
#region 委托命令
|
||||
|
||||
private void DeviceEdit()
|
||||
{
|
||||
if (!_globalInfo.IsAdmin) return;
|
||||
if (SelectedDevice==null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var type = SelectedDevice.DeviceType.Split('.').Last();
|
||||
if(type=="E36233A"|| type == "IT6724CReverse")
|
||||
{
|
||||
_dialogService.Show("Backfeed");
|
||||
}
|
||||
else
|
||||
{
|
||||
_dialogService.Show(type);
|
||||
}
|
||||
}
|
||||
|
||||
private void ParameterDelete()
|
||||
{
|
||||
if (!_globalInfo.IsAdmin) return;
|
||||
Program.Parameters.Remove(SelectedParameter);
|
||||
}
|
||||
|
||||
private void ParameterEdit()
|
||||
{
|
||||
if (!_globalInfo.IsAdmin) return;
|
||||
var param = new DialogParameters
|
||||
{
|
||||
{ "Mode",SelectedParameter==null?"ADD":"Edit" },
|
||||
{ "ScopedContext",_ScopedContext }
|
||||
};
|
||||
_dialogService.ShowDialog("ParameterSetting", param, (r) =>
|
||||
{
|
||||
if (r.Result == ButtonResult.OK)
|
||||
{
|
||||
_eventAggregator.GetEvent<ParamsChangedEvent>().Publish();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private void ParameterAdd()
|
||||
{
|
||||
if (!_globalInfo.IsAdmin) return;
|
||||
var param = new DialogParameters
|
||||
{
|
||||
{ "Mode", "ADD" },
|
||||
{ "ScopedContext",_ScopedContext }
|
||||
};
|
||||
_dialogService.ShowDialog("ParameterSetting", param, (r) =>
|
||||
{
|
||||
if (r.Result == ButtonResult.OK)
|
||||
{
|
||||
_eventAggregator.GetEvent<ParamsChangedEvent>().Publish();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
201
TestingModule/ViewModels/SingleStepEditViewModel.cs
Normal file
201
TestingModule/ViewModels/SingleStepEditViewModel.cs
Normal file
@@ -0,0 +1,201 @@
|
||||
using UIShare.UIViewModel;
|
||||
using UIShare.PubEvent;
|
||||
using Logger;
|
||||
using Microsoft.IdentityModel.Logging;
|
||||
using SqlSugar.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using UIShare.ViewModelBase;
|
||||
using Prism.Dialogs;
|
||||
using Prism.Events;
|
||||
using UIShare.GlobalVariable;
|
||||
|
||||
namespace TestingModule.ViewModels
|
||||
{
|
||||
public class SingleStepEditViewModel:NavigateViewModelBase
|
||||
{
|
||||
#region 属性
|
||||
private Guid _ID;
|
||||
public Guid ID
|
||||
{
|
||||
get => _ID;
|
||||
set => SetProperty(ref _ID, value);
|
||||
}
|
||||
private StepModel _SelectedStep;
|
||||
public StepModel SelectedStep
|
||||
{
|
||||
get => _SelectedStep;
|
||||
set => SetProperty(ref _SelectedStep, value);
|
||||
}
|
||||
public ProgramModel Program
|
||||
{
|
||||
get => _ScopedContext.Program;
|
||||
set
|
||||
{
|
||||
if (_ScopedContext.Program != value)
|
||||
{
|
||||
_ScopedContext.Program = value;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
private ScopedContext _ScopedContext;
|
||||
#region 命令
|
||||
public ICommand CancelEditCommand { get; set; }
|
||||
public ICommand SaveStepCommand { get; set; }
|
||||
#endregion
|
||||
public SingleStepEditViewModel(IContainerProvider containerProvider, ScopedContext scopedContext) : base(containerProvider)
|
||||
{
|
||||
_ScopedContext = scopedContext;
|
||||
_eventAggregator.GetEvent<EditSetpEvent>().Subscribe(EditSingleStep);
|
||||
CancelEditCommand = new DelegateCommand(CancelEdit);
|
||||
SaveStepCommand = new DelegateCommand(SaveStep);
|
||||
_eventAggregator.GetEvent<DeletedStepEvent>().Subscribe(DisposeSelectedStep);
|
||||
_eventAggregator.GetEvent<ParamsChangedEvent>().Subscribe(ParamsChanged);
|
||||
}
|
||||
|
||||
#region 委托命令
|
||||
private void ParamsChanged()
|
||||
{
|
||||
CancelEdit();
|
||||
}
|
||||
|
||||
private void DisposeSelectedStep(Guid id)
|
||||
{
|
||||
if (SelectedStep == null) return;
|
||||
if(id== SelectedStep.ID)
|
||||
SelectedStep = null;
|
||||
}
|
||||
|
||||
private void CancelEdit()
|
||||
{
|
||||
SelectedStep = null;
|
||||
}
|
||||
|
||||
private void SaveStep()
|
||||
{
|
||||
if (SelectedStep == null || (SelectedStep.Method == null && SelectedStep.SubProgram == null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var steps = _ScopedContext.SelectedStepList=="主程序"?_ScopedContext.Program.StepCollection: _ScopedContext.Program.ErrorStepCollection;
|
||||
int index = steps.ToList().FindIndex(x => x.ID == ID);
|
||||
if (index >= 0)
|
||||
{
|
||||
steps[index] = SelectedStep;
|
||||
if (steps[index].Method != null)
|
||||
{
|
||||
if (steps[index].StepType == "循环开始")
|
||||
{
|
||||
try
|
||||
{
|
||||
steps[index].LoopCount = Convert.ToInt32(SelectedStep.Method!.Parameters[0].Value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
LoggerHelper.ErrorWithNotify("循环指令参数设置错误:类型转换失败");
|
||||
}
|
||||
}
|
||||
else
|
||||
{ //设置步骤参数
|
||||
(steps[index].OKGotoStepID, steps[index].NGGotoStepID) = GetOKNGGotoStepID(SelectedStep.GotoSettingString);
|
||||
for (int i = 0; i < steps[index].Method.Parameters.Count; i++)
|
||||
{
|
||||
var editedParam = SelectedStep.Method!.Parameters[i];
|
||||
var originalParam = steps[index].Method.Parameters[i];
|
||||
if (editedParam.IsUseVar)
|
||||
{
|
||||
originalParam.VariableName = editedParam.VariableName;
|
||||
originalParam.VariableID = _ScopedContext.Program.Parameters.FirstOrDefault(x => x.Name == editedParam.VariableName)!.ID;
|
||||
}
|
||||
originalParam.Value = editedParam.Value;
|
||||
originalParam.IsUseVar = editedParam.IsUseVar;
|
||||
originalParam.LowerLimit = editedParam.LowerLimit;
|
||||
originalParam.UpperLimit = editedParam.UpperLimit;
|
||||
}
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ "Title", "提示" },
|
||||
{ "Message", "保存成功!" },
|
||||
{ "Icon", "info" },
|
||||
{ "ShowOk", true }
|
||||
};
|
||||
_dialogService.ShowDialog("MessageBox", parameters);
|
||||
}
|
||||
}
|
||||
else if (steps[index].SubProgram != null)
|
||||
{
|
||||
if (SelectedStep.SubProgram.Parameters.Where(x => x.VariableName == null && x.IsUseVar == true).FirstOrDefault() != null)
|
||||
{
|
||||
var parameters1 = new DialogParameters
|
||||
{
|
||||
{ "Title", "警告" },
|
||||
{ "Message", "选中变量不得为空!" },
|
||||
{ "Icon", "warn" },
|
||||
{ "ShowOk", true },
|
||||
};
|
||||
_dialogService.ShowDialog("MessageBox",parameters1);
|
||||
return;
|
||||
}
|
||||
(steps[index].OKGotoStepID, steps[index].NGGotoStepID) = GetOKNGGotoStepID(SelectedStep.GotoSettingString);
|
||||
for (int i = 0; i < steps[index].SubProgram.Parameters.Count; i++)
|
||||
{
|
||||
var editedParam = SelectedStep.SubProgram!.Parameters[i];
|
||||
var originalParam = steps[index].SubProgram.Parameters[i];
|
||||
if (editedParam.IsUseVar)
|
||||
{
|
||||
originalParam.VariableName = editedParam.VariableName;
|
||||
originalParam.VariableID = _ScopedContext.Program.Parameters.FirstOrDefault(x => x.Name == editedParam.VariableName)!.ID;
|
||||
}
|
||||
originalParam.Value = editedParam.Value;
|
||||
originalParam.IsUseVar = editedParam.IsUseVar;
|
||||
originalParam.LowerLimit = editedParam.LowerLimit;
|
||||
originalParam.UpperLimit = editedParam.UpperLimit;
|
||||
}
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ "Title", "提示" },
|
||||
{ "Message", "保存成功!" },
|
||||
{ "Icon", "info" },
|
||||
{ "ShowOk", true }
|
||||
};
|
||||
_dialogService.ShowDialog("MessageBox", parameters);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
private (Guid,Guid) GetOKNGGotoStepID(string GotoSettingString)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(GotoSettingString))
|
||||
return (Guid.Empty, Guid.Empty);
|
||||
|
||||
var match = Regex.Match(GotoSettingString, @"^(\d+)\s*/\s*(\d+)$");
|
||||
if (match.Success)
|
||||
{
|
||||
int ok = int.Parse(match.Groups[1].Value);
|
||||
int ng = int.Parse(match.Groups[2].Value);
|
||||
|
||||
Guid okGuid = _ScopedContext.Program.StepCollection.ElementAtOrDefault(ok-1)?.ID ?? Guid.Empty;
|
||||
Guid ngGuid = _ScopedContext.Program.StepCollection.ElementAtOrDefault(ng-1)?.ID ?? Guid.Empty;
|
||||
|
||||
return (okGuid, ngGuid);
|
||||
}
|
||||
return (Guid.Empty, Guid.Empty);
|
||||
}
|
||||
private void EditSingleStep()
|
||||
{
|
||||
if (_ScopedContext.SelectedStep == null) return;
|
||||
ID = _ScopedContext.SelectedStep.ID;
|
||||
SelectedStep = new StepModel(_ScopedContext.SelectedStep);
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
237
TestingModule/ViewModels/StepsManagerViewModel.cs
Normal file
237
TestingModule/ViewModels/StepsManagerViewModel.cs
Normal file
@@ -0,0 +1,237 @@
|
||||
using UIShare.PubEvent;
|
||||
using UIShare.UIViewModel;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Xml;
|
||||
using UIShare.GlobalVariable;
|
||||
using UIShare.ViewModelBase;
|
||||
using Prism.Events;
|
||||
|
||||
namespace TestingModule.ViewModels
|
||||
{
|
||||
public class StepsManagerViewModel:NavigateViewModelBase
|
||||
{
|
||||
#region 属性
|
||||
private string _Title;
|
||||
public string Title
|
||||
{
|
||||
get => _Title;
|
||||
set => SetProperty(ref _Title, value);
|
||||
}
|
||||
private bool _Admin;
|
||||
public bool Admin
|
||||
{
|
||||
get => _Admin;
|
||||
set => SetProperty(ref _Admin, value);
|
||||
}
|
||||
private int _SelectedTabIndex;
|
||||
public int SelectedTabIndex
|
||||
{
|
||||
get => _SelectedTabIndex;
|
||||
set => SetProperty(ref _SelectedTabIndex, value);
|
||||
}
|
||||
private string _selectedTabHeader;
|
||||
public string SelectedTabHeader
|
||||
{
|
||||
get { return _selectedTabHeader; }
|
||||
set { SetProperty(ref _selectedTabHeader, value); }
|
||||
}
|
||||
private List<StepModel> _SelectedItems;
|
||||
public List<StepModel> SelectedItems
|
||||
{
|
||||
get { return _SelectedItems; }
|
||||
set { SetProperty(ref _SelectedItems, value); }
|
||||
}
|
||||
private StepModel _SelectedStep;
|
||||
public StepModel SelectedStep
|
||||
{
|
||||
get => _SelectedStep;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _SelectedStep, value))
|
||||
{
|
||||
_ScopedContext.SelectedStep = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
public ProgramModel Program
|
||||
{
|
||||
get => _ScopedContext.Program;
|
||||
set
|
||||
{
|
||||
if (_ScopedContext.Program != value)
|
||||
{
|
||||
_ScopedContext.Program = value;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
ScopedContext _ScopedContext { get; set; }
|
||||
private readonly SystemConfig _systemConfig;
|
||||
private readonly GlobalInfo _globalInfo;
|
||||
private List<StepModel> tmpCopyList = new List<StepModel>();
|
||||
|
||||
|
||||
#endregion
|
||||
#region 命令
|
||||
public ICommand EditStepCommand { get;set; }
|
||||
public ICommand CopyStepCommand { get;set; }
|
||||
public ICommand PasteStepCommand { get;set; }
|
||||
public ICommand DeleteStepCommand { get;set; }
|
||||
public ICommand TabSelectionChangedCommand { get;set; }
|
||||
public ICommand SelectionChangedCommand { get;set; }
|
||||
#endregion
|
||||
|
||||
public StepsManagerViewModel(IContainerProvider containerProvider, ScopedContext scopedContext, SystemConfig systemConfig, GlobalInfo globalInfo) : base(containerProvider)
|
||||
{
|
||||
_ScopedContext = scopedContext;
|
||||
_systemConfig = systemConfig;
|
||||
_globalInfo = globalInfo;
|
||||
EditStepCommand = new DelegateCommand(EditStep);
|
||||
CopyStepCommand = new DelegateCommand(CopyStep);
|
||||
PasteStepCommand = new DelegateCommand(PasteStep);
|
||||
DeleteStepCommand = new DelegateCommand(DeleteStep);
|
||||
TabSelectionChangedCommand = new DelegateCommand<string>(TabSelectionChanged);
|
||||
SelectionChangedCommand = new DelegateCommand<object>(SelectionChanged);
|
||||
Program.StepCollection.CollectionChanged += StepCollection_CollectionChanged;
|
||||
Program.ErrorStepCollection.CollectionChanged += StepCollection_CollectionChanged;
|
||||
Admin = _globalInfo.IsAdmin;
|
||||
_eventAggregator.GetEvent<AlarmEvent>().Subscribe(() =>
|
||||
{
|
||||
SelectedTabIndex = 1;
|
||||
});
|
||||
}
|
||||
|
||||
private void SelectionChanged(object parameter)
|
||||
{
|
||||
var selectedList = parameter as IList;
|
||||
if (selectedList != null)
|
||||
{
|
||||
SelectedItems = selectedList.Cast<StepModel>().ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private void TabSelectionChanged(string SelectedTabHeader)
|
||||
{
|
||||
_ScopedContext.SelectedStepList = SelectedTabHeader;
|
||||
}
|
||||
|
||||
#region 委托命令
|
||||
private void EditStep()
|
||||
{
|
||||
if (_globalInfo.IsAdmin && SelectedStep != null&& _ScopedContext.SelectedStep!=null)
|
||||
{
|
||||
_eventAggregator.GetEvent<EditSetpEvent>().Publish() ;
|
||||
}
|
||||
}
|
||||
private void CopyStep()
|
||||
{
|
||||
if (_globalInfo.IsAdmin && SelectedItems.Any())
|
||||
{
|
||||
tmpCopyList.Clear();
|
||||
foreach (var item in SelectedItems)
|
||||
{
|
||||
tmpCopyList.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void PasteStep()
|
||||
{
|
||||
// 权限校验并确保有可粘贴的内容
|
||||
if (_globalInfo.IsAdmin && tmpCopyList.Any())
|
||||
{
|
||||
int insertIndex;
|
||||
|
||||
if (_ScopedContext.SelectedStepList == "主程序")
|
||||
{
|
||||
insertIndex = SelectedStep != null ? Program.StepCollection.IndexOf(SelectedStep) + 1 : Program.StepCollection.Count;
|
||||
|
||||
foreach (var item in tmpCopyList)
|
||||
{
|
||||
// 创建新副本,避免引用同一个对象,并赋予新 ID
|
||||
var newStep = new StepModel(item) { ID = Guid.NewGuid() };
|
||||
Program.StepCollection.Insert(insertIndex, newStep);
|
||||
insertIndex++; // 递增索引,保证粘贴的多项顺序一致
|
||||
}
|
||||
}
|
||||
else if (_ScopedContext.SelectedStepList == "错误程序")
|
||||
{
|
||||
insertIndex = SelectedStep != null ? Program.ErrorStepCollection.IndexOf(SelectedStep) + 1 : Program.ErrorStepCollection.Count;
|
||||
|
||||
foreach (var item in tmpCopyList)
|
||||
{
|
||||
var newStep = new StepModel(item) { ID = Guid.NewGuid() };
|
||||
Program.ErrorStepCollection.Insert(insertIndex, newStep);
|
||||
insertIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private void DeleteStep()
|
||||
{
|
||||
// 确保有选中的项
|
||||
if (_globalInfo.IsAdmin && SelectedItems != null && SelectedItems.Any())
|
||||
{
|
||||
// 创建一个副本进行循环,防止在 Remove 过程中集合变化导致的问题
|
||||
var toDelete = SelectedItems.ToList();
|
||||
|
||||
foreach (var item in toDelete)
|
||||
{
|
||||
_eventAggregator.GetEvent<DeletedStepEvent>().Publish(item.ID);
|
||||
|
||||
if (_ScopedContext.SelectedStepList == "主程序")
|
||||
{
|
||||
Program.StepCollection.Remove(item);
|
||||
}
|
||||
else if (_ScopedContext.SelectedStepList == "错误程序")
|
||||
{
|
||||
Program.ErrorStepCollection.Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 清空 ViewModel 的选中状态,避免悬挂引用
|
||||
SelectedStep = null;
|
||||
SelectedItems.Clear();
|
||||
_ScopedContext.SelectedStep = null;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 辅助方法
|
||||
private void StepCollection_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
var collection = sender as ObservableCollection<StepModel>;
|
||||
// Add/Move/Remove 都会触发,这里判断具体情形
|
||||
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add ||
|
||||
e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Move||
|
||||
e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
|
||||
{
|
||||
// 如果需要等 UI 更新完再处理,可也用 Dispatcher 延迟一小段时间
|
||||
Application.Current?.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
if(_ScopedContext.SelectedStepList=="主程序")
|
||||
{
|
||||
for (int i = 0; i < Program.StepCollection.Count; i++)
|
||||
Program.StepCollection[i].Index = i + 1;
|
||||
}
|
||||
else if (_ScopedContext.SelectedStepList == "错误程序")
|
||||
for (int i = 0; i < Program.ErrorStepCollection.Count; i++)
|
||||
Program.ErrorStepCollection[i].Index = i + 1;
|
||||
|
||||
|
||||
}), System.Windows.Threading.DispatcherPriority.Background);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
79
TestingModule/Views/CommandTree.xaml
Normal file
79
TestingModule/Views/CommandTree.xaml
Normal file
@@ -0,0 +1,79 @@
|
||||
<UserControl x:Class="TestingModule.Views.CommandTree"
|
||||
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:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:model="clr-namespace:UIShare.UIViewModel;assembly=UIShare"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800">
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="Loaded">
|
||||
<prism:InvokeCommandAction Command="{Binding LoadedCommand}" />
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="439*" />
|
||||
<ColumnDefinition Width="361*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<GroupBox Header="指令"
|
||||
Grid.ColumnSpan="2">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<StackPanel Grid.Row="0"
|
||||
Orientation="Horizontal"
|
||||
Margin="10,0,7,12"
|
||||
VerticalAlignment="Center">
|
||||
<!--<TextBox MinWidth="150"
|
||||
Height="25"
|
||||
Margin="0,0,5,0"
|
||||
VerticalContentAlignment="Center"
|
||||
Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}"
|
||||
materialDesign:HintAssist.Hint="搜索内容">
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="KeyDown">
|
||||
<prism:InvokeCommandAction Command="{Binding SearchEnterCommand}" />
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
</TextBox>-->
|
||||
</StackPanel>
|
||||
|
||||
<!-- TreeView -->
|
||||
<TreeView Grid.Row="1"
|
||||
ItemsSource="{Binding InstructionTree}">
|
||||
<TreeView.Resources>
|
||||
<HierarchicalDataTemplate DataType="{x:Type model:InstructionNode}"
|
||||
ItemsSource="{Binding Children}">
|
||||
<TextBlock Text="{Binding Name}" />
|
||||
</HierarchicalDataTemplate>
|
||||
</TreeView.Resources>
|
||||
<!-- 双击 -->
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="MouseDoubleClick">
|
||||
<prism:InvokeCommandAction Command="{Binding TreeDoubleClickCommand}"
|
||||
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource AncestorType=TreeView}}" />
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
|
||||
<!-- 右键菜单 -->
|
||||
<TreeView.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="重新加载"
|
||||
Command="{Binding ReloadCommand}" />
|
||||
</ContextMenu>
|
||||
</TreeView.ContextMenu>
|
||||
</TreeView>
|
||||
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
28
TestingModule/Views/CommandTree.xaml.cs
Normal file
28
TestingModule/Views/CommandTree.xaml.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace TestingModule.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// CommandTreeView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class CommandTree : UserControl
|
||||
{
|
||||
public CommandTree()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
136
TestingModule/Views/Dialogs/ParameterSetting.xaml
Normal file
136
TestingModule/Views/Dialogs/ParameterSetting.xaml
Normal file
@@ -0,0 +1,136 @@
|
||||
<UserControl x:Class="TestingModule.Views.Dialogs.ParameterSetting"
|
||||
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:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||
xmlns:local="clr-namespace:TestingModule.Views.Dialogs"
|
||||
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
Background="White"
|
||||
xmlns:helpers="clr-namespace:UIShare.Helpers;assembly=UIShare"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
Width="420"
|
||||
Height="284"
|
||||
mc:Ignorable="d">
|
||||
<prism:Dialog.WindowStyle>
|
||||
<Style BasedOn="{StaticResource DialogUserManageStyle}"
|
||||
TargetType="Window" />
|
||||
</prism:Dialog.WindowStyle>
|
||||
<UserControl.Resources>
|
||||
<converters:IsEnumTypeConverter x:Key="IsEnumTypeConverter" />
|
||||
<converters:ParameterValueToStringConverter x:Key="ParameterValueToStringConverter" />
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<GroupBox Padding="10,15,10,0"
|
||||
Header="{Binding Title}"
|
||||
helpers:WindowDragHelper.EnableWindowDrag="True">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<ScrollViewer>
|
||||
<StackPanel>
|
||||
<StackPanel Height="30"
|
||||
Margin="7"
|
||||
Orientation="Horizontal">
|
||||
<Label Width="60"
|
||||
VerticalAlignment="Bottom"
|
||||
Content="参数名称*" />
|
||||
<TextBox Width="120"
|
||||
Text="{Binding Parameter.Name}"
|
||||
materialDesign:HintAssist.Hint="参数名称" />
|
||||
</StackPanel>
|
||||
<StackPanel Height="30"
|
||||
Margin="7"
|
||||
Orientation="Horizontal">
|
||||
<Label Width="60"
|
||||
VerticalAlignment="Bottom"
|
||||
Content="参数类型*" />
|
||||
<ComboBox Width="120"
|
||||
VerticalAlignment="Bottom"
|
||||
ItemsSource="{Binding Types}"
|
||||
materialDesign:HintAssist.Hint="参数类型"
|
||||
SelectedItem="{Binding Parameter.Type}"
|
||||
/>
|
||||
</StackPanel>
|
||||
<StackPanel Height="30"
|
||||
Margin="7"
|
||||
Orientation="Horizontal">
|
||||
<Label Width="60"
|
||||
VerticalAlignment="Bottom"
|
||||
Content="参数类别*" />
|
||||
<ComboBox Width="120"
|
||||
VerticalAlignment="Bottom"
|
||||
ItemsSource="{Binding Categories}"
|
||||
materialDesign:HintAssist.Hint="参数类别"
|
||||
Text="{Binding Parameter.Category}" />
|
||||
</StackPanel>
|
||||
<StackPanel Height="30"
|
||||
Margin="7"
|
||||
Orientation="Horizontal">
|
||||
<Label Width="60"
|
||||
VerticalAlignment="Bottom"
|
||||
Content="参数下限" />
|
||||
<TextBox Width="120"
|
||||
VerticalAlignment="Bottom"
|
||||
Text="{Binding Parameter.LowerLimit}"
|
||||
materialDesign:HintAssist.Hint="参数下限" />
|
||||
</StackPanel>
|
||||
<StackPanel Height="30"
|
||||
Margin="7"
|
||||
Orientation="Horizontal">
|
||||
<Label Width="60"
|
||||
VerticalAlignment="Bottom"
|
||||
Content="参数上限" />
|
||||
<TextBox Width="120"
|
||||
VerticalAlignment="Bottom"
|
||||
Text="{Binding Parameter.UpperLimit}"
|
||||
materialDesign:HintAssist.Hint="参数名上限" />
|
||||
</StackPanel>
|
||||
<StackPanel Height="30"
|
||||
Margin="7"
|
||||
Orientation="Horizontal">
|
||||
<Label Width="60"
|
||||
VerticalAlignment="Bottom"
|
||||
Content="参数值" />
|
||||
|
||||
<!-- 非枚举类型时显示文本框 -->
|
||||
<TextBox MinWidth="120"
|
||||
VerticalAlignment="Bottom"
|
||||
Text="{Binding Parameter.Value, Converter={StaticResource ParameterValueToStringConverter}}"
|
||||
materialDesign:HintAssist.Hint="参数值"
|
||||
Visibility="{Binding Parameter.Type, Converter={StaticResource IsEnumTypeConverter}, ConverterParameter=Collapse}" />
|
||||
|
||||
<!-- 枚举类型时显示下拉框 -->
|
||||
<!--<ComboBox MinWidth="120"
|
||||
VerticalAlignment="Bottom"
|
||||
ItemsSource="{Binding EnumValues}"
|
||||
SelectedItem="{Binding Parameter.Value}"
|
||||
Visibility="{Binding Parameter.Type, Converter={StaticResource IsEnumTypeConverter}}" />-->
|
||||
<CheckBox Margin="10,0"
|
||||
VerticalAlignment="Bottom"
|
||||
Content="保存数据"
|
||||
IsChecked="{Binding Parameter.IsSave}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
<StackPanel Grid.Row="1"
|
||||
Margin="5,10,5,15"
|
||||
FlowDirection="RightToLeft"
|
||||
Orientation="Horizontal">
|
||||
<Button Content="取消"
|
||||
Width="70"
|
||||
Command="{Binding CancelCommand}" />
|
||||
<Button Content="保存"
|
||||
Width="70"
|
||||
Margin="20,0"
|
||||
Command="{Binding SaveCommand}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
37
TestingModule/Views/Dialogs/ParameterSetting.xaml.cs
Normal file
37
TestingModule/Views/Dialogs/ParameterSetting.xaml.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace TestingModule.Views.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// ParameterSetting.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class ParameterSetting : UserControl
|
||||
{
|
||||
public ParameterSetting()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void ComboBox_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is ComboBox comboBox && !comboBox.IsDropDownOpen)
|
||||
{
|
||||
comboBox.IsDropDownOpen = true;
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
TestingModule/Views/LogArea.xaml
Normal file
33
TestingModule/Views/LogArea.xaml
Normal file
@@ -0,0 +1,33 @@
|
||||
<UserControl x:Class="TestingModule.Views.LogArea"
|
||||
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:prism="http://prismlibrary.com/"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800">
|
||||
|
||||
<Grid>
|
||||
<GroupBox Header="系统运行日志">
|
||||
<ListView x:Name="LogListView"
|
||||
FontWeight="DemiBold"
|
||||
ItemsSource="{Binding Logs}"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
|
||||
<ListView.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="清空日志"
|
||||
Command="{Binding ClearLogCommand}" />
|
||||
</ContextMenu>
|
||||
</ListView.ContextMenu>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Foreground="{Binding Color}"
|
||||
Text="{Binding Message}"
|
||||
TextWrapping="Wrap" />
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
34
TestingModule/Views/LogArea.xaml.cs
Normal file
34
TestingModule/Views/LogArea.xaml.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using TestingModule.ViewModels;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace TestingModule.Views
|
||||
{
|
||||
public partial class LogArea : UserControl
|
||||
{
|
||||
public LogArea()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// 绑定 DataContext 后,订阅日志集合变化
|
||||
this.Loaded += (s, e) =>
|
||||
{
|
||||
if (DataContext is LogAreaViewModel vm)
|
||||
{
|
||||
vm.Logs.CollectionChanged += Logs_CollectionChanged;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void Logs_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
// 每次新增日志,滚动到最后一条
|
||||
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
|
||||
{
|
||||
if (this.LogListView.Items.Count > 0)
|
||||
{
|
||||
this.LogListView.ScrollIntoView(this.LogListView.Items[^1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user