| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420 |
- using Newtonsoft.Json;
- using Prism.Mvvm;
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Globalization;
- using System.Linq;
- namespace TeamAAS.Communication.Models
- {
- /// <summary>变量作用域:Global=机器级固定(不随产品变);Product=随产品变(存各产品快照)。</summary>
- public enum VarScope { Global, Product }
- /// <summary>
- /// 全局变量。
- /// 持久化以 <see cref="Text"/> 为唯一载体(<see cref="Value"/> 标了 JsonIgnore):
- /// 反序列化时先落 DataType 再由 Text 解析出强类型值,避免 object 属性被 Newtonsoft
- /// 还原成 long / JArray 而导致下游类型不匹配。
- /// </summary>
- [Serializable]
- public class GlobalVariableModel : BindableBase
- {
- /// <summary>Text ↔ Value 双向同步的重入保护</summary>
- [JsonIgnore]
- private bool _syncing;
- private int _index;
- public int Index
- {
- get => _index;
- set => SetProperty(ref _index, value);
- }
- private string _name = "";
- public string Name
- {
- get => _name;
- set
- {
- if (SetProperty(ref _name, value))
- {
- RaisePropertyChanged(nameof(IsValid));
- }
- }
- }
- private string _dataType = "int";
- public string DataType
- {
- get => _dataType;
- set
- {
- if (!SetProperty(ref _dataType, value)) return;
- // 换类型后同时重置 Value 和 Text:只改 Value 会让"值"列继续显示旧文本,
- // 与实际值错位(原实现的 Bug)
- _value = GetDefaultValue(value);
- var t = ValueToText(_value);
- if (_text != t)
- {
- _text = t;
- RaisePropertyChanged(nameof(Text));
- }
- RaisePropertyChanged(nameof(DisplayValue));
- RaisePropertyChanged(nameof(IsValid));
- }
- }
- private object _value = 0;
- /// <summary>
- /// 变量值。可由代码写入任意类型(object 类型变量尤其如此);写入后自动同步 Text 显示。
- /// 不参与 JSON 序列化——持久化走 Text,加载时按 DataType 重新解析出强类型值。
- /// </summary>
- [JsonIgnore]
- public object Value
- {
- get => _value;
- set
- {
- if (!SetProperty(ref _value, value)) return;
- SyncTextFromValue();
- RaisePropertyChanged(nameof(DisplayValue));
- }
- }
- private string _text = "0";
- /// <summary>值列编辑用的文本表示,也是持久化载体。修改后按 DataType 解析进 Value。</summary>
- public string Text
- {
- get => _text;
- set
- {
- if (!SetProperty(ref _text, value)) return;
- ParseValue(value);
- }
- }
- private VarScope _scope = VarScope.Product;
- /// <summary>作用域:Global(机器级固定)/Product(随产品变)。默认 Product,兼容旧快照。</summary>
- public VarScope Scope
- {
- get => _scope;
- set => SetProperty(ref _scope, value);
- }
- private string _note = "";
- public string Note
- {
- get => _note;
- set => SetProperty(ref _note, value);
- }
- /// <summary>只读展示值:集合统一渲染成 [a, b, c],其余取 ToString。</summary>
- [JsonIgnore]
- public string DisplayValue
- {
- get
- {
- if (_value == null) return "";
- if (_value is string s) return s;
- if (_value is IEnumerable en)
- return "[" + string.Join(", ", en.Cast<object>().Select(x => x?.ToString() ?? "null")) + "]";
- return _value.ToString();
- }
- }
- [JsonIgnore]
- public bool IsValid => !string.IsNullOrWhiteSpace(Name);
- #region 默认值 / 文本互转
- /// <summary>该类型新建变量时的初始文本(数组/字符串/object 给空,bool 给 False,数值给 0)</summary>
- public static string GetDefaultText(string dataType)
- {
- switch (dataType)
- {
- case "bool": return "False";
- case "string":
- case "object":
- case "short[]":
- case "int[]":
- case "double[]":
- case "string[]":
- case "bool[]":
- return "";
- default: return "0";
- }
- }
- private static object GetDefaultValue(string dataType)
- {
- switch (dataType)
- {
- case "short": return (short)0;
- case "short[]": return new List<short>();
- case "int": return 0;
- case "int[]": return new List<int>();
- case "double": return 0.0;
- case "double[]": return new List<double>();
- case "string": return "";
- case "string[]": return new List<string>();
- case "bool": return false;
- case "bool[]": return new List<bool>();
- // object:任意类型容器,默认空引用(由代码 SetValue 写入实际对象)
- case "object": return null;
- default: return 0;
- }
- }
- /// <summary>值 → 文本(集合用逗号连接,bool 用 True/False,数值用不变文化格式)</summary>
- private static string ValueToText(object value)
- {
- if (value == null) return "";
- if (value is string s) return s;
- if (value is bool b) return b ? "True" : "False";
- if (value is IEnumerable en)
- return string.Join(",", en.Cast<object>().Select(FormatElement));
- return FormatElement(value);
- }
- private static string FormatElement(object x)
- {
- if (x == null) return "";
- if (x is bool b) return b ? "True" : "False";
- if (x is IFormattable f) return f.ToString(null, CultureInfo.InvariantCulture);
- return x.ToString();
- }
- #endregion
- #region 文本解析
- /// <summary>
- /// Text → Value。解析失败时回落到该类型默认值,并把文本框还原为默认值文本
- /// (原实现只改 _text 字段不通知,界面仍显示非法输入)。
- /// </summary>
- private void ParseValue(string text)
- {
- if (_syncing) return;
- _syncing = true;
- try
- {
- object parsed;
- if (TryParse(text, _dataType, out parsed))
- {
- _value = parsed;
- }
- else
- {
- _value = GetDefaultValue(_dataType);
- var t = ValueToText(_value);
- if (_text != t)
- {
- _text = t;
- RaisePropertyChanged(nameof(Text));
- }
- }
- RaisePropertyChanged(nameof(DisplayValue));
- }
- finally
- {
- _syncing = false;
- }
- }
- /// <summary>Value → Text(代码写入值时同步界面文本)</summary>
- private void SyncTextFromValue()
- {
- if (_syncing) return;
- _syncing = true;
- try
- {
- var t = ValueToText(_value);
- if (_text != t)
- {
- _text = t;
- RaisePropertyChanged(nameof(Text));
- }
- }
- finally
- {
- _syncing = false;
- }
- }
- private static bool TryParse(string text, string dataType, out object value)
- {
- value = null;
- var s = (text ?? "").Trim();
- switch (dataType)
- {
- case "short":
- {
- short r;
- if (!short.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out r)) return false;
- value = r; return true;
- }
- case "int":
- {
- int r;
- if (!int.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out r)) return false;
- value = r; return true;
- }
- case "double":
- {
- double r;
- if (!double.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out r)) return false;
- value = r; return true;
- }
- case "bool":
- {
- bool r;
- if (!TryParseBool(s, out r)) return false;
- value = r; return true;
- }
- case "string":
- // 字符串保留原文(不 Trim),避免吃掉用户有意输入的首尾空格
- value = text ?? ""; return true;
- case "object":
- // 文本编辑时按字符串存;代码可通过 Value/SetValue 写入任意类型对象
- value = text ?? ""; return true;
- case "short[]": return TryParseArray<short>(s, x => { short r; return short.TryParse(x, NumberStyles.Any, CultureInfo.InvariantCulture, out r) ? (object)r : null; }, out value);
- case "int[]": return TryParseArray<int>(s, x => { int r; return int.TryParse(x, NumberStyles.Any, CultureInfo.InvariantCulture, out r) ? (object)r : null; }, out value);
- case "double[]": return TryParseArray<double>(s, x => { double r; return double.TryParse(x, NumberStyles.Any, CultureInfo.InvariantCulture, out r) ? (object)r : null; }, out value);
- case "bool[]": return TryParseArray<bool>(s, x => { bool r; return TryParseBool(x, out r) ? (object)r : null; }, out value);
- case "string[]": return TryParseArray<string>(s, x => (object)x, out value);
- default:
- value = text ?? ""; return true;
- }
- }
- /// <summary>
- /// 布尔宽松解析:除 True/False 外还接受 1/0、on/off、yes/no、是/否。
- /// 原实现用 bool.Parse,输入 1 会抛异常并把值静默重置为 false。
- /// </summary>
- private static bool TryParseBool(string s, out bool result)
- {
- result = false;
- if (string.IsNullOrWhiteSpace(s)) return false;
- var t = s.Trim();
- if (bool.TryParse(t, out result)) return true;
- switch (t.ToLowerInvariant())
- {
- case "1": case "on": case "yes": case "y": case "是": case "真": result = true; return true;
- case "0": case "off": case "no": case "n": case "否": case "假": result = false; return true;
- default: return false;
- }
- }
- /// <summary>
- /// 数组解析:支持中英文逗号/分号分隔,空串 = 空列表;任一元素解析失败则整体失败。
- /// 泛型参数保证空列表也是正确的强类型(不能靠首元素推断类型)。
- /// 原实现完全没有数组分支,数组类型变量的值永远设不进去。
- /// </summary>
- private static bool TryParseArray<T>(string s, Func<string, object> parseElement, out object value)
- {
- value = null;
- var items = new List<T>();
- if (!string.IsNullOrWhiteSpace(s))
- {
- var parts = s.Split(new[] { ',', ',', ';', ';' }, StringSplitOptions.RemoveEmptyEntries);
- foreach (var p in parts)
- {
- var el = parseElement(p.Trim());
- if (el == null) return false;
- items.Add((T)el);
- }
- }
- value = items;
- return true;
- }
- #endregion
- /// <summary>
- /// 复制一份变量。不用 MemberwiseClone:它会连带复制 PropertyChanged 订阅
- /// (克隆体改动会通知原对象的监听者),且 List 值是浅拷贝(与原对象共享同一实例)。
- /// </summary>
- public GlobalVariableModel Clone()
- {
- // 直接拷字段,绕开各 setter 的联动逻辑,得到与原件完全一致的副本
- return new GlobalVariableModel
- {
- _index = _index,
- _name = _name,
- _dataType = _dataType,
- _value = CopyValue(_value),
- _text = _text,
- _note = _note,
- _scope = _scope,
- };
- }
- /// <summary>创建新变量(统一 Index/Name/默认 Text/Scope 构造,供管理器与编辑器共用)。</summary>
- public static GlobalVariableModel Create(string dataType, VarScope scope, int index)
- {
- var v = new GlobalVariableModel
- {
- Index = index,
- Name = $"Var{index + 1}",
- DataType = dataType,
- Scope = scope,
- };
- v.Text = GetDefaultText(dataType);
- return v;
- }
- private static object CopyValue(object value)
- {
- if (value == null) return null;
- if (value is List<short> ls) return new List<short>(ls);
- if (value is List<int> li) return new List<int>(li);
- if (value is List<double> ld) return new List<double>(ld);
- if (value is List<bool> lb) return new List<bool>(lb);
- if (value is List<string> lstr) return new List<string>(lstr);
- // 基元/字符串不可变;object 变量持有的任意引用无法安全深拷贝,按原样共享
- return value;
- }
- }
- public static class GlobalVariableTypes
- {
- public static readonly string[] SimpleTypes = { "short", "int", "double", "string", "bool" };
- public static readonly string[] ArrayTypes = { "short[]", "int[]", "double[]", "string[]", "bool[]" };
- /// <summary>任意类型:可被代码写入任何对象(图像/集合/自定义类型),文本编辑时按字符串存取</summary>
- public static readonly string[] AnyTypes = { "object" };
- public static readonly string[] AllTypes = SimpleTypes.Concat(ArrayTypes).Concat(AnyTypes).ToArray();
- public static string GetDisplayName(string type)
- {
- switch (type)
- {
- case "short": return "16位整数 (short)";
- case "short[]": return "16位整数数组";
- case "int": return "整数 (int)";
- case "int[]": return "整数数组";
- case "double": return "双精度浮点 (double)";
- case "double[]": return "双精度浮点数组";
- case "string": return "字符串 (string)";
- case "string[]": return "字符串数组";
- case "bool": return "布尔 (bool)";
- case "bool[]": return "布尔数组";
- case "object": return "任意对象 (object)";
- default: return type;
- }
- }
- }
- }
|