Browse Source

Merge pull request #8259 from timunie/feature/MakeNumericUpDownNullable-2

Make NumericUpDown's Value nullable
pull/8365/head
Max Katz 4 years ago
committed by GitHub
parent
commit
717483653a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml
  2. 111
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  3. 6
      src/Avalonia.Controls/NumericUpDown/NumericUpDownValueChangedEventArgs.cs

2
samples/ControlCatalog/Pages/NumericUpDownPage.xaml

@ -65,7 +65,7 @@
Margin="2" HorizontalAlignment="Center"/>
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Value:</TextBlock>
<NumericUpDown Grid.Row="3" Grid.Column="1" Value="{Binding #upDown.Value}" VerticalAlignment="Center"
<NumericUpDown Grid.Row="3" Grid.Column="1" Value="{Binding DecimalValue}" VerticalAlignment="Center"
Margin="2" HorizontalAlignment="Center"/>

111
src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

@ -107,8 +107,8 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="Value"/> property.
/// </summary>
public static readonly DirectProperty<NumericUpDown, decimal> ValueProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, decimal>(nameof(Value), updown => updown.Value,
public static readonly DirectProperty<NumericUpDown, decimal?> ValueProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, decimal?>(nameof(Value), updown => updown.Value,
(updown, v) => updown.Value = v, defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true);
/// <summary>
@ -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
/// <summary>
/// Gets or sets the value.
/// </summary>
public decimal Value
public decimal? Value
{
get { return _value; }
set
@ -351,7 +351,7 @@ namespace Avalonia.Controls
/// <inheritdoc />
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
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
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 <see cref="Value"/> property has to be coerced.
/// </summary>
/// <param name="baseValue">The value.</param>
protected virtual decimal OnCoerceValue(decimal baseValue)
protected virtual decimal? OnCoerceValue(decimal? baseValue)
{
return baseValue;
}
@ -607,7 +607,7 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
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
/// <summary>
/// Converts the formatted text to a value.
/// </summary>
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.
/// </summary>
/// <returns></returns>
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);
}
/// <summary>
@ -665,7 +665,16 @@ namespace Avalonia.Controls
/// </summary>
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
/// </summary>
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);
}
/// <summary>
@ -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));

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

Loading…
Cancel
Save