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 { /// /// PLC 信号地址定义条目 | PLC signal address definition entry /// public class SignalEntry : BindableBase { /// /// 支持的数据类型列表 | Supported data types /// public static readonly string[] SupportedTypes = { "bool", "byte", "short", "int", "single", "double", "string" }; /// /// 固定长度类型(不可编辑 IndexOrLength)| Fixed-length types (IndexOrLength not editable) /// private static readonly HashSet FixedLengthTypes = new(StringComparer.OrdinalIgnoreCase) { "byte", "short", "int", "single", "double" }; /// /// 合法 XML 元素名称正则 | Valid XML element name regex /// 以字母或下划线开头,后续为字母、数字、下划线、连字符、点号 /// 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; /// /// 信号名称(XML 元素标签名)| Signal name (XML element tag name) /// 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); } } } /// /// 数据类型 | Data type /// 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)); } } } /// /// 起始地址 | Start address /// public int StartAddr { get => _startAddr; set { if (SetProperty(ref _startAddr, value)) { RaisePropertyChanged(nameof(ValidationError)); } } } /// /// 长度/索引(string 类型时为长度,bool 类型时为位索引)| Length/Index /// public string IndexOrLength { get => _indexOrLength; set { if (SetProperty(ref _indexOrLength, value ?? string.Empty)) { RaisePropertyChanged(nameof(ValidationError)); } } } /// /// 备注 | Remark /// public string Remark { get => _remark; set => SetProperty(ref _remark, value ?? string.Empty); } private string _groupId = string.Empty; private int _dbNumber; /// /// 所属 Group ID,由解析器赋值,不参与 XML Signal 节点序列化 /// Group ID this signal belongs to, assigned by parser, not serialized to XML Signal node /// public string GroupId { get => _groupId; set => SetProperty(ref _groupId, value ?? string.Empty); } /// /// 所属 DB 块号,由解析器赋值,不参与 XML Signal 节点序列化 /// DB block number this signal belongs to, assigned by parser, not serialized to XML Signal node /// public int DBNumber { get => _dbNumber; set => SetProperty(ref _dbNumber, value); } /// /// 所属信号集合引用,用于名称重复检测 | Owner collection reference for duplicate name detection /// public ObservableCollection OwnerCollection { get; set; } /// /// 长度字段是否可编辑 | Whether length field is editable /// bool 和 string 类型可编辑,其余固定长度类型不可编辑 /// public bool IsLengthEditable => string.Equals(_type, "bool", StringComparison.OrdinalIgnoreCase) || string.Equals(_type, "string", StringComparison.OrdinalIgnoreCase); /// /// 长度字段是否只读(IsLengthEditable 的反转,供 RadGridView IsReadOnlyBinding 使用) /// Whether length field is read-only (inverse of IsLengthEditable, for RadGridView IsReadOnlyBinding) /// public bool IsLengthReadOnly => !IsLengthEditable; /// /// 验证错误信息,返回第一个错误或 null | Validation error message, returns first error or null /// 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; } } /// /// 通知同集合中同名条目刷新验证状态 | Notify siblings with same name to refresh validation /// 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)); } } } } }