这对我来说真的像个错误,但也许一些数据绑定大师可以启发我? (我的WinForms数据绑定知识非常有限。)
我有ComboBox
绑定到已排序的DataView
。如果DataView
中的项目属性发生变化,导致项目出现问题,则SelectedItem
中的ComboBox
不会保持同步状态。它似乎指向某个地方完全随机。这是一个错误,还是我在数据绑定中遗漏了什么?
以下是重现问题的示例应用程序。您所需要的只是Button
和ComboBox
:
public partial class Form1 : Form
{
private DataTable myData;
public Form1()
{
this.InitializeComponent();
this.myData = new DataTable();
this.myData.Columns.Add("ID", typeof(int));
this.myData.Columns.Add("Name", typeof(string));
this.myData.Columns.Add("LastModified", typeof(DateTime));
this.myData.Rows.Add(1, "first", DateTime.Now.AddMinutes(-2));
this.myData.Rows.Add(2, "second", DateTime.Now.AddMinutes(-1));
this.myData.Rows.Add(3, "third", DateTime.Now);
this.myData.DefaultView.Sort = "LastModified DESC";
this.comboBox1.DataSource = this.myData.DefaultView;
this.comboBox1.ValueMember = "ID";
this.comboBox1.DisplayMember = "Name";
}
private void saveStuffButton_Click(object sender, EventArgs e)
{
DataRowView preUpdateSelectedItem = (DataRowView)this.comboBox1.SelectedItem;
// OUTPUT: SelectedIndex = 0; SelectedItem.Name = third
Debug.WriteLine(string.Format("SelectedIndex = {0:N0}; SelectedItem.Name = {1}", this.comboBox1.SelectedIndex, preUpdateSelectedItem["Name"]));
this.myData.Rows[0]["LastModified"] = DateTime.Now;
DataRowView postUpdateSelectedItem = (DataRowView)this.comboBox1.SelectedItem;
// OUTPUT: SelectedIndex = 2; SelectedItem.Name = second
Debug.WriteLine(string.Format("SelectedIndex = {0:N0}; SelectedItem.Name = {1}", this.comboBox1.SelectedIndex, postUpdateSelectedItem["Name"]));
// FAIL!
Debug.Assert(object.ReferenceEquals(preUpdateSelectedItem, postUpdateSelectedItem));
}
}
澄清:
DataRows
切断的DataTable
数组,但这会引起额外的麻烦。答案 0 :(得分:2)
只需将BindingContext添加到ComboBox:
this.comboBox1.DataSource = this.myData.DefaultView;
this.comboBox1.BindingContext = new BindingContext();
this.comboBox1.ValueMember = "ID";
this.comboBox1.DisplayMember = "Name";
顺便说一句,尽量不要为你的小部件(comboBox1,...)保留自动生成的名称,它是脏的。 :-P
答案 1 :(得分:1)
我目前看到的唯一有希望的解决方案是将组合框绑定到分离的数据源,然后每次“真正的”DataView更改时更新它。这是我到目前为止所拥有的。似乎工作,但(1)这是一个完全黑客,(2)它根本不会很好地扩展。
表格声明:
private DataView shadowView;
表单初始化:
this.comboBox1.DisplayMember = "Value";
this.comboBox1.ValueMember = "Key";
this.shadowView = new DataView(GlobalData.TheGlobalTable, null, "LastModified DESC", DataViewRowState.CurrentRows);
this.shadowView.ListChanged += new ListChangedEventHandler(shadowView_ListChanged);
this.ResetComboBoxDataSource(null);
然后是黑客:
private void shadowView_ListChanged(object sender, ListChangedEventArgs e)
{
this.ResetComboBoxDataSource((int)this.comboBox1.SelectedValue);
}
private void ResetComboBoxDataSource(int? selectedId)
{
int selectedIndex = 0;
var detached = new KeyValuePair<int, string>[this.shadowView.Count];
for (int i = 0; i < this.shadowView.Count; i++)
{
int id = (int)this.shadowView[i]["ID"];
detached[i] = new KeyValuePair<int, string>(id, (string)this.shadowView[i]["Name"]);
if (id == selectedId)
{
selectedIndex = i;
}
}
this.comboBox1.DataSource = detached;
this.comboBox1.SelectedIndex = selectedIndex;
}
必须在Dispose中分离事件处理程序:
this.shadowView.ListChanged -= new ListChangedEventHandler(shadowView_ListChanged);
答案 2 :(得分:0)
您的示例对其更新的列上的数据进行排序。更新发生时,行的顺序会发生变化。组合框使用索引来跟踪其选定的项目,因此当项目排序时,索引指向不同的行。在更新行之前,您需要捕获comboxBox1.SelectedItem
的值,并在更新完成后将其设置回来:
DataRowView selected = (DataRowView)this.comboBox1.SelectedItem;
this.myData.Rows[0]["LastModified"] = DateTime.Now;
this.comboBox1.SelectedItem = selected;
答案 3 :(得分:0)
从体系结构的角度来看,重新绑定DataSource时必须清除SelectedItem,因为DataBinder不知道您的SelectedItem是否会持久存在。
从功能角度来看,DataBinder可能无法确保您的旧DataSource中的SelectedItem在您的新DataSource中是相同的(它可以是具有相同SelectedItem ID的不同DataSource)。
它更像是一个应用程序功能或自定义控件功能,而不是通用的数据绑定过程。
恕我直言,如果你想将SelectedItem保留在重新绑定上,你有选择:
使用持久性选项创建可重复使用的自定义控件/自定义DataBinder,尝试使用所有数据验证设置SelectedItem(使用DataSource / item标识确保项目有效性)
使用表单/应用程序上下文(如ViewState for ASP.NET)专门在表单上保留它。
如果DataSource未更改且DataBind未被调用,则.NET市场上的某些控件可帮助您从自己的持久DataSource 重新绑定(包括选择)控件。那是最好的实践。