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 TextBox _txtNoteTemplate; private readonly Label _lblNotePlaceholderCount; private readonly DataGridView _gvNoteBindings; private readonly RichTextBox _rtbNotePreview; private readonly List _remarkParamOptions; private readonly ParamCatalog _catalog; private TemplateSchemas _schemas; private sealed class ParamOption { public string Key { get; set; } public string Display { get; set; } } 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); MinimumSize = new Size(920, 620); StartPosition = FormStartPosition.CenterParent; FormBorderStyle = FormBorderStyle.Sizable; MaximizeBox = true; MinimizeBox = true; ShowInTaskbar = false; _catalog = (catalog ?? ParamCatalog.CreateDefault()).Clone(); _catalog.Normalize(); _remarkParamOptions = (_catalog.Items ?? Enumerable.Empty()) .Where(p => p != null && string.Equals(p.Group ?? string.Empty, "备注参数", StringComparison.OrdinalIgnoreCase)) .OrderBy(p => p.Order) .Select(p => new ParamOption { Key = p.Key, Display = string.Format("{0} ({1})", p.Label, p.Key) }) .ToList(); _schemas = TemplateSchemaStore.Load() ?? new TemplateSchemas(); _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 }; 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); _txtNoteTemplate = new TextBox { Dock = DockStyle.Fill, Multiline = true, ScrollBars = ScrollBars.Vertical, Height = 120 }; _lblNotePlaceholderCount = new Label { AutoSize = true, ForeColor = Color.DarkBlue, Text = "占位符: 0" }; _gvNoteBindings = new DataGridView { Dock = DockStyle.Fill, AutoGenerateColumns = false, AllowUserToAddRows = false, AllowUserToDeleteRows = false, RowHeadersVisible = false, SelectionMode = DataGridViewSelectionMode.FullRowSelect }; var colIdx = new DataGridViewTextBoxColumn { HeaderText = "序号", Width = 60, ReadOnly = true }; var colLine = new DataGridViewTextBoxColumn { HeaderText = "所在行", Width = 80, ReadOnly = true }; var colSnippet = new DataGridViewTextBoxColumn { HeaderText = "文本定位", AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill, ReadOnly = true }; var colParam = new DataGridViewComboBoxColumn { HeaderText = "绑定参数(备注参数)", Width = 320, DisplayStyle = DataGridViewComboBoxDisplayStyle.DropDownButton, DataSource = _remarkParamOptions, DisplayMember = "Display", ValueMember = "Key" }; _gvNoteBindings.Columns.Add(colIdx); _gvNoteBindings.Columns.Add(colLine); _gvNoteBindings.Columns.Add(colSnippet); _gvNoteBindings.Columns.Add(colParam); _gvNoteBindings.DataError += (_, __) => { }; _gvNoteBindings.SelectionChanged += (_, __) => HighlightSelectedPlaceholderInPreview(); _rtbNotePreview = new RichTextBox { Dock = DockStyle.Fill, ReadOnly = true, HideSelection = false }; var btnParseNote = new Button { Text = "解析占位符", AutoSize = true }; btnParseNote.Click += (_, __) => ReloadNoteBindingsFromTemplateText(); 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); var noteArea = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 1, RowCount = 6, Padding = new Padding(0) }; noteArea.RowStyles.Add(new RowStyle(SizeType.AutoSize)); noteArea.RowStyles.Add(new RowStyle(SizeType.AutoSize)); noteArea.RowStyles.Add(new RowStyle(SizeType.AutoSize)); noteArea.RowStyles.Add(new RowStyle(SizeType.Percent, 60f)); noteArea.RowStyles.Add(new RowStyle(SizeType.AutoSize)); noteArea.RowStyles.Add(new RowStyle(SizeType.Percent, 40f)); noteArea.Controls.Add(new Label { Text = "附注模板(多行文本,使用 * 作为占位符;**** 会忽略)", AutoSize = true }, 0, 0); noteArea.Controls.Add(_txtNoteTemplate, 0, 1); var noteToolbar = new FlowLayoutPanel { Dock = DockStyle.Fill, FlowDirection = FlowDirection.LeftToRight, AutoSize = true }; noteToolbar.Controls.Add(btnParseNote); noteToolbar.Controls.Add(_lblNotePlaceholderCount); noteArea.Controls.Add(noteToolbar, 0, 2); noteArea.Controls.Add(_gvNoteBindings, 0, 3); noteArea.Controls.Add(new Label { Text = "编号预览(用于对照绑定):", AutoSize = true }, 0, 4); noteArea.Controls.Add(_rtbNotePreview, 0, 5); var tabs = new TabControl { Dock = DockStyle.Fill }; var tabParams = new TabPage { Text = "参数绑定" }; tabParams.Controls.Add(paramArea); var tabNotes = new TabPage { Text = "附注绑定" }; tabNotes.Controls.Add(noteArea); tabs.TabPages.Add(tabParams); tabs.TabPages.Add(tabNotes); rightPanel.Controls.Add(tabs, 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(); _lbSchemas.SelectedIndexChanged += (_, __) => LoadSelectedSchemaToUi(); ReloadSchemaList(); } 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()) .Where(x => x != null) .Where(x => !string.Equals(x.Group ?? string.Empty, "备注参数", StringComparison.OrdinalIgnoreCase)) .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()) .Where(x => x != null) .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()) .Where(x => x != null) .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()) .Where(x => x != null) .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); LoadNoteBindingsToUi(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()) .Where(x => x != null) .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 => x != null && !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 LoadNoteBindingsToUi(TemplateSchemaDefinition schema) { if (schema == null) { _txtNoteTemplate.Text = string.Empty; ReloadNoteBindingsGrid(string.Empty, null); return; } _txtNoteTemplate.Text = schema.NoteTemplateText ?? string.Empty; ReloadNoteBindingsGrid(_txtNoteTemplate.Text, schema.NoteBindings); } private void ReloadNoteBindingsFromTemplateText() { var map = ReadNoteBindingMapFromGrid(); var bindings = map .Select(kv => new NotePlaceholderBinding { Index = kv.Key, ParamKey = kv.Value }) .ToList(); ReloadNoteBindingsGrid(_txtNoteTemplate.Text ?? string.Empty, bindings); } private void ReloadNoteBindingsGrid(string templateText, IEnumerable bindings) { var occ = NoteTemplateEngine.ParseOccurrences(templateText ?? string.Empty); var count = occ.Count; _lblNotePlaceholderCount.Text = string.Format("占位符: {0}", count); _rtbNotePreview.Text = NoteTemplateEngine.BuildNumberedPreviewText(templateText ?? string.Empty); var map = new Dictionary(); foreach (var b in bindings ?? Enumerable.Empty()) { if (b == null) { continue; } if (b.Index <= 0) { continue; } var key = (b.ParamKey ?? string.Empty).Trim(); if (key.Length == 0) { continue; } if (!map.ContainsKey(b.Index)) { map[b.Index] = key; } } _gvNoteBindings.SuspendLayout(); try { _gvNoteBindings.Rows.Clear(); foreach (var o in occ) { var idx = _gvNoteBindings.Rows.Add(); var row = _gvNoteBindings.Rows[idx]; row.Cells[0].Value = o.Index; row.Cells[1].Value = string.Format("第{0}行", o.LineNumber); row.Cells[2].Value = BuildOccurrenceSnippet(o); row.Cells[3].Value = map.ContainsKey(o.Index) ? map[o.Index] : null; } } finally { _gvNoteBindings.ResumeLayout(); } HighlightSelectedPlaceholderInPreview(); } private Dictionary ReadNoteBindingMapFromGrid() { var map = new Dictionary(); foreach (DataGridViewRow row in _gvNoteBindings.Rows) { if (row == null) { continue; } var idxObj = row.Cells[0].Value; var keyObj = row.Cells[3].Value; int idx; if (idxObj == null || !int.TryParse(idxObj.ToString(), out idx) || idx <= 0) { continue; } var key = (keyObj ?? string.Empty).ToString().Trim(); if (key.Length == 0) { continue; } if (!map.ContainsKey(idx)) { map[idx] = key; } } return map; } private static string BuildOccurrenceSnippet(NoteTemplateEngine.PlaceholderOccurrence o) { if (o == null) { return string.Empty; } var line = o.LineText ?? string.Empty; var token = string.Format("【#{0}】", o.Index); var col = o.ColumnInLine; if (col < 0) col = 0; if (col >= line.Length) { return token; } const int ctx = 16; var start = Math.Max(0, col - ctx); var end = Math.Min(line.Length, col + 1 + ctx); var prefix = start > 0 ? "…" : string.Empty; var suffix = end < line.Length ? "…" : string.Empty; var left = line.Substring(start, col - start); var right = line.Substring(col + 1, end - (col + 1)); return prefix + left + token + right + suffix; } private void HighlightSelectedPlaceholderInPreview() { if (_rtbNotePreview == null || string.IsNullOrEmpty(_rtbNotePreview.Text)) { return; } if (_gvNoteBindings.SelectedRows.Count == 0) { return; } var row = _gvNoteBindings.SelectedRows[0]; if (row == null) { return; } var idxObj = row.Cells[0].Value; int idx; if (idxObj == null || !int.TryParse(idxObj.ToString(), out idx) || idx <= 0) { return; } var token = string.Format("【#{0}】", idx); var pos = _rtbNotePreview.Text.IndexOf(token, StringComparison.Ordinal); if (pos < 0) { return; } _rtbNotePreview.Select(pos, token.Length); _rtbNotePreview.ScrollToCaret(); } 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; var selectedParamKeys = _lvParams.Items .Cast() .Where(i => i.Checked) .Select(i => (string)i.Tag) .Where(k => !string.IsNullOrWhiteSpace(k)) .ToList(); // “备注参数”仅在附注绑定中配置,不出现在模板参数绑定里。 selectedParamKeys = selectedParamKeys .Where(k => { var def = _catalog.FindByKey(k); if (def == null) { return true; } return !string.Equals(def.Group ?? string.Empty, "备注参数", StringComparison.OrdinalIgnoreCase); }) .ToList(); schema.SelectedParamKeys = selectedParamKeys; schema.NoteTemplateText = _txtNoteTemplate.Text ?? string.Empty; var placeholderCount = NoteTemplateEngine.CountPlaceholders(schema.NoteTemplateText); if (_gvNoteBindings.Rows.Count != placeholderCount) { ReloadNoteBindingsFromTemplateText(); MessageBox.Show(this, "附注模板已变化,已重新解析占位符。请确认附注绑定后再保存。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } var noteBindings = new List(); if (placeholderCount > 0) { var map = ReadNoteBindingMapFromGrid(); for (var i = 1; i <= placeholderCount; i++) { if (!map.TryGetValue(i, out var key) || string.IsNullOrWhiteSpace(key)) { MessageBox.Show(this, string.Format("请绑定第 {0} 个占位符(*)对应的参数。", i), "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } var def = _catalog.FindByKey(key); if (def == null || !string.Equals(def.Group ?? string.Empty, "备注参数", StringComparison.OrdinalIgnoreCase)) { MessageBox.Show(this, string.Format("占位符 #{0} 绑定的参数不在【备注参数】分组:{1}", i, key), "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } noteBindings.Add(new NotePlaceholderBinding { Index = i, ParamKey = key.Trim() }); } } schema.NoteBindings = noteBindings; schema.Normalize(); if ((_schemas.Items ?? new List()) .Where(x => x != null) .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); } } } }