2

I've create an application which displays a DataGridView with a series of questions. The dgv structure consists of one string column for the question text and three bool/checkbox columns for the answers (yes, no, N/A). Each single question is displayed on its own row.

I would like my program to only allow the user to select only Yes, only No or only N/A on each row.

I think I would need to uncheck the other checkbox options when one option is checked but I'm not too sure on how to do this.

I've setup CellValueChanged and CellContentClick events but I'm unsure of the code needed to achieve the desired functionality.

DataGridView is bound to a DataTable.

Code I have so far:

private void dgvQuestions_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
    int columnIndex = e.ColumnIndex;
    int rowIndex = e.RowIndex;

    DataGridViewCheckBoxCell chkYes = dgvQuestions.Rows[rowIndex].Cells[2] as DataGridViewCheckBoxCell;
    DataGridViewCheckBoxCell chkNo = dgvQuestions.Rows[rowIndex].Cells[3] as DataGridViewCheckBoxCell;
    DataGridViewCheckBoxCell chkNA = dgvQuestions.Rows[rowIndex].Cells[4] as DataGridViewCheckBoxCell;    

    if (Convert.ToBoolean(chkYes.Value) == true)
    {

    }

    if (Convert.ToBoolean(chkNo.Value) == true)
    {

    }

    if (Convert.ToBoolean(chkNA.Value) == true)
    {

    }
}

private void dgvQuestions_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
    dgvQuestions.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
5
  • 1
    I <3 DataGridView. Great question. Commented Jun 2, 2020 at 13:31
  • Programmatically check a DataGridView CheckBox that was just unchecked. Btw, a question related to a DataGridView requires that you specify the DataSource type. Commented Jun 2, 2020 at 15:43
  • @Jimi, respectfully I would disagree since BindingList<T> is generic and can be used as the DataSource for the DataGridView (at which point all the public properties of class T are visible and populated in the DataGridView automatically). So, my point of view is that such questions could be answered in the generic form as working for any class T. Commented Jun 2, 2020 at 16:25
  • @IVSoftware Yes, well, a BindingList can do what it can do. A DataTable can do other things. A DataTable bound to a BindingSource, much more. My point: using a DataTable (for example), all that's needed is a calibrated Expression (even with calculated Columns). Using a BindingList and a Class Object: does the class implement INotifyPropertyChannged? And/or, could this logic be implemented in the class object itself, so the Single Option is actually triggered internally and you don't bother the UI layer at all? If you can avoid working on the View side, it's a good thing. etc. Commented Jun 2, 2020 at 16:37
  • Sure! My point is very narrow: that BindingList, DataTable are easily implemented for class T thus don't need to be known at post time. DataTable from Constrained Interface or DataTable from IEnumerable<T>. I think that you and I are mostly on the same side of the issue. And in practice it's handled internally in class MyDataGridView : DataGridView{} Commented Jun 2, 2020 at 16:58

2 Answers 2

2

I hope this sample is useful for making DataGridView simple and powerful; it relates to the post as originally worded which was "Any help appreciated".

Does this video show the behavior that you're looking for? What works for me is to use a BindingList as the DataSource of the DataGridView. Then, using the 'CellDirty' event that occurs when a checkbox changes you can make them act like one-hot radio buttons and answer your question: "select only one checkbox from multiple checkbox items".

Here's an example of a class representing one line item of the questionaire.

class QuestionaireItem
{
    public string Question { get; internal set; } = "Question " + _count++;
    public bool Yes { get; set; }
    public bool No { get; set; }
    public bool Maybe { get; set; } // OOPS! I should have said "NA"

    static int _count = 1;
}

When you bind this class to a DataGridView the columns will be automatically populated with a column named 'Question' (which is read-only (because the 'set' is marked internal) and the three checkbox columns whose value can be changed (because both get and set are public). Taking this approach works for any class T and does nearly all the heavy lifting of using DataGridView.

Here's how you handle the CellDirty event to make the three checkboxes (I named them Yes, No and Maybe) act like radio buttons:

private void DataGridView1_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
    // The cell will be considered "dirty" or modified so Commit first.
    dataGridView1.EndEdit(DataGridViewDataErrorContexts.Commit);
    // Get the QuestionaireItem that is bound to the row
    QuestionaireItem item = (QuestionaireItem)
        dataGridView1
        .Rows[dataGridView1.CurrentCell.RowIndex]
        .DataBoundItem;
    // Now see which column changed:
    switch (dataGridView1.Columns[dataGridView1.CurrentCell.ColumnIndex].Name)
    {
        case "Yes":
            item.No = false;        // i.e. "unchecked"
            item.Maybe = false;
            break;
        case "No":
            item.Yes = false;       
            item.Maybe = false;
            break;
        case "Maybe":
            item.Yes = false;
            item.No = false;
            break;
    }
    dataGridView1.Refresh();    // Update the binding list to the display
}

The binding itself is simple to do once the MainForm has its window Handle. We can override OnHandleCreated for this purpose. Here, the binding process will work properly and we can also set the display widths for the columns. This shows how to initialize dataGridView1. I've put comments in to explain what's happening:

protected override void OnHandleCreated(EventArgs e)
{
    base.OnHandleCreated(e);
    if (!DesignMode)    // We only want this behavior at runtime
    {
        // Create the binding list
        BindingList<QuestionaireItem> testdata = new BindingList<QuestionaireItem>();
        // And add 5 example items to it
        for (int i = 0; i < 5; i++) testdata.Add(new QuestionaireItem());
        // Now make this list the DataSource of the DGV.
        dataGridView1.DataSource = testdata;

        // This just formats the column widths a little bit
        dataGridView1.Columns["Question"].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
        dataGridView1.Columns["Maybe"].Width =
        dataGridView1.Columns["Yes"].Width =
        dataGridView1.Columns["No"].Width = 40;

        // And this subscribes to the event (one of them anyway...)
        // that will fire when the checkbox is changed
        dataGridView1.CurrentCellDirtyStateChanged += DataGridView1_CurrentCellDirtyStateChanged;
    }
}

Clone or Download this example from GitHub.

Sign up to request clarification or add additional context in comments.

4 Comments

Hi, thanks for your very concise answer. The video does display the functionality that I'm looking for but I my DGV is bound to a DataTable. Sorry, I should have said this originally.
No worries! I definitely see how that makes it not "the" answer but I hope someone coming across your question in a different context might.
To anyone stumbling across this thread, if you're not already committed to BindingList or DataTable here's a nice MS blog discussing the pros and cons of the two approaches.
Yeah it'll definitely be helpful to others. Thanks.
1

It appears you have the CellContentClick set up properly, however, if there are other columns in the grid, then, it may be beneficial if you check to make sure that the cell whose content was clicked is actually one of the check box cells. Otherwise the code may be setting the cells value unnecessarily.

private void dataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e) {
  string colName = dataGridView1.Columns[e.ColumnIndex].Name;
  if (colName == "Yes" || colName == "No" || colName == "N/A") { 
    dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);
  }
}

In the CellValueChanged event, again the code should check for only the check box values. In addition, I would assume that at least ONE (1) of the cells MUST be checked. Example, if the “N/A” cell is originally checked, then the user “unchecks” that cell, then the row would have NO check boxes checked. This is the final check in the code such that if the user “unchecks” the “N/A” cell AND this leaves ALL check boxes “unchecked”, then, the code will “check” the “N/A” cell. Also, it is important to “turn OFF” the CellValueChanged event before we change any of the check box values IN the CellValueChanged event to avoid reentrant. Something like…

private void dataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e) {
  if (e.RowIndex >= 0 && e.ColumnIndex >= 0) {
    string colName = dataGridView1.Columns[e.ColumnIndex].Name;
    bool checkValue;
    dataGridView1.CellValueChanged -= new DataGridViewCellEventHandler(this.dataGridView1_CellValueChanged);
    switch (colName) {
      case "Yes":
        checkValue = (bool)dataGridView1.Rows[e.RowIndex].Cells["Yes"].Value;
        if (checkValue == true) {
          dataGridView1.Rows[e.RowIndex].Cells["No"].Value = false;
          dataGridView1.Rows[e.RowIndex].Cells["N/A"].Value = false;
        }
        else {
          dataGridView1.Rows[e.RowIndex].Cells["N/A"].Value = true;
        }
        break;
      case "No":
        checkValue = (bool)dataGridView1.Rows[e.RowIndex].Cells["No"].Value;
        if (checkValue == true) {
          dataGridView1.Rows[e.RowIndex].Cells["Yes"].Value = false;
          dataGridView1.Rows[e.RowIndex].Cells["N/A"].Value = false;
        }
        else {
          dataGridView1.Rows[e.RowIndex].Cells["N/A"].Value = true;
        }
        break;
      case "N/A":
        checkValue = (bool)dataGridView1.Rows[e.RowIndex].Cells["N/A"].Value;
        if (checkValue == true) {
          dataGridView1.Rows[e.RowIndex].Cells["Yes"].Value = false;
          dataGridView1.Rows[e.RowIndex].Cells["No"].Value = false;
        }
        else {
          if ((bool)dataGridView1.Rows[e.RowIndex].Cells["Yes"].Value == false &&
              (bool)dataGridView1.Rows[e.RowIndex].Cells["No"].Value == false) {
            dataGridView1.Rows[e.RowIndex].Cells["N/A"].Value = true;
          }
        }
        break;
      default:
        break;
    }
    dataGridView1.CellValueChanged += new DataGridViewCellEventHandler(this.dataGridView1_CellValueChanged);
  }

Below is a simple example with the three columns “Yes”, “No” and “N/A” check box columns.

public Form1() {
  InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e) {
  dataGridView1.DataSource = GetTable();
}

private DataTable GetTable() {
  DataTable dt = new DataTable();
  dt.Columns.Add("Yes", typeof(bool));
  dt.Columns.Add("No", typeof(bool));
  dt.Columns.Add("N/A", typeof(bool));
  for (int i = 0; i < 10; i++) {
    dt.Rows.Add(false, false, true);
  }
  return dt;
}

Hope this helps.

1 Comment

This is perfect. Thanks so much for your help!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.