Come posso ottenere un TextBox per accettare solo input numerici in WPF?

Sto cercando di accettare cifre e il punto decimale, ma nessun segno.

Ho esaminato gli esempi utilizzando il controllo NumericUpDown per Windows Form e questo esempio di controllo personalizzato di NumericUpDown di Microsoft . Ma finora sembra che NumericUpDown (supportato da WPF o meno) non fornirà la funzionalità che voglio. Il modo in cui è progettata la mia applicazione, nessuno sano di mente vorrebbe rovinare le frecce. Non hanno alcun senso pratico, nel contesto della mia domanda.

Quindi sto cercando un modo semplice per fare in modo che un TextBox WPF standard accetti solo i caratteri che voglio. È ansible? È pratico?

Aggiungi un evento di inserimento del testo di anteprima. In questo modo: .

Quindi all’interno di essa impostare e.Handled se il testo non è consentito. e.Handled = !IsTextAllowed(e.Text);

Io uso una semplice regex nel metodo IsTextAllowed per vedere se dovrei consentire ciò che hanno digitato. Nel mio caso voglio solo consentire numeri, punti e trattini.

 private static readonly Regex _regex = new Regex("[^0-9.-]+"); //regex that matches disallowed text private static bool IsTextAllowed(string text) { return !_regex.IsMatch(text); } 

Se si desidera impedire l’incollamento di dati non corretti colbind l’evento DataObject.Pasting="TextBoxPasting" come mostrato qui (codice estratto):

 // Use the DataObject.Pasting Handler private void TextBoxPasting(object sender, DataObjectPastingEventArgs e) { if (e.DataObject.GetDataPresent(typeof(String))) { String text = (String)e.DataObject.GetData(typeof(String)); if (!IsTextAllowed(text)) { e.CancelCommand(); } } else { e.CancelCommand(); } } 

Il gestore eventi sta visualizzando l’anteprima del testo. Qui un’espressione regolare corrisponde all’input di testo solo se non è un numero, e quindi non viene inserito nella casella di testo di immissione.

Se vuoi solo lettere, allora sostituisci l’espressione regolare come [^a-zA-Z] .

XAML

  

FILE XAML.CS

 using System.Text.RegularExpressions; private void NumberValidationTextBox(object sender, TextCompositionEventArgs e) { Regex regex = new Regex("[^0-9]+"); e.Handled = regex.IsMatch(e.Text); } 

Ho usato un po ‘di quello che era già qui e ho messo la mia svolta su di esso usando un comportamento, quindi non devo propagare questo codice in tutta una tonnellata di Views …

 public class AllowableCharactersTextBoxBehavior : Behavior { public static readonly DependencyProperty RegularExpressionProperty = DependencyProperty.Register("RegularExpression", typeof(string), typeof(AllowableCharactersTextBoxBehavior), new FrameworkPropertyMetadata(".*")); public string RegularExpression { get { return (string)base.GetValue(RegularExpressionProperty); } set { base.SetValue(RegularExpressionProperty, value); } } public static readonly DependencyProperty MaxLengthProperty = DependencyProperty.Register("MaxLength", typeof(int), typeof(AllowableCharactersTextBoxBehavior), new FrameworkPropertyMetadata(int.MinValue)); public int MaxLength { get { return (int)base.GetValue(MaxLengthProperty); } set { base.SetValue(MaxLengthProperty, value); } } protected override void OnAttached() { base.OnAttached(); AssociatedObject.PreviewTextInput += OnPreviewTextInput; DataObject.AddPastingHandler(AssociatedObject, OnPaste); } private void OnPaste(object sender, DataObjectPastingEventArgs e) { if (e.DataObject.GetDataPresent(DataFormats.Text)) { string text = Convert.ToString(e.DataObject.GetData(DataFormats.Text)); if (!IsValid(text, true)) { e.CancelCommand(); } } else { e.CancelCommand(); } } void OnPreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e) { e.Handled = !IsValid(e.Text, false); } protected override void OnDetaching() { base.OnDetaching(); AssociatedObject.PreviewTextInput -= OnPreviewTextInput; DataObject.RemovePastingHandler(AssociatedObject, OnPaste); } private bool IsValid(string newText, bool paste) { return !ExceedsMaxLength(newText, paste) && Regex.IsMatch(newText, RegularExpression); } private bool ExceedsMaxLength(string newText, bool paste) { if (MaxLength == 0) return false; return LengthOfModifiedText(newText, paste) > MaxLength; } private int LengthOfModifiedText(string newText, bool paste) { var countOfSelectedChars = this.AssociatedObject.SelectedText.Length; var caretIndex = this.AssociatedObject.CaretIndex; string text = this.AssociatedObject.Text; if (countOfSelectedChars > 0 || paste) { text = text.Remove(caretIndex, countOfSelectedChars); return text.Length + newText.Length; } else { var insert = Keyboard.IsKeyToggled(Key.Insert); return insert && caretIndex < text.Length ? text.Length : text.Length + newText.Length; } } } 

Ecco il codice di visualizzazione pertinente:

      

Questa è una soluzione migliorata della risposta di WilP . I miei miglioramenti sono:

  • Miglioramento del comportamento sui pulsanti Del e Backspace
  • Aggiunta la proprietà EmptyValue , se la stringa vuota non è appropriata
  • Risolti alcuni errori minori
 ///  /// Regular expression for Textbox with properties: /// , /// , /// . ///  public class TextBoxInputRegExBehaviour : Behavior { #region DependencyProperties public static readonly DependencyProperty RegularExpressionProperty = DependencyProperty.Register("RegularExpression", typeof(string), typeof(TextBoxInputRegExBehaviour), new FrameworkPropertyMetadata(".*")); public string RegularExpression { get { return (string)GetValue(RegularExpressionProperty); } set { SetValue(RegularExpressionProperty, value); } } public static readonly DependencyProperty MaxLengthProperty = DependencyProperty.Register("MaxLength", typeof(int), typeof(TextBoxInputRegExBehaviour), new FrameworkPropertyMetadata(int.MinValue)); public int MaxLength { get { return (int)GetValue(MaxLengthProperty); } set { SetValue(MaxLengthProperty, value); } } public static readonly DependencyProperty EmptyValueProperty = DependencyProperty.Register("EmptyValue", typeof(string), typeof(TextBoxInputRegExBehaviour), null); public string EmptyValue { get { return (string)GetValue(EmptyValueProperty); } set { SetValue(EmptyValueProperty, value); } } #endregion ///  /// Attach our behaviour. Add event handlers ///  protected override void OnAttached() { base.OnAttached(); AssociatedObject.PreviewTextInput += PreviewTextInputHandler; AssociatedObject.PreviewKeyDown += PreviewKeyDownHandler; DataObject.AddPastingHandler(AssociatedObject, PastingHandler); } ///  /// Deattach our behaviour. remove event handlers ///  protected override void OnDetaching() { base.OnDetaching(); AssociatedObject.PreviewTextInput -= PreviewTextInputHandler; AssociatedObject.PreviewKeyDown -= PreviewKeyDownHandler; DataObject.RemovePastingHandler(AssociatedObject, PastingHandler); } #region Event handlers [PRIVATE] -------------------------------------- void PreviewTextInputHandler(object sender, TextCompositionEventArgs e) { string text; if (this.AssociatedObject.Text.Length < this.AssociatedObject.CaretIndex) text = this.AssociatedObject.Text; else { // Remaining text after removing selected text. string remainingTextAfterRemoveSelection; text = TreatSelectedText(out remainingTextAfterRemoveSelection) ? remainingTextAfterRemoveSelection.Insert(AssociatedObject.SelectionStart, e.Text) : AssociatedObject.Text.Insert(this.AssociatedObject.CaretIndex, e.Text); } e.Handled = !ValidateText(text); } ///  /// PreviewKeyDown event handler ///  void PreviewKeyDownHandler(object sender, KeyEventArgs e) { if (string.IsNullOrEmpty(this.EmptyValue)) return; string text = null; // Handle the Backspace key if (e.Key == Key.Back) { if (!this.TreatSelectedText(out text)) { if (AssociatedObject.SelectionStart > 0) text = this.AssociatedObject.Text.Remove(AssociatedObject.SelectionStart - 1, 1); } } // Handle the Delete key else if (e.Key == Key.Delete) { // If text was selected, delete it if (!this.TreatSelectedText(out text) && this.AssociatedObject.Text.Length > AssociatedObject.SelectionStart) { // Otherwise delete next symbol text = this.AssociatedObject.Text.Remove(AssociatedObject.SelectionStart, 1); } } if (text == string.Empty) { this.AssociatedObject.Text = this.EmptyValue; if (e.Key == Key.Back) AssociatedObject.SelectionStart++; e.Handled = true; } } private void PastingHandler(object sender, DataObjectPastingEventArgs e) { if (e.DataObject.GetDataPresent(DataFormats.Text)) { string text = Convert.ToString(e.DataObject.GetData(DataFormats.Text)); if (!ValidateText(text)) e.CancelCommand(); } else e.CancelCommand(); } #endregion Event handlers [PRIVATE] ----------------------------------- #region Auxiliary methods [PRIVATE] ----------------------------------- ///  /// Validate certain text by our regular expression and text length conditions ///  ///  Text for validation  ///  True - valid, False - invalid  private bool ValidateText(string text) { return (new Regex(this.RegularExpression, RegexOptions.IgnoreCase)).IsMatch(text) && (MaxLength == int.MinValue || text.Length <= MaxLength); } ///  /// Handle text selection ///  /// true if the character was successfully removed; otherwise, false.  private bool TreatSelectedText(out string text) { text = null; if (AssociatedObject.SelectionLength <= 0) return false; var length = this.AssociatedObject.Text.Length; if (AssociatedObject.SelectionStart >= length) return true; if (AssociatedObject.SelectionStart + AssociatedObject.SelectionLength >= length) AssociatedObject.SelectionLength = length - AssociatedObject.SelectionStart; text = this.AssociatedObject.Text.Remove(AssociatedObject.SelectionStart, AssociatedObject.SelectionLength); return true; } #endregion Auxiliary methods [PRIVATE] -------------------------------- } 

L’utilizzo è piuttosto semplice:

    

Aggiungi una REGOLA DI VALIDAZIONE in modo che quando il testo cambia, verifica se il dato è numerico e, se lo è, consente di continuare l’elaborazione e, in caso contrario, richiede all’utente che solo i dati numerici siano accettati in quel campo.

Maggiori informazioni in Convalida in Windows Presentation Foundation

Il Toolkit WPF esteso ne ha uno: NumericUpDown inserisci la descrizione dell'immagine qui

Potrebbe anche semplicemente implementare una regola di convalida e applicarla al TextBox:

          

Con l’implementazione della regola come segue (utilizzando lo stesso Regex come proposto in altre risposte):

 public class OnlyDigitsValidationRule : ValidationRule { public override ValidationResult Validate(object value, CultureInfo cultureInfo) { var validationResult = new ValidationResult(true, null); if(value != null) { if (!string.IsNullOrEmpty(value.ToString())) { var regex = new Regex("[^0-9.-]+"); //regex that matches disallowed text var parsingOk = !regex.IsMatch(value.ToString()); if (!parsingOk) { validationResult = new ValidationResult(false, "Illegal Characters, Please Enter Numeric Value"); } } } return validationResult; } } 

Ecco un modo molto semplice e semplice per farlo usando MVVM.

Collega la tua casella di testo con una proprietà intera nel modello di visualizzazione e questo funzionerà come un gioiello … mostrerà anche la convalida quando un non intero viene inserito nella casella di testo.

Codice XAML:

  

Visualizza il codice del modello:

 private long _contactNo; public long contactNo { get { return _contactNo; } set { if (value == _contactNo) return; _contactNo = value; OnPropertyChanged(); } } 

Ho permesso numeri di tastiera numerica e backspace:

  private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e) { int key = (int)e.Key; e.Handled = !(key >= 34 && key <= 43 || key >= 74 && key <= 83 || key == 2); } 

Assumerò che:

  1. La casella di testo per cui si desidera consentire solo l’input numerico ha inizialmente la proprietà Text impostata su un valore numerico valido (ad esempio, 2.7172).

  2. La tua casella di testo è figlia della tua finestra principale

  3. La tua finestra principale è di class Window1

  4. Il tuo nome TextBox è numericoTB

Idea base:

  1. Aggiungi: private string previousText; alla class della finestra principale (Finestra1)

  2. Aggiungi: previousText = numericTB.Text; al tuo costruttore di windows principali

  3. Creare un gestore per l’evento numericoTB.TextChanged in modo che sia simile a questo:

     private void numericTB_TextChanged(object sender, TextChangedEventArgs e) { double num = 0; bool success = double.TryParse(((TextBox)sender).Text, out num); if (success & num >= 0) previousText = ((TextBox)sender).Text; else ((TextBox)sender).Text = previousText; } 

Ciò manterrà l’impostazione di previousText a numericTB.Text finché è valido e imposta numericTB.Text sull’ultimo valore valido se l’utente scrive qualcosa che non ti piace. Certo, questa è solo un’idea di base, ed è solo “idiota resistente”, non “prova idiota”. Ad esempio, non gestisce il caso in cui l’utente ha problemi con gli spazi. Quindi ecco una soluzione completa che penso sia “prova idiota”, e se sbaglio mi dica:

  1. Contenuto del file Window1.xaml:

          
  2. Contenuto del file Window.xaml.cs:

     using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace IdiotProofNumericTextBox { public partial class Window1 : Window { private string previousText; public Window1() { InitializeComponent(); previousText = numericTB.Text; } private void numericTB_TextChanged(object sender, TextChangedEventArgs e) { if (string.IsNullOrEmpty(((TextBox)sender).Text)) previousText = ""; else { double num = 0; bool success = double.TryParse(((TextBox)sender).Text, out num); if (success & num >= 0) { ((TextBox)sender).Text.Trim(); previousText = ((TextBox)sender).Text; } else { ((TextBox)sender).Text = previousText; ((TextBox)sender).SelectionStart = ((TextBox)sender).Text.Length; } } } } } 

E questo è tutto. Se hai molti TextBox, ti consiglio di creare un CustomControl che erediti da TextBox, quindi puoi racchiudere testo precedente e numericoTB_TextChanged in un file separato.

Se non vuoi scrivere molto codice per svolgere una funzione di base (non so perché le persone realizzano metodi lunghi) puoi semplicemente fare questo:

  1. Aggiungi spazio dei nomi:

     using System.Text.RegularExpressions; 
  2. In XAML, imposta una proprietà TextChanged:

      
  3. In WPF con il metodo txt1_TextChanged, aggiungi Regex.Replace :

     private void txt1_TextChanged(object sender, TextChangedEventArgs e) { txt1.Text = Regex.Replace(txt1.Text, "[^0-9]+", ""); } 

Questo è l’unico codice necessario:

 void MyTextBox_PreviewTextInput(object sender, TextCompositionEventArgs e) { e.Handled = new Regex("[^0-9]+").IsMatch(e.Text); } 

Questo consente di immettere solo numeri nella casella di testo.

Per consentire un segno decimale o segno meno, è ansible modificare l’espressione regolare in [^0-9.-]+ .

 PreviewTextInput += (s, e) => { e.Handled = !e.Text.All(char.IsDigit); }; 
 e.Handled = (int)e.Key >= 43 || (int)e.Key <= 34; 

in anteprima evento keydown della casella di testo.

Possiamo fare la convalida sull’evento modificato della casella di testo. La seguente implementazione impedisce l’input di pressione di un tasto diverso da quello numerico e da un punto decimale.

 private void textBoxNumeric_TextChanged(object sender, TextChangedEventArgs e) { TextBox textBox = sender as TextBox; Int32 selectionStart = textBox.SelectionStart; Int32 selectionLength = textBox.SelectionLength; String newText = String.Empty; int count = 0; foreach (Char c in textBox.Text.ToCharArray()) { if (Char.IsDigit(c) || Char.IsControl(c) || (c == '.' && count == 0)) { newText += c; if (c == '.') count += 1; } } textBox.Text = newText; textBox.SelectionStart = selectionStart <= textBox.Text.Length ? selectionStart : textBox.Text.Length; } 

Qui ho una soluzione semplice ispirata alla risposta di Ray . Questo dovrebbe essere sufficiente per identificare qualsiasi forma di numero.

Questa soluzione può anche essere facilmente modificata se si desidera solo numeri positivi, valori interi o valori precisi per un numero massimo di posizioni decimali, ecc.


Come suggerito nella risposta di Ray , devi prima aggiungere un evento PreviewTextInput :

  

Quindi inserisci quanto segue nel codice sottostante:

 private void TextBox_OnPreviewTextInput(object sender, TextCompositionEventArgs e) { var s = sender as TextBox; // Use SelectionStart property to find the caret position. // Insert the previewed text into the existing text in the textbox. var text = s.Text.Insert(s.SelectionStart, e.Text); double d; // If parsing is successful, set Handled to false e.Handled = !double.TryParse(text, out d); } 

Ecco una libreria per l’input numerico in WPF

Ha proprietà come RegexPattern e RegexPattern per la convalida.

Sottoclassi WPF TextBox

NuGet

Uso:

 Private Sub DetailTextBox_PreviewTextInput( _ ByVal sender As Object, _ ByVal e As System.Windows.Input.TextCompositionEventArgs) _ Handles DetailTextBox.PreviewTextInput If _IsANumber Then If Not Char.IsNumber(e.Text) Then e.Handled = True End If End If End Sub 

Stavo lavorando con una casella non associata per un progetto semplice su cui stavo lavorando, quindi non ho potuto utilizzare l’approccio di bind standard. Di conseguenza ho creato un semplice trucco che altri potrebbero trovare abbastanza utile semplicemente estendendo il controllo TextBox esistente:

 namespace MyApplication.InterfaceSupport { public class NumericTextBox : TextBox { public NumericTextBox() : base() { TextChanged += OnTextChanged; } public void OnTextChanged(object sender, TextChangedEventArgs changed) { if (!String.IsNullOrWhiteSpace(Text)) { try { int value = Convert.ToInt32(Text); } catch (Exception e) { MessageBox.Show(String.Format("{0} only accepts numeric input.", Name)); Text = ""; } } } public int? Value { set { if (value != null) { this.Text = value.ToString(); } else Text = ""; } get { try { return Convert.ToInt32(this.Text); } catch (Exception ef) { // Not numeric. } return null; } } } } 

Ovviamente, per un tipo floating, si vorrebbe analizzarlo come un float e così via. Si applicano gli stessi principi.

Quindi nel file XAML è necessario includere lo spazio dei nomi rilevante:

  

Dopodiché puoi usarlo come controllo regolare:

  

Dopo aver usato alcune delle soluzioni qui per un po ‘di tempo, ho sviluppato il mio che funziona bene per il mio setup MVVM. Si noti che non è così dinamico come alcuni degli altri, nel senso di consentire comunque agli utenti di inserire caratteri errati, ma li blocca dal premere il pulsante e quindi fare qualsiasi cosa. Questo va bene con il mio tema di ingrigire i pulsanti quando le azioni non possono essere eseguite.

Ho un TextBox che un utente deve inserire un numero di pagine del documento da stampare:

  

… con questa proprietà vincolante:

 private string _numberPagesToPrint; public string NumberPagesToPrint { get { return _numberPagesToPrint; } set { if (_numberPagesToPrint == value) { return; } _numberPagesToPrint = value; OnPropertyChanged("NumberPagesToPrint"); } } 

Ho anche un pulsante:

  

… con questo comando vincolante:

 private RelayCommand _setNumberPagesCommand; public ICommand SetNumberPagesCommand { get { if (_setNumberPagesCommand == null) { int num; _setNumberPagesCommand = new RelayCommand(param => SetNumberOfPages(), () => Int32.TryParse(NumberPagesToPrint, out num)); } return _setNumberPagesCommand; } } 

E poi c’è il metodo di SetNumberOfPages() , ma non è importante per questo argomento. Funziona bene nel mio caso perché non devo aggiungere alcun codice nel file code-behind di View e mi permette di controllare il comportamento usando la proprietà Command .

Quando si controlla un valore numerico, è ansible utilizzare la funzione VisualBasic.IsNumeric .

Nell’applicazione WPF, puoi gestirlo gestendo l’evento TextChanged :

 void arsDigitTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e) { Regex regex = new Regex("[^0-9]+"); bool handle = regex.IsMatch(this.Text); if (handle) { StringBuilder dd = new StringBuilder(); int i = -1; int cursor = -1; foreach (char item in this.Text) { i++; if (char.IsDigit(item)) dd.Append(item); else if(cursor == -1) cursor = i; } this.Text = dd.ToString(); if (i == -1) this.SelectionStart = this.Text.Length; else this.SelectionStart = cursor; } } 

In Windows Form è stato facile; puoi aggiungere un evento per KeyPress e tutto funziona facilmente. Tuttavia, in WPF quell’evento non è presente. Ma c’è un modo molto più semplice per farlo.

Il TextBox WPF ha l’evento TextChanged che è generale per tutto. Comprende incollare, digitare e qualunque cosa ti venga in mente.

Quindi puoi fare qualcosa del genere:

XAML:

  

CODICE DIETRO:

 private void TextBox_TextChanged(object sender, TextChangedEventArgs e) { string s = Regex.Replace(((TextBox)sender).Text, @"[^\d.]", ""); ((TextBox)sender).Text = s; } 

Questo accetta anche . , se non lo vuoi, basta rimuoverlo regex per essere @[^\d] .

Nota : questo evento può essere utilizzato su molti TextBox poiché utilizza il testo dell’object sender . Scrivi l’evento una sola volta e puoi utilizzarlo per più TextBox.

Ora so che questa domanda ha una risposta accettata , ma personalmente, trovo un po ‘confusa e credo che dovrebbe essere più facile di così. Quindi proverò a dimostrare come ho potuto farlo funzionare nel miglior modo ansible:

In Windows Form , c’è un evento chiamato KeyPress che è perfetto per questo tipo di attività. Ma questo non esiste in WPF , quindi, invece, useremo l’evento PreviewTextInput . Inoltre, per la convalida, credo che si possa usare un foreach per scorrere ciclicamente la textbox.Text e controllarne la corrispondenza 😉 la condizione, ma onestamente, questo è ciò che le espressioni regolari servono.

Un’altra cosa prima di immergerci nel codice sacro . Perché l’evento sia licenziato, si possono fare due cose:

  1. Usa XAML per dire al programma quale funzione chiamare:
  2. Fatelo nell’evento Loaded del modulo (in cui è inserito il textBox): textBox.PreviewTextInput += onlyNumeric;

Penso che il secondo metodo sia migliore perché in situazioni come questa, ti verrà principalmente richiesto di applicare la stessa condizione ( regex ) a più di un TextBox e non ti vuoi ripetere! .

Finalmente, ecco come lo faresti:

 private void onlyNumeric(object sender, TextCompositionEventArgs e) { string onlyNumeric = @"^([0-9]+(.[0-9]+)?)$"; Regex regex = new Regex(onlyNumeric); e.Handled = !regex.IsMatch(e.Text); } 

Per coloro che cercano un’implementazione rapida e molto semplice per questo tipo di problema utilizzando solo numeri interi e decimali, nel file XAML aggiungi una proprietà PreviewTextInput al tuo TextBox e quindi nel tuo file xaml.cs utilizza:

 private void Text_PreviewTextInput(object sender, TextCompositionEventArgs e) { e.Handled = !char.IsDigit(e.Text.Last()) && !e.Text.Last() == '.'; } 

È ridondante continuare a controllare l’intera stringa ogni volta, a meno che, come altri hanno già detto, stai facendo qualcosa con la notazione scientifica (anche se, aggiungendo alcuni caratteri come ‘e’, ​​una semplice regex che aggiunge simboli / caratteri è davvero semplice e illustrato in altre risposte). Ma per i semplici valori in virgola mobile, questa soluzione sarà sufficiente.

Scritto come one-liner con un’espressione lambda:

 private void Text_PreviewTextInput(object sender, TextCompositionEventArgs e) => e.Handled = !char.IsDigit(e.Text.Last() && !e.Text.Last() == '.'); 

Ecco la mia versione di esso. Si basa su una class ValidatingTextBox base che annulla semplicemente ciò che è stato fatto se non è “valido”. Supporta incollare, tagliare, eliminare, backspace, +, – ecc.

Per i numeri interi a 32 bit, esiste una class Int32TextBox che si confronta solo con un int. Ho anche aggiunto delle classi di validazione in virgola mobile.

 public class ValidatingTextBox : TextBox { private bool _inEvents; private string _textBefore; private int _selectionStart; private int _selectionLength; public event EventHandler ValidateText; protected override void OnPreviewKeyDown(KeyEventArgs e) { if (_inEvents) return; _selectionStart = SelectionStart; _selectionLength = SelectionLength; _textBefore = Text; } protected override void OnTextChanged(TextChangedEventArgs e) { if (_inEvents) return; _inEvents = true; var ev = new ValidateTextEventArgs(Text); OnValidateText(this, ev); if (ev.Cancel) { Text = _textBefore; SelectionStart = _selectionStart; SelectionLength = _selectionLength; } _inEvents = false; } protected virtual void OnValidateText(object sender, ValidateTextEventArgs e) => ValidateText?.Invoke(this, e); } public class ValidateTextEventArgs : CancelEventArgs { public ValidateTextEventArgs(string text) => Text = text; public string Text { get; } } public class Int32TextBox : ValidatingTextBox { protected override void OnValidateText(object sender, ValidateTextEventArgs e) => e.Cancel = !int.TryParse(e.Text, out var value); } public class Int64TextBox : ValidatingTextBox { protected override void OnValidateText(object sender, ValidateTextEventArgs e) => e.Cancel = !long.TryParse(e.Text, out var value); } public class DoubleTextBox : ValidatingTextBox { protected override void OnValidateText(object sender, ValidateTextEventArgs e) => e.Cancel = !double.TryParse(e.Text, out var value); } public class SingleTextBox : ValidatingTextBox { protected override void OnValidateText(object sender, ValidateTextEventArgs e) => e.Cancel = !float.TryParse(e.Text, out var value); } public class DecimalTextBox : ValidatingTextBox { protected override void OnValidateText(object sender, ValidateTextEventArgs e) => e.Cancel = !decimal.TryParse(e.Text, out var value); } 

Note 1: When using WPF binding, you must make sure you use the class that fits the bound property type otherwise, it may lead to strange results.

Note 2: When using floating point classs with WPF binding, make sure the binding uses the current culture to match the TryParse method I’ve used.

Another approach will be using an attached behavior, I implemented my custom TextBoxHelper class, which can be used on textboxes all over my project. Because I figured that subscribing to the events for every textboxes and in every individual XAML file for this purpose can be time consuming.

The TextBoxHelper class I implemented has these features:

  • Filtering and accepting only numbers in Double , Int , Uint and Natural format
  • Filtering and accepting only Even or Odd numbers
  • Handling paste event handler to prevent pasting invalid text into our numeric textboxes
  • Can set a Default Value which will be used to prevent invalid data as the last shot by subscribing to the textboxes TextChanged event

Here is the implementation of TextBoxHelper class:

 public static class TextBoxHelper { #region Enum Declarations public enum NumericFormat { Double, Int, Uint, Natural } public enum EvenOddConstraint { All, OnlyEven, OnlyOdd } #endregion #region Dependency Properties & CLR Wrappers public static readonly DependencyProperty OnlyNumericProperty = DependencyProperty.RegisterAttached("OnlyNumeric", typeof(NumericFormat?), typeof(TextBoxHelper), new PropertyMetadata(null, DependencyPropertiesChanged)); public static void SetOnlyNumeric(TextBox element, NumericFormat value) => element.SetValue(OnlyNumericProperty, value); public static NumericFormat GetOnlyNumeric(TextBox element) => (NumericFormat) element.GetValue(OnlyNumericProperty); public static readonly DependencyProperty DefaultValueProperty = DependencyProperty.RegisterAttached("DefaultValue", typeof(string), typeof(TextBoxHelper), new PropertyMetadata(null, DependencyPropertiesChanged)); public static void SetDefaultValue(TextBox element, string value) => element.SetValue(DefaultValueProperty, value); public static string GetDefaultValue(TextBox element) => (string) element.GetValue(DefaultValueProperty); public static readonly DependencyProperty EvenOddConstraintProperty = DependencyProperty.RegisterAttached("EvenOddConstraint", typeof(EvenOddConstraint), typeof(TextBoxHelper), new PropertyMetadata(EvenOddConstraint.All, DependencyPropertiesChanged)); public static void SetEvenOddConstraint(TextBox element, EvenOddConstraint value) => element.SetValue(EvenOddConstraintProperty, value); public static EvenOddConstraint GetEvenOddConstraint(TextBox element) => (EvenOddConstraint)element.GetValue(EvenOddConstraintProperty); #endregion #region Dependency Properties Methods private static void DependencyPropertiesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (!(d is TextBox textBox)) throw new Exception("Attached property must be used with TextBox."); switch (e.Property.Name) { case "OnlyNumeric": { var castedValue = (NumericFormat?) e.NewValue; if (castedValue.HasValue) { textBox.PreviewTextInput += TextBox_PreviewTextInput; DataObject.AddPastingHandler(textBox, TextBox_PasteEventHandler); } else { textBox.PreviewTextInput -= TextBox_PreviewTextInput; DataObject.RemovePastingHandler(textBox, TextBox_PasteEventHandler); } break; } case "DefaultValue": { var castedValue = (string) e.NewValue; if (castedValue != null) { textBox.TextChanged += TextBox_TextChanged; } else { textBox.TextChanged -= TextBox_TextChanged; } break; } } } #endregion private static void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e) { var textBox = (TextBox)sender; string newText; if (textBox.SelectionLength == 0) { newText = textBox.Text.Insert(textBox.SelectionStart, e.Text); } else { var textAfterDelete = textBox.Text.Remove(textBox.SelectionStart, textBox.SelectionLength); newText = textAfterDelete.Insert(textBox.SelectionStart, e.Text); } var evenOddConstraint = GetEvenOddConstraint(textBox); switch (GetOnlyNumeric(textBox)) { case NumericFormat.Double: { if (double.TryParse(newText, out double number)) { switch (evenOddConstraint) { case EvenOddConstraint.OnlyEven: if (number % 2 != 0) e.Handled = true; else e.Handled = false; break; case EvenOddConstraint.OnlyOdd: if (number % 2 == 0) e.Handled = true; else e.Handled = false; break; } } else e.Handled = true; break; } case NumericFormat.Int: { if (int.TryParse(newText, out int number)) { switch (evenOddConstraint) { case EvenOddConstraint.OnlyEven: if (number % 2 != 0) e.Handled = true; else e.Handled = false; break; case EvenOddConstraint.OnlyOdd: if (number % 2 == 0) e.Handled = true; else e.Handled = false; break; } } else e.Handled = true; break; } case NumericFormat.Uint: { if (uint.TryParse(newText, out uint number)) { switch (evenOddConstraint) { case EvenOddConstraint.OnlyEven: if (number % 2 != 0) e.Handled = true; else e.Handled = false; break; case EvenOddConstraint.OnlyOdd: if (number % 2 == 0) e.Handled = true; else e.Handled = false; break; } } else e.Handled = true; break; } case NumericFormat.Natural: { if (uint.TryParse(newText, out uint number)) { if (number == 0) e.Handled = true; else { switch (evenOddConstraint) { case EvenOddConstraint.OnlyEven: if (number % 2 != 0) e.Handled = true; else e.Handled = false; break; case EvenOddConstraint.OnlyOdd: if (number % 2 == 0) e.Handled = true; else e.Handled = false; break; } } } else e.Handled = true; break; } } } private static void TextBox_PasteEventHandler(object sender, DataObjectPastingEventArgs e) { var textBox = (TextBox)sender; if (e.DataObject.GetDataPresent(typeof(string))) { var clipboardText = (string) e.DataObject.GetData(typeof(string)); var newText = textBox.Text.Insert(textBox.SelectionStart, clipboardText); var evenOddConstraint = GetEvenOddConstraint(textBox); switch (GetOnlyNumeric(textBox)) { case NumericFormat.Double: { if (double.TryParse(newText, out double number)) { switch (evenOddConstraint) { case EvenOddConstraint.OnlyEven: if (number % 2 != 0) e.CancelCommand(); break; case EvenOddConstraint.OnlyOdd: if (number % 2 == 0) e.CancelCommand(); break; } } else e.CancelCommand(); break; } case NumericFormat.Int: { if (int.TryParse(newText, out int number)) { switch (evenOddConstraint) { case EvenOddConstraint.OnlyEven: if (number % 2 != 0) e.CancelCommand(); break; case EvenOddConstraint.OnlyOdd: if (number % 2 == 0) e.CancelCommand(); break; } } else e.CancelCommand(); break; } case NumericFormat.Uint: { if (uint.TryParse(newText, out uint number)) { switch (evenOddConstraint) { case EvenOddConstraint.OnlyEven: if (number % 2 != 0) e.CancelCommand(); break; case EvenOddConstraint.OnlyOdd: if (number % 2 == 0) e.CancelCommand(); break; } } else e.CancelCommand(); break; } case NumericFormat.Natural: { if (uint.TryParse(newText, out uint number)) { if (number == 0) e.CancelCommand(); else { switch (evenOddConstraint) { case EvenOddConstraint.OnlyEven: if (number % 2 != 0) e.CancelCommand(); break; case EvenOddConstraint.OnlyOdd: if (number % 2 == 0) e.CancelCommand(); break; } } } else { e.CancelCommand(); } break; } } } else { e.CancelCommand(); } } private static void TextBox_TextChanged(object sender, TextChangedEventArgs e) { var textBox = (TextBox)sender; var defaultValue = GetDefaultValue(textBox); var evenOddConstraint = GetEvenOddConstraint(textBox); switch (GetOnlyNumeric(textBox)) { case NumericFormat.Double: { if (double.TryParse(textBox.Text, out double number)) { switch (evenOddConstraint) { case EvenOddConstraint.OnlyEven: if (number % 2 != 0) textBox.Text = defaultValue; break; case EvenOddConstraint.OnlyOdd: if (number % 2 == 0) textBox.Text = defaultValue; break; } } else textBox.Text = defaultValue; break; } case NumericFormat.Int: { if (int.TryParse(textBox.Text, out int number)) { switch (evenOddConstraint) { case EvenOddConstraint.OnlyEven: if (number % 2 != 0) textBox.Text = defaultValue; break; case EvenOddConstraint.OnlyOdd: if (number % 2 == 0) textBox.Text = defaultValue; break; } } else textBox.Text = defaultValue; break; } case NumericFormat.Uint: { if (uint.TryParse(textBox.Text, out uint number)) { switch (evenOddConstraint) { case EvenOddConstraint.OnlyEven: if (number % 2 != 0) textBox.Text = defaultValue; break; case EvenOddConstraint.OnlyOdd: if (number % 2 == 0) textBox.Text = defaultValue; break; } } else textBox.Text = defaultValue; break; } case NumericFormat.Natural: { if (uint.TryParse(textBox.Text, out uint number)) { if(number == 0) textBox.Text = defaultValue; else { switch (evenOddConstraint) { case EvenOddConstraint.OnlyEven: if (number % 2 != 0) textBox.Text = defaultValue; break; case EvenOddConstraint.OnlyOdd: if (number % 2 == 0) textBox.Text = defaultValue; break; } } } else { textBox.Text = defaultValue; } break; } } } } 

And here is some example of its easy usage:

  

O

  

Note that my TextBoxHelper resides in the viewHelpers xmlns alias.

I hope that this implementation eases some other one’s work 🙂

This is what I would use to get a WPF textbox that accept digits and the decimal point:

 class numericTextBox : TextBox { protected override void OnKeyDown(KeyEventArgs e) { bool b = false; switch (e.Key) { case Key.Back: b = true; break; case Key.D0: b = true; break; case Key.D1: b = true; break; case Key.D2: b = true; break; case Key.D3: b = true; break; case Key.D4: b = true; break; case Key.D5: b = true; break; case Key.D6: b = true; break; case Key.D7: b = true; break; case Key.D8: b = true; break; case Key.D9: b = true; break; case Key.OemPeriod: b = true; break; } if (b == false) { e.Handled = true; } base.OnKeyDown(e); } } 

Put the code in a new class file, add

 using System.Windows.Controls; using System.Windows.Input; 

at the top of the file and build the solution. The numericTextBox control will then appear at the top of the toolbox.