diff --git a/CadParamPluging.csproj b/CadParamPluging.csproj index df9aa28..26dc9cc 100644 --- a/CadParamPluging.csproj +++ b/CadParamPluging.csproj @@ -60,6 +60,15 @@ + + + + + + + + + @@ -79,15 +88,27 @@ UserControl + + Form + Form Form + + Form + + + Form + Form + + Form + Form diff --git a/Common/ParamBag.cs b/Common/ParamBag.cs new file mode 100644 index 0000000..68e3250 --- /dev/null +++ b/Common/ParamBag.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace CadParamPluging.Common +{ + [Serializable] + public class ParamBag + { + public List Items { get; set; } + + public ParamBag() + { + Items = new List(); + } + + public void Set(string key, string value) + { + if (string.IsNullOrWhiteSpace(key)) + { + return; + } + + if (Items == null) + { + Items = new List(); + } + + var existing = Items.FirstOrDefault(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase)); + if (existing == null) + { + Items.Add(new ParamValue { Key = key.Trim(), Value = value }); + } + else + { + existing.Value = value; + } + } + + public string GetString(string key) + { + if (string.IsNullOrWhiteSpace(key) || Items == null) + { + return null; + } + + return Items.FirstOrDefault(x => string.Equals(x.Key, key.Trim(), StringComparison.OrdinalIgnoreCase))?.Value; + } + + public double GetDouble(string key, double fallback = 0) + { + var s = GetString(key); + if (string.IsNullOrWhiteSpace(s)) + { + return fallback; + } + + if (double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var v)) + { + return v; + } + + if (double.TryParse(s, NumberStyles.Float, CultureInfo.CurrentCulture, out v)) + { + return v; + } + + return fallback; + } + + public int GetInt(string key, int fallback = 0) + { + var s = GetString(key); + if (string.IsNullOrWhiteSpace(s)) + { + return fallback; + } + + if (int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out var v)) + { + return v; + } + + if (int.TryParse(s, NumberStyles.Integer, CultureInfo.CurrentCulture, out v)) + { + return v; + } + + return fallback; + } + + public bool GetBool(string key, bool fallback = false) + { + var s = GetString(key); + if (string.IsNullOrWhiteSpace(s)) + { + return fallback; + } + + if (bool.TryParse(s, out var v)) + { + return v; + } + + if (string.Equals(s, "1", StringComparison.OrdinalIgnoreCase) || string.Equals(s, "是", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (string.Equals(s, "0", StringComparison.OrdinalIgnoreCase) || string.Equals(s, "否", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + return fallback; + } + } + + [Serializable] + public class ParamValue + { + public string Key { get; set; } + public string Value { get; set; } + } +} diff --git a/Common/ParamCatalog.cs b/Common/ParamCatalog.cs new file mode 100644 index 0000000..68483f6 --- /dev/null +++ b/Common/ParamCatalog.cs @@ -0,0 +1,644 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace CadParamPluging.Common +{ + [Serializable] + public class ParamCatalog + { + public List Items { get; set; } + + public ParamCatalog() + { + Items = new List(); + } + + public ParamCatalog Clone() + { + return new ParamCatalog + { + Items = (Items ?? new List()).Select(x => x?.Clone()).Where(x => x != null).ToList() + }; + } + + public void Normalize() + { + if (Items == null) + { + Items = new List(); + } + + foreach (var it in Items) + { + it?.Normalize(); + } + + Items = Items + .Where(x => x != null && !string.IsNullOrWhiteSpace(x.Key)) + .GroupBy(x => x.Key, StringComparer.OrdinalIgnoreCase) + .Select(g => g.First()) + .OrderBy(x => x.Order) + .ThenBy(x => x.Key, StringComparer.OrdinalIgnoreCase) + .ToList(); + } + + public ParamDefinition FindByKey(string key) + { + if (string.IsNullOrWhiteSpace(key) || Items == null) + { + return null; + } + + return Items.FirstOrDefault(x => string.Equals(x.Key, key.Trim(), StringComparison.OrdinalIgnoreCase)); + } + + public static ParamCatalog CreateDefault() + { + return new ParamCatalog + { + Items = new List + { + // --- Material / classification --- + new ParamDefinition + { + Key = "MaterialGrade", + Label = "材料牌号", + Type = ParamValueType.String, + Required = false, + DefaultValue = "", + Hint = "数据库下拉列表(支持数据扩充),点选映射", + Order = 100, + Group = "材料" + }, + new ParamDefinition + { + Key = "FeatureCategory", + Label = "特性分类", + Type = ParamValueType.Enum, + Required = false, + DefaultValue = "一般件", + EnumOptions = new List { "一般件", "关键件", "重要件" }, + Hint = "下拉列表(一般件/关键件/重要件),点选;关键件/重要件需加方框标注", + Order = 110, + Group = "材料" + }, + new ParamDefinition + { + Key = "MaterialTechnicalCondition", + Label = "材料技术条件", + Type = ParamValueType.String, + Required = false, + DefaultValue = "", + Hint = "多选下拉列表(支持数据扩充),选项用\"或\"连接", + Order = 120, + Group = "材料" + }, + new ParamDefinition + { + Key = "ForgingTechnicalCondition", + Label = "锻件技术条件", + Type = ParamValueType.String, + Required = false, + DefaultValue = "", + Hint = "多选下拉列表(支持数据扩充),选项用\"或\"连接", + Order = 130, + Group = "材料" + }, + + // --- Inspection / heat treatment --- + new ParamDefinition + { + Key = "InspectionCategory", + Label = "检验类别", + Type = ParamValueType.Enum, + Required = false, + DefaultValue = "Ⅱ", + EnumOptions = new List { "Ⅱ", "Ⅱa", "Ⅱ大", "Ⅲ", "Ⅳ" }, + Hint = "单选下拉列表", + Order = 200, + Group = "检验/热处理" + }, + new ParamDefinition + { + Key = "ForgingCategory", + Label = "锻件类别", + Type = ParamValueType.Enum, + Required = false, + DefaultValue = "Ⅱ", + EnumOptions = new List { "Ⅱ", "Ⅲ", "Ⅳ" }, + Hint = "单选下拉列表", + Order = 210, + Group = "检验/热处理" + }, + new ParamDefinition + { + Key = "HeatTreatmentState", + Label = "热处理状态", + Type = ParamValueType.Enum, + Required = false, + DefaultValue = "预备热处理", + EnumOptions = new List { "预备热处理", "热处理" }, + Hint = "单选下拉列表", + Order = 220, + Group = "检验/热处理" + }, + new ParamDefinition + { + Key = "HeatTreatmentProcess", + Label = "热处理制度", + Type = ParamValueType.String, + Required = false, + DefaultValue = "", + Hint = "数据库下拉列表(支持数据扩充),点选", + Order = 230, + Group = "检验/热处理" + }, + new ParamDefinition + { + Key = "Hardness", + Label = "硬度", + Type = ParamValueType.String, + Required = false, + DefaultValue = "", + Hint = "组合选择:硬度符号+连接符+数值,可空", + Order = 240, + Group = "检验/热处理" + }, + new ParamDefinition + { + Key = "UltrasonicRequirement", + Label = "超声波要求", + Type = ParamValueType.String, + Required = false, + DefaultValue = "", + Hint = "手动输入,可空", + Order = 250, + Group = "检验/热处理" + }, + + // --- Marking / notes --- + new ParamDefinition + { + Key = "MarkingContent", + Label = "标刻内容", + Type = ParamValueType.String, + Required = false, + DefaultValue = "", + Hint = "多选下拉列表:零件尾号/零件号、材料牌号、熔炼炉号、锭节号、热处理炉次号、热处理炉批号、批次号", + Order = 300, + Group = "标注/说明" + }, + new ParamDefinition + { + Key = "PartsPerForging", + Label = "一锻件可做零件数", + Type = ParamValueType.Int, + Required = false, + DefaultValue = "1", + Hint = "手动编辑,默认为\"1\"", + Order = 310, + Group = "标注/说明" + }, + new ParamDefinition + { + Key = "SurfaceRoughness", + Label = "表面粗糙度", + Type = ParamValueType.Enum, + Required = false, + DefaultValue = "空", + EnumOptions = new List { "空", "0.8", "1.6", "3.2" }, + Hint = "单选下拉列表,默认为空", + Order = 320, + Group = "标注/说明" + }, + new ParamDefinition + { + Key = "DesignRevision", + Label = "设计图版次", + Type = ParamValueType.Enum, + Required = false, + DefaultValue = "空", + EnumOptions = new List { "空", "A", "B", "C", "D", "E", "F" }, + Hint = "下拉列表(A-F),可手动编辑,支持空选项", + Order = 330, + Group = "标注/说明" + }, + new ParamDefinition + { + Key = "SerialInfo", + Label = "联号信息", + Type = ParamValueType.String, + Required = false, + DefaultValue = "", + Hint = "下拉列表模板\"X个A零件和X个B零件或X个C零件\",点选后可编辑", + Order = 340, + Group = "标注/说明" + }, + new ParamDefinition + { + Key = "MachiningSpecimenRequirement", + Label = "机加试件需求", + Type = ParamValueType.String, + Required = false, + DefaultValue = "", + Hint = "下拉列表模板\"每批/每*件多投*件\",点选后可编辑", + Order = 350, + Group = "标注/说明" + }, + new ParamDefinition + { + Key = "AdditionalNotes", + Label = "辅助说明", + Type = ParamValueType.String, + Required = false, + DefaultValue = "", + Hint = "多选下拉列表:传递抗拉强度实测值、机加夹头标注、使用温度300℃以上、每批投产数量限制,点选后可编辑", + Order = 360, + Group = "标注/说明" + }, + + // --- Template key fields (also exist in panel dropdowns) --- + new ParamDefinition + { + Key = "DeliveryStatus", + Label = "交付状态", + Type = ParamValueType.Enum, + Required = false, + DefaultValue = "毛料态", + EnumOptions = new List { "毛料态", "车加工态" }, + Hint = "单选下拉列表,决定后续模板", + Order = 400, + Group = "模板参数" + }, + new ParamDefinition + { + Key = "ProcessMethod", + Label = "工艺方法", + Type = ParamValueType.Enum, + Required = false, + DefaultValue = "自由锻", + EnumOptions = new List { "自由锻", "轧制" }, + Hint = "单选下拉列表,决定后续模板", + Order = 410, + Group = "模板参数" + }, + new ParamDefinition + { + Key = "StructuralFeature", + Label = "结构特征", + Type = ParamValueType.Enum, + Required = false, + DefaultValue = "方体", + EnumOptions = new List { "方体", "饼盘", "轴杆", "环形" }, + Hint = "单选下拉列表,决定后续模板", + Order = 420, + Group = "模板参数" + }, + new ParamDefinition + { + Key = "SpecialCondition", + Label = "特殊条件", + Type = ParamValueType.Enum, + Required = false, + DefaultValue = "非中心冲孔", + EnumOptions = new List { "非中心冲孔", "中心冲孔", "无圆头", "有圆头", "圆轴", "方轴" }, + Hint = "单选下拉列表,决定后续模板", + Order = 430, + Group = "模板参数" + }, + + // --- Dimensions: ring (outer/inner/height) --- + new ParamDefinition + { + Key = "OuterDiameter1", + Label = "外径Φ1", + Type = ParamValueType.Double, + Required = false, + DefaultValue = "", + Hint = "手动编辑", + Order = 500, + Group = "尺寸-环形" + }, + new ParamDefinition + { + Key = "OuterDiameter1TolPlus", + Label = "外径上差a1", + Type = ParamValueType.Double, + Required = false, + DefaultValue = "0.5", + Hint = "车加工态:手动编辑(默认+0.5);毛料态:下拉列表(+2~+7)", + Order = 510, + Group = "尺寸-环形" + }, + new ParamDefinition + { + Key = "OuterDiameter1TolMinus", + Label = "外径下差b1", + Type = ParamValueType.Double, + Required = false, + DefaultValue = "-0.5", + Hint = "车加工态:手动编辑(默认-0.5);毛料态:下拉列表(-2~-7)", + Order = 520, + Group = "尺寸-环形" + }, + new ParamDefinition + { + Key = "InnerDiameter2", + Label = "内径Φ2", + Type = ParamValueType.Double, + Required = false, + DefaultValue = "", + Hint = "手动编辑", + Order = 530, + Group = "尺寸-环形" + }, + new ParamDefinition + { + Key = "InnerDiameter2TolPlus", + Label = "内径上差a2", + Type = ParamValueType.Double, + Required = false, + DefaultValue = "0.5", + Hint = "车加工态:手动编辑(默认+0.5);毛料态:下拉列表(+2~+7)", + Order = 540, + Group = "尺寸-环形" + }, + new ParamDefinition + { + Key = "InnerDiameter2TolMinus", + Label = "内径下差b2", + Type = ParamValueType.Double, + Required = false, + DefaultValue = "-0.5", + Hint = "车加工态:手动编辑(默认-0.5);毛料态:下拉列表(-2~-7)", + Order = 550, + Group = "尺寸-环形" + }, + new ParamDefinition + { + Key = "Height1", + Label = "高度H1", + Type = ParamValueType.Double, + Required = false, + DefaultValue = "", + Hint = "手动编辑", + Order = 560, + Group = "尺寸-环形" + }, + new ParamDefinition + { + Key = "Height1TolPlus", + Label = "高度上差a3", + Type = ParamValueType.Double, + Required = false, + DefaultValue = "0.5", + Hint = "车加工态:手动编辑(默认+0.5);毛料态:下拉列表(+2~+7)", + Order = 570, + Group = "尺寸-环形" + }, + new ParamDefinition + { + Key = "Height1TolMinus", + Label = "高度下差b3", + Type = ParamValueType.Double, + Required = false, + DefaultValue = "-0.5", + Hint = "车加工态:手动编辑(默认-0.5);毛料态:下拉列表(-2~-7)", + Order = 580, + Group = "尺寸-环形" + }, + new ParamDefinition + { + Key = "MinWallThickness", + Label = "最小壁厚T", + Type = ParamValueType.Double, + Required = false, + DefaultValue = "", + Hint = "自动计算:T=(Φ1-b1+Φ2-a2)/2", + Order = 590, + Group = "尺寸-环形" + }, + new ParamDefinition + { + Key = "UnspecifiedFilletRadiusMax", + Label = "未注圆角半径R≤", + Type = ParamValueType.Enum, + Required = false, + DefaultValue = "", + EnumOptions = new List { "6", "5", "4" }, + Hint = "下拉列表(6/5/4),可手动编辑", + Order = 600, + Group = "尺寸-环形" + }, + new ParamDefinition + { + Key = "InnerRadiusMax", + Label = "内径半径R≤", + Type = ParamValueType.Enum, + Required = false, + DefaultValue = "", + EnumOptions = new List { "8", "10" }, + Hint = "仅中心冲孔结构:下拉列表(8/10),可手动编辑", + Order = 610, + Group = "尺寸-环形" + }, + new ParamDefinition + { + Key = "RoundHeadFilletRadiusMax", + Label = "圆头处圆角半径≤", + Type = ParamValueType.Enum, + Required = false, + DefaultValue = "", + EnumOptions = new List { "8", "10" }, + Hint = "仅方体有圆头结构:下拉列表(8/10),可手动编辑", + Order = 620, + Group = "尺寸-环形" + }, + new ParamDefinition + { + Key = "PartDimensionsPrime", + Label = "零件尺寸(Φ'1/Φ'2/H'1等)", + Type = ParamValueType.String, + Required = false, + DefaultValue = "", + Hint = "手动编辑", + Order = 630, + Group = "尺寸-环形" + }, + + // --- Dimensions: shaft/rod (diameter/length) --- + new ParamDefinition + { + Key = "Diameter", + Label = "直径Φ", + Type = ParamValueType.Double, + Required = false, + DefaultValue = "", + Hint = "手动编辑", + Order = 700, + Group = "尺寸-轴杆" + }, + new ParamDefinition + { + Key = "DiameterTolPlus", + Label = "直径Φ上差a1", + Type = ParamValueType.Double, + Required = false, + DefaultValue = "", + Hint = "下拉列表(+2~+7),可手动编辑", + Order = 710, + Group = "尺寸-轴杆" + }, + new ParamDefinition + { + Key = "DiameterTolMinus", + Label = "直径Φ下差b1", + Type = ParamValueType.Double, + Required = false, + DefaultValue = "", + Hint = "下拉列表(-2~-7),可手动编辑", + Order = 720, + Group = "尺寸-轴杆" + }, + new ParamDefinition + { + Key = "Length", + Label = "长度L", + Type = ParamValueType.Double, + Required = false, + DefaultValue = "", + Hint = "手动编辑", + Order = 730, + Group = "尺寸-轴杆" + }, + new ParamDefinition + { + Key = "LengthTolPlus", + Label = "长度L上差a2", + Type = ParamValueType.Double, + Required = false, + DefaultValue = "", + Hint = "下拉列表(+2~+7),可手动编辑", + Order = 740, + Group = "尺寸-轴杆" + }, + new ParamDefinition + { + Key = "LengthTolMinus", + Label = "长度L下差b2", + Type = ParamValueType.Double, + Required = false, + DefaultValue = "", + Hint = "下拉列表(-2~-7),可手动编辑", + Order = 750, + Group = "尺寸-轴杆" + }, + + // --- Dimensions: generic multi-size --- + new ParamDefinition + { + Key = "Size123", + Label = "尺寸1/尺寸2/尺寸3", + Type = ParamValueType.String, + Required = false, + DefaultValue = "", + Hint = "手动编辑", + Order = 800, + Group = "尺寸-通用" + }, + new ParamDefinition + { + Key = "SizeTolPlusA123", + Label = "尺寸上差a1/a2/a3", + Type = ParamValueType.String, + Required = false, + DefaultValue = "", + Hint = "下拉列表(+2~+7),可手动编辑", + Order = 810, + Group = "尺寸-通用" + }, + new ParamDefinition + { + Key = "SizeTolMinusB123", + Label = "尺寸下差b1/b2/b3", + Type = ParamValueType.String, + Required = false, + DefaultValue = "", + Hint = "下拉列表(-2~-7),可手动编辑", + Order = 820, + Group = "尺寸-通用" + }, + + // --- Dimensions: disk (饼盘) --- + new ParamDefinition + { + Key = "DiskDiameter", + Label = "直径Φ(饼盘)", + Type = ParamValueType.Double, + Required = false, + DefaultValue = "", + Hint = "手动编辑", + Order = 900, + Group = "尺寸-饼盘" + }, + new ParamDefinition + { + Key = "DiskDiameterTolPlus", + Label = "直径Φ上差a1(饼盘)", + Type = ParamValueType.Double, + Required = false, + DefaultValue = "", + Hint = "下拉列表(+2~+7),可手动编辑", + Order = 910, + Group = "尺寸-饼盘" + }, + new ParamDefinition + { + Key = "DiskDiameterTolMinus", + Label = "直径Φ下差b1(饼盘)", + Type = ParamValueType.Double, + Required = false, + DefaultValue = "", + Hint = "下拉列表(-2~-7),可手动编辑", + Order = 920, + Group = "尺寸-饼盘" + }, + new ParamDefinition + { + Key = "DiskHeight", + Label = "高度H(饼盘)", + Type = ParamValueType.Double, + Required = false, + DefaultValue = "", + Hint = "手动编辑", + Order = 930, + Group = "尺寸-饼盘" + }, + new ParamDefinition + { + Key = "DiskHeightTolPlus", + Label = "高度H上差a2(饼盘)", + Type = ParamValueType.Double, + Required = false, + DefaultValue = "", + Hint = "下拉列表(+2~+7),可手动编辑", + Order = 940, + Group = "尺寸-饼盘" + }, + new ParamDefinition + { + Key = "DiskHeightTolMinus", + Label = "高度H下差b2(饼盘)", + Type = ParamValueType.Double, + Required = false, + DefaultValue = "", + Hint = "下拉列表(-2~-7),可手动编辑", + Order = 950, + Group = "尺寸-饼盘" + } + } + }; + } + } +} diff --git a/Common/ParamCatalogStore.cs b/Common/ParamCatalogStore.cs new file mode 100644 index 0000000..82d8508 --- /dev/null +++ b/Common/ParamCatalogStore.cs @@ -0,0 +1,118 @@ +using System; +using System.IO; +using System.Xml.Serialization; + +namespace CadParamPluging.Common +{ + public static class ParamCatalogStore + { + private const string FolderName = "CadParamPluging"; + private const string FileName = "param-catalog.xml"; + + public static string GetFilePath() + { + var dir = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + FolderName); + return Path.Combine(dir, FileName); + } + + public static ParamCatalog Load() + { + try + { + var defaults = ParamCatalog.CreateDefault(); + defaults.Normalize(); + + var path = GetFilePath(); + if (!File.Exists(path)) + { + return defaults; + } + + using (var stream = File.OpenRead(path)) + { + var serializer = new XmlSerializer(typeof(ParamCatalog)); + var obj = serializer.Deserialize(stream) as ParamCatalog; + if (obj == null) + { + return defaults; + } + + obj.Normalize(); + + // Merge: keep local modifications, but add any missing keys from built-in defaults. + foreach (var def in defaults.Items) + { + if (obj.FindByKey(def.Key) == null) + { + obj.Items.Add(def.Clone()); + continue; + } + + var existing = obj.FindByKey(def.Key); + if (existing != null) + { + if (string.IsNullOrWhiteSpace(existing.Label) && !string.IsNullOrWhiteSpace(def.Label)) + { + existing.Label = def.Label; + } + + if (string.IsNullOrWhiteSpace(existing.Group) && !string.IsNullOrWhiteSpace(def.Group)) + { + existing.Group = def.Group; + } + } + } + + // Migration: remove legacy demo parameters if they exist. + obj.Items = (obj.Items ?? new System.Collections.Generic.List()) + .Where(x => x != null + && !string.Equals(x.Key, "Width", StringComparison.OrdinalIgnoreCase) + && !string.Equals(x.Key, "Height", StringComparison.OrdinalIgnoreCase)) + .ToList(); + + obj.Normalize(); + return obj; + } + } + catch + { + var defaults = ParamCatalog.CreateDefault(); + defaults.Normalize(); + return defaults; + } + } + + public static void Save(ParamCatalog catalog) + { + if (catalog == null) + { + throw new ArgumentNullException(nameof(catalog)); + } + + catalog.Normalize(); + + var path = GetFilePath(); + var dir = Path.GetDirectoryName(path); + if (!string.IsNullOrEmpty(dir)) + { + Directory.CreateDirectory(dir); + } + + var tempPath = path + ".tmp"; + var serializer = new XmlSerializer(typeof(ParamCatalog)); + using (var stream = File.Create(tempPath)) + { + serializer.Serialize(stream, catalog); + } + + if (File.Exists(path)) + { + File.Delete(path); + } + + File.Move(tempPath, path); + } + } +} diff --git a/Common/ParamDefinition.cs b/Common/ParamDefinition.cs new file mode 100644 index 0000000..a7af971 --- /dev/null +++ b/Common/ParamDefinition.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace CadParamPluging.Common +{ + [Serializable] + public class ParamDefinition + { + public string Key { get; set; } + public string Label { get; set; } + public ParamValueType Type { get; set; } + public bool Required { get; set; } + + /// + /// Free-form note, typically the original "填写方式". + /// + public string Hint { get; set; } + + /// + /// Stored as string for XML friendliness. + /// + public string DefaultValue { get; set; } + + /// + /// Numeric constraints stored as string for XML friendliness. + /// + public string Min { get; set; } + public string Max { get; set; } + + public List EnumOptions { get; set; } + + public string Group { get; set; } + public int Order { get; set; } + + public ParamDefinition() + { + EnumOptions = new List(); + } + + public ParamDefinition Clone() + { + return new ParamDefinition + { + Key = Key, + Label = Label, + Type = Type, + Required = Required, + Hint = Hint, + DefaultValue = DefaultValue, + Min = Min, + Max = Max, + EnumOptions = (EnumOptions ?? new List()).ToList(), + Group = Group, + Order = Order + }; + } + + public void Normalize() + { + Key = (Key ?? string.Empty).Trim(); + Label = (Label ?? string.Empty).Trim(); + Hint = (Hint ?? string.Empty).Trim(); + DefaultValue = (DefaultValue ?? string.Empty).Trim(); + Min = (Min ?? string.Empty).Trim(); + Max = (Max ?? string.Empty).Trim(); + Group = (Group ?? string.Empty).Trim(); + + if (EnumOptions != null) + { + EnumOptions = EnumOptions + .Where(s => !string.IsNullOrWhiteSpace(s)) + .Select(s => s.Trim()) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + } + else + { + EnumOptions = new List(); + } + } + } +} diff --git a/Common/ParamValueType.cs b/Common/ParamValueType.cs new file mode 100644 index 0000000..cb0dc35 --- /dev/null +++ b/Common/ParamValueType.cs @@ -0,0 +1,11 @@ +namespace CadParamPluging.Common +{ + public enum ParamValueType + { + String = 0, + Int = 1, + Double = 2, + Bool = 3, + Enum = 4 + } +} diff --git a/Common/TemplateKeyBuilder.cs b/Common/TemplateKeyBuilder.cs new file mode 100644 index 0000000..b92ff6d --- /dev/null +++ b/Common/TemplateKeyBuilder.cs @@ -0,0 +1,55 @@ +using System; +using System.Linq; +using CadParamPluging.Domain.Models; + +namespace CadParamPluging.Common +{ + public static class TemplateKeyBuilder + { + public static string Build(TemplateParams tpl) + { + if (tpl == null) + { + return null; + } + + return Build(tpl.ProjectType, tpl.DrawingType, tpl.SheetSize, tpl.Scale); + } + + public static string Build(string projectType, string drawingType, string sheetSize, string scale) + { + var p1 = NormalizeValue(projectType); + var p2 = NormalizeValue(drawingType); + var p3 = NormalizeValue(sheetSize); + var p4 = NormalizeScale(scale); + return string.Join("|", new[] { p1, p2, p3, p4 }); + } + + private static string NormalizeScale(string s) + { + if (string.IsNullOrWhiteSpace(s)) + { + return string.Empty; + } + + if (string.Equals(s.Trim(), "无", StringComparison.OrdinalIgnoreCase)) + { + return string.Empty; + } + + return NormalizeValue(s); + } + + private static string NormalizeValue(string s) + { + if (string.IsNullOrWhiteSpace(s)) + { + return string.Empty; + } + + var trimmed = s.Trim(); + var chars = trimmed.Where(c => !char.IsWhiteSpace(c)).ToArray(); + return new string(chars); + } + } +} diff --git a/Common/TemplateSchemaDefinition.cs b/Common/TemplateSchemaDefinition.cs new file mode 100644 index 0000000..9ccd2a1 --- /dev/null +++ b/Common/TemplateSchemaDefinition.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace CadParamPluging.Common +{ + [Serializable] + public class TemplateSchemaDefinition + { + public string TemplateKey { get; set; } + + public string ProjectType { get; set; } + public string DrawingType { get; set; } + public string SheetSize { get; set; } + public string Scale { get; set; } + + public string DisplayName { get; set; } + + public List SelectedParamKeys { get; set; } + + public TemplateSchemaDefinition() + { + SelectedParamKeys = new List(); + } + + public void Normalize() + { + ProjectType = (ProjectType ?? string.Empty).Trim(); + DrawingType = (DrawingType ?? string.Empty).Trim(); + SheetSize = (SheetSize ?? string.Empty).Trim(); + Scale = (Scale ?? string.Empty).Trim(); + DisplayName = (DisplayName ?? string.Empty).Trim(); + + TemplateKey = TemplateKeyBuilder.Build(ProjectType, DrawingType, SheetSize, Scale); + + if (SelectedParamKeys == null) + { + SelectedParamKeys = new List(); + } + + SelectedParamKeys = SelectedParamKeys + .Where(s => !string.IsNullOrWhiteSpace(s)) + .Select(s => s.Trim()) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + } + } +} diff --git a/Common/TemplateSchemaStore.cs b/Common/TemplateSchemaStore.cs new file mode 100644 index 0000000..4ba8973 --- /dev/null +++ b/Common/TemplateSchemaStore.cs @@ -0,0 +1,89 @@ +using System; +using System.IO; +using System.Linq; +using System.Xml.Serialization; + +namespace CadParamPluging.Common +{ + public static class TemplateSchemaStore + { + private const string FolderName = "CadParamPluging"; + private const string FileName = "template-schemas.xml"; + + public static string GetFilePath() + { + var dir = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + FolderName); + return Path.Combine(dir, FileName); + } + + public static TemplateSchemas Load() + { + try + { + var path = GetFilePath(); + if (!File.Exists(path)) + { + return new TemplateSchemas(); + } + + using (var stream = File.OpenRead(path)) + { + var serializer = new XmlSerializer(typeof(TemplateSchemas)); + var obj = serializer.Deserialize(stream) as TemplateSchemas; + if (obj == null) + { + return new TemplateSchemas(); + } + + obj.Normalize(); + if (obj.Items != null) + { + obj.Items = obj.Items + .Where(x => x != null && !string.IsNullOrWhiteSpace(x.TemplateKey)) + .GroupBy(x => x.TemplateKey, StringComparer.OrdinalIgnoreCase) + .Select(g => g.First()) + .ToList(); + } + return obj; + } + } + catch + { + return new TemplateSchemas(); + } + } + + public static void Save(TemplateSchemas schemas) + { + if (schemas == null) + { + throw new ArgumentNullException(nameof(schemas)); + } + + schemas.Normalize(); + + var path = GetFilePath(); + var dir = Path.GetDirectoryName(path); + if (!string.IsNullOrEmpty(dir)) + { + Directory.CreateDirectory(dir); + } + + var tempPath = path + ".tmp"; + var serializer = new XmlSerializer(typeof(TemplateSchemas)); + using (var stream = File.Create(tempPath)) + { + serializer.Serialize(stream, schemas); + } + + if (File.Exists(path)) + { + File.Delete(path); + } + + File.Move(tempPath, path); + } + } +} diff --git a/Common/TemplateSchemas.cs b/Common/TemplateSchemas.cs new file mode 100644 index 0000000..5ac4a03 --- /dev/null +++ b/Common/TemplateSchemas.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; + +namespace CadParamPluging.Common +{ + [Serializable] + public class TemplateSchemas + { + public List Items { get; set; } + + public TemplateSchemas() + { + Items = new List(); + } + + public void Normalize() + { + if (Items == null) + { + Items = new List(); + } + + foreach (var it in Items) + { + it?.Normalize(); + } + } + } +} diff --git a/Domain/DomainFacade.cs b/Domain/DomainFacade.cs index 91c50a9..12777b2 100644 --- a/Domain/DomainFacade.cs +++ b/Domain/DomainFacade.cs @@ -41,10 +41,7 @@ namespace CadParamPluging.Domain throw new BusinessException("请填写项目类型和图纸类型。"); } - if (drawingParams == null || !drawingParams.HasValidSize()) - { - throw new BusinessException("请填写有效的出图尺寸(宽度和高度需大于 0)。"); - } + // 出图参数由不同模板决定,后续由模板对应的生成器做更精细的校验。 } public static void DrawByParams(TemplateInfo template, DrawingParams drawingParams, CadDrawingService cadService) diff --git a/UI/DrawingParamsForm.cs b/UI/DrawingParamsForm.cs new file mode 100644 index 0000000..1674f6b --- /dev/null +++ b/UI/DrawingParamsForm.cs @@ -0,0 +1,301 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Globalization; +using System.Linq; +using System.Windows.Forms; +using CadParamPluging.Common; + +namespace CadParamPluging.UI +{ + public class DrawingParamsForm : Form + { + private readonly TableLayoutPanel _grid; + private readonly Dictionary _controlsByKey; + private readonly ParamCatalog _catalog; + private readonly TemplateSchemaDefinition _schema; + private readonly ToolTip _toolTip; + + public ParamBag Result { get; private set; } + + public DrawingParamsForm(ParamCatalog catalog, TemplateSchemaDefinition schema) + { + _catalog = (catalog ?? ParamCatalog.CreateDefault()).Clone(); + _catalog.Normalize(); + _schema = schema; + + Text = "填写参数"; + Size = new Size(560, 620); + StartPosition = FormStartPosition.CenterParent; + FormBorderStyle = FormBorderStyle.FixedDialog; + MaximizeBox = false; + MinimizeBox = false; + ShowInTaskbar = false; + + _controlsByKey = new Dictionary(StringComparer.OrdinalIgnoreCase); + _toolTip = new ToolTip(); + + var root = new TableLayoutPanel + { + Dock = DockStyle.Fill, + Padding = new Padding(10), + ColumnCount = 1, + RowCount = 3 + }; + root.RowStyles.Add(new RowStyle(SizeType.AutoSize)); + root.RowStyles.Add(new RowStyle(SizeType.Percent, 100f)); + root.RowStyles.Add(new RowStyle(SizeType.AutoSize)); + + var header = new Label + { + AutoSize = true, + ForeColor = Color.DarkBlue, + Text = BuildHeaderText(schema) + }; + + var scrollPanel = new Panel + { + Dock = DockStyle.Fill, + AutoScroll = true + }; + + _grid = new TableLayoutPanel + { + Dock = DockStyle.Top, + AutoSize = true, + ColumnCount = 2, + RowCount = 1, + Padding = new Padding(0) + }; + _grid.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize)); + _grid.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100f)); + + BuildFields(); + + scrollPanel.Controls.Add(_grid); + + var buttons = new FlowLayoutPanel + { + Dock = DockStyle.Fill, + FlowDirection = FlowDirection.RightToLeft, + AutoSize = true + }; + var btnCancel = new Button { Text = "取消", DialogResult = DialogResult.Cancel }; + var btnOk = new Button { Text = "确定" }; + btnOk.Click += (_, __) => OnOk(); + buttons.Controls.Add(btnCancel); + buttons.Controls.Add(btnOk); + + root.Controls.Add(header, 0, 0); + root.Controls.Add(scrollPanel, 0, 1); + root.Controls.Add(buttons, 0, 2); + + Controls.Add(root); + AcceptButton = btnOk; + CancelButton = btnCancel; + } + + private static string BuildHeaderText(TemplateSchemaDefinition schema) + { + if (schema == null) + { + return "模板参数未配置。"; + } + + var name = string.IsNullOrWhiteSpace(schema.DisplayName) ? "(未命名模板)" : schema.DisplayName; + return $"模板: {name} | {schema.ProjectType}/{schema.DrawingType}/{schema.SheetSize}/{schema.Scale}"; + } + + private void BuildFields() + { + var keys = (_schema?.SelectedParamKeys ?? new List()).ToList(); + if (keys.Count == 0) + { + var rowIdx = _grid.RowCount++; + _grid.RowStyles.Add(new RowStyle(SizeType.AutoSize)); + _grid.Controls.Add(new Label { AutoSize = true, Text = "(该模板未绑定任何参数)" }, 0, rowIdx); + _grid.SetColumnSpan(_grid.Controls[_grid.Controls.Count - 1], 2); + return; + } + + foreach (var key in keys) + { + var def = _catalog.FindByKey(key); + if (def == null) + { + continue; + } + + AddField(def); + } + } + + private void AddField(ParamDefinition def) + { + var rowIdx = _grid.RowCount++; + _grid.RowStyles.Add(new RowStyle(SizeType.AutoSize)); + + var labelText = def.Label; + if (def.Required) + { + labelText += " *"; + } + + var lbl = new Label { AutoSize = true, Text = labelText, TextAlign = ContentAlignment.MiddleLeft, Padding = new Padding(0, 6, 8, 6) }; + var ctrl = CreateControl(def); + ctrl.Width = 260; + + if (!string.IsNullOrWhiteSpace(def.Hint)) + { + _toolTip.SetToolTip(lbl, def.Hint); + _toolTip.SetToolTip(ctrl, def.Hint); + } + + _grid.Controls.Add(lbl, 0, rowIdx); + _grid.Controls.Add(ctrl, 1, rowIdx); + _controlsByKey[def.Key] = ctrl; + } + + private Control CreateControl(ParamDefinition def) + { + switch (def.Type) + { + case ParamValueType.Bool: + return new CheckBox { Checked = ParseBool(def.DefaultValue), AutoSize = true }; + case ParamValueType.Enum: + return CreateEnumCombo(def); + case ParamValueType.Int: + return CreateNumber(def, isInt: true); + case ParamValueType.Double: + return CreateNumber(def, isInt: false); + default: + return new TextBox { Text = def.DefaultValue ?? string.Empty }; + } + } + + private static bool ParseBool(string s) + { + if (string.IsNullOrWhiteSpace(s)) + { + return false; + } + if (bool.TryParse(s, out var v)) + { + return v; + } + return string.Equals(s.Trim(), "1", StringComparison.OrdinalIgnoreCase) + || string.Equals(s.Trim(), "是", StringComparison.OrdinalIgnoreCase); + } + + private ComboBox CreateEnumCombo(ParamDefinition def) + { + // Allow manual edit to match many "下拉列表...可手动编辑" cases. + var cb = new ComboBox { DropDownStyle = ComboBoxStyle.DropDown }; + var arr = (def.EnumOptions ?? new List()).Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => x.Trim()).ToArray(); + if (arr.Length > 0) + { + cb.Items.AddRange(arr); + var idx = Array.FindIndex(arr, x => string.Equals(x, def.DefaultValue, StringComparison.OrdinalIgnoreCase)); + cb.SelectedIndex = idx >= 0 ? idx : 0; + } + if (!string.IsNullOrWhiteSpace(def.DefaultValue)) + { + cb.Text = def.DefaultValue; + } + return cb; + } + + private Control CreateNumber(ParamDefinition def, bool isInt) + { + var num = new NumericUpDown + { + DecimalPlaces = isInt ? 0 : 3, + Minimum = -1000000000, + Maximum = 1000000000, + ThousandsSeparator = true + }; + + if (TryParseDecimal(def.Min, out var min)) + { + num.Minimum = min; + } + if (TryParseDecimal(def.Max, out var max)) + { + num.Maximum = max; + } + if (TryParseDecimal(def.DefaultValue, out var dv)) + { + if (dv < num.Minimum) dv = num.Minimum; + if (dv > num.Maximum) dv = num.Maximum; + num.Value = dv; + } + return num; + } + + private static bool TryParseDecimal(string s, out decimal value) + { + value = 0; + if (string.IsNullOrWhiteSpace(s)) + { + return false; + } + if (decimal.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out value)) + { + return true; + } + return decimal.TryParse(s, NumberStyles.Float, CultureInfo.CurrentCulture, out value); + } + + private void OnOk() + { + var bag = new ParamBag(); + foreach (var key in _controlsByKey.Keys.ToList()) + { + if (!_controlsByKey.TryGetValue(key, out var ctrl) || ctrl == null) + { + continue; + } + + var def = _catalog.FindByKey(key); + if (def == null) + { + continue; + } + + var value = ReadValue(ctrl, def); + if (def.Required && string.IsNullOrWhiteSpace(value)) + { + MessageBox.Show(this, $"请填写:{def.Label} ({def.Key})", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); + ctrl.Focus(); + return; + } + + bag.Set(def.Key, value); + } + + Result = bag; + DialogResult = DialogResult.OK; + Close(); + } + + private static string ReadValue(Control ctrl, ParamDefinition def) + { + switch (def.Type) + { + case ParamValueType.Bool: + return (ctrl as CheckBox)?.Checked == true ? "true" : "false"; + case ParamValueType.Enum: + return ((ctrl as ComboBox)?.Text ?? string.Empty).Trim(); + case ParamValueType.Int: + case ParamValueType.Double: + if (ctrl is NumericUpDown num) + { + return num.Value.ToString(CultureInfo.InvariantCulture); + } + return string.Empty; + default: + return ((ctrl as TextBox)?.Text ?? string.Empty).Trim(); + } + } + } +} diff --git a/UI/ParamCatalogForm.cs b/UI/ParamCatalogForm.cs new file mode 100644 index 0000000..417e0d8 --- /dev/null +++ b/UI/ParamCatalogForm.cs @@ -0,0 +1,222 @@ +using System; +using System.Drawing; +using System.Linq; +using System.Windows.Forms; +using CadParamPluging.Common; + +namespace CadParamPluging.UI +{ + public class ParamCatalogForm : Form + { + private readonly ListBox _list; + private ParamCatalog _catalog; + + public ParamCatalogForm(ParamCatalog initial) + { + Text = "参数总表"; + Size = new Size(640, 520); + StartPosition = FormStartPosition.CenterParent; + FormBorderStyle = FormBorderStyle.FixedDialog; + MaximizeBox = false; + MinimizeBox = false; + ShowInTaskbar = false; + + _catalog = (initial ?? ParamCatalog.CreateDefault()).Clone(); + _catalog.Normalize(); + + var layout = new TableLayoutPanel + { + Dock = DockStyle.Fill, + Padding = new Padding(10), + ColumnCount = 2, + RowCount = 2 + }; + layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100f)); + layout.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize)); + layout.RowStyles.Add(new RowStyle(SizeType.Percent, 100f)); + layout.RowStyles.Add(new RowStyle(SizeType.AutoSize)); + + _list = new ListBox { Dock = DockStyle.Fill }; + ReloadList(); + + var actions = new FlowLayoutPanel + { + Dock = DockStyle.Fill, + FlowDirection = FlowDirection.TopDown, + WrapContents = false, + AutoSize = true + }; + + var btnAdd = new Button { Text = "新增", AutoSize = true }; + btnAdd.Click += (_, __) => OnAdd(); + var btnEdit = new Button { Text = "修改", AutoSize = true }; + btnEdit.Click += (_, __) => OnEdit(); + var btnDelete = new Button { Text = "删除", AutoSize = true }; + btnDelete.Click += (_, __) => OnDelete(); + + actions.Controls.Add(btnAdd); + actions.Controls.Add(btnEdit); + actions.Controls.Add(btnDelete); + + var btnPanel = new FlowLayoutPanel + { + Dock = DockStyle.Fill, + FlowDirection = FlowDirection.RightToLeft, + AutoSize = true + }; + var btnClose = new Button { Text = "关闭", DialogResult = DialogResult.Cancel }; + var btnSave = new Button { Text = "保存" }; + btnSave.Click += (_, __) => OnSave(); + btnPanel.Controls.Add(btnClose); + btnPanel.Controls.Add(btnSave); + + layout.Controls.Add(_list, 0, 0); + layout.Controls.Add(actions, 1, 0); + layout.Controls.Add(btnPanel, 0, 1); + layout.SetColumnSpan(btnPanel, 2); + + Controls.Add(layout); + CancelButton = btnClose; + } + + private void ReloadList() + { + _list.Items.Clear(); + foreach (var it in (_catalog.Items ?? Enumerable.Empty()).OrderBy(x => x.Order)) + { + _list.Items.Add(Format(it)); + } + if (_list.Items.Count > 0) + { + _list.SelectedIndex = 0; + } + } + + private static string Format(ParamDefinition def) + { + if (def == null) + { + return "(null)"; + } + var label = string.IsNullOrWhiteSpace(def.Label) ? def.Key : def.Label; + return $"{label} ({def.Key}) [{def.Type}]"; + } + + private ParamDefinition GetSelected() + { + var idx = _list.SelectedIndex; + if (idx < 0 || _catalog.Items == null) + { + return null; + } + + var ordered = _catalog.Items.OrderBy(x => x.Order).ThenBy(x => x.Key, StringComparer.OrdinalIgnoreCase).ToList(); + if (idx >= ordered.Count) + { + return null; + } + + var selKey = ordered[idx].Key; + return _catalog.FindByKey(selKey); + } + + private void OnAdd() + { + using (var f = new ParamDefinitionForm("新增参数", new ParamDefinition { Type = ParamValueType.String }, isEdit: false)) + { + if (f.ShowDialog(this) != DialogResult.OK) + { + return; + } + + var def = f.Result; + if (_catalog.FindByKey(def.Key) != null) + { + MessageBox.Show(this, $"Key 已存在:{def.Key}", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + _catalog.Items.Add(def); + _catalog.Normalize(); + ReloadList(); + } + } + + private void OnEdit() + { + var selected = GetSelected(); + if (selected == null) + { + MessageBox.Show(this, "请先选择一项再修改。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + + using (var f = new ParamDefinitionForm("修改参数", selected, isEdit: true)) + { + if (f.ShowDialog(this) != DialogResult.OK) + { + return; + } + + var updated = f.Result; + var existing = _catalog.FindByKey(updated.Key); + if (existing == null) + { + _catalog.Items.Add(updated); + } + else + { + existing.Label = updated.Label; + existing.Type = updated.Type; + existing.Required = updated.Required; + existing.Hint = updated.Hint; + existing.DefaultValue = updated.DefaultValue; + existing.Min = updated.Min; + existing.Max = updated.Max; + existing.EnumOptions = updated.EnumOptions; + existing.Group = updated.Group; + existing.Order = updated.Order; + existing.Normalize(); + } + + _catalog.Normalize(); + ReloadList(); + } + } + + private void OnDelete() + { + var selected = GetSelected(); + if (selected == null) + { + MessageBox.Show(this, "请先选择一项再删除。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + + var confirm = MessageBox.Show(this, $"确认删除参数:{selected.Label} ({selected.Key})?", "删除", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); + if (confirm != DialogResult.Yes) + { + return; + } + + _catalog.Items = (_catalog.Items ?? Enumerable.Empty()) + .Where(x => !string.Equals(x.Key, selected.Key, StringComparison.OrdinalIgnoreCase)) + .ToList(); + _catalog.Normalize(); + ReloadList(); + } + + private void OnSave() + { + try + { + ParamCatalogStore.Save(_catalog); + MessageBox.Show(this, "已保存。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + catch (Exception ex) + { + MessageBox.Show(this, $"保存失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + } +} diff --git a/UI/ParamDefinitionForm.cs b/UI/ParamDefinitionForm.cs new file mode 100644 index 0000000..337e5eb --- /dev/null +++ b/UI/ParamDefinitionForm.cs @@ -0,0 +1,297 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Globalization; +using System.Linq; +using System.Windows.Forms; +using CadParamPluging.Common; + +namespace CadParamPluging.UI +{ + public class ParamDefinitionForm : Form + { + private readonly bool _isEdit; + private readonly TextBox _txtKey; + private readonly TextBox _txtLabel; + private readonly ComboBox _cbType; + private readonly CheckBox _chkRequired; + private readonly TextBox _txtHint; + private readonly TextBox _txtDefault; + private readonly TextBox _txtMin; + private readonly TextBox _txtMax; + private readonly TextBox _txtEnumOptions; + private readonly TextBox _txtGroup; + private readonly NumericUpDown _numOrder; + + public ParamDefinition Result { get; private set; } + + public ParamDefinitionForm(string title, ParamDefinition initial, bool isEdit) + { + _isEdit = isEdit; + + Text = title; + Size = new Size(520, 520); + StartPosition = FormStartPosition.CenterParent; + FormBorderStyle = FormBorderStyle.FixedDialog; + MaximizeBox = false; + MinimizeBox = false; + ShowInTaskbar = false; + + var def = (initial ?? new ParamDefinition()).Clone(); + def.Normalize(); + + var layout = new TableLayoutPanel + { + Dock = DockStyle.Fill, + Padding = new Padding(10), + ColumnCount = 2, + RowCount = 12, + AutoSize = true + }; + layout.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize)); + layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100f)); + + _txtKey = new TextBox { Width = 260, Text = def.Key }; + _txtLabel = new TextBox { Width = 260, Text = def.Label }; + _cbType = new ComboBox { DropDownStyle = ComboBoxStyle.DropDownList, Width = 260 }; + _cbType.Items.AddRange(Enum.GetNames(typeof(ParamValueType))); + _cbType.SelectedItem = def.Type.ToString(); + _cbType.SelectedIndexChanged += (_, __) => UpdateTypeUi(); + + _chkRequired = new CheckBox { Text = "必填", Checked = def.Required, AutoSize = true }; + _txtHint = new TextBox { Width = 260, Text = def.Hint, Multiline = true, Height = 60, ScrollBars = ScrollBars.Vertical }; + _txtDefault = new TextBox { Width = 260, Text = def.DefaultValue }; + _txtMin = new TextBox { Width = 260, Text = def.Min }; + _txtMax = new TextBox { Width = 260, Text = def.Max }; + _txtEnumOptions = new TextBox { Width = 260, Height = 120, Multiline = true, ScrollBars = ScrollBars.Vertical }; + _txtEnumOptions.Text = string.Join(Environment.NewLine, def.EnumOptions ?? new List()); + _txtGroup = new TextBox { Width = 260, Text = def.Group }; + _numOrder = new NumericUpDown { Width = 120, Minimum = -100000, Maximum = 100000, Value = def.Order }; + + if (_isEdit) + { + _txtKey.ReadOnly = true; + } + + var row = 0; + layout.Controls.Add(new Label { Text = "Key(英文)", AutoSize = true, TextAlign = ContentAlignment.MiddleLeft }, 0, row); + layout.Controls.Add(_txtKey, 1, row++); + + layout.Controls.Add(new Label { Text = "Label(中文)", AutoSize = true, TextAlign = ContentAlignment.MiddleLeft }, 0, row); + layout.Controls.Add(_txtLabel, 1, row++); + + layout.Controls.Add(new Label { Text = "类型", AutoSize = true, TextAlign = ContentAlignment.MiddleLeft }, 0, row); + layout.Controls.Add(_cbType, 1, row++); + + layout.Controls.Add(new Label { Text = "", AutoSize = true }, 0, row); + layout.Controls.Add(_chkRequired, 1, row++); + + layout.Controls.Add(new Label { Text = "提示/填写方式", AutoSize = true, TextAlign = ContentAlignment.MiddleLeft }, 0, row); + layout.Controls.Add(_txtHint, 1, row++); + + layout.Controls.Add(new Label { Text = "默认值", AutoSize = true, TextAlign = ContentAlignment.MiddleLeft }, 0, row); + layout.Controls.Add(_txtDefault, 1, row++); + + layout.Controls.Add(new Label { Text = "最小值(数字)", AutoSize = true, TextAlign = ContentAlignment.MiddleLeft }, 0, row); + layout.Controls.Add(_txtMin, 1, row++); + + layout.Controls.Add(new Label { Text = "最大值(数字)", AutoSize = true, TextAlign = ContentAlignment.MiddleLeft }, 0, row); + layout.Controls.Add(_txtMax, 1, row++); + + layout.Controls.Add(new Label { Text = "枚举选项(每行一项)", AutoSize = true, TextAlign = ContentAlignment.MiddleLeft }, 0, row); + layout.Controls.Add(_txtEnumOptions, 1, row++); + + layout.Controls.Add(new Label { Text = "分组", AutoSize = true, TextAlign = ContentAlignment.MiddleLeft }, 0, row); + layout.Controls.Add(_txtGroup, 1, row++); + + layout.Controls.Add(new Label { Text = "排序", AutoSize = true, TextAlign = ContentAlignment.MiddleLeft }, 0, row); + layout.Controls.Add(_numOrder, 1, row++); + + var btnPanel = new FlowLayoutPanel + { + Dock = DockStyle.Fill, + FlowDirection = FlowDirection.RightToLeft, + AutoSize = true + }; + var btnCancel = new Button { Text = "取消", DialogResult = DialogResult.Cancel }; + var btnOk = new Button { Text = "确定" }; + btnOk.Click += (_, __) => OnOk(); + btnPanel.Controls.Add(btnCancel); + btnPanel.Controls.Add(btnOk); + + layout.Controls.Add(btnPanel, 0, row); + layout.SetColumnSpan(btnPanel, 2); + + Controls.Add(layout); + AcceptButton = btnOk; + CancelButton = btnCancel; + + UpdateTypeUi(); + } + + private void UpdateTypeUi() + { + var type = GetSelectedType(); + var showEnum = type == ParamValueType.Enum; + _txtEnumOptions.Enabled = showEnum; + + var showMinMax = type == ParamValueType.Int || type == ParamValueType.Double; + _txtMin.Enabled = showMinMax; + _txtMax.Enabled = showMinMax; + } + + private ParamValueType GetSelectedType() + { + var selected = _cbType.SelectedItem as string; + if (Enum.TryParse(selected, out ParamValueType t)) + { + return t; + } + + return ParamValueType.String; + } + + private void OnOk() + { + var key = (_txtKey.Text ?? string.Empty).Trim(); + var label = (_txtLabel.Text ?? string.Empty).Trim(); + + if (key.Length == 0) + { + MessageBox.Show(this, "Key 不能为空。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + if (!IsValidKey(key)) + { + MessageBox.Show(this, "Key 格式不合法:必须以字母开头,仅允许字母/数字/下划线。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + if (label.Length == 0) + { + MessageBox.Show(this, "Label 不能为空。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + var type = GetSelectedType(); + + var def = new ParamDefinition + { + Key = key, + Label = label, + Type = type, + Required = _chkRequired.Checked, + Hint = (_txtHint.Text ?? string.Empty).Trim(), + DefaultValue = (_txtDefault.Text ?? string.Empty).Trim(), + Min = (_txtMin.Text ?? string.Empty).Trim(), + Max = (_txtMax.Text ?? string.Empty).Trim(), + Group = (_txtGroup.Text ?? string.Empty).Trim(), + Order = (int)_numOrder.Value, + EnumOptions = ParseLines(_txtEnumOptions.Text) + }; + def.Normalize(); + + var err = ValidateDefinition(def); + if (err != null) + { + MessageBox.Show(this, err, "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + Result = def; + DialogResult = DialogResult.OK; + Close(); + } + + private static List ParseLines(string text) + { + if (string.IsNullOrWhiteSpace(text)) + { + return new List(); + } + + return text + .Replace("\r\n", "\n") + .Replace("\r", "\n") + .Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries) + .Select(s => (s ?? string.Empty).Trim()) + .Where(s => s.Length > 0) + .ToList(); + } + + private static bool IsValidKey(string key) + { + if (string.IsNullOrWhiteSpace(key)) + { + return false; + } + + if (!char.IsLetter(key[0])) + { + return false; + } + + foreach (var ch in key) + { + if (char.IsLetterOrDigit(ch) || ch == '_') + { + continue; + } + return false; + } + + return true; + } + + private static string ValidateDefinition(ParamDefinition def) + { + if (def == null) + { + return "参数定义不能为空。"; + } + + if (def.Type == ParamValueType.Enum && (def.EnumOptions == null || def.EnumOptions.Count == 0)) + { + return "Enum 类型必须提供至少 1 个枚举选项。"; + } + + if ((def.Type == ParamValueType.Int || def.Type == ParamValueType.Double) && (def.Min.Length > 0 || def.Max.Length > 0)) + { + if (!TryParseDouble(def.Min, out var min, allowEmpty: true)) + { + return "最小值不是有效数字。"; + } + + if (!TryParseDouble(def.Max, out var max, allowEmpty: true)) + { + return "最大值不是有效数字。"; + } + + if (def.Min.Length > 0 && def.Max.Length > 0 && min > max) + { + return "最小值不能大于最大值。"; + } + } + + return null; + } + + private static bool TryParseDouble(string text, out double value, bool allowEmpty) + { + value = 0; + if (string.IsNullOrWhiteSpace(text)) + { + return allowEmpty; + } + + var s = text.Trim(); + if (double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out value)) + { + return true; + } + + return double.TryParse(s, NumberStyles.Float, CultureInfo.CurrentCulture, out value); + } + } +} diff --git a/UI/ParamDrawingPanel.cs b/UI/ParamDrawingPanel.cs index 9455725..8af9b62 100644 --- a/UI/ParamDrawingPanel.cs +++ b/UI/ParamDrawingPanel.cs @@ -20,8 +20,6 @@ namespace CadParamPluging.UI private readonly ComboBox _cbDrawingType; private readonly ComboBox _cbSheetSize; private readonly ComboBox _cbScale; - private readonly TextBox _txtWidth; - private readonly TextBox _txtHeight; private Label _lblTemplateInfo; private TextBox _txtLog; private TextBox _txtTemplatePath; @@ -53,8 +51,6 @@ namespace CadParamPluging.UI _cbDrawingType = CreateComboBox(dropdownOptions.ProcessMethods); _cbSheetSize = CreateComboBox(dropdownOptions.StructuralFeatures); _cbScale = CreateComboBox(dropdownOptions.SpecialConditions); - _txtWidth = CreateTextBox("5000"); - _txtHeight = CreateTextBox("3000"); var templateGroup = BuildTemplateGroup(); var drawingGroup = BuildDrawingGroup(); @@ -135,21 +131,14 @@ namespace CadParamPluging.UI AutoSizeMode = AutoSizeMode.GrowAndShrink }; - var grid = new TableLayoutPanel + var label = new Label { - Dock = DockStyle.Fill, - ColumnCount = 2, - RowCount = 3, - AutoSize = true + AutoSize = true, + Padding = new Padding(8), + Text = "出图参数较多,请点击【生成图纸】后在弹窗中填写。" }; - grid.Controls.Add(new Label { Text = "宽度(mm)", AutoSize = true, TextAlign = ContentAlignment.MiddleLeft }, 0, 0); - grid.Controls.Add(_txtWidth, 1, 0); - - grid.Controls.Add(new Label { Text = "高度(mm)", AutoSize = true, TextAlign = ContentAlignment.MiddleLeft }, 0, 1); - grid.Controls.Add(_txtHeight, 1, 1); - - group.Controls.Add(grid); + group.Controls.Add(label); return group; } @@ -524,7 +513,6 @@ namespace CadParamPluging.UI try { var tplParams = CollectTemplateParams(); - var drawingParams = CollectDrawingParams(); if (_selectedTemplate == null || string.IsNullOrWhiteSpace(_selectedTemplate.FilePath)) { @@ -542,6 +530,39 @@ namespace CadParamPluging.UI } } + var templateKey = TemplateKeyBuilder.Build(tplParams); + if (string.IsNullOrWhiteSpace(templateKey)) + { + AppendLog("模板参数不完整,无法生成 TemplateKey。"); + return; + } + + var schemas = TemplateSchemaStore.Load(); + var schema = (schemas?.Items ?? Enumerable.Empty()) + .FirstOrDefault(s => string.Equals(s.TemplateKey, templateKey, StringComparison.OrdinalIgnoreCase)); + if (schema == null) + { + AppendLog($"未找到该模板的参数绑定配置(TemplateKey={templateKey})。请到【设置→模板参数绑定】中新增/配置。"); + return; + } + + var catalog = ParamCatalogStore.Load(); + CadParamPluging.Common.ParamBag bag; + using (var f = new DrawingParamsForm(catalog, schema)) + { + var r = f.ShowDialog(this); + if (r != DialogResult.OK) + { + AppendLog("用户取消填写参数。"); + return; + } + bag = f.Result; + } + + // Current demo generator uses a rectangle. Size defaults are handled inside DomainFacade.DrawByParams. + // Real templates will use their own generator/parameters. + var drawingParams = new DrawingParams(); + DomainFacade.ValidateParameters(tplParams, drawingParams); var doc = TemplateDrawingService.CreateDocumentFromTemplate(_selectedTemplate); @@ -669,15 +690,6 @@ namespace CadParamPluging.UI }; } - private DrawingParams CollectDrawingParams() - { - return new DrawingParams - { - Width = ParseDouble(_txtWidth.Text), - Height = ParseDouble(_txtHeight.Text) - }; - } - private static ComboBox CreateComboBox(System.Collections.Generic.IEnumerable items) { var cb = new ComboBox @@ -725,25 +737,6 @@ namespace CadParamPluging.UI } } - private static TextBox CreateTextBox(string defaultText) - { - return new TextBox { Width = 160, Text = defaultText }; - } - - private static double ParseDouble(string text) - { - if (double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var value)) - { - return value; - } - - if (double.TryParse(text, NumberStyles.Float, CultureInfo.CurrentCulture, out value)) - { - return value; - } - - return 0; - } private void AppendLog(string message) { diff --git a/UI/SettingsForm.cs b/UI/SettingsForm.cs index 73919b2..f420978 100644 --- a/UI/SettingsForm.cs +++ b/UI/SettingsForm.cs @@ -57,8 +57,32 @@ namespace CadParamPluging.UI tabs.TabPages.Add(BuildEditableTab("结构特征", _lbStructuralFeatures)); tabs.TabPages.Add(BuildEditableTab("特殊条件", _lbSpecialConditions)); - // Bottom buttons - var flowButtons = new FlowLayoutPanel + // Bottom area + var bottom = new TableLayoutPanel + { + Dock = DockStyle.Fill, + ColumnCount = 2, + RowCount = 1 + }; + bottom.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100f)); + bottom.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize)); + + var flowLeft = new FlowLayoutPanel + { + Dock = DockStyle.Fill, + FlowDirection = FlowDirection.LeftToRight, + AutoSize = true + }; + var btnParamCatalog = new Button { Text = "参数总表...", AutoSize = true }; + btnParamCatalog.Click += (_, __) => OnOpenParamCatalog(); + + var btnTemplateBinding = new Button { Text = "模板参数绑定...", AutoSize = true }; + btnTemplateBinding.Click += (_, __) => OnOpenTemplateBinding(); + + flowLeft.Controls.Add(btnParamCatalog); + flowLeft.Controls.Add(btnTemplateBinding); + + var flowRight = new FlowLayoutPanel { Dock = DockStyle.Fill, FlowDirection = FlowDirection.RightToLeft, @@ -69,11 +93,14 @@ namespace CadParamPluging.UI var btnOk = new Button { Text = "确定" }; btnOk.Click += (_, __) => OnOk(); - flowButtons.Controls.Add(btnCancel); - flowButtons.Controls.Add(btnOk); + flowRight.Controls.Add(btnCancel); + flowRight.Controls.Add(btnOk); + + bottom.Controls.Add(flowLeft, 0, 0); + bottom.Controls.Add(flowRight, 1, 0); layout.Controls.Add(tabs, 0, 0); - layout.Controls.Add(flowButtons, 0, 1); + layout.Controls.Add(bottom, 0, 1); Controls.Add(layout); @@ -81,6 +108,44 @@ namespace CadParamPluging.UI CancelButton = btnCancel; } + private void OnOpenParamCatalog() + { + try + { + var catalog = ParamCatalogStore.Load(); + using (var f = new ParamCatalogForm(catalog)) + { + f.ShowDialog(this); + } + } + catch (Exception ex) + { + MessageBox.Show(this, $"打开参数总表失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private void OnOpenTemplateBinding() + { + try + { + var catalog = ParamCatalogStore.Load(); + + var projectTypes = _lbDeliveryStatuses.Items.Cast().Select(o => (o as string) ?? string.Empty).ToList(); + var drawingTypes = _lbProcessMethods.Items.Cast().Select(o => (o as string) ?? string.Empty).ToList(); + var sheetSizes = _lbStructuralFeatures.Items.Cast().Select(o => (o as string) ?? string.Empty).ToList(); + var scales = _lbSpecialConditions.Items.Cast().Select(o => (o as string) ?? string.Empty).ToList(); + + using (var f = new TemplateSchemaBindingForm(projectTypes, drawingTypes, sheetSizes, scales, catalog)) + { + f.ShowDialog(this); + } + } + catch (Exception ex) + { + MessageBox.Show(this, $"打开模板参数绑定失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + private static void FillList(ListBox listBox, System.Collections.Generic.IEnumerable items) { listBox.Items.Clear(); diff --git a/UI/TemplateSchemaBindingForm.cs b/UI/TemplateSchemaBindingForm.cs new file mode 100644 index 0000000..bfa1092 --- /dev/null +++ b/UI/TemplateSchemaBindingForm.cs @@ -0,0 +1,528 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Windows.Forms; +using CadParamPluging.Common; + +namespace CadParamPluging.UI +{ + public class TemplateSchemaBindingForm : Form + { + private readonly ComboBox _cbProjectType; + private readonly ComboBox _cbDrawingType; + private readonly ComboBox _cbSheetSize; + private readonly ComboBox _cbScale; + private readonly TextBox _txtDisplayName; + private readonly Label _lblTemplateKey; + private readonly ListBox _lbSchemas; + private readonly ListView _lvParams; + + private readonly ParamCatalog _catalog; + private TemplateSchemas _schemas; + + public TemplateSchemaBindingForm( + System.Collections.Generic.IEnumerable projectTypes, + System.Collections.Generic.IEnumerable drawingTypes, + System.Collections.Generic.IEnumerable sheetSizes, + System.Collections.Generic.IEnumerable scales, + ParamCatalog catalog) + { + Text = "模板参数绑定"; + Size = new Size(920, 620); + StartPosition = FormStartPosition.CenterParent; + FormBorderStyle = FormBorderStyle.FixedDialog; + MaximizeBox = false; + MinimizeBox = false; + ShowInTaskbar = false; + + _catalog = (catalog ?? ParamCatalog.CreateDefault()).Clone(); + _catalog.Normalize(); + + _schemas = TemplateSchemaStore.Load(); + _schemas.Normalize(); + + var root = new TableLayoutPanel + { + Dock = DockStyle.Fill, + Padding = new Padding(10), + ColumnCount = 2, + RowCount = 2 + }; + root.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 260)); + root.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100f)); + root.RowStyles.Add(new RowStyle(SizeType.Percent, 100f)); + root.RowStyles.Add(new RowStyle(SizeType.AutoSize)); + + _lbSchemas = new ListBox { Dock = DockStyle.Fill }; + _lbSchemas.SelectedIndexChanged += (_, __) => LoadSelectedSchemaToUi(); + ReloadSchemaList(); + + var leftButtons = new FlowLayoutPanel + { + Dock = DockStyle.Bottom, + FlowDirection = FlowDirection.LeftToRight, + AutoSize = true + }; + var btnNew = new Button { Text = "新增模板", AutoSize = true }; + btnNew.Click += (_, __) => OnNewSchema(); + var btnDelete = new Button { Text = "删除模板", AutoSize = true }; + btnDelete.Click += (_, __) => OnDeleteSchema(); + leftButtons.Controls.Add(btnNew); + leftButtons.Controls.Add(btnDelete); + + var leftPanel = new Panel { Dock = DockStyle.Fill }; + leftPanel.Controls.Add(_lbSchemas); + leftPanel.Controls.Add(leftButtons); + leftButtons.Dock = DockStyle.Bottom; + + var editor = new TableLayoutPanel + { + Dock = DockStyle.Fill, + ColumnCount = 2, + RowCount = 4, + AutoSize = true + }; + editor.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize)); + editor.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100f)); + + _cbProjectType = CreateCombo(projectTypes); + _cbDrawingType = CreateCombo(drawingTypes); + _cbSheetSize = CreateCombo(sheetSizes); + _cbScale = CreateCombo(scales); + + _cbProjectType.SelectedIndexChanged += (_, __) => UpdateTemplateKeyLabel(); + _cbDrawingType.SelectedIndexChanged += (_, __) => UpdateTemplateKeyLabel(); + _cbSheetSize.SelectedIndexChanged += (_, __) => UpdateTemplateKeyLabel(); + _cbScale.SelectedIndexChanged += (_, __) => UpdateTemplateKeyLabel(); + + _txtDisplayName = new TextBox { Width = 380 }; + + _lblTemplateKey = new Label { AutoSize = true, ForeColor = Color.DarkBlue }; + + var row = 0; + editor.Controls.Add(new Label { Text = "交付状态", AutoSize = true, TextAlign = ContentAlignment.MiddleLeft }, 0, row); + editor.Controls.Add(_cbProjectType, 1, row++); + editor.Controls.Add(new Label { Text = "工艺方法", AutoSize = true, TextAlign = ContentAlignment.MiddleLeft }, 0, row); + editor.Controls.Add(_cbDrawingType, 1, row++); + editor.Controls.Add(new Label { Text = "结构特征", AutoSize = true, TextAlign = ContentAlignment.MiddleLeft }, 0, row); + editor.Controls.Add(_cbSheetSize, 1, row++); + editor.Controls.Add(new Label { Text = "特殊条件", AutoSize = true, TextAlign = ContentAlignment.MiddleLeft }, 0, row); + editor.Controls.Add(_cbScale, 1, row++); + + var topBar = new TableLayoutPanel + { + Dock = DockStyle.Top, + ColumnCount = 2, + RowCount = 2, + AutoSize = true + }; + topBar.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize)); + topBar.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100f)); + + topBar.Controls.Add(new Label { Text = "显示名称", AutoSize = true, TextAlign = ContentAlignment.MiddleLeft }, 0, 0); + topBar.Controls.Add(_txtDisplayName, 1, 0); + topBar.Controls.Add(new Label { Text = "TemplateKey", AutoSize = true, TextAlign = ContentAlignment.MiddleLeft }, 0, 1); + topBar.Controls.Add(_lblTemplateKey, 1, 1); + + _lvParams = new ListView + { + Dock = DockStyle.Fill, + View = View.Details, + FullRowSelect = true, + HideSelection = false, + CheckBoxes = true + }; + _lvParams.Columns.Add("Label", 220); + _lvParams.Columns.Add("Key", 160); + + var paramsButtonPanel = new FlowLayoutPanel + { + Dock = DockStyle.Top, + FlowDirection = FlowDirection.LeftToRight, + AutoSize = true + }; + var btnUp = new Button { Text = "上移", AutoSize = true }; + btnUp.Click += (_, __) => MoveSelectedParam(-1); + var btnDown = new Button { Text = "下移", AutoSize = true }; + btnDown.Click += (_, __) => MoveSelectedParam(1); + var btnSelectAll = new Button { Text = "全选", AutoSize = true }; + btnSelectAll.Click += (_, __) => SetAllChecked(true); + var btnUnselectAll = new Button { Text = "全不选", AutoSize = true }; + btnUnselectAll.Click += (_, __) => SetAllChecked(false); + paramsButtonPanel.Controls.Add(btnUp); + paramsButtonPanel.Controls.Add(btnDown); + paramsButtonPanel.Controls.Add(btnSelectAll); + paramsButtonPanel.Controls.Add(btnUnselectAll); + + var rightPanel = new TableLayoutPanel + { + Dock = DockStyle.Fill, + ColumnCount = 1, + RowCount = 3 + }; + rightPanel.RowStyles.Add(new RowStyle(SizeType.AutoSize)); + rightPanel.RowStyles.Add(new RowStyle(SizeType.AutoSize)); + rightPanel.RowStyles.Add(new RowStyle(SizeType.Percent, 100f)); + + rightPanel.Controls.Add(editor, 0, 0); + rightPanel.Controls.Add(topBar, 0, 1); + + var paramArea = new TableLayoutPanel + { + Dock = DockStyle.Fill, + ColumnCount = 1, + RowCount = 2 + }; + paramArea.RowStyles.Add(new RowStyle(SizeType.AutoSize)); + paramArea.RowStyles.Add(new RowStyle(SizeType.Percent, 100f)); + paramArea.Controls.Add(paramsButtonPanel, 0, 0); + paramArea.Controls.Add(_lvParams, 0, 1); + + rightPanel.Controls.Add(paramArea, 0, 2); + + var bottomButtons = new FlowLayoutPanel + { + Dock = DockStyle.Fill, + FlowDirection = FlowDirection.RightToLeft, + AutoSize = true + }; + var btnClose = new Button { Text = "关闭", DialogResult = DialogResult.Cancel }; + var btnSave = new Button { Text = "保存" }; + btnSave.Click += (_, __) => OnSave(); + bottomButtons.Controls.Add(btnClose); + bottomButtons.Controls.Add(btnSave); + + root.Controls.Add(leftPanel, 0, 0); + root.Controls.Add(rightPanel, 1, 0); + root.Controls.Add(bottomButtons, 0, 1); + root.SetColumnSpan(bottomButtons, 2); + + Controls.Add(root); + CancelButton = btnClose; + + BuildParamListItems(); + UpdateTemplateKeyLabel(); + if (_lbSchemas.Items.Count > 0) + { + _lbSchemas.SelectedIndex = 0; + } + } + + private static ComboBox CreateCombo(System.Collections.Generic.IEnumerable items) + { + var cb = new ComboBox { DropDownStyle = ComboBoxStyle.DropDownList, Width = 220 }; + var arr = (items ?? Enumerable.Empty()) + .Where(s => !string.IsNullOrWhiteSpace(s)) + .Select(s => s.Trim()) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray(); + if (arr.Length > 0) + { + cb.Items.AddRange(arr); + cb.SelectedIndex = 0; + } + return cb; + } + + private void BuildParamListItems() + { + _lvParams.BeginUpdate(); + try + { + _lvParams.Items.Clear(); + foreach (var p in (_catalog.Items ?? Enumerable.Empty()).OrderBy(x => x.Order)) + { + var it = new ListViewItem(new[] { p.Label, p.Key }) + { + Tag = p.Key, + Checked = false + }; + _lvParams.Items.Add(it); + } + } + finally + { + _lvParams.EndUpdate(); + } + } + + private void ApplySchemaToParamList(TemplateSchemaDefinition schema) + { + var selectedKeys = (schema?.SelectedParamKeys ?? new List()).ToList(); + var keySet = new HashSet(selectedKeys, StringComparer.OrdinalIgnoreCase); + + var all = _lvParams.Items.Cast().ToList(); + var byKey = all.ToDictionary(i => (string)i.Tag, i => i, StringComparer.OrdinalIgnoreCase); + + var ordered = new List(); + foreach (var k in selectedKeys) + { + if (byKey.TryGetValue(k, out var it)) + { + ordered.Add(it); + } + } + + foreach (var it in all) + { + if (!keySet.Contains((string)it.Tag)) + { + ordered.Add(it); + } + } + + _lvParams.BeginUpdate(); + try + { + _lvParams.Items.Clear(); + foreach (var it in ordered) + { + var key = (string)it.Tag; + it.Checked = keySet.Contains(key); + _lvParams.Items.Add(it); + } + } + finally + { + _lvParams.EndUpdate(); + } + } + + private void ReloadSchemaList() + { + var prev = _lbSchemas.SelectedIndex; + + _lbSchemas.Items.Clear(); + foreach (var s in (_schemas.Items ?? Enumerable.Empty()) + .OrderBy(x => x.ProjectType) + .ThenBy(x => x.DrawingType) + .ThenBy(x => x.SheetSize) + .ThenBy(x => x.Scale)) + { + _lbSchemas.Items.Add(FormatSchema(s)); + } + + if (_lbSchemas.Items.Count > 0) + { + _lbSchemas.SelectedIndex = Math.Max(0, Math.Min(prev, _lbSchemas.Items.Count - 1)); + } + } + + private static string FormatSchema(TemplateSchemaDefinition s) + { + if (s == null) + { + return "(null)"; + } + + var name = string.IsNullOrWhiteSpace(s.DisplayName) ? "(未命名)" : s.DisplayName; + return $"{name} | {s.ProjectType}/{s.DrawingType}/{s.SheetSize}/{s.Scale}"; + } + + private TemplateSchemaDefinition GetSelectedSchema() + { + var idx = _lbSchemas.SelectedIndex; + if (idx < 0) + { + return null; + } + + var ordered = (_schemas.Items ?? Enumerable.Empty()) + .OrderBy(x => x.ProjectType) + .ThenBy(x => x.DrawingType) + .ThenBy(x => x.SheetSize) + .ThenBy(x => x.Scale) + .ToList(); + + if (idx >= ordered.Count) + { + return null; + } + + var key = ordered[idx].TemplateKey; + return (_schemas.Items ?? new List()).FirstOrDefault(x => string.Equals(x.TemplateKey, key, StringComparison.OrdinalIgnoreCase)); + } + + private void LoadSelectedSchemaToUi() + { + var schema = GetSelectedSchema(); + if (schema == null) + { + return; + } + + SelectComboByText(_cbProjectType, schema.ProjectType); + SelectComboByText(_cbDrawingType, schema.DrawingType); + SelectComboByText(_cbSheetSize, schema.SheetSize); + SelectComboByText(_cbScale, schema.Scale); + _txtDisplayName.Text = schema.DisplayName ?? string.Empty; + UpdateTemplateKeyLabel(); + ApplySchemaToParamList(schema); + } + + private static void SelectComboByText(ComboBox cb, string value) + { + if (cb == null) + { + return; + } + + var v = (value ?? string.Empty).Trim(); + for (var i = 0; i < cb.Items.Count; i++) + { + if (string.Equals((cb.Items[i] as string) ?? string.Empty, v, StringComparison.OrdinalIgnoreCase)) + { + cb.SelectedIndex = i; + return; + } + } + } + + private void UpdateTemplateKeyLabel() + { + var key = TemplateKeyBuilder.Build(_cbProjectType.Text, _cbDrawingType.Text, _cbSheetSize.Text, _cbScale.Text); + _lblTemplateKey.Text = key ?? string.Empty; + } + + private void OnNewSchema() + { + if (string.IsNullOrWhiteSpace(_cbProjectType.Text) || string.IsNullOrWhiteSpace(_cbDrawingType.Text)) + { + MessageBox.Show(this, "请先选择模板四个参数。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + var candidate = new TemplateSchemaDefinition + { + ProjectType = _cbProjectType.Text, + DrawingType = _cbDrawingType.Text, + SheetSize = _cbSheetSize.Text, + Scale = _cbScale.Text, + DisplayName = _txtDisplayName.Text + }; + candidate.Normalize(); + + if ((_schemas.Items ?? new List()).Any(x => string.Equals(x.TemplateKey, candidate.TemplateKey, StringComparison.OrdinalIgnoreCase))) + { + MessageBox.Show(this, "该模板定义已存在(四参数组合重复)。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + if (_schemas.Items == null) + { + _schemas.Items = new List(); + } + _schemas.Items.Add(candidate); + _schemas.Normalize(); + ReloadSchemaList(); + } + + private void OnDeleteSchema() + { + var schema = GetSelectedSchema(); + if (schema == null) + { + MessageBox.Show(this, "请先选择一个模板定义。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + + var confirm = MessageBox.Show(this, $"确认删除模板定义:{FormatSchema(schema)}?", "删除", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); + if (confirm != DialogResult.Yes) + { + return; + } + + _schemas.Items = (_schemas.Items ?? Enumerable.Empty()) + .Where(x => !string.Equals(x.TemplateKey, schema.TemplateKey, StringComparison.OrdinalIgnoreCase)) + .ToList(); + _schemas.Normalize(); + ReloadSchemaList(); + } + + private void MoveSelectedParam(int delta) + { + if (_lvParams.SelectedItems.Count == 0) + { + return; + } + + var item = _lvParams.SelectedItems[0]; + var idx = item.Index; + var newIdx = idx + delta; + if (newIdx < 0 || newIdx >= _lvParams.Items.Count) + { + return; + } + + _lvParams.BeginUpdate(); + try + { + _lvParams.Items.RemoveAt(idx); + _lvParams.Items.Insert(newIdx, item); + item.Selected = true; + item.Focused = true; + } + finally + { + _lvParams.EndUpdate(); + } + } + + private void SetAllChecked(bool value) + { + _lvParams.BeginUpdate(); + try + { + foreach (ListViewItem it in _lvParams.Items) + { + it.Checked = value; + } + } + finally + { + _lvParams.EndUpdate(); + } + } + + private void OnSave() + { + var schema = GetSelectedSchema(); + if (schema == null) + { + MessageBox.Show(this, "请先选择一个模板定义。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + + schema.ProjectType = _cbProjectType.Text; + schema.DrawingType = _cbDrawingType.Text; + schema.SheetSize = _cbSheetSize.Text; + schema.Scale = _cbScale.Text; + schema.DisplayName = _txtDisplayName.Text; + schema.SelectedParamKeys = _lvParams.Items + .Cast() + .Where(i => i.Checked) + .Select(i => (string)i.Tag) + .ToList(); + schema.Normalize(); + + if ((_schemas.Items ?? new List()) + .Any(x => !ReferenceEquals(x, schema) && string.Equals(x.TemplateKey, schema.TemplateKey, StringComparison.OrdinalIgnoreCase))) + { + MessageBox.Show(this, "保存失败:存在重复的四参数组合。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + try + { + TemplateSchemaStore.Save(_schemas); + MessageBox.Show(this, "已保存。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); + ReloadSchemaList(); + } + catch (Exception ex) + { + MessageBox.Show(this, $"保存失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + } +}