Проверка подлинности WinForm UI
Мне нужно выполнить проверку ввода во всем приложении winform. Существует множество различных форм, в которые можно вводить данные, и я бы не хотел управлять ими по форме и создавать isValid и т.д. Для каждого элемента. Как другие справлялись с этим?
Я вижу, что большинство связанных сообщений посвящено веб-приложениям и/или упоминанию Блок приложений для проверки корпоративной библиотеки. Теперь я признаю, что я не тщательно изучил ELVAB, но, похоже, это излишний фактор для того, что мне нужно. Моя нынешняя мысль состоит в том, чтобы написать библиотеку классов с различными требованиями и передать ей элемент управления в качестве параметра. У меня уже есть библиотека функций RegEx для таких вещей, как isValidZipCode, и поэтому я могу начать с нее.
То, что я хотел бы иметь, это кнопка Validate, в которой onClick циклически перемещается по всем элементам управления на этой странице формы и выполняет необходимую проверку. Как я могу это сделать?
Ответы
Ответ 1
В моем собственном приложении мне нужно проверить размеры по мере их ввода. Последовательность, которую я использовал, выглядит следующим образом
- Пользователь выбирает или затем перемещает
от контроля.
- Элемент управления теряет фокус и уведомляет
Просмотр отправки его идентификатора и
текст ввода.
- Просмотр проверяет, какая формальная программа
(класс, реализующий интерфейс)
создал форму и передал ее
ID и текст ввода
- Программа формы возвращает
ответ.
- Если ответ в порядке, View
обновляет правильность Ввод формы
Класс.
- Если ответ выполнен в порядке,
Форма через интерфейс, который он
ОК, чтобы переключить фокус на следующую запись.
- Если ответ не подходит, просмотр
рассматривает ответ и использует
Интерфейс формы сообщает форме, что
делать. Это обычно означает фокус
переходит к оскорбительной записи
с сообщением, отображающим
пользователь, что произошло.
Преимущество такого подхода заключается в том, что валидация централизована в одном месте для данной Формируемой программы. Мне не нужно изменять каждый элемент управления или даже беспокоиться о различных типах элементов управления в форме. Когда я разработал программное обеспечение, я решил, как пользовательский интерфейс будет работать для текстовых полей, списков, комбинированных ящиков и т.д. Также разные уровни серьезности обрабатываются по-разному.
View заботится о том, чтобы инструктировать форму, что делать через интерфейс. Как это фактически реализовано, обрабатывается самой Формой в ее реализации интерфейса. View не заботится, отображается ли форма для желтого цвета для предупреждения и красного цвета для ошибки. Только то, что он обрабатывает эти два уровня. Позже, если появится лучшая идея отображения предупреждений и ошибок, я могу внести изменения в форму, а не обманывать логику View или проверку в программе Shape.
Вы уже на полпути, если вы подумываете о том, чтобы создать класс для проверки логики проверки, это приведет вас к концу в новом дизайне.
Ответ 2
Валидация уже встроена в библиотеку WinForms.
Каждый Control
-уложенный объект имеет два события с именем Validating
и Validated
. Также он имеет свойство CausesValidation
. Если для этого параметра установлено значение true (оно истинно по умолчанию), тогда элемент управления участвует в проверке. В противном случае это не так.
Валидация происходит как часть фокуса. Когда вы фокусируетесь на контроле, его события проверки активируются. Фактически фокусные события запускаются в определенном порядке. Из MSDN:
Когда вы меняете фокус, используя клавиатура (TAB, SHIFT + TAB и т.д.), вызывая функцию Select или SelectNextControl или установление ContainerControl..::. ActiveControl свойство в текущую форму, фокус события происходят в следующем порядке:
- Enter
- GotFocus
- Оставьте
- Validating
- Подтверждено
- LostFocus
Когда вы меняете фокус, используя мышь или вызов метода Фокус, события фокуса происходят в следующем порядок:
- Enter
- GotFocus
- LostFocus
- Оставьте
- Validating
- Подтверждено
Если свойство CausesValidation равно установите значение false, подтверждение и Проверенные события подавляются.
Если свойство Отмена CancelEventArgs установлено в true в Проверка делегата события, всех событий что обычно происходит после Проверяющее событие подавляется.
Также ContainerControl имеет метод ValidateChildren()
, который будет проходить через содержащиеся элементы управления и проверять их.
Ответ 3
Я понимаю, что эта тема довольно старая, но я думал, что опубликую решение, которое я придумал.
Самая большая проблема с проверкой на WinForms - это проверка выполняется только тогда, когда элемент управления "потерял фокус". Поэтому пользователь должен щелкнуть внутри текстового поля, а затем щелкнуть в другом месте для выполнения процедуры проверки. Это нормально, если вы беспокоитесь только о правильности введенных данных. Но это не сработает, если вы пытаетесь убедиться, что пользователь не оставил текстовое поле пустым, пропустив его.
В моем решении, когда пользователь нажимает кнопку отправки для формы, я проверяю каждый элемент управления на форме (или любом конкретном контейнере) и использую отражение, чтобы определить, определен ли метод проверки для элемента управления. Если это так, выполняется метод проверки. Если какая-либо проверка не выполняется, процедура возвращает сбой и позволяет остановить процесс. Это решение работает хорошо, особенно если у вас есть несколько форм для проверки.
1) Просто скопируйте и вставьте этот раздел кода в свой проект. Мы используем Reflection, поэтому вам нужно добавить System.Reflection в ваши операторы using.
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 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 determine if the control has a validating method attached to it
// and if it does, let 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 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) Используйте стандартное проверочное событие для любого элемента управления, который вы хотите проверить. Будьте уверены, использовать 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, "");
}
Не пропустите этот шаг! Это позволит использовать табуляцию для другого элемента управления, даже если проверка не выполняется.
4) Наконец, в методе Submit Button вызовите метод Validation и укажите, какой контейнер вы хотите проверить. Это может быть вся форма или просто контейнер в форме, такой как панель или группа.
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();
}
Счастливое кодирование!
Ответ 4
Мне бы не нужно было управлять элементом управления по форме и создавать isValid и т.д. за элемент.
В качестве некоторого уровня вам нужно будет определить, что означает valid
для каждого элемента управления, если только вы не заботитесь о том, что элемент управления имеет какое-то значение.
Тем не менее, там компонент ErrorProvider, который вы можете использовать, работает очень хорошо.
Ответ 5
Нам повезло с Noogen ValidationProvider. Он прост для простых случаев (проверки типов данных и требуемых полей) и легко добавлять пользовательскую проверку для более сложных случаев.
Ответ 6
Во всех моих формах я реализую событие isValidating для конкретного элемента управления, и если данные не проверяются, у меня есть errorProvider в форме, и я использую его метод SetError (...) для установки ошибки к соответствующему вопросу с соответствующей информацией о том, почему это неправильно.
edit > Следует отметить, что я обычно использую шаблон mvc при этом, поэтому конкретная проверка для этого элемента управления/члена модели происходит в модели, поэтому isValidating выглядит примерно так:
private uicontrol_isValidating(...)
{
if(!m_Model.MemberNameIsValid())
{
errorProvider.SetError(...);
}
}
Ответ 7
Так или иначе. Или вы можете иметь одно проверяющее событие, связанное со всеми или элементами управления, которые нуждаются в аналогичных проверках. Это приведет к удалению цикла из кода. Скажем, у вас есть четыре текстовых поля, которые могут иметь только целое число. Вы можете сделать одно событие для каждого из них. У меня нет IDE, поэтому код ниже - это лучшее, что я могу придумать.
this.textbox1.Validated += <ValidatedEvent>
this.textbox2.Validated += <ValidatedEvent>
this.textbox3.Validated += <ValidatedEvent>
this.textbox4.Validated += <ValidatedEvent>
В случае:
- Отдать отправителя как текстовое поле.
- Проверьте, является ли значение в текстовом поле числовым.
И так у вас есть события, выстроенные в линию.
Надеюсь, что это поможет.
Ответ 8
Если вы объедините приведенные выше идеи с этим общим проверочным обработчиком событий, вы получите хорошую "фреймворк" проверки достоверности со всеми методами проверки в своих бизнес-классах. Я просто расширил код Брюса датской идеей.
Это было сделано для компонентов 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 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 determine if the control has a validating method attached to it
// and if it does, let 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 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.");
}
}
Ответ 9
Использование элементов управления через управление может работать, но оно подвержено ошибкам. Я работал над проектом, который использовал этот метод (если он был проектом Delphi, а не С#), и он работал так, как ожидалось, но было очень сложно обновить, если элемент управления был добавлен или изменен. Возможно, это было правильно. Я не уверен.
В любом случае это сработало, создав один обработчик событий, который затем был привязан к каждому элементу управления. Затем обработчик будет использовать RTTI для определения типа элемента управления. Затем он будет использовать свойство имени элемента управления в большом элементе select, чтобы найти код проверки для запуска. Если проверка не удалась, пользователю было отправлено сообщение об ошибке, а элементу управления был задан фокус. Чтобы сделать вещи более сложными, форма была разделена на несколько вкладок, и надлежащая вкладка должна была быть видимой для него, чтобы получить контроль.
Так что мой опыт.
Я бы скорее использовал шаблон пассивного представления, чтобы удалить все бизнес-правила из формы и вставить их в класс Presenter. В зависимости от состояния вашей формы, которое может быть больше работы, чем ваше желание инвестировать.
Ответ 10
Просто грубая идея:
void btnValidate_Click(object sender, EventArgs e)
{
foreach( Control c in this.Controls )
{
if( c is TextBox )
{
TextBox tbToValidate = (TextBox)c;
Validate(tbToValidate.Text);
}
}
}
Вы можете вставлять текстовые поля внутри панели и только прокручивать элементы управления там, если вы хотите избежать чередования других элементов управления.
Ответ 11
Почему вы не используете проверочное событие? Вы можете иметь одно проверочное событие и проверять элементы управления там. Нет необходимости использовать циклы, и каждый элемент управления будет проверен при вводе данных.