Select only one checkbox from multiple checkbox columns in questionnaire style DataGridView

纵然是瞬间 提交于 2020-06-16 04:18:26

问题


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);
}

回答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.




回答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.



来源:https://stackoverflow.com/questions/62150438/select-only-one-checkbox-from-multiple-checkbox-columns-in-questionnaire-style-d

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!