Is there any decent way to get a WPF control which is bound to a decimal
value?
When I just bind the TextBox
or DataGridTextColumn
to a decimal
, data entry is a problem.
<TextBox Text="{Binding MyDecimal, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>
When I try to enter "0,5" in this TextBox
I'll get "5" as a result. It is nearly impossible to enter "0,5" at all (apart from entering 1,5 and replacing the "1" with a "0").
When I use StringFormat
, data entry is only slightly improved:
<TextBox Text="{Binding MyDecimal, StringFormat=F1, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>
Now, when I try to enter "0,5" I'll end up with "0,5,0", which still is wrong but at least I can remove the trailing ",0" without much difficulty.
Still, entering decimal
types using WPF is very awkward, because these TextBox
es are very prone to data entry errors, which is a real pain especially for values!
So what am I supposed to use for decimal data entry in WPF? Or does Microsoft not support decimal data??
I currently use this behavior for digital and decimal input:
public class TextBoxInputBehavior : Behavior<TextBox> { const NumberStyles validNumberStyles = NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands | NumberStyles.AllowLeadingSign; public TextBoxInputBehavior() { this.InputMode = TextBoxInputMode.None; this.JustPositivDecimalInput = false; } public TextBoxInputMode InputMode { get; set; } public static readonly DependencyProperty JustPositivDecimalInputProperty = DependencyProperty.Register("JustPositivDecimalInput", typeof(bool), typeof(TextBoxInputBehavior), new FrameworkPropertyMetadata(false)); public bool JustPositivDecimalInput { get { return (bool)GetValue(JustPositivDecimalInputProperty); } set { SetValue(JustPositivDecimalInputProperty, value); } } protected override void OnAttached() { base.OnAttached(); AssociatedObject.PreviewTextInput += AssociatedObjectPreviewTextInput; AssociatedObject.PreviewKeyDown += AssociatedObjectPreviewKeyDown; DataObject.AddPastingHandler(AssociatedObject, Pasting); } protected override void OnDetaching() { base.OnDetaching(); AssociatedObject.PreviewTextInput -= AssociatedObjectPreviewTextInput; AssociatedObject.PreviewKeyDown -= AssociatedObjectPreviewKeyDown; DataObject.RemovePastingHandler(AssociatedObject, Pasting); } private void Pasting(object sender, DataObjectPastingEventArgs e) { if (e.DataObject.GetDataPresent(typeof(string))) { var pastedText = (string)e.DataObject.GetData(typeof(string)); if (!this.IsValidInput(this.GetText(pastedText))) { System.Media.SystemSounds.Beep.Play(); e.CancelCommand(); } } else { System.Media.SystemSounds.Beep.Play(); e.CancelCommand(); } } private void AssociatedObjectPreviewKeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Space) { if (!this.IsValidInput(this.GetText(" "))) { System.Media.SystemSounds.Beep.Play(); e.Handled = true; } } } private void AssociatedObjectPreviewTextInput(object sender, TextCompositionEventArgs e) { if (!this.IsValidInput(this.GetText(e.Text))) { System.Media.SystemSounds.Beep.Play(); e.Handled = true; } } private string GetText(string input) { var txt = this.AssociatedObject; int selectionStart = txt.SelectionStart; if (txt.Text.Length < selectionStart) selectionStart = txt.Text.Length; int selectionLength = txt.SelectionLength; if (txt.Text.Length < selectionStart + selectionLength) selectionLength = txt.Text.Length - selectionStart; var realtext = txt.Text.Remove(selectionStart, selectionLength); int caretIndex = txt.CaretIndex; if (realtext.Length < caretIndex) caretIndex = realtext.Length; var newtext = realtext.Insert(caretIndex, input); return newtext; } private bool IsValidInput(string input) { switch (InputMode) { case TextBoxInputMode.None: return true; case TextBoxInputMode.DigitInput: return CheckIsDigit(input); case TextBoxInputMode.DecimalInput: decimal d; //wen mehr als ein Komma if (input.ToCharArray().Where(x => x == ',').Count() > 1) return false; if (input.Contains("-")) { if (this.JustPositivDecimalInput) return false; if (input.IndexOf("-",StringComparison.Ordinal) > 0) return false; if(input.ToCharArray().Count(x=>x=='-') > 1) return false; //minus einmal am anfang zulässig if (input.Length == 1) return true; } var result = decimal.TryParse(input, validNumberStyles, CultureInfo.CurrentCulture, out d); return result; default: throw new ArgumentException("Unknown TextBoxInputMode"); } return true; } private bool CheckIsDigit(string wert) { return wert.ToCharArray().All(Char.IsDigit); } } public enum TextBoxInputMode { None, DecimalInput, DigitInput }
The XAML usage looks like this:
<TextBox Text="{Binding Sum}"> <i:Interaction.Behaviors> <Behaviors:TextBoxInputBehavior InputMode="DecimalInput"/> </i:Interaction.Behaviors> </TextBox>
private void DecimalTextBox_PreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e) { bool approvedDecimalPoint = false; if (e.Text == ".") { if (!((TextBox)sender).Text.Contains(".")) approvedDecimalPoint = true; } if (!(char.IsDigit(e.Text, e.Text.Length - 1) || approvedDecimalPoint)) e.Handled = true; }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With