WinForms TabControl 验证:切换到验证失败的选项卡

发布于 2024-08-21 16:16:05 字数 2268 浏览 11 评论 0原文

我目前有一个带有 TabControl 的表单,其中包含一些 TabPage。每个 TabPage 都有几个带有验证逻辑和适当的 ErrorProvider 的控件。在我的 OK_Button_Clicked 事件中,我调用 Form.ValidateChildren() 以确定是否保存并关闭表单。现在,假设选项卡 1 中有一个控件验证失败,但当前可见的选项卡是选项卡 2。当用户按下“确定”时,他不会得到任何关于表单未关闭原因的视觉指示。我希望能够自动切换到验证失败的选项卡,以便用户能够看到 ErrorProvider 的错误指示。

一种方法是订阅所有适当控件的 Validatedvalidating 事件,并了解每个控件位于哪个选项卡中,每当验证失败时,就会显示一个选项卡列表可以构建失败的验证。据我所知,由于没有生成 ValidationFailed 事件,这可能很麻烦(例如为每个控件定义一个布尔值,在验证之前将其设置为 false 并在其 Validated 上设置为 true > 事件)。即使我有这样的事件,我也将被迫监听许多验证事件,每个验证事件可能会失败,并在代码中维护未验证选项卡的列表。我应该在这里指出,直接订阅 TabPage 的验证事件不起作用,因为即使其中包含的控件未通过验证,它们也会通过验证。

另一种方法可以利用我的 TabPage 中的控件恰好是自定义控件这一事实。然后,我可以让它们实现一个接口,例如:

interface ILastValidationInfoProvider
{
    public bool LastValidationSuccessful {get; set;}
}

例如:

public MyControl : UserControl, ILastValidationInfoProvider
{
    MyControl_Validing(object sender, object sender, CancelEventArgs e)
    {
        if (this.PassesValidation())
          this.ErrorProvider.SetError(sender, null);
          LastValidationSuccessful = true;
        else
          e.Cancel = true;
          this.ErrorProvider.SetError("Validation failed!", null);
          LastValidationSuccessful = false;
    }
}

然后,在调用 ValidateChildren 之后,我可以使用以下代码:

public void OK_Button_Click
{
     if (form.ValidateChildren())
         this.Close()
     else
         foreach (TabPage tab in this.TabControl)
             foreach (Control control in tab.Controls)
             {
                 ValidationInfo = control as ILastValidationInfoProvider
                 if (ValidationInfo != null && !ValidationInfo.LastValidationSuccessful)
                 {
                    this.TabControl.SelectTab(tab);
                    return;
                 }
             }
}

我更喜欢这种方法,但它不适合以下情况:正在验证的控件不是自定义的。

我很乐意使用更好的方法。有什么想法吗?

编辑 我正在使用 Form.AutoValidate = EnableAllowFocusChange (正如 Chris Sells 在他的 WinForms 书中所推荐的那样),因此焦点确实可以从验证失败的控件更改(包括移动到其他选项卡)。我还更新了自定义控件的示例代码,以强调 ErrorProvider 驻留在其中的内部事实。

I currently have a Form with a TabControl containing some TabPages. Each TabPage has several controls with validation logic and appropriate ErrorProviders. On my OK_Button_Clicked event, I call Form.ValidateChildren() in order to determine whether to save and close the form . Now, suppose I have a control in tab 1 that fails validation, but the currently visible tab is tab 2. When the user presses OK, he would get no visual indication as to why the form is not closing. I would like to be able to automatically switch to a tab where validation failed, so the user would see the ErrorProvider's indication of error.

One approach would be subscribing to the Validated and validating events of all appropriate controls, and knowing which tab each of them is in, whenever one fails validation, a list of tabs that failed validation could be built. Since no ValidationFailed event is generated as far as I know, this could be cumbersome (e.g. defining a boolean for each control, setting it to false before validation and to true on its Validated event). And even if I had such an event, I would be forced to listen to many validation events, one for each control that might fail validation, and maintain the list of unvalidated tabs in code. I should note here that subscribing directly to the TabPage's validation events doesn't work, because they pass as validated even if controls contained inside them fail validation.

Another approach could leverage the fact that the controls in my TabPage happen to be custom controls. I could then make them implement an interface such as:

interface ILastValidationInfoProvider
{
    public bool LastValidationSuccessful {get; set;}
}

For example:

public MyControl : UserControl, ILastValidationInfoProvider
{
    MyControl_Validing(object sender, object sender, CancelEventArgs e)
    {
        if (this.PassesValidation())
          this.ErrorProvider.SetError(sender, null);
          LastValidationSuccessful = true;
        else
          e.Cancel = true;
          this.ErrorProvider.SetError("Validation failed!", null);
          LastValidationSuccessful = false;
    }
}

And then, after the call to ValidateChildren I could use code such as:

public void OK_Button_Click
{
     if (form.ValidateChildren())
         this.Close()
     else
         foreach (TabPage tab in this.TabControl)
             foreach (Control control in tab.Controls)
             {
                 ValidationInfo = control as ILastValidationInfoProvider
                 if (ValidationInfo != null && !ValidationInfo.LastValidationSuccessful)
                 {
                    this.TabControl.SelectTab(tab);
                    return;
                 }
             }
}

I like this approach better but it doesn't cater to cases where the controls being validated are not custom.

I would gladly use a better approach. Any ideas?

EDIT I am using Form.AutoValidate = EnableAllowFocusChange (as recommended by Chris Sells in his WinForms book), So the focus can indeed change from controls that failed validation (including moving to other tabs). I have also updated the sample code for the custom control to emphasize the fact that the ErrorProvider resides internally inside it.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

水波映月 2024-08-28 16:16:05

好吧,我终于明白了。

我保留了一个字典,其键是 TabPages ,值是相应选项卡中未经验证的控件的 HashSet 。通过订阅每个选项卡中控件的所有验证和验证事件可以轻松完成此操作。最后,在 OK_BUtton_Click 中,如果 ValidateChildren 失败,我知道其中一个哈希集将不为空,我只需跳转到第一个未验证的选项卡(仅当当前选定的选项卡不存在时)本身没有任何错误)。

    Dictionary<TabPage, HashSet<Control>> _tabControls 
                           = new Dictionary<TabPage, HashSet<Control>>();

    public OptionsForm()
    {   
        InitializeComponent();
        RegisterToValidationEvents();
    }

    private void RegisterToValidationEvents()
    {
        foreach (TabPage tab in this.OptionTabs.TabPages)
        {
            var tabControlList = new HashSet<Control>();
            _tabControls[tab] = tabControlList;
            foreach (Control control in tab.Controls)
            {
                var capturedControl = control; //this is necessary
                control.Validating += (sender, e) =>
                    tabControlList.Add(capturedControl);
                control.Validated += (sender, e) =>
                    tabControlList.Remove(capturedControl);
            }
        }
    }

    private void Ok_Button_Click(object sender, EventArgs e)
    {
        if (this.ValidateChildren())
        {
            _settings.Save();
            this.Close();
        }
        else
        {
            var unvalidatedTabs = _tabControls.Where(kvp => kvp.Value.Count != 0)
                                              .Select(kvp => kvp.Key);
            TabPage firstUnvalidated = unvalidatedTabs.FirstOrDefault();
            if (firstUnvalidated != null && 
                !unvalidatedTabs.Contains(OptionTabs.SelectedTab))
                    OptionTabs.SelectedTab = firstUnvalidated;
        }
    }

我觉得还蛮甜的!

OK so I finally figured it out.

I keep a dictionary whose keys are the TabPages and the values are HashSets of unvalidated controls within the corresponding tab. This is easily done by subscribing to all the validating and validated events of the controls in each tab. Finally, in OK_BUtton_Click, if ValidateChildren fails, I know one of the hashsets will be none empty and I simply jump to the first unvalidated tab (only if the currently selected tab doesn't have any error itself).

    Dictionary<TabPage, HashSet<Control>> _tabControls 
                           = new Dictionary<TabPage, HashSet<Control>>();

    public OptionsForm()
    {   
        InitializeComponent();
        RegisterToValidationEvents();
    }

    private void RegisterToValidationEvents()
    {
        foreach (TabPage tab in this.OptionTabs.TabPages)
        {
            var tabControlList = new HashSet<Control>();
            _tabControls[tab] = tabControlList;
            foreach (Control control in tab.Controls)
            {
                var capturedControl = control; //this is necessary
                control.Validating += (sender, e) =>
                    tabControlList.Add(capturedControl);
                control.Validated += (sender, e) =>
                    tabControlList.Remove(capturedControl);
            }
        }
    }

    private void Ok_Button_Click(object sender, EventArgs e)
    {
        if (this.ValidateChildren())
        {
            _settings.Save();
            this.Close();
        }
        else
        {
            var unvalidatedTabs = _tabControls.Where(kvp => kvp.Value.Count != 0)
                                              .Select(kvp => kvp.Key);
            TabPage firstUnvalidated = unvalidatedTabs.FirstOrDefault();
            if (firstUnvalidated != null && 
                !unvalidatedTabs.Contains(OptionTabs.SelectedTab))
                    OptionTabs.SelectedTab = firstUnvalidated;
        }
    }

I think it's pretty sweet !

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文