Como obtenho um TextBox para aceitar apenas input numérica no WPF?

Eu estou olhando para aceitar dígitos e o ponto decimal, mas nenhum sinal.

Eu olhei para amostras usando o controle NumericUpDown para Windows Forms e esse exemplo de um controle personalizado NumericUpDown da Microsoft . Mas até agora parece que NumericUpDown (suportado pelo WPF ou não) não vai fornecer a funcionalidade que eu quero. A maneira como meu aplicativo é projetado, ninguém no seu perfeito juízo vai querer mexer com as setas. Eles não fazem qualquer sentido prático, no contexto da minha aplicação.

Então eu estou procurando uma maneira simples de fazer um padrão WPF TextBox aceitar apenas os caracteres que eu quero. Isso é possível? É prático?

Adicione um evento de input de texto de visualização. Da mesma forma: .

Então, dentro desse conjunto, o e.Handled se o texto não for permitido. e.Handled = !IsTextAllowed(e.Text);

Eu uso um regex simples no método IsTextAllowed para ver se devo permitir o que eles digitaram. No meu caso, só quero permitir números, pontos e traços.

 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 você quiser evitar colar dados incorretos, conecte o DataObject.Pasting evento DataObject.Pasting="TextBoxPasting" como mostrado aqui (código extraído):

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

O manipulador de events está visualizando a input de texto. Aqui, uma expressão regular corresponde à input de texto somente se não for um número e, em seguida, não é feita para a checkbox de texto de input.

Se você quiser apenas letras, substitua a expressão regular por [^a-zA-Z] .

XAML

  

XAML.CS FILE

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

Eu usei um pouco do que já estava aqui e coloquei meu próprio toque nele usando um comportamento, então não tenho que propagar este código por uma tonelada de 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; } } } 

Aqui está o código de visualização relevante:

      

Esta é uma solução melhorada da resposta do WilP . Minhas melhorias são:

  • Comportamento aprimorado nos botões Del e Backspace
  • Adicionada a propriedade EmptyValue , se a string vazia for inadequada
  • Corrigidos alguns pequenos erros de digitação
 ///  /// 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] -------------------------------- } 

O uso é bem direto:

    

Inclua uma REGRA DE VALIDAÇÃO para que, quando o texto for alterado, verifique se os dados são numéricos e, se estiver, permite que o processamento continue e, se não estiver, solicita ao usuário que apenas dados numéricos sejam aceitos nesse campo.

Leia mais em Validação no Windows Presentation Foundation

O extenso WPF Toolkit tem um: NumericUpDown insira a descrição da imagem aqui

Também poderia simplesmente implementar uma regra de validação e aplicá-la ao TextBox:

          

Com a implementação da regra a seguir (usando o mesmo Regex como proposto em outras respostas):

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

Aqui está uma maneira muito simples e fácil de fazer isso usando o MVVM.

Vincule sua checkbox de texto a uma propriedade de inteiro no modelo de visualização, e isso funcionará como uma gem … ela até mostrará a validação quando um inteiro não for typescript na checkbox de texto.

Código XAML:

  

Visualizar o código do modelo:

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

Eu permiti números de teclado numérico 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); } 

Eu assumirei que:

  1. Seu TextBox para o qual você deseja permitir a input numérica tem apenas sua propriedade Text definida inicialmente para um valor numérico válido (por exemplo, 2,7172).

  2. Sua checkbox de texto é filha da sua janela principal

  3. Sua janela principal é da class Window1

  4. Seu nome TextBox é numericTB

Ideia básica:

  1. Adicione: private string previousText; para sua class de janela principal (Window1)

  2. Adicione: previousText = numericTB.Text; para o seu construtor da janela principal

  3. Crie um manipulador para o evento numericTB.TextChanged para ser algo como isto:

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

Isso manterá definindo previousText como numericTB.Text, desde que seja válido, e definirá o numericTB.Text como seu último valor válido se o usuário escrever algo que você não gosta. Claro, isso é apenas uma idéia básica, e é apenas “idiota resistente”, não “idiota prova”. Não lida com o caso em que o usuário mexe com espaços, por exemplo. Então aqui está uma solução completa que eu acho que é “idiota à prova”, e se eu estiver errado por favor me diga:

  1. Conteúdo do seu arquivo Window1.xaml:

          
  2. Conteúdo do seu arquivo 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 é isso. Se você tiver muitos TextBoxes, então eu recomendo criar um CustomControl que herda de TextBox, para que você possa agrupar previousText e numericTB_TextChanged em um arquivo separado.

Se você não quer escrever muito código para fazer uma function básica (não sei por que as pessoas fazem methods longos), você pode fazer isso:

  1. Adicionar namespace:

     using System.Text.RegularExpressions; 
  2. Em XAML, defina uma propriedade TextChanged:

      
  3. No WPF, no método txt1_TextChanged, adicione Regex.Replace :

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

Este é o único código necessário:

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

Isso permite que apenas números sejam inseridos na checkbox de texto.

Para permitir um ponto decimal ou um sinal de menos, você pode alterar a expressão regular para [^0-9.-]+ .

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

no evento keydown de visualização da checkbox de texto.

Podemos fazer validação no evento alterado da checkbox de texto. A implementação a seguir impede a input de pressionamento de tecla diferente de numérico e um ponto decimal.

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

Aqui eu tenho uma solução simples inspirada na resposta de Ray . Isso deve ser suficiente para identificar qualquer forma de número.

Essa solução também pode ser facilmente modificada se você quiser apenas números positivos, valores inteiros ou valores precisos para um número máximo de casas decimais, etc.


Como sugerido na resposta de Ray , primeiro você precisa adicionar um evento PreviewTextInput :

  

Em seguida, coloque o seguinte no código por trás:

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

Aqui está uma biblioteca para input numérica no WPF

Tem propriedades como NumberStyles e RegexPattern para validação.

Subclasss WPF TextBox

NuGet

Usar:

 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 

Eu estava trabalhando com uma checkbox não acoplada para um projeto simples no qual eu estava trabalhando, então não pude usar a abordagem de vinculação padrão. Consequentemente, criei um hack simples que os outros podem achar bastante útil simplesmente estendendo o controle TextBox existente:

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

Obviamente, para um tipo flutuante, você gostaria de analisá-lo como um float e assim por diante. Os mesmos princípios se aplicam.

Em seguida, no arquivo XAML, você precisa include o namespace relevante:

  

Depois disso, você pode usá-lo como um controle regular:

  

Depois de usar algumas das soluções aqui por algum tempo, desenvolvi o meu próprio que funciona bem para a configuração do MVVM. Note que não é tão dynamic quanto alguns dos outros, no sentido de ainda permitir que os usuários digitem caracteres errôneos, mas os impede de pressionar o botão e, assim, fazer qualquer coisa. Isso vai bem com meu tema de botões acinzentados quando as ações não podem ser executadas.

Eu tenho um TextBox que um usuário deve inserir um número de páginas do documento a serem impressas:

  

… com esta propriedade de binding:

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

Eu também tenho um botão:

  

… com esta binding de comando:

 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 depois há o método de SetNumberOfPages() , mas não é importante para este tópico. Ele funciona bem no meu caso porque eu não tenho que adicionar qualquer código no arquivo code-behind do View e ele me permite controlar o comportamento usando a propriedade Command .

Ao verificar um valor numérico, você pode usar a function VisualBasic.IsNumeric .

No aplicativo WPF, você pode lidar com isso manipulando o 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; } } 

No Windows Forms, era fácil; você pode adicionar um evento para KeyPress e tudo funciona facilmente. No entanto, no WPF esse evento não está lá. Mas há uma maneira muito mais fácil para isso.

O TextBox WPF tem o evento TextChanged, que é geral para tudo. Inclui colar, digitar e o que quer que possa vir à sua mente.

Então você pode fazer algo assim:

XAML:

  

CÓDIGO POR TRÁS:

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

Isso também aceita . , se você não quiser, basta removê-lo da instrução regex para ser @[^\d] .

Nota : Este evento pode ser usado em muitos TextBox, já que usa o Texto do object sender . Você só escreve o evento uma vez e pode usá-lo para vários TextBox’es.

Agora eu sei que esta pergunta tem uma resposta aceita , mas pessoalmente, acho um pouco confuso e acredito que deveria ser mais fácil do que isso. Então, vou tentar demonstrar como consegui que funcionasse da melhor maneira possível:

No Windows Forms , há um evento chamado KeyPress que é perfeitamente bom para esse tipo de tarefa. Mas isso não existe no WPF , então, vamos usar o evento PreviewTextInput . Além disso, para a validação, acredito que um pode usar um foreach para percorrer a textbox.Text e verificar se ele corresponde 😉 a condição, mas honestamente, é para isso que servem as expressões regulares .

Mais uma coisa antes de mergulharmos no código sagrado . Para o evento ser demitido, pode-se fazer duas coisas:

  1. Use o XAML para informar ao programa qual function chamar:
  2. Faça isso no evento Loaded do formulário (em que o textBox está): textBox.PreviewTextInput += onlyNumeric;

Eu acho que o segundo método é melhor, porque em situações como esta, você será obrigado a aplicar a mesma condição ( regex ) para mais de um TextBox e você não vai querer se repetir! .

Finalmente, aqui está como você faria:

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

Para aqueles que procuram uma implementação rápida e muito simples para esse tipo de problema usando apenas números inteiros e decimais, em seu arquivo XAML, inclua uma propriedade PreviewTextInput em seu TextBox e, em seu arquivo xaml.cs, use:

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

É meio redundante continuar checando toda a string toda vez que, como outros já mencionaram, você está fazendo algo com notação científica (embora, se você estiver adicionando certos caracteres como ‘e’, ​​um simples regex adicionando símbolos / caracteres é realmente simples e ilustrado em outras respostas). Mas para valores simples de ponto flutuante, esta solução será suficiente.

Escrito como um one-liner com uma expressão lambda:

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

Aqui está a minha versão disso. É baseado em uma base de ValidatingTextBox class que apenas desfaz o que foi feito se não for “válido”. Suporta colar, cortar, excluir, retroceder, +, – etc.

Para inteiro de 32 bits, existe uma class Int32TextBox que apenas compara com um int. Eu também adicionei classs de validação de ponto flutuante.

 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:

  

Ou

  

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.