Проверка WinForm UI - PullRequest
       29

Проверка WinForm UI

42 голосов
/ 20 апреля 2009

Мне нужно реализовать проверку ввода во всем приложении winform. Существует много различных форм, в которые можно вводить данные, и я бы не хотел контролировать элемент управления посредством формы и создавать isValid и т. Д. Для каждого элемента. Как другие справились с этим?

Я вижу, что большинство связанных публикаций посвящено веб-приложениям и / или упоминается Блок приложения для проверки библиотеки предприятия . Теперь я признаю, что я не исследовал ELVAB, но он кажется излишним для того, что мне нужно. Моя текущая мысль - написать библиотеку классов с различными требованиями и передать ей элемент управления в качестве параметра. У меня уже есть библиотека функций RegEx для таких вещей, как isValidZipCode и так далее, поэтому я могу начать с нее.

Я хотел бы иметь кнопку Validate, которая onClick циклически перебирает все элементы управления на этой странице формы и выполняет необходимую проверку. Как мне это сделать?

Ответы [ 11 ]

62 голосов
/ 20 апреля 2009

Проверка уже встроена в библиотеку WinForms.

Каждый Control -приведенный объект имеет два события с именами Validating и Validated. Также у него есть свойство под названием CausesValidation. Если для этого параметра установлено значение true (по умолчанию оно равно true), элемент управления участвует в проверке. В противном случае это не так.

Проверка происходит как часть фокуса. Когда вы отключаете элемент управления, его события проверки запускаются. На самом деле фокус событий происходит в определенном порядке. От MSDN :

При изменении фокуса с помощью клавиатура (TAB, SHIFT + TAB и т. д.), позвонив в Select или Методы SelectNextControl или установка ContainerControl .. ::. ActiveControl свойство текущей формы, фокус события происходят в следующем порядке:

  1. Введите
  2. GotFocus
  3. Leave
  4. Пользователи
  5. Утвержденные
  6. LostFocus

При изменении фокуса с помощью мыши или вызывая метод Focus, события фокуса происходят в следующем заказ:

  1. Введите
  2. GotFocus
  3. LostFocus
  4. Leave
  5. Пользователи
  6. Подтверждено

Если свойство CausesValidation имеет значение установить в false, проверка и Подтвержденные события подавляются.

Если свойство Cancel объекта Для свойства CancelEventArgs установлено значение true в Утверждение делегата события, все события что обычно происходит после Подтверждающее событие подавлено.

Также в ContainerControl есть метод, называемый ValidateChildren(), который будет циклически проходить по элементам управления и проверять их.

42 голосов
/ 25 сентября 2010

Я понимаю, что эта ветка довольно старая, но я решил опубликовать решение, которое придумал.

Самая большая проблема с проверкой на WinForms заключается в том, что проверка выполняется только тогда, когда элемент управления «потерял фокус». Таким образом, пользователь должен щелкнуть внутри текстового поля, а затем щелкнуть где-нибудь еще, чтобы выполнить процедуру проверки. Это нормально, если вас беспокоит только правильность введенных данных. Но это не работает, если вы пытаетесь убедиться, что пользователь не оставил текстовое поле пустым, пропустив его.

В моем решении, когда пользователь нажимает кнопку отправки для формы, я проверяю каждый элемент управления в форме (или любой другой указанный контейнер) и использую отражение, чтобы определить, определен ли метод проверки для элемента управления. Если это так, метод проверки выполняется. Если какая-либо из проверок завершается неудачно, процедура возвращает ошибку и позволяет остановить процесс. Это решение хорошо работает, особенно если у вас есть несколько форм для проверки.

1) Просто скопируйте и вставьте этот фрагмент кода в ваш проект. Мы используем Reflection, поэтому вам нужно добавить System.Reflection в ваши операторы использования

class Validation
{
    public static bool hasValidationErrors(System.Windows.Forms.Control.ControlCollection controls)
    {
        bool hasError = false;

        // Now we need to loop through the controls and deterime if any of them have errors
        foreach (Control control in controls)
        {
            // check the control and see what it returns
            bool validControl = IsValid(control);
            // If it's not valid then set the flag and keep going.  We want to get through all
            // the validators so they will display on the screen if errorProviders were used.
            if (!validControl)
                hasError = true;

            // If its a container control then it may have children that need to be checked
            if (control.HasChildren)
            {
                if (hasValidationErrors(control.Controls))
                    hasError = true;
            }
        }
        return hasError;
    }

    // Here, let's determine if the control has a validating method attached to it
    // and if it does, let's execute it and return the result
    private static bool IsValid(object eventSource)
    {
        string name = "EventValidating";

        Type targetType = eventSource.GetType();

        do
        {
            FieldInfo[] fields = targetType.GetFields(
                 BindingFlags.Static |
                 BindingFlags.Instance |
                 BindingFlags.NonPublic);

            foreach (FieldInfo field in fields)
            {
                if (field.Name == name)
                {
                    EventHandlerList eventHandlers = ((EventHandlerList)(eventSource.GetType().GetProperty("Events",
                        (BindingFlags.FlattenHierarchy |
                        (BindingFlags.NonPublic | BindingFlags.Instance))).GetValue(eventSource, null)));

                    Delegate d = eventHandlers[field.GetValue(eventSource)];

                    if ((!(d == null)))
                    {
                        Delegate[] subscribers = d.GetInvocationList();

                        // ok we found the validation event,  let's get the event method and call it
                        foreach (Delegate d1 in subscribers)
                        {
                            // create the parameters
                            object sender = eventSource;
                            CancelEventArgs eventArgs = new CancelEventArgs();
                            eventArgs.Cancel = false;
                            object[] parameters = new object[2];
                            parameters[0] = sender;
                            parameters[1] = eventArgs;
                            // call the method
                            d1.DynamicInvoke(parameters);
                            // if the validation failed we need to return that failure
                            if (eventArgs.Cancel)
                                return false;
                            else
                                return true;
                        }
                    }
                }
            }

            targetType = targetType.BaseType;

        } while (targetType != null);

        return true;
    }

}

2) Используйте стандартное событие Validating для любого элемента управления, который вы хотите проверить. Обязательно используйте e.Cancel при сбое проверки!

private void txtLastName_Validating(object sender, CancelEventArgs e)
    {
        if (txtLastName.Text.Trim() == String.Empty)
        {
            errorProvider1.SetError(txtLastName, "Last Name is Required");
            e.Cancel = true;
        }
        else
            errorProvider1.SetError(txtLastName, "");
    }

3) Не пропустите этот шаг! Установите для свойства AutoValidate в форме значение EnableAllowFocusChange . Это позволит перейти на другой элемент управления, даже если проверка не пройдена.

4) Наконец, в вашем методе Submit Button вызовите метод Validation и укажите, какой контейнер вы хотите проверить. Это может быть вся форма или просто контейнер в форме, такой как Panel или Группа.

private void btnSubmit_Click(object sender, EventArgs e)
    {
        // the controls collection can be the whole form or just a panel or group
        if (Validation.hasValidationErrors(frmMain.Controls))
            return;

        // if we get here the validation passed
        this.close();
    }

Счастливого кодирования!

8 голосов
/ 20 апреля 2009

В моем собственном приложении мне нужно проверить размеры по мере их ввода. Я использовал следующую последовательность:

  1. Пользователь выбирает или печатает, а затем перемещается подальше от контроля.
  2. Элемент управления теряет фокус и уведомляет представление отправляет его идентификатор и текст записи.
  3. Вид проверяет, какая программа Shape (класс, реализующий интерфейс) создал форму и передает ее Идентификатор и текст записи
  4. Программа Shape возвращает ответ.
  5. Если с ответом все в порядке, просмотр обновляет правильную запись формы Класс.
  6. Если ответ в порядке, представление говорит Форма через интерфейс, что это в порядке, чтобы переместить фокус на следующую запись.
  7. Если ответ не в порядке, просмотр смотрит на ответ и используя Интерфейс формы сообщает форме, что сделать. Это обычно означает фокус переходит к оскорбительной записи с сообщением, отображающим Пользователь, что случилось.

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

View позаботится об этом, сообщив Форме, что делать через Интерфейс. Как это на самом деле реализовано, обрабатывается самой формой в реализации интерфейса. Представлению не важно, отображается ли форма желтым для предупреждения и красным для ошибки. Только то, что он обрабатывает эти два уровня. Позже, если придет лучшая идея отображения предупреждений и ошибок, я могу сделать изменения в самой форме, скорее применяя логику представления или проверку в программе Shape Program.

Вы уже на полпути, если вы планируете создать класс, который будет содержать логику валидации, и вы получите оставшуюся часть вашего нового дизайна.

4 голосов
/ 20 апреля 2009

Я бы не хотел, чтобы контроль проходил через форму и создавал isValid и т. Д. Для каждого элемента.

Как некоторый уровень, вам нужно будет определить, что значит быть valid для каждого элемента управления, если только вас не волнует, что элемент управления имеет какое-то значение.

Тем не менее, есть Компонент ErrorProvider , который вы можете использовать, который работает довольно хорошо.

3 голосов
/ 20 апреля 2009

Нам повезло с Noogen ValidationProvider . Это просто для простых случаев (проверки типов данных и обязательные поля) и легко добавить пользовательскую проверку для более сложных случаев.

2 голосов
/ 10 марта 2012

Если вы объедините вышеизложенные идеи с этим универсальным обработчиком событий Validating, вы получите хорошую «структуру» ошибок проверки со всеми методами проверки в ваших бизнес-классах. Я просто расширил код Брюса с датской идеей. Это было сделано для компонентов Entity Framework и Dev Express, но эти зависимости могут быть легко удалены. Наслаждайтесь!

public class ValidationManager
{
    /// <summary>
    /// Call this method to validate all controls of the given control list 
    /// Validating event will be called on each one
    /// </summary>
    /// <param name="controls"></param>
    /// <returns></returns>
    public static bool HasValidationErrors(System.Windows.Forms.Control.ControlCollection controls)
    {
        bool hasError = false;

        // Now we need to loop through the controls and deterime if any of them have errors
        foreach (Control control in controls)
        {
            // check the control and see what it returns
            bool validControl = IsValid(control);
            // If it's not valid then set the flag and keep going.  We want to get through all
            // the validators so they will display on the screen if errorProviders were used.
            if (!validControl)
                hasError = true;

            // If its a container control then it may have children that need to be checked
            if (control.HasChildren)
            {
                if (HasValidationErrors(control.Controls))
                    hasError = true;
            }
        }
        return hasError;
    }

    /// <summary>
    /// Attach all youe Validating events to this event handler (if the controls requieres validation)
    /// A method with name Validate + PropertyName will be searched on the binded business entity, and if found called
    /// Throw an exception with the desired message if a validation error is detected in your method logic
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    public static void ValidationHandler(object sender, CancelEventArgs e)
    {
        BaseEdit control = sender as BaseEdit;

        if (control.DataBindings.Count > 0) //control is binded
        {
            string bindedFieldName = control.DataBindings[0].BindingMemberInfo.BindingField;

            object bindedObject = control.BindingManager.Current;

            if (bindedObject != null) //control is binded to an object instance
            {
                //find and call method with name = Validate + PropertyName

                MethodInfo validationMethod = (from method in bindedObject.GetType().GetMethods()
                                               where method.IsPublic &&
                                                     method.Name == String.Format("Validate{0}",bindedFieldName) &&
                                                     method.GetParameters().Count() == 0
                                               select method).FirstOrDefault();

                if (validationMethod != null) //has validation method
                {
                    try
                    {
                        validationMethod.Invoke(bindedObject, null);

                        control.ErrorText = String.Empty; //property value is valid
                    }
                    catch (Exception exp)
                    {
                        control.ErrorText = exp.InnerException.Message;
                        e.Cancel = true;
                    }
                }
            }
        }
    }

    // Here, let's determine if the control has a validating method attached to it
    // and if it does, let's execute it and return the result
    private static bool IsValid(object eventSource)
    {
        string name = "EventValidating";

        Type targetType = eventSource.GetType();

        do
        {
            FieldInfo[] fields = targetType.GetFields(
                 BindingFlags.Static |
                 BindingFlags.Instance |
                 BindingFlags.NonPublic);

            foreach (FieldInfo field in fields)
            {
                if (field.Name == name)
                {
                    EventHandlerList eventHandlers = ((EventHandlerList)(eventSource.GetType().GetProperty("Events",
                        (BindingFlags.FlattenHierarchy |
                        (BindingFlags.NonPublic | BindingFlags.Instance))).GetValue(eventSource, null)));

                    Delegate d = eventHandlers[field.GetValue(eventSource)];

                    if ((!(d == null)))
                    {
                        Delegate[] subscribers = d.GetInvocationList();

                        // ok we found the validation event,  let's get the event method and call it
                        foreach (Delegate d1 in subscribers)
                        {
                            // create the parameters
                            object sender = eventSource;
                            CancelEventArgs eventArgs = new CancelEventArgs();
                            eventArgs.Cancel = false;
                            object[] parameters = new object[2];
                            parameters[0] = sender;
                            parameters[1] = eventArgs;
                            // call the method
                            d1.DynamicInvoke(parameters);
                            // if the validation failed we need to return that failure
                            if (eventArgs.Cancel)
                                return false;
                            else
                                return true;
                        }
                    }
                }
            }

            targetType = targetType.BaseType;

        } while (targetType != null);

        return true;
    }

}

Пример метода проверки:

partial class ClientName
{
    public void ValidateFirstName()
    {
        if (String.IsNullOrWhiteSpace(this.FirstName))
            throw new Exception("First Name is required.");
    }

    public void ValidateLastName()
    {
        if (String.IsNullOrWhiteSpace(this.LastName))
            throw new Exception("Last Name is required.");
    }
}
2 голосов
/ 20 апреля 2009

В любом случае. Или вы можете иметь одно событие проверки, связанное со всеми, или элементы управления, которые требуют аналогичных проверок. Это удалит зацикливание из кода. Скажем, у вас есть четыре текстовых поля, которые могут иметь только целое число. То, что вы можете сделать, - это создать одно событие для каждого из них. У меня нет какой-либо IDE, поэтому приведенный ниже код - лучшее, что я могу придумать.

this.textbox1.Validated += <ValidatedEvent>
this.textbox2.Validated += <ValidatedEvent>
this.textbox3.Validated += <ValidatedEvent>
this.textbox4.Validated += <ValidatedEvent>

В случае:

  1. Ввести отправителя как текстовое поле.
  2. Проверьте, является ли значение в текстовом поле числовым.

И так далее, у вас есть события в очереди.

Надеюсь, это поможет.

2 голосов
/ 20 апреля 2009

Во всех моих формах я реализую событие isValidating для конкретного элемента управления, и если данные не проверяются, у меня есть errorProvider в форме, и я использую его метод SetError (...) для установки ошибки к соответствующему контролю с соответствующей информацией о том, почему это не так.

edit> Я должен отметить, что я обычно использую шаблон mvc при этом, поэтому конкретная проверка для этого элемента управления / элемента модели происходит в модели, поэтому isValidating выглядит примерно так:

private uicontrol_isValidating(...)
{
    if(!m_Model.MemberNameIsValid())
    {
        errorProvider.SetError(...);
    }
}
1 голос
/ 20 апреля 2009

Почему вы не используете событие Validating? Вы можете иметь одно событие проверки и проверить элементы управления там. Не будет необходимости использовать циклы, и каждый элемент управления будет проверяться при вводе данных.

1 голос
/ 20 апреля 2009

Просто грубая идея:


void btnValidate_Click(object sender, EventArgs e)
{
  foreach( Control c in this.Controls )
  {
    if( c is TextBox )
    {
      TextBox tbToValidate = (TextBox)c;
      Validate(tbToValidate.Text);
    }
  }
}

Вы можете вставить текстовые поля внутри панели и проходить там только элементы управления, если хотите избежать циклического перемещения по другим элементам управления.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...