Entrée de données numériques dans WPF

Comment gérez-vous la saisie de valeurs numériques dans les applications WPF?

Sans un contrôle NumericUpDown, j’utilise un TextBox et gère son événement PreviewKeyDown avec le code ci-dessous, mais c’est plutôt moche.

Quelqu’un at-il trouvé un moyen plus élégant d’obtenir des données numériques de l’utilisateur sans avoir recours à un contrôle tiers?

private void NumericEditPreviewKeyDown(object sender, KeyEventArgs e) { bool isNumPadNumeric = (e.Key >= Key.NumPad0 && e.Key = Key.D0 && e.Key <= Key.D9) || e.Key == Key.OemPeriod; if ((isNumeric || isNumPadNumeric) && Keyboard.Modifiers != ModifierKeys.None) { e.Handled = true; return; } bool isControl = ((Keyboard.Modifiers != ModifierKeys.None && Keyboard.Modifiers != ModifierKeys.Shift) || e.Key == Key.Back || e.Key == Key.Delete || e.Key == Key.Insert || e.Key == Key.Down || e.Key == Key.Left || e.Key == Key.Right || e.Key == Key.Up || e.Key == Key.Tab || e.Key == Key.PageDown || e.Key == Key.PageUp || e.Key == Key.Enter || e.Key == Key.Return || e.Key == Key.Escape || e.Key == Key.Home || e.Key == Key.End); e.Handled = !isControl && !isNumeric && !isNumPadNumeric; } 

Que diriez-vous:

 protected override void OnPreviewTextInput(System.Windows.Input.TextCompositionEventArgs e) { e.Handled = !AreAllValidNumericChars(e.Text); base.OnPreviewTextInput(e); } private bool AreAllValidNumericChars(ssortingng str) { foreach(char c in str) { if(!Char.IsNumber(c)) return false; } return true; } 

C’est comme ça que je le fais. Il utilise une expression régulière pour vérifier si le texte qui sera dans la boîte est numérique ou non.

 Regex NumEx = new Regex(@"^-?\d*\.?\d*$"); private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e) { if (sender is TextBox) { ssortingng text = (sender as TextBox).Text + e.Text; e.Handled = !NumEx.IsMatch(text); } else throw new NotImplementedException("TextBox_PreviewTextInput Can only Handle TextBoxes"); } 

Il y a maintenant un meilleur moyen de le faire dans WPF et Silverlight. Si votre contrôle est lié à une propriété, il vous suffit de modifier un peu votre instruction de liaison. Utilisez ce qui suit pour votre liaison:

  

Notez que vous pouvez également l’utiliser sur les propriétés personnalisées, il suffit de lancer une exception si la valeur de la zone n’est pas valide et que le contrôle sera mis en évidence par une bordure rouge. Si vous cliquez en haut à droite de la bordure rouge, le message d’exception s’affiche.

J’ai utilisé une propriété jointe pour permettre à l’utilisateur d’utiliser les touches haut et bas pour modifier les valeurs dans la zone de texte. Pour l’utiliser, il suffit d’utiliser

 100 

Cela n’aborde pas réellement les problèmes de validation auxquels il est fait référence dans cette question, mais cela concerne ce que je fais pour ne pas avoir de contrôle numérique haut / bas. En l’utilisant un petit peu, je pense que je l’aimerai peut-être mieux que l’ancien contrôle numérique haut / bas.

Le code n’est pas parfait, mais il gère les cas où j’en avais besoin:

  • Flèche Up flèche Down
  • Shift + Up Flèche Shift + Up , Shift + Down Flèche Shift + Down
  • Page Up , Page Down
  • Binding Converter sur la propriété text

Code behind

 using System; using System.Collections.Generic; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Input; namespace Helpers { public class TextBoxNumbers { public static Decimal GetSingleDelta(DependencyObject obj) { return (Decimal)obj.GetValue(SingleDeltaProperty); } public static void SetSingleDelta(DependencyObject obj, Decimal value) { obj.SetValue(SingleDeltaProperty, value); } // Using a DependencyProperty as the backing store for SingleValue. This enables animation, styling, binding, etc... public static readonly DependencyProperty SingleDeltaProperty = DependencyProperty.RegisterAttached("SingleDelta", typeof(Decimal), typeof(TextBoxNumbers), new UIPropertyMetadata(0.0m, new PropertyChangedCallback(f))); public static void f(DependencyObject o, DependencyPropertyChangedEventArgs e) { TextBox t = o as TextBox; if (t == null) return; t.PreviewKeyDown += new System.Windows.Input.KeyEventHandler(t_PreviewKeyDown); } private static Decimal GetSingleValue(DependencyObject obj) { return GetSingleDelta(obj); } private static Decimal GetDoubleValue(DependencyObject obj) { return GetSingleValue(obj) * 10; } private static Decimal GetTripleValue(DependencyObject obj) { return GetSingleValue(obj) * 100; } static void t_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e) { TextBox t = sender as TextBox; Decimal i; if (t == null) return; if (!Decimal.TryParse(t.Text, out i)) return; switch (e.Key) { case System.Windows.Input.Key.Up: if (Keyboard.Modifiers == ModifierKeys.Shift) i += GetDoubleValue(t); else i += GetSingleValue(t); break; case System.Windows.Input.Key.Down: if (Keyboard.Modifiers == ModifierKeys.Shift) i -= GetDoubleValue(t); else i -= GetSingleValue(t); break; case System.Windows.Input.Key.PageUp: i += GetTripleValue(t); break; case System.Windows.Input.Key.PageDown: i -= GetTripleValue(t); break; default: return; } if (BindingOperations.IsDataBound(t, TextBox.TextProperty)) { try { Binding binding = BindingOperations.GetBinding(t, TextBox.TextProperty); t.Text = (ssortingng)binding.Converter.Convert(i, null, binding.ConverterParameter, binding.ConverterCulture); } catch { t.Text = i.ToSsortingng(); } } else t.Text = i.ToSsortingng(); } } } 

J’ai décidé de simplifier la réponse marquée comme réponse ici pour essentiellement 2 lignes en utilisant une expression LINQ.

 e.Handled = !e.Text.All(Char.IsNumber); base.OnPreviewTextInput(e); 

J’utilise une ValidationRule personnalisée pour vérifier si le texte est numérique.

 public class DoubleValidation : ValidationRule { public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) { if (value is ssortingng) { double number; if (!Double.TryParse((value as ssortingng), out number)) return new ValidationResult(false, "Please enter a valid number"); } return ValidationResult.ValidResult; } 

Ensuite, lorsque je lie une zone de TextBox à une propriété numérique, j’ajoute la nouvelle classe personnalisée à la collection Binding.ValidationRules . Dans l’exemple ci-dessous, la règle de validation est vérifiée à chaque fois que TextBox.Text change.

          

Pourquoi n’essayez-vous pas simplement d’utiliser l’événement KeyDown plutôt que l’événement PreviewKeyDown. Vous pouvez arrêter les caractères non valides, mais tous les caractères de contrôle sont acceptés. Cela semble fonctionner pour moi:

 private void NumericKeyDown(object sender, System.Windows.Input.KeyEventArgs e) { bool isNumPadNumeric = (e.Key >= Key.NumPad0 && e.Key <= Key.NumPad9); bool isNumeric =((e.Key >= Key.D0 && e.Key <= Key.D9) && (e.KeyboardDevice.Modifiers == ModifierKeys.None)); bool isDecimal = ((e.Key == Key.OemPeriod || e.Key == Key.Decimal) && (((TextBox)sender).Text.IndexOf('.') < 0)); e.Handled = !(isNumPadNumeric || isNumeric || isDecimal); } 
 public class NumericTextBox : TextBox { public NumericTextBox() : base() { DataObject.AddPastingHandler(this, new DataObjectPastingEventHandler(CheckPasteFormat)); } private Boolean CheckFormat(ssortingng text) { short val; return Int16.TryParse(text, out val); } private void CheckPasteFormat(object sender, DataObjectPastingEventArgs e) { var isText = e.SourceDataObject.GetDataPresent(System.Windows.DataFormats.Text, true); if (isText) { var text = e.SourceDataObject.GetData(DataFormats.Text) as ssortingng; if (CheckFormat(text)) { return; } } e.CancelCommand(); } protected override void OnPreviewTextInput(System.Windows.Input.TextCompositionEventArgs e) { if (!CheckFormat(e.Text)) { e.Handled = true; } else { base.OnPreviewTextInput(e); } } } 

De plus, vous pouvez personnaliser le comportement d’parsing en fournissant des propriétés de dépendance appropriées.

En combinant les idées de quelques unes de ces réponses, j’ai créé un NumericTextBox qui

  • Gère les décimales
  • Effectue une validation de base pour garantir l’entrée de «-» ou «. est valable
  • Gère les valeurs collées

N’hésitez pas à mettre à jour si vous pouvez penser à toute autre logique qui devrait être incluse.

 public class NumericTextBox : TextBox { public NumericTextBox() { DataObject.AddPastingHandler(this, OnPaste); } private void OnPaste(object sender, DataObjectPastingEventArgs dataObjectPastingEventArgs) { var isText = dataObjectPastingEventArgs.SourceDataObject.GetDataPresent(System.Windows.DataFormats.Text, true); if (isText) { var text = dataObjectPastingEventArgs.SourceDataObject.GetData(DataFormats.Text) as ssortingng; if (IsTextValid(text)) { return; } } dataObjectPastingEventArgs.CancelCommand(); } private bool IsTextValid(ssortingng enteredText) { if (!enteredText.All(c => Char.IsNumber(c) || c == '.' || c == '-')) { return false; } //We only validation against unselected text since the selected text will be replaced by the entered text var unselectedText = this.Text.Remove(SelectionStart, SelectionLength); if (enteredText == "." && unselectedText.Contains(".")) { return false; } if (enteredText == "-" && unselectedText.Length > 0) { return false; } return true; } protected override void OnPreviewTextInput(System.Windows.Input.TextCompositionEventArgs e) { e.Handled = !IsTextValid(e.Text); base.OnPreviewTextInput(e); } } 

Vous pouvez également essayer d’utiliser la validation des données si les utilisateurs valident des données avant de les utiliser. Faire ce que j’ai trouvé était assez simple et plus propre que de bidouiller avec des clés.

Sinon, vous pouvez toujours désactiver le collage!

Ma version de réponse Arcturus , peut changer la méthode de conversion utilisée pour travailler avec int / uint / decimal / byte (pour les couleurs) ou tout autre format numérique que vous souhaitez utiliser, fonctionne également avec copier / coller

 protected override void OnPreviewTextInput( System.Windows.Input.TextCompositionEventArgs e ) { try { if ( Ssortingng.IsNullOrEmpty( SelectedText ) ) { Convert.ToDecimal( this.Text.Insert( this.CaretIndex, e.Text ) ); } else { Convert.ToDecimal( this.Text.Remove( this.SelectionStart, this.SelectionLength ).Insert( this.SelectionStart, e.Text ) ); } } catch { // mark as handled if cannot convert ssortingng to decimal e.Handled = true; } base.OnPreviewTextInput( e ); } 

NB Code non testé.

Ajoutez ceci à la solution principale pour vous assurer que la liaison est mise à jour à zéro lorsque la zone de texte est effacée.

 protected override void OnPreviewKeyUp(System.Windows.Input.KeyEventArgs e) { base.OnPreviewKeyUp(e); if (BindingOperations.IsDataBound(this, TextBox.TextProperty)) { if (this.Text.Length == 0) { this.SetValue(TextBox.TextProperty, "0"); this.SelectAll(); } } } 

Appelez-moi fou, mais pourquoi ne pas placer les boutons plus et moins des deux côtés du contrôle TextBox et empêcher simplement le TextBox de recevoir le focus du curseur, créant ainsi votre propre contrôle NumericUpDown?

 private void txtNumericValue_PreviewKeyDown(object sender, KeyEventArgs e) { KeyConverter converter = new KeyConverter(); ssortingng key = converter.ConvertToSsortingng(e.Key); if (key != null && key.Length == 1) { e.Handled = Char.IsDigit(key[0]) == false; } } 

C’est la technique la plus simple que j’ai trouvée pour y parvenir. L’inconvénient est que le menu contextuel de la TextBox autorise toujours les non-numériques via Coller. Pour résoudre ce problème rapidement, j’ai simplement ajouté l’atsortingbut / la propriété ContextMenu = “{x: Null}” au TextBox, ce qui l’a désactivé. Pas idéal mais pour mon scénario cela suffira.

De toute évidence, vous pourriez append quelques clés / caractères supplémentaires dans le test pour inclure des valeurs acceptables supplémentaires (par exemple, «.», «$», Etc.).

 Private Sub Value1TextBox_PreviewTextInput(ByVal sender As Object, ByVal e As TextCompositionEventArgs) Handles Value1TextBox.PreviewTextInput Try If Not IsNumeric(e.Text) Then e.Handled = True End If Catch ex As Exception End Try End Sub 

Travaillé pour moi

Ne pouvez-vous pas simplement utiliser ce qui suit?

 int numericValue = 0; if (false == int.TryParse(yourInput, out numericValue)) { // handle non-numeric input } 
 void PreviewTextInputHandler(object sender, TextCompositionEventArgs e) { ssortingng sVal = e.Text; int val = 0; if (sVal != null && sVal.Length > 0) { if (int.TryParse(sVal, out val)) { e.Handled = false; } else { e.Handled = true; } } } 

Peut aussi utiliser un convertisseur comme:

 public class IntegerFormatConverter : IValueConverter { public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture) { int result; int.TryParse(value.ToSsortingng(), out result); return result; } public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture) { int result; int.TryParse(value.ToSsortingng(), out result); return result; } }