587 lines
20 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|
|
}
|