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));
}
}
}
}
}