绑定的 DataGridView 未更新以显示信息 + 排序问题
Bound DataGridView not updating to display information + sorting issues
我有一个绑定的 DataGridView1 和几个绑定的 TextBoxes 和 DateTimePickers。
一切都很好,除了当我试图同时完成这两件事时:
- 按特定列(日期)对 DataGridView1 进行排序。
- 根据 DateTimePicker 更改同一列日期。
我的想法是我像这样对 DataGridView1 进行排序(我在表单加载中设置了它):
DataGridView1.Sort(DataGridView1.Columns(45), ListSortDirection.Ascending)
(第 45 列只是我的 "MyDateColumn" 列)
后来,当我像这样更改日期列时:
DirectCast(MyBindingSource.Current, DataRowView).Item("MyDateColumn") = MyDateTimePicker.Value.AddDays(100)
DataGridView1 应自动并立即按日期排序。
好的,这就是问题所在。排序有效 - 但由于某种原因,我的 DataGridView 拒绝反映 DirectCast 设置的日期,直到我 select 另一行 。当我 select 另一行时 - then 它会更改以反映我刚刚发送的日期。
我已经尝试了所有我能想到的 - 我终于找到了解决方案:
Me.BindingContext(MyBindingSource).EndCurrentEdit()
这很好用——但前提是我不使用:
DataGridView1.Sort(DataGridView1.Columns(45), ListSortDirection.Ascending)
如果我正在对我的 DataGridView 进行排序,我的绑定似乎只是...在我 .EndCurrentEdit 之后停止工作。如果我注释掉 ListSortDirection.Ascending 行,那么效果很好!
我尝试使用 DataGridView1.Refresh() 代替 .EndCurrentEdit(),但它非常慢 - 我宁愿完全避免使用它。
有人有什么建议吗?
DGV 在单元格验证后显示编辑后的值,这似乎是 DGV 的一个问题。
为了做到这一点(被视为解决方法),您可以在单元格上设置值后 simulate/force 自己进行网格验证(调用下面的方法):
Private Sub SimulateDGVValidation()
Try
Dim currentCell As DataGridViewCell = Me.DataGridView1.CurrentCell
If (currentCell) IsNot Nothing Then
Dim c As Integer = IIf(currentCell.ColumnIndex > 1, currentCell.ColumnIndex - 1, 1)
Me.DataGridView1.CurrentCell = Me.DataGridView1(c, currentCell.RowIndex)
Else
Me.DataGridView1.CurrentCell = Me.DataGridView1(0, 0)
End If
Application.DoEvents()
'Return in the initial cell
If (currentCell) IsNot Nothing Then
Me.DataGridView1.CurrentCell = currentCell
End If
Catch ex As Exception
Console.WriteLine(ex.ToString)
End Try
End Sub
既然BindingSource数据源是可以排序的,就用BindingSource.Sort 属性代替排序控件(你的DataGridView)。 columnName
是用于排序的Column名称(可以指定多个Column):
myBindingSource.Sort = $"{columnName} ASC"
然后,当用户设置DateTimePicker.Value
时,使用ValueChanged
事件更新单元格值,对应于当前行的DateTime
列:
方法一:绑定DateTimePicker
在DateTimePicker.ValueChanged
中调用了BindingSource.EndEdit()方法,立即将新值应用到数据源。
注意 1:要使此方法按预期工作,BindingSource 数据源必须是 DataTable 或实现 IEditableObject 的其他容器接口.
DateTimePicker1.DataBindings.Add(
New Binding("Value", myBindingSource, "MyDateTimeColumn", False, DataSourceUpdateMode.OnValidation))
'(...)
Private Sub DateTimePicker1_ValueChanged(sender As Object, e As EventArgs) Handles DateTimePicker1.ValueChanged
myBindingSource.EndEdit()
End Sub
注 2:DateTimePicker 不支持 null
/DbNull
值。如果 DataSouce 可能包含 DBNull
个值,它可能会变得 不稳定 。您可能需要从中创建自定义控件,以管理其行为。否则,参见方法 2
方法二:解绑DateTimePicker
在代码中设置 DateTimePicker 值时使用 userSetValue
字段,以防止 ValueChanged
事件中的过程更新 DateTime 列的值。这样,事件只会在用户手动更改日期时更新列。
Private columnName As String = String.Empty
Private userSetValue As Boolean = True
Private Sub DateTimePicker1_ValueChanged(sender As Object, e As EventArgs) Handles DateTimePicker1.ValueChanged
If (Not userSetValue) Then Return
If (DataGridView1.CurrentRow Is Nothing) OrElse
(DataGridView1.CurrentRow.Cells($"{columnName}").ValueType IsNot GetType(Date)) Then Return
'DataGridView1.SuspendLayout()
DataGridView1.CurrentRow.Cells($"{columnName}").Value = DateTimePicker1.Value
myBindingSource.EndEdit()
'DataGridView1.ResumeLayout(False)
End Sub
► 测试使用和不使用 SuspendLayout()
/ ResumeLayout()
的两种方法。
RowPostPaint
事件(例如)可用于更新 DateTimePicker.Value
相关列的值:
Private Sub DataGridView1_RowPostPaint(sender As Object, e As DataGridViewRowPostPaintEventArgs) Handles DataGridView1.RowPostPaint
userSetValue = False
Dim currentValue = DataGridView1.CurrentRow.Cells("MyDateTimeColumn").Value
If currentValue Is Nothing OrElse currentValue Is DBNull.Value Then currentValue = Date.Now
DateTimePicker1.Value = DirectCast(currentValue, Date)
userSetValue = True
End Sub
使用这两种方法中的任何一种,结果如下:
我有一个绑定的 DataGridView1 和几个绑定的 TextBoxes 和 DateTimePickers。
一切都很好,除了当我试图同时完成这两件事时:
- 按特定列(日期)对 DataGridView1 进行排序。
- 根据 DateTimePicker 更改同一列日期。
我的想法是我像这样对 DataGridView1 进行排序(我在表单加载中设置了它):
DataGridView1.Sort(DataGridView1.Columns(45), ListSortDirection.Ascending)
(第 45 列只是我的 "MyDateColumn" 列)
后来,当我像这样更改日期列时:
DirectCast(MyBindingSource.Current, DataRowView).Item("MyDateColumn") = MyDateTimePicker.Value.AddDays(100)
DataGridView1 应自动并立即按日期排序。
好的,这就是问题所在。排序有效 - 但由于某种原因,我的 DataGridView 拒绝反映 DirectCast 设置的日期,直到我 select 另一行 。当我 select 另一行时 - then 它会更改以反映我刚刚发送的日期。
我已经尝试了所有我能想到的 - 我终于找到了解决方案:
Me.BindingContext(MyBindingSource).EndCurrentEdit()
这很好用——但前提是我不使用:
DataGridView1.Sort(DataGridView1.Columns(45), ListSortDirection.Ascending)
如果我正在对我的 DataGridView 进行排序,我的绑定似乎只是...在我 .EndCurrentEdit 之后停止工作。如果我注释掉 ListSortDirection.Ascending 行,那么效果很好!
我尝试使用 DataGridView1.Refresh() 代替 .EndCurrentEdit(),但它非常慢 - 我宁愿完全避免使用它。
有人有什么建议吗?
DGV 在单元格验证后显示编辑后的值,这似乎是 DGV 的一个问题。 为了做到这一点(被视为解决方法),您可以在单元格上设置值后 simulate/force 自己进行网格验证(调用下面的方法):
Private Sub SimulateDGVValidation()
Try
Dim currentCell As DataGridViewCell = Me.DataGridView1.CurrentCell
If (currentCell) IsNot Nothing Then
Dim c As Integer = IIf(currentCell.ColumnIndex > 1, currentCell.ColumnIndex - 1, 1)
Me.DataGridView1.CurrentCell = Me.DataGridView1(c, currentCell.RowIndex)
Else
Me.DataGridView1.CurrentCell = Me.DataGridView1(0, 0)
End If
Application.DoEvents()
'Return in the initial cell
If (currentCell) IsNot Nothing Then
Me.DataGridView1.CurrentCell = currentCell
End If
Catch ex As Exception
Console.WriteLine(ex.ToString)
End Try
End Sub
既然BindingSource数据源是可以排序的,就用BindingSource.Sort 属性代替排序控件(你的DataGridView)。 columnName
是用于排序的Column名称(可以指定多个Column):
myBindingSource.Sort = $"{columnName} ASC"
然后,当用户设置DateTimePicker.Value
时,使用ValueChanged
事件更新单元格值,对应于当前行的DateTime
列:
方法一:绑定DateTimePicker
在DateTimePicker.ValueChanged
中调用了BindingSource.EndEdit()方法,立即将新值应用到数据源。
注意 1:要使此方法按预期工作,BindingSource 数据源必须是 DataTable 或实现 IEditableObject 的其他容器接口.
DateTimePicker1.DataBindings.Add(
New Binding("Value", myBindingSource, "MyDateTimeColumn", False, DataSourceUpdateMode.OnValidation))
'(...)
Private Sub DateTimePicker1_ValueChanged(sender As Object, e As EventArgs) Handles DateTimePicker1.ValueChanged
myBindingSource.EndEdit()
End Sub
注 2:DateTimePicker 不支持 null
/DbNull
值。如果 DataSouce 可能包含 DBNull
个值,它可能会变得 不稳定 。您可能需要从中创建自定义控件,以管理其行为。否则,参见方法 2
方法二:解绑DateTimePicker
在代码中设置 DateTimePicker 值时使用 userSetValue
字段,以防止 ValueChanged
事件中的过程更新 DateTime 列的值。这样,事件只会在用户手动更改日期时更新列。
Private columnName As String = String.Empty
Private userSetValue As Boolean = True
Private Sub DateTimePicker1_ValueChanged(sender As Object, e As EventArgs) Handles DateTimePicker1.ValueChanged
If (Not userSetValue) Then Return
If (DataGridView1.CurrentRow Is Nothing) OrElse
(DataGridView1.CurrentRow.Cells($"{columnName}").ValueType IsNot GetType(Date)) Then Return
'DataGridView1.SuspendLayout()
DataGridView1.CurrentRow.Cells($"{columnName}").Value = DateTimePicker1.Value
myBindingSource.EndEdit()
'DataGridView1.ResumeLayout(False)
End Sub
► 测试使用和不使用 SuspendLayout()
/ ResumeLayout()
的两种方法。
RowPostPaint
事件(例如)可用于更新 DateTimePicker.Value
相关列的值:
Private Sub DataGridView1_RowPostPaint(sender As Object, e As DataGridViewRowPostPaintEventArgs) Handles DataGridView1.RowPostPaint
userSetValue = False
Dim currentValue = DataGridView1.CurrentRow.Cells("MyDateTimeColumn").Value
If currentValue Is Nothing OrElse currentValue Is DBNull.Value Then currentValue = Date.Now
DateTimePicker1.Value = DirectCast(currentValue, Date)
userSetValue = True
End Sub
使用这两种方法中的任何一种,结果如下: