.Net Winforms DataGridView数据绑定

时间:2019-02-27 15:03:53

标签: vb.net winforms datagridview

我支持财务计划团队使用的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

结束班级

2 个答案:

答案 0 :(得分:1)

很难描述您所描述的内容。因此,我将保持简单,重点介绍Person示例。

List<T>用作DataSourceDataGridView即可。但是,它将仅显示“非公开”的“公开”公开的“属性”。

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中创建列。