The problem is as follows: I connected the ListBox
with a list of elements of some custom class (List<Person> persons = new List<Person>()
) using DataSource
property. Of course ValueMember
and DisplayMember
are both assigned to appropriate properties of this class. When I first load data, everything looks ok. However, when I click on some item (i.e. 7th position, counting from 1) and then rebuild the list AND the number of elements is LESS than 7, as a result I can't see the proper texts on the list. Instead, every item shows up as a class name, preceded by the namespace.
In other words, instead of the list:
I see this:
It looks like it depends on last SelectedIndex
. If there is no longer an item with that index (there are less items), the problem occurs.
I've tried different combinations of reassigning ValueMember
and DisplayMember
, as well as assigning null to the DataSource
property of the list and reassign the list to this property, even tried to assign -1 to SelectedIndex
before unbinding, but none of them helped.
[Edit]
I was asked to show some code. I'll paste the relevant fragments:
1. Class Person:
public class Person
{
private int id;
private string name;
public Person(int m_id, string m_name)
{
id = m_id;
name = m_name;
}
public int Id
{
get
{
return id;
}
}
public string Name
{
get
{
return name;
}
}
}`
2. In a constructor of the form:
List<Person> persons = new List<Person>();
3. In a method fired on buton1 click:
listBox1.DataSource = null; // this is optional. Commenting this line out doesn't help
persons.Add(new Person(1, "John Doe"));
persons.Add(new Person(2, "Jane Doe"));
persons.Add(new Person(3, "Somebody Else"));
listBox1.ValueMember = "Id";
listBox1.DisplayMember = "Name";
listBox1.DataSource = persons;
4. In a method fired on buton2 click:
listBox1.DataSource = null; // this is optional. Commenting this line out doesn't help
persons.Add(new Person(1, "Person One"));
persons.Add(new Person(2, "Person Two"));
listBox1.ValueMember = "Id";
listBox1.DisplayMember = "Name";
listBox1.DataSource = persons;
When I click button1, the listbox is filled and everything works fine. When I select last item ("Somebode Else") and then clisk button2, the listbox shows 2 identical items: "MyNamespace.Person".
[Edit 2 - complete code of form]
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace MyNamespace
{
public partial class Form1 : Form
{
private List<Person> persons = new List<Person>();
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
persons.Clear();
persons.Add(new Person(1, "John Doe"));
persons.Add(new Person(2, "Jane Doe"));
persons.Add(new Person(1, "Somebody Else"));
listBox1.DataSource = null;
listBox1.ValueMember = "Id";
listBox1.DisplayMember = "Name";
listBox1.DataSource = persons;
}
private void button2_Click(object sender, EventArgs e)
{
persons.Clear();
persons.Add(new Person(1, "Person One"));
persons.Add(new Person(2, "Person Two"));
listBox1.DataSource = null;
listBox1.ValueMember = "Id";
listBox1.DisplayMember = "Name";
listBox1.DataSource = persons;
}
}
class Person
{
private int id;
private string name;
public Person(int m_id, string m_name)
{
id = m_id;
name = m_name;
}
public int Id
{
get
{
return id;
}
}
public string Name
{
get
{
return name;
}
}
public string ToString()
{
return id + ". " + name;
}
}
}
Steps to reproduce the problem:
If you select "John Doe" or "Jane Doe" on the list, everything works fine. It seems to "crash" when the selected index is not valid after rebuilding the list. I guess it's some bug.
When one sets the DataSource
to null
it clears the DisplayMember
value. So to resolve, set it after you set a new DataSource
and the problem disappears.
listBox1.DataSource = null; // this is optional. Commenting this line out doesn't help
persons.Add(new Person(1, "John Doe"));
persons.Add(new Person(2, "Jane Doe"));
persons.Add(new Person(3, "Somebody Else"));
listBox1.DataSource = persons;
listBox1.DisplayMember = "Name";
Otherwise in the Person class override the ToString
method to ensure that the proper property will be shown if DataMember
is empty:
public class Person
{
private int id;
private string name;
public Person(int m_id, string m_name)
{
id = m_id;
name = m_name;
}
public int Id
{
get
{
return id;
}
}
public string Name
{
get
{
return name;
}
}
public override string ToString()
{
return name;
}
}
With this whenever you set the listbox datasource to a List<Person>
the listbox will automatically use the ToString method as the display. Using the selecteditem is simply a matter of casting it as Person, (Person)listBox1.SelectedItem
.