Как посимвольно проверить соответствие заданной маске?
Regex
— мощный инструмент: он позволяет проверить соответствие строки заданной маске.
К примеру, если дан следующий Regex
:
/// <summary>
/// <see cref="Regex"/>, которому соответствует только формат "+7 (XXX) XXX-XX-XX"
/// </summary>
/// <returns></returns>
[GeneratedRegex(@"^\+7 \(\d{3}\) \d{3}-\d{2}-\d{2}$")]
private static partial Regex PhoneNumberOnlyRegex();
И следующий метод:
/// <summary>
/// Метод, проверяющий соответствие текста заданному <see cref="Regex"/>
/// </summary>
/// <param name="Text">Проверяемый текст</param>
/// <param name="Regex">Проверяющий <see cref="Regex"/></param>
/// <returns></returns>
private static bool IsTextAllowed(string Text, Regex Regex)
{
return Regex.IsMatch(Text);
}
То мы можем его использовать следующим образом:
if (!IsTextAllowed(PlaceholderTextBox.Text, FullNameOnlyRegex()))
{
PlaceholderTextBox.BorderBrush = Brushes.Red;
}
Но он остаётся эффективным до тех пор, пока нам не придётся проверять не всю строку, а правильность вводимой последовательности. Вот уже тогда возникают проблемы.
К примеру:
Строка + 7 (000) 000-00-00
пройдёт по Regex
'у, но как проверять правильность, когда пользователь только начинает вводить номер телефона?
Вот он только наберёт +
и Regex
уже будет ругаться.
В каких-то случаях это может показаться незначительной проблемой, но что делать, если разработчику нужно контролировать ввод каждого символа пользователя через UI, не давая пользователю ввести неверный символ? Например, через события PreviewTextInput
и Pasting
у элемента TextBox
?
Из события PreviewTextInput
, кстати, вытекает ещё одна проблема: а как обрабатывать пробелы, вводимые пользователем, ведь это событие не срабатывает на них (оно реагирует только на вводимые символы)?
Ответы (2 шт):
Достойный вопрос, требующий не менее достойного ответа! Представим, что на форме есть проверяемый PhoneNumberTextBox
.
Тогда для требуемого функционала можно воспользоваться следующим методом:
private const string PhoneNumberMask = "+7 (XXX) XXX-XX-XX";
/// <summary>
/// Метод проверки валидности телефонного номера
/// </summary>
/// <param name="InputText"></param>
/// <returns></returns>
public static bool IsValidPhoneNumber(string InputText)
{
if (InputText.Length > PhoneNumberMask.Length)
{
return false;
}
for (int i = 0; i < InputText.Length; i++)
{
if (PhoneNumberMask[i] == 'X')
{
if (!char.IsDigit(InputText[i]))
{
return false;
}
}
else
{
if (InputText[i] != PhoneNumberMask[i])
{
return false;
}
}
}
return true;
}
Используем наш метод IsValidPhoneNumber
тут:
PhoneNumberTextBox.PreviewTextInput += (sender, e) =>
{
if (sender is TextBox TextBox)
{
if (!IsValidPhoneNumber(TextBox.Text + e.Text))
{
e.Handled = true;
}
}
};
И тут:
/// <summary>
/// Событие, срабатывающее перед вставкой символа/текста в <see href="PhoneNumberTextBox"/>
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void PhoneNumberTextBox_Pasting(object sender, DataObjectPastingEventArgs e)
{
if (e.DataObject.GetDataPresent(typeof(string)))
{
string Text = string.Empty;
if (e.DataObject.GetData(typeof(string)) is string String)
{
Text = String;
}
if (sender is TextBox TextBox)
{
if (!IsValidPhoneNumber(TextBox.Text + Text))
{
e.CancelCommand();
}
}
}
else
{
e.CancelCommand();
}
}
А для отслеживания пробелов — и тут:
PhoneNumberTextBox.PreviewKeyDown += (sender, e) =>
{
if (e.Key == Key.Space)
{
if (sender is TextBox TextBox)
{
if (!IsValidPhoneNumber(TextBox.Text + " "))
{
e.Handled = true;
}
}
}
};
Если нам также понадобиться проверить верность написанной фамилии/имени/отчества для, к примеру, NameTextBox
, то тогда можно сделать так:
Для проверки имени целиком (например, перед отправкой в БД):
/// <summary>
/// <see cref="Regex"/>, которому соответствует только формат кириллицы с первой заглавной буквой и остальными строчными буквами
/// </summary>
/// <returns></returns>
[GeneratedRegex("^[А-ЯЁ][а-яё]+$")]
private static partial Regex FullNameOnlyRegex();
Для проверки введённого пользователем символа:
/// <summary>
/// <see cref="Regex"/>, которому соответствует только формат кириллической заглавной или строчной буквы
/// </summary>
/// <returns></returns>
[GeneratedRegex("^[А-ЯЁа-яё]$")]
private static partial Regex CyrillicCharOnlyRegex();
Расширим наш метод путём перегрузки:
/// <summary>
/// Метод, проверяющий соответствие символа заданному <see cref="Regex"/>
/// </summary>
/// <param name="Char">Проверяемый символ</param>
/// <param name="Regex">Проверяющий <see cref="Regex"/></param>
/// <returns></returns>
private static bool IsTextAllowed(char Char, Regex Regex)
{
return Regex.IsMatch(Char.ToString());
}
А сам метод проверки имени (а также фамилии и отчества) будет таким:
/// <summary>
/// Метод проверки валидности ФИО
/// </summary>
/// <param name="InputText"></param>
/// <returns></returns>
public static bool IsValidFullName(string InputText)
{
if (string.IsNullOrEmpty(InputText))
{
return false;
}
// Проверка первой буквы
if (!char.IsUpper(InputText[0]) || !IsTextAllowed(InputText[0], CyrillicCharOnlyRegex()))
{
return false;
}
for (int i = 1; i < InputText.Length; i++)
{
if (!char.IsLower(InputText[i]) || !IsTextAllowed(InputText[i], CyrillicCharOnlyRegex()))
{
return false;
}
}
return true;
}
Тогда перед сохранением/отправкой данных в БД мы можем уже проверить правильность строки целиком по Regex
:
/// <summary>
/// Событие нажатия на кнопку "Сохранить"
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void SaveChangesButton_Click(object sender, RoutedEventArgs e)
{
if (!IsTextAllowed(PhoneNumberTextBox.Text, PhoneNumberOnlyRegex()))
{
PhoneNumberTextBox.BorderBrush = Brushes.Red;
return;
}
if (!IsTextAllowed(NameTextBox.Text, FullNameOnlyRegex()))
{
NameTextBox.BorderBrush = Brushes.Red;
return;
}
}
Как видите, ничего не мешает тому, чтобы совмещать эти два подхода: как посимвольную проверку, так и Regex
:)
Для финальной валидации используйте ваше регурярное выражение.
Для проверки частичного ввода используйте
^\+(?:7(?: (?:\((?:\d{1,3}(?:\)(?: (?:\d{1,3}(?:-(?:\d{1,2}(?:-\d{0,2})?)?)?)?)?)?)?)?)?)?$