diff --git a/src/Avalonia.Controls/NumericUpDown/AllowedSpecialValues.cs b/src/Avalonia.Controls/NumericUpDown/AllowedSpecialValues.cs
new file mode 100644
index 0000000000..a4ec5ecdaf
--- /dev/null
+++ b/src/Avalonia.Controls/NumericUpDown/AllowedSpecialValues.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Avalonia.Controls
+{
+ [Flags]
+ public enum AllowedSpecialValues
+ {
+ None = 0,
+ NaN = 1,
+ PositiveInfinity = 2,
+ NegativeInfinity = 4,
+ AnyInfinity = PositiveInfinity | NegativeInfinity,
+ Any = NaN | AnyInfinity
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/NumericUpDown/ByteUpDown.cs b/src/Avalonia.Controls/NumericUpDown/ByteUpDown.cs
new file mode 100644
index 0000000000..835abae773
--- /dev/null
+++ b/src/Avalonia.Controls/NumericUpDown/ByteUpDown.cs
@@ -0,0 +1,24 @@
+namespace Avalonia.Controls
+{
+ ///
+ public class ByteUpDown : CommonNumericUpDown
+ {
+ ///
+ /// Initializes static members of the class.
+ ///
+ static ByteUpDown() => UpdateMetadata(typeof(ByteUpDown), 1, byte.MinValue, byte.MaxValue);
+
+ ///
+ /// Initializes new instance of the class.
+ ///
+ public ByteUpDown() : base(byte.TryParse, decimal.ToByte, (v1, v2) => v1 < v2, (v1, v2) => v1 > v2)
+ {
+ }
+
+ ///
+ protected override byte IncrementValue(byte value, byte increment) => (byte)(value + increment);
+
+ ///
+ protected override byte DecrementValue(byte value, byte increment) => (byte)(value - increment);
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/NumericUpDown/CommonNumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/CommonNumericUpDown.cs
index 1ce2508b4a..eed792d9dd 100644
--- a/src/Avalonia.Controls/NumericUpDown/CommonNumericUpDown.cs
+++ b/src/Avalonia.Controls/NumericUpDown/CommonNumericUpDown.cs
@@ -6,7 +6,7 @@ using System.Linq;
namespace Avalonia.Controls
{
///
- /// Base class for controls that represents a TextBox with button spinners that allow incrementing and decrementing numeric values.
+ /// Base class for controls that represents a TextBox with button spinners that allow incrementing and decrementing nullable numeric values.
///
public abstract class CommonNumericUpDown : NumericUpDown where T : struct, IFormattable, IComparable
{
diff --git a/src/Avalonia.Controls/NumericUpDown/DecimalUpDown.cs b/src/Avalonia.Controls/NumericUpDown/DecimalUpDown.cs
new file mode 100644
index 0000000000..10cfa537d7
--- /dev/null
+++ b/src/Avalonia.Controls/NumericUpDown/DecimalUpDown.cs
@@ -0,0 +1,24 @@
+namespace Avalonia.Controls
+{
+ ///
+ public class DecimalUpDown : CommonNumericUpDown
+ {
+ ///
+ /// Initializes static members of the class.
+ ///
+ static DecimalUpDown() => UpdateMetadata(typeof(DecimalUpDown), 1m, decimal.MinValue, decimal.MaxValue);
+
+ ///
+ /// Initializes new instance of the class.
+ ///
+ public DecimalUpDown() : base(decimal.TryParse, d => d, (v1, v2) => v1 < v2, (v1, v2) => v1 > v2)
+ {
+ }
+
+ ///
+ protected override decimal IncrementValue(decimal value, decimal increment) => value + increment;
+
+ ///
+ protected override decimal DecrementValue(decimal value, decimal increment) => value - increment;
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/NumericUpDown/DoubleUpDown.cs b/src/Avalonia.Controls/NumericUpDown/DoubleUpDown.cs
new file mode 100644
index 0000000000..edee1247fb
--- /dev/null
+++ b/src/Avalonia.Controls/NumericUpDown/DoubleUpDown.cs
@@ -0,0 +1,109 @@
+using System;
+
+namespace Avalonia.Controls
+{
+ ///
+ public class DoubleUpDown : CommonNumericUpDown
+ {
+ ///
+ /// Defines the property.
+ ///
+ public static readonly DirectProperty AllowInputSpecialValuesProperty =
+ AvaloniaProperty.RegisterDirect(nameof(AllowInputSpecialValues),
+ updown => updown.AllowInputSpecialValues, (updown, v) => updown.AllowInputSpecialValues = v);
+
+ private AllowedSpecialValues _allowInputSpecialValues;
+
+ ///
+ /// Initializes static members of the class.
+ ///
+ static DoubleUpDown() => UpdateMetadata(typeof(DoubleUpDown), 1d, double.NegativeInfinity, double.PositiveInfinity);
+
+ ///
+ /// Initializes new instance of the class.
+ ///
+ public DoubleUpDown() : base(double.TryParse, decimal.ToDouble, (v1, v2) => v1 < v2, (v1, v2) => v1 > v2)
+ {
+ }
+
+ ///
+ /// Gets or sets a value representing the special values the user is allowed to input, such as "Infinity", "-Infinity" and "NaN" values.
+ ///
+ public AllowedSpecialValues AllowInputSpecialValues
+ {
+ get { return _allowInputSpecialValues; }
+ set { SetAndRaise(AllowInputSpecialValuesProperty, ref _allowInputSpecialValues, value); }
+ }
+
+ ///
+ protected override double IncrementValue(double value, double increment) => value + increment;
+
+ ///
+ protected override double DecrementValue(double value, double increment) => value - increment;
+
+ ///
+ protected override double? OnCoerceIncrement(double? baseValue)
+ {
+ if (baseValue.HasValue && double.IsNaN(baseValue.Value))
+ {
+ throw new ArgumentException("NaN is invalid for Increment.");
+ }
+ return base.OnCoerceIncrement(baseValue);
+ }
+
+ ///
+ protected override double? OnCoerceMaximum(double? baseValue)
+ {
+ if (baseValue.HasValue && double.IsNaN(baseValue.Value))
+ {
+ throw new ArgumentException("NaN is invalid for Maximum.");
+ }
+ return base.OnCoerceMaximum(baseValue);
+ }
+
+ ///
+ protected override double? OnCoerceMinimum(double? baseValue)
+ {
+ if (baseValue.HasValue && double.IsNaN(baseValue.Value))
+ {
+ throw new ArgumentException("NaN is invalid for Minimum.");
+ }
+ return base.OnCoerceMinimum(baseValue);
+ }
+
+ ///
+ protected override void SetValidSpinDirection()
+ {
+ if (Value.HasValue && double.IsInfinity(Value.Value) && (Spinner != null))
+ {
+ Spinner.ValidSpinDirection = ValidSpinDirections.None;
+ }
+ else
+ {
+ base.SetValidSpinDirection();
+ }
+ }
+
+ ///
+ protected override double? ConvertTextToValue(string text)
+ {
+ var result = base.ConvertTextToValue(text);
+ if (result != null)
+ {
+ if (double.IsNaN(result.Value))
+ {
+ TestInputSpecialValue(AllowInputSpecialValues, AllowedSpecialValues.NaN);
+ }
+ else if (double.IsPositiveInfinity(result.Value))
+ {
+ TestInputSpecialValue(AllowInputSpecialValues, AllowedSpecialValues.PositiveInfinity);
+ }
+ else if (double.IsNegativeInfinity(result.Value))
+ {
+ TestInputSpecialValue(AllowInputSpecialValues, AllowedSpecialValues.NegativeInfinity);
+ }
+ }
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/NumericUpDown/IntegerUpDown.cs b/src/Avalonia.Controls/NumericUpDown/IntegerUpDown.cs
new file mode 100644
index 0000000000..7f9e05762c
--- /dev/null
+++ b/src/Avalonia.Controls/NumericUpDown/IntegerUpDown.cs
@@ -0,0 +1,24 @@
+namespace Avalonia.Controls
+{
+ ///
+ public class IntegerUpDown : CommonNumericUpDown
+ {
+ ///
+ /// Initializes static members of the class.
+ ///
+ static IntegerUpDown() => UpdateMetadata(typeof(IntegerUpDown), 1, int.MinValue, int.MaxValue);
+
+ ///
+ /// Initializes new instance of the class.
+ ///
+ public IntegerUpDown() : base(int.TryParse, decimal.ToInt32, (v1, v2) => v1 < v2, (v1, v2) => v1 > v2)
+ {
+ }
+
+ ///
+ protected override int IncrementValue(int value, int increment) => value + increment;
+
+ ///
+ protected override int DecrementValue(int value, int increment) => value - increment;
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/NumericUpDown/LongUpDown.cs b/src/Avalonia.Controls/NumericUpDown/LongUpDown.cs
new file mode 100644
index 0000000000..d116df0ad2
--- /dev/null
+++ b/src/Avalonia.Controls/NumericUpDown/LongUpDown.cs
@@ -0,0 +1,24 @@
+namespace Avalonia.Controls
+{
+ ///
+ public class LongUpDown : CommonNumericUpDown
+ {
+ ///
+ /// Initializes static members of the class.
+ ///
+ static LongUpDown() => UpdateMetadata(typeof(LongUpDown), 1L, long.MinValue, long.MaxValue);
+
+ ///
+ /// Initializes new instance of the class.
+ ///
+ public LongUpDown() : base(long.TryParse, decimal.ToInt64, (v1, v2) => v1 < v2, (v1, v2) => v1 > v2)
+ {
+ }
+
+ ///
+ protected override long IncrementValue(long value, long increment) => value + increment;
+
+ ///
+ protected override long DecrementValue(long value, long increment) => value - increment;
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/NumericUpDown/ShortUpDown.cs b/src/Avalonia.Controls/NumericUpDown/ShortUpDown.cs
new file mode 100644
index 0000000000..cdba5b9b64
--- /dev/null
+++ b/src/Avalonia.Controls/NumericUpDown/ShortUpDown.cs
@@ -0,0 +1,24 @@
+namespace Avalonia.Controls
+{
+ ///
+ public class ShortUpDown : CommonNumericUpDown
+ {
+ ///
+ /// Initializes static members of the class.
+ ///
+ static ShortUpDown() => UpdateMetadata(typeof(ShortUpDown), 1, short.MinValue, short.MaxValue);
+
+ ///
+ /// Initializes new instance of the class.
+ ///
+ public ShortUpDown() : base(short.TryParse, decimal.ToInt16, (v1, v2) => v1 < v2, (v1, v2) => v1 > v2)
+ {
+ }
+
+ ///
+ protected override short IncrementValue(short value, short increment) => (short)(value + increment);
+
+ ///
+ protected override short DecrementValue(short value, short increment) => (short)(value - increment);
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/NumericUpDown/SingleUpDown.cs b/src/Avalonia.Controls/NumericUpDown/SingleUpDown.cs
new file mode 100644
index 0000000000..cc3da078bf
--- /dev/null
+++ b/src/Avalonia.Controls/NumericUpDown/SingleUpDown.cs
@@ -0,0 +1,106 @@
+using System;
+
+namespace Avalonia.Controls
+{
+ ///
+ public class SingleUpDown : CommonNumericUpDown
+ {
+ ///
+ /// Defines the property.
+ ///
+ public static readonly DirectProperty AllowInputSpecialValuesProperty =
+ AvaloniaProperty.RegisterDirect(nameof(AllowInputSpecialValues),
+ updown => updown.AllowInputSpecialValues, (updown, v) => updown.AllowInputSpecialValues = v);
+
+ private AllowedSpecialValues _allowInputSpecialValues;
+
+ ///
+ /// Initializes static members of the class.
+ ///
+ static SingleUpDown() => UpdateMetadata(typeof(SingleUpDown), 1f, float.NegativeInfinity, float.PositiveInfinity);
+
+ ///
+ /// Initializes new instance of the class.
+ ///
+ public SingleUpDown() : base(float.TryParse, decimal.ToSingle, (v1, v2) => v1 < v2, (v1, v2) => v1 > v2)
+ {
+ }
+
+ ///
+ /// Gets or sets a value representing the special values the user is allowed to input, such as "Infinity", "-Infinity" and "NaN" values.
+ ///
+ public AllowedSpecialValues AllowInputSpecialValues
+ {
+ get { return _allowInputSpecialValues; }
+ set { SetAndRaise(AllowInputSpecialValuesProperty, ref _allowInputSpecialValues, value); }
+ }
+
+ ///
+ protected override float? OnCoerceIncrement(float? baseValue)
+ {
+ if (baseValue.HasValue && float.IsNaN(baseValue.Value))
+ throw new ArgumentException("NaN is invalid for Increment.");
+
+ return base.OnCoerceIncrement(baseValue);
+ }
+
+ ///
+ protected override float? OnCoerceMaximum(float? baseValue)
+ {
+ if (baseValue.HasValue && float.IsNaN(baseValue.Value))
+ throw new ArgumentException("NaN is invalid for Maximum.");
+
+ return base.OnCoerceMaximum(baseValue);
+ }
+
+ ///
+ protected override float? OnCoerceMinimum(float? baseValue)
+ {
+ if (baseValue.HasValue && float.IsNaN(baseValue.Value))
+ throw new ArgumentException("NaN is invalid for Minimum.");
+
+ return base.OnCoerceMinimum(baseValue);
+ }
+
+ ///
+ protected override float IncrementValue(float value, float increment) => value + increment;
+
+ ///
+ protected override float DecrementValue(float value, float increment) => value - increment;
+
+ ///
+ protected override void SetValidSpinDirection()
+ {
+ if (Value.HasValue && float.IsInfinity(Value.Value) && (Spinner != null))
+ {
+ Spinner.ValidSpinDirection = ValidSpinDirections.None;
+ }
+ else
+ {
+ base.SetValidSpinDirection();
+ }
+ }
+
+ ///
+ protected override float? ConvertTextToValue(string text)
+ {
+ var result = base.ConvertTextToValue(text);
+ if (result != null)
+ {
+ if (float.IsNaN(result.Value))
+ {
+ TestInputSpecialValue(AllowInputSpecialValues, AllowedSpecialValues.NaN);
+ }
+ else if (float.IsPositiveInfinity(result.Value))
+ {
+ TestInputSpecialValue(AllowInputSpecialValues, AllowedSpecialValues.PositiveInfinity);
+ }
+ else if (float.IsNegativeInfinity(result.Value))
+ {
+ TestInputSpecialValue(AllowInputSpecialValues, AllowedSpecialValues.NegativeInfinity);
+ }
+ }
+ return result;
+ }
+ }
+}
\ No newline at end of file