diff --git a/MahApps.Metro/Controls/NumericUpDown.cs b/MahApps.Metro/Controls/NumericUpDown.cs
new file mode 100644
index 0000000000..0d6d061c62
--- /dev/null
+++ b/MahApps.Metro/Controls/NumericUpDown.cs
@@ -0,0 +1,450 @@
+namespace MahApps.Metro.Controls
+{
+ #region Using Directives
+
+ using System;
+ using System.ComponentModel;
+ using System.Globalization;
+ using System.Threading;
+ using System.Windows;
+ using System.Windows.Controls;
+ using System.Windows.Controls.Primitives;
+
+ #endregion
+
+ ///
+ /// Represents a Windows spin box (also known as an up-down control) that displays numeric values.
+ ///
+ [TemplatePart(Name = ElementNumericUp, Type = typeof(RepeatButton))]
+ [TemplatePart(Name = ElementNumericDown, Type = typeof(RepeatButton))]
+ [TemplatePart(Name = ElementTextBox, Type = typeof(TextBox))]
+ public class NumericUpDown : RangeBase
+ {
+ public static readonly RoutedEvent IncrementValueEvent = EventManager.RegisterRoutedEvent("IncrementValue", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(NumericUpDown));
+ public static readonly RoutedEvent DecrementValueEvent = EventManager.RegisterRoutedEvent("DecrementValue", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(NumericUpDown));
+
+ ///
+ /// Event for "To value has been reached"
+ ///
+ public static readonly RoutedEvent MaximumReachedEvent = EventManager.RegisterRoutedEvent("MaximumReached", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(NumericUpDown));
+
+ ///
+ /// Event for "From value has been reached"
+ ///
+ public static readonly RoutedEvent MinimumReachedEvent = EventManager.RegisterRoutedEvent("MinimumReached", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(NumericUpDown));
+
+ ///
+ /// DependencyProperty for property.
+ ///
+ public static readonly DependencyProperty DelayProperty = DependencyProperty.Register("Delay",
+ typeof(int),
+ typeof(NumericUpDown),
+ new FrameworkPropertyMetadata(DefaultDelay, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnDelayChanged),
+ ValidateDelay);
+ ///
+ /// DependencyProperty for property.
+ ///
+ public static readonly DependencyProperty TextAlignmentProperty = DependencyProperty.Register("TextAlignment",
+ typeof(TextAlignment),
+ typeof(NumericUpDown),
+ new PropertyMetadata(default(TextAlignment)));
+
+ ///
+ /// DependencyProperty for property.
+ ///
+ public static readonly DependencyProperty SpeedupProperty = DependencyProperty.Register("Speedup",
+ typeof(bool),
+ typeof(NumericUpDown),
+ new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSpeedupChanged));
+
+ ///
+ /// DependencyProperty for property.
+ ///
+ public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register("IsReadOnly",
+ typeof(bool),
+ typeof(NumericUpDown),
+ new FrameworkPropertyMetadata(default(bool), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, IsReadOnlyChanged));
+
+ ///
+ /// DependencyProperty for property.
+ ///
+ public static readonly DependencyProperty StringFormatProperty = DependencyProperty.Register("StringFormat", typeof(string), typeof(NumericUpDown), new FrameworkPropertyMetadata(null, OnStringFormatChanged));
+
+ private const double DefaultInterval = 1d;
+ private const int DefaultDelay = 500;
+ private const string ElementNumericDown = "PART_NumericDown";
+ private const string ElementNumericUp = "PART_NumericUp";
+ private const string ElementTextBox = "PART_TextBox";
+ private double _internalIntervalMultiplierForCalculation = DefaultInterval;
+ private double _intervalValueSinceReset;
+ private RepeatButton _repeatDown;
+ private RepeatButton _repeatUp;
+ private TextBox _valueTextBox;
+
+ static NumericUpDown()
+ {
+ DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericUpDown), new FrameworkPropertyMetadata(typeof(NumericUpDown)));
+ MinimumProperty.OverrideMetadata(typeof(NumericUpDown), new FrameworkPropertyMetadata(double.MinValue, null, CoerceMinimum));
+ SmallChangeProperty.OverrideMetadata(typeof(NumericUpDown), new FrameworkPropertyMetadata(DefaultInterval, IntervalChanged));
+ LargeChangeProperty.OverrideMetadata(typeof(NumericUpDown), new FrameworkPropertyMetadata(100 * DefaultInterval));
+ MaximumProperty.OverrideMetadata(typeof(NumericUpDown), new FrameworkPropertyMetadata(double.MaxValue));
+
+ VerticalContentAlignmentProperty.OverrideMetadata(typeof(NumericUpDown), new FrameworkPropertyMetadata(VerticalAlignment.Center));
+ HorizontalContentAlignmentProperty.OverrideMetadata(typeof(NumericUpDown), new FrameworkPropertyMetadata(HorizontalAlignment.Right));
+ }
+
+ ///
+ /// Event fired from this NumericUpDown when its value has reached the maximum value
+ ///
+ public event RoutedEventHandler MaximumReached
+ {
+ add { AddHandler(MaximumReachedEvent, value); }
+ remove { RemoveHandler(MaximumReachedEvent, value); }
+ }
+
+ ///
+ /// Event fired from this NumericUpDown when its value has reached the minimum value
+ ///
+ public event RoutedEventHandler MinimumReached
+ {
+ add { AddHandler(MinimumReachedEvent, value); }
+ remove { RemoveHandler(MinimumReachedEvent, value); }
+ }
+
+ public event RoutedEventHandler IncrementValue
+ {
+ add { AddHandler(IncrementValueEvent, value); }
+ remove { RemoveHandler(IncrementValueEvent, value); }
+ }
+
+ public event RoutedEventHandler DecrementValue
+ {
+ add { AddHandler(DecrementValueEvent, value); }
+ remove { RemoveHandler(DecrementValueEvent, value); }
+ }
+
+ ///
+ /// Gets or sets the amount of time, in milliseconds, the NumericUpDown waits while the up/down button is pressed before it starts increasing/decreasing the
+ /// for the specified . The value must be non-negative.
+ ///
+ [DefaultValue(DefaultDelay)]
+ [Category("Common")]
+ public int Delay
+ {
+ get { return (int)GetValue(DelayProperty); }
+ set { SetValue(DelayProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the NumericUpDown allows to get UserInput or not
+ ///
+ [Category("Common")]
+ public bool IsReadOnly
+ {
+ get { return (bool)GetValue(IsReadOnlyProperty); }
+ set { SetValue(IsReadOnlyProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the horizontal alignment of the contents of the text box.
+ ///
+ [Category("Common")]
+ [DefaultValue(default(TextAlignment))]
+ public TextAlignment TextAlignment
+ {
+ get { return (TextAlignment)GetValue(TextAlignmentProperty); }
+ set { SetValue(TextAlignmentProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the value to be added to or subtracted from remains always
+ /// or it will change to and increase this.
+ ///
+ [DefaultValue(true)]
+ [Category("Common")]
+ public bool Speedup
+ {
+ get { return (bool)GetValue(SpeedupProperty); }
+ set { SetValue(SpeedupProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the formatting for the displaying
+ ///
+ ///
+ ///
+ ///
+ [Category("Common")]
+ public string StringFormat
+ {
+ get { return (string)GetValue(StringFormatProperty); }
+ set { SetValue(StringFormatProperty, value); }
+ }
+
+ ///
+ /// When overridden in a derived class, is invoked whenever application code or internal processes call
+ /// .
+ ///
+ public override void OnApplyTemplate()
+ {
+ base.OnApplyTemplate();
+
+ _repeatUp = GetTemplateChild(ElementNumericUp) as RepeatButton;
+ _repeatDown = GetTemplateChild(ElementNumericDown) as RepeatButton;
+
+ _valueTextBox = GetTemplateChild(ElementTextBox) as TextBox;
+
+ if (_repeatUp == null || _repeatDown == null || _valueTextBox == null)
+ {
+ throw new InvalidOperationException(string.Format("You have missed to specify {0}, {1} or {2} in your template", ElementNumericUp, ElementNumericDown, ElementTextBox));
+ }
+
+ _valueTextBox.LostFocus += OnTextBoxLostFocus;
+ _valueTextBox.IsReadOnly = IsReadOnly;
+
+ _repeatUp.Click += (o, e) => ChangeValue(true);
+ _repeatDown.Click += (o, e) => ChangeValue(false);
+
+ _repeatUp.PreviewMouseUp += (o, e) => ResetInternal();
+ _repeatDown.PreviewMouseUp += (o, e) => ResetInternal();
+
+ OnValueChanged(Value, Value);
+ }
+
+ protected virtual void OnDelayChanged(int oldDelay, int newDelay)
+ {
+ if (oldDelay != newDelay)
+ {
+ if (_repeatDown != null)
+ {
+ _repeatDown.Delay = newDelay;
+ }
+
+ if (_repeatUp != null)
+ {
+ _repeatUp.Delay = newDelay;
+ }
+ }
+ }
+
+ protected override void OnMaximumChanged(double oldMaximum, double newMaximum)
+ {
+ base.OnMaximumChanged(oldMaximum, newMaximum);
+
+ if (_repeatUp != null)
+ {
+ _repeatUp.IsEnabled = Value < newMaximum;
+ }
+ }
+
+ protected override void OnMinimumChanged(double oldMinimum, double newMinimum)
+ {
+ base.OnMinimumChanged(oldMinimum, newMinimum);
+
+ if (_repeatDown != null)
+ {
+ _repeatDown.IsEnabled = Value > newMinimum;
+ }
+ }
+
+ protected virtual void OnSpeedupChanged(bool oldSpeedup, bool newSpeedup) { }
+
+ ///
+ /// Raises the routed event.
+ ///
+ ///
+ /// Old value of the property
+ ///
+ ///
+ /// New value of the property
+ ///
+ protected override void OnValueChanged(double oldValue, double newValue)
+ {
+ base.OnValueChanged(oldValue, newValue);
+
+ if (_repeatUp != null && !_repeatUp.IsEnabled)
+ {
+ _repeatUp.IsEnabled = true;
+ }
+
+ if (_repeatDown != null && !_repeatDown.IsEnabled)
+ {
+ _repeatDown.IsEnabled = true;
+ }
+
+ if (newValue <= Minimum)
+ {
+ if (_repeatDown != null)
+ {
+ _repeatDown.IsEnabled = false;
+ }
+
+ ResetInternal();
+
+ if (IsLoaded)
+ {
+ RaiseEvent(new RoutedEventArgs(MinimumReachedEvent));
+ }
+ }
+
+ if (newValue >= Maximum)
+ {
+ if (_repeatUp != null)
+ {
+ _repeatUp.IsEnabled = false;
+ }
+
+ ResetInternal();
+ if (IsLoaded)
+ {
+ RaiseEvent(new RoutedEventArgs(MaximumReachedEvent));
+ }
+ }
+
+ if (_valueTextBox != null)
+ {
+ if (string.IsNullOrEmpty(StringFormat))
+ {
+ _valueTextBox.Text = newValue.ToString();
+ }
+ else
+ {
+ _valueTextBox.Text = newValue.ToString(StringFormat);
+ }
+ }
+ }
+
+ private static object CoerceMinimum(DependencyObject d, object value)
+ {
+ RangeBase ctrl = (RangeBase)d;
+ double max = ctrl.Maximum;
+ if ((double)value > max)
+ {
+ return max;
+ }
+
+ return value;
+ }
+
+ private static void IntervalChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var numericUpDown = (NumericUpDown)d;
+
+ numericUpDown.ResetInternal();
+ }
+
+ private static void IsReadOnlyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ TextBox valueTextBox = ((NumericUpDown)d)._valueTextBox;
+
+ if (valueTextBox != null)
+ {
+ valueTextBox.IsReadOnly = (bool)e.NewValue;
+ }
+ }
+
+ private static void OnDelayChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ NumericUpDown ctrl = (NumericUpDown)d;
+
+ ctrl.OnDelayChanged((int)e.OldValue, (int)e.NewValue);
+ }
+
+ private static void OnSpeedupChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ NumericUpDown ctrl = (NumericUpDown)d;
+
+ ctrl.OnSpeedupChanged((bool)e.OldValue, (bool)e.NewValue);
+ }
+
+ private static void OnStringFormatChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ NumericUpDown nud = (NumericUpDown)d;
+
+ if (nud._valueTextBox != null)
+ {
+ nud._valueTextBox.Text = nud.Value.ToString((string)e.NewValue);
+ }
+ }
+
+ private static bool ValidateDelay(object value)
+ {
+ return Convert.ToInt32(value) >= 0;
+ }
+
+ private void ChangeValue(bool toPositive)
+ {
+ RaiseEvent(new RoutedEventArgs(toPositive ? IncrementValueEvent : DecrementValueEvent));
+ if (Speedup)
+ {
+ double d = SmallChange * LargeChange;
+ if ((_intervalValueSinceReset += SmallChange * _internalIntervalMultiplierForCalculation) > d)
+ {
+ LargeChange *= 10;
+ _internalIntervalMultiplierForCalculation *= 10;
+ }
+ }
+
+ if (toPositive)
+ {
+ SetSilentValue(Value + _internalIntervalMultiplierForCalculation);
+ }
+ else
+ {
+ SetSilentValue(Value - _internalIntervalMultiplierForCalculation);
+ }
+ }
+
+ private void OnTextBoxLostFocus(object sender, RoutedEventArgs e)
+ {
+ TextBox tb = (TextBox)sender;
+
+ double convertedValue;
+ if (double.TryParse(tb.Text, NumberStyles.Any, Thread.CurrentThread.CurrentCulture, out convertedValue))
+ {
+ if (convertedValue > Maximum)
+ {
+ if (Value == Maximum)
+ {
+ OnValueChanged(Value, Value);
+ }
+ else
+ {
+ SetValue(ValueProperty, Maximum);
+ }
+ }
+ else if (convertedValue < Minimum)
+ {
+ if (Value == Minimum)
+ {
+ OnValueChanged(Value, Value);
+ }
+ else
+ {
+ SetValue(ValueProperty, Minimum);
+ }
+ }
+ else
+ {
+ SetValue(ValueProperty, convertedValue);
+ }
+ }
+ else
+ {
+ OnValueChanged(Value, Value);
+ }
+ }
+
+ private void ResetInternal()
+ {
+ LargeChange = 100 * SmallChange;
+ _internalIntervalMultiplierForCalculation = SmallChange;
+ _intervalValueSinceReset = 0;
+ }
+
+ private void SetSilentValue(double value)
+ {
+ Value = value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MahApps.Metro/MahApps.Metro.NET45.csproj b/MahApps.Metro/MahApps.Metro.NET45.csproj
index 928b3ff649..bb3060804e 100644
--- a/MahApps.Metro/MahApps.Metro.NET45.csproj
+++ b/MahApps.Metro/MahApps.Metro.NET45.csproj
@@ -90,6 +90,7 @@
+
@@ -441,6 +442,10 @@
Designer
MSBuild:Compile
+
+ MSBuild:Compile
+ Designer
+
Designer
MSBuild:Compile
diff --git a/MahApps.Metro/MahApps.Metro.csproj b/MahApps.Metro/MahApps.Metro.csproj
index 7bf182e9b1..e06dd33d0e 100644
--- a/MahApps.Metro/MahApps.Metro.csproj
+++ b/MahApps.Metro/MahApps.Metro.csproj
@@ -109,6 +109,7 @@
+
@@ -465,6 +466,10 @@
Designer
MSBuild:Compile
+
+ MSBuild:Compile
+ Designer
+
Designer
MSBuild:Compile
@@ -583,7 +588,6 @@
Controls.xaml
-
False
diff --git a/MahApps.Metro/Styles/Controls.Buttons.xaml b/MahApps.Metro/Styles/Controls.Buttons.xaml
index 65287d28d4..3fb39cf32f 100644
--- a/MahApps.Metro/Styles/Controls.Buttons.xaml
+++ b/MahApps.Metro/Styles/Controls.Buttons.xaml
@@ -108,7 +108,7 @@
+ TargetType="{x:Type ButtonBase}">
+
\ No newline at end of file
diff --git a/samples/MetroDemo/MainWindow.xaml b/samples/MetroDemo/MainWindow.xaml
index 96e2783c00..1cd5c4e569 100644
--- a/samples/MetroDemo/MainWindow.xaml
+++ b/samples/MetroDemo/MainWindow.xaml
@@ -296,6 +296,7 @@
+
@@ -441,6 +442,38 @@
IsEnabled="False"
Password="Password" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+