205 lines
7.2 KiB
C#
205 lines
7.2 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|