225 lines
8.4 KiB
C#
225 lines
8.4 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Collections.ObjectModel;
|
||
using System.Linq;
|
||
using System.Text.RegularExpressions;
|
||
using Prism.Mvvm;
|
||
|
||
namespace XP.Hardware.PLC.Models
|
||
{
|
||
/// <summary>
|
||
/// PLC 信号地址定义条目 | PLC signal address definition entry
|
||
/// </summary>
|
||
public class SignalEntry : BindableBase
|
||
{
|
||
/// <summary>
|
||
/// 支持的数据类型列表 | Supported data types
|
||
/// </summary>
|
||
public static readonly string[] SupportedTypes = { "bool", "byte", "short", "int", "single", "double", "string" };
|
||
|
||
/// <summary>
|
||
/// 固定长度类型(不可编辑 IndexOrLength)| Fixed-length types (IndexOrLength not editable)
|
||
/// </summary>
|
||
private static readonly HashSet<string> FixedLengthTypes = new(StringComparer.OrdinalIgnoreCase)
|
||
{
|
||
"byte", "short", "int", "single", "double"
|
||
};
|
||
|
||
/// <summary>
|
||
/// 合法 XML 元素名称正则 | Valid XML element name regex
|
||
/// 以字母或下划线开头,后续为字母、数字、下划线、连字符、点号
|
||
/// </summary>
|
||
private static readonly Regex ValidXmlNameRegex = new(@"^[a-zA-Z_][\w\.\-]*$", RegexOptions.Compiled);
|
||
|
||
private string _name = string.Empty;
|
||
private string _type = "byte";
|
||
private int _startAddr;
|
||
private string _indexOrLength = string.Empty;
|
||
private string _remark = string.Empty;
|
||
|
||
/// <summary>
|
||
/// 信号名称(XML 元素标签名)| Signal name (XML element tag name)
|
||
/// </summary>
|
||
public string Name
|
||
{
|
||
get => _name;
|
||
set
|
||
{
|
||
var oldName = _name;
|
||
if (SetProperty(ref _name, value ?? string.Empty))
|
||
{
|
||
RaisePropertyChanged(nameof(ValidationError));
|
||
// 通知同集合中与旧名或新名相同的条目刷新验证 | Notify siblings with old/new name to refresh validation
|
||
NotifySiblingsValidation(oldName);
|
||
NotifySiblingsValidation(_name);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 数据类型 | Data type
|
||
/// </summary>
|
||
public string Type
|
||
{
|
||
get => _type;
|
||
set
|
||
{
|
||
if (SetProperty(ref _type, value ?? "byte"))
|
||
{
|
||
// 固定长度类型自动清空 IndexOrLength | Auto-clear IndexOrLength for fixed-length types
|
||
if (FixedLengthTypes.Contains(_type))
|
||
{
|
||
IndexOrLength = string.Empty;
|
||
}
|
||
|
||
RaisePropertyChanged(nameof(IsLengthEditable));
|
||
RaisePropertyChanged(nameof(IsLengthReadOnly));
|
||
RaisePropertyChanged(nameof(ValidationError));
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 起始地址 | Start address
|
||
/// </summary>
|
||
public int StartAddr
|
||
{
|
||
get => _startAddr;
|
||
set
|
||
{
|
||
if (SetProperty(ref _startAddr, value))
|
||
{
|
||
RaisePropertyChanged(nameof(ValidationError));
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 长度/索引(string 类型时为长度,bool 类型时为位索引)| Length/Index
|
||
/// </summary>
|
||
public string IndexOrLength
|
||
{
|
||
get => _indexOrLength;
|
||
set
|
||
{
|
||
if (SetProperty(ref _indexOrLength, value ?? string.Empty))
|
||
{
|
||
RaisePropertyChanged(nameof(ValidationError));
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 备注 | Remark
|
||
/// </summary>
|
||
public string Remark
|
||
{
|
||
get => _remark;
|
||
set => SetProperty(ref _remark, value ?? string.Empty);
|
||
}
|
||
|
||
private string _groupId = string.Empty;
|
||
private int _dbNumber;
|
||
|
||
/// <summary>
|
||
/// 所属 Group ID,由解析器赋值,不参与 XML Signal 节点序列化
|
||
/// Group ID this signal belongs to, assigned by parser, not serialized to XML Signal node
|
||
/// </summary>
|
||
public string GroupId
|
||
{
|
||
get => _groupId;
|
||
set => SetProperty(ref _groupId, value ?? string.Empty);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 所属 DB 块号,由解析器赋值,不参与 XML Signal 节点序列化
|
||
/// DB block number this signal belongs to, assigned by parser, not serialized to XML Signal node
|
||
/// </summary>
|
||
public int DBNumber
|
||
{
|
||
get => _dbNumber;
|
||
set => SetProperty(ref _dbNumber, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 所属信号集合引用,用于名称重复检测 | Owner collection reference for duplicate name detection
|
||
/// </summary>
|
||
public ObservableCollection<SignalEntry> OwnerCollection { get; set; }
|
||
|
||
/// <summary>
|
||
/// 长度字段是否可编辑 | Whether length field is editable
|
||
/// bool 和 string 类型可编辑,其余固定长度类型不可编辑
|
||
/// </summary>
|
||
public bool IsLengthEditable =>
|
||
string.Equals(_type, "bool", StringComparison.OrdinalIgnoreCase) ||
|
||
string.Equals(_type, "string", StringComparison.OrdinalIgnoreCase);
|
||
|
||
/// <summary>
|
||
/// 长度字段是否只读(IsLengthEditable 的反转,供 RadGridView IsReadOnlyBinding 使用)
|
||
/// Whether length field is read-only (inverse of IsLengthEditable, for RadGridView IsReadOnlyBinding)
|
||
/// </summary>
|
||
public bool IsLengthReadOnly => !IsLengthEditable;
|
||
|
||
/// <summary>
|
||
/// 验证错误信息,返回第一个错误或 null | Validation error message, returns first error or null
|
||
/// </summary>
|
||
public string ValidationError
|
||
{
|
||
get
|
||
{
|
||
// 验证信号名称非空 | Validate name is not empty
|
||
if (string.IsNullOrWhiteSpace(_name))
|
||
return "信号名称不能为空";
|
||
|
||
// 验证信号名称为合法 XML 元素名称字符 | Validate name contains valid XML element name chars
|
||
if (!ValidXmlNameRegex.IsMatch(_name))
|
||
return "信号名称包含非法字符";
|
||
|
||
// 验证同一分区内名称不重复 | Validate name uniqueness within the same collection
|
||
if (OwnerCollection != null && OwnerCollection.Count(s => string.Equals(s.Name, _name, StringComparison.Ordinal)) > 1)
|
||
return "信号名称重复";
|
||
|
||
// 验证起始地址非负 | Validate start address is non-negative
|
||
if (_startAddr < 0)
|
||
return "起始地址必须为非负整数";
|
||
|
||
// 验证 string 类型长度为正整数 | Validate string type length is positive integer
|
||
if (string.Equals(_type, "string", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
if (!int.TryParse(_indexOrLength, out int length) || length <= 0)
|
||
return "字符串长度必须为正整数";
|
||
}
|
||
|
||
// 验证 bool 类型位索引在 0-7 之间 | Validate bool type bit index is 0-7
|
||
if (string.Equals(_type, "bool", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(_indexOrLength))
|
||
{
|
||
if (!int.TryParse(_indexOrLength, out int bitIndex) || bitIndex < 0 || bitIndex > 7)
|
||
return "位索引必须在 0-7 之间";
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通知同集合中同名条目刷新验证状态 | Notify siblings with same name to refresh validation
|
||
/// </summary>
|
||
private void NotifySiblingsValidation(string name)
|
||
{
|
||
if (OwnerCollection == null || string.IsNullOrWhiteSpace(name))
|
||
return;
|
||
|
||
foreach (var sibling in OwnerCollection)
|
||
{
|
||
if (sibling != this && string.Equals(sibling.Name, name, StringComparison.Ordinal))
|
||
{
|
||
sibling.RaisePropertyChanged(nameof(ValidationError));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|