【问题标题】:Current cell cannot be set to an invisible cell in DataGridView WITHOUT DataSource当前单元格不能设置为 DataGridView 中没有数据源的不可见单元格
【发布时间】:2020-02-15 04:41:10
【问题描述】:

我有一个 dgv C# DataGridView 构建没有 DataSource。

我有一个索引为c 的列,当另一列的单元格的值更改为某个值时(在CellValueChanged 事件处理程序中),该列设置为不可见:

dgv.Columns[c].Visible = false;

在修改单元格然后单击另一个单元格后调用CellValueChanged 事件处理程序。问题是,当我单击即将设置为不可见的列时,我得到异常“当前单元格不能设置为不可见的单元格”,而如果我单击其他列之一,一切都很好。

我阅读了一些其他答案(例如thisthis),建议使用CurrencyManager.SuspendBindingCurrencyManager.ResumeBinding。但是这对我不起作用,因为 DataSource 为空。

有什么提示吗?

谢谢。

【问题讨论】:

  • 阅读链接时,如果选择了单元格/行/列,您似乎无法使其不可见。所以选择另一列或取消选择该列。
  • 这个要求需要更多细节。当单元格值更改为特定值时,“永远”隐藏一列似乎很奇怪。如果用户将单元格值“更改”回来怎么办?你想让隐藏的列重新出现吗?这将有助于澄清隐藏列需要“什么条件”,以及是否存在您想要“取消隐藏”隐藏列的情况。
  • 我有一个 hacky 方法(我可以发布)来(在某种程度上)使用网格 SelectionChanged 事件和 CellValueChanged 事件来解决这个问题。然而,这只揭示了更多类似上述的问题。另一个是如果用户在将单元格值更改为特定值后“确实”单击隐藏列,您希望“选择”去哪里。
  • 最后,在CellValueChanged 事件中将列设置为“隐藏”时遇到的主要问题是……在代码中,网格不会给你有关用户移动到“何处”的任何信息。
  • 因此,由于您(当时)不可能知道用户单击或“移动”选择的“位置”……在该事件中隐藏该列总是容易出现您所遇到的错误得到。这就是为什么您可能必须设置一些“全局变量”来简单地“指示”满足条件,然后在知道用户移动到哪里之后“隐藏”列。

标签: c# .net datagridview


【解决方案1】:

在网格CellValueChanged 事件中将列设置为“不可见”的问题在于,虽然执行范围在CellValueChanged 事件中,但网格不会为您提供有关“位置”的任何信息单元格值更改后,用户单击/移动“到”。

仅此一项总是会导致隐藏列(在那种情况下)容易出错,因为我们不知道用户点击/移动的“位置”。因此,很明显,如果我们想避免该错误,我们将不得不在其他事件中“隐藏”该列。

您可能遇到的另一个问题是,当您尝试“更改”网格“选定”单元格时,某些事件不喜欢它,这是我们希望在用户单击我们想要的列时执行的操作使隐形。我们需要将选定的单元格“更改”为其他单元格以避免错误。

鉴于此,以下是一种可能会有所帮助的 hacky 方法。请注意,没有对代码进行大量测试,并且当用户单击第一列的列标题(进行排序)时发生了一个问题/错误。这导致了崩溃,我最终关闭了该列的“排序”。

下面的小示例使用此逻辑... 一个带有四 (4) 个文本列的 DataGridView 被设置在一个表单上并填充了一些数据。网格中的ConditionColumn(第 0 列)是“条件”列,它的字符串值范围为 0-19。正是这一列将触发“何时”“隐藏”HideColumn(第 2 列)。

如果在ConditionColumn, 的任何单元格中输入了大于二十 (20) 的值,那么HideColumn 将被设置为不可见。有一个小函数 AllCellsLessThan20 循环遍历网格中的所有行并检查 ConditionColumn 单元格中是否有任何大于 20 的值。如果至少有一个单元格值大于 20,则该函数将返回 false .这用于打开或关闭隐藏列,这样如果ConditionColumn 中的所有单元格都小于 20,则将显示该列。否则,如果一个或多个单元格大于 20,则该列将被隐藏。

为避免上述错误,代码会将列设置为在网格SelectionChanged 事件中不可见。如果用户以任何方式选择另一个单元格……单击、制表键或箭头键,此事件将在 CellValueChanged 事件“之后”触发。我们将检查几件事以确定是否需要隐藏或显示该列。

显然,因为这个事件是在CellValueChange 事件触发之后调用的,所以网格不会给我们任何关于之前选择的单元格“在哪里”的信息。因此,全局变量。在CellValueChanged 事件中设置这些全局值将使它们在SelectionChanged 事件中可用。

也许是一些变量,例如: ColumnShownbool 指示列当前是隐藏还是显示。 ValueGreaterThan20bool 表示ConditionColumn 中的任何值是否大于 20 和 PreviousRow : int 表示最后一个“已更改”单元格的行索引。

bool ColumnShown = true;
bool ValueGreaterThan20 = false;
int PreviousRow = 0;

为网格列设置一些有用的名称以避免任何索引问题,然后用一些测试数据填充网格。

public Form1() {
  InitializeComponent();
  dataGridView1.Columns[0].Name = "ConditionColumn";
  dataGridView1.Columns[0].HeaderText = "Condition";
  dataGridView1.Columns[2].Name = "HideColumn";
  dataGridView1.Columns[2].HeaderText = "Hide Column";
}

private void Form1_Load(object sender, EventArgs e) {
  FillGrid();
}

private void FillGrid() {
  for (int i = 0; i < 20; i++) {
    dataGridView1.Rows.Add(i, "C1R" + i, "C2R" + i, "C3R" + i);
  }
}

Next 是检查ConditionColumn 中是否有任何值大于20 的函数。

private bool AllCellsLessThan20() {
  foreach (DataGridViewRow row in dataGridView1.Rows) {
    if (row.Cells[0].Value != null) {
      string sValue = row.Cells["ConditionColumn"].Value.ToString();
      if (int.TryParse(sValue, out int value)) {
        if (value > 20) {
          return false;
        }
      }
    }
  }
  return true;
}

接下来是网格CellValueChanged 事件。这里我们只需要检查两件事:1) 列 0 ConditionColumn 中的值是否发生了变化,并且由于该列中的值确实发生了变化,我们需要检查所有值。如果任何值超过 20,则将变量 ValueGreaterThan20 设置为 true,否则设置为 false。此外,我们将保存更改单元格的行索引 (PreviousRow),因为我们希望在 SelectionChanged 事件中使用它。

private void dataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e) {
  if (e.ColumnIndex == 0) {
    if (AllCellsLessThan20())
      ValueGreaterThan20 = false;
    else
      ValueGreaterThan20 = true;
  }
  PreviousRow = e.RowIndex;
}

SelectionChanged 事件如下。我知道这可能更紧凑。首先检查该列是显示还是隐藏。

如果该列已经“隐藏”,那么我们只需要检查以确保ConditionColumn 中至少有一个值大于20,然后将其隐藏。如果所有值都小于 20,那么我们要取消隐藏该列。

如果列不是“隐藏”的,那么我们检查是否有任何值大于 20。如果没有值大于 20,则让列可见。

如果列不是“隐藏”的,并且有一个或多个值大于 20,那么我们需要检查当前选择是否是我们要隐藏的列。如果当前选择不是我们想要隐藏的列,那么只需隐藏它,因为我们知道这不会引发错误。

最后,如果该列没有“隐藏”并且有一个大于 20 的值并且当前选择是我们要隐藏的列。在这种情况下,代码会将CurrentCell 设置/更改为PreviousRow 中的第一个单元格。然后将列设置为不可见,并设置全局变量ColumnShown.

在我的测试中,如果用户将第 0 列中的单元格更改为大于 20 的值,然后“单击”第 2 列中的单元格HideColumn,则不会弹出错误并且选择更改为第一个上一行的单元格。

private void dataGridView1_SelectionChanged(object sender, EventArgs e) {
  if (ColumnShown) {
    if (ValueGreaterThan20) {
      int curColIndex = dataGridView1.CurrentCell.ColumnIndex;
      if (dataGridView1.Columns[curColIndex].Name == "HideColumn") {
        dataGridView1.CurrentCell = dataGridView1.Rows[PreviousRow].Cells["ConditionColumn"];
        dataGridView1.CurrentCell.Selected = true;
      }
      dataGridView1.Columns["HideColumn"].Visible = false;
      ColumnShown = false;
    }
  }
  else {
    if (!ValueGreaterThan20) {
      dataGridView1.Columns["HideColumn"].Visible = true;
      ColumnShown = true;
    }
  }
}

正如我所说,它很老套,但有一些注意事项。希望这会有所帮助!

【讨论】:

  • 这对我有用,谢谢。由于单元格值也可以通过编程方式更改,因此我必须在CellValueChanged 处理程序中检查dgv.Rows[e.RowIndex].Cells[e.ColumnIndex] == dgv.CurrentCell,这意味着更改是手动的,因此必须在SelectionChanged 处理程序中推迟隐藏,否则,对于程序更改,必须立即隐藏。
  • 另一个观察结果是,当dgv.SelectionModeDataGridViewSelectionMode.CellSelect 不同时,最好使用CellEndEdit 而不是SelectionChanged,否则,例如,使用dgv.SelectionMode = DataGridViewSelectionMode.FullRowSelect 它只能工作如果退出已编辑的单元格,则单击另一行。
猜你喜欢
  • 1970-01-01
  • 2014-09-10
  • 1970-01-01
  • 2023-03-31
  • 1970-01-01
  • 2013-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多