I support a winforms application that is used by a financial planning team. One of the main screens of the program, referred to as the Planning Grid, uses a datagridview to show changes in the financial plan, week over week, for product groups that the company sells.
The datagridview displays financial measures, by week, for selected product groups. There is one column for each week of the year (total of 52 columns). There are 15 financial measures per product group, so performing a product group selection in the UI adds 15 sequential rows of data to the datagridview.
Currently, the program gets the data back from the business layer in a 2 dimensional array. It then builds the rows programatically by looping through the array and using each row of the array to create a row in the data grid.
My question relates to data binding. I would like to refactor the code to use data binding to automatically build the data grid. Essentially, I want to return a list of business objects (PlannedProductGroup) and set that list as the data source for the datagridview. My problem however, is that I don't know how to handle the binding. When I have done datagridview binding in the past each object in the data source equates to one row in the grid. This time however, one object would be creating 15 rows in the grid, and I am not sure if this is possible using data binding.
Is what I want to do possible? If so, does anyone know how to implement this more complex data binding scenario?
Edit - Adding Snippets of current implementation that I want to refactor
'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
Edit 2 - Adding POC code I created a quick Proof of Concept project to show what I want to do. Each person object should be two rows in the data grid, one row for the FavoriteColors list and one row for the FavoriteFoods list.
Public Class Form1
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
End Class
DataGridView? Changing it to be data bound shouldn't be a "complex data binding scenario". - Zer0