65.9K
CodeProject 正在变化。 阅读更多。
Home

DataBind ComboBox 失败及更多

starIconstarIconstarIconstarIconstarIcon

5.00/5 (11投票s)

2010 年 2 月 4 日

CPOL

7分钟阅读

viewsIcon

67943

downloadIcon

386

在使用 ComboBox 的 DisplayMember / ValueMember 时,经常会遇到此问题。

引言

本文将深入探讨一个常用的 .NET 构造(数据绑定),一步一步进行。首先,文章将设置一个简单的程序,该程序使用 DisplayMemberValueMember 属性将对象数组绑定到 ComboBox。接下来,它将展示这通常会如何失败。之后,我们将看到修复该问题的方法,并了解为什么控件、Visual Studio 或其他某些方面可能存在 bug。

背景

我很久以前就发现了这个问题,但由于它是一个较大程序的一部分,所以一直无法确定其原因。因此,我决定编写一个具体的程序和文章,其中提供详细信息。

  1. 如何将对象的属性直接绑定到 ComboBox
  2. 出现什么问题、错误和故障。
  3. 为什么这些问题经常发生。

Using the Code

在阅读本文时,您会看到代码会经过许多细微的修改,以向您展示过程中发生的情况。附带的代码是实现了 ArrayAnimal 类上 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;。之前,我是在设置 DisplayMemberValueMember 之后设置它的。现在,我是在之前设置。现在,当您尝试运行程序时,它将立即崩溃。您将看到类似以下内容:

为什么会崩溃?

为了找出程序崩溃的原因,我使用调试器逐步执行了代码。抛出了以下异常:

但这似乎没什么意义。为什么它告诉我值不能为空?我的值不是 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()

好吧,至少这给了我一个线索。现在,我正在想,嘿,这使用的是我 AnimalToString() 方法的默认值。让我们重写 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 日 - 发布了本文和代码的第一个版本。
© . All rights reserved.