CadParamPluging/UI/DrawingParamsForm.cs

587 lines
20 KiB
C#

using System;
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<string, Control> _controlsByKey;
private readonly Dictionary<string, ParamDefinition> _defsByControlKey;
private readonly ParamCatalog _catalog;
private readonly TemplateSchemaDefinition _schema;
private readonly ToolTip _toolTip;
private readonly TabControl _notePreviewTabs;
private readonly RichTextBox _rtbNoteNumbered;
private readonly RichTextBox _rtbNoteRendered;
private Dictionary<int, NoteTemplateEngine.PlaceholderOccurrence> _occByIndex;
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(980, 680);
MinimumSize = new Size(900, 620);
StartPosition = FormStartPosition.CenterParent;
FormBorderStyle = FormBorderStyle.Sizable;
MaximizeBox = true;
MinimizeBox = true;
ShowInTaskbar = false;
_controlsByKey = new Dictionary<string, Control>(StringComparer.OrdinalIgnoreCase);
_defsByControlKey = new Dictionary<string, ParamDefinition>(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
};
_notePreviewTabs = new TabControl { Dock = DockStyle.Fill };
_rtbNoteNumbered = new RichTextBox { Dock = DockStyle.Fill, ReadOnly = true, HideSelection = false };
_rtbNoteRendered = new RichTextBox { Dock = DockStyle.Fill, ReadOnly = true, HideSelection = false };
var tabNumbered = new TabPage { Text = "附注编号预览" };
tabNumbered.Controls.Add(_rtbNoteNumbered);
var tabRendered = new TabPage { Text = "填充值预览" };
tabRendered.Controls.Add(_rtbNoteRendered);
_notePreviewTabs.TabPages.Add(tabNumbered);
_notePreviewTabs.TabPages.Add(tabRendered);
_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 split = new SplitContainer
{
Dock = DockStyle.Fill,
Orientation = Orientation.Vertical
};
split.Panel1.Controls.Add(scrollPanel);
split.Panel2.Controls.Add(_notePreviewTabs);
split.SizeChanged += (_, __) => EnsureValidSplitterDistance(split);
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(split, 0, 1);
root.Controls.Add(buttons, 0, 2);
Controls.Add(root);
AcceptButton = btnOk;
CancelButton = btnCancel;
Shown += (_, __) =>
{
split.Panel1MinSize = 360;
split.Panel2MinSize = 280;
EnsureValidSplitterDistance(split);
UpdateNotePreviews();
};
}
private static void EnsureValidSplitterDistance(SplitContainer split)
{
if (split == null)
{
return;
}
// SplitterDistance must be within [Panel1MinSize, Width - Panel2MinSize - SplitterWidth]
var width = split.Width;
if (width <= 0)
{
return;
}
// If container is too narrow, collapse preview panel to avoid throwing.
var required = split.Panel1MinSize + split.Panel2MinSize + split.SplitterWidth + 8;
if (width < required)
{
split.Panel2Collapsed = true;
return;
}
split.Panel2Collapsed = false;
var min = split.Panel1MinSize;
var max = width - split.Panel2MinSize - split.SplitterWidth;
if (max < min)
{
// Too narrow: keep it at minimum to avoid exception.
max = min;
}
var desired = (int)(width * 0.6);
if (desired < min) desired = min;
if (desired > max) desired = max;
try
{
split.SplitterDistance = desired;
}
catch
{
// ignore
}
}
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()
{
_occByIndex = NoteTemplateEngine.ParseOccurrences(_schema?.NoteTemplateText ?? string.Empty)
.Where(o => o != null)
.GroupBy(o => o.Index)
.Select(g => g.First())
.ToDictionary(o => o.Index, o => o);
var mainDefs = new List<ParamDefinition>();
var seenMain = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var key in (_schema?.SelectedParamKeys ?? new List<string>()))
{
if (string.IsNullOrWhiteSpace(key))
{
continue;
}
var def = _catalog.FindByKey(key);
if (def == null)
{
continue;
}
if (string.Equals(def.Group ?? string.Empty, "备注参数", StringComparison.OrdinalIgnoreCase))
{
// 备注参数只在附注里填写
continue;
}
if (!seenMain.Add(def.Key))
{
continue;
}
mainDefs.Add(def);
}
var noteBindings = (_schema?.NoteBindings ?? new List<NotePlaceholderBinding>())
.Where(b => b != null && b.Index > 0 && !string.IsNullOrWhiteSpace(b.ParamKey))
.OrderBy(b => b.Index)
.ToList();
if (mainDefs.Count == 0 && noteBindings.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;
}
if (mainDefs.Count > 0)
{
AddSectionHeader("模板参数");
foreach (var def in mainDefs)
{
AddField(def, def.Key, def.Label, null);
}
}
if (noteBindings.Count > 0)
{
AddSectionHeader("备注参数(附注)");
foreach (var b in noteBindings)
{
var baseKey = (b.ParamKey ?? string.Empty).Trim();
var def = _catalog.FindByKey(baseKey);
if (def == null)
{
continue;
}
// If same ParamKey is used by multiple placeholders, give each placeholder its own value key.
var valueKey = baseKey;
var dupCount = noteBindings.Count(x => string.Equals((x.ParamKey ?? string.Empty).Trim(), baseKey, StringComparison.OrdinalIgnoreCase));
if (dupCount > 1)
{
valueKey = string.Format("{0}@{1}", baseKey, b.Index);
}
var labelText = string.Format("{0} (占位符: #{1})", def.Label, b.Index);
var tip = BuildOccurrenceTip(b.Index);
AddField(def, valueKey, labelText, tip);
}
}
}
private string BuildOccurrenceTip(int placeholderIndex)
{
if (_occByIndex == null)
{
return null;
}
if (!_occByIndex.TryGetValue(placeholderIndex, out var o) || o == null)
{
return null;
}
var line = o.LineText ?? string.Empty;
var col = o.ColumnInLine;
if (col < 0) col = 0;
if (col >= line.Length)
{
return string.Format("第{0}行: 【#{1}】", o.LineNumber, placeholderIndex);
}
const int ctx = 18;
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));
var snippet = prefix + left + string.Format("【#{0}】", placeholderIndex) + right + suffix;
return string.Format("第{0}行: {1}", o.LineNumber, snippet);
}
private void AddSectionHeader(string text)
{
var rowIdx = _grid.RowCount++;
_grid.RowStyles.Add(new RowStyle(SizeType.AutoSize));
var lbl = new Label
{
AutoSize = true,
ForeColor = Color.DarkBlue,
Text = text,
Padding = new Padding(0, 10, 0, 4)
};
_grid.Controls.Add(lbl, 0, rowIdx);
_grid.SetColumnSpan(lbl, 2);
}
private void AddField(ParamDefinition def, string controlKey, string labelText, string extraToolTip)
{
var rowIdx = _grid.RowCount++;
_grid.RowStyles.Add(new RowStyle(SizeType.AutoSize));
if (string.IsNullOrWhiteSpace(labelText))
{
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);
}
if (!string.IsNullOrWhiteSpace(extraToolTip))
{
_toolTip.SetToolTip(lbl, extraToolTip);
_toolTip.SetToolTip(ctrl, extraToolTip);
}
_grid.Controls.Add(lbl, 0, rowIdx);
_grid.Controls.Add(ctrl, 1, rowIdx);
_controlsByKey[controlKey] = ctrl;
_defsByControlKey[controlKey] = def;
AttachPreviewUpdate(ctrl);
}
private void AttachPreviewUpdate(Control ctrl)
{
if (ctrl == null)
{
return;
}
if (ctrl is TextBox tb)
{
tb.TextChanged += (_, __) => UpdateNotePreviews();
return;
}
if (ctrl is ComboBox cb)
{
cb.SelectedIndexChanged += (_, __) => UpdateNotePreviews();
cb.TextChanged += (_, __) => UpdateNotePreviews();
return;
}
if (ctrl is NumericUpDown num)
{
num.ValueChanged += (_, __) => UpdateNotePreviews();
return;
}
if (ctrl is CheckBox chk)
{
chk.CheckedChanged += (_, __) => UpdateNotePreviews();
}
}
private void UpdateNotePreviews()
{
if (_schema == null)
{
_rtbNoteNumbered.Text = "(模板参数未配置)";
_rtbNoteRendered.Text = string.Empty;
return;
}
var noteTemplate = _schema.NoteTemplateText ?? string.Empty;
if (string.IsNullOrWhiteSpace(noteTemplate))
{
_rtbNoteNumbered.Text = "(未配置附注模板文本:请到【设置→模板参数绑定→附注绑定】粘贴附注模板)";
_rtbNoteRendered.Text = string.Empty;
return;
}
_rtbNoteNumbered.Text = NoteTemplateEngine.BuildNumberedPreviewText(noteTemplate);
var tempBag = CollectBagFromUi();
var effective = NoteTemplateEngine.BuildEffectiveValueKeyBindings(_schema.NoteBindings);
_rtbNoteRendered.Text = NoteTemplateEngine.Render(noteTemplate, effective, tempBag.GetString);
}
private ParamBag CollectBagFromUi()
{
var bag = new ParamBag();
foreach (var key in _controlsByKey.Keys.ToList())
{
if (!_controlsByKey.TryGetValue(key, out var ctrl) || ctrl == null)
{
continue;
}
if (!_defsByControlKey.TryGetValue(key, out var def) || def == null)
{
continue;
}
var value = ReadValue(ctrl, def);
bag.Set(key, value);
}
return bag;
}
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<string>()).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;
}
if (!_defsByControlKey.TryGetValue(key, out var def) || 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(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();
}
}
}
}