代码之家  ›  专栏  ›  技术社区  ›  Dave Mateer

如何在重排序数据源时维护ComboBox.SelectEditem引用?

  •  1
  • Dave Mateer  · 技术社区  · 14 年前

    这对我来说真的像一个bug,但也许一些数据绑定专家可以启发我?(我的winforms数据绑定知识非常有限。)

    我有一个 ComboBox 绑定到已排序的 DataView . 当 数据视图 更改以便重新调用项, SelectedItem 在我的 组合框 不保持同步。它似乎指向了一个完全随机的地方。这是个错误,还是我在数据绑定中遗漏了什么?

    下面是一个重现问题的示例应用程序。你只需要一个 Button 和A 组合框 :

    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 ,但这会引起额外的头痛。
    4 回复  |  直到 13 年前
        1
  •  2
  •   Mr.Wizard naktepe    13 年前

    只需将bindingContext添加到组合框:

    this.comboBox1.DataSource = this.myData.DefaultView;  
    this.comboBox1.BindingContext = new BindingContext();  
    this.comboBox1.ValueMember = "ID";  
    this.comboBox1.DisplayMember = "Name";
    

    顺便说一下,不要为小部件保留自动生成的名称(combobox1,…),这是不干净的。- P

        2
  •  1
  •   Dave Mateer    14 年前

    我现在看到的唯一有希望的解决方案是将组合框绑定到分离的数据源,然后在每次“真实”数据视图更改时更新它。这是我目前所拥有的。似乎是可行的,但(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);
    
        3
  •  0
  •   Rory    14 年前

    您的示例对它更新的列上的数据进行排序。更新发生时,行的顺序将更改。组合框使用索引来跟踪其选定的项,因此当对项进行排序时,索引将指向另一行。你需要抓住 comboxBox1.SelectedItem 更新行之前,并在更新完成后将其设置回:

            DataRowView selected = (DataRowView)this.comboBox1.SelectedItem;
            this.myData.Rows[0]["LastModified"] = DateTime.Now;
            this.comboBox1.SelectedItem = selected;
    
        4
  •  0
  •   JoeBilly    14 年前

    从体系结构的角度来看,重新绑定数据源时必须清除selecteditem,因为databinder不知道selecteditem是否会持久。

    从功能的角度来看,databinder可能无法确保旧数据源中的selecteditem在新数据源中是相同的(它可以是具有相同selecteditem id的不同数据源)。

    与其说它是一个通用的数据绑定过程,不如说它是一个应用程序特性或自定义控件特性。

    嗯,如果要将selecteditem保留在重新绑定上,您可以选择这些选项:

    • 使用persistence选项创建一个可重用的自定义控件/自定义databinder,该选项尝试用所有数据验证设置selecteditem(使用数据源/项标识来确保项的有效性)

    • 使用表单/应用程序上下文(如用于asp.net的viewstate)将其具体持久化在表单上。

    .NET市场上的一些控件正在帮助您从它们自己的持久化数据源重新绑定(包括选择)控件 如果数据源未更改且数据绑定未被调用。这是最好的做法。