- PVSM.RU - https://www.pvsm.ru -

Валидация форм в декларативном стиле (C#)

Я думаю, что многие из вас сталкивались с задачей валидации данных в формах. Это долго, утомительно и часто требует значительных усилий. Порой мне кажется, что ребята из Редмонта издеваются над нами, предлагая проводить валидацию так [1]. Шучу, конечно, но этот метод мы использовать не будем.

Мы будем производить валидацию данных формы в автоматическом режиме, при помощи правил, описанных в декларативном стиле. Возьмем простейшую форму:

Валидация форм в декларативном стиле (C#)

Здесь есть несколько обычных текстовых полей, поле ввода числа и поле для e-mail'a. Зададим следующие правила для нашей формы:

Поля Фамилия, Имя и Отчество должны быть заполнены как минимум одним печатным (не whitespace) символом:

txtSurname
    .ValidateControl()
    .IsNotNullOrWhitespace();

txtName
    .ValidateControl()
    .IsNotNullOrWhitespace();

txtMiddleName
    .ValidateControl()
    .IsNotNullOrWhitespace();

Возраст должен быть не менее 16 лет. Если указанный возраст менее 21 года – необходимо вывести предупреждение, но разрешить сохранить форму:

nmAge
    .ValidateControl()
    .IsTrue(ctl => ctl.Value >= 16, "Возраст должен быть не менее 16 лет.", ValidationType.Required)
    .IsTrue(ctl => ctl.Value >= 21, "Некоторый контент (21+) для вас будет недоступен.", ValidationType.Optional);

Поле e-mail должно быть заполнено корректным значением (или по крайней мере похожим на e-mail):

txtEMail
    .ValidateControl()
    .IsValidEMail(false);

Если все поля заполнены правильно – разрешить нажатие кнопки «Сохранить», иначе – нет:

butSave
    .ValidateControl()
    .EnableByValidationResult();
Код класса формы одной простыней

public partial class frmMain : Form
    {
        public frmMain()
        {
            InitializeComponent();

            // проверим, что текстовые поля заполнены 
            txtSurname
                .ValidateControl()
                .IsNotNullOrWhitespace();

            txtName
                .ValidateControl()
                .IsNotNullOrWhitespace();

            txtMiddleName
                .ValidateControl()
                .IsNotNullOrWhitespace();

            // зададим жесткое ограничение в 16 лет
            // и не жесткое ограничение (предупреждение не препятствующее вводу формы) в 21 год
            nmAge
                .ValidateControl()
                .IsTrue(ctl => ctl.Value >= 16, "Возраст должен быть не менее 16 лет.", ValidationType.Required)
                .IsTrue(ctl => ctl.Value >= 21, "Некоторый контент (21+) для вас будет недоступен.", ValidationType.Optional);

            // включим проверку на ввод корректного e-mail'а в этом поле
            txtEMail
                .ValidateControl()
                .IsValidEMail(false);

            // по результатам валидации будем разрешать/запрещать указанную кнопку
            butSave
                .ValidateControl()
                .EnableByValidationResult();
        }
    }

Что мы получим при запуске формы? Во первых: мы получим удобное подсвечивание каждого поля, значение которого заполнено некорректными данными. Во вторых: если подвести курсор мыши к индикатору мы увидим что именно хочет от нас форма:

Валидация форм в декларативном стиле (C#)

В третьих: кнопка «Сохранить» будет доступна к нажатию только после успешной валидации данных.

Хорошо, с простой формой мы разобрались, а как быть с формой посложнее? Усложним задачу. Будем делать импровизированную форму поиска статей для Хабра:

Валидация форм в декларативном стиле (C#)

Мы имеем три фильтра, при выборе которых должны активироваться проверки заданных условий для каждого фильтра. Плюс активация контролов, которые относятся к выбранному фильтру. Хотелось бы отметить, что управление состоянием контролов не совсем относится к валидации данных, но в данном случае мы не будем заострять на этом внимание.

Активацию контролов будем делать так:

// список чекбоксов с категориями
var categoryCheckBoxes = pnlCategories.Controls.Cast<CheckBox>();

// управление состоянием контролов в зависимости от включенных чекбоксов
dtBegin.EnableByTimer(() => chkFilterByDate.Checked);
dtEnd.EnableByTimer(() => chkFilterByDate.Checked);
pnlCategories.EnableByTimer(() => chkFilterByCategory.Checked);
pnlTextFilter.EnableByTimer(() => chkFilterByText.Checked);

Теперь правила валидации. Если фильтр по дате включен, то начальная дата должна быть меньше либо равна конечной. Начальная дата не может быть раньше 1990 года. Валидация будет происходить в обоих DatePicker'ах, но индикация будет отображаться только на dtEnd:

dtEnd
    .ValidateControl()
    .IsTrue(ctl => !chkFilterByDate.Checked || dtBegin.Value >= new DateTime(1990, 1, 1), 
            "Начальная дата отбора не может быть раньше 1990 года")
    .IsTrue(ctl => !chkFilterByDate.Checked || dtBegin.Value <= dtEnd.Value, 
            "Начальная дата должна быть меньше или равной конечной");

Если осуществляется фильтрация по категориям, то необходимо выбрать как минимум одну категорию:

pnlCategories
    .ValidateControl()
    .IsTrue(ctl => !chkFilterByCategory.Checked || categoryCheckBoxes.Any(c => c.Checked), 
            "Необходимо выбрать категорию");

Если осуществляется поиск текста, то необходимо задать текст и выбрать где его искать:

pnlTextFilter
    .ValidateControl()
    .IsTrue(ctl => !chkFilterByText.Checked || chkSearchTextInBody.Checked || chkSearchTextInHeader.Checked, 
            "Необходимо выбрать места поиска текста")
    .IsTrue(ctl => !chkFilterByText.Checked || !string.IsNullOrWhiteSpace(txtSearchText.Text), 
            "Необходимо задать текст");

Также, для успешной валидации формы, должен быть задан хотя бы один из фильтров для поиска:

gbSearchParameters
    .ValidateControl()
    .IsTrue(ctl => chkFilterByCategory.Checked || chkFilterByDate.Checked || chkFilterByText.Checked,
            "Необходимо задать условия поиска.");

Ну и по результатам валидации формы активируем главную действующую кнопку:

butSearch
    .ValidateControl()
    .EnableByValidationResult();
Полный код второй формы

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Teleavtomatika.Forms;

namespace Teleavtomatika_Form_Validation
{
    public partial class frmMain2 : Form
    {
        public frmMain2()
        {
            InitializeComponent();

            // список чекбоксов с категориями
            var categoryCheckBoxes = pnlCategories.Controls.Cast<CheckBox>();

            // управление состоянием контролов в зависимости от включенных чекбоксов
            dtBegin.EnableByTimer(() => chkFilterByDate.Checked);
            dtEnd.EnableByTimer(() => chkFilterByDate.Checked);
            pnlCategories.EnableByTimer(() => chkFilterByCategory.Checked);
            pnlTextFilter.EnableByTimer(() => chkFilterByText.Checked);

            // теперь правила валидации:
            // если фильтр по дате включен, то начальная дата должна быть меньше либо равна конечной
            // начальная дата не может быть раньше 1990 года
            // валидация будет происходить в обоих DatePicker'ах, но индикация будет отображаться только на dtEnd
            dtEnd
                .ValidateControl()
                .IsTrue(ctl => !chkFilterByDate.Checked || dtBegin.Value >= new DateTime(1990, 1, 1),
                        "Начальная дата отбора не может быть раньше 1990 года")
                .IsTrue(ctl => !chkFilterByDate.Checked || dtBegin.Value <= dtEnd.Value,
                        "Начальная дата должна быть меньше или равной конечной");

            // если осуществляется фильтрация по категориям
            // то необходимо выбрать как минимум одну категорию
            pnlCategories
                .ValidateControl()
                .IsTrue(ctl => !chkFilterByCategory.Checked || categoryCheckBoxes.Any(c => c.Checked),
                        "Необходимо выбрать категорию");

            // если осуществляется поиск текста
            // то необходимо задать текст
            // и выбрать где его искать
            pnlTextFilter
                .ValidateControl()
                .IsTrue(ctl => !chkFilterByText.Checked || chkSearchTextInBody.Checked || chkSearchTextInHeader.Checked,
                        "Необходимо выбрать места поиска текста")
                .IsTrue(ctl => !chkFilterByText.Checked || !string.IsNullOrWhiteSpace(txtSearchText.Text),
                        "Необходимо задать текст");

            // должен быть задан хотя-бы один из фильтров для поиска
            gbSearchParameters
                .ValidateControl()
                .IsTrue(ctl => chkFilterByCategory.Checked || chkFilterByDate.Checked || chkFilterByText.Checked,
                        "Необходимо задать условия поиска.");

            // Ну и по результатам валидации формы активируем главную действующую кнопку "Найти":
            butSearch
                .ValidateControl()
                .EnableByValidationResult();
        }
    }
}

Посмотреть как это работает в живую можно на видео:

Исходники тут. [2]

Автор: teleavtomatika

Источник [3]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/interfejsy/53234

Ссылки в тексте:

[1] так: http://msdn.microsoft.com/en-us/library/ms229603(v=vs.110).aspx

[2] тут.: https://github.com/teleavtomatika/Teleavtomatika_Form_Validation

[3] Источник: http://habrahabr.ru/post/209820/