WPF TextBox per inserire valori decimali

C’è un modo decente per ottenere un controllo WPF che è legato a un valore decimale?

Quando lego solo TextBox o DataGridTextColumn a un valore decimale, l’immissione di dati fa schifo.

 

Quando provo a inserire “0,5” in questo TextBox, otterrò “5” come risultato. È quasi imansible inserire “0,5” (tranne inserire 1,5 e sostituire “1” con uno “0”).

Quando uso StringFormat, la data entry fa ancora schifo:

  

Ora quando provo ad inserire “0,5” finisco con “0,5,0”, che è ancora sbagliato ma almeno posso rimuovere il trailing “, 0” senza molti problemi.

Tuttavia, inserire decimali usando WPF fa schifo perché questi campi di inserimento sono molto inclini agli errori di immissione dei dati, il che è un vero dolore soprattutto per i valori!

Quindi cosa dovrei usare per l’immissione dei dati decimali in wpf? O Microsoft non supporta i dati decimali ??

Attualmente utilizzo questo comportamento per l’input digitale e decimale:

 public class TextBoxInputBehavior : Behavior { 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 } 

L’utilizzo di XAML è simile al seguente:

      

Il toolkit WPF Extended ha un controllo DecimalUpDown che può soddisfare le tue esigenze. È gratis da usare, ed è meglio usarlo piuttosto che provare a tirare il tuo.

Per quanto riguarda la convalida dell’input su di esso, ci sono diversi modi per applicare la validazione, eccone uno dettagliato in MSDN. Descrivo in dettaglio un altro approccio per la convalida personalizzabile del bindable in due post sul mio blog (si applicherebbe la convalida al binding della proprietà Value sul controllo DecimalUpDown).

  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; } 

Ho anche trovato questo problema; con UpdateSourceTrigger=PropertyChanged sembra che l’associazione cerchi di aggiornare il testo mentre lo si sta digitando. Per risolvere questo problema abbiamo modificato i nostri campi di input in modo che UpdateSourceTrigger=LostFocus , ad esempio:

  

È ansible definire i propri errori di convalida utilizzando l’interfaccia IDataErrorInfo . Hai solo bisogno di aggiungere quanto segue al tuo modello di supporto:

  public class MyModel : IDataErrorInfo { /* my properties */ public string Error { get { return null; } } public string this[string name] { get { switch (name) { case "MyDecimal": return NumberHelper.IsValidValue(MyDecimal) ? message : null; default: return null; } } } private string message = "Invalid value"; } 

Ho implementato il mio TextBox personale. Aggiorna la fonte, quando c’è un numero nel testo, altrimenti no. In Focus perso, ho letto la proprietà sorgente. Tutto ciò che devi fare è sostituire il TextBox con questa class e associare la proprietà “Number” che è di tipo double.

 public class DoubleTextBox: TextBox { public DoubleTextBox() { TextChanged += DoubleTextBox_TextChanged; LostFocus += DoubleTextBox_LostFocus; } void DoubleTextBox_LostFocus(object sender, System.Windows.RoutedEventArgs e) { Text = Number.ToString("N2"); } void DoubleTextBox_TextChanged(object sender, TextChangedEventArgs e) { double zahl; if (string.IsNullOrWhiteSpace(Text)) { Number = 0; } else if (double.TryParse(Text, out zahl)) { Number = Double.Parse(zahl.ToString("N2")); } else { ValidationError validationError = new ValidationError(new ExceptionValidationRule(), GetBindingExpression(NumberProperty)); validationError.ErrorContent = "Keine gültige Zahl"; Validation.MarkInvalid( GetBindingExpression(NumberProperty), validationError); } } public double Number { get { return (double)this.GetValue(NumberProperty); } set { this.SetValue(NumberProperty, value); } } public static readonly DependencyProperty NumberProperty = DependencyProperty.Register( "Number", typeof(double), typeof(DoubleTextBox), new FrameworkPropertyMetadata ( 0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault ) ); } 

Sono nuovo, quindi non posso commentare la sua risposta, ma ho risolto i numeri negativi nel codice di blindmeis .

Basta modificare il

 if (input.Contains("-")) 

sezione di IsValidInput () in …

  if (input.Contains("-")) { if (this.JustPositivDecimalInput) return false; //minus einmal am anfang zulässig //minus once at the beginning if (input.IndexOf("-", StringComparison.Ordinal) == 0 && input.ToCharArray().Count(x => x == '-') == 1) { if(input.Length == 1) { //INPUT IS "-" return true; } else if (input.Length == 2) { //VALIDATE NEGATIVE DECIMALS...INPUT IS "-." if (input.IndexOf(".", StringComparison.Ordinal) == 1) { return true; } } else { return decimal.TryParse(input, validNumberStyles, CultureInfo.CurrentCulture, out d); } } } 

se si desidera che la casella di testo consenta solo i decimali, quindi scrivere l’evento previewinputtext per quella casella di testo. quindi in quell’evento scrivi questo codice

 decimal result; e.Handled=!decimal.TryParse((sender as TextBox).Text + e.Text, out result) 

Questa regex funziona

 private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e) { Regex regex = new Regex("^[.][0-9]+$|^[0-9]*[.]{0,1}[0-9]*$"); e.Handled = !regex.IsMatch((sender as TextBox).Text.Insert((sender as TextBox).SelectionStart,e.Text)); } 

Ciò consentirà di inserire solo i decimali nella casella di testo e nient’altro.

Il viewmodel si presenta così:

  private string _decimalVal = "0"; public string decimalVal { get { return _decimalVal.ToString(); } set { if (string.IsNullOrEmpty(value)) value = "0"; if (value == "-" || Decimal.TryParse(value, out decimal newVal)) SetProperty(ref _decimalVal, value); } } 

L’utilizzo di XAML è simile al seguente: