932 lines
32 KiB
C#
932 lines
32 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;
|
||
using Autodesk.AutoCAD.ApplicationServices;
|
||
using Autodesk.AutoCAD.EditorInput;
|
||
|
||
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;
|
||
|
||
// 最小壁厚T自动计算相关
|
||
private bool _canAutoCalcMinWallThickness;
|
||
private Control _ctrlMinWallThickness;
|
||
private const string KeyOuterDiameter1 = "OuterDiameter1";
|
||
private const string KeyOuterDiameter1TolMinus = "OuterDiameter1TolMinus";
|
||
private const string KeyInnerDiameter2 = "InnerDiameter2";
|
||
private const string KeyInnerDiameter2TolPlus = "InnerDiameter2TolPlus";
|
||
private const string KeyMinWallThickness = "MinWallThickness";
|
||
|
||
public ParamBag Result { get; private set; }
|
||
private ParamBag _loadedDefaults; // 新增:外部传入的默认值
|
||
|
||
public DrawingParamsForm(ParamCatalog catalog, TemplateSchemaDefinition schema, ParamBag loadedDefaults = null)
|
||
{
|
||
_catalog = (catalog ?? ParamCatalog.CreateDefault()).Clone();
|
||
_catalog.Normalize();
|
||
_schema = schema;
|
||
_loadedDefaults = loadedDefaults; // 存储传入的默认值
|
||
|
||
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();
|
||
CheckMinWallThicknessAutoCalc();
|
||
};
|
||
}
|
||
|
||
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 forgingDefs = new List<ParamDefinition>();
|
||
var partDefs = new List<ParamDefinition>();
|
||
var seenMain = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||
|
||
var partKeySet = new HashSet<string>(
|
||
(_schema?.SelectedPartParamKeys ?? new List<string>())
|
||
.Where(s => !string.IsNullOrWhiteSpace(s))
|
||
.Select(s => s.Trim()),
|
||
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;
|
||
}
|
||
|
||
if (partKeySet.Contains(def.Key))
|
||
{
|
||
partDefs.Add(def);
|
||
}
|
||
else
|
||
{
|
||
forgingDefs.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 (forgingDefs.Count == 0 && partDefs.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 (forgingDefs.Count > 0)
|
||
{
|
||
AddSectionHeader("锻件尺寸");
|
||
foreach (var def in forgingDefs)
|
||
{
|
||
AddField(def, def.Key, def.Label, null);
|
||
}
|
||
}
|
||
|
||
if (partDefs.Count > 0)
|
||
{
|
||
AddSectionHeader("零件尺寸");
|
||
foreach (var def in partDefs)
|
||
{
|
||
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);
|
||
}
|
||
}
|
||
|
||
// 收集所有已绑定的参数 key
|
||
var boundKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||
foreach (var key in (_schema?.SelectedParamKeys ?? new List<string>()))
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(key))
|
||
{
|
||
boundKeys.Add(key.Trim());
|
||
}
|
||
}
|
||
foreach (var b in noteBindings)
|
||
{
|
||
var key = (b.ParamKey ?? string.Empty).Trim();
|
||
if (key.Length > 0)
|
||
{
|
||
boundKeys.Add(key);
|
||
}
|
||
}
|
||
|
||
// 查找必填但未绑定的参数
|
||
var unboundRequiredDefs = (_catalog.Items ?? Enumerable.Empty<ParamDefinition>())
|
||
.Where(p => p != null && p.Required && !boundKeys.Contains(p.Key ?? string.Empty))
|
||
.OrderBy(p => p.Order)
|
||
.ToList();
|
||
|
||
if (unboundRequiredDefs.Count > 0)
|
||
{
|
||
AddSectionHeader("其他必填参数");
|
||
foreach (var def in unboundRequiredDefs)
|
||
{
|
||
AddField(def, def.Key, def.Label, null);
|
||
}
|
||
}
|
||
}
|
||
|
||
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, controlKey);
|
||
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);
|
||
}
|
||
|
||
Control finalCtrl = ctrl;
|
||
if (string.Equals(def.Key, "MarkingContent", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
var flow = new FlowLayoutPanel
|
||
{
|
||
AutoSize = true,
|
||
FlowDirection = FlowDirection.LeftToRight,
|
||
WrapContents = false,
|
||
Margin = new Padding(0)
|
||
};
|
||
|
||
bool isChecked = true;
|
||
if (_loadedDefaults != null)
|
||
{
|
||
var showVal = _loadedDefaults.GetString(controlKey + "_ShowInNote");
|
||
if (showVal != null)
|
||
{
|
||
isChecked = showVal == "1" || showVal.Equals("true", StringComparison.OrdinalIgnoreCase);
|
||
}
|
||
}
|
||
var chk = new CheckBox
|
||
{
|
||
Text = "显示在附注中",
|
||
AutoSize = true,
|
||
Checked = isChecked,
|
||
Margin = new Padding(10, 3, 0, 0)
|
||
};
|
||
|
||
_controlsByKey[controlKey + "_ShowInNote"] = chk;
|
||
chk.CheckedChanged += (_, __) => UpdateNotePreviews();
|
||
|
||
flow.Controls.Add(ctrl);
|
||
flow.Controls.Add(chk);
|
||
finalCtrl = flow;
|
||
}
|
||
|
||
_grid.Controls.Add(lbl, 0, rowIdx);
|
||
_grid.Controls.Add(finalCtrl, 1, rowIdx);
|
||
_controlsByKey[controlKey] = ctrl;
|
||
_defsByControlKey[controlKey] = def;
|
||
|
||
AttachPreviewUpdate(ctrl);
|
||
AttachMinWallThicknessUpdate(controlKey, 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 AttachMinWallThicknessUpdate(string paramKey, Control ctrl)
|
||
{
|
||
if (ctrl == null || string.IsNullOrWhiteSpace(paramKey))
|
||
{
|
||
return;
|
||
}
|
||
|
||
var key = paramKey.Trim();
|
||
if (!string.Equals(key, KeyOuterDiameter1, StringComparison.OrdinalIgnoreCase)
|
||
&& !string.Equals(key, KeyOuterDiameter1TolMinus, StringComparison.OrdinalIgnoreCase)
|
||
&& !string.Equals(key, KeyInnerDiameter2, StringComparison.OrdinalIgnoreCase)
|
||
&& !string.Equals(key, KeyInnerDiameter2TolPlus, StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (ctrl is NumericUpDown num)
|
||
{
|
||
num.ValueChanged += (_, __) => UpdateMinWallThickness();
|
||
}
|
||
else if (ctrl is TextBox tb)
|
||
{
|
||
tb.TextChanged += (_, __) => UpdateMinWallThickness();
|
||
}
|
||
}
|
||
|
||
private void CheckMinWallThicknessAutoCalc()
|
||
{
|
||
_canAutoCalcMinWallThickness = false;
|
||
|
||
if (!_controlsByKey.ContainsKey(KeyMinWallThickness))
|
||
{
|
||
return;
|
||
}
|
||
|
||
_ctrlMinWallThickness = _controlsByKey[KeyMinWallThickness];
|
||
|
||
var hasOuter = _controlsByKey.ContainsKey(KeyOuterDiameter1);
|
||
var hasOuterTol = _controlsByKey.ContainsKey(KeyOuterDiameter1TolMinus);
|
||
var hasInner = _controlsByKey.ContainsKey(KeyInnerDiameter2);
|
||
var hasInnerTol = _controlsByKey.ContainsKey(KeyInnerDiameter2TolPlus);
|
||
|
||
if (hasOuter && hasOuterTol && hasInner && hasInnerTol)
|
||
{
|
||
_canAutoCalcMinWallThickness = true;
|
||
UpdateMinWallThickness();
|
||
|
||
if (_ctrlMinWallThickness != null)
|
||
{
|
||
_toolTip.SetToolTip(_ctrlMinWallThickness, "自动计算: T=((Φ1+b1)-(Φ2+a2))/2\n(根据外径Φ1、外径下差b1、内径Φ2、内径上差a2)");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
var missing = new List<string>();
|
||
if (!hasOuter) missing.Add("外径Φ1");
|
||
if (!hasOuterTol) missing.Add("外径下差b1");
|
||
if (!hasInner) missing.Add("内径Φ2");
|
||
if (!hasInnerTol) missing.Add("内径上差a2");
|
||
|
||
if (_ctrlMinWallThickness != null && missing.Count > 0)
|
||
{
|
||
var msg = string.Format("需人工填写(缺少参数: {0})", string.Join(", ", missing));
|
||
_toolTip.SetToolTip(_ctrlMinWallThickness, msg);
|
||
}
|
||
}
|
||
}
|
||
|
||
private void UpdateMinWallThickness()
|
||
{
|
||
if (!_canAutoCalcMinWallThickness || _ctrlMinWallThickness == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
var phi1 = GetNumericValue(KeyOuterDiameter1);
|
||
var b1 = GetNumericValue(KeyOuterDiameter1TolMinus);
|
||
var phi2 = GetNumericValue(KeyInnerDiameter2);
|
||
var a2 = GetNumericValue(KeyInnerDiameter2TolPlus);
|
||
|
||
// 注意:b1通常为负数(如-0.5),a2通常为正数(如0.5)
|
||
// 最小壁厚(径向):T = ((外径最小) - (内径最大)) / 2
|
||
// 外径最小 = Φ1 + b1,内径最大 = Φ2 + a2
|
||
// T = ((Φ1 + b1) - (Φ2 + a2)) / 2
|
||
var t = ((phi1 + b1) - (phi2 + a2)) / 2.0;
|
||
|
||
if (_ctrlMinWallThickness is NumericUpDown num)
|
||
{
|
||
var val = (decimal)t;
|
||
if (val < num.Minimum) val = num.Minimum;
|
||
if (val > num.Maximum) val = num.Maximum;
|
||
num.Value = val;
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// ignore calculation errors
|
||
}
|
||
}
|
||
|
||
private double GetNumericValue(string key)
|
||
{
|
||
if (!_controlsByKey.TryGetValue(key, out var ctrl) || ctrl == null)
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
if (ctrl is NumericUpDown num)
|
||
{
|
||
return (double)num.Value;
|
||
}
|
||
|
||
if (ctrl is TextBox tb)
|
||
{
|
||
if (double.TryParse(tb.Text, NumberStyles.Float, CultureInfo.InvariantCulture, out var v))
|
||
{
|
||
return v;
|
||
}
|
||
if (double.TryParse(tb.Text, NumberStyles.Float, CultureInfo.CurrentCulture, out v))
|
||
{
|
||
return v;
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
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);
|
||
Func<string, string> getValueW = (k) =>
|
||
{
|
||
var val = tempBag.GetString(k);
|
||
if (k != null && k.StartsWith("MarkingContent", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
if (tempBag.GetString(k + "_ShowInNote") == "0") return "__SKIP_NOTE__";
|
||
}
|
||
return val;
|
||
};
|
||
|
||
_rtbNoteNumbered.Text = NoteTemplateEngine.BuildNumberedPreviewText(noteTemplate);
|
||
|
||
_rtbNoteRendered.Text = NoteTemplateEngine.Render(noteTemplate, effective, getValueW);
|
||
}
|
||
|
||
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 (key.EndsWith("_ShowInNote", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
if (ctrl is CheckBox chk)
|
||
{
|
||
bag.Set(key, chk.Checked ? "1" : "0");
|
||
}
|
||
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, string valueKey)
|
||
{
|
||
var initialValue = def.DefaultValue;
|
||
|
||
// 优先使用传入的默认值 (loadedDefaults)
|
||
if (_loadedDefaults != null)
|
||
{
|
||
var val = _loadedDefaults.GetString(valueKey);
|
||
if (val != null) // only if key exists
|
||
{
|
||
initialValue = val;
|
||
if (valueKey.StartsWith("Hardness"))
|
||
{
|
||
// Logging removed
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (valueKey.StartsWith("Hardness"))
|
||
{
|
||
// Logging removed
|
||
}
|
||
}
|
||
}
|
||
// 确保不为 null
|
||
if (initialValue == null) initialValue = string.Empty;
|
||
|
||
switch (def.Type)
|
||
{
|
||
case ParamValueType.Bool:
|
||
return new CheckBox { Checked = ParseBool(initialValue), AutoSize = true };
|
||
case ParamValueType.Enum:
|
||
return CreateEnumCombo(def, initialValue);
|
||
case ParamValueType.Int:
|
||
return CreateNumber(def, isInt: true, overrideValue: initialValue);
|
||
case ParamValueType.Double:
|
||
return CreateNumber(def, isInt: false, overrideValue: initialValue);
|
||
default:
|
||
return new TextBox { Text = initialValue };
|
||
}
|
||
}
|
||
|
||
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, string initialValue)
|
||
{
|
||
// Allow manual edit to match many "下拉列表...可手动编辑" cases.
|
||
var cb = new ComboBox { DropDownStyle = ComboBoxStyle.DropDown };
|
||
var list = (def.EnumOptions ?? new List<string>())
|
||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||
.Select(x => x.Trim())
|
||
.ToList();
|
||
|
||
var arr = list.ToArray();
|
||
if (arr.Length > 0)
|
||
{
|
||
cb.Items.AddRange(arr);
|
||
var idx = Array.FindIndex(arr, x => string.Equals(x, initialValue, StringComparison.OrdinalIgnoreCase));
|
||
if (idx >= 0)
|
||
{
|
||
cb.SelectedIndex = idx;
|
||
}
|
||
}
|
||
if (!string.IsNullOrWhiteSpace(initialValue))
|
||
{
|
||
cb.Text = initialValue;
|
||
}
|
||
|
||
// Auto-match functionality: filter items as user types (Contains logic)
|
||
cb.TextUpdate += (s, e) =>
|
||
{
|
||
var text = cb.Text;
|
||
var selectionStart = cb.SelectionStart;
|
||
var selectionLength = cb.SelectionLength;
|
||
|
||
// Empty text -> Show all? Or keep filtered?
|
||
// Usually empty -> Show all.
|
||
var matches = string.IsNullOrEmpty(text)
|
||
? arr
|
||
: list.Where(x => x.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0).ToArray();
|
||
|
||
cb.BeginUpdate();
|
||
cb.Items.Clear();
|
||
if (matches.Length > 0)
|
||
{
|
||
cb.Items.AddRange(matches);
|
||
}
|
||
cb.EndUpdate();
|
||
|
||
// Restoring SelectionStart is critical because updating Items resets it
|
||
cb.SelectionStart = selectionStart;
|
||
cb.SelectionLength = selectionLength;
|
||
|
||
if (!cb.DroppedDown)
|
||
{
|
||
cb.DroppedDown = true;
|
||
}
|
||
|
||
// Keep the mouse cursor visible/normal
|
||
Cursor.Current = Cursors.Default;
|
||
};
|
||
|
||
return cb;
|
||
}
|
||
|
||
private Control CreateNumber(ParamDefinition def, bool isInt, string overrideValue)
|
||
{
|
||
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;
|
||
}
|
||
|
||
// 使用 overrideValue 或 def.DefaultValue
|
||
// 但 CreateNumber 被调用时,overrideValue 已经是处理过的“最终初始值”
|
||
if (TryParseDecimal(overrideValue, 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 (key.EndsWith("_ShowInNote", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
if (ctrl is CheckBox chk)
|
||
{
|
||
bag.Set(key, chk.Checked ? "1" : "0");
|
||
}
|
||
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);
|
||
|
||
if (key.StartsWith("Hardness"))
|
||
{
|
||
// Logging removed
|
||
}
|
||
}
|
||
|
||
Result = bag;
|
||
DialogResult = DialogResult.OK;
|
||
Close();
|
||
}
|
||
|
||
private static string ReadValue(Control ctrl, ParamDefinition def)
|
||
{
|
||
if (ctrl is CheckBox chk)
|
||
{
|
||
return chk.Checked ? "true" : "false";
|
||
}
|
||
if (ctrl is NumericUpDown num)
|
||
{
|
||
return num.Value.ToString(CultureInfo.InvariantCulture);
|
||
}
|
||
if (ctrl is ComboBox cb)
|
||
{
|
||
// Handles both Enum type and String type rendered as ComboBox
|
||
return (cb.Text ?? string.Empty).Trim();
|
||
}
|
||
if (ctrl is TextBox tb)
|
||
{
|
||
return (tb.Text ?? string.Empty).Trim();
|
||
}
|
||
|
||
return string.Empty;
|
||
}
|
||
}
|
||
}
|