将Feature/XP.Common和Feature/XP.Hardware分支合并至Develop/XP.forHardwareAndCommon,完善XPapp注册和相关硬件类库通用类库功能。
This commit is contained in:
@@ -0,0 +1,602 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Configuration;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using Prism.Commands;
|
||||
using Prism.Ioc;
|
||||
using Prism.Mvvm;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XP.Hardware.Plc.Abstractions;
|
||||
using XP.Hardware.Plc.Exceptions;
|
||||
using XP.Hardware.Plc.Services;
|
||||
using XP.Hardware.PLC.Configs;
|
||||
using XP.Hardware.PLC.Helpers;
|
||||
using XP.Hardware.PLC.Models;
|
||||
using XP.Hardware.PLC.Sentry.Models;
|
||||
using XP.App.Views;
|
||||
using XP.Hardware.PLC.Views;
|
||||
|
||||
namespace XP.Hardware.PLC.Sentry.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// PLC Sentry Monitor 主窗口 ViewModel | PLC Sentry Monitor main window ViewModel
|
||||
/// 负责 PLC 连接管理、信号定义加载、操作日志管理 | Manages PLC connection, signal definition loading, operation log
|
||||
/// </summary>
|
||||
public class SentryMainViewModel : BindableBase
|
||||
{
|
||||
private readonly PlcService _plcService;
|
||||
private readonly ISignalDataService _signalDataService;
|
||||
private readonly PLC.Configs.ConfigLoader _configLoader;
|
||||
private readonly XmlSignalParser _signalParser;
|
||||
private readonly ILoggerService _logger;
|
||||
private readonly IContainerProvider _containerProvider;
|
||||
|
||||
/// <summary>
|
||||
/// 信号值周期性刷新定时器 | Signal value periodic refresh timer
|
||||
/// </summary>
|
||||
private DispatcherTimer _refreshTimer;
|
||||
|
||||
// === 连接状态属性 | Connection status properties ===
|
||||
|
||||
private bool _isConnected;
|
||||
/// <summary>
|
||||
/// PLC 连接状态 | PLC connection status
|
||||
/// </summary>
|
||||
public bool IsConnected
|
||||
{
|
||||
get => _isConnected;
|
||||
set => SetProperty(ref _isConnected, value);
|
||||
}
|
||||
|
||||
private string _statusText = "未连接 | Not Connected";
|
||||
/// <summary>
|
||||
/// 状态文本 | Status text
|
||||
/// </summary>
|
||||
public string StatusText
|
||||
{
|
||||
get => _statusText;
|
||||
set => SetProperty(ref _statusText, value);
|
||||
}
|
||||
|
||||
// === 操作日志 | Operation log ===
|
||||
|
||||
/// <summary>
|
||||
/// 操作日志集合 | Operation log entries collection
|
||||
/// </summary>
|
||||
public ObservableCollection<SentryLogEntry> LogEntries { get; } = new();
|
||||
|
||||
// === 信号分组数据 | Signal group data ===
|
||||
|
||||
/// <summary>
|
||||
/// 信号分组集合,供 SignalMonitorView 标签页绑定 | Signal groups for tab binding
|
||||
/// </summary>
|
||||
public ObservableCollection<SignalGroupViewModel> SignalGroups { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 是否无信号分组数据(用于显示占位提示)| Whether no signal groups (for placeholder display)
|
||||
/// </summary>
|
||||
public bool HasNoGroups => SignalGroups.Count == 0;
|
||||
|
||||
// === 命令 | Commands ===
|
||||
|
||||
/// <summary>
|
||||
/// 连接 PLC 命令 | Connect PLC command
|
||||
/// </summary>
|
||||
public DelegateCommand ConnectCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 断开 PLC 命令 | Disconnect PLC command
|
||||
/// </summary>
|
||||
public DelegateCommand DisconnectCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 打开 PLC 连接管理窗口命令 | Open PLC connection management window command
|
||||
/// </summary>
|
||||
public DelegateCommand OpenConfigEditorCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 刷新信号定义命令 | Refresh signal definitions command
|
||||
/// </summary>
|
||||
public DelegateCommand RefreshSignalDefinitionsCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 清除日志命令 | Clear log command
|
||||
/// </summary>
|
||||
public DelegateCommand ClearLogCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 打开信号读写测试窗口命令 | Open signal data demo window command
|
||||
/// </summary>
|
||||
public DelegateCommand OpenSignalDataDemoCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
public SentryMainViewModel(
|
||||
PlcService plcService,
|
||||
ISignalDataService signalDataService,
|
||||
PLC.Configs.ConfigLoader configLoader,
|
||||
XmlSignalParser signalParser,
|
||||
ILoggerService logger,
|
||||
IContainerProvider containerProvider)
|
||||
{
|
||||
_plcService = plcService ?? throw new ArgumentNullException(nameof(plcService));
|
||||
_signalDataService = signalDataService ?? throw new ArgumentNullException(nameof(signalDataService));
|
||||
_configLoader = configLoader ?? throw new ArgumentNullException(nameof(configLoader));
|
||||
_signalParser = signalParser ?? throw new ArgumentNullException(nameof(signalParser));
|
||||
_logger = logger?.ForModule<SentryMainViewModel>() ?? throw new ArgumentNullException(nameof(logger));
|
||||
_containerProvider = containerProvider ?? throw new ArgumentNullException(nameof(containerProvider));
|
||||
|
||||
// 监听 PlcService 属性变更,同步连接状态 | Listen to PlcService property changes, sync connection status
|
||||
_plcService.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(PlcService.IsConnected))
|
||||
{
|
||||
var wasConnected = IsConnected;
|
||||
IsConnected = _plcService.IsConnected;
|
||||
ConnectCommand.RaiseCanExecuteChanged();
|
||||
DisconnectCommand.RaiseCanExecuteChanged();
|
||||
|
||||
// 刷新所有信号行的写入命令可用状态 | Refresh write command state for all signal rows
|
||||
RefreshAllSignalCommandStates();
|
||||
|
||||
// 记录连接状态变化到日志 | Log connection state change
|
||||
if (!wasConnected && IsConnected)
|
||||
{
|
||||
AddLog(SentryLogLevel.Info, "PLC 连接已建立 | PLC connection established");
|
||||
_logger.Info("PLC 连接已建立 | PLC connection established");
|
||||
}
|
||||
else if (wasConnected && !IsConnected)
|
||||
{
|
||||
AddLog(SentryLogLevel.Warning, "PLC 连接已断开 | PLC connection lost");
|
||||
_logger.Warn("PLC 连接已断开 | PLC connection lost");
|
||||
}
|
||||
|
||||
// 连接状态变化时管理刷新定时器 | Manage refresh timer on connection state change
|
||||
if (_plcService.IsConnected)
|
||||
StartRefreshTimer();
|
||||
else
|
||||
StopRefreshTimer();
|
||||
}
|
||||
else if (e.PropertyName == nameof(PlcService.StatusText))
|
||||
{
|
||||
StatusText = _plcService.StatusText;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化命令 | Initialize commands
|
||||
ConnectCommand = new DelegateCommand(async () => await ConnectAsync(), () => !IsConnected);
|
||||
DisconnectCommand = new DelegateCommand(Disconnect, () => IsConnected);
|
||||
OpenConfigEditorCommand = new DelegateCommand(OpenConfigEditor);
|
||||
RefreshSignalDefinitionsCommand = new DelegateCommand(RefreshSignalDefinitions);
|
||||
OpenSignalDataDemoCommand = new DelegateCommand(OpenSignalDataDemo);
|
||||
ClearLogCommand = new DelegateCommand(() => LogEntries.Clear());
|
||||
|
||||
// 同步初始状态 | Sync initial status
|
||||
IsConnected = _plcService.IsConnected;
|
||||
StatusText = _plcService.StatusText;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接 PLC | Connect to PLC
|
||||
/// </summary>
|
||||
private async Task ConnectAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
AddLog(SentryLogLevel.Info, "正在加载 PLC 配置... | Loading PLC configuration...");
|
||||
var config = _configLoader.LoadPlcConfig();
|
||||
AddLog(SentryLogLevel.Info, $"PLC 配置加载成功: {config.IpAddress}:{config.Port} | PLC config loaded: {config.IpAddress}:{config.Port}");
|
||||
|
||||
AddLog(SentryLogLevel.Info, "正在连接 PLC... | Connecting to PLC...");
|
||||
var result = await _plcService.InitializeAsync(config);
|
||||
|
||||
if (result)
|
||||
{
|
||||
AddLog(SentryLogLevel.Info, "PLC 连接成功 | PLC connected successfully");
|
||||
_logger.Info("PLC 连接成功 | PLC connected successfully");
|
||||
|
||||
// 连接成功后自动加载信号定义 | Auto-load signal definitions after connection
|
||||
LoadSignalDefinitions();
|
||||
}
|
||||
else
|
||||
{
|
||||
AddLog(SentryLogLevel.Error, "PLC 连接失败 | PLC connection failed");
|
||||
_logger.Warn("PLC 连接失败 | PLC connection failed");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AddLog(SentryLogLevel.Error, $"PLC 连接异常: {ex.Message} | PLC connection error: {ex.Message}");
|
||||
_logger.Error(ex, "PLC 连接异常 | PLC connection error");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断开 PLC | Disconnect from PLC
|
||||
/// </summary>
|
||||
private void Disconnect()
|
||||
{
|
||||
try
|
||||
{
|
||||
StopRefreshTimer();
|
||||
_plcService.Dispose();
|
||||
AddLog(SentryLogLevel.Info, "PLC 已断开 | PLC disconnected");
|
||||
_logger.Info("PLC 已断开 | PLC disconnected");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AddLog(SentryLogLevel.Error, $"PLC 断开异常: {ex.Message} | PLC disconnect error: {ex.Message}");
|
||||
_logger.Error(ex, "PLC 断开异常 | PLC disconnect error");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 打开 PLC 连接管理窗口 | Open PLC connection management window
|
||||
/// </summary>
|
||||
private void OpenConfigEditor()
|
||||
{
|
||||
try
|
||||
{
|
||||
var window = _containerProvider.Resolve<PlcAddrConfigEditorWindow>();
|
||||
window.Owner = Application.Current.MainWindow;
|
||||
window.ShowDialog();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AddLog(SentryLogLevel.Error, $"打开配置编辑器失败: {ex.Message} | Failed to open config editor: {ex.Message}");
|
||||
_logger.Error(ex, "打开配置编辑器失败 | Failed to open config editor");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 打开信号读写测试窗口 | Open signal data demo window
|
||||
/// </summary>
|
||||
private void OpenSignalDataDemo()
|
||||
{
|
||||
try
|
||||
{
|
||||
var window = _containerProvider.Resolve<SignalDataDemoWindow>();
|
||||
window.Owner = Application.Current.MainWindow;
|
||||
window.Show();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AddLog(SentryLogLevel.Error, $"打开信号读写测试窗口失败: {ex.Message} | Failed to open signal data demo window: {ex.Message}");
|
||||
_logger.Error(ex, "打开信号读写测试窗口失败 | Failed to open signal data demo window");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新信号定义 | Refresh signal definitions
|
||||
/// </summary>
|
||||
private void RefreshSignalDefinitions()
|
||||
{
|
||||
LoadSignalDefinitions();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载信号定义文件并生成分组标签页 | Load signal definition file and generate group tabs
|
||||
/// </summary>
|
||||
private void LoadSignalDefinitions()
|
||||
{
|
||||
try
|
||||
{
|
||||
var xmlPath = GetPlcAddrDfnPath();
|
||||
AddLog(SentryLogLevel.Info, $"正在加载信号定义: {xmlPath} | Loading signal definitions: {xmlPath}");
|
||||
|
||||
// 预检查文件是否存在,提供更友好的错误提示 | Pre-check file existence for better error message
|
||||
if (!File.Exists(xmlPath))
|
||||
{
|
||||
var fullPath = Path.GetFullPath(xmlPath);
|
||||
AddLog(SentryLogLevel.Error, $"信号定义文件不存在: {fullPath} | Signal definition file not found: {fullPath}");
|
||||
AddLog(SentryLogLevel.Warning, "请检查 App.config 中 Sentry:PlcAddrDfnPath 配置,或将 PlcAddrDfn.xml 放置到应用程序目录 | Please check Sentry:PlcAddrDfnPath in App.config, or place PlcAddrDfn.xml in the application directory");
|
||||
_logger.Error(new FileNotFoundException(fullPath), "信号定义文件不存在: {XmlPath} | Signal definition file not found: {XmlPath}", fullPath);
|
||||
return;
|
||||
}
|
||||
|
||||
// 通过 PlcService 加载信号定义(注册到内部字典)| Load signal definitions via PlcService
|
||||
_plcService.LoadSignalDefinitions(xmlPath);
|
||||
|
||||
// 通过 XmlSignalParser 加载分组结构用于 UI 展示 | Load group structure via XmlSignalParser for UI display
|
||||
var groups = _signalParser.LoadFromFile(xmlPath);
|
||||
BuildSignalGroups(groups);
|
||||
|
||||
AddLog(SentryLogLevel.Info, $"信号定义加载成功,共 {groups.Count} 个分组 | Signal definitions loaded, {groups.Count} groups");
|
||||
_logger.Info("信号定义加载成功: {XmlPath}, 分组数: {GroupCount} | Signal definitions loaded: {XmlPath}, groups: {GroupCount}", xmlPath, groups.Count);
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
var filePath = ex.FileName ?? "未知路径 | unknown path";
|
||||
AddLog(SentryLogLevel.Error, $"信号定义文件不存在: {filePath} | Signal definition file not found: {filePath}");
|
||||
AddLog(SentryLogLevel.Warning, "请检查文件路径是否正确,确保 PlcAddrDfn.xml 文件存在 | Please verify the file path and ensure PlcAddrDfn.xml exists");
|
||||
_logger.Error(ex, "信号定义文件不存在: {FilePath} | Signal definition file not found: {FilePath}", filePath);
|
||||
}
|
||||
catch (System.Xml.XmlException ex)
|
||||
{
|
||||
AddLog(SentryLogLevel.Error, $"信号定义 XML 格式错误(行 {ex.LineNumber}, 列 {ex.LinePosition}): {ex.Message} | Signal definition XML format error (line {ex.LineNumber}, col {ex.LinePosition}): {ex.Message}");
|
||||
AddLog(SentryLogLevel.Warning, "请检查 PlcAddrDfn.xml 文件的 XML 格式是否正确 | Please check the XML format of PlcAddrDfn.xml");
|
||||
_logger.Error(ex, "信号定义 XML 格式错误: 行={LineNumber}, 列={LinePosition} | Signal definition XML format error: line={LineNumber}, col={LinePosition}", ex.LineNumber, ex.LinePosition);
|
||||
}
|
||||
catch (PlcException ex)
|
||||
{
|
||||
AddLog(SentryLogLevel.Error, $"信号定义加载异常: {ex.Message} | Signal definition loading error: {ex.Message}");
|
||||
AddLog(SentryLogLevel.Warning, "请检查 PlcAddrDfn.xml 中是否存在重复信号名称或无效配置 | Please check for duplicate signal names or invalid configuration in PlcAddrDfn.xml");
|
||||
_logger.Error(ex, "信号定义加载 PLC 异常 | Signal definition loading PLC error");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AddLog(SentryLogLevel.Error, $"信号定义加载失败: {ex.Message} | Signal definition loading failed: {ex.Message}");
|
||||
_logger.Error(ex, "信号定义加载失败 | Signal definition loading failed");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据 SignalGroup 列表构建 SignalGroupViewModel 集合 | Build SignalGroupViewModel collection from SignalGroup list
|
||||
/// </summary>
|
||||
/// <param name="groups">信号分组列表 | Signal group list</param>
|
||||
private void BuildSignalGroups(System.Collections.Generic.List<SignalGroup> groups)
|
||||
{
|
||||
Application.Current?.Dispatcher?.Invoke(() =>
|
||||
{
|
||||
SignalGroups.Clear();
|
||||
|
||||
foreach (var group in groups)
|
||||
{
|
||||
var groupVm = new SignalGroupViewModel
|
||||
{
|
||||
GroupId = group.GroupId,
|
||||
DBNumber = group.DBNumber
|
||||
};
|
||||
|
||||
foreach (var signal in group.Signals)
|
||||
{
|
||||
var rowVm = new SignalRowViewModel(
|
||||
signal,
|
||||
applyAction: OnApplyWrite,
|
||||
directWriteAction: OnDirectWrite,
|
||||
canExecuteWrite: () => IsConnected);
|
||||
groupVm.Signals.Add(rowVm);
|
||||
}
|
||||
|
||||
SignalGroups.Add(groupVm);
|
||||
}
|
||||
|
||||
RaisePropertyChanged(nameof(HasNoGroups));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 队列写入回调(由 SignalRowViewModel.ApplyCommand 触发)| Queue write callback (triggered by ApplyCommand)
|
||||
/// </summary>
|
||||
/// <param name="row">信号行 ViewModel | Signal row ViewModel</param>
|
||||
private void OnApplyWrite(SignalRowViewModel row)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 类型校验(委托给 SignalRowViewModel)| Type validation (delegated to SignalRowViewModel)
|
||||
var validationError = row.ValidateWriteValue();
|
||||
if (validationError != null)
|
||||
{
|
||||
row.ValidationError = validationError;
|
||||
return;
|
||||
}
|
||||
|
||||
var result = _signalDataService.EnqueueWrite(row.Name, row.WriteValue);
|
||||
if (result)
|
||||
{
|
||||
AddLog(SentryLogLevel.Info, $"写入入队: {row.Name} = {row.WriteValue} | Write enqueued: {row.Name} = {row.WriteValue}");
|
||||
_logger.Info("写入入队: 信号={SignalName}, 值={WriteValue} | Write enqueued: signal={SignalName}, value={WriteValue}", row.Name, row.WriteValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddLog(SentryLogLevel.Warning, $"写入入队失败(队列未运行或已满): {row.Name} | Write enqueue failed (queue not running or full): {row.Name}");
|
||||
_logger.Warn("写入入队失败: 信号={SignalName} | Write enqueue failed: signal={SignalName}", row.Name);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AddLog(SentryLogLevel.Error, $"写入异常: {row.Name}, {ex.Message} | Write error: {row.Name}, {ex.Message}");
|
||||
_logger.Error(ex, "队列写入异常: 信号={SignalName} | Queue write error: signal={SignalName}", row.Name);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 直接写入+回读校验回调(由 SignalRowViewModel.DirectWriteCommand 触发)| Direct write callback
|
||||
/// </summary>
|
||||
/// <param name="row">信号行 ViewModel | Signal row ViewModel</param>
|
||||
private async void OnDirectWrite(SignalRowViewModel row)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 类型校验(委托给 SignalRowViewModel)| Type validation (delegated to SignalRowViewModel)
|
||||
var validationError = row.ValidateWriteValue();
|
||||
if (validationError != null)
|
||||
{
|
||||
row.ValidationError = validationError;
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await _signalDataService.WriteDirectWithVerify(row.Name, row.WriteValue);
|
||||
if (result)
|
||||
{
|
||||
AddLog(SentryLogLevel.Info, $"直接写入成功: {row.Name} = {row.WriteValue} | Direct write success: {row.Name} = {row.WriteValue}");
|
||||
_logger.Info("直接写入成功: 信号={SignalName}, 值={WriteValue} | Direct write success: signal={SignalName}, value={WriteValue}", row.Name, row.WriteValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddLog(SentryLogLevel.Warning, $"直接写入回读校验失败: {row.Name} | Direct write verification failed: {row.Name}");
|
||||
_logger.Warn("直接写入回读校验失败: 信号={SignalName} | Direct write verification failed: signal={SignalName}", row.Name);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AddLog(SentryLogLevel.Error, $"直接写入异常: {row.Name}, {ex.Message} | Direct write error: {row.Name}, {ex.Message}");
|
||||
_logger.Error(ex, "直接写入异常: 信号={SignalName} | Direct write error: signal={SignalName}", row.Name);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新所有信号行的写入命令可用状态 | Refresh write command state for all signal rows
|
||||
/// </summary>
|
||||
private void RefreshAllSignalCommandStates()
|
||||
{
|
||||
foreach (var group in SignalGroups)
|
||||
{
|
||||
foreach (var row in group.Signals)
|
||||
{
|
||||
row.RefreshCommandState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 PlcAddrDfn.xml 文件路径 | Get PlcAddrDfn.xml file path
|
||||
/// </summary>
|
||||
private string GetPlcAddrDfnPath()
|
||||
{
|
||||
var configPath = ConfigurationManager.AppSettings["Sentry:PlcAddrDfnPath"];
|
||||
if (string.IsNullOrEmpty(configPath))
|
||||
configPath = "PlcAddrDfn.xml";
|
||||
|
||||
// 如果是相对路径,则基于应用程序目录 | If relative path, base on app directory
|
||||
if (!Path.IsPathRooted(configPath))
|
||||
configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, configPath);
|
||||
|
||||
return configPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加操作日志 | Add operation log entry
|
||||
/// </summary>
|
||||
/// <param name="level">日志级别 | Log level</param>
|
||||
/// <param name="message">日志消息 | Log message</param>
|
||||
public void AddLog(SentryLogLevel level, string message)
|
||||
{
|
||||
var entry = new SentryLogEntry
|
||||
{
|
||||
Timestamp = DateTime.Now,
|
||||
Level = level,
|
||||
Message = message
|
||||
};
|
||||
|
||||
// 插入到集合头部,实现时间倒序 | Insert at beginning for reverse chronological order
|
||||
Application.Current?.Dispatcher?.Invoke(() =>
|
||||
{
|
||||
LogEntries.Insert(0, entry);
|
||||
});
|
||||
}
|
||||
|
||||
// === 信号值周期性刷新 | Signal value periodic refresh ===
|
||||
|
||||
/// <summary>
|
||||
/// 启动信号值刷新定时器 | Start signal value refresh timer
|
||||
/// </summary>
|
||||
private void StartRefreshTimer()
|
||||
{
|
||||
if (_refreshTimer != null)
|
||||
return;
|
||||
|
||||
// 确保在 UI 线程上创建 DispatcherTimer | Ensure DispatcherTimer is created on UI thread
|
||||
Application.Current?.Dispatcher?.Invoke(() =>
|
||||
{
|
||||
if (_refreshTimer != null)
|
||||
return;
|
||||
|
||||
_refreshTimer = new DispatcherTimer
|
||||
{
|
||||
// 刷新周期 250ms,与批量读取周期一致 | Refresh interval 250ms, same as bulk read cycle
|
||||
Interval = TimeSpan.FromMilliseconds(250)
|
||||
};
|
||||
_refreshTimer.Tick += RefreshTimer_Tick;
|
||||
_refreshTimer.Start();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止信号值刷新定时器 | Stop signal value refresh timer
|
||||
/// </summary>
|
||||
private void StopRefreshTimer()
|
||||
{
|
||||
if (_refreshTimer == null)
|
||||
return;
|
||||
|
||||
// 确保在 UI 线程上停止 DispatcherTimer | Ensure DispatcherTimer is stopped on UI thread
|
||||
Application.Current?.Dispatcher?.Invoke(() =>
|
||||
{
|
||||
if (_refreshTimer == null)
|
||||
return;
|
||||
|
||||
_refreshTimer.Stop();
|
||||
_refreshTimer.Tick -= RefreshTimer_Tick;
|
||||
_refreshTimer = null;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新定时器回调,更新所有信号的当前值 | Refresh timer callback, update all signal current values
|
||||
/// </summary>
|
||||
private void RefreshTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
if (!IsConnected || SignalGroups.Count == 0)
|
||||
return;
|
||||
|
||||
foreach (var group in SignalGroups)
|
||||
{
|
||||
foreach (var row in group.Signals)
|
||||
{
|
||||
RefreshSignalValue(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新单个信号的当前值(异常隔离)| Refresh single signal current value (error isolated)
|
||||
/// 单个信号读取异常不影响其他信号 | Single signal read error does not affect others
|
||||
/// </summary>
|
||||
/// <param name="row">信号行 ViewModel | Signal row ViewModel</param>
|
||||
private void RefreshSignalValue(SignalRowViewModel row)
|
||||
{
|
||||
try
|
||||
{
|
||||
var value = _signalDataService.GetValueByName(row.Name);
|
||||
row.CurrentValue = FormatSignalValue(row.Type, value);
|
||||
row.HasReadError = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 异常隔离:仅标记当前信号为读取错误,不影响其他信号 | Error isolation: only mark current signal as read error
|
||||
if (!row.HasReadError)
|
||||
{
|
||||
// 仅在首次出错时记录日志,避免高频刷新时日志刷屏 | Only log on first error to avoid log flooding
|
||||
AddLog(SentryLogLevel.Error, $"信号读取异常: {row.Name}, {ex.Message} | Signal read error: {row.Name}, {ex.Message}");
|
||||
_logger.Error(ex, "信号读取异常: 信号={SignalName} | Signal read error: signal={SignalName}", row.Name);
|
||||
}
|
||||
row.CurrentValue = "读取错误";
|
||||
row.HasReadError = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格式化信号值显示 | Format signal value display
|
||||
/// </summary>
|
||||
/// <param name="signalType">信号数据类型 | Signal data type</param>
|
||||
/// <param name="value">信号值 | Signal value</param>
|
||||
/// <returns>格式化后的字符串 | Formatted string</returns>
|
||||
private static string FormatSignalValue(string signalType, object value)
|
||||
{
|
||||
if (value == null)
|
||||
return "--";
|
||||
|
||||
return signalType?.ToLowerInvariant() switch
|
||||
{
|
||||
"bool" => value is bool b ? (b ? "True" : "False") : value.ToString(),
|
||||
_ => value.ToString()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using Prism.Commands;
|
||||
using Prism.Mvvm;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XP.Hardware.Plc.Abstractions;
|
||||
using XP.Hardware.Plc.Exceptions;
|
||||
using XP.Hardware.Plc.Services;
|
||||
using XP.Hardware.PLC.Configs;
|
||||
|
||||
namespace XP.App.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 信号数据服务 DEMO 窗口 ViewModel
|
||||
/// </summary>
|
||||
public class SignalDataDemoViewModel : BindableBase
|
||||
{
|
||||
private readonly PlcService _plcService;
|
||||
private readonly ISignalDataService _signalService;
|
||||
private readonly ConfigLoader _configLoader;
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
// 日志
|
||||
public ObservableCollection<string> LogEntries { get; } = new();
|
||||
|
||||
// 连接状态
|
||||
public bool IsConnected => _plcService.IsConnected;
|
||||
public string StatusText => _plcService.StatusText;
|
||||
|
||||
private bool _isInitialized;
|
||||
public bool IsInitialized
|
||||
{
|
||||
get => _isInitialized;
|
||||
set => SetProperty(ref _isInitialized, value);
|
||||
}
|
||||
|
||||
// 缓存读取
|
||||
private string _readSignalName = "";
|
||||
public string ReadSignalName
|
||||
{
|
||||
get => _readSignalName;
|
||||
set => SetProperty(ref _readSignalName, value);
|
||||
}
|
||||
|
||||
private string _readResult = "";
|
||||
public string ReadResult
|
||||
{
|
||||
get => _readResult;
|
||||
set => SetProperty(ref _readResult, value);
|
||||
}
|
||||
|
||||
// 队列写入
|
||||
private string _queueSignalName = "";
|
||||
public string QueueSignalName
|
||||
{
|
||||
get => _queueSignalName;
|
||||
set => SetProperty(ref _queueSignalName, value);
|
||||
}
|
||||
|
||||
private string _queueWriteValue = "";
|
||||
public string QueueWriteValue
|
||||
{
|
||||
get => _queueWriteValue;
|
||||
set => SetProperty(ref _queueWriteValue, value);
|
||||
}
|
||||
|
||||
// 直接写入+回读校验
|
||||
private string _directSignalName = "";
|
||||
public string DirectSignalName
|
||||
{
|
||||
get => _directSignalName;
|
||||
set => SetProperty(ref _directSignalName, value);
|
||||
}
|
||||
|
||||
private string _directWriteValue = "";
|
||||
public string DirectWriteValue
|
||||
{
|
||||
get => _directWriteValue;
|
||||
set => SetProperty(ref _directWriteValue, value);
|
||||
}
|
||||
|
||||
// 信号定义文件路径
|
||||
private string _xmlFilePath = "PlcAddrDfn.xml";
|
||||
public string XmlFilePath
|
||||
{
|
||||
get => _xmlFilePath;
|
||||
set => SetProperty(ref _xmlFilePath, value);
|
||||
}
|
||||
|
||||
// 命令
|
||||
public DelegateCommand ConnectAndLoadCommand { get; }
|
||||
public DelegateCommand CacheReadCommand { get; }
|
||||
public DelegateCommand QueueWriteCommand { get; }
|
||||
public DelegateCommand DirectWriteCommand { get; }
|
||||
public DelegateCommand ClearLogCommand { get; }
|
||||
|
||||
public SignalDataDemoViewModel(
|
||||
PlcService plcService,
|
||||
ISignalDataService signalService,
|
||||
ConfigLoader configLoader,
|
||||
ILoggerService logger)
|
||||
{
|
||||
_plcService = plcService ?? throw new ArgumentNullException(nameof(plcService));
|
||||
_signalService = signalService ?? throw new ArgumentNullException(nameof(signalService));
|
||||
_configLoader = configLoader ?? throw new ArgumentNullException(nameof(configLoader));
|
||||
_logger = logger?.ForModule<SignalDataDemoViewModel>() ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
_plcService.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(PlcService.IsConnected))
|
||||
RaisePropertyChanged(nameof(IsConnected));
|
||||
else if (e.PropertyName == nameof(PlcService.StatusText))
|
||||
RaisePropertyChanged(nameof(StatusText));
|
||||
};
|
||||
|
||||
ConnectAndLoadCommand = new DelegateCommand(async () => await ConnectAndLoadAsync());
|
||||
CacheReadCommand = new DelegateCommand(CacheRead);
|
||||
QueueWriteCommand = new DelegateCommand(QueueWrite);
|
||||
DirectWriteCommand = new DelegateCommand(async () => await DirectWriteAsync());
|
||||
ClearLogCommand = new DelegateCommand(() => LogEntries.Clear());
|
||||
}
|
||||
|
||||
private void Log(string msg)
|
||||
{
|
||||
var text = $"[{DateTime.Now:HH:mm:ss.fff}] {msg}";
|
||||
LogEntries.Add(text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接 PLC 并加载信号定义
|
||||
/// </summary>
|
||||
private async Task ConnectAndLoadAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
Log("正在加载配置...");
|
||||
var config = _configLoader.LoadPlcConfig();
|
||||
Log($"配置加载成功: {config.IpAddress}:{config.Port}, DB读={config.ReadDbBlock}");
|
||||
|
||||
Log("正在连接 PLC...");
|
||||
var result = await _plcService.InitializeAsync(config);
|
||||
if (!result)
|
||||
{
|
||||
Log("ERROR: PLC 连接失败");
|
||||
return;
|
||||
}
|
||||
Log("PLC 连接成功");
|
||||
|
||||
Log($"正在加载信号定义: {XmlFilePath}");
|
||||
_plcService.LoadSignalDefinitions(XmlFilePath);
|
||||
Log("信号定义加载成功");
|
||||
|
||||
IsInitialized = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"ERROR: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 缓存读取
|
||||
/// </summary>
|
||||
private void CacheRead()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ReadSignalName))
|
||||
{
|
||||
Log("ERROR: 请输入信号名称");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var value = _signalService.GetValueByName(ReadSignalName);
|
||||
ReadResult = value?.ToString() ?? "null";
|
||||
Log($"缓存读取成功: {ReadSignalName} = {ReadResult} (类型: {value?.GetType().Name ?? "null"})");
|
||||
}
|
||||
catch (PlcException ex)
|
||||
{
|
||||
ReadResult = "ERROR";
|
||||
Log($"ERROR: 缓存读取失败 - {ex.Message}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ReadResult = "ERROR";
|
||||
Log($"ERROR: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 队列写入
|
||||
/// </summary>
|
||||
private void QueueWrite()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(QueueSignalName))
|
||||
{
|
||||
Log("ERROR: 请输入信号名称");
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(QueueWriteValue))
|
||||
{
|
||||
Log("ERROR: 请输入写入值");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 尝试解析为数值,否则作为字符串
|
||||
object value = ParseInputValue(QueueWriteValue);
|
||||
bool enqueued = _signalService.EnqueueWrite(QueueSignalName, value);
|
||||
Log(enqueued
|
||||
? $"队列写入成功: {QueueSignalName} = {QueueWriteValue}"
|
||||
: $"WARN: 队列写入返回 false(队列未运行或已满): {QueueSignalName}");
|
||||
}
|
||||
catch (PlcException ex)
|
||||
{
|
||||
Log($"ERROR: 队列写入失败 - {ex.Message}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"ERROR: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 直接写入+回读校验
|
||||
/// </summary>
|
||||
private async Task DirectWriteAsync()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(DirectSignalName))
|
||||
{
|
||||
Log("ERROR: 请输入信号名称");
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(DirectWriteValue))
|
||||
{
|
||||
Log("ERROR: 请输入写入值");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
object value = ParseInputValue(DirectWriteValue);
|
||||
Log($"正在直接写入: {DirectSignalName} = {DirectWriteValue}...");
|
||||
bool verified = await _signalService.WriteDirectWithVerify(DirectSignalName, value);
|
||||
Log(verified
|
||||
? $"直接写入校验通过: {DirectSignalName} = {DirectWriteValue}"
|
||||
: $"WARN: 直接写入校验失败: {DirectSignalName} = {DirectWriteValue}");
|
||||
}
|
||||
catch (PlcException ex)
|
||||
{
|
||||
Log($"ERROR: 直接写入失败 - {ex.Message}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"ERROR: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析用户输入值:尝试 bool → int → double → string
|
||||
/// </summary>
|
||||
private object ParseInputValue(string input)
|
||||
{
|
||||
if (bool.TryParse(input, out bool bVal)) return bVal;
|
||||
if (int.TryParse(input, out int iVal)) return iVal;
|
||||
if (double.TryParse(input, out double dVal)) return dVal;
|
||||
return input;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using Prism.Mvvm;
|
||||
|
||||
namespace XP.Hardware.PLC.Sentry.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 信号分组 ViewModel,对应一个 Tab 页 | Signal group ViewModel, corresponds to one Tab page
|
||||
/// </summary>
|
||||
public class SignalGroupViewModel : BindableBase
|
||||
{
|
||||
private string _groupId = string.Empty;
|
||||
private int _dbNumber;
|
||||
private ObservableCollection<SignalRowViewModel> _signals = new();
|
||||
|
||||
/// <summary>
|
||||
/// 分组 ID | Group ID
|
||||
/// </summary>
|
||||
public string GroupId
|
||||
{
|
||||
get => _groupId;
|
||||
set => SetProperty(ref _groupId, value ?? string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DB 块号 | DB block number
|
||||
/// </summary>
|
||||
public int DBNumber
|
||||
{
|
||||
get => _dbNumber;
|
||||
set => SetProperty(ref _dbNumber, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标签页标题,格式 "{GroupId} [DB{DBNumber}]" | Tab header, format "{GroupId} [DB{DBNumber}]"
|
||||
/// </summary>
|
||||
public string TabHeader => $"{GroupId} [DB{DBNumber}]";
|
||||
|
||||
/// <summary>
|
||||
/// 该分组下的信号行集合 | Signal row collection under this group
|
||||
/// </summary>
|
||||
public ObservableCollection<SignalRowViewModel> Signals
|
||||
{
|
||||
get => _signals;
|
||||
set => SetProperty(ref _signals, value ?? new ObservableCollection<SignalRowViewModel>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Prism.Commands;
|
||||
using Prism.Mvvm;
|
||||
using XP.Hardware.PLC.Models;
|
||||
|
||||
namespace XP.Hardware.PLC.Sentry.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 单行信号 ViewModel,封装 SignalEntry 并添加运行时状态 | Single signal row ViewModel wrapping SignalEntry with runtime state
|
||||
/// </summary>
|
||||
public class SignalRowViewModel : BindableBase
|
||||
{
|
||||
private readonly SignalEntry _signal;
|
||||
private string _currentValue = "--";
|
||||
private bool _hasReadError;
|
||||
private object _writeValue;
|
||||
private string _validationError;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
/// <param name="signal">信号定义条目 | Signal definition entry</param>
|
||||
/// <param name="applyAction">队列写入回调 | Queue write callback</param>
|
||||
/// <param name="directWriteAction">直接写入回调 | Direct write callback</param>
|
||||
/// <param name="canExecuteWrite">写入命令是否可用判断 | Write command can-execute predicate</param>
|
||||
public SignalRowViewModel(
|
||||
SignalEntry signal,
|
||||
Action<SignalRowViewModel> applyAction,
|
||||
Action<SignalRowViewModel> directWriteAction,
|
||||
Func<bool> canExecuteWrite)
|
||||
{
|
||||
_signal = signal ?? throw new ArgumentNullException(nameof(signal));
|
||||
|
||||
ApplyCommand = new DelegateCommand(
|
||||
() => applyAction?.Invoke(this),
|
||||
() => canExecuteWrite?.Invoke() ?? false);
|
||||
|
||||
DirectWriteCommand = new DelegateCommand(
|
||||
() => directWriteAction?.Invoke(this),
|
||||
() => canExecuteWrite?.Invoke() ?? false);
|
||||
}
|
||||
|
||||
// === 信号定义信息(只读)| Signal definition info (read-only) ===
|
||||
|
||||
/// <summary>
|
||||
/// 信号名称 | Signal name
|
||||
/// </summary>
|
||||
public string Name => _signal.Name;
|
||||
|
||||
/// <summary>
|
||||
/// 数据类型 | Data type
|
||||
/// </summary>
|
||||
public string Type => _signal.Type;
|
||||
|
||||
/// <summary>
|
||||
/// 起始地址 | Start address
|
||||
/// </summary>
|
||||
public int StartAddr => _signal.StartAddr;
|
||||
|
||||
/// <summary>
|
||||
/// 长度/索引 | Length/Index
|
||||
/// </summary>
|
||||
public string IndexOrLength => _signal.IndexOrLength;
|
||||
|
||||
/// <summary>
|
||||
/// 备注 | Remark
|
||||
/// </summary>
|
||||
public string Remark => _signal.Remark;
|
||||
|
||||
/// <summary>
|
||||
/// DB 块号 | DB block number
|
||||
/// </summary>
|
||||
public int DBNumber => _signal.DBNumber;
|
||||
|
||||
/// <summary>
|
||||
/// 原始信号定义条目 | Original signal definition entry
|
||||
/// </summary>
|
||||
public SignalEntry Signal => _signal;
|
||||
|
||||
/// <summary>
|
||||
/// 格式化的地址显示 | Formatted address display
|
||||
/// bool 类型显示位地址(DB{n}.{addr}.{bit}),其他类型显示字节地址(DB{n}.{addr})
|
||||
/// </summary>
|
||||
public string AddressDisplay => Type?.ToLowerInvariant() == "bool"
|
||||
? $"DB{DBNumber}.{StartAddr}.{IndexOrLength}"
|
||||
: $"DB{DBNumber}.{StartAddr}";
|
||||
|
||||
// === 运行时状态 | Runtime state ===
|
||||
|
||||
/// <summary>
|
||||
/// 当前读取值 | Current read value
|
||||
/// </summary>
|
||||
public string CurrentValue
|
||||
{
|
||||
get => _currentValue;
|
||||
set => SetProperty(ref _currentValue, value ?? "--");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否存在读取错误 | Whether read error exists
|
||||
/// </summary>
|
||||
public bool HasReadError
|
||||
{
|
||||
get => _hasReadError;
|
||||
set => SetProperty(ref _hasReadError, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入值 | Write value
|
||||
/// </summary>
|
||||
public object WriteValue
|
||||
{
|
||||
get => _writeValue;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _writeValue, value))
|
||||
{
|
||||
// 写入值变更时清除校验错误 | Clear validation error when write value changes
|
||||
ValidationError = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验错误信息 | Validation error message
|
||||
/// </summary>
|
||||
public string ValidationError
|
||||
{
|
||||
get => _validationError;
|
||||
set => SetProperty(ref _validationError, value);
|
||||
}
|
||||
|
||||
// === 命令 | Commands ===
|
||||
|
||||
/// <summary>
|
||||
/// 队列写入命令 | Queue write command
|
||||
/// </summary>
|
||||
public DelegateCommand ApplyCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 直接写入+回读校验命令 | Direct write with verify command
|
||||
/// </summary>
|
||||
public DelegateCommand DirectWriteCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 刷新写入命令可用状态 | Refresh write command can-execute state
|
||||
/// </summary>
|
||||
public void RefreshCommandState()
|
||||
{
|
||||
ApplyCommand.RaiseCanExecuteChanged();
|
||||
DirectWriteCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验写入值是否符合信号数据类型要求 | Validate write value against signal data type
|
||||
/// </summary>
|
||||
/// <returns>校验错误信息,null 表示通过 | Validation error message, null means passed</returns>
|
||||
public string ValidateWriteValue()
|
||||
{
|
||||
if (WriteValue == null)
|
||||
return "写入值不能为空 | Write value cannot be empty";
|
||||
|
||||
var strValue = WriteValue.ToString();
|
||||
if (string.IsNullOrWhiteSpace(strValue))
|
||||
return "写入值不能为空 | Write value cannot be empty";
|
||||
|
||||
switch (Type?.ToLowerInvariant())
|
||||
{
|
||||
case "bool":
|
||||
if (!bool.TryParse(strValue, out _))
|
||||
return "布尔值仅接受 True/False | Bool accepts only True/False";
|
||||
break;
|
||||
case "byte":
|
||||
if (!byte.TryParse(strValue, out _))
|
||||
return "字节值超出范围 (0-255) | Byte value out of range (0-255)";
|
||||
break;
|
||||
case "short":
|
||||
if (!short.TryParse(strValue, out _))
|
||||
return "短整型值超出范围 | Short value out of range";
|
||||
break;
|
||||
case "int":
|
||||
if (!int.TryParse(strValue, out _))
|
||||
return "整型值超出范围 | Int value out of range";
|
||||
break;
|
||||
case "single":
|
||||
if (!float.TryParse(strValue, out _))
|
||||
return "浮点数格式错误 | Float format error";
|
||||
break;
|
||||
case "double":
|
||||
if (!double.TryParse(strValue, out _))
|
||||
return "双精度浮点数格式错误 | Double format error";
|
||||
break;
|
||||
case "string":
|
||||
// 字符串接受任意非空值 | String accepts any non-empty value
|
||||
break;
|
||||
default:
|
||||
return $"不支持的类型: {Type} | Unsupported type: {Type}";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user