Windows Develop Bookmark and Share   
 index > Windows Forms Data Controls and Databinding > Losing cell edits in DataGridView
 

Losing cell edits in DataGridView

In my data editing app, I automatically call a method to save edits when the DataGridView loses the focus ('Leave' event). This stops users from having to manually click a save button. But under certain circumstances, the strategy appears to fail. Any incomplete edits to the current cell are not always saved to the database if the user hits the refresh button (which updates the DGV from the database as well as generating summary information). Here is the code of the Save Edit method which is linked to the DataGridView leave event:

private void SaveEdits()
{
dgvTasks.EndEdit();
tasksBindingSource.EndEdit();
try
{
this.tasksTableAdapter.Update(this.workreportDataSet.Tasks);
dgvTasks.Update();
}
catch (Exception err)
{
ShowMsg(err.Message, MsgType.Error); //shouldn't happen, but just in case
return;
}
UpdateSummary();
UpdateProjects();
}

I would have thought that the first line, calling EndEdit on the DGV, should ensure that the contents of the cell that is currently being edited will be saved before the click event on the refresh button refreshes from the database. What am I missing here?
pedroponting  Monday, June 02, 2008 5:18 AM

Hi Pedroponting,

How is your problem going on?

Based on my experience, I have to say that update database in Leave event is not a good design.

Please refer to the following sequence about the process when control gets and loses focus:

When you change the focus by using the keyboard (TAB, SHIFT+TAB, and so on), by calling the Select or SelectNextControl methods, or by setting the ContainerControl..::.ActiveControl property to the current form, focus events occur in the following order:

1. Enter

2. GotFocus

3. Leave

4. Validating

5. Validated

6. LostFocus

When you change the focus by using the mouse or by calling the Focus method, focus events occur in the following order:

1. Enter

2. GotFocus

3. LostFocus

4. Leave

5. Validating

6. Validated

And here is the sequence in DataGridView when focus is moving from one Cell/Row to another:

When moving from cell to cell (in the same row)

1. Cell Leave (old cell)

2. Cell Validating/ed (old cell)

3. Cell EndEdit (old cell)

4. Cell Enter (new cell)

5. Cell BeginEdit (new cell)

When moving from one row to another you get:

1. Cell Leave (old cell), Row leave (old row)

2. Cell Validating/ed (old cell)

3. Cell EndEdit (old cell)

4. Row Validating/ed (old row)

5. Row Enter (new row)

6. Cell Enter (new cell)

7. Cell BeginEdit (new cell)

We can see that whatever the process is, Leave event is always prior to Validating/Validated event, while Validating/Validated event happens before EndEdit event. So we can see that if you want to make the change to take effect immediately, it would be best to trigger EndEdit() after Validating, which means calling EndEdit in Validated event would be our choice. In your situation, I would like to suggest you implement DataGridView.RowValidated event instead of DataGridView.Leave event.

For your “Refresh�button problem, I cannot reproduce it. If your “Refresh�button happen to be a ToolStripButton, please explicitly call this.dataGridView1.Invalidate() before updating data from database, because ToolStripButton does not get control’s focus while clicked. If your “Refresh�button is just a normal button, then it should work correctly after writing DataGridView.RowValidated event as I advised above.

Hope it helps.

Please feel free to let me know how your problem is going on.

Thanks.

Best wishes,

Jun Wang

Jun Wang Tim  Friday, June 06, 2008 8:22 AM
Any ideas? Before my post falls into the void?
pedroponting  Wednesday, June 04, 2008 12:11 AM

Hi Pedroponting,

How is your problem going on?

Based on my experience, I have to say that update database in Leave event is not a good design.

Please refer to the following sequence about the process when control gets and loses focus:

When you change the focus by using the keyboard (TAB, SHIFT+TAB, and so on), by calling the Select or SelectNextControl methods, or by setting the ContainerControl..::.ActiveControl property to the current form, focus events occur in the following order:

1. Enter

2. GotFocus

3. Leave

4. Validating

5. Validated

6. LostFocus

When you change the focus by using the mouse or by calling the Focus method, focus events occur in the following order:

1. Enter

2. GotFocus

3. LostFocus

4. Leave

5. Validating

6. Validated

And here is the sequence in DataGridView when focus is moving from one Cell/Row to another:

When moving from cell to cell (in the same row)

1. Cell Leave (old cell)

2. Cell Validating/ed (old cell)

3. Cell EndEdit (old cell)

4. Cell Enter (new cell)

5. Cell BeginEdit (new cell)

When moving from one row to another you get:

1. Cell Leave (old cell), Row leave (old row)

2. Cell Validating/ed (old cell)

3. Cell EndEdit (old cell)

4. Row Validating/ed (old row)

5. Row Enter (new row)

6. Cell Enter (new cell)

7. Cell BeginEdit (new cell)

We can see that whatever the process is, Leave event is always prior to Validating/Validated event, while Validating/Validated event happens before EndEdit event. So we can see that if you want to make the change to take effect immediately, it would be best to trigger EndEdit() after Validating, which means calling EndEdit in Validated event would be our choice. In your situation, I would like to suggest you implement DataGridView.RowValidated event instead of DataGridView.Leave event.

For your “Refresh�button problem, I cannot reproduce it. If your “Refresh�button happen to be a ToolStripButton, please explicitly call this.dataGridView1.Invalidate() before updating data from database, because ToolStripButton does not get control’s focus while clicked. If your “Refresh�button is just a normal button, then it should work correctly after writing DataGridView.RowValidated event as I advised above.

Hope it helps.

Please feel free to let me know how your problem is going on.

Thanks.

Best wishes,

Jun Wang

Jun Wang Tim  Friday, June 06, 2008 8:22 AM
Thank you Jun! I always wondered if the Leave event was the best one, and now you say it, it's kind of obvious that it should be the Validated event. This has fixed my problem.
pedroponting  Tuesday, June 10, 2008 2:14 AM
Hello,

I have a question related to your thread. I have a column with checkboxes and another column with textboxes. I need to catch the click in one of the checkboxes so I tried to set the CellClick or CellContentClick. The strange (unexpected) behavior is as following: when I have clicked one checkbox, the next click on the same checkbox goes to the Click events but if I had clicked one checkbox and now I click another one, the CellEndEdit event is raised but none of the Click event is raised. In the second case, the checkbox state stays the same, like I'd never clicked it. It is indeed "kind of" selected so a new click does the expected behavior and also changes the checkbox state.

I'd appreciate your comments.

Thanks much,

Marcelo
MarceloRos  Wednesday, July 23, 2008 1:15 PM

Hi Marcelo,

I'm no expert on this stuff, but your situation is familiar in the it is similar to what happens when you click on a combobox inside a DataGridView. First the cell gets selected, and then the combobox gets the event. Three clicks are required in order to actually select an option: one for the cell, one for the combobox, one for the item. The solution is that you create a new class derived from the DataGridView that allows single click editing. I don't know where I got this code from, but here it is:

public class OneClickEditDataGridView : DataGridView

{

#region Operations

public void DefineSingleClickColumns(params DataGridViewColumn[] columns)

{

singleClickColumns.Clear();

foreach (DataGridViewColumn column in columns)

{

if (this.Columns.IndexOf(column) == -1)

{

throw new ArgumentException("Instance of column (" + column.Name + ") is not in this DataGridView");

}

singleClickColumns.Add(column);

}

}

public void DefineSingleClickColumns(params int[] columnIndexes)

{

singleClickColumns.Clear();

foreach (int columnIndex in columnIndexes)

{

if (columnIndex < 0 || columnIndex >= this.Columns.Count)

{

throw new ArgumentOutOfRangeException("Column index (" + columnIndex + ") is out of range");

}

singleClickColumns.Add(this.Columns[columnIndex]);

}

}

protected void BaseOnMouseDown(MouseEventArgs e)

{

base.OnMouseDown(e);

}

#endregion

#region Overrides

protected override void OnMouseDown(MouseEventArgs e)

{

// If primary mouse button not down, do standard processing

if (e.Button != MouseButtons.Left)

{

base.OnMouseDown(e);

return;

}

// Get info on where user clicked

DataGridView.HitTestInfo hitInfo = HitTest(e.X, e.Y);

// If a cell wasn't clicked, column isn't text or it's read only, do standard processing

if (hitInfo.Type != DataGridViewHitTestType.Cell ||

!(this.Columns[hitInfo.ColumnIndex] is DataGridViewTextBoxColumn) ||

this.Columns[hitInfo.ColumnIndex].ReadOnly)

{

base.OnMouseDown(e);

return;

}

// If specific columns specified and column clicked is not

// one of these, do standard processing

if (singleClickColumns.Count >= 1 && singleClickColumns.IndexOf(this.Columns[hitInfo.ColumnIndex]) == -1)

{

base.OnMouseDown(e);

return;

}

// Get clicked cell

DataGridViewCell clickedCell = this.Rows[hitInfo.RowIndex].Cells[hitInfo.ColumnIndex];

// If cell not current, try and make it so

if (CurrentCell != clickedCell)

{

// Allow standard processing make clicked cell current

base.OnMouseDown(e);

// If this didn't happen (validation failed etc), abort

if (this.CurrentCell != clickedCell)

{

return;

}

}

// If already in edit mode, do standard processing (will position caret)

if (this.CurrentCell.IsInEditMode)

{

base.OnMouseDown(e);

return;

}

// Enter edit mode

this.BeginEdit(false);

// Ensure text is scrolled to the left

TextBoxBase textBox = (TextBoxBase)this.EditingControl;

textBox.SelectionStart = 0;

textBox.ScrollToCaret();

// Position caret by simulating a mouse click within control

int editOffset = e.X - hitInfo.ColumnX - this.EditingControl.Left;

Int32 lParam = MakeLong(editOffset, 0);

SendMessage(this.EditingControl.Handle, WM_LBUTTONDOWN, 0, lParam);

SendMessage(this.EditingControl.Handle, WM_LBUTTONUP, 0, lParam);

}

#endregion

#region Implementation

const int WM_LBUTTONDOWN = 0x0201;

const int WM_LBUTTONUP = 0x0202;

[System.Runtime.InteropServices.DllImport("user32.dll")]

static extern bool SendMessage(IntPtr hWnd, Int32 msg, Int32 wParam, Int32 lParam);

List<DataGridViewColumn> singleClickColumns = new List<DataGridViewColumn>();

int MakeLong(int loWord, int hiWord)

{

return (hiWord << 16) | (loWord & 0xffff);

}

#endregion

}

After declaring your SingleClickDataGridView, you call DefineSingleClickColumns(), with your column indices as parameters.

I hope this helps!

Cheers,

pp

pedroponting  Thursday, July 24, 2008 8:50 AM

can anyone please suggest a way by which I can clear the value of a cell that has been invalidated in the cell_validating event?

I have written the logic of validating a particular cell content in the cell_validating event. But, I need to clear out the cell's content once validation fails. But i am seeing that the cell_validating event does not allow to change the value of the cell for which validation is being done.

Please help.

Thanks in advance,

jads.

dr_jay  Friday, December 05, 2008 7:50 AM

You can use google to search for other answers

Custom Search

More Threads

• Problem with datasource (type object)
• combobobx and datagridview
• Performance problem. Page faults.
• Datagrid not utilizing table style created
• encoding iso 8859-1 - read file
• Asynchronous Delegate Call having a Responsive UI while Populating a ListBox
• DataTable Expression Column vs SQL Server Formula Column
• Adding new rows to DataGridView does not follow sort order
• Binding to DropDownList ComboBox Problem
• Binding to DataGridViewImageColumn