C# ComboBox的SelectedValue

(原创内容,elig首发于博客园,转载请注明出处)

今天在修改一个以前的项目时发现如下问题:

IDE环境 : VS2010

private void frmAdd_Load(object sender, EventArgs e)
{
InitializeComboBox();

}

private void InitializeComboBox()
{
cboCategory.DropDownStyle
= ComboBoxStyle.DropDownList;
cboCategory.DataSource
= mainForm.CategoryList;
cboCategory.DisplayMember
= "CategoryName";
cboCategory.ValueMember
= "CategoryID";
cboSencondaryCategory.DropDownStyle
= ComboBoxStyle.DropDownList;
cboSencondaryCategory.DataSource
= mainForm.SecondaryList;
cboSencondaryCategory.DisplayMember
= "SecondaryCategoryName";
cboSencondaryCategory.ValueMember
= "SecondaryCategoryID";
        //(原创内容,elig首发于博客园,转载请注明出处)
/*如果没有传进来对应的数据,则按初始值(-1)显示*/
cboCategory.SelectedIndex
= 1; //为了方便阅读,这里改为常量显示
cboSencondaryCategory.SelectedIndex
= 1; //为了方便阅读,这里改为常量显示

}

private void cboCategory_SelectedValueChanged(object sender, EventArgs e)
{
if (cboCategory.SelectedIndex == -1)
{
cboSencondaryCategory.DataSource
= mainForm.SecondaryList;
cboSencondaryCategory.DisplayMember
= "SecondaryCategoryName";
cboSencondaryCategory.ValueMember
= "SecondaryCategoryID";
}
else
{
cboSencondaryCategory.DataSource
= secondaryCategoryBLL.SelectByCategoryID((int)cboCategory.SelectedValue);
cboSencondaryCategory.DisplayMember
= "SecondaryCategoryName";
cboSencondaryCategory.ValueMember
= "SecondaryCategoryID";
}

}

在这种情况下,(int)cboCategory.SelectedValue部分,无论如何得不到正确的结果,一直显示转换出错。照理是不应该有这样的问题的。

经过研究发现,一模一样的代码(cboCategory_SelectedValueChanged()内的内容),在SelectedIndexChanged事件中也一样无法正确获得所需要的结果。

但是,在SelectionChangeCommitted()中则可以正常工作.

(原创内容,elig首发于博客园,转载请注明出处)

于是,我对此做了几个测试.过程如下:

首先,测试几个模块里分别添加如下几行:

int id = Convert.ToInt32(cboCategory.SelectedValue);
MessageBox.Show(id.ToString());

1.在cboCategory_SelectedIndexChanged()   cboCategory_SelectedValueChanged()中添加,编译无法通过,报错如下:

   无法将类型为“myProject.Model.Category”的对象强制转换为类型“System.IConvertible”。

2.在普通方法或者load事件里,添加,则可以正常通过。

(原创内容,elig首发于博客园,转载请注明出处)

然后,测试代码改为如下:

Category id = (Category)(cboCategory.SelectedValue);
if (id == null)
{
MessageBox.Show(
"Test");
}

1.在普通方法里调用,第一行可以正确编译通过,id == null 条件成立

2.在load事件,以及SelectionChangeCommitted,SelectedIndexChanged,SelectedValueChanged事件里,都报错如下:

   无法将类型为“System.Int32”的对象强制转换为类型“myProject.Model.Category”。

最后,测试如下代码:

MessageBox.Show(cboCategory.SelectedIndex.GetType().ToString());
String str
= cboCategory.SelectedValue.ToString();
MessageBox.Show(str);
int id = Convert.ToInt32(str);
MessageBox.Show(id.ToString());

1.load事件  普通方法 以及   SelectionChangeCommitted事件 都可以得到预期结果。

    type : System.Int32

2.而SelectionChangeCommitted,SelectedIndexChanged事件里

    type : System.Int32

    str = myProject.Model.Category

    转int后出错

(原创内容,elig首发于博客园,转载请注明出处)

虽然当时没有想明白问题到底出在哪里,但是解决方法就是上面提到的,把需要实现的代码写到SelectionChangeCommitted事件里,就可以正常地得到预期的结果。

项目结束后花了点时间仔细研究了下这个问题,因为同样的代码放在不同的地方却会出现如此丰富多彩的结果,实在让人忍不住。

最后发现,我们如果在SelectedIndexChanged,SelectedValueChanged两者任意一个事件里,把测试代码加到如下if判断里:


if (cboCategory.SelectedIndex >= 1)

//测试代码

那么3个事件都能正常编译通过。

注意:问题就出在这个红色的1上,在SelectedIndexChanged,SelectedValueChanged里,只能判断到index >= 1

有人就要问了,comboBox的index不是从0开始的吗?

非常正确!

但是只要把条件改为 >= 0 那么窗体加载时必定报错。

我们来看看这3个事件的区别:

SelectedIndexChanged,SelectedValueChanged是在任何情况下,只要改变了它们监视的值(index或者valueMember)就会触发对应事件

而我们都知道,在控件生成的时候,这2个值不可避免地要发生改变。也就是说,每次改变都会触发这个事件。

而SelectionChangeCommitted事件,官方的翻译解释是:当从下拉列表中选择项而下拉列表关闭时发生。

但是实际是这个翻译是不准确的,因为我装的不是英文的VS环境,所以无从知晓原文如何,但是实际上,该事件个人理解,更准确的翻译是,用户对该控件的选择项作出任何改变时发生。

这个“任何”包括:鼠标点击,获得焦点的情况下方向键上下选择。

(原创内容,elig首发于博客园,转载请注明出处)

在我的问题中,SelectedIndexChanged,SelectedValueChanged无法正确运作,是因为同样的代码在这两个事件中,无法对index = 0的item做处理,或者说SelectedValue的值不明确。从index = 1开始,SelectedValue的(int)转换非常正常。但是正因为这两个事件连系统对comboBox控件作出的改动都会监视,所以不可避免的会在生成第一行的时候即触发事件,导致接触到不正常的index = 0的行。

(原创内容,elig首发于博客园,转载请注明出处)

这个问题应该是一个BUG,如果有朋友知道问题具体出在哪个地方,非常欢迎指出。

关于解决方案实际上,至少在目前看来,非极端的情况下,都可以用SelectionChangeCommitted事件取代SelectedIndexChanged,SelectedValueChanged。而且如果需要写的代码比较复杂,又没有必要在窗体加载生成comboBox的items集合的时候即触发对应事件,那么没有必要用SelectedIndexChanged,SelectedValueChanged,至少,在加载过程中所触发的事件,属于浪费系统资源的行为。

补充内容:根据@admin的提示,终于把之前的所有问题想通了:

  问题如下:其实现在发现,这不是一个BUG。出问题的机制在于:在执行

 

cboCategory.DataSource = mainForm.CategoryList;

之后,执行下一步之前,因为根据DataSource开始生成comboBox的item生成的时候,就已经触发了SelectedIndexChanged,SelectedValueChanged事件。在这个时候,因为还没有执行到下面的手动指定ValueMember命令。那么,这个时候comboBox的ValueMember则默认为Model.Category类型。

而comboBox有个特点,在生成下拉项之前,它的index是-1,因为尚未有任何项(可以新建一个空白的comboBox然后观察它的SelectedIndex)。在生成之后,先是SelectedIndex会等于0,这也是 我们通常看到的如果在生成之后不指定它的index,则默认是选中第一项的。然后再生成更多的项的时候,被选中的项是不会变的,依然保持第一个。

这就是为什么如果和DisplayMember一起指定了ValueMember的时候,如果if条件里控制了index >= 1的时候才执行相应代码,就不会出错。因为这个时候已经执行完后面的内容了。在

cboCategory.DataSource = mainForm.CategoryList;所产生的动作中,SelectedIndex始终保持着非-1即0的状态。在这个时候始终不会执行SelectedIndexChanged,SelectedValueChanged事件

所以后面的就迎刃而解了

(原创内容,elig首发于博客园,转载请注明出处)

posted on 2011-02-12 00:27  寒关月  阅读(35526)  评论(8编辑  收藏  举报

导航