Search code examples
c#winformsdatagridviewdatatablerow-number

Add row number column to DataGridView bound to DataTable


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.


Solution

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