使用 Enter 键在底行上 VB.net Datagridview 单元格验证

VB.net Datagridview Cell Validation on bottom row with Enter key

提问人:pball 提问时间:9/25/2021 更新时间:9/29/2021 访问量:508

问:

我有一个datagridview,我正在使用Cell Validating事件来做一些事情。我遇到了按底行上的 Enter 键不会触发 Cell Validating 事件的问题。这似乎是由于焦点没有转移到不同的单元格。这可以通过向窗体添加 datagridview,添加一些列和至少 1 行,然后添加带有消息提示的 Cell Validating 事件来测试。

有没有办法在按底行的 Enter 键时触发 Cell Validating 事件?

我尝试这样做的原因是因为我有一个组合框列,该列正在修改为允许手动输入文本,并且该代码的一部分使用 Cell Validating 事件将手动输入添加到组合框项目列表中。如果手动输入的文本未添加到组合框项目列表中,则该文本将被清除。因此,如果您手动在底行中键入内容并按回车键,则该文本将消失,因为 Cell Validating 事件未触发。允许手动输入 datagridview 组合框单元格的代码如下。

Private Sub DGV_Risk_EditingControlShowing(ByVal sender As Object, ByVal e As DataGridViewEditingControlShowingEventArgs) Handles DGV_Risk.EditingControlShowing
    If (DGV_Risk.CurrentCell.ColumnIndex = 1) Then
        Dim c As ComboBox = TryCast(e.Control, ComboBox)
        If c IsNot Nothing Then c.DropDownStyle = ComboBoxStyle.DropDown
    End If
End Sub

Private Sub DGV_Risk_CellValidating(sender As Object, e As DataGridViewCellValidatingEventArgs) Handles DGV_Risk.CellValidating
    MsgBox("here")
    If (e.ColumnIndex <> 1) Then Exit Sub
    Dim Entry As String = e.FormattedValue
    If (Not User.Items.Contains(Entry)) Then
        User.Items.Add(Entry)
        DGV_Risk.Rows(e.RowIndex).Cells(e.ColumnIndex).Value = Entry
    End If
End Sub
vb.net DataGridView

评论

0赞 JohnG 9/25/2021
什么?User
0赞 pball 9/26/2021
User 是设置为 combobox 的 datagridview 列,即列索引 1。这就是将手动输入的值添加到组合框项的代码。
0赞 JohnG 9/27/2021
我能看到你描述的问题;然而,解决方案并不是那么简单。问题是“通常”的“网格”组合框单元格是不可编辑的,因为你已经做了它。因此,当用户在最后一个“新”行的组合框单元格中键入时...然后,下一个“新行”不会像任何其他单元格那样“自动”添加到网格中。换句话说,如果您在网格的“新建”行的任何非组合框单元格中键入单个字符......然后,一个“新”行会自动添加到网格中......当用户在组合框单元格中键入内容时,不会发生这种情况。
0赞 JohnG 9/27/2021
另外,这是你特别想做的事情吗?或。。。这是其他人想要实现的东西吗?允许用户直接在网格单元格中“编辑”组合框单元格不是正常行为,并且会根据您的问题引入许多不同的问题。我认为如果您必须有这种行为,创建一个“自定义可编辑”组合框列可能是最好的方法。
1赞 JohnG 9/27/2021
让我知道,我可以发布一个简单的解决方案,允许用户将项目添加到组合框列,但是,它使用一个单独的表单,该表单会弹出并允许用户添加项目。它使用组合框列的数据源,只需将新项添加到组合框列数据源中。当然,这不允许用户直接在网格中“编辑”组合框值,但是,这将很容易实现,并将消除您当前遇到的问题。另外。。。这种方法,IMO,是用户友好和直观的。

答:

0赞 pball 9/29/2021 #1

这并不能直接解决最初的问题,但实现了我想要的最终目标。根据 JohnG 的建议,datagridview 组合框已恢复为默认的不可编辑状态,并添加了一个单独的按钮,用于向组合框添加值。输入框从用户那里获取一个值,并在简单验证后将该值添加到组合框列表中。

Private Sub Btn_AddNewUser_Click(sender As Object, e As EventArgs) Handles Btn_AddNewUser.Click
    Dim NewUser As String = InputBox("Please enter a new user")
    If (NewUser <> "") Then
        Me.User.Items.Add(NewUser)
    End If
End Sub
1赞 JohnG 9/29/2021 #2

网格组合框单元格可能非常挑剔,并且会在常规组合框不会的情况下抛出网格。网格组合框因抛出网格而臭名昭著,声称组合框单元格中的组合框项目不“属于”组合框项目列表......并且会“看起来”该项目确实属于项目组合框列表。请参阅本文末尾的在家试用。DataErrorDataError

您当前的答案将起作用,但是,在使用此方法之前,您可能需要修复/重新考虑几件事。一个可解决的问题是,没有检查项目组合框中的重复项目,也没有检查多余的添加,如“item1”和“项目1”等。

此外,一个不太可解决的问题是,如果“GRID”使用 .在我的测试中,使用您的代码,需要做更多的工作才能将新项目添加到项目组合框列表中。当网格使用 .在追踪时...您可以在组合框列项目列表中看到新添加的项目;但是,它们“没有”显示。DataSourceDataSource

我猜到这是“为什么”;但是,我真的不在乎,因为 99.999% 的时间......“如果”我使用...“然后”我将使用 .我强烈建议你也这样做。这不仅减少了您必须编写的代码量,而且还可能为您通常必须实现的许多“内置功能”打开了大门......喜欢。。。排序和过滤以及其他有用的功能。同样的理念也适用于 .DataGridViewDataSourceDataSourceDataGridViewComboBoxColumn

因此,在下面的示例中,我同时对网格和组合框列都使用了 a。对于网格,代码使用 simple 作为数据源,因为这是从数据库查询返回的通用数据容器。组合框使用...这是专门为组合框创建的自定义类的简单。我们可以对组合框数据源使用 a,但是在此示例中,我使用了自定义类的列表。造成这种情况的一些原因...DataSourceDataTableList<ComboItem>ListDataTable

为了帮助最大程度地减少网格因为“看似”糟糕的项目值而抛出它的机会......我建议为组合框项目制作一个“具体”......即使它们很简单.“应该”使事情以更直观的方式工作。下面的简单示例使用了一个非常简单的 call,专门用于网格的组合框列。DataErrorClassstringsList(Of MyObject)ClassComboItem

该类包含一个属性,我们将有一个用作网格组合框列的属性。在大多数情况下,还将使用唯一属性,该属性将成为组合框属性,但是在这种情况下,它不会被使用,并且属性值将用作组合框。该类需要实现的唯一其他方法是允许代码使用 和 方法的 和方法。这个简单的类可能看起来像......StringList(Of ComboItem)DataSourceintIDValueMemberStringItemNameDisplayMemberEqualsCompareToListContainsSort

Public Class ComboItem : Implements IComparable

  Public Property ItemName As String

  Public Overrides Function Equals(obj As Object) As Boolean
    Dim that = TryCast(obj, ComboItem)
    If that IsNot Nothing Then
      Return Me.ItemName.Equals(that.ItemName)
    End If
    Return False
  End Function

  Public Function CompareTo(obj As Object) As Integer Implements IComparable.CompareTo
    Dim that = CType(obj, ComboItem)
    Return Me.ItemName.CompareTo(that.ItemName)
  End Function

End Class

请记住...上面的类基本上是对象的“包装器”。String

下面的示例使用了三个全局变量...用作 的 调用。调用 和 用作 的 。最后,为了方便起见,全局公开了它,因为当用户向列表中添加新项目时,我们将更新它。DataTableGridTableDataSourceDataGridViewList(Of ComboItem)ComboItemsDataSourceDatagridViewComboBoxColumnDataGridViewComboBoxColumnDataSourceComboItems

Dim GridTable As DataTable
Dim ComboItems As List(Of ComboItem)
Dim comboCol As DataGridViewComboBoxColumn

为了帮助测试这一点,我们将为网格创建一些测试数据。它将是一个简单的表,其中包含三 (3) 列,分别命名为 和 。组合框列在哪里。由于网格将使用带有实际测试数据...然后,我们需要从中获取测试数据值,以将这些值添加到组合框项目列表中。我们必须在设置网格之前执行此操作,以避免获得网格。StringCol0Col1Col2Col1DataSouceCol1DataSourceDataError

下面是一个返回包含五 (5) 行测试数据的方法。这些值...需要将“ZZZ”、“Cab”、“Bac”和“AAA”添加到组合框项目列表中,以避免错误。GetGridDataDataTableCol1

Private Function GetGridData() As DataTable
  Dim dt = New DataTable()
  dt.Columns.Add("Col0", GetType(String))
  dt.Columns.Add("Col1", GetType(String))
  dt.Columns.Add("Col2", GetType(String))
  dt.Rows.Add("C0", "ZZZ", "C2")
  dt.Rows.Add("C0", "Cab", "C2")
  dt.Rows.Add("C0", "Bac", "C2")
  dt.Rows.Add("C0", "Cab", "C2")
  dt.Rows.Add("C0", "AAA", "C2")
  dt.Rows.Add("", "", "")
  Return dt
End Function

接下来,我们需要获取组合框列的数据。如果网格没有数据,则空的将起作用。因此,将创建一个方法,该方法采用 a 和 a 并返回 .这将返回给定数据表中列名等于 的所有 sting 值。此列表将用作组合框列的数据源。将列添加到网格后,我们可以更轻松地知道网格数据源中的所有组合框项也是组合框列中的项项。List(Of ComboItem)GetComboDataDataTablecurDataStringcolNameList(Of ComboItem)colName

Private Function GetComboData(curData As DataTable, colName As String) As List(Of ComboItem)
  Dim items As List(Of ComboItem) = New List(Of ComboItem)
  Dim ci As ComboItem
  For Each row As DataRow In curData.Rows
    ci = New ComboItem()
    ci.ItemName = row(colName).ToString()
    If (Not String.IsNullOrEmpty(ci.ItemName)) Then
      If (Not items.Contains(ci)) Then
        items.Add(ci)
      End If
    End If
  Next
  Return items
End Function

接下来,我们将(在代码中)设置网格中的列。将有两个和一个.很明显,在调用此方法之前,我们需要调用该方法来填充全局变量。这是相当直接的,每列都设置为给定数据表中的正确列名,我要指出的是,组合框列被赋予了一个,其属性设置为我们类的属性。DataGridViewTextBoxColumnsDataGridViewComboBoxColumnGetComboDataComboItemsDataPropertyNameDataSourceDisplayMemberItemNameComboItem

Private Sub AddGridColumns()
  Dim txtCol As DataGridViewTextBoxColumn = New DataGridViewTextBoxColumn()
  txtCol.HeaderText = "Col0"
  txtCol.Name = "Col0"
  txtCol.DataPropertyName = "Col0"
  DGV_Risk.Columns.Add(txtCol)
  'combo box column
  comboCol = New DataGridViewComboBoxColumn()
  comboCol.HeaderText = "Col1"
  comboCol.Name = "Col1"
  comboCol.DataPropertyName = "Col1"
  comboCol.DataSource = ComboItems
  comboCol.DisplayMember = "ItemName"
  DGV_Risk.Columns.Add(comboCol)

  txtCol = New DataGridViewTextBoxColumn()
  txtCol.HeaderText = "Col2"
  txtCol.Name = "Col2"
  txtCol.DataPropertyName = "Col2"
  DGV_Risk.Columns.Add(txtCol)
End Sub

如果我们把所有这些放在表单加载事件中......它可能看起来像下面这样......

Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
  GridTable = GetGridData()
  ComboItems = GetComboData(GridTable, "Col1")
  AddGridColumns()
  DGV_Risk.DataSource = GridTable
End Sub

enter image description here

如果我们运行当前代码,组合框应该按预期工作,但是用户无法将项目添加到组合框列项目列表中。为此,我们将添加一个按钮来打开一个新表单以添加项目。它将有一个 和一个 .表单构造函数将被赋予一个 .当表单加载时,将填充给定的组合框项目列表。用户可以在文本框中键入内容以将项目“添加”到列表中。完成后,用户可以单击关闭框返回到原始表单。ListBoxTextBoxButtonList(Of ComboItem)ListBox

当用户进行添加并关闭对话框窗体时,代码将返回到上一个窗体,并更新组合框列的数据源以反映新项。这个基本形式可能看起来像......

Private Sub Form3_Load(sender As Object, e As EventArgs) Handles MyBase.Load
  listBoxCurItems.DataSource = itemsList
  listBoxCurItems.DisplayMember = "ItemName"
End Sub

Dim itemsList As List(Of ComboItem)

Public Sub New(items As List(Of ComboItem))
  InitializeComponent()
  itemsList = items
End Sub

Private Sub btnAddNewComboText_Click(sender As Object, e As EventArgs) Handles btnAddNewComboText.Click
  Dim newValue = TextBox2.Text.Trim()
  Dim ci As ComboItem = New ComboItem
  ci.ItemName = newValue
  If (Not String.IsNullOrEmpty(ci.ItemName)) Then
    If (Not itemsList.Contains(ci)) Then
      itemsList.Add(ci)
      itemsList.Sort()
      listBoxCurItems.DataSource = Nothing
      listBoxCurItems.DataSource = itemsList
      listBoxCurItems.DisplayMember = "ItemName"
    Else
      MessageBox.Show("Duplicate items not allowed")
    End If
  Else
    MessageBox.Show("Item cannot be an empty string")
  End If
End Sub

在此示例中,当用户按下窗体上的按钮时,将调用用于添加新项的窗体,该按钮看起来像...

Private Sub btnAddNewComboText_Click(sender As Object, e As EventArgs) Handles btnAddNewComboText.Click
  Dim f3 = New Form3(ComboItems)
  F3.ShowDialog()
  comboCol.DataSource = Nothing
  comboCol.DataSource = ComboItems
End Sub

这应该可以做到。如前所述,这消除了我们之前编写的所有网格事件代码,并且该功能以直观且用户友好的方式工作。

我希望这有帮助并且有意义。

在家试试这个

问“为什么”我使用“包装器”类并不是没有道理的,而一个简单的类可以完成同样的事情。该类还实现了我们需要的 和 接口...所以。。。这似乎没有必要。但是,这个“自己试一试”可以演示为什么使用包装器,也可以演示“为什么”当项目“显然”相同时,您可能会收到无效项目错误。List(Of String)StringEqualsCompareTo

所以试试这个......创建一个新表单并使用上面相同的代码,然后从列表中更改变量...ComboItemsComboItem

Dim ComboItems As List(Of ComboItem)

到列表String

Dim ComboItems As List(Of String)

显然,您需要更改代码的其他部分以使用 .此外,由于我们不再使用具有属性的类...我们需要注释掉 因为没有该属性。这将包括对向列表添加项目的表单的更改。这个想法是,我们想使用 a 而不是 a 作为组合框列的 a。StringComboItemcomboCol.DisplayMember = “ItemName”StringList(Of String)List(Of ComboItem)DataSource

进行更改后,运行代码。在添加项目和更改项目后,它可能/应该按预期工作。然而。。。我几乎可以保证,如果您继续添加新项目,请将组合框更改为这些新项目,并使用组合框进行基本的用户测试......您最终将获得声称该项目不属于项目组合框列表的网格。当我使用“包装器”类时,我从未遇到过此错误。DataError