WPF TextBox para ingresar valores decimales

¿Hay alguna forma decente de obtener un control WPF que esté vinculado a un valor decimal?

Cuando solo enlace el TextBox o DataGridTextColumn a un decimal, la entrada de datos apesta a lo grande.

 

Cuando bash ingresar “0,5” en este TextBox obtendré “5” como resultado. Es casi imposible ingresar “0,5” en absoluto (además de ingresar 1,5 y reemplazar el “1” con un “0”).

Cuando uso la entrada de datos StringFormat todavía apesta poco tiempo:

  

Ahora, cuando bash ingresar “0,5”, terminaré con “0,5,0”, que sigue estando mal, pero al menos puedo eliminar el final “, 0” sin muchos problemas.

Aun así, ingresar los decimales usando WPF es una gran sorpresa porque estos campos de entrada son muy propensos a los errores de entrada de datos, ¡lo cual es un verdadero dolor especialmente para los valores!

Entonces, ¿qué se supone que debo usar para la entrada de datos decimales en wpf? ¿O Microsoft no admite datos decimales?

Actualmente uso este comportamiento para la entrada digital y decimal:

 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 } 

El uso de XAML tiene este aspecto:

      

El kit de herramientas WPF Extended tiene un control DecimalUpDown que puede adaptarse a sus necesidades. Es de uso gratuito, y es mejor usar esto que intentar hacer lo propio.

En cuanto a la validación de la entrada en él, hay una serie de formas de aplicar la validación, aquí hay una detallada en MSDN. Detallo otro enfoque para la validación vinculable personalizada en dos publicaciones en mi blog (aplicaría la validación al enlace de la propiedad Value en el control 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; } 

También me encontré con este problema; con UpdateSourceTrigger=PropertyChanged parece que el enlace intenta actualizar el texto a medida que lo escribe. Para solucionar este problema, modificamos nuestros campos de entrada para que UpdateSourceTrigger=LostFocus , por ejemplo:

  

Puede definir sus propios errores de validación utilizando la interfaz IDataErrorInfo . Solo necesita agregar lo siguiente a su modelo de respaldo:

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

Implementé mi propio TextBox. Actualiza la fuente, cuando hay un número en el texto, de lo contrario no. En el foco perdido, leo la propiedad de origen. Todo lo que tiene que hacer es reemplazar el TextBox con esta clase y enlazar la propiedad “Number” que es de 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 ) ); } 

Soy nuevo, así que no puedo comentar su respuesta, pero arreglé los números negativos en el código de blindmeis .

Solo modifica el

 if (input.Contains("-")) 

sección de IsValidInput () para …

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

si desea que el cuadro de texto solo permita el decimal, escriba el evento previewinputtext para ese cuadro de texto. luego, en ese caso, escribe este código

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

Esta expresión regular funciona

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

Esto permitirá ingresar solo decimales en el cuadro de texto y nada más.

El modelo de vista se ve así:

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

El uso de XAML tiene este aspecto:

  
    Intereting Posts