麦田守望者's profile异想空间PhotosBlogLists Tools Help

Blog


    10/18/2006

    解决ComboBox控件绑定到数据时多次触发SelectedIndexChanged事件的问题

    在Windows Forms应用程序中,我们经常会在ComboBox控件上绑定一个数据源,为用户提供友好的界面输入元素。如果应用程序的控制逻辑依赖ComboBox控件的SelectedIndexChanged事件,我们会发现在绑定数据的时候(即设置DataSource、DisplayMember和ValueMember),该事件会被触发多次。然而事与愿违,那并非是我们想要的。重要的是,无法从事件代码引用的SelectedValue属性中得到预期的绑定数据。
    为什么会这样?究其内部机制,不甚了解,这就是ComboBox控件的特性。那么得找个办法达到自己的目的。
     
    思路是这样:把ComboBox控件绑定到ArrayList对象,它的每一项是某种类型的对象,包含显示给用户的文本和其对应的值(用户选择后,我们要取回的数据)。如此一来,SelectedIndexChanged事件只会被触发一次,在事件代码中引用控件的SelectedItem属性能够获得数据源中相应的对象,将它转换回正确的类型就即可读取我们想要得数据了。
    重要的一点是,ArrayList中的成员对象必须重写基类的ToString方法。用户在ComboBox控件中看到的文本依赖于ToString方法的返回值。
     
    接下来的工作就是用代码实现想法了。首先我会创建一个NameValueItem类,它拥有两个属性Name和Value,Value引用可读性文本,Name则引用被选择的值。如上所述,NameValueItem类重写了Object类的ToString方法,返回Value引用的文本。然后将若干NameValueItem对象添加进ArrayList对象,再把此ArrayList对象设置到ComboBox控件的DataSource即可。
    SelectedIndexChanged事件代码照旧就可以了。SelectedItem和SelectedValue属性都引用当前被选择的对象,将他们引用的值转换回NameValueItem类型便可进行后续的处理。示例代码如下:
     
    下面的代码包含NameValueItem类和窗体中与数据绑定有关的主要代码。请注意代码中,两行重要的注释。
    class NameValueItem {
      private string _name;
      private string _value;
    
      public NameValueItem() : this(String.Empty, String.Empty) {}
      public NameValueItem(string name, string value) {
        _name = name;
        _value = value;
      }
    
      public string Name {
        get { return _name; }
        set { _name = value; }
      }
    
      public string Value {
        get { return _value; }
        set { _value = value; }
      }
    
      // *** IMPORTANT! User will see this Value in the ComboBox control.
      public override string ToString() {
        return Value;
      }
    }
    
    public class MyForm : System.Windows.Forms.Form {
      private System.Windows.Forms.ComboBox cboProgrammingLang;
    
      private void InitializeComponent() {
        this.cboProgrammingLang = new System.Windows.Forms.ComboBox();
        cboProgrammingLang.SelectedIndexChanged +=
          new System.EventHandler(this.cboProgrammingLang_SelectedIndexChanged);
        this.Controls.Add(cboProgrammingLang);
    
        this.Load += new System.EventHandler(this.MainForm_Load);
      }
    
      private void MainForm_Load(object sender, System.EventArgs e) {
        ArrayList al = new ArrayList();
    
        al.Add(new NameValueItem("1", "Visual Basic"));
        al.Add(new NameValueItem("2", "Visual C#"));
        al.Add(new NameValueItem("3", "Visual FoxPro"));
        
        // *** That's enough. The DisplayMember and ValueMember are unnecessary.
        this.cboProgrammingLang.DataSource = al;
      }
    
      private void cboProgrammingLang_SelectedIndexChanged(object sender, System.EventArgs e) {
        if (this.cboProgrammingLang.SelectedIndex > -1) {
          NameValueItem item = this.cboProgrammingLang.SelectedValue as NameValueItem;
          MessageBox.Show(
            String.Format("Name: {0}; Value: {1}", item.Name, item.Value));
        }
      }
    }
    
    结束语。这里的方法只是迂回找到了一条小径,并没有从根本上解决题目中的问题。实际上,根据实验,使用DataSource、DisplayMember和ValueMember属性实现数据绑定,会导致ComboBox控件的SelectedIndexChanged事件被触发三次,而在最后一次中,从SelectedValue能够获得预期的数据。那么据此使用计数器也可以解决问题,但这是不可靠的,同时也增加了逻辑的复杂性。