WinForms Проверяющее событие предотвращает закрытие формы клавиши Escape
У меня есть простая форма с одним текстовым полем, плюс кнопки "ОК" и "Отмена" . Форма AcceptButton и CancelButton установлены правильно, а кнопки "ОК" и "Отмена" имеют свой диалог DialogResult для "ОК" и "Отмена" .
Я хочу добавить валидацию в TextBox, которая не позволит пользователю очистить форму, когда проверка не удалась, но которая также позволит им отменить, как обычно.
Свойство CausesValidation True по умолчанию для всех элементов управления, но я изменил его на False на кнопке Cancel.
Конечно, нажав ОК или нажав клавишу Enter, будет запущено событие проверки, которое я подключил к TextBox. Нажатие кнопки "Отмена" обходит "Проверка", которая идеально подходит.
Однако нажатие Escape для отмены формы делает не выполнение так же, как нажатие кнопки "Отмена" - это вызывает событие проверки и предотвращает выход пользователя.
Есть ли способ заставить клавишу Escape выполнять, как предполагалось, то есть не поднимать событие проверки, как если бы была нажата кнопка "Отмена" ?
Полностью обработанное решение:
Создайте новое приложение Windows Forms. Добавить вторую форму в проект.
Вставьте этот код в конструктор Form1 после InitializeComponent():
MessageBox.Show((new Form2()).ShowDialog().ToString());
Это показывает, что DialogResult передан из нашей второй формы.
Вставьте этот код в конструктор Form2 после InitializeComponent():
TextBox txtName = new TextBox();
txtName.Validating +=
new CancelEventHandler((sender, e) =>
{
if (txtName.Text.Length == 3)
{
MessageBox.Show("Validation failed.");
e.Cancel = true;
}
});
Button btnOk = new Button
{
Text = "OK",
DialogResult = DialogResult.OK
};
Button btnCancel = new Button
{
Text = "Cancel",
CausesValidation = false,
DialogResult = DialogResult.Cancel
};
FlowLayoutPanel panel = new FlowLayoutPanel();
panel.Controls.AddRange(new Control[]
{
txtName, btnOk, btnCancel
});
this.AcceptButton = btnOk;
this.CancelButton = btnCancel;
this.Controls.Add(panel);
В этом упрощенном примере текстовое поле не позволит вам продолжить, если есть 3 символа ввода. Вы можете нажать кнопку "Отмена" или закрыть форму напрямую, даже если присутствуют 3 символа; однако нажатие клавиши Escape будет не делать то же самое - оно запускает событие проверки, тогда как оно должно делать то же самое, что нажатие Cancel.
Ответы
Ответ 1
Да, это неудобная причуда метода ValidateChildren. Он не знает, что отмена была задумана. Вставьте этот код, чтобы устранить проблему:
protected override void OnFormClosing(FormClosingEventArgs e) {
base.OnFormClosing(e);
e.Cancel = false;
}
Чтобы избежать выполнения обработчика события Validate, вызывающего побочные эффекты, например, окна сообщения, добавьте этот оператор в начало метода:
private void txtName_Validating(object sender, CancelEventArgs e)
{
if (this.DialogResult != DialogResult.None) return;
// etc..
}
Вставьте этот код в свою форму, чтобы установить набор DialogResult, прежде чем он попытается подтвердить форму:
protected override bool ProcessDialogKey(Keys keyData) {
if (keyData == Keys.Escape) this.DialogResult = DialogResult.Cancel;
return base.ProcessDialogKey(keyData);
}
Ответ 2
Я просто видел эту проблему, так как я искал решение для нее, и переопределение ProcessdialogKey является одобренным MS решением, пока они не исправят ошибку (Escape должен делать то же самое, что и нажатие Cancel). Обсуждение этой ошибки также найдено здесь (просто работа с Visual Basic вместо С#. Ошибка старше 5 лет и, по-видимому, еще не исправлена): Ошибка или функция? CancelButton vs Escape Key Я пытаюсь выработать решение на С++.
Изменить для добавления:
Решение из ссылки в С#:
protected override bool ProcessDialogKey(Keys keyData)
{
if (keyData == Keys.Escape)
{
this.AutoValidate = AutoValidate.Disable;
cancelButton.PerformClick();
this.AutoValidate = AutoValidate.Inherit;
return true;
}
return base.ProcessDialogKey(keyData);
}
Ответ 3
На самом деле это вызывает новые проблемы, поскольку он перехватывает escape, который должны использовать другие элементы управления, например. если у вас есть выпадающее поле со списком, нажатие escape должно закрыть выпадающее поле и не выйти из диалогового окна, который будет выполняться выше.
Можно было бы исключить определенные типы управления в событии escape-ключа, но это не очень хорошее решение, это всего лишь вопрос времени, пока другой элемент управления, который использует escape внутри, не будет введен в форму, например. управление распространением, когда режим редактирования должен быть удален при выходе.
ИМО это довольно затормозило, что они запускают проверку на побег. Кто-нибудь знает, какова идея этого, потому что это не ошибка... но это ошибка.
Если бы этот код вызывал base.ProcessDialogKey(keyData) вместо cancelButton.PerformClick, тогда вы были бы ближе к решению, так как кто-то другой мог бы определить, что делать с ключом эвакуации. Но установка AutoValidate на Disabled здесь, а затем возврат его к исходному значению не препятствует проверке, поскольку он, вероятно, просто для того, чтобы просто отправлять события и помещать сообщение в очередь, не использует это значение до тех пор, пока оно не будет установлено к нему оригинальное значение.
Просто установив для параметра "Отключено" и не возвращая его к исходному значению, он будет работать, но если ключ-побег будет перехвачен, например, вышеупомянутое упакованное combobox, то вы внезапно отключили проверку также на OK.
Туше!
У кого-нибудь есть другие яркие идеи о том, как заставить это работать, без необходимости указывать всевозможные виды управления, которые делают и каковы условия, когда им нужно бежать, например. control - это ComboBox и проверьте, не упало ли оно и если это произойдет.
Ответ 4
Ни обработчики ProcessDialogKeys, ни ответы Validating обработчика не работали для меня, возможно потому, что я использую errorProvider вместо MessageBox. Самый простой ответ, который я получил на работу, - это забыть о Validating и просто использовать следующее: -
buttonOk.Click += (_,__) =>
{
if(txtName.Text.Length == 3)
{
errorProvider1.SetError(txtName, "Wrong Length!");
DialogResult = DialogResult.None;
}
else
{
errorProvider1.SetError(txtName, string.Empty);
}
};