Files

225 lines
8.4 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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));
}
}
}
}
}