【问题标题】:winform datagridview update value during CellValueChanged在 CellValueChanged 期间 winform datagridview 更新值
【发布时间】:2021-03-21 12:25:39
【问题描述】:

我正在构建这样的应用程序:

在表格的每一行内:

  • 用户可以选中或取消选中Option 1Option 2 列中的值。
    之后,All option 列中复选框的值将更新为:
    - “已选中”:如果 Option 1Option 2 都已选中
    - “未选中”:如果 Option 1Option 2 都未选中
    - “不确定”:其他情况

  • 用户可以选中或取消选中 All option 列中的值。
    之后,Option 1Option 2 的值都将根据All option 的当前值进行更新。
    在这种情况下,用户无法将All option 列的值更改为Indeterminate(只能在CheckedUnchecked 之间切换)。

我正在使用 Winforms 中的 DataGridView 实现上述应用程序。以下是我的代码:

public partial class Form1 : Form
    {
        DataTable dataTable;

        public Form1()
        {
            InitializeComponent();

            dataTable = new DataTable();
            dataTable.Columns.Add("Item");
            dataTable.Columns.Add("All option", typeof(CheckState));
            dataTable.Columns.Add("Option 1", typeof(bool));
            dataTable.Columns.Add("Option 2", typeof(bool));
            dataTable.Rows.Add("Item 1", CheckState.Unchecked, false, false);
            dataTable.Rows.Add("Item 2", CheckState.Unchecked, false, false);
            dataGrid.DataSource = dataTable;

        }

        private void dataGrid_CurrentCellDirtyStateChanged(object sender, EventArgs e)
        {
            if (dataGrid.CurrentCell is DataGridViewCheckBoxCell)
            {
                dataGrid.CommitEdit(DataGridViewDataErrorContexts.Commit);
            }
        }

        bool isUnderUpdateAll = false;

        private void dataGrid_CellValueChanged(object sender, DataGridViewCellEventArgs e)
        {
            if ((dataGrid.CurrentCell is DataGridViewCheckBoxCell) == false) return;

            // This is column "All option"
            if (e.ColumnIndex == 1)
            {
                // Avoid stackoverflow exception
                if (isUnderUpdateAll) return;

                CheckState allOption = (CheckState)dataGrid["All option", e.RowIndex].Value;
                
                // Don't allow user select 'indeterminate' value
                if (allOption == CheckState.Indeterminate)
                {
                    allOption = CheckState.Unchecked;

                    // These code didn't work
                    dataTable.Rows[e.RowIndex]["All option"] = CheckState.Unchecked;
                    dataGrid["All option", e.RowIndex].Value = CheckState.Unchecked;
                    dataGrid.UpdateCellValue(e.ColumnIndex, e.RowIndex);
                    dataGrid.NotifyCurrentCellDirty(true);
                    dataGrid.Refresh();
                    dataGrid.Update();

                }

                dataGrid["Option 1", e.RowIndex].Value = allOption;
                dataGrid["Option 2", e.RowIndex].Value = allOption;
            }

            // This is column "Option 1" or "Option 2"
            else
            {
                bool option1 = (bool)dataGrid["Option 1", e.RowIndex].Value;
                bool option2 = (bool)dataGrid["Option 2", e.RowIndex].Value;

                isUnderUpdateAll = true;
                dataGrid["All option", e.RowIndex].Value = (option1 && option2) ? CheckState.Checked 
                    : (!option1 && !option2 ? CheckState.Unchecked : CheckState.Indeterminate );
                isUnderUpdateAll = false;
            }
        }
    }

代码似乎有效,但有 1 点不完整:用户仍然能够在 All option 列中切换到 Indeterminate 状态。在我的代码中,我已经添加了许多类似的内容:

// Don't allow user select 'indeterminate' value
if (allOption == CheckState.Indeterminate)
{
    allOption = CheckState.Unchecked;

    // These code didn't work
    dataTable.Rows[e.RowIndex]["All option"] = CheckState.Unchecked;
    dataGrid["All option", e.RowIndex].Value = CheckState.Unchecked;
    dataGrid.UpdateCellValue(e.ColumnIndex, e.RowIndex);
    dataGrid.NotifyCurrentCellDirty(true);
    dataGrid.Refresh();
    dataGrid.Update();

}

但是这些代码不起作用。当点击All option复选框时,状态仍然在Checked -> indeterminate -> Unchecked -> Checked之间切换> -> ...
那么,有人可以帮助建议如何从上面删除 indeterminate 状态吗?
我希望它是:Checked -> Unchecked -> Checked -> Unchecked ...

【问题讨论】:

    标签: c# winforms datagridview


    【解决方案1】:

    尝试将CheckBox.ThreeState Property 设置为false。

    ((DataGridViewCheckBoxCell)(dataGrid.CurrentCell)).ThreeState = false;
    

    【讨论】:

      【解决方案2】:

      我不确定是否同意@Kyle Wang 的解决方案。当您将当前单元格的ThreeState 设置为false 时,基本上这将不允许“不确定”状态。这是用户单击“所有选项”复选框时您想要的行为,但这不是您在“选项 1”或“选项 2”复选框上更改时想要的行为。

      假设用户单击“所有选项”复选框并将其值更改为true。此时单元格“ThreeState”设置为false,因此Indeterminate 状态不是一个选项。稍后,用户更改“选项 1”或“选项 2”复选框,其中一个是 true,另一个是 false... 然后,代码将不得不更改“所有选项”单元格的 ThreeState 属性返回true,以便根据您的要求将其设置为Indeterminate 状态。我可以看到一个噩梦,跟踪每个“所有选项”单元格的状态。

      此外,您可能需要小心选择“哪些”事件来执行某些操作。例如,代码似乎正在连接两个网格事件:CurrentCellDirtyStateChangedCellValueChanged。这是完全有效的并且会起作用,但是它可能有助于了解这些事件的触发频率。我没有检查这一点,因为我有信心,如果您显示事件触发的次数,您会发现它们触发的次数比您预期的要多得多。在下面的示例中,在测试事件时,它有助于直观地查看事件触发的次数。我建议您在当前代码中尝试此操作。

      鉴于此,显然“最佳”事件是网格CellValueChanged 事件。不幸的是,在用户“离开”单元格之前,该事件不会触发。使用复选框,我们希望在用户单击复选框时立即触发事件。一个可能对此有所帮助的事件是网格CellContentClick 事件。一旦用户点击“复选框”,该事件就会触发。请记住,如果用户单击复选框“单元格”而不是“复选框”本身,它将不会触发。

      使用此事件的一个问题是它何时触发。在网格中实际更改复选框“值”之前触发它。幸运的是,由于我们知道它是一个复选框单元格,我们可以继续“提交”网格更改。然后我们将得到单元格的实际最终值。

      如果您创建一个新的“winform”项目,将DataGridView 和多行TextBox 拖放到表单上(如下所示),您应该能够测试下面的代码。

      我将所有初始化代码移到表单Load 事件中,但是除了添加了几个不同的行之外,代码是相同的。

      遍历网格CellContentClick 事件:代码将文本添加到文本框以显示事件已触发(如前所述)。然后代码“提交”单元格中的更改。然后使用if 语句检查更改的单元格是“所有选项”列还是“选项”列之一。如果更改的单元格位于“所有选项”列中,则变量 allOption 设置为“所有选项”单元格的 CheckState 值,选中、未选中或不确定。

      接下来,allOption 变量上的 switch 语句用于每个状态,选中、未选中或不确定。如果状态更改为truefalse,则代码只需将两个“选项”更改为相同的状态。显然,没有必要更改“All option”状态。

      如果“所有选项”单元格的当前状态为Indeterminate,则表示一个选项为真,另一个为假。此“不确定”状态由其他选项复选框之一设置。哪个单元格是truefalse 无关紧要,代码只是将所有状态更改为未选中。笔记;由于我们正在更改代码中的“所有选项”单元格,并且,我们知道该单元格已经提交到 Indeterminate 状态(我们不想要),我们需要刷新网格中的编辑以将单元格更改为“未选中”,不要将其留在“不确定”。

      接下来在else 部分检查更改的单元格是否是“选项”单元格之一,如果是,则代码仅根据选项值设置“所有选项”复选框值.此处代码可以将“所有选项”单元格设置为“不确定”状态。

      private void Form1_Load(object sender, EventArgs e) {
        dataTable = new DataTable();
        dataTable.Columns.Add("Item");
        dataTable.Columns.Add("All option", typeof(CheckState));
        dataTable.Columns.Add("Option 1", typeof(bool));
        dataTable.Columns.Add("Option 2", typeof(bool));
        dataTable.Rows.Add("Item 1", CheckState.Unchecked, false, false);
        dataTable.Rows.Add("Item 2", CheckState.Checked, true, true);
        dataTable.Rows.Add("Item 3", CheckState.Indeterminate, false, true);
        dataTable.Rows.Add("Item 4", CheckState.Indeterminate, true, false);
        dataGrid.DataSource = dataTable;
      }
      
      private void dataGrid_CellContentClick(object sender, DataGridViewCellEventArgs e) {
        textBox1.Text += "CellContentClick" + Environment.NewLine;
        dataGrid.CommitEdit(DataGridViewDataErrorContexts.Commit);
        if (dataGrid.Columns[e.ColumnIndex].Name == "All option") {
          var allOption = (CheckState)dataGrid["All option", e.RowIndex].Value;
          switch (allOption) {
            case CheckState.Unchecked:
              dataGrid["Option 1", e.RowIndex].Value = CheckState.Unchecked;
              dataGrid["Option 2", e.RowIndex].Value = CheckState.Unchecked;
              break;
            case CheckState.Checked:
              dataGrid["Option 1", e.RowIndex].Value = CheckState.Checked;
              dataGrid["Option 2", e.RowIndex].Value = CheckState.Checked;
              break;
            case CheckState.Indeterminate:
              dataGrid["Option 1", e.RowIndex].Value = CheckState.Unchecked;
              dataGrid["Option 2", e.RowIndex].Value = CheckState.Unchecked;
              dataGrid["All option", e.RowIndex].Value = CheckState.Unchecked;
              // we changed the current All option cell so we need to refresh the edit
              dataGrid.RefreshEdit();
              break;
          }
        }
        else {
          if (dataGrid.Columns[e.ColumnIndex].Name == "Option 1" ||
              dataGrid.Columns[e.ColumnIndex].Name == "Option 2") {
            var op1Checked = (bool)dataGrid["Option 1", e.RowIndex].Value;
            var op2Checked = (bool)dataGrid["Option 2", e.RowIndex].Value;
            if (op1Checked && op2Checked) {
              dataGrid["All option", e.RowIndex].Value = CheckState.Checked;
            }
            else {
              if (!op1Checked && !op2Checked) {
                dataGrid["All option", e.RowIndex].Value = CheckState.Unchecked;
              }
              else {
                dataGrid["All option", e.RowIndex].Value = CheckState.Indeterminate;
              }
            }
          }
        }
      }
      

      我希望这是有道理的。

      【讨论】:

      • 嗨@JohnG,我认为您的解决方案应该比设置ThreeState = false 更直接。我已经测试了代码,它似乎有效。但是,当我们很快(变成双击)点击All optionOption 1Option 2时,它不会按预期工作。
      • 我通过将事件this.dataGrid.CellContentDoubleClick 设置为相同的方法this.dataGrid_CellContentClick 解决了我的上述评论,一切正常。
      • 我不会争辩说,当用户“快速”多次单击复选框时,它可能会搞砸。我认为问题更多在于快速单击并且计算机无法跟上代码而不是代码。我打赌其他(点击类型)事件也会有类似的不良行为。我将检查可以快速解决此问题的解决方案,更改复选框。
      猜你喜欢
      • 2012-11-29
      • 2014-09-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-27
      • 1970-01-01
      相关资源
      最近更新 更多