using System; using System.ComponentModel; using System.Drawing; using System.Windows.Forms; namespace TemuKJD.UserComponent { [DefaultEvent(nameof(ValueChanged))] public class UcNumericRange : UserControl { private const decimal DefaultMaximum = 1000000m; private const int NormalWidth = 220; private const int CompactWidth = 112; private readonly Label separator = new Label { Text = "至", AutoSize = true, Anchor = AnchorStyles.None }; private readonly NumericUpDown lower = CreateEditor("起始值"); private readonly NumericUpDown upper = CreateEditor("结束值"); private bool updating; public UcNumericRange() { upper.Value = DefaultMaximum; var layout = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 3, RowCount = 1 }; layout.RowStyles.Add(new RowStyle(SizeType.Percent, 100)); layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50)); layout.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize)); layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50)); layout.Controls.Add(CreateCell(lower), 0, 0); layout.Controls.Add(separator, 1, 0); layout.Controls.Add(CreateCell(upper), 2, 0); Controls.Add(layout); Size = new Size(NormalWidth, lower.PreferredHeight + 2); lower.ValueChanged += (s, e) => UpdateRange(lower, upper); upper.ValueChanged += (s, e) => UpdateRange(upper, lower); } [Description("两个数值之间显示的文字。"), DefaultValue("至")] public string SeparatorText { get => separator.Text; set => separator.Text = value; } [Description("使用窄布局并隐藏微调按钮,仍可用方向键调整数值。"), DefaultValue(false)] public bool Compact { get => separator.Margin == Padding.Empty; set { if (value == Compact) return; separator.Margin = value ? Padding.Empty : new Padding(3, 0, 3, 0); SetCompactBorder(lower, value); SetCompactBorder(upper, value); if (Dock == DockStyle.None && Width == (value ? NormalWidth : CompactWidth)) Width = value ? CompactWidth : NormalWidth; } } private static void SetCompactBorder(NumericUpDown editor, bool compact) { var cell = (Panel)editor.Parent; editor.BorderStyle = compact ? BorderStyle.None : BorderStyle.Fixed3D; cell.BorderStyle = compact ? BorderStyle.FixedSingle : BorderStyle.None; cell.BackColor = compact ? editor.BackColor : Color.Empty; cell.PerformLayout(); } private Panel CreateCell(NumericUpDown editor) { var cell = new Panel { Dock = DockStyle.Fill, Margin = Padding.Empty }; cell.Controls.Add(editor); var buttons = editor.Controls[0] is TextBox ? editor.Controls[1] : editor.Controls[0]; // 紧凑模式将右侧按钮移出可见区域,保留原生数字输入和键盘操作。 cell.Layout += (s, e) => { int clippedWidth = Compact ? editor.Width - buttons.Left : 0; editor.Width = cell.ClientSize.Width + clippedWidth; editor.Top = Compact ? Math.Max(0, (cell.ClientSize.Height - editor.Height) / 2) : 0; }; return cell; } private static NumericUpDown CreateEditor(string name) { return new NumericUpDown { AccessibleName = name, Margin = Padding.Empty, Maximum = DefaultMaximum }; } [Description("允许输入的最小值。"), DefaultValue(typeof(decimal), "0")] public decimal Minimum { get => lower.Minimum; set => SetLimits(value, Math.Max(value, Maximum)); } [Description("允许输入的最大值。"), DefaultValue(typeof(decimal), "1000000")] public decimal Maximum { get => lower.Maximum; set => SetLimits(Math.Min(value, Minimum), value); } [Description("选中范围的起始值。"), DefaultValue(typeof(decimal), "0")] public decimal LowerValue { get => lower.Value; set => lower.Value = value; } [Description("选中范围的结束值。"), DefaultValue(typeof(decimal), "1000000")] public decimal UpperValue { get => upper.Value; set => upper.Value = value; } [Description("数字输入框显示的小数位数。"), DefaultValue(0)] public int DecimalPlaces { get => lower.DecimalPlaces; set { if (value < 0 || value > 28) throw new ArgumentOutOfRangeException(nameof(value)); lower.DecimalPlaces = upper.DecimalPlaces = value; decimal increment = 1m; for (int i = 0; i < value; i++) increment /= 10m; lower.Increment = upper.Increment = increment; } } public event EventHandler ValueChanged; private void SetLimits(decimal minimum, decimal maximum) { decimal oldLower = LowerValue, oldUpper = UpperValue; updating = true; try { lower.Minimum = upper.Minimum = minimum; lower.Maximum = upper.Maximum = maximum; } finally { updating = false; } if (LowerValue != oldLower || UpperValue != oldUpper) ValueChanged?.Invoke(this, EventArgs.Empty); } private void UpdateRange(NumericUpDown edited, NumericUpDown other) { if (updating) return; updating = true; try { if (LowerValue > UpperValue) other.Value = edited.Value; } finally { updating = false; } ValueChanged?.Invoke(this, EventArgs.Empty); } } }