views:

488

answers:

1

Hi,

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 TabPages' 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 ?

Thanks!

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.

+1  A: 

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 !

ohadsc