简体   繁体   中英

Event to prevent C# DataGridView from changing the current row

I would need an event that fires when the current row of a System.Windows.Forms.DataGridView is going to be changed and which allows me to cancel this change, eg by setting the Cancel-property of the EventArgs to true.

I know about the CurrentCellChanged (the row has already changed when the event is called) and the RowLeave (no possibility to cancel the leave-operation) events, but neither provide what I would need. I also tried to use the RowValidating event, but this event is also called when the row is just going to be validated (without the intention to leave it), for example when I call <ParentForm>.Validate() , which leads to many confusions.

Is there any other possibility or a clean(er) solution to get the desired behaviour?

Just Ran into a similar issue and after many attempts my only work around was to use "Enter and Leave" to know when the form was NotActive to avoid Validating - Luckily the firing order was before the row\\col level events

HTH - Mike

    private bool IsActive = false;

    private void dgbList_RowValidating(object sender, DataGridViewCellCancelEventArgs e)
    {
        if (IsActive)
        {
            if (Do_I_NeedTo_Cancel)
              e.Cancel = true;
        }
    }

    private void dgList_Leave(object sender, EventArgs e)
    {
        IsActive = false;
    }

    private void dgList_Enter(object sender, EventArgs e)
    {
        IsActive = true;
    }

I think your best option is to use RowValidating with a bool condition to check if you call .Validate().

EDIT

Per your last comment, why not add a check for dataGridView.IsCurrentRowDirty ? For example:

private void dataGridView1_RowValidating(object sender, DataGridViewCellCancelEventArgs e) {
    if (dataGridView1.IsCurrentRowDirty) {
        if (dataCheck())
            if (MessageBox.Show("Ok?", "Save?", MessageBoxButtons.YesNoCancel) == DialogResult.Cancel) {
                e.Cancel = true;
            }
    }
}

If there is no dirty data, no matter who calls the validation the dataCheck won't be made and the messageBox will not appear.

EDIT

You can replace the 'if' clauses with any check you want, including one for dataGridView2.

You can also extend the dataGridView control if you have very complicated requirements.

EDIT

I now understand your requirement. I don't think there is a quick and clean solution. I would use the SelectionChanged event and there set the logic to prevent the change. Something like:

//rember the selection of the index
private int _currentIndex;
private bool _rollingBackSelection;

private void SelectionChanged(...){
     //when changing back to the selection in dgv1 prevent dgv2 check
     if (_rollingBackSelection) {
         _rollingBackSelection = false;
         return;
     }
     if (dgv2IsDirty()) {
          var result = MessageBox.Show("Ok?", "Save?", MessageBoxButtons.YesNoCancel);
          if (result == DialogResult.Cancel) {
             _rollingBackSelection = true;
             //rollback to the previous index
             dgv1.Rows[_currentIndex].Selected = true;
             return;
          }
          if (result == DialogResult.Yes)
             dgv2Save();
         dgv2Load();
         _currentIndex = dgv1.SelectedRows[0].Index;
     }
}

I think something like above is your best shot.

After trying lots of different things I came to the solution that the easiest and (for me) best working solution would be to check which control is focused in the RowValidating event of the DataGridView . This solution addresses exactly the problem I had: the RowValidating event was thrown by clicking other buttons for example. There are still some special cases which cause the RowValidating event to arise even if the current row isn't changing (sorting the DataGridView by clicking a column header, for example) but I think I can live with this minor issues. Maybe a future version of .NET will implement a DataGridView with a RowLeaving event which can be cancelled.

Changing the datagridview selected row will not clear any textbox or any other form controls automatically unless you assign an event handler to datagridview's ".SelectionChanged" event that clears data.

The trick is that you have to check the validity of the selected row index before clearing form data or taking any other action. If the data has been modified in a form control and you want to keep the modified data in form controls, you have to prevent calling the procedure that clears the form data.

I have the complete code below. This is reliable, stable, and I guess the easiest way to do it. Create a Form, and then create a datagridview object named "DGVobj" and a button named "Button1" to test the code. "Button1" toggles the boolean value to allow or not allow the selected row to be changed.

"TakeAction()" procedure is only and only executed if function "CheckIfDataHasChanged()" returns false. In other words, "TakeAction()" is executed only if the form data are not changed. If form data are changed, the procedure "SelectThePreviousRow()" is executed. This procedure clears the selection of the rows selected by the user, and selects the previous row again. The index of the valid row is stored in the variable "PrvRowIdx". "PrvRowIdx" is needed in case you do not want to allow the user to change row selection.

You need the procedure "DGV_CellBeginEdit()" to handle the datagridview's "CellBeginEdit" event. In case the data are changed, and the row index of the cells which the user will edit is different frm the index of the row whose data are already edited by the user, you don't want to allow the user to edit a new row.

You see that I have not used ".RowValidating" event and its event canceling approach. Because ".RowValidating" event is fired before ".SelectionChanged" event, and you will have no opportunity to check whether the user has selected a new row or not.

The boolean variables "bln_clearingSelection", "bln_CancelingEdit", and "bln_RowSelectionIsChanging" are used to prevent multiple calls to the procedures and prevent "StackOverFlow" exception. This prevents "StackOverFlow" exception from being fired if the user insists on changing the selected row by clicking the rows and cells nonstop.

"DataGridViewTestForm" is a form object.

Public Class DataGridViewTestForm

    Private WithEvents DGVobj3 As New System.Windows.Forms.DataGridView
    Private dgvSelectedRow As System.Windows.Forms.DataGridViewRow
    Dim PrvRowIdx As Integer = -1
    Private bln_AllowRowChange As Boolean = False
    Private bln_clearingSelection As Boolean = False, bln_CancelingEdit As Boolean = False, bln_RowSelectionIsChanging As Boolean = False
    Public Sub New()

        ' This call is required by the designer.
        InitializeComponent()

        DGVobj.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect
        DGVobj.MultiSelect = False

        Button1.Text = "Not Allowed"
        CreateNewDataTable()
    End Sub
    Private Sub CreateNewDataTable()
        Dim objTable As New System.Data.DataTable
        Dim col1 As New System.Data.DataColumn("Column1")
        Dim col2 As New System.Data.DataColumn("Column2")
        objTable.Columns.Add(col1)
        objTable.Columns.Add(col2)
        Dim rw1 As System.Data.DataRow = objTable.NewRow
        Dim rw2 As System.Data.DataRow = objTable.NewRow
        Dim rw3 As System.Data.DataRow = objTable.NewRow
        objTable.Rows.Add(rw1)
        objTable.Rows.Add(rw2)
        objTable.Rows.Add(rw3)
        DGVobj.DataSource = objTable
    End Sub
    Private Sub DGV_SelectionChanged(sender As DataGridView, e As EventArgs) Handles DGVobj.SelectionChanged
        If (bln_clearingSelection Or bln_CancelingEdit Or bln_RowSelectionIsChanging) Then
            Exit Sub
        End If
        If CheckIfDataHasChanged() Then
            SelectThePreviousRow()
        Else
            TakeAction()
        End If
    End Sub
    Private Sub TakeAction()
        bln_RowSelectionIsChanging = True
        Dim dgvSRows As DataGridViewSelectedRowCollection = DGVobj.SelectedRows
        If dgvSRows IsNot Nothing Then
            If dgvSRows.Count = 1 Then
                dgvSelectedRow = dgvSRows.Item(0)
                PrvRowIdx = dgvSelectedRow.Index
            End If
        End If
        ClearFormControls()
        bln_RowSelectionIsChanging = False
    End Sub

    Private Sub ClearFormControls()

    End Sub

    Private Function SelectThePreviousRow() As Boolean
        bln_clearingSelection = True
        Dim bln_Reverted As Boolean = False
        Dim dgvRowCollection As DataGridViewSelectedRowCollection = DGVobj.SelectedRows
        If dgvRowCollection IsNot Nothing Then
            DGVobj.ClearSelection()
            bln_Reverted = True
        End If
        If PrvRowIdx >= 0 Then
            If DGVobj.Rows IsNot Nothing Then
                DGVobj.Rows.Item(PrvRowIdx).Selected = True
            End If
        End If
        bln_clearingSelection = False
        Return bln_Reverted
    End Function

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        If bln_AllowRowChange Then
            bln_AllowRowChange = False
            Button1.Text = "Not Allowed"
        Else
            bln_AllowRowChange = True
            Button1.Text = "Allowed"
        End If
    End Sub

    Private Sub DGV_CellBeginEdit(sender As DataGridView, e As DataGridViewCellCancelEventArgs) Handles DGVobj.CellBeginEdit
        Dim bln_CancelingEdit = True
        Dim bln_EditWasCanceled As Boolean = False
        Dim RowIdx As Integer = e.RowIndex
        Dim dgvRowCollection As DataGridViewSelectedRowCollection = DGVobj.SelectedRows
        If dgvRowCollection IsNot Nothing Then
            Dim rwCnt As Integer = dgvRowCollection.Count
            If rwCnt = 1 Then
                If PrvRowIdx <> RowIdx Then
                    e.Cancel = True
                    bln_EditWasCanceled = True
                End If
            Else
                e.Cancel = True
                bln_EditWasCanceled = True
            End If
        Else
            e.Cancel = True
            bln_EditWasCanceled = True
        End If
        bln_CancelingEdit = False
    End Sub

    Private Function CheckIfDataHasChanged() As Boolean
        If bln_AllowRowChange Then
            Return False
        Else
            Return True
        End If

    End Function
End Class

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM