You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

799 lines
27 KiB

using System;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
namespace Gumming
{
[TemplatePart(Name = "PART_TextBox", Type = typeof (TextBox))]
[TemplatePart(Name = "PART_IncreaseButton", Type = typeof (RepeatButton))]
[TemplatePart(Name = "PART_DecreaseButton", Type = typeof (RepeatButton))]
public class NumericUpDown : Control
{
#region Properties
#region Value
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof (Decimal), typeof (NumericUpDown),
new PropertyMetadata(0m, OnValueChanged, CoerceValue));
public Decimal Value
{
get { return (Decimal) GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
private static void OnValueChanged(DependencyObject element,
DependencyPropertyChangedEventArgs e)
{
var control = (NumericUpDown) element;
if (control.TextBox != null)
{
control.TextBox.UndoLimit = 0;
control.TextBox.UndoLimit = 1;
}
}
private static object CoerceValue(DependencyObject element, object baseValue)
{
var control = (NumericUpDown) element;
var value = (Decimal) baseValue;
control.CoerceValueToBounds(ref value);
// Get the text representation of Value
var valueString = value.ToString(control.Culture);
// Count all decimal places
var decimalPlaces = control.GetDecimalPlacesCount(valueString);
if (decimalPlaces > control.DecimalPlaces)
{
if (control.IsDecimalPointDynamic)
{
// Assigning DecimalPlaces will coerce the number
control.DecimalPlaces = decimalPlaces;
// If the specified number of decimal places is still too much
if (decimalPlaces > control.DecimalPlaces)
{
value = control.TruncateValue(valueString, control.DecimalPlaces);
}
}
else
{
// Remove all overflowing decimal places
value = control.TruncateValue(valueString, decimalPlaces);
}
}
else if (control.IsDecimalPointDynamic)
{
control.DecimalPlaces = decimalPlaces;
}
if (control.IsThousandSeparatorVisible)
{
if (control.TextBox != null)
{
control.TextBox.Text = value.ToString("N", control.Culture);
}
}
else
{
if (control.TextBox != null)
{
control.TextBox.Text = value.ToString("F", control.Culture);
}
}
return value;
}
#endregion
#region MaxValue
public static readonly DependencyProperty MaxValueProperty =
DependencyProperty.Register("MaxValue", typeof (Decimal), typeof (NumericUpDown),
new PropertyMetadata(100000000m, OnMaxValueChanged,
CoerceMaxValue));
public Decimal MaxValue
{
get { return (Decimal) GetValue(MaxValueProperty); }
set { SetValue(MaxValueProperty, value); }
}
private static void OnMaxValueChanged(DependencyObject element,
DependencyPropertyChangedEventArgs e)
{
var control = (NumericUpDown) element;
var maxValue = (Decimal) e.NewValue;
// If maxValue steps over MinValue, shift it
if (maxValue < control.MinValue)
{
control.MinValue = maxValue;
}
if (maxValue <= control.Value)
{
control.Value = maxValue;
}
}
private static object CoerceMaxValue(DependencyObject element, Object baseValue)
{
var maxValue = (Decimal) baseValue;
if (maxValue == Decimal.MaxValue)
{
return DependencyProperty.UnsetValue;
}
return maxValue;
}
#endregion
#region MinValue
public static readonly DependencyProperty MinValueProperty =
DependencyProperty.Register("MinValue", typeof (Decimal), typeof (NumericUpDown),
new PropertyMetadata(0m, OnMinValueChanged,
CoerceMinValue));
public Decimal MinValue
{
get { return (Decimal) GetValue(MinValueProperty); }
set { SetValue(MinValueProperty, value); }
}
private static void OnMinValueChanged(DependencyObject element,
DependencyPropertyChangedEventArgs e)
{
var control = (NumericUpDown) element;
var minValue = (Decimal) e.NewValue;
// If minValue steps over MaxValue, shift it
if (minValue > control.MaxValue)
{
control.MaxValue = minValue;
}
if (minValue >= control.Value)
{
control.Value = minValue;
}
}
private static object CoerceMinValue(DependencyObject element, Object baseValue)
{
var minValue = (Decimal) baseValue;
if (minValue == Decimal.MinValue)
{
return DependencyProperty.UnsetValue;
}
return minValue;
}
#endregion
#region DecimalPlaces
public static readonly DependencyProperty DecimalPlacesProperty =
DependencyProperty.Register("DecimalPlaces", typeof (Int32), typeof (NumericUpDown),
new PropertyMetadata(0, OnDecimalPlacesChanged,
CoerceDecimalPlaces));
public Int32 DecimalPlaces
{
get { return (Int32) GetValue(DecimalPlacesProperty); }
set { SetValue(DecimalPlacesProperty, value); }
}
private static void OnDecimalPlacesChanged(DependencyObject element,
DependencyPropertyChangedEventArgs e)
{
var control = (NumericUpDown) element;
var decimalPlaces = (Int32) e.NewValue;
control.Culture.NumberFormat.NumberDecimalDigits = decimalPlaces;
if (control.IsDecimalPointDynamic)
{
control.IsDecimalPointDynamic = false;
control.InvalidateProperty(ValueProperty);
control.IsDecimalPointDynamic = true;
}
else
{
control.InvalidateProperty(ValueProperty);
}
}
private static object CoerceDecimalPlaces(DependencyObject element, Object baseValue)
{
var decimalPlaces = (Int32) baseValue;
var control = (NumericUpDown) element;
if (decimalPlaces < control.MinDecimalPlaces)
{
decimalPlaces = control.MinDecimalPlaces;
}
else if (decimalPlaces > control.MaxDecimalPlaces)
{
decimalPlaces = control.MaxDecimalPlaces;
}
return decimalPlaces;
}
#endregion
#region MaxDecimalPlaces
public static readonly DependencyProperty MaxDecimalPlacesProperty =
DependencyProperty.Register("MaxDecimalPlaces", typeof (Int32), typeof (NumericUpDown),
new PropertyMetadata(28, OnMaxDecimalPlacesChanged,
CoerceMaxDecimalPlaces));
public Int32 MaxDecimalPlaces
{
get { return (Int32) GetValue(MaxDecimalPlacesProperty); }
set { SetValue(MaxDecimalPlacesProperty, value); }
}
private static void OnMaxDecimalPlacesChanged(DependencyObject element,
DependencyPropertyChangedEventArgs e)
{
var control = (NumericUpDown) element;
control.InvalidateProperty(DecimalPlacesProperty);
}
private static object CoerceMaxDecimalPlaces(DependencyObject element, Object baseValue)
{
var maxDecimalPlaces = (Int32) baseValue;
var control = (NumericUpDown) element;
if (maxDecimalPlaces > 28)
{
maxDecimalPlaces = 28;
}
else if (maxDecimalPlaces < 0)
{
maxDecimalPlaces = 0;
}
else if (maxDecimalPlaces < control.MinDecimalPlaces)
{
control.MinDecimalPlaces = maxDecimalPlaces;
}
return maxDecimalPlaces;
}
#endregion
#region MinDecimalPlaces
public static readonly DependencyProperty MinDecimalPlacesProperty =
DependencyProperty.Register("MinDecimalPlaces", typeof (Int32), typeof (NumericUpDown),
new PropertyMetadata(0, OnMinDecimalPlacesChanged,
CoerceMinDecimalPlaces));
public Int32 MinDecimalPlaces
{
get { return (Int32) GetValue(MinDecimalPlacesProperty); }
set { SetValue(MinDecimalPlacesProperty, value); }
}
private static void OnMinDecimalPlacesChanged(DependencyObject element,
DependencyPropertyChangedEventArgs e)
{
var control = (NumericUpDown) element;
control.InvalidateProperty(DecimalPlacesProperty);
}
private static object CoerceMinDecimalPlaces(DependencyObject element, Object baseValue)
{
var minDecimalPlaces = (Int32) baseValue;
var control = (NumericUpDown) element;
if (minDecimalPlaces < 0)
{
minDecimalPlaces = 0;
}
else if (minDecimalPlaces > 28)
{
minDecimalPlaces = 28;
}
else if (minDecimalPlaces > control.MaxDecimalPlaces)
{
control.MaxDecimalPlaces = minDecimalPlaces;
}
return minDecimalPlaces;
}
#endregion
#region IsDecimalPointDynamic
public static readonly DependencyProperty IsDecimalPointDynamicProperty =
DependencyProperty.Register("IsDecimalPointDynamic", typeof (Boolean), typeof (NumericUpDown),
new PropertyMetadata(false));
public Boolean IsDecimalPointDynamic
{
get { return (Boolean) GetValue(IsDecimalPointDynamicProperty); }
set { SetValue(IsDecimalPointDynamicProperty, value); }
}
#endregion
#region MinorDelta
public static readonly DependencyProperty MinorDeltaProperty =
DependencyProperty.Register("MinorDelta", typeof (Decimal), typeof (NumericUpDown),
new PropertyMetadata(1m, OnMinorDeltaChanged,
CoerceMinorDelta));
public Decimal MinorDelta
{
get { return (Decimal) GetValue(MinorDeltaProperty); }
set { SetValue(MinorDeltaProperty, value); }
}
private static void OnMinorDeltaChanged(DependencyObject element,
DependencyPropertyChangedEventArgs e)
{
var minorDelta = (Decimal) e.NewValue;
var control = (NumericUpDown) element;
if (minorDelta > control.MajorDelta)
{
control.MajorDelta = minorDelta;
}
}
private static object CoerceMinorDelta(DependencyObject element, Object baseValue)
{
var minorDelta = (Decimal) baseValue;
return minorDelta;
}
#endregion
#region MajorDelta
public static readonly DependencyProperty MajorDeltaProperty =
DependencyProperty.Register("MajorDelta", typeof (Decimal), typeof (NumericUpDown),
new PropertyMetadata(10m, OnMajorDeltaChanged,
CoerceMajorDelta));
public Decimal MajorDelta
{
get { return (Decimal) GetValue(MajorDeltaProperty); }
set { SetValue(MajorDeltaProperty, value); }
}
private static void OnMajorDeltaChanged(DependencyObject element,
DependencyPropertyChangedEventArgs e)
{
var majorDelta = (Decimal) e.NewValue;
var control = (NumericUpDown) element;
if (majorDelta < control.MinorDelta)
{
control.MinorDelta = majorDelta;
}
}
private static object CoerceMajorDelta(DependencyObject element, Object baseValue)
{
var majorDelta = (Decimal) baseValue;
return majorDelta;
}
#endregion
#region IsThousandSeparatorVisible
public static readonly DependencyProperty IsThousandSeparatorVisibleProperty =
DependencyProperty.Register("IsThousandSeparatorVisible", typeof (Boolean), typeof (NumericUpDown),
new PropertyMetadata(false, OnIsThousandSeparatorVisibleChanged));
public Boolean IsThousandSeparatorVisible
{
get { return (Boolean) GetValue(IsThousandSeparatorVisibleProperty); }
set { SetValue(IsThousandSeparatorVisibleProperty, value); }
}
private static void OnIsThousandSeparatorVisibleChanged(DependencyObject element,
DependencyPropertyChangedEventArgs e)
{
var control = (NumericUpDown) element;
control.InvalidateProperty(ValueProperty);
}
#endregion
#region IsAutoSelectionActive
public static readonly DependencyProperty IsAutoSelectionActiveProperty =
DependencyProperty.Register("IsAutoSelectionActive", typeof (Boolean), typeof (NumericUpDown),
new PropertyMetadata(false));
public Boolean IsAutoSelectionActive
{
get { return (Boolean) GetValue(IsAutoSelectionActiveProperty); }
set { SetValue(IsAutoSelectionActiveProperty, value); }
}
#endregion
#region IsValueWrapAllowed
public static readonly DependencyProperty IsValueWrapAllowedProperty =
DependencyProperty.Register("IsValueWrapAllowed", typeof (Boolean), typeof (NumericUpDown),
new PropertyMetadata(false));
public Boolean IsValueWrapAllowed
{
get { return (Boolean) GetValue(IsValueWrapAllowedProperty); }
set { SetValue(IsValueWrapAllowedProperty, value); }
}
#endregion
#endregion
#region Fields
protected readonly CultureInfo Culture;
protected RepeatButton DecreaseButton;
protected RepeatButton IncreaseButton;
protected TextBox TextBox;
#endregion
#region Commands
private readonly RoutedUICommand _minorDecreaseValueCommand =
new RoutedUICommand("MinorDecreaseValue", "MinorDecreaseValue", typeof (NumericUpDown));
private readonly RoutedUICommand _minorIncreaseValueCommand =
new RoutedUICommand("MinorIncreaseValue", "MinorIncreaseValue", typeof (NumericUpDown));
private readonly RoutedUICommand _majorDecreaseValueCommand =
new RoutedUICommand("MajorDecreaseValue", "MajorDecreaseValue", typeof (NumericUpDown));
private readonly RoutedUICommand _majorIncreaseValueCommand =
new RoutedUICommand("MajorIncreaseValue", "MajorIncreaseValue", typeof (NumericUpDown));
private readonly RoutedUICommand _updateValueStringCommand =
new RoutedUICommand("UpdateValueString", "UpdateValueString", typeof (NumericUpDown));
private readonly RoutedUICommand _cancelChangesCommand =
new RoutedUICommand("CancelChanges", "CancelChanges", typeof (NumericUpDown));
#endregion
#region Constructors
static NumericUpDown()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof (NumericUpDown),
new FrameworkPropertyMetadata(
typeof (NumericUpDown)));
}
public NumericUpDown()
{
Culture = (CultureInfo) CultureInfo.CurrentCulture.Clone();
Culture.NumberFormat.NumberDecimalDigits = DecimalPlaces;
Loaded += OnLoaded;
}
#endregion
#region Event handlers
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
AttachToVisualTree();
AttachCommands();
}
private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
UpdateValue();
}
}
private void TextBoxOnLostFocus(object sender, RoutedEventArgs routedEventArgs)
{
UpdateValue();
}
private void TextBoxOnPreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
if (IsAutoSelectionActive)
{
TextBox.SelectAll();
}
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
InvalidateProperty(ValueProperty);
}
private void ButtonOnPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
Value = 0;
}
#endregion
#region Utility Methods
#region Attachment
private void AttachToVisualTree()
{
AttachTextBox();
AttachIncreaseButton();
AttachDecreaseButton();
}
private void AttachTextBox()
{
var textBox = GetTemplateChild("PART_TextBox") as TextBox;
// A null check is advised
if (textBox != null)
{
TextBox = textBox;
TextBox.LostFocus += TextBoxOnLostFocus;
TextBox.PreviewKeyDown += TextBox_PreviewKeyDown;
TextBox.PreviewMouseLeftButtonUp += TextBoxOnPreviewMouseLeftButtonUp;
TextBox.TextChanged += TextBox_TextChanged;
TextBox.UndoLimit = 1;
TextBox.IsUndoEnabled = true;
}
}
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
TextBox textBox = sender as TextBox;
TextChange[] change = new TextChange[e.Changes.Count];
e.Changes.CopyTo(change, 0);
int offset = change[0].Offset;
if (change[0].AddedLength > 0)
{
double num = 0;
if (!Double.TryParse(textBox.Text, out num))
{
textBox.Text = textBox.Text.Remove(offset, change[0].AddedLength);
textBox.Select(offset, 0);
}
}
}
private void AttachIncreaseButton()
{
var increaseButton = GetTemplateChild("PART_IncreaseButton") as RepeatButton;
if (increaseButton != null)
{
IncreaseButton = increaseButton;
IncreaseButton.Focusable = false;
IncreaseButton.Command = _minorIncreaseValueCommand;
IncreaseButton.PreviewMouseLeftButtonDown += (sender, args) => RemoveFocus();
IncreaseButton.PreviewMouseRightButtonDown += ButtonOnPreviewMouseRightButtonDown;
}
}
private void AttachDecreaseButton()
{
var decreaseButton = GetTemplateChild("PART_DecreaseButton") as RepeatButton;
if (decreaseButton != null)
{
DecreaseButton = decreaseButton;
DecreaseButton.Focusable = false;
DecreaseButton.Command = _minorDecreaseValueCommand;
DecreaseButton.PreviewMouseLeftButtonDown += (sender, args) => RemoveFocus();
DecreaseButton.PreviewMouseRightButtonDown += ButtonOnPreviewMouseRightButtonDown;
}
}
private void AttachCommands()
{
CommandBindings.Add(new CommandBinding(_minorIncreaseValueCommand, (a, b) => IncreaseValue(true)));
CommandBindings.Add(new CommandBinding(_minorDecreaseValueCommand, (a, b) => DecreaseValue(true)));
CommandBindings.Add(new CommandBinding(_majorIncreaseValueCommand, (a, b) => IncreaseValue(false)));
CommandBindings.Add(new CommandBinding(_majorDecreaseValueCommand, (a, b) => DecreaseValue(false)));
CommandBindings.Add(new CommandBinding(_updateValueStringCommand, (a, b) => UpdateValue()));
CommandBindings.Add(new CommandBinding(_cancelChangesCommand, (a, b) => CancelChanges()));
CommandManager.RegisterClassInputBinding(typeof (TextBox),
new KeyBinding(_minorIncreaseValueCommand, new KeyGesture(Key.Up)));
CommandManager.RegisterClassInputBinding(typeof (TextBox),
new KeyBinding(_minorDecreaseValueCommand, new KeyGesture(Key.Down)));
CommandManager.RegisterClassInputBinding(typeof (TextBox),
new KeyBinding(_majorIncreaseValueCommand,
new KeyGesture(Key.PageUp)));
CommandManager.RegisterClassInputBinding(typeof (TextBox),
new KeyBinding(_majorDecreaseValueCommand,
new KeyGesture(Key.PageDown)));
/* will affect other textbox
CommandManager.RegisterClassInputBinding(typeof (TextBox),
new KeyBinding(_updateValueStringCommand, new KeyGesture(Key.Enter)));*/
CommandManager.RegisterClassInputBinding(typeof (TextBox),
new KeyBinding(_cancelChangesCommand, new KeyGesture(Key.Escape)));
}
#endregion
#region Data retrieval and deposit
private Decimal ParseStringToDecimal(String source)
{
Decimal value;
Decimal.TryParse(source, out value);
return value;
}
public Int32 GetDecimalPlacesCount(String valueString)
{
return valueString.SkipWhile(c => c.ToString(Culture)
!= Culture.NumberFormat.NumberDecimalSeparator).Skip(1).Count();
}
private Decimal TruncateValue(String valueString, Int32 decimalPlaces)
{
var endPoint = valueString.Length - (decimalPlaces - DecimalPlaces);
endPoint++;
var tempValueString = valueString.Substring(0, endPoint);
return Decimal.Parse(tempValueString, Culture);
}
#endregion
#region SubCoercion
private void CoerceValueToBounds(ref Decimal value)
{
if (value < MinValue)
{
value = MinValue;
}
else if (value > MaxValue)
{
value = MaxValue;
}
}
#endregion
#endregion
#region Methods
private void UpdateValue()
{
Value = ParseStringToDecimal(TextBox.Text);
}
private void CancelChanges()
{
TextBox.Undo();
}
private void RemoveFocus()
{
// Passes focus here and then just deletes it
Focusable = true;
Focus();
Focusable = false;
}
private void IncreaseValue(Boolean minor)
{
// Get the value that's currently in the _textBox.Text
decimal value = ParseStringToDecimal(TextBox.Text);
// Coerce the value to min/max
CoerceValueToBounds(ref value);
// Only change the value if it has any meaning
if (value >= MinValue)
{
if (minor)
{
if (IsValueWrapAllowed && value + MinorDelta > MaxValue)
{
value = MinValue;
}
else
{
value += MinorDelta;
}
}
else
{
if (IsValueWrapAllowed && value + MajorDelta > MaxValue)
{
value = MinValue;
}
else
{
value += MajorDelta;
}
}
}
Value = value;
}
private void DecreaseValue(Boolean minor)
{
// Get the value that's currently in the _textBox.Text
decimal value = ParseStringToDecimal(TextBox.Text);
// Coerce the value to min/max
CoerceValueToBounds(ref value);
// Only change the value if it has any meaning
if (value <= MaxValue)
{
if (minor)
{
if (IsValueWrapAllowed && value - MinorDelta < MinValue)
{
value = MaxValue;
}
else
{
value -= MinorDelta;
}
}
else
{
if (IsValueWrapAllowed && value - MajorDelta < MinValue)
{
value = MaxValue;
}
else
{
value -= MajorDelta;
}
}
}
Value = value;
}
#endregion
}
}