DataBind ComboBox 失败及更多





5.00/5 (11投票s)
在使用 ComboBox 的 DisplayMember / ValueMember 时,经常会遇到此问题。
引言
本文将深入探讨一个常用的 .NET 构造(数据绑定),一步一步进行。首先,文章将设置一个简单的程序,该程序使用 DisplayMember
和 ValueMember
属性将对象数组绑定到 ComboBox
。接下来,它将展示这通常会如何失败。之后,我们将看到修复该问题的方法,并了解为什么控件、Visual Studio 或其他某些方面可能存在 bug。
背景
我很久以前就发现了这个问题,但由于它是一个较大程序的一部分,所以一直无法确定其原因。因此,我决定编写一个具体的程序和文章,其中提供详细信息。
- 如何将对象的属性直接绑定到
ComboBox
。 - 出现什么问题、错误和故障。
- 为什么这些问题经常发生。
Using the Code
在阅读本文时,您会看到代码会经过许多细微的修改,以向您展示过程中发生的情况。附带的代码是实现了 Array
和 Animal
类上 ToString()
重写的方法。在阅读完本文后,这一点会更清楚。
最终代码是一个基本的 Windows Forms 项目,带有一个额外的类,该类保持得非常简单,以创建一个清晰展示正在发生的事情的示例。
这是 Animal
类
class Animal
{
private string commonName;
private string species;
private int speciesId;
// Creates a class (global) randomizer used
// to generate a unique id for each species
static Random rnd = new Random();
public Animal(string inSpecies, string inCommonName)
{
// original code
this.speciesId = rnd.Next();
species = inSpecies;
commonName = inCommonName;
}
public int SpeciesId
{
get
{
return speciesId;
}
}
public string Species
{
get
{
return species;
}
}
public string CommonName
{
get
{
return commonName;
}
}
}
Animal 类详细信息
Animal
类使我们可以轻松地将动物列表绑定到 ComboBox
。在我们窗体代码中包含 ComboBox
的地方,我们将创建一个 Animal
对象数组,然后将其绑定到我们的 ComboBox
。它看起来会像这样:
public partial class Form1 : Form
{
// set up an array to hold some animals
Animal[] allAnimals = new Animal[10];
public Form1()
{
InitializeComponent();
// initialize some animals
InitAnimals();
comboBox1.DisplayMember = "CommonName";
comboBox1.ValueMember = "SpeciesId";
comboBox1.DataSource = allAnimals;
}
private void InitAnimals()
{
//create 3 animals and add them to the array.
allAnimals[0] = new Animal("Fidelis Caninus", "Dog");
allAnimals[1] = new Animal("Felinus Catticus", "Cat");
allAnimals[2] = new Animal("Elephantos Largus", "Elephant");
}
}
此代码将生成一个应用程序,该应用程序将动物对象绑定到 ComboBox
,其外观如下:
第二张图:显示一个空项
请注意,第二张图显示的是程序刚启动时的样子。优点是此 ComboBox
有一个空项。换句话说,用户还没有选择任何选项。这现在可能看起来不重要,但在我们尝试解决下面的问题时会变得重要。所以,暂时请记住这一点。
现在,让我们添加一个事件处理程序,以便选择其中一个项可以执行某些操作。在 Visual Studio 中,确保选中了 ComboBox
,然后在窗体属性对话框中切换到“事件”视图,并添加 SelectedIndexChanged
事件。您将得到一个类似以下内容的方法:
private void comboBox1_SelectedIndexChanged_1(object sender, EventArgs e)
{
Animal currentAnimal = (Animal)comboBox1.SelectedItem;
MessageBox.Show(this,currentAnimal.Species);
}
当您运行程序并在组合框中选择一个项时,事件处理程序会触发,获取当前选定的项,将其转换为 Animal
对象,并显示动物的物种名称。其外观如下,具体取决于您选择的项:
为什么我们需要转换项?
我们转换从 ComboBox
返回的项,因为我们告诉它将该项存储在 ComboBox
的列表中,但 ComboBox
仅将其存储为 object
- 所有对象的基类。由于我们希望从特定的(Animal
)对象中获取特定属性,因此我们将其转换回其真实类型。
看起来工作正常
因此,您可能会注意到它似乎工作正常。但是存在一个问题,有些读者可能已经注意到。问题是:有一个数组包含一些空元素。我将数组定义为大小为 10,但我只 new
了三个 Animal
对象。
正常工作:除非您更改顺序
好的,您看到代码确实有效,那么它重要吗?现在,让我们假设它不重要。但是,让我们更改一行代码,并表明这会导致代码崩溃。
public Form1()
{
InitializeComponent();
//My private method to create some animals
InitAnimals();
comboBox1.DataSource = allAnimals;
// set the Animal property which will be used as the DisplayMember
comboBox1.DisplayMember = "CommonName";
// set the Animal property which will be used as the ValueMember
comboBox1.ValueMember = "SpeciesId";
}
崩溃
请注意,我所做的唯一更改是在移动设置 DataSource 的行:comboBox1.DataSource = allAnimals;
。之前,我是在设置 DisplayMember
和 ValueMember
之后设置它的。现在,我是在之前设置。现在,当您尝试运行程序时,它将立即崩溃。您将看到类似以下内容:
为什么会崩溃?
为了找出程序崩溃的原因,我使用调试器逐步执行了代码。抛出了以下异常:
但这似乎没什么意义。为什么它告诉我值不能为空?我的值不是 null。它是一个完全有效的字符串,指向我的 Animal
类中的 ValueMember
SpeciesId
。此外,如果我稍后放置此行,它就可以正常工作。
Microsoft 关于数据绑定的文档?
也许 Microsoft 甚至声称您必须按此顺序执行此操作?实际上,他们并没有。就任何文档而言,我创建的内容应该是没问题的。但是,事实并非如此。
第一个解决方法
第一个也是最明显的解决方案是:不要这样做。换句话说,只需最后设置 DataSource
成员即可。但这听起来像是一个完全魔术般的解决方案,所以让我们寻找另一个。
第二个解决方法:处理(丢弃)异常
是的,让我们把异常丢弃掉。我只需将该行包装在 try
...catch
... 中,它会处理 ArgumentNullException
。它看起来如下:
try
{
comboBox1.ValueMember = "SpeciesId";
}
catch (ArgumentNullException ex)
{
}
Try...Catch...有效吗?
它确实有效。应用程序启动,一切看起来都很正常,直到我单击 ComboBox
。请看接下来的两张图:
奇怪的显示值
到底是怎么回事?为什么我现在有了那些奇怪的显示值?这些奇怪的显示值可能来自哪里?
仔细查看 Animal 类
嘿,还记得我在 Animal
类中生成的 ID 吗?我这样做是为了模拟您可能从数据库或其他地方获得的值。执行此操作的代码如下所示:
public Animal(string inSpecies, string inCommonName)
{
this.speciesId = rnd.Next();
species = inSpecies;
commonName = inCommonName;
}
我进行了更多分析,并确定,是的,那些值就是从这里来的。但为什么它们被设置为 DisplayValue
?而且,如果我选择其中一个值会怎样?程序如您(可能)预期那样工作。看起来是这样的:
第三种解决方法
好的,我们试试别的?我们甚至不设置那个愚蠢的(是的,我说了愚蠢的)comboBox1.ValueMember
。让我们忽略它。让我们注释掉那行代码,然后重试。这是代码和结果:
//comboBox1.ValueMember = "SpeciesId";
同样,一开始一切看起来都很好。然后我下拉列表,然后……什么?现在,它将我的namespace.className 列为 DisplayMember
。太疯狂了!但是,它仍然有效,如果您可以说这叫有效的话。
一个线索和 ToString()
好吧,至少这给了我一个线索。现在,我正在想,嘿,这使用的是我 Animal
类 ToString()
方法的默认值。让我们重写 ToString()
方法,看看我能做什么。所以,我转到 Animal
类并添加以下重写方法。请注意,自动代码生成器(Visual Studio 助手)尝试将 return base.ToString();
行添加到我的代码中,但我将其注释掉了。我不想执行默认行为。
相反,我让它返回 Animal
类中的 commonName
属性。
public override string ToString()
{
return this.commonName;
//return base.ToString();
}
它工作了,但等等!它应该吗?
以下是运行时的样子:
所以它工作了,但我并不认为它应该。微软告诉你需要设置所有三个项:
comboBox1.DisplayMember = "CommonName";
comboBox1.ValueMember = "SpeciesId";
comboBox1.DataSource = allAnimals;
但是,请记住,我现在只设置了 DisplayMember
。我从未设置过 ValueMember
,因为我将其注释掉了。为什么我不再需要设置它了?控件如何区分正确的项/值?
又一个解决方法
去掉数组,使用 List(集合)
那么,也许这都与使用这个数组有关,这个数组有一些元素仍然是 null?好的,我将更改我的代码,使其使用一个集合。这是代码:
// set up a collection to hold some animals
List<animal> allAnimals = new List<animal>();
public Form1()
{
InitializeComponent();
//My private method to create some animals
InitAnimals();
// Please note that in this example,
// the DataSource is set first.
// However, with a list it will work.
comboBox1.DataSource = allAnimals;
// set the Animal property which will be used as the DisplayMember
comboBox1.DisplayMember = "CommonName";
// set the Animal property which will be used as the ValueMember
comboBox1.ValueMember = "SpeciesId";
}
private void InitAnimals()
{
//create 3 animals and add them to the array.
allAnimals.Add(new Animal("Fidelis Caninus", "Dog"));
allAnimals.Add(new Animal("Felinus Catticus", "Cat"));
allAnimals.Add(new Animal("Elephantos Largus", "Elephant"));
}
我还修改了 Animal
类,移除了先前添加的 ToString()
。
现在我运行它,我看到的第一件事是:
是的,我在看到主窗体之前就看到了它。显然,现在组合框的 SelectedItemChanged
事件在程序启动后立即运行。为什么?这里还有其他解释吗?有吗?
在此之后不久,主窗体出现,外观如下:
ComboBox 空项:消失了!
还记得我让您注意到 ComboBox 中有一个空项吗?我这么做了。在本文的开头,我说:“嘿,看,组合框里有个空项,因为用户还没选。”好吧,我让您注意到了,所以现在我们可以谈谈它。您看,它现在消失了。
嘿,您可能认为这没什么大不了的。我们可以解决这个问题。我知道,但为什么这个绑定东西一点都不一致?难道不应该很简单吗?
希望能有所帮助
我希望帮助那些在各种地方遇到此问题的人。
历史
- 2010 年 4 月 2 日 - 发布了本文和代码的第一个版本。