WinForms验证事件阻止了Esc键关闭窗体。

13

我有一个简单的表单,只有一个文本框,以及确定和取消按钮。表单的AcceptButton和CancelButton已正确设置,并且OK和Cancel按钮的DialogResult已分别设置为“OK”和“Cancel”。

我想要在文本框中添加验证功能,当验证失败时防止用户点击OK提交表单,但仍能像往常一样进行取消操作。

所有控件的CausesValidation属性默认为True,但我已将其在取消按钮上更改为False。

确实,单击OK或按Enter键会运行我连接到TextBox的Validating事件。按取消按钮将绕过Validating,这很完美。

然而,按Escape取消表单并不像按取消按钮那样执行相同的操作 - 它会触发Validating事件并阻止用户退出。

是否有办法使Escape键按预期执行,即不触发Validating事件,就像按下取消按钮一样?

完整的解决方案如下:

创建一个新的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个字符,您也可以按取消按钮或直接关闭表单;但是按Esc键不会产生相同的效果-它会触发验证事件,而应该执行与按Cancel相同的操作。

WinForms验证防止按下Esc键关闭窗体。我也有同样的问题。 - Colonel Panic
4个回答

16

是的,这是ValidateChildren方法的一个尴尬问题。它不知道取消操作旨在何处。请复制此代码以解决问题:

    protected override void OnFormClosing(FormClosingEventArgs e) {
        base.OnFormClosing(e);
        e.Cancel = false;
    }
为了避免运行具有副作用(例如消息框)的验证事件处理程序,请在此方法顶部添加此语句:

为避免运行具有副作用(例如消息框)的验证事件处理程序,请在此方法的开头添加以下语句:

    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);
    }

目前还不清楚如何让“Esc”键关闭表单,但是已经在帖子中添加了潜在的解决方法。 - Hans Passant
在VS2010中,必须删除“base.OnFormClosing(e);”这一行,否则会出现StackOverflowException。除此之外,很好。 - Fernando Gonzalez Sanchez
1
这是无稽之谈,不要删除它。 - Hans Passant
嗨,我有点困惑,这个怎么运作?我需要三个部分吗? - Colonel Panic
@ColonelPanic 我发现你不需要 OnFormClosing 覆盖,但其他两个代码片段是必需的。 - Dave R.
显示剩余9条评论

6
我在寻找解决方案时遇到了同样的问题,覆盖ProcessdialogKey是微软官方的解决方案,直到他们修复这个错误(Escape应该与点击取消相同)。 这个错误的讨论也可以在这里找到(只是使用Visual Basic而不是C#。该错误已经存在5年以上,显然仍未修复):Bug or Feature? CancelButton vs Escape Key 我正在尝试找出C ++解决方案。
编辑添加: 来自C#链接的解决方案:
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);
}

1

实际上,这会引起新的问题,因为它截取了其他控件应该使用的转义字符,例如,如果您有一个下拉框,按下escape键应该关闭下拉框而不是退出对话框,但上面的代码会导致退出对话框。

可以在escape键事件中豁免某些控件类型,但这不是一个好的解决方案,因为很快就会有另一个使用内部转义字符的控件被引入到表单中,例如,一个需要在escape键上退出编辑模式的扩展控件。

我认为在escape键上运行验证非常愚蠢。有人知道背后的想法是什么吗?因为这不是一个错误...但它确实是一个错误。

如果那段代码调用base.ProcessDialogKey(keyData)而不是cancelButton.PerformClick,那么你会更接近一个解决方案,因为由别人来决定如何处理escape键。但在这里将AutoValidate设置为Disabled,然后将其返回到原始值并不能防止验证,因为它可能只是发布事件并将消息放入队列,直到将其设置回原始值之后才使用该值。

只需将其设置为禁用并不返回原始值,它就可以工作,但如果例如上述的下拉组合框拦截了转义键,那么您突然也无法在“确定”上进行验证。

Touché!

有没有其他聪明的想法可以让这个工作,而不必指定所有可能的控件类型以及它们需要转义的条件,例如控件是ComboBox并检查它是否已展开,如果是,则退出。


0

对于我来说,既不能使用 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);
  }
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接