19

This is my code:

private void getData(string selectCommand) 
{
    string connectionString = @ "Server=localhost;User=SYSDBA;Password=masterkey;Database=C:\data\test.fdb";

    dataAdapter = new FbDataAdapter(selectCommand,
        connectionString);
    
    DataTable data = new DataTable();
    dataAdapter.Fill(data);
    bindingSource.DataSource = data;
}

private void button1_Click(object sender, EventArgs e) 
{
    getData(dataAdapter.SelectCommand.CommandText);
}

private void Form1_Load(object sender, EventArgs e) 
{
    dataGridView1.DataSource = bindingSource;
    getData("SELECT * FROM cities");
}

After reload data on button1 click event, cell selection jumps on first column and scrollbars is reset. How to save position of DataGridView?

spaleet
  • 838
  • 2
  • 10
  • 23
bobik
  • 201
  • 1
  • 2
  • 4

11 Answers11

38

Here's the solution I came up with. Doesn't require a row to be selected and puts the scrollbar back in the same area after the refresh, provided that the number of rows doesn't vary greatly.

int saveRow = 0;
if (dataGridView1.Rows.Count > 0 && dataGridView1.FirstDisplayedCell != null)
    saveRow = dataGridView1.FirstDisplayedCell.RowIndex;

dataGridView1.DataSource = dataTable1;

if (saveRow != 0 && saveRow < dataGridView1.Rows.Count)
    dataGridView1.FirstDisplayedScrollingRowIndex = saveRow;
Chris Arbogast
  • 113
  • 1
  • 7
ovinophile
  • 793
  • 1
  • 9
  • 20
  • Exactly what I needed, looks like the answer to me :) – Jacco May 02 '14 at 12:25
  • @ovinophile how to achieve this mate with datagrid ? not with datagridview? – Roxy'Pro Sep 30 '16 at 10:54
  • I tried this one and put it onto a 100ms timer that contains my datagridview reloading from database and this code which saves the row I am in... And I am having a null reference error... How can I fix that? – Lucifer Rodstark Dec 11 '17 at 20:59
5
int FirstDisplayedScrollingRowIndex = this.dgvItems.FirstDisplayedScrollingRowIndex; //Save Current Scroll Index
int SelectedRowIndex = 0;
if (this.dgvItems.SelectedRows.Count > 0) SelectedRowIndex = this.dgvItems.SelectedRows[0].Index; //Save Current Selected Row Index

//REFRESH DataGridView HERE

if ((FirstDisplayedScrollingRowIndex >=0) && ((this.dgvItems.Rows.Count -1) >= FirstDisplayedScrollingRowIndex)) this.dgvItems.FirstDisplayedScrollingRowIndex = FirstDisplayedScrollingRowIndex; //Restore Scroll Index
if ((this.dgvItems.Rows.Count -1) >= SelectedRowIndex) this.dgvItems.Rows[SelectedRowIndex].Selected = true; //Restore Selected Row
Dave Lucre
  • 1,105
  • 1
  • 14
  • 16
  • I tried the code of the answer above you and I put it on a 100ms timer which gives me a Null Reference Error in my DataGridView... If I use your code format instead, will the Null Reference Error be fixed? – Lucifer Rodstark Dec 11 '17 at 21:03
  • A null reference error is pretty generic, it's difficult to say what the issue is. This code was not written to be executed within a timer, so that may be your problem. Try it without the timer first and work back from there. – Dave Lucre Dec 12 '17 at 22:34
  • It works with no null reference error without a timer. Someone told me maybe its because the 100ms. timer executes too fast that the datagridview is not even called yet. So I tried to increase the timer to 1 second and the null reference error at startup is gone but not entirely because there are some moments that it just pops up a null reference error again. – Lucifer Rodstark Dec 12 '17 at 22:59
  • Sounds like you have a race condition between your code and the datagridview. Perhaps there's an attribute on the DGV that you can inspect to determine whether it has finished doing what it needs to do? – Dave Lucre Dec 13 '17 at 21:06
3

You could save selected row before launching getData, using DataGridView.CurrentRow, and select that row after the Grid has been loaded.

In this question I answered how to select an specific row in a DataGridView.


Edit: I supposed that you are using WinForms

Edit2: And what about scrollbars?

You can save the first visible row index, too with this statement

DataGridView.FirstDisplayedCell.RowIndex
Community
  • 1
  • 1
Javier
  • 4,051
  • 2
  • 22
  • 20
3

The easy way is Blow code:

int CurrentRowIndex = (hSuperGrid1.CurrentRow.Index);

////after Fill The DataGridView

hSuperGrid1.ClearSelection();
hSuperGrid1.CurrentRow.Selected = false;

hSuperGrid1.Rows[CurrentRowIndex].Selected = true;

hSuperGrid1.CurrentCell = hSuperGrid1[0, CurrentRowIndex];
3

@ovinophile's answer helped out, for sure, but it did not address the horizontal scrolling of the DataGridView for me. Piggy-backing on @ovinophile's answer, this is working well to maintain both horizontal and vertical scroll position:

// Remember the vertical scroll position of the DataGridView
int saveVScroll = 0;
if (DataGridView1.Rows.Count > 0)
    saveVScroll = DataGridView1.FirstDisplayedCell.RowIndex;

// Remember the horizontal scroll position of the DataGridView
int saveHScroll = 0;
if (DataGridView1.HorizontalScrollingOffset > 0)
    saveHScroll = DataGridView1.HorizontalScrollingOffset;

// Refresh the DataGridView
DataGridView1.DataSource = ds.Tables(0);

// Go back to the saved vertical scroll position if available
if (saveVScroll != 0 && saveVScroll < DataGridView1.Rows.Count)
    DataGridView1.FirstDisplayedScrollingRowIndex = saveVScroll;

// Go back to the saved horizontal scroll position if available
if (saveHScroll != 0)
    DataGridView1.HorizontalScrollingOffset = saveHScroll;
Mako-Wish
  • 303
  • 2
  • 12
  • I am refreshing my DataGridView using a function which executes an SQL Query from my database instead... If I put in my reload function in place of the ‘DataGridView1.DataSource = ds.tables(0)’ in your code format will it work for me too? – Lucifer Rodstark Dec 11 '17 at 21:05
1

At another forum, I found a solution without any manipulation:

    private void getData(string selectCommand)
    {
        string connectionString = @"Server=localhost;User=SYSDBA;Password=masterkey;Database=C:\data\test.fdb";

        dataAdapter = new FbDataAdapter(selectCommand, connectionString);
        data = new DataTable();
        dataAdapter.Fill(data);
        bindingSource.DataSource = data;
    }

    private void button1_Click(object sender, EventArgs e)
    {
        dataAdapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;
        dataAdapter.Fill(data);
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        dataGridView1.DataSource = bindingSource;
        getData("SELECT * FROM cities");
    }
bobik
  • 201
  • 1
  • 2
  • 4
0

Currently, you are loading the data everytime the page is loaded. I would suggest using the Page.IsPostback property to check and see if it is a postback or not.

if(!Page.IsPostback)
{
    dataGridView1.DataSource = bindingSource;  
    getData("SELECT * FROM cities");
}

This will reduce the amount of load on your DB and will quit causing issues with your selections.

Lucas
  • 478
  • 4
  • 15
0

This does so because you're resetting or reaffecting the DataSource property of your DataGridView control.

In order to perform what you wish, you have to save the CurrentItem index into a local variable before resetting the DataSource property of your DataGridView. However, doing so implies that you know you're going to have the same or more amount of data, otherwise you will come out with an IndexOutOfRangeException trying to move to a greater index than the amount of data actually contained in your DataGridView.

So, if you wish to save the index of your row or cell, you will need to make it through the DataGridView control properties as your BindingSource wil not deliver such feature.

In a DataGridView, the selected row and the current row (indicated by an arrow in the row header) may not be the same row. In addition, we could select multiple rows in a DataGridView but the current row can only be one row. When the SelectionMode property of the DataGridView is set to FullRowSelect, the current row will be always selected. If you'd like to change the current row in a DataGridView control, you may set the CurrentCell property

dataGridView1.CurrentCell = dataGridView1.Rows[1].Cells[0]; 

If you'd like to just change the selected row, you may set the Selected property of the row you want to true.

dataGridView1.CurrentRow.Selected = false; 
dataGridView1.Rows[1].Selected = true; 
Will Marcouiller
  • 23,773
  • 22
  • 96
  • 162
  • And what with scroll bars? If the user simply scroll datagrid without selecting a cell. – bobik Mar 14 '10 at 15:43
  • Then neither the DataGridView, nor the BindingSource can identify where the user is scrolling. When you load data and set it to the DataSource property of your DataGridView, you tell the WinForm to refresh with new data, then everything is reinitialized. I know of no other way then with a selected cell or row. Windows itself does not behave differently. – Will Marcouiller Mar 14 '10 at 15:51
0

I did something like this.

  1. Save FirstDisplayedCell and CurrentCell selected
  2. Refresh
  3. Set dataGridView previous row and set previous selected row

    //Declare variables int rowIndex = 0; int saveRow = 0;

    if(dataGridView1.SelectedRows.Count > 0)
    {
        rowIndex = dataGridView1.CurrentCell.RowIndex;
        saveRow = dataGridView1.FirstDisplayedCell.RowIndex;
    }
    
    //REFRESH CODE HERE
    
    if(dataGridView1.SelectedRows.Count > 0)
    {
        dataGridView1.FirstDisplayedScrollingRowIndex = saveRow;
        dataGridView1.CurrentCell = dataGridView1.Rows[rowIndex].Cells[0];
    }
    
levinjay
  • 106
  • 1
  • 4
0

We can use the SelectionChanged event of the DataGridView to track the selected row. The problem is when rebinding the datasource the CurrentRow.Index is reset to zero.

We can handle this by detaching from the SelectionChanged event before binding our datasource and re-attaching to the event after binding our datasource.

// Detach Event
dataGridView1.SelectionChanged -= dataGridView1_SelectionChanged;
// Bind Data
bindingSource.DataSource = data; // or dataGridView1.DataSource = data;
// Set Selected Row
dataGridView1.Rows[LastSelectedRowIndex].Selected = true;
// Re-attach Event
dataGridView1.SelectionChanged += dataGridView1_SelectionChanged;

The event to track the selected index is simple.

int LastSelectedRowIndex = 0;
private void dataGridView1_SelectionChanged(object sender, EventArgs e)
{
     LastSelectedRowIndex = dataGridView1.CurrentRow.Index;  
}

This is to give you an idea of the concept.

Maintain selection with a unique key value

Generally when we are rebinding information to a datasource this is because the information has changed. If the size of the dataset has changed this means that your row indexes will also change.

This is why we should not rely on LastSelectedRowIndex for maintaining the selected row. Instead we should use a unique key in the datasource.

Our SelectionChanged event becomes the following.

// KeyIndex is the Unique Key column within your dataset.
int KeyIndex = 2;
string LastSelectedKey = string.Empty;
private void dataGridView1_SelectionChanged(object sender, EventArgs e)
{
     LastSelectedKey = dataGridView1.CurrentRow.Cells[KeyIndex].Value.ToString();
}

And instead of setting the DataGridView selected row by index, we can set it by our key value.

// Set Selected Row
// If we need to scroll the selected row into view
// this would be a good place to set FirstDisplayedScrollingRowIndex
foreach (DataGridViewRow row in dataGridView1.Rows)
     if (row.Cells[KeyIndex].Value.ToString() == LastSelectedKey)
          row.Selected = true;
clamchoda
  • 4,411
  • 2
  • 36
  • 74
0

I could not get this to work with large datasets. The position got reset after I did:

saveRow = dataGridView1.FirstDisplayedScrollingRowIndex;
*** refresh data ***
dataGridView1.FirstDisplayedScrollingRowIndex = saveRow;
*** Now we still got a reset ***

Probably because the dataset wasn't loaded before I reset the saveRow.

The solution that worked for me was to reset the position inside the DataBindingComplete event like this:

        dataGridView1.DataBindingComplete += (o, args) =>
        {
            if(dataGridView1.RowCount > 0 && saveRow < dataGridView1.RowCount && saveRow > 0)
                dataGridView1.FirstDisplayedScrollingRowIndex = saveRow;
        };
Bassebus
  • 682
  • 1
  • 6
  • 14