Filipe Almeida
Filipe Almeida

Reputation: 113

How to force a "refresh" in a DataGridView

I am a newbie doing my first C# project (with minimal experience in Haskell and C as well) looking for guidance on how to implement a small feature on my program.

I have a DataGridView table (with 3 columns of checkboxes among other stuff) for the user to fill. When in a row, there gets a second checkbox checked, the first one that got checked must be unchecked. I can do this already but the problem is, the first checked one only gets unchecked after I select something else in my table.

Here is the code pertaining to the event of CellValueChanged (What is in the comments is what I've tried to help me)

if (e.ColumnIndex == 0 || e.ColumnIndex == 1 || tabela_NormasDataGridView.Rows.Count == 0)
{
    return;
}

var isChecked = (bool)tabela_NormasDataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value;

if (isChecked)
{
    for (int i = 2; i < 5; i++)
    {
        //Console.WriteLine("og " + e.ColumnIndex);
        DataGridViewCell cell = tabela_NormasDataGridView.Rows[e.RowIndex].Cells[i];
        //Console.WriteLine("segunda " + cell.ColumnIndex);
        if (cell.ColumnIndex != e.ColumnIndex)
        {
            cell.Value = false;
            //this.Refresh();
        }
    }
}

Upvotes: 3

Views: 2130

Answers (2)

JohnG
JohnG

Reputation: 9479

It is wise to use a little caution when wiring up certain events to do certain things. It may not be immediately obvious why a particular event is not necessarily a good "fit" for what you want the code to do IN that event.

In other words, the code works, and does not crash, however looking under the hood may reveal that the code is executing steps you may not have intended. Often this may be harmless and just waste CPU cycles. And it can also lead to a code crash.

A more subtle problem could arise where one of the grid's events waste a large amount of CPU cycles such that application using the grid becomes sluggish. Example; if the grid is anchored into a resizable control and the user resizes the control. The user may see a stutter in the re-drawing of the grid as it is resized.

In your current code, when the line below is executed...

cell.Value = false;

the cell's value will be changed. This appears harmless, however this code is IN the grid's CellValueChanged event... and the code is "changing a cells value." This will cause the event to fire again. And in this case we really do not want this.

LarsTech's solution will work and we will use it in the example below, however, it should be noted, that this solution works with the caveat that it creates extra unnecessary steps during execution.

On possible easy solution…

One possible solution to avoid this re-firing is to “turn off” the grid's CellValueChanged event just before the code sets the cells value to false. Then turn it back "on" after the code changes the value.

Another option using a different grid event…

Another possible (better) solution is to change the event the code is in. This can be done if you wire up the grid's CellContentClick event.

The key advantage to using the CellContnetClick event is that it will fired BEFORE the grid's CellValueChanged event gets fired. This will allow the code to "change" the cell values and they will be immediately updated since the grid's CellValueChanged event will get fired "automatically" when the code changes the value.

A full example...

To help visualize this, an example is below. Drop two DataGridViews onto a Form. The only change that needs to be done is that you will need to change the top grids name to tabela_NormasDataGridView. The columns are added in the code.

The two grids can be seen on the left in the picture below.
In this example, both grids wire up the same events. The wired up events are the CellValueChanged, CurrentCellDirtyStateChanged and the CellContentClick events.

In each event Debug statements are added to output "when" the event is "entered" and "when" the event "exits" (leaves.)

What code is in the Top grid events

  • CellValueChanged - this event contains our code.
  • CurrentCellDirtyStateChanged - this event contains code to "commit" the edits.
  • CellContentClick - this event contains NO code.

What code is in the Bottom grid events

  • CellValueChanged - this event contains NO code.
  • CurrentCellDirtyStateChanged - this event contains NO code.
  • CellContentClick - this event contains our code.

enter image description here

Te replicate, run the code below, click on the top grids check box cell in the "Check2" column. Then do the same in the bottom grid. This should produce the picture above.

Tracing the code in the top grid (Yellow)...

  • We can see the grids CurrentCellDirtyStateChanged event is fired first and the code in that event is committing the edits to the grid. It is entered… however…

    ... We can see that it does NOT immediately "leave" the event. At least not yet. In fact, the event gets re-entered well down the list of debug statements. We can also see this event is fired TWICE.

  • Next, we have SIX (6) calls to the grids CellValueChanged event. Initally the event is re-entered BEFORE it is finished (enter-enter).

    Calling the grids CellValueChanged event SIX (6) times when we only want to change TWO cells, is "creating" unnecessary work. In addition, this opens the code to infinite loop possibilities.

  • After the six calls to the event (where our code is), we can see that the CurrentCellDirtyStateChanged event is re-entered, then the code leaves the event twice.

  • Finally, the grids CellContentClick is fired and leaves immediately as there is no code in the event.

This code appears to “create” a lot of unnecessary calls to the CellValueChanged event... And this is where our code is... hmm.

Tracing the code in the bottom grid (Red)…

  • We can see that the grids CurrentCellDirtyStateChanged event is fired first and immediately exits as there is no code in that event.

  • The CellContentClick event is fired and entered. This is where our code is.

  • BEFORE we leave the CellContnetClick event, the grid's CellValueChanged event is fired (entered/leave) TWICE as there is no code there. The two calls to the grid's CellValueChanged event are coming from our code when the code sets the two cell values to false.

  • Finally, the code leaves the grids CellContentClick event.

This looks much cleaner and clearly is not creating extra steps.

Code that is used in the picture above…

First preliminaries to add the columns and wire up the events.

private void Form1_Load(object sender, EventArgs e) {
  AddCheckBoxColumnsToGrid(tabela_NormasDataGridView);
  tabela_NormasDataGridView.CellValueChanged += new DataGridViewCellEventHandler(tabela_NormasDataGridView_CellValueChanged);
  tabela_NormasDataGridView.CurrentCellDirtyStateChanged += new EventHandler(tabela_NormasDataGridView_CurrentCellDirtyStateChanged);
  tabela_NormasDataGridView.CellContentClick += new DataGridViewCellEventHandler(tabela_NormasDataGridView_CellContentClick);

  AddCheckBoxColumnsToGrid(dataGridView2);
  dataGridView2.CellValueChanged += new DataGridViewCellEventHandler(dataGridView2_CellValueChanged);
  dataGridView2.CurrentCellDirtyStateChanged += new EventHandler(dataGridView2_CurrentCellDirtyStateChanged);
  dataGridView2.CellContentClick += new DataGridViewCellEventHandler(dataGridView2_CellContentClick);
}

private void AddCheckBoxColumnsToGrid(DataGridView dgv) {
  DataGridViewTextBoxColumn txtCol = new DataGridViewTextBoxColumn();
  txtCol.Name = "col0";
  dgv.Columns.Add(txtCol);
  txtCol = new DataGridViewTextBoxColumn();
  txtCol.Name = "col0";
  dgv.Columns.Add(txtCol);
  DataGridViewCheckBoxColumn col = new DataGridViewCheckBoxColumn();
  col.Name = "Check1";
  dgv.Columns.Add(col);
  col = new DataGridViewCheckBoxColumn();
  col.Name = "Check2";
  dgv.Columns.Add(col);
  col = new DataGridViewCheckBoxColumn();
  col.Name = "Check3";
  dgv.Columns.Add(col);
  dgv.Rows.Add(4);
}

Events for the top grid…

private void tabela_NormasDataGridView_CellValueChanged(object sender, DataGridViewCellEventArgs e) {
  Debug.WriteLine("DGV1 - Cell Value Changed -> enter");
  if (e.ColumnIndex == 0 || e.ColumnIndex == 1 || tabela_NormasDataGridView.Rows.Count == 0) {
    return;
  }
  var isChecked = (bool)tabela_NormasDataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value;
  if (isChecked) {
    for (int i = 2; i < 5; i++) {
      DataGridViewCell cell = tabela_NormasDataGridView.Rows[e.RowIndex].Cells[i];
      if (cell.ColumnIndex != e.ColumnIndex) {
        cell.Value = false;
      }
    }
  }
  Debug.WriteLine("DGV1 - Cell Value Changed -> leave");
}

private void tabela_NormasDataGridView_CurrentCellDirtyStateChanged(object sender, EventArgs e) {
  Debug.WriteLine("DGV1 - Current Cell Dirty Cell State Changed -> enter");
  tabela_NormasDataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit);
  Debug.WriteLine("DGV1 - Current Cell Dirty Cell State Changed -> leave");
}

private void tabela_NormasDataGridView_CellContentClick(object sender, DataGridViewCellEventArgs e) {
  Debug.WriteLine("DGV1 - CellContentClick -> enter");
  // Do nothing
  Debug.WriteLine("DGV1 - CellContentClick -> leave");
}

Events for the bottom grid…

private void dataGridView2_CellValueChanged(object sender, DataGridViewCellEventArgs e) {
  Debug.WriteLine("DGV2 - Cell Value Changed -> enter");
  // do nothing
  Debug.WriteLine("DGV2 - Cell Value Changed -> leave");
}

private void dataGridView2_CurrentCellDirtyStateChanged(object sender, EventArgs e) {
  Debug.WriteLine("DGV2 - Current Cell Dirty Cell State Changed -> enter");
  // do nothing
  //dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);
  Debug.WriteLine("DGV2 - Current Cell Dirty Cell State Changed -> leave");
}

private void dataGridView2_CellContentClick(object sender, DataGridViewCellEventArgs e) {
  Debug.WriteLine("DGV2 - Cell Content Click -> enter");
  string colName = dataGridView2.Columns[e.ColumnIndex].Name;
  if (colName == "Check1" || colName == "Check2" || colName == "Check3") {
    int rowIndex = e.RowIndex;
    switch (colName) {
      case "Check1":
        dataGridView2.Rows[rowIndex].Cells["Check2"].Value = false;
        dataGridView2.Rows[rowIndex].Cells["Check3"].Value = false;
        break;
      case "Check2":
        dataGridView2.Rows[rowIndex].Cells["Check1"].Value = false;
        dataGridView2.Rows[rowIndex].Cells["Check3"].Value = false;
        break;
      case "Check3":
        dataGridView2.Rows[rowIndex].Cells["Check1"].Value = false;
        dataGridView2.Rows[rowIndex].Cells["Check2"].Value = false;
        break;
    }
  }
  Debug.WriteLine("DGV2 - Cell Content Click -> leave");
}

I hope this makes sense.

Upvotes: 2

LarsTech
LarsTech

Reputation: 81675

Try committing the change to force the refresh:

void tabela_NormasDataGridView_CurrentCellDirtyStateChanged(object sender, EventArgs e) {
    tabela_NormasDataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit);
}

Make sure you wire the event up.

Upvotes: 5

Related Questions