Проверка WinForms TabControl: переход на вкладку, где проверка не удалась - PullRequest
3 голосов
/ 16 февраля 2010

У меня сейчас есть форма с TabControl, содержащая некоторые TabPage с. Каждый TabPage имеет несколько элементов управления с логикой проверки и соответствующими ErrorProvider с. На моем OK_Button_Clicked событии я звоню Form.ValidateChildren(), чтобы определить, сохранить ли и закрыть форму. Теперь предположим, что у меня есть элемент управления на вкладке 1, который не проходит проверку, но в настоящее время видимой вкладкой является вкладка 2. Когда пользователь нажимает кнопку ОК, он не получает визуальной индикации о том, почему форма не закрывается. Я хотел бы иметь возможность автоматически переключаться на вкладку, где проверка не удалась, чтобы пользователь мог видеть сообщение об ошибке ErrorProvider.

Один из подходов состоит в том, чтобы подписаться на события Validated и validating всех соответствующих элементов управления и знать, в какой вкладке находится каждый из них, и при каждом сбое проверки может быть создан список вкладок, которые не прошли проверку. Поскольку, насколько я знаю, событие ValidationFailed не генерируется, это может быть громоздким (например, определение логического значения для каждого элемента управления, установка его в значение false перед проверкой и значение true в его событии Validated). И даже если бы у меня было такое событие, я был бы вынужден прослушать много событий проверки, по одному для каждого элемента управления, которые могут не пройти проверку, и поддерживать список неподтвержденных вкладок в коде. Здесь следует отметить, что прямая подписка на события проверки 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 (как рекомендовано Крисом Селлсом в его книге WinForms), поэтому фокус действительно может измениться с элементов управления, которые не прошли проверку (включая перемещение на другие вкладки). Я также обновил пример кода для пользовательского элемента управления, чтобы подчеркнуть тот факт, что ErrorProvider находится внутри него.

1 Ответ

3 голосов
/ 18 февраля 2010

ОК, так что я наконец понял.

Я держу словарь, ключи которого TabPages, а значения HashSet s неподтвержденных элементов управления на соответствующей вкладке. Это легко сделать, подписавшись на все проверяющие и проверенные события элементов управления на каждой вкладке. Наконец, в 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;
        }
    }

Я думаю, что это довольно мило!

...