DataGridView级联/相关ComboBox列 [英] DataGridView Cascading/Dependent ComboBox Columns
问题描述
所以我不时在一个遗留的应用程序的Winforms上工作,并且对于绑定对象在任何时候都不熟悉最佳实践。基本上我有三个部分,我有两个人,他们可能只有一个产品,但该产品可能会有可能有不同的SKU套。有没有办法从第一个组合框的值触发组合框的事件和人口?我一直在寻找,我正在找到基本的数据,只是如何绑定一个组合框(我可以做得那么好)或做一些你如何绑定它。触发依赖父变更后更改数据集时不绑定。例如:
POCOS:
公共课人
Public Property PersonID As Integer
公共属性FirstName As String
公共属性LastName As String
公共属性ProductId As Integer
公共属性SkuId As Integer
结束类
公共类产品
公共属性ProductId As Integer
公共属性描述As String
结束类
公共类Sku
公共属性SKUId As Integer
公共属性ProductId As Integer
公共属性描述As String
结束类
$主要代码示例(基本UI实际上只有一个标记为ds的Datset与几乎相同的Person和Product POCOS与数据表匹配。一个datagridview'dgv',其列绑定到Person EXCEPT中的数据列称为SKU ta没有约束力,因为我想在事实之后绑定,那是我失败的惨败。
更新9-13-2016
我可以得到以下代码在某些大规模解决方案原因我这样做)。它基本上不会执行将cell()转换为datagridviewcomboboxcell并将其忽略并跳过该行的行。没有理由,它只是跳过它。我想知道如果在较大的类上,datagrid视图可能会变得腐败或某事。
主要代码:
私人_ =新列表(个人)
私人_产品作为列表(产品)=新列表(产品)
私人_SKUs作为列表(Sku)=新列表(Sku)
私人_initialLoadDone = False
Private _currentRow As Integer? = Nothing
Private Sub DynamicComboBoxDoubleFill_Load(发件人作为对象,作为EventArgs)处理MyBase.Load
_products =新列表(的产品)({
新产品与{.ProductId = 1,.Description =离线},
新产品与{.ProductId = 2,.Description =在线}
})
Dim s =
对于每个o在_products
Dim row As DataRow = ds.Tables(tProducts)。NewRow
row(ProductId)= o.ProductId
row )= o.Description
ds.Tables(tProducts)。Rows.Add(row)
下一个
_SKUs =新列表(Sku)({
新Sku与{.SKUId = 1,.ProductId = 1,.Description =邮件},
新的Sku与{.SKUId = 2,.ProductId = 1,.Description =杂志},
新的Sku与{.SKUId = 3,.ProductId = 2,.Description =电子邮件},
新的Sku与{.SKUId = 4,.Produc tId = 2,.Description =APIRequest}
})
Dim items = _SKUs
_people =新列表(人)({
新人与{.PersonID = 1,.FirstName =Emily,.LastName =X,.ProductId = 1,.SkuId = 1},
新人与{.PersonID = 2,.FirstName =Brett,.LastName =X,.ProductId = 2,.SkuId = 3}
})
对于每个p在_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)
下一步
对于每行作为DataGridViewRow在dgv.Rows
ArrangeValuesForSKUComboBox(行)
下一个
_initialLoadDone = True
End Sub
Private Sub ArrangeValuesForSKUComboBox(行为DataGridViewRow)
D im productId = CInt(row.Cells(ProductId)?。Value)
Dim skus = _SKUs.Where(Function(x)x.ProductId = productId).ToList()。Select(Function(x)New使用{Key .SkuId = x.SKUId,.SkuDesc = x.Description})ToList()
Dim cell = row.Cells(SKU)
'总是工作在这个例子中,我做的,在其他的我不会。
'因此,我只想要更多的想法。我不在乎你是否完全破坏了约束力如何完成,并完成其他任务。
Dim combobox = CType(cell,DataGridViewComboBoxCell)
combobox.DataSource = skus
combobox.ValueMember =SKUId
combobox.DisplayMember =SkuDesc
组合框。 Value = skus.FirstOrDefault()?SkuId
End Sub
Private Sub dgv_CellValueChanged(sender As Object,e As DataGridViewCellEventArgs)处理dgv.CellValueChanged
如果_initialLoadDone然后
Dim headerText As String = TryCast(sender,DataGridView).Columns(e.ColumnIndex).HeaderText
如果headerText =PRODUCT然后
ArrangeValuesForSKUComboBox(dgv?.CurrentRow)
End If
End If
End Sub
要在 DataGridView
中依赖(级联或主/从) ComboBox
列,可以按照以下步骤操作:
-
将slave列的
DataSource
设置为所有可用值。
目标: 这里的目标是防止在首次加载时出现渲染错误,因此所有从属组合框都可以显示值
-
Hanlde
从属组合。EditingControlShowing
事件的网格,并检查当前单元格是从组合单元格,然后使用DataGridViewComboBoxEditingControl
的e.Control
获取编辑控件。然后检查主组合单元格的值,并将编辑控件的DataSource
属性设置为基于主组合单元格的值的合适子集。如果主单元格的值为空,则将数据源设置为空。 -
处理
CellValueChanged
并检查当前单元格是否为主组合,然后设置值注意:不要将从单元的值设置为空,而是可以根据主单元格值将其设置为第一个可用的有效值。
目标: 这里的目标是防止从属组合在主组合的值更改后具有无效值,所以我们重新设置值。
按照上述规则,您可以根据需要拥有尽可能多的依赖组合框。 / p>
示例
在下面的示例中,我有一个国家(Id,名称)表,状态(Id,Name,CountryId)表和Population(CountryId,StateId,Population)表。而且我想使用2个国家/地区和州的组合列和一个文本列的总体数据表进行数据输入。我知道这不是一个普通的数据库设计,但它只是在网格中拥有主/从(依赖)组合框列的例子:
Private Sub EditingControlShowing(sender As Object,_ $ b $ as As DataGridViewEditingControlShowingEventArgs)_
句柄PopulationDataGridView.EditingControlShowing
Dim grid = DirectCast(sender,DataGridView)
如果(grid.CurrentCell.ColumnIndex = 1)然后'状态列
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作为DataGridViewCellEv entArgs)_
句柄PopulationDataGridView.CellValueChanged
Dim grid = DirectCast(sender,DataGridView)
If(e.ColumnIndex = 0 And e.RowIndex> = 0)然后'Country Column
grid.Rows(e.RowIndex).Cells(1).Value = DBNull.Value'状态列
如果
结束Sub
So I work from time to time in Winforms on a legacy app and am not familiar with best practices at all times with binding objects. Basically I have a three part set where I have two people, they may have only one product, but that product could cause the possibility to have different sets of SKUs. Is there a way to trigger an event and population of a combobox from the values of a first combobox? I have been looking around and I am either finding basic data of just how to bind a combobox(I can do that fine) or do something with how you bind it. Not binding after a dependent parent change is triggered and changing the dataset. Example below:
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
Main code example (basic UI really only has a Datset labeled 'ds' that matches nearly identically the Person and Product POCOS with datatables. A datagridview 'dgv' whose columns are bound to data in Person EXCEPT for a column called SKU ta has no binding as I want to bind it after the fact and that is where I am failing miserably at.
Update 9-13-2016 I can get the below code to work EXCEPT in certain large scale solutions(the whole reason I did this). It basically will NOT execute the line that casts the cell() to a datagridviewcomboboxcell and ignores it and jumps over the line. No reason why, it just jumps over it. I am wondering if on larger classes the datagrid views can become corrupt or something.
Main Code:
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
To have dependent (cascading or master/slave) ComboBox
columns in DataGridView
, you can follow this steps:
Set
DataSource
of slave column to all available values.Goal: Here the goal is prevent rendering errors at first load, so all slave combo boxes can show value correctly.
Hanlde
EditingControlShowing
event of the grid and check if the current cell is slave combo cell, then get the editing control usinge.Control
which is of typeDataGridViewComboBoxEditingControl
. Then check the value of master combo cell and set theDataSource
property of editing control to a suitable subset of values based on the value of master combo cell. If the value of master cell is null, set the data source to null.Goal: Here the goal is setting data source of slave combo to show only suitable values when selecting values from slave combo.
Handle
CellValueChanged
and check if the current cell is master combo, then set the value for dependent cell to null.
Note: Instead of setting the value of slave cell to null, you can set it to first available valid value based on master cell value.Goal: Here the goal is prevent the slave combo to have invalid values after the value of master combo changed, so we reset the value.
Following above rules you can have as many dependent combo boxes as you need.
Example
In below example I have a Country (Id, Name) table, a State(Id, Name, CountryId) table and a Population(CountryId, StateId, Population) table. And I want to perform data entry for Population table using 2 combo columns for country and state and a text column for population. I know this is not a normal db design, but it's just for example of having master/slave (dependent) combo box columns in grid:
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
这篇关于DataGridView级联/相关ComboBox列的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!