我支持财务计划团队使用的winforms应用程序。该程序的主要屏幕之一,称为“计划网格”,使用datagridview来显示公司所销售产品组的财务计划的变化。
datagridview按星期显示所选产品组的财务指标。一年中的每个星期都有一列(总共52列)。 每个产品组有15种财务指标,因此在UI中执行产品组选择会在datagridview中添加15个连续的数据行。
当前,该程序以二维数组的形式从业务层获取数据。然后,通过遍历数组并使用数组的每一行在数据网格中创建一行,以编程方式构建行。
我的问题与数据绑定有关。我想重构代码以使用数据绑定来自动构建数据网格。本质上,我想返回一个业务对象列表(PlannedProductGroup),并将该列表设置为datagridview的数据源。但是,我的问题是我不知道如何处理绑定。在过去完成datagridview绑定后,数据源中的每个对象都等于网格中的一行。但是,这次,一个对象将在网格中创建15行,并且我不确定是否可以使用数据绑定。
我想做些什么吗?如果是这样,是否有人知道如何实现这种更复杂的数据绑定方案?
编辑-添加要重构的当前实现的片段
'create rows
Dim _planningDetailsArray(,) As Single = Load_Planning_Details()
For Each productGroup As ProductGroup In _productGroups
If Product_Group_Checked(productGroup.ID) Then
'Measure.ProductGroupId
productGroupId = Convert.ToInt32(_planningDetailsArray((productGroupCounter * Measure.Count) + Measure.ProductGroupId, 0))
productGroupName = _productGroups.Find(Function(p) p.ID = productGroupId).Name
myDataGridViewRow = New DataGridViewRow
myDataGridViewRow.Cells.Add(NewDataGridViewIntegerCell(productGroupId))
For weekCounter As Integer = 0 To 51
Dim priceMultipleId As Integer = CInt(_planningDetailsArray((productGroupCounter * Measure.Count) + Measure.PriceMultiple, weekCounter))
Dim priceMultipleName As String = _priceMultiples.Find(Function(p) p.ID = priceMultipleId).Name
Dim complexOffer = String.Empty
If CInt(_planningDetailsArray((productGroupCounter * Measure.Count) + Measure.Complex, weekCounter)) = 1 Then
complexOffer = " *"
End If
Dim retailPrice As String = Math.Abs(_planningDetailsArray((productGroupCounter * Measure.Count) + Measure.RetailPrice, weekCounter)).ToString("C2") & complexOffer
Dim cellText = $"{priceMultipleName} {retailPrice}"
_myDataGridViewCell = NewDataGridViewTextCell(cellText)
If _blackoutCollection.Contains((weekCounter + 1).ToString) Then
_myDataGridViewCell.Style.BackColor = Color.LightSlateGray
Else
_myDataGridViewCell.Style.BackColor = Color.LightGray
End If
myDataGridViewRow.Cells.Add(_myDataGridViewCell)
Next
myDataGridViewRow.Cells.Add(NewDataGridViewIntegerCell(Measure.ProductGroupId))
myDataGridViewRow.HeaderCell.Value = productGroupName
myDataGridViewRow.ReadOnly = True
_dgvPlanningDetails.Rows.Add(myDataGridViewRow)
'Measure.Retail_Price
myDataGridViewRow = New DataGridViewRow
myDataGridViewRow.Cells.Add(NewDataGridViewIntegerCell(productGroupId))
For weekCounter As Integer = 0 To 51
myDataGridViewRow.Cells.Add(NewDataGridViewSingleCellDash(_planningDetailsArray((productGroupCounter * Measure.Count) + Measure.RetailPrice, weekCounter), False, True))
Next
myDataGridViewRow.Cells.Add(NewDataGridViewIntegerCell(Measure.RetailPrice))
myDataGridViewRow.HeaderCell.Value = " Retail Price"
myDataGridViewRow.Visible = ToolStripCheckBox_Retail.Checked
myDataGridViewRow.ReadOnly = _isReadOnly
myDataGridViewRow.DefaultCellStyle.Format = "C2"
_dgvPlanningDetails.Rows.Add(myDataGridViewRow)
'Measure.Price_Multiple_ID
myDataGridViewRow = New DataGridViewRow
myDataGridViewRow.Cells.Add(NewDataGridViewIntegerCell(productGroupId))
For weekCounter As Integer = 0 To 51
myDataGridViewRow.Cells.Add(NewDataGridViewComboBoxCell(_priceMultiples.AsEnumerable(), Convert.ToInt32(_planningDetailsArray((productGroupCounter * Measure.Count) + Measure.PriceMultiple, weekCounter))))
Next
myDataGridViewRow.Cells.Add(NewDataGridViewIntegerCell(Measure.PriceMultiple))
myDataGridViewRow.HeaderCell.Value = " Price Multiple"
myDataGridViewRow.Visible = ToolStripCheckBox_Retail.Checked
myDataGridViewRow.ReadOnly = _isReadOnly
_dgvPlanningDetails.Rows.Add(myDataGridViewRow)
End If
Next
编辑2-添加POC代码 我创建了一个快速的概念验证项目,以显示我想要做什么。每个人对象在数据网格中应为两行,FavoriteColors列表为一行,FavoriteFoods列表为一行。
公共类表格1
Public Sub New ()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim people = New List(Of Person)
Dim person1 = New Person
person1.Name = "Jim"
person1.FavoriteColors = New List(Of String)(New String() {"Red", "Green", "Blue"})
person1.FavoriteFoods = New List(Of String)(New String() {"Pizza", "Salad", "Burger"})
Dim person2 = New Person
person2.Name = "Bob"
person2.FavoriteColors = New List(Of String)(New String() {"Yellow", "Black", "Pink"})
person2.FavoriteFoods = New List(Of String)(New String() {"Hotdog", "French Fries", "Steak"})
people.Add(person1)
people.Add(person2)
DataGridView1.DataSource = people
End Sub
Private Class Person
Public Property Name As String
Public Property FavoriteColors As List(Of String)
Public Property FavoriteFoods As List(Of String)
End Class
结束班级
答案 0 :(得分:1)
很难描述您所描述的内容。因此,我将保持简单,重点介绍Person
示例。
将List<T>
用作DataSource
到DataGridView
即可。但是,它将仅显示“非公开”的“公开”公开的“属性”。
在Person
类示例中,网格将仅显示“名称”列。而且,这是有道理的……网格将不会知道如何使单个“单元”值等于值的集合。
因此,如果要将列表中的每个项目显示为网格中的“列”…。然后,您将需要执行此操作。我不确定绑定或其他机制是否有帮助,但是,我相信创建给定List<T>
会返回DataTable
的方法并不是很困难,该方法已按照您的描述进行了设置
鉴于每个Person
都有两个(2)列表(我在原始示例中假设15个),这意味着每个Person.
都有两行。如果这是正确的,则大约您唯一需要担心的是……您将需要多少列Person
,以便每个人“可以”在以下一项中拥有不同数量的物品(颜色,食物)列表。
很明显,您需要的列数将是Person
列表中所有people
中最大的列表(颜色,食物)。我猜在原始情况下总是会是52,但是,请谨慎检查,因为这样可以确保不会发生崩溃。
要提供帮助,需要一种方法来获取DataTable
的列数。此GetMaxColumns
方法采用List<Person>
并从最大的“颜色”和“食物”列表中返回计数。这将确保无论任何颜色或食物清单的大小,我们都能保持入境。可能如下图所示……
Private Function GetMaxColumns(people As List(Of Person)) As Int32
Dim max = 0
For Each person In people
If (person.FavoriteColors.Count > max) Then
max = person.FavoriteColors.Count
End If
If (person.FavoriteFoods.Count > max) Then
max = person.FavoriteFoods.Count
End If
Next
Return max
End Function
接下来,方法GetDataTable
接受一个List<Person>
并返回一个DataTable
,其中包含给定List<Person>
的适当列数。在这里可以方便地命名列。
Private Function GetDataTable(people As List(Of Person)) As DataTable
Dim dt = New DataTable()
Dim maxColumns = GetMaxColumns(people)
For index = 0 To maxColumns
dt.Columns.Add()
Next
Return dt
End Function
FillDataTable
方法使用List<Person>
,然后DataTable
然后如前所述从DataTable
填充List<Person>
。每个人都有两行,但是名称列下的第二行将为空,因为它的名称与前一行相同。
在下面的代码中,循环从列表中的每个Person
开始。名称将添加到该行,然后在“颜色”列表中循环以添加每个颜色值。然后添加第二行,其中包含“食物”列表中的值。该名称不会添加到第二行。
Private Sub FillDataTable(people As List(Of Person), dt As DataTable)
Dim dr As DataRow
Dim curCol = 0
For Each person In people
dr = dt.NewRow()
curCol = 0
dr(curCol) = person.Name.ToString()
curCol += 1
For Each favColor In person.FavoriteColors
dr(curCol) = favColor
curCol += 1
Next
dt.Rows.Add(dr)
dr = dt.NewRow()
curCol = 1
For Each favFood In person.FavoriteFoods
dr(curCol) = favFood
curCol += 1
Next
dt.Rows.Add(dr)
Next
End Sub
最后,将所有这些放在一起可能看起来像下面的…。
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim people = New List(Of Person)
Dim person = New Person
person.Name = "Jim"
person.FavoriteColors = New List(Of String)(New String() {"Red", "Green", "Blue"})
person.FavoriteFoods = New List(Of String)(New String() {"Pizza", "Salad", "Burger"})
people.Add(person)
person = New Person
person.Name = "Bob"
person.FavoriteColors = New List(Of String)(New String() {"Yellow", "Black", "Pink"})
person.FavoriteFoods = New List(Of String)(New String() {"Hotdog", "French Fries", "Steak"})
people.Add(person)
person = New Person
person.Name = "John"
person.FavoriteColors = New List(Of String)(New String() {"Purple", "Olive Grey", "Polka Dot"})
person.FavoriteFoods = New List(Of String)(New String() {"Ice Cream", "Fish", "Crutons"})
people.Add(person)
Dim GridTable = GetDataTable(people)
FillDataTable(people, GridTable)
DataGridView1.DataSource = GridTable
End Sub
希望这会有所帮助。
答案 1 :(得分:0)
这取决于数据库中数据的结构。例如,关系数据是否存储在52列中,每周一列?查询是否对该数据执行PIVOT以生成52列?是否存在一个查询来填充现有2D数组?多个查询?
无论如何,如果您可以重构代码并将查询结果放入DataTable中,则只需将DataTable这样绑定到DataGridView:
myDataGridView.DataSource = myDataTable
只需确保将DataGridView的AutoGenerateColumns属性设置为True。绑定数据就是这些的全部。但是,您必须意识到像这样的简单绑定将显示DataTable中的所有字段。您必须手动将不想在代码中显示的列设置为Visible = False。
相反,您可以创建一个数据传输对象(DTO),它实际上只是一个只有属性的类。然后,为查询结果中的每个 row 实例化一个DTO对象,并将所有这些DTO添加到List(of T)中。然后,将该List(Of T)绑定到DataGridView:
Dim myResults As List(Of PlanningDetailDTO) = myFunction.ReadTheData()
myDataGridView.DataSource = myResults
但是我真的不想创建一个包含52个以上字段的DTO类。 DataTable方法的优点是,它将根据查询结果在DataTable中创建列。