2025-08-20 12:10:13 +08:00
|
|
|
|
using System.Windows.Input;
|
|
|
|
|
|
|
2026-01-02 17:30:41 +08:00
|
|
|
|
namespace Melskin.Controls;
|
2025-08-20 12:10:13 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 用于输入和显示数值的控件。该控件继承自TextBox,但增加了对数值范围、小数位数等的控制。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class NumericBox : TextBox
|
|
|
|
|
|
{
|
|
|
|
|
|
#region DependencyProperty
|
|
|
|
|
|
private const double VbCurvalue = 0; //当前值
|
|
|
|
|
|
private const double VbMinvalue = double.MinValue; //最小值
|
|
|
|
|
|
private const double VbMaxvalue = double.MaxValue; //最大值
|
|
|
|
|
|
private const int VbDigits = 15; //小数点精度
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static readonly DependencyProperty CurValueProperty;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 定义NumericBox控件的最小值属性。此依赖属性允许设置和获取控件中可以输入或显示的最小数值。
|
|
|
|
|
|
/// 该属性的默认值为double类型的最小可能值,即double.MinValue。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static readonly DependencyProperty MinValueProperty;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 代表NumericBox控件中可以设置的最大数值属性。此属性允许用户定义NumericBox实例能够接受的最大值。
|
|
|
|
|
|
/// 默认情况下,该属性的值被设置为double类型的最大值,即表示在没有特别指定的情况下,控件理论上支持所有小于或等于double.MaxValue的数值作为最大值。
|
|
|
|
|
|
/// 可以通过设置此属性来限制用户输入或程序设定的数值不超过某个特定的上限。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static readonly DependencyProperty MaxValueProperty;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 表示NumericBox控件中允许显示的小数位数的依赖属性。该属性定义了数值精度,用户可以通过设置此属性来控制数字显示时的小数点后位数。
|
|
|
|
|
|
/// 属性值的有效范围是0到15。如果设置的值小于等于0,则默认为0;若大于15,则会被限制为最大值15。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static readonly DependencyProperty DigitsProperty;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取或设置当前数值框中的值。该属性会根据设定的最小值(MinValue)、最大值(MaxValue)和小数位数(Digits)自动调整输入值,确保其处于允许的范围内,并且格式正确。
|
|
|
|
|
|
/// 当尝试设置超出范围的值时,CurValue 会自动修正为最近的有效边界值;同时,它也会根据 Digits 属性指定的小数位数对数值进行四舍五入处理。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public double CurValue
|
|
|
|
|
|
{
|
|
|
|
|
|
get => (double)GetValue(CurValueProperty);
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
var v = value;
|
|
|
|
|
|
if(value < MinValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
v = MinValue;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if(value > MaxValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
v = MaxValue;
|
|
|
|
|
|
}
|
|
|
|
|
|
v = Math.Round(v, Digits);
|
|
|
|
|
|
|
|
|
|
|
|
SetValue(CurValueProperty, v);
|
|
|
|
|
|
// if do not go into OnCurValueChanged then force update ui
|
|
|
|
|
|
if(v != value)
|
|
|
|
|
|
{
|
|
|
|
|
|
this.Text = v.ToString();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取或设置NumericBox控件的最小值。此属性允许定义控件中可以输入或显示的最小数值。
|
|
|
|
|
|
/// 通过设置此属性,可以确保用户不能输入低于指定值的数值。默认情况下,该属性的值被设定为double类型的最小可能值,即double.MinValue。
|
|
|
|
|
|
/// 当尝试将当前值CurValue设置为小于MinValue时,系统会自动将CurValue调整到MinValue。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public double MinValue
|
|
|
|
|
|
{
|
|
|
|
|
|
get => (double)GetValue(MinValueProperty);
|
|
|
|
|
|
set => SetValue(MinValueProperty, value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 代表NumericBox控件中可以设置的最大数值属性。此依赖属性允许用户定义NumericBox实例能够接受的最大值。
|
|
|
|
|
|
/// 默认情况下,该属性的值被设置为double类型的最大可能值,即double.MaxValue。
|
|
|
|
|
|
/// 通过设置此属性,可以限制用户输入或程序设定的数值不超过某个特定的上限。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public double MaxValue
|
|
|
|
|
|
{
|
|
|
|
|
|
get => (double)GetValue(MaxValueProperty);
|
|
|
|
|
|
set => SetValue(MaxValueProperty, value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 表示NumericBox控件中允许显示的小数位数的属性。通过设置此属性,可以控制数值在显示时小数点后的位数。
|
|
|
|
|
|
/// 属性值的有效范围是0到15。如果设置的值小于等于0,则默认为0;若大于15,则会被限制为最大值15。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public int Digits
|
|
|
|
|
|
{
|
|
|
|
|
|
get => (int)GetValue(DigitsProperty);
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
var digits = value;
|
|
|
|
|
|
if(digits <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
digits = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
if(digits > 15)
|
|
|
|
|
|
{
|
|
|
|
|
|
digits = 15;
|
|
|
|
|
|
}
|
|
|
|
|
|
SetValue(DigitsProperty, value);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static NumericBox()
|
|
|
|
|
|
{
|
|
|
|
|
|
DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericBox), new FrameworkPropertyMetadata(typeof(NumericBox)));
|
|
|
|
|
|
|
|
|
|
|
|
var metadata = new FrameworkPropertyMetadata(VbCurvalue, OnCurValueChanged);
|
|
|
|
|
|
CurValueProperty = DependencyProperty.Register("CurValue", typeof(double), typeof(NumericBox), metadata);
|
|
|
|
|
|
|
|
|
|
|
|
metadata = new FrameworkPropertyMetadata(VbMinvalue, OnMinValueChanged);
|
|
|
|
|
|
MinValueProperty = DependencyProperty.Register("MinValue", typeof(double), typeof(NumericBox), metadata);
|
|
|
|
|
|
|
|
|
|
|
|
metadata = new FrameworkPropertyMetadata(VbMaxvalue, OnMaxValueChanged);
|
|
|
|
|
|
MaxValueProperty = DependencyProperty.Register("MaxValue", typeof(double), typeof(NumericBox), metadata);
|
|
|
|
|
|
|
|
|
|
|
|
metadata = new FrameworkPropertyMetadata(VbDigits, OnDigitsChanged);
|
|
|
|
|
|
DigitsProperty = DependencyProperty.Register("Digits", typeof(int), typeof(NumericBox), metadata);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void OnCurValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
var value = (double)e.NewValue;
|
|
|
|
|
|
var numericBox = (NumericBox)sender;
|
|
|
|
|
|
numericBox.Text = value.ToString();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void OnMinValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
var minValue = (double)e.NewValue;
|
|
|
|
|
|
var numericBox = (NumericBox)sender;
|
|
|
|
|
|
numericBox.MinValue = minValue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void OnMaxValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
var maxValue = (double)e.NewValue;
|
|
|
|
|
|
var numericBox = (NumericBox)sender;
|
|
|
|
|
|
numericBox.MaxValue = maxValue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void OnDigitsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
var digits = (int)e.NewValue;
|
|
|
|
|
|
var numericBox = (NumericBox)sender;
|
|
|
|
|
|
numericBox.CurValue = Math.Round(numericBox.CurValue, digits);
|
|
|
|
|
|
numericBox.MinValue = Math.Round(numericBox.MinValue, digits);
|
|
|
|
|
|
numericBox.MaxValue = Math.Round(numericBox.MaxValue, digits);
|
|
|
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 用于输入和显示数值的控件。该控件继承自TextBox,但增加了对数值范围、小数位数等的控制。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public NumericBox()
|
|
|
|
|
|
{
|
|
|
|
|
|
this.TextChanged += NumericBox_TextChanged;
|
|
|
|
|
|
this.PreviewKeyDown += NumericBox_KeyDown;
|
|
|
|
|
|
this.LostFocus += NumericBox_LostFocus;
|
|
|
|
|
|
DataObject.AddPastingHandler(this, NumericBox_Pasting);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void NumericBox_TextChanged(object sender, TextChangedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
var numericBox = sender as NumericBox;
|
|
|
|
|
|
if(string.IsNullOrEmpty(numericBox?.Text))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TrimZeroStart();
|
|
|
|
|
|
|
|
|
|
|
|
if(!Double.TryParse(numericBox?.Text, out var value))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(value != this.CurValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
this.CurValue = value;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void NumericBox_KeyDown(object sender, KeyEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
var key = e.Key;
|
|
|
|
|
|
if(IsControlKeys(key))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(IsDigit(key))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(IsSubtract(key)) //-
|
|
|
|
|
|
{
|
|
|
|
|
|
if (sender is not TextBox textBox) return;
|
|
|
|
|
|
var str = textBox.Text;
|
|
|
|
|
|
if(str.Length > 0 && textBox.SelectionStart != 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else if(IsDot(key)) //point
|
|
|
|
|
|
{
|
|
|
|
|
|
if(this.Digits > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (sender is not TextBox textBox) return;
|
|
|
|
|
|
var str = textBox.Text;
|
|
|
|
|
|
if(str.Contains('.') || str == "-")
|
|
|
|
|
|
{
|
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void NumericBox_LostFocus(object sender, RoutedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
var numericBox = sender as NumericBox;
|
|
|
|
|
|
if (!string.IsNullOrEmpty(numericBox?.Text)) return;
|
|
|
|
|
|
if (numericBox != null) numericBox.Text = this.CurValue.ToString();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void NumericBox_Pasting(object sender, DataObjectPastingEventArgs e) { e.CancelCommand(); }
|
|
|
|
|
|
|
|
|
|
|
|
private static readonly List<Key> ControlKeys =
|
|
|
|
|
|
[
|
|
|
|
|
|
Key.Back,
|
|
|
|
|
|
Key.CapsLock,
|
|
|
|
|
|
Key.Down,
|
|
|
|
|
|
Key.End,
|
|
|
|
|
|
Key.Enter,
|
|
|
|
|
|
Key.Escape,
|
|
|
|
|
|
Key.Home,
|
|
|
|
|
|
Key.Insert,
|
|
|
|
|
|
Key.Left,
|
|
|
|
|
|
Key.PageDown,
|
|
|
|
|
|
Key.PageUp,
|
|
|
|
|
|
Key.Right,
|
|
|
|
|
|
Key.Tab,
|
|
|
|
|
|
Key.Up
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 判断给定的键是否为控制键。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="key">要检查的键。</param>
|
|
|
|
|
|
/// <returns>如果键是控制键,则返回true;否则返回false。</returns>
|
|
|
|
|
|
public static bool IsControlKeys(Key key)
|
|
|
|
|
|
{
|
|
|
|
|
|
return ControlKeys.Contains(key);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 判断给定的键是否为数字键。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="key">要检查的键。</param>
|
|
|
|
|
|
/// <returns>如果键是数字键,则返回true;否则返回false。</returns>
|
|
|
|
|
|
public static bool IsDigit(Key key)
|
|
|
|
|
|
{
|
|
|
|
|
|
var shiftKey = (Keyboard.Modifiers & ModifierKeys.Shift) != 0;
|
|
|
|
|
|
bool retVal;
|
|
|
|
|
|
if(key >= Key.D0 && key <= Key.D9 && !shiftKey)
|
|
|
|
|
|
{
|
|
|
|
|
|
retVal = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
retVal = key >= Key.NumPad0 && key <= Key.NumPad9;
|
|
|
|
|
|
}
|
|
|
|
|
|
return retVal;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 判断给定的键是否为小数点键。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="key">要检查的键。</param>
|
|
|
|
|
|
/// <returns>如果键是小数点键,则返回true;否则返回false。</returns>
|
|
|
|
|
|
public static bool IsDot(Key key)
|
|
|
|
|
|
{
|
|
|
|
|
|
var shiftKey = (Keyboard.Modifiers & ModifierKeys.Shift) != 0;
|
|
|
|
|
|
var flag = false;
|
|
|
|
|
|
if(key == Key.Decimal)
|
|
|
|
|
|
{
|
|
|
|
|
|
flag = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if(key == Key.OemPeriod && !shiftKey)
|
|
|
|
|
|
{
|
|
|
|
|
|
flag = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return flag;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 判断给定的键是否为减号键。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="key">要检查的键。</param>
|
|
|
|
|
|
/// <returns>如果键是减号键,则返回true;否则返回false。</returns>
|
|
|
|
|
|
public static bool IsSubtract(Key key)
|
|
|
|
|
|
{
|
|
|
|
|
|
var shiftKey = (Keyboard.Modifiers & ModifierKeys.Shift) != 0;
|
|
|
|
|
|
var flag = false;
|
|
|
|
|
|
if(key == Key.Subtract)
|
|
|
|
|
|
{
|
|
|
|
|
|
flag = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if(key == Key.OemMinus && !shiftKey)
|
|
|
|
|
|
{
|
|
|
|
|
|
flag = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return flag;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void TrimZeroStart()
|
|
|
|
|
|
{
|
|
|
|
|
|
if(this.Text.Length == 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var zeroCount = 0;
|
|
|
|
|
|
foreach(var c in this.Text)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(c == '0')
|
|
|
|
|
|
{
|
|
|
|
|
|
zeroCount++;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if(zeroCount == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(this.Text.Contains('.'))
|
|
|
|
|
|
{
|
|
|
|
|
|
if(this.Text[zeroCount] != '.')
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
else if(zeroCount > 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else if(zeroCount > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|