Как получить TextBox, чтобы принимать только числовые данные в WPF?
Я хочу принять цифры и десятичную точку, но без знака.
Я просмотрел образцы, используя элемент управления NumericUpDown для Windows Forms, и этот образец пользовательского элемента NumericUpDown от Microsoft . Но пока кажется, что NumericUpDown (поддерживается WPF или нет) не будет предоставлять функциональность, которая мне нужна. То, как мое приложение разработано, никто в здравом уме не захочет возиться со стрелками. Они не имеют практического смысла в контексте моей заявки.
Поэтому я ищу простой способ сделать стандартный текстовый редактор WPF принимать только те символы, которые я хочу. Это возможно? Это практично?
- KeyBinding в UserControl не работает, когда TextBox имеет фокус
- Обратный вызов, когда свойство зависимостей получает изменение xaml
- WPF MVVM Зачем использовать ContentControl + DataTemplate Views, а не прямые XAML Window Views?
- Как отключить элемент списка ListBox на основе значения свойства?
- WPF: отобразить значение bool как «Да» / «Нет»
- Применение инсульта к текстовому блоку в WPF
- Как заставить WPF использовать URI ресурсов, которые используют сильное имя сборки? Argh!
- Как применить правило пользовательской сортировки к WPF DataGrid?
- Имя не существует в ошибке пространства имен в XAML
- Имеет ли XAML условную директиву компилятора для режима отладки?
- Что делает звезда WPF (Width = "100 *")
- Почему ActualWidth и ActualHeight 0.0 в этом случае?
- Как получить ListBox ItemTemplate, чтобы растянуть по горизонтали всю ширину ListBox?
Добавить событие ввода текста предварительного просмотра. Например:
.
Затем внутри этого набора e.Handled
если текст не разрешен. e.Handled = !IsTextAllowed(e.Text);
Я использую простое регулярное выражение в методе IsTextAllowed
чтобы увидеть, разрешать ли мне то, что они набрали. В моем случае я хочу только указать числа, точки и тире.
private static readonly Regex _regex = new Regex("[^0-9.-]+"); //regex that matches disallowed text private static bool IsTextAllowed(string text) { return !_regex.IsMatch(text); }
Если вы хотите предотвратить вставку некорректных данных, подключите событие DataObject.Pasting="TextBoxPasting"
как показано здесь ( DataObject.Pasting="TextBoxPasting"
код):
// 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(); } }
Обработчик событий просматривает ввод текста. Здесь регулярное выражение соответствует текстовому вводу только в том случае, если оно не является числом, а затем оно не вводится в текстовое поле ввода.
Если вы хотите только буквы, замените регулярное выражение как [^a-zA-Z]
.
XAML
ФАЙЛ 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); }
Я использовал часть того, что уже было здесь, и наложил на него свой собственный поворот, используя поведение, поэтому мне не нужно распространять этот код на протяжении тонны 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; } } }
Вот соответствующий код вида:
Это улучшенное решение ответа WilP . Мои улучшения:
- Улучшенное поведение кнопок Del и Backspace
- Добавлено свойство
EmptyValue
, если пустая строка неуместна - Исправлены некоторые незначительные опечатки
/// /// 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] -------------------------------- }
Использование довольно просто:
Добавьте в ПРАВИЛО ВАЛИДАЦИИ, чтобы при изменении текста проверьте, является ли данные числовыми, и если это так, позволяет продолжить обработку, а если нет, запрашивает у пользователя, что в этом поле принимаются только числовые данные.
Подробнее о проверке в Windows Presentation Foundation
У расширенного набора инструментов WPF есть одно: NumericUpDown
Также можно просто реализовать правило проверки и применить его к TextBox:
С выполнением правила, как следует (с использованием того же Regex, как предложено в других ответах):
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; } }
Вот простой и простой способ сделать это с помощью MVVM.
Свяжите свой текстовый блок с целым свойством в модели представления, и это будет работать как gem … он даже покажет подтверждение, если в текстовое поле введено нецелое число.
Код XAML:
Показать код модели:
private long _contactNo; public long contactNo { get { return _contactNo; } set { if (value == _contactNo) return; _contactNo = value; OnPropertyChanged(); } }
Я допустил числовые номера клавиатуры и обратное пространство:
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); }
Я буду считать, что:
-
Ваш TextBox, для которого вы хотите разрешить только числовой ввод, имеет свойство Text, первоначально установленное на некоторое действительное числовое значение (например, 2.7172).
-
Ваше текстовое поле является дочерним элементом вашего основного windows
-
Главное окно – это class Window1
-
Имя вашего TextBox – numericTB
Основная идея:
-
Добавить:
private string previousText;
в ваш основной class windows (Window1) -
Добавить:
previousText = numericTB.Text;
к главному конструктору windows -
Создайте обработчик для события numericTB.TextChanged таким образом:
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; }
Это будет поддерживать настройку previousText на numericTB.Text до тех пор, пока он действителен, и установите numericTB.Text на его последнее допустимое значение, если пользователь пишет что-то, что вам не нравится. Конечно, это просто базовая идея, и она просто «идиотская», а не «идиотская». Например, он не обрабатывает случай, когда пользователь путается с пробелами. Итак, вот полное решение, которое я считаю «доказательством идиота», и если я ошибаюсь, скажите мне:
-
Содержимое вашего файла Window1.xaml:
-
Содержимое вашего файла 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; } } } } }
Вот и все. Если у вас много текстовых полей, я рекомендую создать CustomControl, который наследует от TextBox, поэтому вы можете обернуть предыдущийText и numericTB_TextChanged в отдельном файле.
Если вы не хотите писать много кода для выполнения основной функции (я не знаю, почему люди делают длинные методы), вы можете просто сделать это:
-
Добавить пространство имен:
using System.Text.RegularExpressions;
-
В XAML установите свойство TextChanged:
-
В WPF под методом
Regex.Replace
добавьтеRegex.Replace
:private void txt1_TextChanged(object sender, TextChangedEventArgs e) { txt1.Text = Regex.Replace(txt1.Text, "[^0-9]+", ""); }
Это единственный необходимый код:
void MyTextBox_PreviewTextInput(object sender, TextCompositionEventArgs e) { e.Handled = new Regex("[^0-9]+").IsMatch(e.Text); }
Это позволяет вводить номера только в текстовое поле.
Чтобы разрешить знак десятичной точки или минуса, вы можете изменить регулярное выражение на [^0-9.-]+
.
PreviewTextInput += (s, e) => { e.Handled = !e.Text.All(char.IsDigit); };
e.Handled = (int)e.Key >= 43 || (int)e.Key <= 34;
в событии предварительного просмотра в текстовом поле.
Мы можем выполнить проверку на событие с измененным текстовым полем. Следующая реализация предотвращает ввод ввода, отличный от числовой и одной десятичной точки.
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; }
Здесь у меня есть простое решение, вдохновленное ответом Рэя . Этого должно быть достаточно для идентификации любой формы числа.
Это решение также может быть легко изменено, если вы хотите только положительные числа, целые значения или значения с точностью до максимального количества десятичных знаков и т. Д.
Как было предложено в ответе Рэя , вам нужно сначала добавить событие PreviewTextInput
:
Затем добавьте следующее в код:
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); }
Вот библиотека для ввода числовых данных в WPF
Он имеет свойства, такие как RegexPattern
и RegexPattern
для проверки.
Подclassы WPF TextBox
Использование:
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
Я работал с несвязанным полем для простого проекта, над которым я работал, поэтому я не мог использовать стандартный подход привязки. Следовательно, я создал простой хак, который другие могут найти весьма удобным, просто расширив существующий элемент управления TextBox:
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; } } } }
Очевидно, что для плавающего типа вы хотели бы проанализировать его как float и так далее. Те же принципы применяются.
Затем в файле XAML необходимо включить соответствующее пространство имен:
После этого вы можете использовать его как обычный элемент управления:
После использования некоторых решений здесь в течение некоторого времени, я разработал свой собственный, который хорошо работает для моей установки MVVM. Обратите внимание, что это не так динамично, как некоторые из других, в том смысле, что они все еще позволяют пользователям вводить ошибочные символы, но они не позволяют им нажимать кнопку и тем самым делать что-либо. Это хорошо сочетается с моей темой серых кнопок, когда действия не могут быть выполнены.
У меня есть TextBox
что пользователь должен ввести несколько страниц документа для печати:
… с этим свойством связывания:
private string _numberPagesToPrint; public string NumberPagesToPrint { get { return _numberPagesToPrint; } set { if (_numberPagesToPrint == value) { return; } _numberPagesToPrint = value; OnPropertyChanged("NumberPagesToPrint"); } }
У меня также есть кнопка:
… с этой привязкой команды:
private RelayCommand _setNumberPagesCommand; public ICommand SetNumberPagesCommand { get { if (_setNumberPagesCommand == null) { int num; _setNumberPagesCommand = new RelayCommand(param => SetNumberOfPages(), () => Int32.TryParse(NumberPagesToPrint, out num)); } return _setNumberPagesCommand; } }
И тогда есть метод SetNumberOfPages()
, но это неважно для этой темы. Это хорошо работает в моем случае, потому что мне не нужно добавлять какой-либо код в файл с кодом для кода View, и он позволяет мне управлять поведением с использованием свойства Command
.
При проверке числового значения вы можете использовать функцию VisualBasic.IsNumeric .
В приложении WPF вы можете справиться с этим, обработав событие 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; } }
В Windows Forms это было легко; вы можете добавить событие для KeyPress, и все работает легко. Однако в WPF этого события нет. Но для него есть намного более простой способ.
WPF TextBox имеет событие TextChanged, которое является общим для всего. Он включает в себя вставку, ввод текста и все, что может придумать вам.
Таким образом, вы можете сделать что-то вроде этого:
XAML:
КОД:
private void TextBox_TextChanged(object sender, TextChangedEventArgs e) { string s = Regex.Replace(((TextBox)sender).Text, @"[^\d.]", ""); ((TextBox)sender).Text = s; }
Это также принимает .
, если вы этого не хотите, просто удалите его из оператора regex
чтобы он был @[^\d]
.
Примечание . Это событие можно использовать во многих текстовых блоках, поскольку оно использует текст объекта sender
. Вы только записываете событие один раз и можете использовать его для нескольких текстовых полей.
Теперь я знаю, что у этого вопроса есть принятый ответ , но лично я считаю это немного запутанным, и я считаю, что это должно быть проще. Поэтому я попытаюсь продемонстрировать, как я заработал как можно лучше:
В Windows Forms есть событие под названием KeyPress
которое отлично подходит для такого рода задач. Но это не существует в WPF , поэтому вместо этого мы будем использовать событие PreviewTextInput
. Кроме того, для проверки я считаю, что можно использовать foreach
для циклического прохождения через textbox.Text
и проверить, соответствует ли оно;) условию, но, честно говоря, для этого используются регулярные выражения .
Еще одна вещь, прежде чем мы погрузимся в священный код . Чтобы событие было уволено, можно сделать две вещи:
- Используйте XAML, чтобы сообщить программе, какую функцию вызывать:
- Сделайте это в событии
Loaded
формы (в котором находитсяtextBox.PreviewTextInput += onlyNumeric;
):textBox.PreviewTextInput += onlyNumeric;
Я думаю, что второй метод лучше, потому что в таких ситуациях вам в основном потребуется применить одно и то же условие ( regex
) к нескольким TextBox
и вы не хотите повторять себя! ,
Наконец, вот как вы это сделаете:
private void onlyNumeric(object sender, TextCompositionEventArgs e) { string onlyNumeric = @"^([0-9]+(.[0-9]+)?)$"; Regex regex = new Regex(onlyNumeric); e.Handled = !regex.IsMatch(e.Text); }
Для тех, кто ищет быструю и очень простую реализацию для этого типа проблем, используя только целые числа и десятичные числа, в вашем файле XAML добавьте свойство PreviewTextInput
в свой TextBox
а затем в файл xaml.cs:
private void Text_PreviewTextInput(object sender, TextCompositionEventArgs e) { e.Handled = !char.IsDigit(e.Text.Last()) && !e.Text.Last() == '.'; }
Это лишний раз, чтобы каждый раз проверять всю строку, если, как другие не упоминали, вы делаете что-то с научной нотацией (хотя, если вы добавляете определенные символы типа «e», простое добавление символов / символов регулярных выражений действительно простой и иллюстрированный в других ответах). Но для простых значений с плавающей запятой этого решения будет достаточно.
Написано как однострочный с выражением lambda:
private void Text_PreviewTextInput(object sender, TextCompositionEventArgs e) => e.Handled = !char.IsDigit(e.Text.Last() && !e.Text.Last() == '.');
Вот моя версия. Он основан на базовом classе ValidatingTextBox
который просто отменяет то, что было сделано, если оно не является «действительным». Он поддерживает вставку, вырезание, удаление, обратное пространство, +, – и т. Д.
Для 32-разрядного целого числа существует class Int32TextBox, который просто сравнивается с int. Я также добавил classы валидации с плавающей точкой.
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 classes 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:
Или
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.