Fabius Wiesner
Fabius Wiesner

Reputation: 926

Current cell cannot be set to an invisible cell in DataGridView WITHOUT DataSource

I have a dgv C# DataGridView built without DataSource.

I have a column with index c that is set to invisible when the value of a cell of another column changes to a certain value (within the CellValueChanged event handler):

dgv.Columns[c].Visible = false;

The CellValueChanged event handler is called after modifying the cell and then clicking on another cell. The problem is that when I click on the column which is about to be set to invisible I get the exception "Current cell cannot be set to an invisible cell", whereas everything is fine if I click on one of the other columns.

I read some other answers (e.g. this or this) where using CurrencyManager.SuspendBinding and CurrencyManager.ResumeBindingis suggested. However that doesn't work for me because the DataSource is null.

Any hint?

Thank you.

Upvotes: 2

Views: 1065

Answers (1)

JohnG
JohnG

Reputation: 9469

The problem with setting the column to “not visible” in the grids CellValueChanged event is that while the scope of execution is in the CellValueChanged event, the grid is NOT going to give you ANY information about “where” the user clicked/moved “to” after the cells value changed.

This alone is ALWAYS going to leave hiding the column (in that event) prone to errors since we do not know “where” the user clicked/moved. Because of this it would appear obvious that if we want to avoid the error we are going to have to “hide” the column in some other event.

Another issue you may run into is that some events don’t like it when you try to “change” the grids “selected” cell, which is what we are looking to do when the user clicks on the column we want to make invisible. We need to “change” the selected cell to some other cell to avoid the error.

Given this, below is a hacky approach that may help. Be aware that there was NOT a lot of testing on the code and one issue/error occurred when the user clicked on the column header (to sort) for the first column. This caused a crash and I ended up turning the “sorting” off for that column.

The small example below uses this logic... A DataGridView with four (4) text columns is set on a form and filled with some data. ConditionColumn (column 0) in the grid is the “condition” column and it has string values from 0-19. It is this column that will trigger “when” to “hide” the HideColumn (column 2).

IF a value greater than twenty (20) is entered into any cell in the ConditionColumn, THEN the HideColumn will be set as invisible. There is a small function AllCellsLessThan20 that loops through all the rows in the grid and checks the ConditionColumn cells for any values that are greater than 20. If at least one cell value is greater than 20, then the function will return false. This is used to turn the hidden column on or off such that if ALL cells in the ConditionColumn are less than 20, then the column will be shown. Otherwise, if one or more cells is greater than 20, then the column will be hidden.

To avoid the error described, the code will set the column to invisible in the grids SelectionChanged event. This event will fire “after” the CellValueChanged event if the user selects another cell by any means… click, tab or arrow keys. We will check several things to determine if we need to hide or show the column.

Obviously since this event is called AFTER the CellValueChange event fired, the grid is not going to give us ANY information about “where” the previously selected cell was. Hence, the global variables. Setting these global values in the CellValueChanged event will make them available in the SelectionChanged event.

Maybe some variables like: ColumnShown : bool to indicated if the column is currently hidden or shown. ValueGreaterThan20: bool to indicate if any value in the ConditionColumn is greater than 20 and PreviousRow : int to indicate the row index of the last “Changed” cell.

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

Set the grids columns with some useful names to avoid any indexing problems, then fill the grid with some test data.

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 is a function to check if any value in the ConditionColumn is greater than 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;
}

Next, is the grids CellValueChanged event. Here we only need to check two things: 1) was the value changed in column 0 ConditionColumn and since a value did change in that column we need to check ALL the values. If any value is over 20, then set the variable ValueGreaterThan20 to true otherwise false. Also, we will save the row index (PreviousRow) of the changed cell since we will want to use it in the SelectionChanged event.

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

The SelectionChanged event is below. I am aware this could be more compact. First a check is made to see if the column is shown or hidden.

If the column is already “hidden”, then we just need to check to make sure at least one value in the ConditionColumn is greater than 20 and simply leave it hidden. If all values are less than 20 then we want to un-hide the column.

If the column is NOT “hidden,” then we check to see if any values are greater than 20. If no values are greater than 20, then simply leave the column visible.

If the column is NOT “hidden,” and one or more values are greater than 20, then we need to check to see if the current selection is the column we want to hide. If the current selection is NOT the column we want to hide, then simply hide it since we know this will not throw an error.

Lastly, if the column is NOT “hidden” AND there is a value greater than 20 AND the current selection IS the column we want to hide. In this case the code will set/change the CurrentCell to the first cell in the PreviousRow. Then set the column to invisible and set the global variable ColumnShown.

In my test, if the user changes a cell in column 0 to a value grater than 20 then “Clicks” on a cell in column 2 HideColumn, then the error does not pop up and the selection changes to the first cell in the previous row.

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

As I said its hacky but it works with some caveats. Hope this helps!

Upvotes: 2

Related Questions