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.