.NET WinForms DataGridView单业务对象对应多行的数据绑定实现问询
嘿,这个需求完全可行!核心就是把单个PlannedProductGroup对象拆分成多个对应不同财务指标的子对象,让每个子对象对应DataGridView的一行,这样就能用数据绑定自动生成表格了。下面我给你一步步拆解实现方案:
核心思路:拆分业务对象为行级模型
DataGridView的数据绑定逻辑是「一行对应一个对象」,所以我们需要把原来一个产品组对应的15个指标行,转换成15个独立的行级对象。每个行级对象包含:产品组ID、指标类型、行头文本、52周的数据,以及行的只读/可见性配置。
步骤1:定义行级数据模型
先创建一个适配DataGridView行的模型类,用来承载单条指标行的所有信息:
Public Enum Measure ProductGroupId = 0 RetailPrice = 1 PriceMultiple = 2 ' 补充剩下的12个枚举值 End Enum Public Class PlanningRowModel ' 产品组ID(对应表格第一列) Public Property ProductGroupId As Integer ' 指标类型(用来区分是哪类财务指标行) Public Property MeasureType As Measure ' 行头显示文本 Public Property HeaderText As String ' 52周的数据(用Object类型适配不同数据格式:文本、数值、下拉框值) Public Property WeeklyValues As Object() ' 行是否只读 Public Property IsReadOnly As Boolean ' 行是否可见 Public Property IsVisible As Boolean End Class
步骤2:转换业务数据为行级模型集合
把从业务层获取的PlannedProductGroup列表,转换成PlanningRowModel的集合。这里完全复用你原来的业务逻辑,只是把手动创建行的逻辑改成创建行级对象:
' 从业务层获取产品组规划数据 Dim plannedGroups As List(Of PlannedProductGroup) = Load_Planning_Details_As_Business_Objects() Dim planningRows As New List(Of PlanningRowModel)() For Each group In plannedGroups If Product_Group_Checked(group.ID) Then ' 1. 生成「产品组名称」对应的行(对应原代码第一个手动创建的行) Dim productGroupRow = New PlanningRowModel() With { .ProductGroupId = group.ID, .MeasureType = Measure.ProductGroupId, .HeaderText = group.Name, .WeeklyValues = New Object(51){}, .IsReadOnly = True, .IsVisible = True } ' 填充周数据(复用原代码的组合文本逻辑) For weekCounter As Integer = 0 To 51 Dim priceMultipleId = group.GetWeeklyValue(Measure.PriceMultiple, weekCounter) Dim priceMultipleName = _priceMultiples.Find(Function(p) p.ID = priceMultipleId).Name Dim complexOffer = If(group.GetWeeklyValue(Measure.Complex, weekCounter) = 1, " *", "") Dim retailPrice = Math.Abs(group.GetWeeklyValue(Measure.RetailPrice, weekCounter)).ToString("C2") & complexOffer productGroupRow.WeeklyValues(weekCounter) = $"{priceMultipleName} {retailPrice}" Next planningRows.Add(productGroupRow) ' 2. 生成「零售价」对应的行 Dim retailPriceRow = New PlanningRowModel() With { .ProductGroupId = group.ID, .MeasureType = Measure.RetailPrice, .HeaderText = " Retail Price", .WeeklyValues = New Object(51){}, .IsReadOnly = _isReadOnly, .IsVisible = ToolStripCheckBox_Retail.Checked } For weekCounter As Integer = 0 To 51 retailPriceRow.WeeklyValues(weekCounter) = group.GetWeeklyValue(Measure.RetailPrice, weekCounter) Next planningRows.Add(retailPriceRow) ' 3. 生成「价格倍数」对应的行 Dim priceMultipleRow = New PlanningRowModel() With { .ProductGroupId = group.ID, .MeasureType = Measure.PriceMultiple, .HeaderText = " Price Multiple", .WeeklyValues = New Object(51){}, .IsReadOnly = _isReadOnly, .IsVisible = ToolStripCheckBox_Retail.Checked } For weekCounter As Integer = 0 To 51 priceMultipleRow.WeeklyValues(weekCounter) = group.GetWeeklyValue(Measure.PriceMultiple, weekCounter) Next planningRows.Add(priceMultipleRow) ' ... 剩下的12个指标行,按照同样的逻辑生成即可 End If Next
注:这里假设
PlannedProductGroup有GetWeeklyValue(measure As Measure, week As Integer)方法来获取对应指标的周数据,你可以根据实际业务层结构调整这个逻辑。
步骤3:配置DataGridView数据绑定
用BindingSource包装行级模型集合,然后绑定到DataGridView:
Dim bindingSource As New BindingSource() bindingSource.DataSource = planningRows _dgvPlanningDetails.DataSource = bindingSource ' 手动添加列:第一列是产品组ID,然后添加52周的列 _dgvPlanningDetails.Columns.Clear() ' 添加产品组ID列 Dim productGroupIdCol As New DataGridViewTextBoxColumn() productGroupIdCol.DataPropertyName = "ProductGroupId" productGroupIdCol.HeaderText = "Product Group ID" _dgvPlanningDetails.Columns.Add(productGroupIdCol) ' 添加52周的列 For week As Integer = 1 To 52 Dim weekCol As New DataGridViewTextBoxColumn() weekCol.HeaderText = $"Week {week}" weekCol.DataPropertyName = $"WeeklyValues[{week-1}]" ' 绑定到数组的对应索引 _dgvPlanningDetails.Columns.Add(weekCol) Next
步骤4:处理UI细节(行头、样式、单元格类型)
原来手动设置的行头、单元格样式、下拉框类型,现在可以通过DataGridView的事件来处理:
1. 设置行头文本
Private Sub _dgvPlanningDetails_RowPostPaint(sender As Object, e As DataGridViewRowPostPaintEventArgs) Handles _dgvPlanningDetails.RowPostPaint Dim rowModel = TryCast(_dgvPlanningDetails.Rows(e.RowIndex).DataBoundItem, PlanningRowModel) If rowModel IsNot Nothing Then _dgvPlanningDetails.Rows(e.RowIndex).HeaderCell.Value = rowModel.HeaderText ' 同步行的可见性和只读属性 _dgvPlanningDetails.Rows(e.RowIndex).Visible = rowModel.IsVisible _dgvPlanningDetails.Rows(e.RowIndex).ReadOnly = rowModel.IsReadOnly End If End Sub
2. 设置单元格样式(黑周背景、格式)
Private Sub _dgvPlanningDetails_CellFormatting(sender As Object, e As DataGridViewCellFormattingEventArgs) Handles _dgvPlanningDetails.CellFormatting ' 只处理周列(索引1到52) If e.ColumnIndex >= 1 And e.ColumnIndex <= 52 Then Dim weekNumber = e.ColumnIndex ' 对应原代码的weekCounter+1 ' 设置黑周背景色 If _blackoutCollection.Contains(weekNumber.ToString()) Then e.CellStyle.BackColor = Color.LightSlateGray Else e.CellStyle.BackColor = Color.LightGray End If Dim rowModel = TryCast(_dgvPlanningDetails.Rows(e.RowIndex).DataBoundItem, PlanningRowModel) If rowModel Is Nothing Then Return ' 零售价行设置货币格式 If rowModel.MeasureType = Measure.RetailPrice Then e.CellStyle.Format = "C2" End If ' 价格倍数行替换为下拉框单元格 If rowModel.MeasureType = Measure.PriceMultiple Then Dim comboCell = New DataGridViewComboBoxCell() comboCell.DataSource = _priceMultiples.AsEnumerable() comboCell.ValueMember = "ID" comboCell.DisplayMember = "Name" comboCell.Value = e.Value _dgvPlanningDetails.Rows(e.RowIndex).Cells(e.ColumnIndex) = comboCell End If End If End Sub
对比原代码的优势
- 代码更清晰:业务数据转换和UI渲染分离,后续修改指标或周数只需调整模型和转换逻辑,不用动UI代码
- 可维护性更高:避免手动循环创建行的重复代码,减少出错概率
- 原生数据绑定支持:可以利用BindingSource的排序、过滤功能,后续扩展更方便
内容的提问来源于stack exchange,提问作者Scope Creep
相关产品推荐
相关产品推荐

