在同一数据源中每行数据绑定一个控件
Databinding one control per row in the same datasource
我一直在努力解决一个与 Winforms 中的数据绑定有关的令人沮丧的问题。
我有一个数据源,是DataSet
中的一个DataTable
。这个 DataTable
有三行。我的表单上有三个 CheckBox
控件和一个 Button
。我想将每个 CheckBox
绑定到此数据源中的一行,并让数据源在 CheckBox
更新时反映 Checked
属性 中的值。我还希望通过调用 HasChanges()
和调用 GetChanges()
.
正确获取这些更改
单击 Button
时,将调用 EndCurrentEdit()
并传递绑定到的数据源,并使用 [=20= 检查 DataSet
的更改]方法。
但是,在尝试执行此操作时,我在调用 EndCurrentEdit()
后遇到了两种情况之一。
在第一种情况下,仅检测到第一个 CheckBox
的更改。在第二种情况下,所有其他 CheckBoxes
都更新为上次在调用 EndCurrentEdit()
.
时检查的 CheckBox
的值
在调用 EndCurrentEdit()
后查看 RowState
值,在场景 1 中,只有第一行的状态为 Modified
。在场景 2 中,只有第三行的状态为 Modified
。对于场景 2,用户是否更新了第三个 CheckBox
并不重要。
为了演示我的问题,我创建了一个简单的示例来演示它。
这是一个常用 Windows 表单,包含三个 CheckBox
控件和一个 Button
控件,所有控件都具有默认名称。
Option Strict On
Option Explicit On
Public Class Form1
Public ds As DataSet
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
ds = New DataSet()
Dim dt As New DataTable()
dt.Columns.Add("ID", GetType(Integer))
dt.Columns.Add("Selected", GetType(Boolean))
dt.Rows.Add(1, False)
dt.Rows.Add(2, False)
dt.Rows.Add(3, False)
dt.TableName = "Table1"
ds.Tables.Add(dt)
ds.AcceptChanges()
For i As Integer = 1 To 3
Dim bs As New BindingSource()
'After the call to Me.BindingContext(ds.Tables("Table1")).EndCurrentEdit() there are two scenarios:
'Scenario 1 - only changes to the first CheckBox are detected.
'Scenario 2 - when any CheckBox is checked, they all become checked.
'Uncomment the first and comment out the second to see Scenario 1.
'Uncomment the second and comment out the first to see Scenario 2.
'bs.DataSource = New DataView(ds.Tables("Table1")) 'Scenario 1
bs.DataSource = ds.Tables("Table1") 'Scenario 2
bs.Filter = "ID=" & i
Dim db As New Binding("Checked", bs, "Selected")
db.DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged
If i = 1
CheckBox1.DataBindings.Add(db)
ElseIf i = 2
CheckBox2.DataBindings.Add(db)
ElseIf i = 3
CheckBox3.DataBindings.Add(db)
End If
Next
End Sub
Private Sub Button1_Click( sender As Object, e As EventArgs) Handles Button1.Click
Me.BindingContext(ds.Tables("Table1")).EndCurrentEdit()
If ds.HasChanges()
MessageBox.Show("Number of rows changed: " & ds.GetChanges().Tables("Table1").Rows.Count)
ds.AcceptChanges()
End If
End Sub
End Class
我已经做了很多搜索,但一直无法弄清楚发生了什么,因此完全不知所措。感觉我想做的事情很简单,但我怀疑我一定是在某处误解了某些东西,或者错过了一些关于绑定过程的重要内容。
编辑
它已经在第二段中了,但为了清楚起见,这里是我正在尝试做的事情的基本轮廓:
- 我有一个
DataSet
包含一个 DataTable
和值。对于此示例,有两列。 ID
可以是任何 Integer
,'Selected' 可以是任何 Boolean
值。 None 其中永远是 Nothing
或 DBNull
。
- 我想将每一行绑定到一个
Checkbox
控件。每个 ID
值一个 CheckBox
。 Checked
属性 应绑定到 DataTable
中的 Selected
列。
- 当进行更改并且用户单击
Button
时,我希望能够使用DataSet
(即,如果用户更改了两个 Checkbox
控件的 Checked
属性,则会更新 2 行)。
编辑 2
感谢@RezaAghaei,我想出了一个解决方案。我从我的问题示例中改进了代码,并使所有控件都基于数据生成(并相应地定位它们自己)以使该示例易于复制和粘贴。此外,这在 DataView
上使用了 RowFilter
,而不是 BindingSource
.
的 Position
属性
Option Strict On
Option Explicit On
Public Class Form1
Public ds As DataSet
Private Sub Form1_Load( sender As Object, e As EventArgs) Handles MyBase.Load
ds = New DataSet()
Dim dt As New DataTable()
dt.Columns.Add("ID", GetType(Integer))
dt.Columns.Add("Selected", GetType(Boolean))
dt.Rows.Add(1, False)
dt.Rows.Add(2, False)
dt.Rows.Add(3, False)
dt.TableName = "Table1"
ds.Tables.Add(dt)
ds.AcceptChanges()
AddHandler dt.ColumnChanged, Sub(sender2 As Object, e2 As DataColumnChangeEventArgs)
e2.Row.EndEdit()
End Sub
For i As Integer = 0 To dt.Rows.Count-1
Dim cb As New CheckBox() With {.Text = "CheckBox" & i+1, .Location = New Point(10, 25 * (i))}
Dim dv As New DataView(dt)
dv.RowFilter = "ID=" & DirectCast(dt.Rows(i)(0), Integer)
Dim bs As New BindingSource(dv, Nothing)
Dim db As New Binding("Checked", bs, "Selected")
db.DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged
cb.DataBindings.Add(db)
Me.Controls.Add(cb)
Next
Dim btn As New Button()
btn.Location = New Point(10, 30 * dt.Rows.Count)
btn.Text = "Submit"
AddHandler btn.Click, AddressOf Button1_Click
Me.Controls.Add(btn)
End Sub
Private Sub Button1_Click( sender As Object, e As EventArgs)
'Me.BindingContext(ds.Tables("Table1")).EndCurrentEdit() 'Doesn't cut the mustard!
If ds.HasChanges()
MessageBox.Show("Number of rows changed: " & ds.GetChanges().Tables("Table1").Rows.Count)
ds.AcceptChanges()
Else
MessageBox.Show("Number of rows changed: 0")
End If
End Sub
End Class
关键是 DataTable
的 ColumnChanged
事件中对 EndEdit()
的调用(对 EndCurrentEdit()
的一般调用似乎并没有削减它),尽管我遇到的另一个问题是,如果这段代码在表单的 New()
方法中,则它不会工作,即使它在调用 InitializeComponent()
之后也是如此。我猜这是因为 Winforms 在调用 New()
之后进行了一些初始化,这是数据绑定正常工作所必需的。
我希望这个例子可以节省其他人我花在研究这个问题上的时间。
考虑这些更正,问题将得到解决:
- 要将控件绑定到列表的特定索引,请将控件绑定到包含该列表的绑定源,然后将绑定源的位置设置为特定索引。
- 向
CheckBox
控件添加数据绑定时,将更新模式设置为 OnPropertyChanged
。
- 处理
CheckBox
控件的 CheckedChanged
事件并调用网格的 Invalidate
方法以显示网格的变化。
- 在
CheckedChanged
中也调用 BindingSource
的 EndEdit
,CheckBox
绑定到 BindingSource
。
- 注意:调用
EndEdit
usnig BeginInvoke
让勾选复选框的所有过程(包括更新数据源)完成。
C# 示例
DataTable dt = new DataTable();
private void Form3_Load(object sender, EventArgs e)
{
dt.Columns.Add("Id");
dt.Columns.Add("Selected", typeof(bool));
dt.Rows.Add("1", true);
dt.Rows.Add("2", false);
dt.Rows.Add("3", true);
this.dataGridView1.DataSource = dt;
var chekBoxes = new CheckBox[] { checkBox1, checkBox2, checkBox3 };
for (int i = 0; i < dt.Rows.Count; i++)
{
var bs = new BindingSource(dt, null);
chekBoxes[i].DataBindings.Add("Checked", bs, "Selected",
true, DataSourceUpdateMode.OnPropertyChanged);
chekBoxes[i].CheckedChanged += (obj, arg) =>
{
this.dataGridView1.Invalidate();
var c = (CheckBox)obj;
var b = (BindingSource)(c.DataBindings[0].DataSource);
this.BeginInvoke(()=>{b.EndEdit();});
};
bs.Position = i;
}
}
VB 示例
Dim dt As DataTable = New DataTable()
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles MyBase.Load
dt.Columns.Add("Id")
dt.Columns.Add("Selected", GetType(Boolean))
dt.Rows.Add("1", True)
dt.Rows.Add("2", False)
dt.Rows.Add("3", True)
dt.AcceptChanges()
Me.DataGridView1.DataSource = dt
Dim chekBoxes = New CheckBox() {CheckBox1, CheckBox2, CheckBox3}
For i = 0 To dt.Rows.Count - 1
Dim bs = New BindingSource(dt, Nothing)
chekBoxes(i).DataBindings.Add("Checked", bs, "Selected", _
True, DataSourceUpdateMode.OnPropertyChanged)
AddHandler chekBoxes(i).CheckedChanged, _
Sub(obj, arg)
Me.DataGridView1.Invalidate()
Dim c = DirectCast(obj, CheckBox)
Dim b = DirectCast(c.DataBindings(0).DataSource, BindingSource)
Me.BeginInvoke(Sub() b.EndEdit())
End Sub
bs.Position = i
Next i
End Sub
我一直在努力解决一个与 Winforms 中的数据绑定有关的令人沮丧的问题。
我有一个数据源,是DataSet
中的一个DataTable
。这个 DataTable
有三行。我的表单上有三个 CheckBox
控件和一个 Button
。我想将每个 CheckBox
绑定到此数据源中的一行,并让数据源在 CheckBox
更新时反映 Checked
属性 中的值。我还希望通过调用 HasChanges()
和调用 GetChanges()
.
单击 Button
时,将调用 EndCurrentEdit()
并传递绑定到的数据源,并使用 [=20= 检查 DataSet
的更改]方法。
但是,在尝试执行此操作时,我在调用 EndCurrentEdit()
后遇到了两种情况之一。
在第一种情况下,仅检测到第一个 CheckBox
的更改。在第二种情况下,所有其他 CheckBoxes
都更新为上次在调用 EndCurrentEdit()
.
CheckBox
的值
在调用 EndCurrentEdit()
后查看 RowState
值,在场景 1 中,只有第一行的状态为 Modified
。在场景 2 中,只有第三行的状态为 Modified
。对于场景 2,用户是否更新了第三个 CheckBox
并不重要。
为了演示我的问题,我创建了一个简单的示例来演示它。
这是一个常用 Windows 表单,包含三个 CheckBox
控件和一个 Button
控件,所有控件都具有默认名称。
Option Strict On
Option Explicit On
Public Class Form1
Public ds As DataSet
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
ds = New DataSet()
Dim dt As New DataTable()
dt.Columns.Add("ID", GetType(Integer))
dt.Columns.Add("Selected", GetType(Boolean))
dt.Rows.Add(1, False)
dt.Rows.Add(2, False)
dt.Rows.Add(3, False)
dt.TableName = "Table1"
ds.Tables.Add(dt)
ds.AcceptChanges()
For i As Integer = 1 To 3
Dim bs As New BindingSource()
'After the call to Me.BindingContext(ds.Tables("Table1")).EndCurrentEdit() there are two scenarios:
'Scenario 1 - only changes to the first CheckBox are detected.
'Scenario 2 - when any CheckBox is checked, they all become checked.
'Uncomment the first and comment out the second to see Scenario 1.
'Uncomment the second and comment out the first to see Scenario 2.
'bs.DataSource = New DataView(ds.Tables("Table1")) 'Scenario 1
bs.DataSource = ds.Tables("Table1") 'Scenario 2
bs.Filter = "ID=" & i
Dim db As New Binding("Checked", bs, "Selected")
db.DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged
If i = 1
CheckBox1.DataBindings.Add(db)
ElseIf i = 2
CheckBox2.DataBindings.Add(db)
ElseIf i = 3
CheckBox3.DataBindings.Add(db)
End If
Next
End Sub
Private Sub Button1_Click( sender As Object, e As EventArgs) Handles Button1.Click
Me.BindingContext(ds.Tables("Table1")).EndCurrentEdit()
If ds.HasChanges()
MessageBox.Show("Number of rows changed: " & ds.GetChanges().Tables("Table1").Rows.Count)
ds.AcceptChanges()
End If
End Sub
End Class
我已经做了很多搜索,但一直无法弄清楚发生了什么,因此完全不知所措。感觉我想做的事情很简单,但我怀疑我一定是在某处误解了某些东西,或者错过了一些关于绑定过程的重要内容。
编辑
它已经在第二段中了,但为了清楚起见,这里是我正在尝试做的事情的基本轮廓:
- 我有一个
DataSet
包含一个DataTable
和值。对于此示例,有两列。ID
可以是任何Integer
,'Selected' 可以是任何Boolean
值。 None 其中永远是Nothing
或DBNull
。 - 我想将每一行绑定到一个
Checkbox
控件。每个ID
值一个CheckBox
。Checked
属性 应绑定到DataTable
中的Selected
列。 - 当进行更改并且用户单击
Button
时,我希望能够使用DataSet
(即,如果用户更改了两个Checkbox
控件的Checked
属性,则会更新 2 行)。
编辑 2
感谢@RezaAghaei,我想出了一个解决方案。我从我的问题示例中改进了代码,并使所有控件都基于数据生成(并相应地定位它们自己)以使该示例易于复制和粘贴。此外,这在 DataView
上使用了 RowFilter
,而不是 BindingSource
.
Position
属性
Option Strict On
Option Explicit On
Public Class Form1
Public ds As DataSet
Private Sub Form1_Load( sender As Object, e As EventArgs) Handles MyBase.Load
ds = New DataSet()
Dim dt As New DataTable()
dt.Columns.Add("ID", GetType(Integer))
dt.Columns.Add("Selected", GetType(Boolean))
dt.Rows.Add(1, False)
dt.Rows.Add(2, False)
dt.Rows.Add(3, False)
dt.TableName = "Table1"
ds.Tables.Add(dt)
ds.AcceptChanges()
AddHandler dt.ColumnChanged, Sub(sender2 As Object, e2 As DataColumnChangeEventArgs)
e2.Row.EndEdit()
End Sub
For i As Integer = 0 To dt.Rows.Count-1
Dim cb As New CheckBox() With {.Text = "CheckBox" & i+1, .Location = New Point(10, 25 * (i))}
Dim dv As New DataView(dt)
dv.RowFilter = "ID=" & DirectCast(dt.Rows(i)(0), Integer)
Dim bs As New BindingSource(dv, Nothing)
Dim db As New Binding("Checked", bs, "Selected")
db.DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged
cb.DataBindings.Add(db)
Me.Controls.Add(cb)
Next
Dim btn As New Button()
btn.Location = New Point(10, 30 * dt.Rows.Count)
btn.Text = "Submit"
AddHandler btn.Click, AddressOf Button1_Click
Me.Controls.Add(btn)
End Sub
Private Sub Button1_Click( sender As Object, e As EventArgs)
'Me.BindingContext(ds.Tables("Table1")).EndCurrentEdit() 'Doesn't cut the mustard!
If ds.HasChanges()
MessageBox.Show("Number of rows changed: " & ds.GetChanges().Tables("Table1").Rows.Count)
ds.AcceptChanges()
Else
MessageBox.Show("Number of rows changed: 0")
End If
End Sub
End Class
关键是 DataTable
的 ColumnChanged
事件中对 EndEdit()
的调用(对 EndCurrentEdit()
的一般调用似乎并没有削减它),尽管我遇到的另一个问题是,如果这段代码在表单的 New()
方法中,则它不会工作,即使它在调用 InitializeComponent()
之后也是如此。我猜这是因为 Winforms 在调用 New()
之后进行了一些初始化,这是数据绑定正常工作所必需的。
我希望这个例子可以节省其他人我花在研究这个问题上的时间。
考虑这些更正,问题将得到解决:
- 要将控件绑定到列表的特定索引,请将控件绑定到包含该列表的绑定源,然后将绑定源的位置设置为特定索引。
- 向
CheckBox
控件添加数据绑定时,将更新模式设置为OnPropertyChanged
。 - 处理
CheckBox
控件的CheckedChanged
事件并调用网格的Invalidate
方法以显示网格的变化。 - 在
CheckedChanged
中也调用BindingSource
的EndEdit
,CheckBox
绑定到BindingSource
。 - 注意:调用
EndEdit
usnigBeginInvoke
让勾选复选框的所有过程(包括更新数据源)完成。
C# 示例
DataTable dt = new DataTable();
private void Form3_Load(object sender, EventArgs e)
{
dt.Columns.Add("Id");
dt.Columns.Add("Selected", typeof(bool));
dt.Rows.Add("1", true);
dt.Rows.Add("2", false);
dt.Rows.Add("3", true);
this.dataGridView1.DataSource = dt;
var chekBoxes = new CheckBox[] { checkBox1, checkBox2, checkBox3 };
for (int i = 0; i < dt.Rows.Count; i++)
{
var bs = new BindingSource(dt, null);
chekBoxes[i].DataBindings.Add("Checked", bs, "Selected",
true, DataSourceUpdateMode.OnPropertyChanged);
chekBoxes[i].CheckedChanged += (obj, arg) =>
{
this.dataGridView1.Invalidate();
var c = (CheckBox)obj;
var b = (BindingSource)(c.DataBindings[0].DataSource);
this.BeginInvoke(()=>{b.EndEdit();});
};
bs.Position = i;
}
}
VB 示例
Dim dt As DataTable = New DataTable()
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles MyBase.Load
dt.Columns.Add("Id")
dt.Columns.Add("Selected", GetType(Boolean))
dt.Rows.Add("1", True)
dt.Rows.Add("2", False)
dt.Rows.Add("3", True)
dt.AcceptChanges()
Me.DataGridView1.DataSource = dt
Dim chekBoxes = New CheckBox() {CheckBox1, CheckBox2, CheckBox3}
For i = 0 To dt.Rows.Count - 1
Dim bs = New BindingSource(dt, Nothing)
chekBoxes(i).DataBindings.Add("Checked", bs, "Selected", _
True, DataSourceUpdateMode.OnPropertyChanged)
AddHandler chekBoxes(i).CheckedChanged, _
Sub(obj, arg)
Me.DataGridView1.Invalidate()
Dim c = DirectCast(obj, CheckBox)
Dim b = DirectCast(c.DataBindings(0).DataSource, BindingSource)
Me.BeginInvoke(Sub() b.EndEdit())
End Sub
bs.Position = i
Next i
End Sub