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:
-
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; }
}
}