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" /> + +