VB.NET DataGridView 结束编辑和单元格移动

VB.NET DataGridView End Edits and Cell Movment

我创建了一个添加了 CheckBoxColumns 和 TextBoxColumns 的 DGV。当用户在 TextBoxCell 中输入文本值时,需要单击 2 次鼠标才能移动到下一个单击的单元格,有没有办法允许单击鼠标以允许立即在所选单元格中输入下一个值?

还尝试在输入值后使用箭头 Up/Down 键不会移动到相关的新单元格?当我使用箭头键似乎结束了编辑但没有移动到下一个单元格时,看起来 DGV 在鼠标光标出现时失去了焦点。

我需要这两个功能才能正常工作并允许立即编辑/数据输入,因为每个文本单元格都供用户输入,无法使用鼠标键快速导航或必须双击鼠标导致浪费了很多时间。

如能就这两个问题提供任何帮助,我们将不胜感激!

附加信息::

这是网格的样子::

Grid Layout Image

前 2 列是从以前的表单填充的数据,数据保存在 DataTable 中,其余列是通过程序添加的

对于 3 个文本列,需要在下一个单元格上单击两次才能移动到请求的单元格并开始编辑(如果已键入内容),如果已在单元格中键入内容并且我使用箭头键则不会似乎没有将您带到下一个单元格,它使放置的单元格没有开始编辑或将光标放在预期的单元格中。

我正在寻找的是当我在文本列中输入一个值并单击下一个单元格时它会立即开始编辑,或者如果我使用箭头键它会将您带到相关单元格并开始编辑。

下面是我遇到问题的表单的完整代码,我还添加了 DGV 属性的屏幕截图,因为我敢打赌所有这些都可以通过一些简单的事情解决。

DGV Settings

Public Class frmAppTest

Public DTApp As DataTable = New DataTable("Application")

Private Sub frmAppTest_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

    Call InitializeAppTable()

    'Add each column required for user input (inc dropdown option and "Select All" option)
    Call Add_ChkColumn("Team Crest", "TEAM_CREST")
    Call AddSAChkBox(dgvApp, 186, 57, 2, "TC")
    Call AddCrestSelect(dgvApp, 165, 33, "Crest")
    Call Add_ChkColumn("Chest Logo", "CHEST_LOGO")
    Call AddSAChkBox(dgvApp, 246, 57, 3, "CL")
    Call AddSponsorSelect(dgvApp, 225, 33, "Chest")
    Call Add_ChkColumn("Back" & vbCr & "Top", "BACK_TOP_LOGO")
    Call AddSAChkBox(dgvApp, 306, 57, 4, "BT")
    Call AddSponsorSelect(dgvApp, 285, 33, "BackTop")
    Call Add_ChkColumn("Back Bottom", "BACK_BOTTOM_LOGO")
    Call AddSAChkBox(dgvApp, 366, 57, 5, "BB")
    Call AddSponsorSelect(dgvApp, 345, 33, "BackBottom")
    Call Add_ChkColumn("Left Sleeve", "LEFT_SLEEVE_LOGO")
    Call AddSAChkBox(dgvApp, 426, 57, 6, "LS")
    Call AddSponsorSelect(dgvApp, 405, 33, "LeftSlv")
    Call Add_ChkColumn("Right Sleeve", "RIGHT_SLEEVE_LOGO")
    Call AddSAChkBox(dgvApp, 486, 57, 7, "RS")
    Call AddSponsorSelect(dgvApp, 465, 33, "RightSlv")
    Call Add_TxtColumn("Player Name", "PLAYER_NAME")
    Call AddColourSelect(dgvApp, 528, 33, "Name")
    Call Add_TxtColumn("Player Number", "PLAYER_NUMBER")
    Call AddColourSelect(dgvApp, 638, 33, "Number")
    Call Add_TxtColumn("Player Initials", "PLAYER_INITIALS")
    Call AddColourSelect(dgvApp, 748, 33, "Initials")
    Call FormatDGVApp()

    'Populate default data
    Call PopulateDetails()

End Sub

Private Sub AddCrestSelect(ByVal theDataGridView As DataGridView, ByVal XLocation As Integer, ByVal YLocation As Integer, ByVal CboName As String)
    Dim cbo As New ComboBox
    cbo.Name = "cboLogo" & CboName
    'The box size
    cbo.DropDownStyle = ComboBoxStyle.DropDownList
    cbo.Visible = True
    cbo.Items.Clear()
    cbo.Items.Add("Embro")
    cbo.Items.Add("Heat")
    cbo.Size = New Size(55, 14)
    cbo.SelectedIndex = 0

    cbo.Location = New System.Drawing.Point(XLocation, YLocation)
    cbo.BackColor = Color.White
    theDataGridView.Controls.Add(cbo)

End Sub

Private Sub AddSponsorSelect(ByVal theDataGridView As DataGridView, ByVal XLocation As Integer, ByVal YLocation As Integer, ByVal CboName As String)
    Dim cbo As New ComboBox
    cbo.Name = "cboLogo" & CboName
    'The box size
    cbo.DropDownStyle = ComboBoxStyle.DropDownList
    cbo.Visible = True
    cbo.Items.Clear()
    cbo.Items.Add("Single")
    cbo.Items.Add("Multi")
    cbo.Size = New Size(55, 14)
    cbo.SelectedIndex = 0

    cbo.Location = New System.Drawing.Point(XLocation, YLocation)
    cbo.BackColor = Color.White
    theDataGridView.Controls.Add(cbo)

End Sub

Private Sub AddColourSelect(ByVal theDataGridView As DataGridView, ByVal XLocation As Integer, ByVal YLocation As Integer, ByVal CboName As String)
    Dim cbo As New ComboBox
    cbo.Name = "cboColour" & CboName
    'The box size
    cbo.DropDownStyle = ComboBoxStyle.DropDownList
    cbo.Visible = True
    cbo.Items.Clear()
    cbo.Items.Add("White")
    cbo.Items.Add("Black")
    cbo.Items.Add("Royal")
    cbo.Items.Add("Navy")
    cbo.Items.Add("Yellow")
    cbo.Items.Add("Red")
    cbo.Size = New Size(100, 14)
    cbo.SelectedIndex = 0

    cbo.Location = New System.Drawing.Point(XLocation, YLocation)
    cbo.BackColor = Color.White
    theDataGridView.Controls.Add(cbo)

End Sub

Private Sub InitializeAppTable()

    DTApp.Columns.Add("Size", GetType(String))
    DTApp.Columns.Add("Price", GetType(String))
    dgvApp.DataSource = DTApp

End Sub

Private Sub FormatDGVApp()

    Dim xCol As New DataGridViewColumn

    dgvApp.Columns("Size").Width = 70
    dgvApp.Columns("Size").ReadOnly = True
    dgvApp.Columns("Size").SortMode = DataGridViewColumnSortMode.NotSortable
    dgvApp.Columns("Size").HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleCenter
    dgvApp.Columns("Size").DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter

    dgvApp.Columns("Price").Width = 70
    dgvApp.Columns("Price").ReadOnly = True
    dgvApp.Columns("Price").SortMode = DataGridViewColumnSortMode.NotSortable
    dgvApp.Columns("Price").HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleCenter
    dgvApp.Columns("Price").DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter

    dgvApp.ColumnHeadersHeight = 74

    dgvApp.Font = New Font("Arial", 7)

End Sub

Private Sub PopulateDetails()

    Dim AppRow As DataRow

    Dim i As Integer

    For i = 0 To 4
        AppRow = DTApp.NewRow
        AppRow("Size") = "S"
        AppRow("Price") = "5.00"
        DTApp.Rows.Add(AppRow)
    Next

End Sub

Private Sub Add_ChkColumn(ByVal Header As String, ByVal Name As String)

    Dim AddColumnLabelExport As New DataGridViewCheckBoxColumn

    With AddColumnLabelExport
        .HeaderText = Header
        .Name = Name
        .Width = 60
        .HeaderCell.Style.Alignment = DataGridViewContentAlignment.TopCenter
        .DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter
        .SortMode = DataGridViewColumnSortMode.NotSortable
    End With

    dgvApp.Columns.Add(AddColumnLabelExport)

End Sub

Private _IsSelectAllChecked As Boolean

Private Sub AddSAChkBox(ByVal theDataGridView As DataGridView, ByVal XLocation As Integer, ByVal YLocation As Integer, ByVal TagNo As Integer, ByVal ChkName As String)
    Dim cbx As New CheckBox
    cbx.Name = "SelectAll" & ChkName
    'The box size
    cbx.Size = New Size(14, 14)
    cbx.Tag = TagNo

    cbx.Location = New System.Drawing.Point(XLocation, YLocation)
    cbx.BackColor = Color.White
    theDataGridView.Controls.Add(cbx)

    AddHandler cbx.Click, AddressOf HeaderCheckBox_Click
   
    AddHandler theDataGridView.CellValueChanged, AddressOf DataGridView_CellChecked
    AddHandler theDataGridView.CurrentCellDirtyStateChanged, AddressOf DataGridView_CurrentCellDirtyStateChanged

End Sub

Private Sub HeaderCheckBox_Click(ByVal sender As Object, ByVal e As EventArgs)
    Me._IsSelectAllChecked = True

    Dim cbx As CheckBox
    cbx = DirectCast(sender, CheckBox)
    Dim theDataGridView As DataGridView = cbx.Parent
    Dim rowId As Integer = cbx.Tag

    For Each row As DataGridViewRow In dgvApp.Rows
        row.Cells(rowId).Value = cbx.Checked
    Next

    theDataGridView.EndEdit()

    Me._IsSelectAllChecked = False
End Sub

Private Sub DataGridView_CellChecked(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs)
    dgvApp.EndEdit()
    Dim dataGridView As DataGridView = DirectCast(sender, DataGridView)
    If Not Me._IsSelectAllChecked Then
        Select Case e.ColumnIndex
            Case 2
                If dataGridView.Rows(e.RowIndex).Cells(2).Value = False Then
                    'When any single CheckBox is unchecked, uncheck the header CheckBox.
                    DirectCast(dataGridView.Controls.Item("SelectAllTC"), CheckBox).Checked = False
                Else
                    'When any single CheckBox is checked, loop through all CheckBoxes to determine
                    'if the header CheckBox needs to be unchecked.
                    Dim isAllChecked As Boolean = True
                    For Each row As DataGridViewRow In dataGridView.Rows
                        If row.Cells(2).Value = False Then
                            isAllChecked = False
                            Exit For
                        End If
                    Next
                    DirectCast(dataGridView.Controls.Item("SelectAllTC"), CheckBox).Checked = isAllChecked
                End If
            Case 3
                If dataGridView.Rows(e.RowIndex).Cells(3).Value = False Then
                    'When any single CheckBox is unchecked, uncheck the header CheckBox.
                    DirectCast(dataGridView.Controls.Item("SelectAllCL"), CheckBox).Checked = False
                Else
                    'When any single CheckBox is checked, loop through all CheckBoxes to determine
                    'if the header CheckBox needs to be unchecked.
                    Dim isAllChecked As Boolean = True
                    For Each row As DataGridViewRow In dataGridView.Rows
                        If row.Cells(3).Value = False Then
                            isAllChecked = False
                            Exit For
                        End If
                    Next
                    DirectCast(dataGridView.Controls.Item("SelectAllCL"), CheckBox).Checked = isAllChecked
                End If
            Case 4
                If dataGridView.Rows(e.RowIndex).Cells(4).Value = False Then
                    'When any single CheckBox is unchecked, uncheck the header CheckBox.
                    DirectCast(dataGridView.Controls.Item("SelectAllBT"), CheckBox).Checked = False
                Else
                    'When any single CheckBox is checked, loop through all CheckBoxes to determine
                    'if the header CheckBox needs to be unchecked.
                    Dim isAllChecked As Boolean = True
                    For Each row As DataGridViewRow In dataGridView.Rows
                        If row.Cells(4).Value = False Then
                            isAllChecked = False
                            Exit For
                        End If
                    Next
                    DirectCast(dataGridView.Controls.Item("SelectAllBT"), CheckBox).Checked = isAllChecked
                End If
            Case 5
                If dataGridView.Rows(e.RowIndex).Cells(5).Value = False Then
                    'When any single CheckBox is unchecked, uncheck the header CheckBox.
                    DirectCast(dataGridView.Controls.Item("SelectAllBB"), CheckBox).Checked = False
                Else
                    'When any single CheckBox is checked, loop through all CheckBoxes to determine
                    'if the header CheckBox needs to be unchecked.
                    Dim isAllChecked As Boolean = True
                    For Each row As DataGridViewRow In dataGridView.Rows
                        If row.Cells(5).Value = False Then
                            isAllChecked = False
                            Exit For
                        End If
                    Next
                    DirectCast(dataGridView.Controls.Item("SelectAllBB"), CheckBox).Checked = isAllChecked
                End If
            Case 6
                If dataGridView.Rows(e.RowIndex).Cells(6).Value = False Then
                    'When any single CheckBox is unchecked, uncheck the header CheckBox.
                    DirectCast(dataGridView.Controls.Item("SelectAllLS"), CheckBox).Checked = False
                Else
                    'When any single CheckBox is checked, loop through all CheckBoxes to determine
                    'if the header CheckBox needs to be unchecked.
                    Dim isAllChecked As Boolean = True
                    For Each row As DataGridViewRow In dataGridView.Rows
                        If row.Cells(6).Value = False Then
                            isAllChecked = False
                            Exit For
                        End If
                    Next
                    DirectCast(dataGridView.Controls.Item("SelectAllLS"), CheckBox).Checked = isAllChecked
                End If
            Case 7
                If dataGridView.Rows(e.RowIndex).Cells(7).Value = False Then
                    'When any single CheckBox is unchecked, uncheck the header CheckBox.
                    DirectCast(dataGridView.Controls.Item("SelectAllRS"), CheckBox).Checked = False
                Else
                    'When any single CheckBox is checked, loop through all CheckBoxes to determine
                    'if the header CheckBox needs to be unchecked.
                    Dim isAllChecked As Boolean = True
                    For Each row As DataGridViewRow In dataGridView.Rows
                        If row.Cells(7).Value = False Then
                            isAllChecked = False
                            Exit For
                        End If
                    Next
                    DirectCast(dataGridView.Controls.Item("SelectAllRS"), CheckBox).Checked = isAllChecked
                End If
        End Select
    End If

    dgvApp.CurrentCell = Nothing

End Sub

Private Sub DataGridView_CurrentCellDirtyStateChanged(ByVal sender As System.Object, ByVal e As System.EventArgs)
    Dim dataGridView As DataGridView = DirectCast(sender, DataGridView)
    If TypeOf dataGridView.CurrentCell Is DataGridViewCheckBoxCell Then
        dataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit)
    End If
End Sub

Private Sub Add_TxtColumn(ByVal Header As String, ByVal Name As String)

    Dim AddColumnLabelExport As New DataGridViewTextBoxColumn

    With AddColumnLabelExport
        .HeaderText = Header
        .Name = Name
        .Width = 110
        .HeaderCell.Style.Alignment = DataGridViewContentAlignment.TopCenter
        .DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter
        .SortMode = DataGridViewColumnSortMode.NotSortable
    End With

    dgvApp.Columns.Add(AddColumnLabelExport)

End Sub

Private Sub btnCancelLine_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCancelLine.Click
    Me.Close()
End Sub
End Class

好吧……更新后的代码解释了您可能遇到的许多问题。总的来说,您在订阅某些事件时需要多加小心。如果您不仔细检查您订阅的事件触发的时间和频率,那么制造问题并不难。

例如,在您当前的代码中,您有方法...

Private Sub AddSAChkBox(ByVal theDataGridView As DataGridView, ByVal XLocation As Integer, ByVal YLocation As Integer, ByVal TagNo As Integer, ByVal ChkName As String)
    Dim cbx As New CheckBox
    cbx.Name = "SelectAll" & ChkName
    'The box size
    cbx.Size = New Size(14, 14)
    cbx.Tag = TagNo
    cbx.Location = New System.Drawing.Point(XLocation, YLocation)
    cbx.BackColor = Color.White
    theDataGridView.Controls.Add(cbx)
    AddHandler cbx.Click, AddressOf HeaderCheckBox_Click
    AddHandler theDataGridView.CellValueChanged, AddressOf DataGridView_CellChecked
    AddHandler theDataGridView.CurrentCellDirtyStateChanged, AddressOf DataGridView_CurrentCellDirtyStateChanged
End Sub

此代码将一个复选框放入网格中并被调用六 (6) 次。每个“select 全部”Header 复选框一次。是不是最后两行代码不对……

AddHandler theDataGridView.CellValueChanged, AddressOf DataGridView_CellChecked
AddHandler theDataGridView.CurrentCellDirtyStateChanged, AddressOf DataGridView_CurrentCellDirtyStateChanged

这很奇怪,因为每次调用该方法时,都会将一个新的处理程序添加到网格中。换句话说,代码订阅了同一个 GRID 事件的六 (6) 次。

如果您在事件的开始和结束处放置几个调试语句,您会发现事件将比需要多触发五次。

底线……这些 GRID 事件只需要订阅一次。我建议将代码移至 Forms Load 事件或仅调用一次的某些方法。

接下来,您当前的问题与以下事实有关:当您在其他 non-check 框单元格中键入文本时,wired-up 网格事件仍在触发,而您在编辑时确实不希望这样那些 non-check 盒式电池。因此,一个简单的解决方案是检查并确保当这些事件触发时......如果单元格不是网格中的复选框单元格之一,我们什么也不做。

当前代码正在使用网格 CellValueChanged 事件及其 CurrentCellDirtyStateChanged 事件。在当前单元格脏状态更改事件中,代码只是将编辑“提交”到单元格,这反过来会触发代码检查复选框单元格的网格 CellValueChanged 事件。正如您所注意到的……当前单元格脏状态更改事件将在实际单元格值更改之前触发,这就是提交的目的,因此我们可以立即看到网格中的更改。

我建议您使用网格,CellContentClick 事件而不是 CurrentCellDirtyStateChanged 事件。当用户单击单元格并开始编辑单元格时,将触发此事件。当用户单击复选框单元格并更改其值时,它将触发。除了检查单元格是否为复选框单元格并提交编辑外,我们不需要做任何事情。如果该单元格不是复选框单元格,则什么也不做。这个事件可能看起来像……

Private Sub DataGridView_CellContentClick(ByVal sender As System.Object, ByVal e As System.EventArgs)
    If (dgvApp.CurrentCell.ColumnIndex < 2 Or dgvApp.CurrentCell.ColumnIndex > 7) Then
        Return
    End If
    Debug.WriteLine("DataGridView_CellContentClick <- Enter")
    dgvApp.CommitEdit(DataGridViewDataErrorContexts.Commit)
    Debug.WriteLine("DataGridView_CellContentClick -> Leave")
End Sub

这将在更改复选框时起作用,它将忽略 non-check 框单元格,但是如果用户正在编辑 non-check 框单元格,那么网格 CellValueChanged如果 non-check 框单元格值更改,事件仍会触发。...与上面的代码类似,我们需要检查网格 CellValueChanged 事件以查看哪个单元格值更改。如果更改的不是复选框单元格,那么我们不想做任何事情。因此,您可以使用 e.RowIndexe.ColumnIndex 变量添加相同的检查……在网格 CellValueChanged 事件中……类似于……

Private Sub DataGridView_CellChecked(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs)
    If (e.ColumnIndex < 2 Or e.ColumnIndex > 7) Then
        Return
    End If
     ….. ‘rest of code

我希望我有一个很好的答案的另一个问题是......如果你调用 HeaderCheckBox_Click 事件,当用户点击“select 全部”header 复选框,那么你可能会注意到......如果用户点击了一个复选框单元格并更改了它的值,那么这将使该单元格成为网格 CurrentCell 并且由于某种原因,它不会改变它的值预期的。我只能编写一个变通方法,只需将网格 CurrentCell 更改为其他单元格,然后在代码更改复选框单元格值后将其设置回去。我所做的更改可能看起来像……

Private Sub HeaderCheckBox_Click(ByVal sender As Object, ByVal e As EventArgs)
    Debug.WriteLine("HeaderCheckBox <- Enter")
    Dim curCellRowIndex = dgvApp.CurrentCell.RowIndex
    Dim curCellColIndex = dgvApp.CurrentCell.ColumnIndex
    dgvApp.CurrentCell = dgvApp.Rows(0).Cells(0)
    Dim cbx As CheckBox
    cbx = DirectCast(sender, CheckBox)
    Dim rowId As Integer = cbx.Tag
    RemoveHandler dgvApp.CellValueChanged, AddressOf DataGridView_CellValueChanged
    For Each row As DataGridViewRow In dgvApp.Rows
        row.Cells(rowId).Value = cbx.Checked
    Next
    AddHandler dgvApp.CellValueChanged, AddressOf DataGridView_CellValueChanged
    dgvApp.CurrentCell = dgvApp.Rows(curCellRowIndex).Cells(curCellColIndex)
    Debug.WriteLine("HeaderCheckBox -> Leave")
End Sub

您可能会注意到,就在通过网格行开始 For Each 循环之前,我们删除了 GRIDS CellValueChanged 事件以防止其触发。我们不希望它在此时触发,因为我们正在选中或取消选中所有复选框,因此不需要检查它们是否全部选中。

在我的测试中,这些更改使 non-check 框单元格能够按预期工作。我希望这是有道理的并有所帮助。您可能需要考虑对此进行简化。例如,在每个 Select/Case 部分中,代码使用不同的列索引执行相同的操作。我建议创建一个简单的方法来检查是否在给定列和 return true/false 中选中了所有复选框。我希望您不要计划对网格进行任何水平滚动,因为这会为您滚动添加到网格中的组合框、标签和复选框带来大量工作……请注意。