1

In ADO.NET, I’m using a DataAdapter.Fill (..) call to fill a DataTable with values from a database. Then I bind the DataTable to a DataGrid to allow scrolling through all the values, editing, and updating changes back to the database. All standard stuff. I use the Visual Studio Windows Forms wizards for all of it since it is so standard.

But now I want to display a left-side column (not part of the database row) to number the rows that show up in the DataGrid. I would still like to keep the bound row data as is, with auto-updating back to the database and so on. Obviously, I can’t do this with wizards.

METHOD 1

If I was doing it manually, I would change the DataTable load query to include a dummy integer column to inject the desired row number column into the returned table:

SELECT ‘’ as Num, * from MyTable

Then I would programmatically insert row numbers into that field before binding the DataTable to the grid, which would show the row numbers as desired. I would hope that the automatic updating code for the DataTable would just ignore the extra column in the grid.

METHOD 2

Another possible way is to programmatically add a new column to the DataGrid after it is bound to the DataSource (my DataTable). Then I would populate the new column with row numbers before the grid was displayed.

QUESTION

But before I abandon the convenient wizards and do the manual work for everything, I wanted to ask if there is a standard way of doing this sort of thing. I cannot believe I’m the first person to want to use row numbers (not part of the database row) in a grid display.

I have searched here and on various other forums for ideas, but none of them talk about what happens to the updating code when you inject new columns into the table loading query (method 1) or into the grid after you bind the DataGrid to the Datasource (method 2).

I even thought of using two adjacent grid controls fed from two different binding sources. But the code required to keeping them synchronized during scrolls seems like even more work.

Could anyone point me to the best way to solve this problem or provide a code snippet? I’m OK with going into the form designer-generated code to add a column to the bound DataGrid, but I get lost in trying to find and understand the updating part that updates changes back into the database. Thank you.

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
Kevin
  • 1,548
  • 2
  • 19
  • 34
  • You might be able add a row number to the db query result using SQL depending on the actual DB (you didnt say). Failing that you *can* have unbound columns in the datatable. You could add such a column and fill in the result in a row painting event. In that way, you woulnt have the headache of keeping two different things in synch. Note that if it is `WinForms` is almost certainly a `DataGridView` not a `DataGrid`. using the keywords `Datagridview row number c#` returns 61,000 hits on this site alone – Ňɏssa Pøngjǣrdenlarp Nov 17 '19 at 21:34
  • Thank you for your help. Sorry I didn't say I'm using MS Access and Windows DataGridViews and an Infragistics UltraGrid (but my question was more general). Yes, I am painfully aware of the large number of Internet hits on various search expressions. I'm sure I have investigated more than 100. Yes, I knew that I could add unbound columns to a grid, but the key issue for me was whether doing so would affect the db updates on the bound columns only. – Kevin Nov 17 '19 at 22:50
  • 1
    I'm pretty sure MS Access doesnt support Row Number but that doesnt prevent you from adding the column via the SQL SELECT statement and updating the value in the PrePaint event. The basic DB Provider objects are certainly smart enough to not be confused by an artificial column even if you manually add it. All you have to do is try it to see if the "wizards" balk or not – Ňɏssa Pøngjǣrdenlarp Nov 18 '19 at 00:14
  • 1
    see: [DataGridViewRow.HeaderCell Property](https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.datagridviewrow.headercell?view=netframework-4.8#examples) – TnTinMn Nov 18 '19 at 01:28
  • Thank you recent posters! One of you has asserted that the data update objects won't get confused by additional columns that are not part of the underlying DataTable . And one of you pointed out that I can show row numbers in the grid itself by setting the HeaderCell.Value text. Both great answers to my question. – Kevin Nov 18 '19 at 05:22
  • Possible duplicate of [Show row number in row header of a DataGridView](https://stackoverflow.com/questions/9581626/show-row-number-in-row-header-of-a-datagridview) – TnTinMn Nov 18 '19 at 14:28

2 Answers2

2

There are good options which don't interfere in the query or structure of data and are just based on GUI logic:

  • Using RowPostPaint event to Draw Row Number on RowHeader
  • Using RowPrePaint event to assign Row Number to HeaderCell of the row
  • Creating a new DataGridViewRowNumberColumn to show Row Number

Using RowPostPaint event to Draw Row Number on RowHeader

You can handle RowPostPaint and draw the row number in header cell. The following piece of code shows a simple logic for that:

private void dataGridView1_RowPostPaint(object sender, DataGridViewRowPostPaintEventArgs e)
{
    var g = (DataGridView)sender;
    var r = new Rectangle(e.RowBounds.Left, e.RowBounds.Top,
        g.RowHeadersWidth, e.RowBounds.Height);
    TextRenderer.DrawText(e.Graphics, $"{e.RowIndex + 1}",
        g.RowHeadersDefaultCellStyle.Font, r, g.RowHeadersDefaultCellStyle.ForeColor);
}

Above code is good enough, however you may want to enhance the logic by mapping datagridview cell alignment to text format flags like this method. You may also want to put the logic inside a custom DataGridView and override OnRowPostPaint and also set DoubleBuffered property if the derived DataGridView to true.

Using RowPrePaint event to assign Row Number to HeaderCell of the row If you assign a string value to Value property of the header cell, it will be displayed on the row header.

You assign values in a loop, in this case you need to handle RowAdded and RowRemoved events and re-assign the row number. A better solution is using RowPrePaint and check if the value of the header cell is not correct, then correct it:

private void dataGridView1_RowPrePaint(object sender, DataGridViewRowPrePaintEventArgs e)
{
    var g = (DataGridView)sender;
    if (e.RowIndex > -1 && $"{g.Rows[e.RowIndex].HeaderCell.Value}" != $"{e.RowIndex + 1}")
    {
        g.Rows[e.RowIndex].HeaderCell.Value = $"{e.RowIndex + 1}";
    }
}

Creating a new DataGridViewRowNumberColumn to show Row Number

The second option is creating a new reusable column type to show row number. You can work with this column type like any other column types. You can add the column at design-time or run-time.

using System.ComponentModel;
using System.Windows.Forms;
public class DataGridViewRowNumberColumn : DataGridViewColumn
{
    public DataGridViewRowNumberColumn() : base()
    {
        this.CellTemplate = new DataGridViewRowNumberCell();
        this.Width = 40;
        this.SortMode = DataGridViewColumnSortMode.NotSortable;
    }
    [Browsable(false)]
    [DefaultValue(true)]
    public override bool ReadOnly
    {
        get { return true; }
        set { base.ReadOnly = true; }
    }
}
public class DataGridViewRowNumberCell : DataGridViewTextBoxCell
{
    protected override void Paint(System.Drawing.Graphics graphics,
        System.Drawing.Rectangle clipBounds, System.Drawing.Rectangle cellBounds,
        int rowIndex, DataGridViewElementStates cellState, object value,
        object formattedValue, string errorText, DataGridViewCellStyle cellStyle,
        DataGridViewAdvancedBorderStyle advancedBorderStyle,
        DataGridViewPaintParts paintParts)
    {
        base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, value,
            formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts);
    }
    protected override object GetValue(int rowIndex)
    {
        return rowIndex + 1;
    }
    protected override bool SetValue(int rowIndex, object value)
    {
        return base.SetValue(rowIndex, rowIndex + 1);
    }
}
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • Wow, what an awesome answer! (And with code snippets too.) You're obviously a master at this. Thank you! - Kevin – Kevin Nov 19 '19 at 03:03
-1

See: DataGridViewRow.HeaderCell Property – TnTinMn

TnTinMn provided the easiest answer that would accomplish my goal. I copied his comment here so that I could mark it as an answered question.

Once the grid is loaded, just walk down the rows and assign a row label (the row number) to the "header cell" at the left end of the row. The nice part is that the row number is a label in the grid, rather than being a column of unbound data in the grid.

Kevin
  • 1,548
  • 2
  • 19
  • 34
  • If user can add or remove rows, then you need to handle row added and row removed events and assign new row number to header cells. – Reza Aghaei Nov 18 '19 at 07:00