|
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 |
|