Trigger Grid row validation when selection of cell editor LookUpEdit changes - c#

I have a DevExpress.XtraGrid.GridControl which has a column using DevExpress.XtraEditors.Repository.RepositoryItemLookUpEdit as cell editors.
When I select an item from the LookUpEdit, this does not cause any validation but does nothing until I click anywhere to make the Grid lose focus.
My desired behaviour would be that changing the LookUpEdit's selection immediately triggers a row validation event of the Grid.
How could this be achieved?
The official DevExpress Documentation tells to call the GridView's UpdateCurrentRow method:
There may be cases when you need to implement row validation. [...] To do so, handle the ColumnView.ValidateRow event. Note that you can also initiate row validation manually by calling the ColumnView.UpdateCurrentRow method.
I did this in the GridView's ValidatingEditor event handler:
private void gridView1_ValidatingEditor(object sender,BaseContainerValidateEditorEventArgs e)
{
(sender as GridView).UpdateCurrentRow();
}
However, now I can't add new rows to the Grid any more as end-user.
What would be the correct approach?
Update:
I am now listening to the GridView's CellValueChanging event and handling it like this:
private void gridView_CellValueChanging(object sender, CellValueChangedEventArgs e)
{
GridView gv = (sender as GridView);
gv.CellValueChanging -= gridView_CellValueChanging; // detach this event handler
gv.ActiveEditor.EditValue = e.Value;
gv.CellValueChanging += gridView_CellValueChanging; // re-attach handler
gv.CloseEditor();
}
I am sure this is not the way how one should do it, but it works for existing rows. However, it does not work on the "new row" - I still have to click anywhere else to apply changes that create a new entry. I want a new row to be created as soon as any cell of the "new row" got edited.

The editor in the cell is still open, you have to close it.
private void lookup_Closed(object sender, EventArgs e)
{
gvMyGrid.CloseEditor();
gvMyGrid.UpdateCurrentRow();
}

Related

How to force a "refresh" in a DataGridView

I am a newbie doing my first C# project (with minimal experience in Haskell and C as well) looking for guidance on how to implement a small feature on my program.
I have a DataGridView table (with 3 columns of checkboxes among other stuff) for the user to fill. When in a row, there gets a second checkbox checked, the first one that got checked must be unchecked. I can do this already but the problem is, the first checked one only gets unchecked after I select something else in my table.
Here is the code pertaining to the event of CellValueChanged (What is in the comments is what I've tried to help me)
if (e.ColumnIndex == 0 || e.ColumnIndex == 1 || tabela_NormasDataGridView.Rows.Count == 0)
{
return;
}
var isChecked = (bool)tabela_NormasDataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value;
if (isChecked)
{
for (int i = 2; i < 5; i++)
{
//Console.WriteLine("og " + e.ColumnIndex);
DataGridViewCell cell = tabela_NormasDataGridView.Rows[e.RowIndex].Cells[i];
//Console.WriteLine("segunda " + cell.ColumnIndex);
if (cell.ColumnIndex != e.ColumnIndex)
{
cell.Value = false;
//this.Refresh();
}
}
}
Try committing the change to force the refresh:
void tabela_NormasDataGridView_CurrentCellDirtyStateChanged(object sender, EventArgs e) {
tabela_NormasDataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
Make sure you wire the event up.
It is wise to use a little caution when wiring up certain events to do certain things. It may not be immediately obvious why a particular event is not necessarily a good "fit" for what you want the code to do IN that event.
In other words, the code works, and does not crash, however looking under the hood may reveal that the code is executing steps you may not have intended. Often this may be harmless and just waste CPU cycles. And it can also lead to a code crash.
A more subtle problem could arise where one of the grid's events waste a large amount of CPU cycles such that application using the grid becomes sluggish. Example; if the grid is anchored into a resizable control and the user resizes the control. The user may see a stutter in the re-drawing of the grid as it is resized.
In your current code, when the line below is executed...
cell.Value = false;
the cell's value will be changed. This appears harmless, however this code is IN the grid's CellValueChanged event... and the code is "changing a cells value." This will cause the event to fire again. And in this case we really do not want this.
LarsTech's solution will work and we will use it in the example below, however, it should be noted, that this solution works with the caveat that it creates extra unnecessary steps during execution.
On possible easy solution…
One possible solution to avoid this re-firing is to “turn off” the grid's CellValueChanged event just before the code sets the cells value to false. Then turn it back "on" after the code changes the value.
Another option using a different grid event…
Another possible (better) solution is to change the event the code is in. This can be done if you wire up the grid's CellContentClick event.
The key advantage to using the CellContnetClick event is that it will fired BEFORE the grid's CellValueChanged event gets fired. This will allow the code to "change" the cell values and they will be immediately updated since the grid's CellValueChanged event will get fired "automatically" when the code changes the value.
A full example...
To help visualize this, an example is below. Drop two DataGridViews onto a Form. The only change that needs to be done is that you will need to change the top grids name to tabela_NormasDataGridView. The columns are added in the code.
The two grids can be seen on the left in the picture below.
In this example, both grids wire up the same events. The wired up events are the CellValueChanged, CurrentCellDirtyStateChanged and the CellContentClick events.
In each event Debug statements are added to output "when" the event is "entered" and "when" the event "exits" (leaves.)
What code is in the Top grid events
CellValueChanged - this event contains our code.
CurrentCellDirtyStateChanged - this event contains code to "commit"
the edits.
CellContentClick - this event contains NO code.
What code is in the Bottom grid events
CellValueChanged - this event contains NO code.
CurrentCellDirtyStateChanged - this event contains NO code.
CellContentClick - this event contains our code.
Te replicate, run the code below, click on the top grids check box cell in the "Check2" column. Then do the same in the bottom grid. This should produce the picture above.
Tracing the code in the top grid (Yellow)...
We can see the grids CurrentCellDirtyStateChanged event is
fired first and the code in that event is committing the edits to the
grid. It is entered… however…
... We can see that it does NOT immediately "leave" the event. At least
not yet. In fact, the event gets re-entered well down the list of
debug statements. We can also see this event is fired TWICE.
Next, we have SIX (6) calls to the grids CellValueChanged event.
Initally the event is re-entered BEFORE it is finished (enter-enter).
Calling the grids CellValueChanged event SIX (6) times when we only
want to change TWO cells, is "creating" unnecessary work. In
addition, this opens the code to infinite loop possibilities.
After the six calls to the event (where our code is), we can see that
the CurrentCellDirtyStateChanged event is re-entered, then the code
leaves the event twice.
Finally, the grids CellContentClick is fired and leaves immediately
as there is no code in the event.
This code appears to “create” a lot of unnecessary calls to the CellValueChanged event... And this is where our code is... hmm.
Tracing the code in the bottom grid (Red)…
We can see that the grids CurrentCellDirtyStateChanged event is
fired first and immediately exits as there is no code in that event.
The CellContentClick event is fired and entered. This is where our
code is.
BEFORE we leave the CellContnetClick event, the grid's
CellValueChanged event is fired (entered/leave) TWICE as there is no code
there.
The two calls to the grid's CellValueChanged event are coming from our code when the code sets the two cell values to false.
Finally, the code leaves the grids CellContentClick event.
This looks much cleaner and clearly is not creating extra steps.
Code that is used in the picture above…
First preliminaries to add the columns and wire up the events.
private void Form1_Load(object sender, EventArgs e) {
AddCheckBoxColumnsToGrid(tabela_NormasDataGridView);
tabela_NormasDataGridView.CellValueChanged += new DataGridViewCellEventHandler(tabela_NormasDataGridView_CellValueChanged);
tabela_NormasDataGridView.CurrentCellDirtyStateChanged += new EventHandler(tabela_NormasDataGridView_CurrentCellDirtyStateChanged);
tabela_NormasDataGridView.CellContentClick += new DataGridViewCellEventHandler(tabela_NormasDataGridView_CellContentClick);
AddCheckBoxColumnsToGrid(dataGridView2);
dataGridView2.CellValueChanged += new DataGridViewCellEventHandler(dataGridView2_CellValueChanged);
dataGridView2.CurrentCellDirtyStateChanged += new EventHandler(dataGridView2_CurrentCellDirtyStateChanged);
dataGridView2.CellContentClick += new DataGridViewCellEventHandler(dataGridView2_CellContentClick);
}
private void AddCheckBoxColumnsToGrid(DataGridView dgv) {
DataGridViewTextBoxColumn txtCol = new DataGridViewTextBoxColumn();
txtCol.Name = "col0";
dgv.Columns.Add(txtCol);
txtCol = new DataGridViewTextBoxColumn();
txtCol.Name = "col0";
dgv.Columns.Add(txtCol);
DataGridViewCheckBoxColumn col = new DataGridViewCheckBoxColumn();
col.Name = "Check1";
dgv.Columns.Add(col);
col = new DataGridViewCheckBoxColumn();
col.Name = "Check2";
dgv.Columns.Add(col);
col = new DataGridViewCheckBoxColumn();
col.Name = "Check3";
dgv.Columns.Add(col);
dgv.Rows.Add(4);
}
Events for the top grid…
private void tabela_NormasDataGridView_CellValueChanged(object sender, DataGridViewCellEventArgs e) {
Debug.WriteLine("DGV1 - Cell Value Changed -> enter");
if (e.ColumnIndex == 0 || e.ColumnIndex == 1 || tabela_NormasDataGridView.Rows.Count == 0) {
return;
}
var isChecked = (bool)tabela_NormasDataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value;
if (isChecked) {
for (int i = 2; i < 5; i++) {
DataGridViewCell cell = tabela_NormasDataGridView.Rows[e.RowIndex].Cells[i];
if (cell.ColumnIndex != e.ColumnIndex) {
cell.Value = false;
}
}
}
Debug.WriteLine("DGV1 - Cell Value Changed -> leave");
}
private void tabela_NormasDataGridView_CurrentCellDirtyStateChanged(object sender, EventArgs e) {
Debug.WriteLine("DGV1 - Current Cell Dirty Cell State Changed -> enter");
tabela_NormasDataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit);
Debug.WriteLine("DGV1 - Current Cell Dirty Cell State Changed -> leave");
}
private void tabela_NormasDataGridView_CellContentClick(object sender, DataGridViewCellEventArgs e) {
Debug.WriteLine("DGV1 - CellContentClick -> enter");
// Do nothing
Debug.WriteLine("DGV1 - CellContentClick -> leave");
}
Events for the bottom grid…
private void dataGridView2_CellValueChanged(object sender, DataGridViewCellEventArgs e) {
Debug.WriteLine("DGV2 - Cell Value Changed -> enter");
// do nothing
Debug.WriteLine("DGV2 - Cell Value Changed -> leave");
}
private void dataGridView2_CurrentCellDirtyStateChanged(object sender, EventArgs e) {
Debug.WriteLine("DGV2 - Current Cell Dirty Cell State Changed -> enter");
// do nothing
//dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);
Debug.WriteLine("DGV2 - Current Cell Dirty Cell State Changed -> leave");
}
private void dataGridView2_CellContentClick(object sender, DataGridViewCellEventArgs e) {
Debug.WriteLine("DGV2 - Cell Content Click -> enter");
string colName = dataGridView2.Columns[e.ColumnIndex].Name;
if (colName == "Check1" || colName == "Check2" || colName == "Check3") {
int rowIndex = e.RowIndex;
switch (colName) {
case "Check1":
dataGridView2.Rows[rowIndex].Cells["Check2"].Value = false;
dataGridView2.Rows[rowIndex].Cells["Check3"].Value = false;
break;
case "Check2":
dataGridView2.Rows[rowIndex].Cells["Check1"].Value = false;
dataGridView2.Rows[rowIndex].Cells["Check3"].Value = false;
break;
case "Check3":
dataGridView2.Rows[rowIndex].Cells["Check1"].Value = false;
dataGridView2.Rows[rowIndex].Cells["Check2"].Value = false;
break;
}
}
Debug.WriteLine("DGV2 - Cell Content Click -> leave");
}
I hope this makes sense.

CurrentRow in Data Grid giving first row after Selecting differant Row

I have a C# project (VS 2017) that uses a Data Grid View to show data in an object list. Using a contextMenuStrip I want to be able to right click on a row and be able to remove it from the datagridview and the underlying datasource.
I have the contextMenuStrip set in the Properties of the Datagridview with one item with the following to methods to handle the events.
private void dgv_Test_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
var hti = dgv_Test.HitTest(e.X, e.Y);
dgv_Test.ClearSelection();
dgv_Test.Rows[hti.RowIndex].Selected = true;
}
}
private void cms_DGV_Remove_Click(object sender, EventArgs e)
{
MessageBox.Show("Content Menu Clicked on Remove Option");
PersonModel temp = (PersonModel)dgv_Test.CurrentRow.DataBoundItem;
string msg = $"The index for the selected Person is {temp.Id}.";
MessageBox.Show(msg);
}
I expect this to sent the current row to the row that is right clicked on. This is not happening as the CurrentRow is staying on the top row. It does work if I first Left click on the row then right click the same row.
The problem you are describing is coming from the cms_DGV_Remove_Click event. When the user right-clicks on the grid, this is NOT going to make the cell/row underneath the cursor the grid’s CurrentRow. Even though the code in the dgv_Test_MouseDown method sets the row to “selected”…. it isn’t necessarily going to be the “current” row. The grids CurrentRow property is read only and you cannot directly set it from your code.
Given this, it is clear that getting the mouse coordinates “in relation to the grid” FROM the context menu will take a little effort since its coordinates are global. You have apparently noticed this from wiring up of the grids MouseDown event. This event makes it easy to capture the mouse position in relation to the grid. Problem is… you are NOT saving this info. By the time the context menu fires, that info is LOST.
Solution: make the DataGridView.HitTest info global. Then, set it every time the user right clicks into the grid. With this global variable set, when the context menu fires it will know which row the cursor is under.
DataGridView.HitTestInfo HT_Info; // <- Global variable
private void dgv_Test_MouseDown(object sender, MouseEventArgs e) {
if (e.Button == MouseButtons.Right) {
HT_Info = dgv_Test.HitTest(e.X, e.Y);
if (HT_Info.RowIndex >= 0) {
dgv_Test.ClearSelection();
dgv_Test.Rows[HT_Info.RowIndex].Selected = true;
}
}
}
It does not appear that the posted code is actually removing the row, however below is how the context menu “remove” event may look…
Below should work for a non-data bound grid and also a grid with a data bound DataTable.
private void cms_DGV_Remove_Click(object sender, EventArgs e) {
if (HT_Info.RowIndex >= 0) {
dgv_Test.Rows.Remove(dgv_Test.Rows[HT_Info.RowIndex]);
}
}
If you are using a List<T>, the method to remove may look something like below...
private void cms_DGV_Remove_Click(object sender, EventArgs e) {
if (HT_Info.RowIndex >= 0) {
PersonModel targetPerson = (PersonModel)dgv_Test.Rows[HT_Info.RowIndex].DataBoundItem;
AllPersons.Remove(targetPerson);
dgv_Test.DataSource = null;
dgv_Test.DataSource = AllPersons;
}
}
I am guessing this is the behavior you are looking for. The user right-clicks into the grid, the row under the cursor is selected and the context menu “remove” pops up. The user can either “select” remove to remove the row or click away from the context menu to cancel the remove.
Hope that makes sense.

how to listen a cell of gridview if its value changed

I have a gridview and I pass It records from db and I want to change a column value when click It and save the new value into db namely update the grid how can I achieve that.Is it possible to listen a cell of gridview if its value changed or not?
Try the CellValueChanged or CellEndEdit events or you can even try the below :
private void grid_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
// do something with grid.Rows[e.RowIndex].Cells[e.ColumnIndex].Value
}
I think you should have a saveButton and in it's click event you can save edited data to database:
private void btnSave_Click(object sender, EventArgs e)
{
DataRow[] modifiedRows = (gridView.DataSource as DataTable).Select("", "", DataViewRowState.ModifiedCurrent);
foreach (DataRow row in modifiedRows)
{ . . .}
}
You should look into CellValueChanged event which gets triggered when cell value is changed in a DataGridView. Note that this event gets triggered when the cell loses focus.
MSDN Reference

Why didn't trigger the CellEndEdit event of DataGridView

All,I knew we can set a column editable for a DataGridView.
And when finish editing the cell. the CellEndEdit event would be triggered.
But I just want to know why didn't end the edit of cell when I click the blank area of DataGridView. And click the area out of DataGridView doesn't trigger it too. only clicking the other cells could make it happen. It really doesn't make sense. Could anyone know why ? and How to make it ? It try to use the Click event of the DataGridView, But When I click the cell, It also trigger the DataGridView_click event.
private void dgvList_Click(object sender, EventArgs e)
{
dgvFileList.EndEdit();
}
Try using the HitTest function in the MouseDown event of the grid:
void dgvFileList_MouseDown(object sender, MouseEventArgs e) {
DataGridView.HitTestInfo hit = dgvFileList.HitTest(e.X, e.Y);
if (hit.RowIndex < 0 | hit.ColumnIndex < 0) {
dgvFileList.EndEdit();
}
}
Clicking outside the DataGridView control would require hitting a focusable control.
Before BeginEdit. Set a variable to identify if current state is edit mode.
bBeginEdit = true;
dgvFileList.BeginEdit(false);
In the Form_Click event
if (bBeginEdit)
{
dgvFileList.EndEdit();
bBeginEdit = false;
}
Thanks,
Joe
CellEndEdit() causes the event to be fired only if the cell was in edit mode (see Joe.wang's response). You can simply preceed CellEndEdit() with CellBeginEdit() to enter Edit mode (code from CellContentClick-handler, PickNewFont() is a wrapper for the FontDialog):
[...]
else if (String.Compare(rowName, "Font name") == 0) // user clicks on Font-row
{
dgvConfigSettings.BeginEdit(true);
Font newFont = PickNewFont(fontName, fontSize, fontStyle);
dgvConfigSettings.CurrentCell.Tag = newFont; // a bit dirty.... but that way we can pick-up the font in the panel-handler more easily
dgvConfigSettings.CurrentCell.Value = newFont.Name.ToString();
dgvConfigSettings.EndEdit();
}

Open dropdown(in a datagrid view) items on a single click

How can i avoid the double click on a DropDownButton used within a DataGridView? Right now I am able to view the drop down items within the DataGridView by clicking two or more times. First time it selects the cell and second time when I click on the DropDownButton arrow, it shows the list. How can I achieve the same in a single click?
Set EditMode property of the DataGridView to EditOnEnter: link
DataGridView.EditMode - Gets or sets a value indicating how to begin editing a cell.
EditOnEnter - Editing begins when the cell receives focus.
You can achieve this by subscribing for the EditingControlShowing event of the grid and there for control of type ComboBox
ComboBox ctl = e.Control as ComboBox;
ctl.Enter -= new EventHandler(ctl_Enter);
ctl.Enter += new EventHandler(ctl_Enter);
And in the Enter event, use the property
void ctl_Enter(object sender, EventArgs e)
{
(sender as ComboBox).DroppedDown = true;
}
DroppedDown indicates as the name suggests whether the dropdown area is shown or not, so whenever the control is entered this will set it to true and display the items without the need of further clicks.
The "set EditMode property of the DataGridView to EditOnEnter" worked for me, but I found another problem: user can't delete a row by just selecting and pressing DEL key. So, a google search gave me another way to do it. Just catch the event CellEnter and check if the cell is the appropriated type to perform appropriated action like this sample code:
private void Form_OnLoad(object sender, EventArgs e){
dgvArmazem.CellEnter += new DataGridViewCellEventHandler(dgvArmazem_CellEnter);
}
void dgvArmazem_CellEnter(object sender, DataGridViewCellEventArgs e)
{
DataGridView dg = (DataGridView)sender;
if (dg.CurrentCell.EditType == typeof(DataGridViewComboBoxEditingControl))
{
SendKeys.Send("{F4}");
}
}
Now the ComboBox drops down faster and the user still delete a row by selecting a row and pressing DEL key.
That's it.

Categories