0
votes

I have a simple Winform example that demonstrates what happens.

There is a GroupBox with two RadioButtons. The two buttons share a validating event handler. Also on the form is a Button that does nothing. No events are connected. Finally there is a CheckBox that controls the state of a passing validation. Checked it passes and unchecked it fails.

When the program starts and the user clicks on either RadioButton the validating event does not fire. Then when I click on any control other than the current button the Validating Event fires. The NOP button gives me something to click besides the CheckBox.

In this test the CheckBox represents the status of passing the validation.

This will not work because once I uncheck the CheckBox and then click a radio button the focus is forever stuck. You can't get the focus to the CheckBox to change its state.

The reason is the Validating event is always called when the focus is leaving and not when the RadioButton is clicked. It sees that "valdating" fails and cancels the event.

This is obviously the wrong approach. What should I be doing to test at the time of the initial click? The Click event happens after the state has changed.

What event should I use so that I can test validation before changing the RadioButton state? Then I can leave the button and fix the issue before trying again.

This example is a simplified test that shows my dead end. My real world example is the two RadioButtons select one of two similar tables in a DataGridView. The two tables are related and I want them to be on the same TabPage. When the user selects the alternate table I want to do a validation/confirmation before switching away. If the confirmation fails I want to cancel the radio button.

// Validate is called when the radio button is clicked and when it leaves the box
using System.ComponentModel;
using System.Windows.Forms;

namespace ValidateRadioButton
{
  public partial class Form1 : Form
  {
    int count;
    public Form1()
    {
      InitializeComponent();
    }

    private void radiobutton_Validating(object sender, CancelEventArgs e)
    {
      if (sender is RadioButton) {
        // If box not checked, cancel radioButton change
        // This becomes a Hotel California test, once unchecked you 
        // can never leave the control
        e.Cancel = !chkAllowChange.Checked;
      }
      count++;
      //Display the number of times validating is called in the title bar
      //Demonstrates when the event is called
      Text = count.ToString(); 
    }

  }
}
2

2 Answers

0
votes

I have reproduced your form in a test project and I think I see what is going on.

Setting the Checked property of CancelEventArgs to true will prevent whatever control set it from losing focus until input is "corrected". As you are aware, the Validating event is triggered whenever a control loses focus. The user becomes "stuck" as you say because the only way they can "correct" their input is to modify the check box, which they cannot get to because of the Validating event which fires on the radio button.

A solution which I came up with was moving the Validated event to the GroupBox, instead of the radio buttons:

private void groupBox_Validating(object sender, CancelEventArgs e)
{
    if (sender is RadioButton)
    {
        e.Cancel = !checkBox1.Checked;
    }
    count++;

    //Display the number of times validating is called in the title bar
    //Demonstrates when the event is called
    Text = count.ToString();
}

Be sure to remove the Validating event handler from the radio buttons.


For clarity, I have set my form up in this manner:

Screenshot of layout of form controls, within group box.

The result is now I cannot click the 'OK' until I have ticked the check box. I have preserved your Text = count.ToString() assignment so that you can see that the form now calls Validating only as it should.

0
votes

I ended up using the CheckChanged and Click events for the RadioButtons.

I track the currently active RadioButton and do the validation in the CheckChanged event. Then in the Click event, if the validation failed I restore the previous active RadioButton. Otherwise we can continue to the purpose of the RadioButton.

This solution works for two or more RadioButtons in a group and it works when using the keyboard.

using System.ComponentModel;
using System.Windows.Forms;

namespace ValidateRadioButton
{
  public partial class Form1 : Form
  {

    // RadioButton that is currently active
    RadioButton ActiveRadioButton;
    bool cancelingChange;

    public Form1()
    {
      InitializeComponent();
      activeRadioButton = this.radioButton1;
    }

    private void radioButton_CheckedChanged(object sender, System.EventArgs e)
    {
      // Each click fires two events
      // One for the RadioButton loosing its check and the other that is gaining it
      // We are interested in the one gaining it
      if (sender is RadioButton) {
        RadioButton radioButton = (RadioButton)sender;
        if (radioButton.Checked) {
          // If this button is changing because of a canceled check
          // do not validate
          if (!cancelingChange) {
            cancelingChange = !ValidateData();
            if (!cancelingChange) {
              // Mark this as the active value
              activeRadioButton = radioButton;
            }
          }
        }
      }
    }

    private void radioButton_Click(object sender, System.EventArgs e)
    {
      // This is called after the RadioButton has changed
      if (cancelingChange) {
        // Check theRadioButton that was previously checked
        activeRadioButton.Checked = true;
        cancelingChange = false;
      }
      else {
        // Do the thing the RadioButton should do
        // If using separate events they all must start with the condition above.
        this.Text = ((RadioButton)sender).Name;
      }
    }

    /// <summary>
    /// Validate state of data
    /// </summary>
    /// <returns></returns>
    private bool ValidateData()
    {
      bool result = chkAllowChange.Checked;
      if (!result) {
        result = MessageBox.Show("Do you want to save your data?", "CheckBox unchecked", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation) == DialogResult.OK;
        if (result) {
          // This is where I would save the data
          chkAllowChange.Checked = true;
        }
      }
      return result;
    }
  }
}