DataGridView Cascading/Dependent 组合框列
DataGridView Cascading/Dependent ComboBox Columns
所以我时不时地在 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' 的 Datset,它几乎完全匹配具有数据表的 Person 和 Product POCOS。一个 datagridview 'dgv' 其列绑定到Person 中的数据,除了名为 SKU 的列没有绑定,因为我想事后绑定它,这就是我失败的地方。
2016 年 9 月 13 日更新
我可以让下面的代码在某些大型解决方案中工作(我这样做的全部原因)。它基本上不会执行将 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
要在 DataGridView
中具有依赖(级联或 master/slave)ComboBox
列,您可以按照以下步骤操作:
将从属列的 DataSource
设置为所有可用值。
目标:这里的目标是防止第一次加载时出现渲染错误,所以所有的从属组合框都可以正确显示值。
Hanlde EditingControlShowing
事件并检查当前单元格是否为从组合单元格,然后使用 e.Control
类型的 DataGridViewComboBoxEditingControl
获取编辑控件].然后检查主组合单元格的值,并根据主组合单元格的值将编辑控件的DataSource
属性设置为一个suitable值子集。如果主单元格的值为空,则将数据源设置为空。
目标: 这里的目标是设置slave combo的数据源在选择值时只显示suitable值来自奴隶组合。
处理CellValueChanged
并检查当前单元格是否为主组合,然后将依赖单元格的值设置为空。
注意:您可以根据主单元格值将其设置为第一个可用的有效值,而不是将从单元格的值设置为空。
目标:这里的目标是防止master combo的值改变后slave combo有无效值,所以我们重新设置值。
按照以上规则,您可以根据需要拥有任意数量的依赖组合框。
例子
在下面的示例中,我有一个国家 (Id, Name) table、一个州 (Id, Name, CountryId) table 和一个人口 (CountryId, StateId, Population) table.我想为人口 table 执行数据输入,使用国家和州的 2 个组合列和人口的文本列。我知道这不是正常的数据库设计,但这只是在网格中具有 master/slave(相关)组合框列的示例:
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
所以我时不时地在 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' 的 Datset,它几乎完全匹配具有数据表的 Person 和 Product POCOS。一个 datagridview 'dgv' 其列绑定到Person 中的数据,除了名为 SKU 的列没有绑定,因为我想事后绑定它,这就是我失败的地方。
2016 年 9 月 13 日更新
我可以让下面的代码在某些大型解决方案中工作(我这样做的全部原因)。它基本上不会执行将 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
要在 DataGridView
中具有依赖(级联或 master/slave)ComboBox
列,您可以按照以下步骤操作:
将从属列的
DataSource
设置为所有可用值。目标:这里的目标是防止第一次加载时出现渲染错误,所以所有的从属组合框都可以正确显示值。
Hanlde
EditingControlShowing
事件并检查当前单元格是否为从组合单元格,然后使用e.Control
类型的DataGridViewComboBoxEditingControl
获取编辑控件].然后检查主组合单元格的值,并根据主组合单元格的值将编辑控件的DataSource
属性设置为一个suitable值子集。如果主单元格的值为空,则将数据源设置为空。目标: 这里的目标是设置slave combo的数据源在选择值时只显示suitable值来自奴隶组合。
处理
CellValueChanged
并检查当前单元格是否为主组合,然后将依赖单元格的值设置为空。
注意:您可以根据主单元格值将其设置为第一个可用的有效值,而不是将从单元格的值设置为空。目标:这里的目标是防止master combo的值改变后slave combo有无效值,所以我们重新设置值。
按照以上规则,您可以根据需要拥有任意数量的依赖组合框。
例子
在下面的示例中,我有一个国家 (Id, Name) table、一个州 (Id, Name, CountryId) table 和一个人口 (CountryId, StateId, Population) table.我想为人口 table 执行数据输入,使用国家和州的 2 个组合列和人口的文本列。我知道这不是正常的数据库设计,但这只是在网格中具有 master/slave(相关)组合框列的示例:
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