TextBox WPF не имеет встроенных средств проверки ввода только чисел и в сети интернет всегда актуальна тема фильтрации ввода в текстовое поле только цифр. Просто замечательно, что модель ООП (Объектно-ориентированное программирования) дает нам возможность создания собственного класса с выводом чисел на основе существующего TextBox.
На данной странице описывается исходный код на C# одного из способов обеспечения корректности ввода цифр в элемент отображения и редактирования текста. Исходник оформлен в виде класса TextBoxNumeric производного от TextBox. В текстовое поле элемента TextBoxNumeric можно вводить только цифры и знаки пунктуации, формируя соответственно целые и дробные числа.
Класс TextBoxNumeric имеет простой и небольшой по величине код, работающий достаточно эффективно. Логика TextBoxNumeric прекрасно справляется с посимвольным вводом и вставкой текста из буфера обмена. Поведение нового текстового поля формируют события изменения свойства зависимости TextProperty и потери фокуса LostFocus, в остальном TextBoxNumeric наследует все стандартные свойства TextBox.
Непосредственно проверка и коррекция ввода символов происходит в переопределенном обработчике события изменения свойств зависимостей TextBox.OnPropertyChanged(DependencyPropertyChangedEventArgs e). Восстановление значения по умолчанию происходит в переопределенном обработчике события потери фокуса TextBox. OnLostFocus(RoutedEventArgs e)
TextBoxNumeric имеет настроечные открытые свойства для определения их в программном коде и разметке XAML. Эти свойства подробно описываются в комментариях листинга структуры класса.
Структура программного кода класса TextBoxNumeric (методы описываются ниже):
public class TextBoxNumeric : TextBox
{
#region Открытые свойства класса
// Свойство хранит значение по умолчанию текста элемента
private string _defaultValue = "0";
public string DefaultValue
{
get { return _defaultValue; }
set { _defaultValue = value; }
}
// Открытое свойство указывающее восстанавливать ли последнее значение
// в случае полного удаления текста элемента.
// Восстановление происходит при потере фокуса.
private bool _restoreDefaultValue = false;
public bool RestoreDefaultValue
{
get { return _restoreDefaultValue; }
set { _restoreDefaultValue = value; }
}
// Вид десятичного разделителя
char _decimalSeparator = '.';
public char DecimalSeparator
{
get { return _decimalSeparator; }
set { _decimalSeparator = value; }
}
// Вид текстового поля для ввода целых чисел,
// или чисел с плавающей запятой.
private bool _inputDouble = false;
public bool InputDouble
{
get { return _inputDouble; }
set { _inputDouble = value; }
}
#endregion
#region События, формирующие функциональность числового текстового поля
// Уведомление об изменениях свойств зависимости элемента TextBox.
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
. . .
}
protected override void OnLostFocus(RoutedEventArgs e)
{
. . .
}
#endregion
#region Фильтры ввода текста
// Фильтр формирования корректного целого числа.
private string FilterInteger(string inputText)
{
. . .
}
// Фильтр формирования корректного числа с дробной частью
private string FilterFloat(string inputText)
{
. . .
}
#endregion
}
Метод OnPropertyChanged вызывается при изменении любого свойства элемента TextBox. Отследить изменение только интересующего свойства зависимостей можно получая идентификатор свойства из аргументов, например: if (e.Property == TextBox.TextProperty) {}.
В нашем случае нас интересует изменение свойства зависимости TextBox.TextProperty. В OnPropertyChanged изменение TextProperty отслеживается раньше, чем вызывается событие изменения введенного текста TextBox.TextChanged. Поэтому в теле метода OnPropertyChanged рационально разместить код фильтра ввода только цифр и свойство TextBox.Text будет получать готовую «оцифрованную» строку, без недопустимых символов.
Программный код метода TextBoxNumeric. OnPropertyChanged:
// Уведомление об изменениях свойств зависимости элемента TextBox.
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
// Обязательный вызов базовой реализации.
// Всегда вызывайте базовую реализацию первой операции.
// Невыполнение этого требования приведет к значительному
// отключению всей системы свойств WPF,
// что приведет к сообщению о неверных значениях.
base.OnPropertyChanged(e);
// Включаем работу кода только в контексте свойста Text элемента TextBox.
if (e.Property == TextBox.TextProperty)
{
// Сопоставление шаблона с объявление новой локальной переменной:
// проверка новое значение должно быть только типом string,
// но не null или еще что-то.
if (e.NewValue is string newv == true)
{
// Если текстовое поле не пусто и есть изменения.
if (Text != "" && newv.Equals(e.OldValue) == false)
{
// Выбор фильтра для коррекции ввода символов
if (_inputDouble == false) Text = FilterInteger(newv);
if (_inputDouble == true) Text = FilterFloat(newv);
}
}
}
}
Для некоторых приложений может понадобиться значение умолчанию, например, в случаях недопустимости пустой строки в TextBox, когда пользователь очистил строковое содержимое TextBox и перевел фокус на другой элемент. Очень возможно, что при запуске определённого действия в приложении может выброситься исключение, поскольку программный код ожидает число, но получает пустую строку.
В таких случаях и выручит значение по умолчанию. И ситуация уже будет выглядеть так: пользователь очистил текстовое поле, перевел фокус на другой элемент, а в текстовом поле восстановилось заранее определенное значение.
Для случаев, когда недопустимо пустое текстовое поле и наоборот, когда приложению требуется очистка TextBox в классе предусмотрена пара открытых свойств RestoreDefaultValue и DefaultValue (см. код структуры класса TextBoxNumeric выше).
При переводе фокуса на другой визуальный элемент приложения в TextBox вызывается событие потери фокуса TextBox.LostFocus. Возврат значения по умолчанию организован в переопределённом обработчике события TextBoxNumeric.OnLostFocus. Данный программный код минимального объема и производит проверку разрешения возврата значения по умолчанию в пустой элемент TextBox.
Программный код обработчика события потери фокуса элементом TextBox:
protected override void OnLostFocus(RoutedEventArgs e)
{
// Обязательный вызов базовой реализации.
base.OnLostFocus(e);
// Если разрешен вывод значения по умолчанию в пустое текстовое поле.
if (Text == "" && RestoreDefaultValue == true)
{
// Теперь элемент TextBox содержит корректное числовое значение.
Text = _defaultValue;
}
}
Методы FilterInteger(string inputText) и FilterFloat(string inputText) предназначены для удаления нецифровых (нечисловых) символов в текстовое поле TextBoxNumeric. После воздействия данных фильтров на вводимую пользователем строку формируются правильные текстовые целые числа и числа с дробной частью.
Фильтры построены на примитивной, но прекрасно работающей схеме посимвольной проверки строки и удаления первого нуля перед числом. Посимвольная проверка корректирует текст налету и не удаляет всю строку целиком из-за «неправильного» символа. Для экономии аппаратных ресурсов и повышения производительности строки формируются на основе экономичного класса StringBuilder.
Для обратной связи с пользователем при вводе «неправильного» символа в методах корректировки строк генерируется звук «недовольства» функцией Console.Beep(300, 100).
Программный код методов корректировки ввода числе в TextBoxNumeric:
// Фильтр формирования корректного целого числа.
private string FilterInteger(string inputText)
{
StringBuilder res = new();
bool onlyoneBeep = true;
for (int i = 0; i < inputText.Length; i++)
{
// Фильтр ввода чисел, пропускает только числа (цифры).
if (Char.IsDigit(inputText[i]) == true)
{
// Добавляем символ в строку результата.
res.Append(inputText[i]);
}
else
{
// Недовольство звуком только один раз на всю новую строку или символ.
if (onlyoneBeep == true)
{
onlyoneBeep = false;
Console.Beep(300, 100);
}
}
}
// Удаление первого нуля, если количество цифр больше 1.
if (res.Length > 1)
{
if (res[0] == '0')
{
res.Remove(0, 1);
}
}
return res.ToString();
}
// Фильтр формирования корректного числа с дробной частью
private string FilterFloat(string inputText)
{
StringBuilder res = new();
bool onlyoneBeep = true;
bool onlyonePunctuation = true;
for (int i = 0; i < inputText.Length; i++)
{
// Фильтр ввода чисел, пропускает только числа (цифры).
if (Char.IsDigit(inputText[i]) == true)
{
// Добавляем символ в строку результата.
res.Append(inputText[i]);
}
else if (onlyonePunctuation == true &&
Char.IsPunctuation(inputText[i]) == true)
{
// Добавляем символ в строку результата только один раз
onlyonePunctuation = false;
// Все виды разделителей заменяем на установленный.
res.Append(_decimalSeparator);
}
else
{
// Недовольство звуком только один раз на всю новую строку или символ.
if (onlyoneBeep == true)
{
onlyoneBeep = false;
Console.Beep(300, 100);
}
}
}
// Удаление первого нуля, если за ним не следует разделитель.
if (res.Length > 1)
{
if (res[0] == '0' && res[1] != _decimalSeparator)
{
res.Remove(0, 1);
}
}
return res.ToString();
}
Достоинства работы кода TextBoxNumeric: перед событием изменения текста формируется правильно оцифрованная строка.
Недостатки: при каждом вводе проверяется весь текст заново.
На практическом уровне логика отбора цифр из вводимого в TextBoxNumeric текста работает очень быстро и безотказно, при посимвольном вводе и вставки строки из буфера обмена.
Исходный код класса TextBoxNumeric предоставляется как есть, не претендуя на идеальность, его можно изменять в соответствии со своими нуждами. TextBoxNumeric идет в составе приложения WPF для быстрого тестирования кода. Все что не описано на странице можно получить при тестировании приложения с текстовым полем для ввода чисел TextBoxNumeric. Исходник написан на языке C# WPF в MS Visual Studio 2022, платформа .NET6.