using System; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Data; using System.Windows.Input; using System.Windows.Media; using HandyControl.Data; using HandyControl.Interactivity; using HandyControl.Tools; namespace HandyControl.Controls { /// /// 数值选择控件 /// [TemplatePart(Name = ElementTextBox, Type = typeof(TextBox))] [TemplatePart(Name = ElementErrorTip, Type = typeof(UIElement))] public class NumericUpDown : Control, IDataInput { #region Constants private const string ElementTextBox = "PART_TextBox"; private const string ElementErrorTip = "PART_ErrorTip"; #endregion Constants #region Data private TextBox _textBox; private UIElement _errorTip; private bool _updateText; #endregion Data public NumericUpDown() { CommandBindings.Add(new CommandBinding(ControlCommands.Prev, (s, e) => { if (IsReadOnly) return; Value += Increment; })); CommandBindings.Add(new CommandBinding(ControlCommands.Next, (s, e) => { if (IsReadOnly) return; Value -= Increment; })); CommandBindings.Add(new CommandBinding(ControlCommands.Clear, (s, e) => { if (IsReadOnly) return; SetCurrentValue(ValueProperty, ValueBoxes.Double0Box); })); Loaded += (s, e) => OnApplyTemplate(); } protected override void OnGotFocus(RoutedEventArgs e) { base.OnGotFocus(e); if (_textBox != null) { _textBox?.Focus(); _textBox.Select(_textBox.Text.Length, 0); } } public override void OnApplyTemplate() { if (_textBox != null) { TextCompositionManager.RemovePreviewTextInputHandler(_textBox, PreviewTextInputHandler); _textBox.TextChanged -= TextBox_TextChanged; _textBox.PreviewKeyDown -= TextBox_PreviewKeyDown; _textBox.LostFocus -= TextBox_LostFocus; } base.OnApplyTemplate(); _textBox = GetTemplateChild(ElementTextBox) as TextBox; _errorTip = GetTemplateChild(ElementErrorTip) as UIElement; if (_textBox != null) { _textBox.SetBinding(SelectionBrushProperty, new Binding(SelectionBrushProperty.Name) { Source = this }); #if !(NET40 || NET45 || NET451 || NET452 || NET46 || NET461 || NET462 || NET47 || NET471 || NET472) _textBox.SetBinding(SelectionTextBrushProperty, new Binding(SelectionTextBrushProperty.Name) { Source = this }); #endif _textBox.SetBinding(SelectionOpacityProperty, new Binding(SelectionOpacityProperty.Name) { Source = this }); _textBox.SetBinding(CaretBrushProperty, new Binding(CaretBrushProperty.Name) { Source = this }); TextCompositionManager.AddPreviewTextInputHandler(_textBox, PreviewTextInputHandler); _textBox.TextChanged += TextBox_TextChanged; _textBox.PreviewKeyDown += TextBox_PreviewKeyDown; _textBox.LostFocus += TextBox_LostFocus; _textBox.Text = CurrentText; } } private void TextBox_TextChanged(object sender, TextChangedEventArgs e) => UpdateData(); private void UpdateData() { if (!VerifyData()) { _updateText = true; return; } _updateText = false; if (string.IsNullOrWhiteSpace(_textBox.Text)) { Value = 0; SetCurrentValue(ErrorStrProperty, string.Empty); SetCurrentValue(IsErrorProperty, ValueBoxes.FalseBox); } else if (double.TryParse(_textBox.Text, out var value)) { Value = value; if (Validation.GetHasError(this)) { SetCurrentValue(ErrorStrProperty, Validation.GetErrors(this)[0].ErrorContent); SetCurrentValue(IsErrorProperty, ValueBoxes.TrueBox); } } _updateText = true; } private void PreviewTextInputHandler(object sender, TextCompositionEventArgs e) => UpdateData(); private void TextBox_LostFocus(object sender, RoutedEventArgs e) { if (IsError && _errorTip != null) return; _textBox.Text = CurrentText; } private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e) { if (IsReadOnly) return; if (e.Key == Key.Up) { Value += Increment; } else if (e.Key == Key.Down) { Value -= Increment; } } protected override void OnMouseWheel(MouseWheelEventArgs e) { base.OnMouseWheel(e); if (_textBox.IsFocused && !IsReadOnly) { Value += e.Delta > 0 ? Increment : -Increment; e.Handled = true; } } private string CurrentText => string.IsNullOrWhiteSpace(ValueFormat) ? DecimalPlaces.HasValue ? Value.ToString($"#0.{new string('0', DecimalPlaces.Value)}") : Value.ToString() : Value.ToString(ValueFormat); protected virtual void OnValueChanged(FunctionEventArgs e) => RaiseEvent(e); /// /// 值改变事件 /// public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, typeof(EventHandler>), typeof(NumericUpDown)); /// /// 值改变事件 /// public event EventHandler> ValueChanged { add => AddHandler(ValueChangedEvent, value); remove => RemoveHandler(ValueChangedEvent, value); } /// /// 当前值 /// public static readonly DependencyProperty ValueProperty = DependencyProperty.Register( "Value", typeof(double), typeof(NumericUpDown), new FrameworkPropertyMetadata(ValueBoxes.Double0Box, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValueChanged, CoerceValue), ValidateHelper.IsInRangeOfDouble); private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var ctl = (NumericUpDown) d; var v = (double) e.NewValue; ctl.SetText(); ctl.OnValueChanged(new FunctionEventArgs(ValueChangedEvent, ctl) { Info = v }); } private void SetText() { if (_updateText && _textBox != null) { _textBox.Text = CurrentText; _textBox.Select(_textBox.Text.Length, 0); } } private static object CoerceValue(DependencyObject d, object basevalue) { var ctl = (NumericUpDown) d; var minimum = ctl.Minimum; var num = (double) basevalue; if (num < minimum) { ctl.Value = minimum; return minimum; } var maximum = ctl.Maximum; if (num > maximum) { ctl.Value = maximum; } ctl.SetText(); return num > maximum ? maximum : num; } /// /// 当前值 /// public double Value { get => (double) GetValue(ValueProperty); set => SetValue(ValueProperty, value); } /// /// 最大值 /// public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register( "Maximum", typeof(double), typeof(NumericUpDown), new PropertyMetadata(double.MaxValue, OnMaximumChanged, CoerceMaximum), ValidateHelper.IsInRangeOfDouble); private static void OnMaximumChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var ctl = (NumericUpDown) d; ctl.CoerceValue(MinimumProperty); ctl.CoerceValue(ValueProperty); } private static object CoerceMaximum(DependencyObject d, object basevalue) { var minimum = ((NumericUpDown) d).Minimum; return (double) basevalue < minimum ? minimum : basevalue; } /// /// 最大值 /// public double Maximum { get => (double) GetValue(MaximumProperty); set => SetValue(MaximumProperty, value); } /// /// 最小值 /// public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register( "Minimum", typeof(double), typeof(NumericUpDown), new PropertyMetadata(double.MinValue, OnMinimumChanged, CoerceMinimum), ValidateHelper.IsInRangeOfDouble); private static void OnMinimumChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var ctl = (NumericUpDown) d; ctl.CoerceValue(MaximumProperty); ctl.CoerceValue(ValueProperty); } private static object CoerceMinimum(DependencyObject d, object basevalue) { var maximum = ((NumericUpDown) d).Maximum; return (double) basevalue > maximum ? maximum : basevalue; } /// /// 最小值 /// public double Minimum { get => (double) GetValue(MinimumProperty); set => SetValue(MinimumProperty, value); } /// /// 指示每单击一下按钮时增加或减少的数量 /// public static readonly DependencyProperty IncrementProperty = DependencyProperty.Register( "Increment", typeof(double), typeof(NumericUpDown), new PropertyMetadata(ValueBoxes.Double1Box)); /// /// 指示每单击一下按钮时增加或减少的数量 /// public double Increment { get => (double) GetValue(IncrementProperty); set => SetValue(IncrementProperty, value); } /// /// 指示要显示的小数位数 /// public static readonly DependencyProperty DecimalPlacesProperty = DependencyProperty.Register( "DecimalPlaces", typeof(int?), typeof(NumericUpDown), new PropertyMetadata(default(int?))); /// /// 指示要显示的小数位数 /// public int? DecimalPlaces { get => (int?) GetValue(DecimalPlacesProperty); set => SetValue(DecimalPlacesProperty, value); } /// /// 指示要显示的数字的格式 /// public static readonly DependencyProperty ValueFormatProperty = DependencyProperty.Register( "ValueFormat", typeof(string), typeof(NumericUpDown), new PropertyMetadata(default(string))); /// /// 指示要显示的数字的格式,这将会覆盖 属性 /// public string ValueFormat { get => (string) GetValue(ValueFormatProperty); set => SetValue(ValueFormatProperty, value); } /// /// 是否显示上下调值按钮 /// internal static readonly DependencyProperty ShowUpDownButtonProperty = DependencyProperty.Register( "ShowUpDownButton", typeof(bool), typeof(NumericUpDown), new PropertyMetadata(ValueBoxes.TrueBox)); /// /// 是否显示上下调值按钮 /// internal bool ShowUpDownButton { get => (bool) GetValue(ShowUpDownButtonProperty); set => SetValue(ShowUpDownButtonProperty, ValueBoxes.BooleanBox(value)); } /// /// 数据是否错误 /// public static readonly DependencyProperty IsErrorProperty = DependencyProperty.Register( "IsError", typeof(bool), typeof(NumericUpDown), new PropertyMetadata(ValueBoxes.FalseBox)); public bool IsError { get => (bool) GetValue(IsErrorProperty); set => SetValue(IsErrorProperty, ValueBoxes.BooleanBox(value)); } /// /// 错误提示 /// public static readonly DependencyProperty ErrorStrProperty = DependencyProperty.Register( "ErrorStr", typeof(string), typeof(NumericUpDown), new PropertyMetadata(default(string))); public string ErrorStr { get => (string) GetValue(ErrorStrProperty); set => SetValue(ErrorStrProperty, value); } public static readonly DependencyPropertyKey TextTypePropertyKey = DependencyProperty.RegisterReadOnly("TextType", typeof(TextType), typeof(NumericUpDown), new PropertyMetadata(default(TextType))); /// /// 文本类型 /// public static readonly DependencyProperty TextTypeProperty = TextTypePropertyKey.DependencyProperty; public TextType TextType { get => (TextType) GetValue(TextTypeProperty); set => SetValue(TextTypeProperty, value); } /// /// 是否显示清除按钮 /// public static readonly DependencyProperty ShowClearButtonProperty = DependencyProperty.Register( "ShowClearButton", typeof(bool), typeof(NumericUpDown), new PropertyMetadata(ValueBoxes.FalseBox)); public bool ShowClearButton { get => (bool) GetValue(ShowClearButtonProperty); set => SetValue(ShowClearButtonProperty, ValueBoxes.BooleanBox(value)); } /// /// 标识 IsReadOnly 依赖属性。 /// public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register( "IsReadOnly", typeof(bool), typeof(NumericUpDown), new PropertyMetadata(ValueBoxes.FalseBox)); /// /// 获取或设置一个值,该值指示NumericUpDown是否只读。 /// public bool IsReadOnly { get => (bool) GetValue(IsReadOnlyProperty); set => SetValue(IsReadOnlyProperty, ValueBoxes.BooleanBox(value)); } public static readonly DependencyProperty SelectionBrushProperty = TextBoxBase.SelectionBrushProperty.AddOwner(typeof(NumericUpDown)); public Brush SelectionBrush { get => (Brush) GetValue(SelectionBrushProperty); set => SetValue(SelectionBrushProperty, value); } #if !(NET40 || NET45 || NET451 || NET452 || NET46 || NET461 || NET462 || NET47 || NET471 || NET472) public static readonly DependencyProperty SelectionTextBrushProperty = TextBoxBase.SelectionTextBrushProperty.AddOwner(typeof(NumericUpDown)); public Brush SelectionTextBrush { get => (Brush) GetValue(SelectionTextBrushProperty); set => SetValue(SelectionTextBrushProperty, value); } #endif public static readonly DependencyProperty SelectionOpacityProperty = TextBoxBase.SelectionOpacityProperty.AddOwner(typeof(NumericUpDown)); public double SelectionOpacity { get => (double) GetValue(SelectionOpacityProperty); set => SetValue(SelectionOpacityProperty, value); } public static readonly DependencyProperty CaretBrushProperty = TextBoxBase.CaretBrushProperty.AddOwner(typeof(NumericUpDown)); public Brush CaretBrush { get => (Brush) GetValue(CaretBrushProperty); set => SetValue(CaretBrushProperty, value); } public Func> VerifyFunc { get; set; } public virtual bool VerifyData() { OperationResult result; if (VerifyFunc != null) { result = VerifyFunc.Invoke(_textBox.Text); } else { if (!string.IsNullOrEmpty(_textBox.Text)) { if (double.TryParse(_textBox.Text, out var value)) { if (value < Minimum || value > Maximum) { result = OperationResult.Failed(Properties.Langs.Lang.OutOfRange); } else { result = OperationResult.Success(); } } else { result = OperationResult.Failed(Properties.Langs.Lang.FormatError); } } else if (InfoElement.GetNecessary(this)) { result = OperationResult.Failed(Properties.Langs.Lang.IsNecessary); } else { result = OperationResult.Success(); } } SetCurrentValue(ErrorStrProperty, result.Message); SetCurrentValue(IsErrorProperty, ValueBoxes.BooleanBox(!result.Data)); return result.Data; } } }