Как посимвольно проверить соответствие заданной маске?

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 шт):

Автор решения: Chaos_Sower

Достойный вопрос, требующий не менее достойного ответа! Представим, что на форме есть проверяемый 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 :)

→ Ссылка
Автор решения: Wiktor Stribiżew

Для финальной валидации используйте ваше регурярное выражение.

Для проверки частичного ввода используйте

^\+(?:7(?: (?:\((?:\d{1,3}(?:\)(?: (?:\d{1,3}(?:-(?:\d{1,2}(?:-\d{0,2})?)?)?)?)?)?)?)?)?)?$

См. пример работы регулярного выражения.

→ Ссылка