DataGridView级联/依赖ComboBox列

时间:2016-09-11 16:19:56

标签: vb.net winforms data-binding datagridview datagridviewcombobox

所以我不时在Winforms上使用遗留应用程序工作,并且在任何时候都不熟悉绑定对象的最佳实践。基本上我有三个部分,我有两个人,他们可能只有一个产品,但该产品可能会导致有不同的SKU集。有没有办法从第一个组合框的值触发组合框的事件和数量?我一直在环顾四周,我要么找到如何绑定组合框的基本数据(我可以做得那么好),要么就如何绑定它做一些事情。触发从属父更改并更改数据集后不绑定。示例如下:

POCOS:

Public Class Person
  Public Property PersonID As Integer
  Public Property FirstName As String
  Public Property LastName As String
  Public Property ProductId As Integer
  Public Property SkuId As Integer
End Class

Public Class Product
  Public Property ProductId As Integer
  Public Property Description As String
End Class

Public Class Sku
  Public Property SKUId As Integer
  Public Property ProductId As Integer
  Public Property Description As String
End Class

主要代码示例(基本UI实际上只有一个标记为' ds'几乎与Person和Product POCOS匹配数据表.datagridview' dgv'其列绑定到人物中的数据EXCEPT中有一个名为SKU ta的列没有绑定,因为我想在事后绑定它,这就是我惨遭失败的地方。

更新9-13-2016 我可以使用以下代码在某些大规模解决方案中工作除外(我做这个的全部原因)。它基本上不会执行将cell()强制转换为datagridviewcomboboxcell的行并忽略它并跳过该行。没有理由,它只是跳过它。我想知道在较大的类上数据网格视图是否会变得腐败或者其他东西。

主要代码:

Private _people As List(Of Person) = New List(Of Person)
Private _products As List(Of Product) = New List(Of Product)
Private _SKUs As List(Of Sku) = New List(Of Sku)
Private _initialLoadDone = False
Private _currentRow As Integer? = Nothing

Private Sub DynamicComboBoxDoubleFill_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    _products = New List(Of Product)({
                                     New Product With {.ProductId = 1, .Description = "Offline"},
                                     New Product With {.ProductId = 2, .Description = "Online"}
                                     })

    Dim s = ""
    For Each o In _products
      Dim row As DataRow = ds.Tables("tProducts").NewRow
      row("ProductId") = o.ProductId
      row("Description") = o.Description
      ds.Tables("tProducts").Rows.Add(row)
    Next

    _SKUs = New List(Of Sku)({
     New Sku With {.SKUId = 1, .ProductId = 1, .Description = "Mail"},
     New Sku With {.SKUId = 2, .ProductId = 1, .Description = "Magazine"},
     New Sku With {.SKUId = 3, .ProductId = 2, .Description = "Email"},
     New Sku With {.SKUId = 4, .ProductId = 2, .Description = "APIRequest"}
    })

    Dim items = _SKUs

    _people = New List(Of Person)({
      New Person With {.PersonID = 1, .FirstName = "Emily", .LastName = "X", .ProductId = 1, .SkuId = 1},
      New Person With {.PersonID = 2, .FirstName = "Brett", .LastName = "X", .ProductId = 2, .SkuId = 3}
                                  })
    For Each p In _people
      Dim row As DataRow = ds.Tables("tPeople").NewRow
      row("PersonId") = p.PersonId
      row("FirstName") = p.FirstName
      row("LastName") = p.LastName
      row("ProductId") = p.ProductId
      row("SkuId") = p.SkuId
      ds.Tables("tPeople").Rows.Add(row)
    Next

    For Each row As DataGridViewRow In dgv.Rows
      ArrangeValuesForSKUComboBox(row)
    Next

    _initialLoadDone = True
  End Sub

  Private Sub ArrangeValuesForSKUComboBox(row As DataGridViewRow)
    Dim productId = CInt(row.Cells("ProductId")?.Value)
    Dim skus = _SKUs.Where(Function(x) x.ProductId = productId).ToList().Select(Function(x) New With {Key .SkuId = x.SKUId, .SkuDesc = x.Description}).ToList()

    Dim cell = row.Cells("SKU")
    'Yeah I don't always work.  In this example I do, in others I won't.
    'For this reason I just want more ideas.  I don't care if you completely blow up how the binding is done and do something else entirely.
    Dim combobox = CType(cell, DataGridViewComboBoxCell)
    combobox.DataSource = skus
    combobox.ValueMember = "SKUId"
    combobox.DisplayMember = "SkuDesc"
    combobox.Value = skus.FirstOrDefault()?.SkuId
  End Sub

  Private Sub dgv_CellValueChanged(sender As Object, e As DataGridViewCellEventArgs) Handles dgv.CellValueChanged
    If _initialLoadDone Then
      Dim headerText As String = TryCast(sender, DataGridView).Columns(e.ColumnIndex).HeaderText
      If headerText = "PRODUCT" Then
        ArrangeValuesForSKUComboBox(dgv?.CurrentRow)
      End If
    End If
  End Sub

1 个答案:

答案 0 :(得分:3)

要在ComboBox中拥有相关(级联或主/从)DataGridView列,您可以按照以下步骤操作:

  1. 将从列的DataSource设置为所有可用值。

    目标: 此处的目标是防止首次加载时出现错误,因此所有从属组合框都可以正确显示值。

  2. 网格的Hanlde EditingControlShowing事件并检查当前单元格是否为从属组合单元格,然后使用e.Control类型的DataGridViewComboBoxEditingControl获取编辑控件。然后检查主组合单元格的值,并根据主组合单元格的值将编辑控件的DataSource属性设置为合适的值子集。如果master cell的值为null,则将数据源设置为null。

    目标: 这里的目标是设置从属组合的数据源,以便在从子组合中选择值时仅显示合适的值。

  3. 处理CellValueChanged并检查当前单元格是否为主组合,然后将依赖单元格的值设置为空。
    注意:您可以根据主单元格值将其设置为第一个可用的有效值,而不是将slave cell的值设置为null。

    目标: 这里的目标是防止奴隶组合在更改主组合的值后具有无效值,因此我们重置该值。

    < / LI>

    遵循上述规则,您可以根据需要拥有尽可能多的相关组合框。

    示例

    在下面的例子中,我有一个国家(Id,Name)表,一个State(Id,Name,CountryId)表和一个Population(CountryId,StateId,Population)表。我想使用国家和州的2个组合列和人口的文本列为人口表执行数据输入。我知道这不是一个普通的数据库设计,但它只是在网格中有主/从(从属)组合框列:

    Private Sub EditingControlShowing(sender As Object, _
        e As DataGridViewEditingControlShowingEventArgs) _
        Handles PopulationDataGridView.EditingControlShowing
    
        Dim grid = DirectCast(sender, DataGridView)
        If (grid.CurrentCell.ColumnIndex = 1) Then 'State column
            Dim combo = DirectCast(e.Control, DataGridViewComboBoxEditingControl)
            If (grid.CurrentRow.Cells(0).Value IsNot DBNull.Value) Then
                Dim data = Me.DataSet1.State.AsDataView()
                data.RowFilter = "CountryId = " + grid.CurrentRow.Cells(0).Value.ToString()
                combo.DataSource = data
            Else
                combo.DataSource = Nothing
            End If
        End If
    End Sub
    
    Private Sub CellValueChanged(sender As Object, e As DataGridViewCellEventArgs) _
        Handles PopulationDataGridView.CellValueChanged
        Dim grid = DirectCast(sender, DataGridView)
        If (e.ColumnIndex = 0 And e.RowIndex >= 0) Then 'Country Column
            grid.Rows(e.RowIndex).Cells(1).Value = DBNull.Value 'State Column 
        End If
    End Sub