WinForms TabControl validation: Switch to a tab where validation failed

tabcontrol, tabpage, validation, winforms

Solution

OK so I finally figured it out.

I keep a dictionary whose keys are the `TabPages` and the values are `HashSet`s 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 !

Problem

I currently have a Form with a `TabControl` containing some `TabPage`s. Each `TabPage` has several controls with validation logic and appropriate `ErrorProvider`s. 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.

Original source