Search code examples
c#eventsdatagridviewrowdatagridrow

Datagridview rowsadded event fires before bindsource completed


First of all what i have:

I got a datagridview and bindsource (currently my own datatable from an sql-call). The bindsource has multiple columns, but one is a specific "position" column for my data.

What i want to do:

When i set the bindsource to the datagridview i want to change the color of a row with a specified "position".

What i did:

I thought it would be enough to create a RowsAdded event and within check if the "position"-column of the added rows equals my specified position. Based on that i would have changed the Backcolor of the row.

The code would be this:

private void setBindSource(BindingSource bindSource)
{
   gridview.DataSource = bindSource;
   gridview.EndEdit();
   bindSource.EndEdit();
}

private void gridView_RowsAdded(object sender, System.Windows.Forms.DataGridViewRowsAddedEventArgs e)
{
   if (e.RowIndex == -1 || e.RowCount == 0)
   {
      return;
   }

   for (int index = e.RowIndex; index <= e.RowIndex + e.RowCount - 1; index++)
   {
      DataGridView dgv = sender as DataGridView;
      DataGridViewRow row = dgv.Rows[index];
   
      if ((Int32)row.Cells["position"].Value == specificPosition)
         row.DefaultCellStyle.BackColor = Color.Green;
   }
}

The problem i'm facing:

The rowsadded event fires as expected (at the gridview.DataSource = bindSource;). But the row itself does not have any data in it (the used datatable shows the expected data). So it obviously throws the error, that it can't find the specified column "position". I'm assuming that the gridview isn't fully initialized yet?

How would i be able to change the color of the row when it is added?


Solution

  • When a row’s back ground color depends on a specific cells value, there are a few things you should keep in mind when “changing” the rows color.

    “Where” the code is located to test the specific cell value and change the row’s color can be done in many different grid events and all will (basically) accomplish the same thing. However, it may be beneficial to do some simple tests to help you determine if the “grid event” you choose is “REALLY” the one you need or should use.

    Example, as suggested, you can subscribe to the grids CellFormatting event to do what you describe and it will work as expected. However, there is a subtle yet possible problematic issue(s) that using this event creates. For starters, just about all the grid “formatting” events will fire many many times.

    If the user simply moves the cursor over the grid, then, the formatting event will fire. Therefore, if the cursor just so happens to move over the target cell, the code is executed. This may be acceptable; however, it is clear from this that checking the cells value is really unnecessary since the cells value has not changed. The user simply moved the cursor over the target cell.

    As previously stated, this MAY be acceptable, however, if this same unnecessary calling of an event is compounded, it is not difficult to see where many combinations of this situation could impact the UI’s performance and the user may experience a sluggish UI.

    The main idea here… is to NOT place your code in an event where the event will get fired unnecessarily, and this is a good example. It may work, but the code is “creating” extra and unnecessary execution steps.


    Given this, your attempt to wire up the grids RowsAdded event may well work. However, as you noted, there seems to be a need to do something different when the data is “initially” loaded and when the user manually adds a new row or when the code adds new rows. When the data is “initially” loaded, you are correct that some cells may not yet be initialized. At least the cell your code is looking at.

    This can be fixed, however, using the RowsAdded event leaves out one other senario…

    _“what if the user CHANGES a cells value of a ‘’position’’ cell?”

    The new rows event is not going to fire in that scenario. If the user changes a “position” cell to the value of the specifiedPosition, then, that row’s background color will not get changed.

    Given all this, to help, it is best to pin point “WHEN” we want the code to run. And from what I can see, this would be “WHEN” the value in a cell that is in the position column changes. Therefore, I suggest you subscribe to the grids CellValueChanged event. If the cell that “changed” is a “position” cell, then test the cells value and color the row accordingly.

    This will minimize the number of calls to the code unnecessarily and will fix the previously described problem when the user “changes” a cells value. This will also work when the “user” adds a new row. Unfortunately, this event will not fire when the data is initially loaded OR when new rows are added through code.

    Given this, one possible solution to color the rows after the grid has been initially loaded with data in addition to setting the proper row color for newly added rows in code, a couple of small methods may come in handy.

    The first method would simply color one (1) given row depending on what value the ‘position” cell has. This assumes the given row is not null and the position column exists and it is a valid int.

    private void ColorRow(DataGridViewRow row) {
      if ((int)row.Cells["position"].Value == specificPosition) {
        row.DefaultCellStyle.BackColor = Color.LightGreen;
      }
      else {
        row.DefaultCellStyle.BackColor = Color.White;
      }
    }
    

    We can use the ColorRow method in the grids CellValueChanged event to color that specific row. In addition, in the forms load event, after the data has be initially set into the grid, then we can call an additional method that simply loops through all the grid’s rows and calls the ColorRow method above. This ColorSpecificRows method may look something like…

    private void ColorSpecificRows() {
      foreach (DataGridViewRow row in gridView.Rows) {
        if (!row.IsNewRow) {
          ColorRow(row);
        }
      }
    }
    

    Next, all that is left is to subscribe to the grids CellValueChanged event and it may look something like…

    private void gridView_CellValueChanged(object sender, DataGridViewCellEventArgs e) {
      ColorRow(gridView.Rows[e.RowIndex]);
    }
    

    To help test this a complete example is below. Create a new win form solution, add a DataGridView and a Button to the form. The button is used to demonstrate adding rows programmatically.

    DataTable GridTable;
    BindingSource GridBS;
    Random rand = new Random();
    int specificPosition = 2;
    int formattingCount = 0;
    
    public Form1() {
      InitializeComponent();
    }
    
    private void Form1_Load(object sender, EventArgs e) {
      GridTable = GetTable();
      FillTable(GridTable);
      GridBS = new BindingSource(GridTable, null);
      gridView.DataSource = GridBS;
      ColorSpecificRows();
    }
    
    private DataTable GetTable() {
      DataTable dt = new DataTable();
      dt.Columns.Add("Col0", typeof(string));
      dt.Columns.Add("position", typeof(int));
      dt.Columns.Add("Col2", typeof(string));
      return dt;
    }
    
    private void FillTable(DataTable dt) {
      for (int i = 0; i < 10; i++) {
        dt.Rows.Add("C0R" + i, rand.Next(1, 4), "C2R" + i);
      }
    }
    
    private void button1_Click(object sender, EventArgs e) {
      GridTable.Rows.Add("new0", 2, "new0");
      GridTable.Rows.Add("new1", 3, "new0");
      GridTable.Rows.Add("new2", 2, "new0");
      ColorSpecificRows();
    }
    

    Lastly, for testing purposes, you can wire up the grids CellFormatting event to demonstrate how often this even is called.

    private void gridView_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) {
      Debug.WriteLine("CellFormatting - Enter -- FormattingCount: " + ++formattingCount);
       // code to change row color
      Debug.WriteLine("CellFormatting - Leave");
    }
    

    I hope this makes sense.