From 164757c915ec36e422231479c24676b0640ee0bd Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 3 Jun 2022 13:41:09 +0200 Subject: [PATCH 1/2] Make NumericUpDown nullable --- .../NumericUpDown/NumericUpDown.cs | 111 +++++++++++------- .../NumericUpDownValueChangedEventArgs.cs | 6 +- 2 files changed, 73 insertions(+), 44 deletions(-) diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs index 4d86a0f17c..705e68e3ea 100644 --- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs +++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs @@ -107,8 +107,8 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly DirectProperty ValueProperty = - AvaloniaProperty.RegisterDirect(nameof(Value), updown => updown.Value, + public static readonly DirectProperty ValueProperty = + AvaloniaProperty.RegisterDirect(nameof(Value), updown => updown.Value, (updown, v) => updown.Value = v, defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true); /// @@ -131,7 +131,7 @@ namespace Avalonia.Controls private IDisposable? _textBoxTextChangedSubscription; - private decimal _value; + private decimal? _value; private string? _text; private bool _internalValueSet; private bool _clipValueToMinMax; @@ -277,7 +277,7 @@ namespace Avalonia.Controls /// /// Gets or sets the value. /// - public decimal Value + public decimal? Value { get { return _value; } set @@ -351,7 +351,7 @@ namespace Avalonia.Controls /// protected override void OnLostFocus(RoutedEventArgs e) { - CommitInput(); + CommitInput(true); base.OnLostFocus(e); } @@ -489,9 +489,9 @@ namespace Avalonia.Controls { SetValidSpinDirection(); } - if (ClipValueToMinMax) + if (ClipValueToMinMax && Value.HasValue) { - Value = MathUtilities.Clamp(Value, Minimum, Maximum); + Value = MathUtilities.Clamp(Value.Value, Minimum, Maximum); } } @@ -506,9 +506,9 @@ namespace Avalonia.Controls { SetValidSpinDirection(); } - if (ClipValueToMinMax) + if (ClipValueToMinMax && Value.HasValue) { - Value = MathUtilities.Clamp(Value, Minimum, Maximum); + Value = MathUtilities.Clamp(Value.Value, Minimum, Maximum); } } @@ -530,7 +530,7 @@ namespace Avalonia.Controls /// /// The old value. /// The new value. - protected virtual void OnValueChanged(decimal oldValue, decimal newValue) + protected virtual void OnValueChanged(decimal? oldValue, decimal? newValue) { if (!_internalValueSet && IsInitialized) { @@ -573,7 +573,7 @@ namespace Avalonia.Controls /// Called when the property has to be coerced. /// /// The value. - protected virtual decimal OnCoerceValue(decimal baseValue) + protected virtual decimal? OnCoerceValue(decimal? baseValue) { return baseValue; } @@ -607,7 +607,7 @@ namespace Avalonia.Controls /// /// The old value. /// The new value. - protected virtual void RaiseValueChangedEvent(decimal oldValue, decimal newValue) + protected virtual void RaiseValueChangedEvent(decimal? oldValue, decimal? newValue) { var e = new NumericUpDownValueChangedEventArgs(ValueChangedEvent, oldValue, newValue); RaiseEvent(e); @@ -616,9 +616,9 @@ namespace Avalonia.Controls /// /// Converts the formatted text to a value. /// - private decimal ConvertTextToValue(string text) + private decimal? ConvertTextToValue(string? text) { - decimal result = 0; + decimal? result = null; if (string.IsNullOrEmpty(text)) { @@ -635,9 +635,9 @@ namespace Avalonia.Controls result = ConvertTextToValueCore(currentValueText, text); - if (ClipValueToMinMax) + if (ClipValueToMinMax && result.HasValue) { - return MathUtilities.Clamp(result, Minimum, Maximum); + return MathUtilities.Clamp(result.Value, Minimum, Maximum); } ValidateMinMax(result); @@ -649,7 +649,7 @@ namespace Avalonia.Controls /// Converts the value to formatted text. /// /// - private string ConvertValueToText() + private string? ConvertValueToText() { //Manage FormatString of type "{}{0:N2} °" (in xaml) or "{0:N2} °" in code-behind. if (FormatString.Contains("{0")) @@ -657,7 +657,7 @@ namespace Avalonia.Controls return string.Format(NumberFormat, FormatString, Value); } - return Value.ToString(FormatString, NumberFormat); + return Value?.ToString(FormatString, NumberFormat); } /// @@ -665,7 +665,16 @@ namespace Avalonia.Controls /// private void OnIncrement() { - var result = Value + Increment; + decimal result; + if (Value.HasValue) + { + result = Value.Value + Increment; + } + else + { + result = Minimum; + } + Value = MathUtilities.Clamp(result, Minimum, Maximum); } @@ -674,7 +683,17 @@ namespace Avalonia.Controls /// private void OnDecrement() { - var result = Value - Increment; + decimal result; + + if (Value.HasValue) + { + result = Value.Value - Increment; + } + else + { + result = Maximum; + } + Value = MathUtilities.Clamp(result, Minimum, Maximum); } @@ -688,6 +707,11 @@ namespace Avalonia.Controls // Zero increment always prevents spin. if (Increment != 0 && !IsReadOnly) { + if (!Value.HasValue) + { + validDirections = ValidSpinDirections.Increase | ValidSpinDirections.Decrease; + } + if (Value < Maximum) { validDirections = validDirections | ValidSpinDirections.Increase; @@ -825,13 +849,13 @@ namespace Avalonia.Controls { if (e.Sender is NumericUpDown upDown) { - var oldValue = (decimal)e.OldValue!; - var newValue = (decimal)e.NewValue!; + var oldValue = (decimal?)e.OldValue; + var newValue = (decimal?)e.NewValue; upDown.OnValueChanged(oldValue, newValue); } } - private void SetValueInternal(decimal value) + private void SetValueInternal(decimal? value) { _internalValueSet = true; try @@ -946,9 +970,9 @@ namespace Avalonia.Controls remove { RemoveHandler(ValueChangedEvent, value); } } - private bool CommitInput() + private bool CommitInput(bool forceTextUpdate = false) { - return SyncTextAndValueProperties(true, Text); + return SyncTextAndValueProperties(true, Text, forceTextUpdate); } /// @@ -978,28 +1002,24 @@ namespace Avalonia.Controls { if (updateValueFromText) { - if (!string.IsNullOrEmpty(text)) + try { - try + var newValue = ConvertTextToValue(text); + if (!Equals(newValue, Value)) { - var newValue = ConvertTextToValue(text); - if (!Equals(newValue, Value)) - { - SetValueInternal(newValue); - } - } - catch - { - parsedTextIsValid = false; + SetValueInternal(newValue); } } + catch + { + parsedTextIsValid = false; + } } // Do not touch the ongoing text input from user. if (!_isTextChangedFromUI) { - var keepEmpty = !forceTextUpdate && string.IsNullOrEmpty(Text); - if (!keepEmpty) + if (forceTextUpdate) { var newText = ConvertValueToText(); if (!Equals(Text, newText)) @@ -1036,10 +1056,15 @@ namespace Avalonia.Controls return parsedTextIsValid; } - private decimal ConvertTextToValueCore(string currentValueText, string text) + private decimal? ConvertTextToValueCore(string? currentValueText, string? text) { decimal result; + if (string.IsNullOrEmpty(text)) + { + return null; + } + if (IsPercent(FormatString)) { result = ParsePercent(text, NumberFormat); @@ -1052,7 +1077,7 @@ namespace Avalonia.Controls var shouldThrow = true; // Check if CurrentValueText is also failing => it also contains special characters. ex : 90° - if (!decimal.TryParse(currentValueText, ParsingNumberStyle, NumberFormat, out var _)) + if (!string.IsNullOrEmpty(currentValueText) && !decimal.TryParse(currentValueText, ParsingNumberStyle, NumberFormat, out var _)) { // extract non-digit characters var currentValueTextSpecialCharacters = currentValueText.Where(c => !char.IsDigit(c)); @@ -1082,8 +1107,12 @@ namespace Avalonia.Controls return result; } - private void ValidateMinMax(decimal value) + private void ValidateMinMax(decimal? value) { + if (!value.HasValue) + { + return; + } if (value < Minimum) { throw new ArgumentOutOfRangeException(nameof(value), string.Format("Value must be greater than Minimum value of {0}", Minimum)); diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDownValueChangedEventArgs.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDownValueChangedEventArgs.cs index 9b467d682c..af835541ae 100644 --- a/src/Avalonia.Controls/NumericUpDown/NumericUpDownValueChangedEventArgs.cs +++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDownValueChangedEventArgs.cs @@ -4,13 +4,13 @@ namespace Avalonia.Controls { public class NumericUpDownValueChangedEventArgs : RoutedEventArgs { - public NumericUpDownValueChangedEventArgs(RoutedEvent routedEvent, decimal oldValue, decimal newValue) : base(routedEvent) + public NumericUpDownValueChangedEventArgs(RoutedEvent routedEvent, decimal? oldValue, decimal? newValue) : base(routedEvent) { OldValue = oldValue; NewValue = newValue; } - public decimal OldValue { get; } - public decimal NewValue { get; } + public decimal? OldValue { get; } + public decimal? NewValue { get; } } } From 43cf66fc49fa6f6e431e26fd747d2bb6edd02eae Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 3 Jun 2022 13:41:28 +0200 Subject: [PATCH 2/2] Update demo to reflect actual selected value --- samples/ControlCatalog/Pages/NumericUpDownPage.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml index 9ddc6b6228..e32632dac2 100644 --- a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml +++ b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml @@ -65,7 +65,7 @@ Margin="2" HorizontalAlignment="Center"/> Value: -