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:
John Doe
Jane Doe
Somebody Else
I see this:
MyNamespace.Person
MyNamespace.Person
MyNamespace.Person
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:
Run the form
Click button1
Select last position on the list ("Somebody Else")
Click button2
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.
Try setting the "ValueMember" and "DisplayMember" properties only once before any datasource binding. Can you set them in the designer? Then always before changing the datasource, run ListBox's "ClearSelected()"-method to clear any selections. Then unbind the datasource, edit the list, then set the edited list as datasource. Seems that this behavior is some kind of bug. Try if this helps.
Related
Here i am using this code for copy one list to another
public class Person
{
public string Name { get; set; }
}
private void Form1_Load(object sender, EventArgs e)
{
var originalList = new List<Person>();
originalList.Add(new Person { Name = "name 1" });
originalList.Add(new Person { Name = "name 2" });
// var newList = originalList.ToList();
var newList = new List<Person>(originalList);
newList[0].Name = "New name";
Console.WriteLine(originalList[0].Name);
}
My result in console is 'New name', why this is happen? When i am updating my new list it also updates my original one. How can i fix this?
Do not worry, this is normal behavior, you have the original list, then you have another list filled in by the original, in your case, both lists point to the same items, which means that you have two references that point to the same memeory case , reason why you change an element from the original one the same element change in the second one and vice-versa .
Case 1 :
public class Person
{
public string Name { get; set; }
}
private void Form1_Load(object sender, EventArgs e)
{
//here you have the original list, you create your list
//you add element to your list .
var originalList = new List<Person>();
originalList.Add(new Person { Name = "name 1" });
originalList.Add(new Person { Name = "name 2" });
// you create a second list , but here the contain the
// same element than the original list
var newList = new List<Person>(originalList);
newList[0].Name = "New name";
Console.WriteLine(originalList[0].Name);
}
So I'm going back and cleaning up code I did years ago. In one part, (and I used this several times) I have a list box displaying employee names such as
Smith, John
Doe, Jane
When the user clicks the name, I do something like
String unBrokenName = ListBox1.SelectedItem.ToString();
String LastName = unBrokenName.Substring(...
You get the idea, I extract the first and last name based upon the ", "
Then I do this to get the employee from the sql database.
Employee SelectedEmployee = Employee.GetEmployeeByFirstLast(FirstName, LastName);
At the time, it was the best I knew. Now it feels wrong, because I KNOW I should be able to get the sql ID of the employee when they select it, like
int EmployeeId = SOMELISTBOXSELECTEDITEMPROPERTY;
Employee SelectedEmployee = Employee.GetEmployeeByID(EmployeeId);
Is there some property for a listbox item that will store this id while displaying the same name the users are used to seeing?
You can actually add anything you'd like to a listbox:
class Foo
{
public int Id { get; set; }
public string Name { get; set; }
public override string ToString()
{
return this.Name;
}
}
And then:
listBox1.Items.Add( new Foo() { Id = 101, Name = "Foo Bar" } );
listBox1.Items.Add( new Foo() { Id = 102, Name = "Foo Bar Jr." } );
The SelectedItem property will now give you the selected Foo, while displaying the Name property in the list itself.
private void listBox1_SelectedIndexChanged( object sender, EventArgs e )
{
Foo item = ( listBox1.SelectedItem as Foo );
if( item != null )
{
// use item.Id here
}
}
Instead of overriding ToString, you can also use the DisplayMember property of the listbox to select which property the listbox will display.
You can do something like this:
listBox1.DataSource = employeesList;
listBox1.DisplayMember = "LastName";
listBox1.ValueMember = "EmployeeId";
When you run your application the listbox will have the list of employees that you are passing and it will show the LastName. But when you select an item, you can access the id by:
`listBox1.SelectedValue();`
And then in the listbox1_Click event something like:
if (listBox1.SelectedIndex != -1)
{
int employeeId = listBox1.SelectedValue();
//do something here;
}
I several objects of class:
class Person
{
public string Name { get; set; }
public string Sex { get; set; }
public int Age { get; set; }
public override string ToString()
{
return Name + "; " + Sex + "; " + Age;
}
}
and a class that has a property of type Person:
class Cl
{
public Person Person { get; set; }
}
And I want to bind Cl.Person to combobox. When I try to do it like this:
Cl cl = new cl();
comboBox.DataSource = new List<Person> {new Person{Name = "1"}, new Person{Name = "2"}};
comboBox.DataBindings.Add("Item", cl, "Person");
I get an ArgumentException. How should I modify my binding to get the correct program behavior?
Thanks in advance!
Bind to "SelectedItem":
var persons = new List<Person> { new Person() { Name = "John Doe"}, new Person() { Name = "Scott Tiger" }};
comboBox1.DisplayMember = "Name";
comboBox1.DataSource = persons;
comboBox1.DataBindings.Add("SelectedItem", cl, "Person");
For simple databinding, this will work
cl.Person = new Person{ Name="Harold" };
comboBox.DataBindings.Add("Text",cl.Person, "Name");
But I don't think that's what you want. I think you want to bind to a list of items, then select one. To bind to a list of items and show the Name property, try this:
comboBox.DataSource = new List<Person> {new Person{Name = "1"}, new Person{Name = "2"}};
comboBox.DisplayMember = "Name";
Provided your Person class overrides Equals() such that, say, a Person is equal to another if they have the same Name, then binding to the SelectedItem property will work like so:
Cl cl = new Cl {Person = new Person {Name="2" }};
comboBox.DataBindings.Add("SelectedItem", cl, "Person");
If you can't override Equals(), then you just have to make sure you're referencing a Person instance from the DataSource list, so the code below works for your specific code:
Cl cl = new Cl();
cl.Person = ((List<Person>)comboBox1.DataSource)[1];
comboBox.DataBindings.Add("SelectedItem", cl, "Person");
Try
comboBox.DataBindings.Add("Text", cl, "Person.Name");
instead
You need to tell the combobox which property on it you want to bind to which property on your object (it's Text property, in my example which will show the Name property of the selected person).
*EDIT:* Actually scrap that, I was getting confused. You almost had it, only combobox doesn;t have a property called item, you want SelectedItem instead, like this:
Cl cl = new cl();
comboBox.DataSource = new List<Person> {new Person{Name = "1"}, new Person{Name = "2"}};
comboBox.DataBindings.Add("SelectedItem", cl, "Person");
if you are using Enums may be u have a class of enums you can a combo box like this
Specify the combo box datasourse eg
comboBoxname.DataSource = Enum.GetValues(typeof(your enum));
Now lets bind the combox box since we have the data source
comboBoxname.DataBindings.Add("SelectedItem",
object,
"field of type enum in the object");
I am experiencing some problems while working with ComboBox.
The display member for my combobox is not being populated by the overridden ToString method of class MAP.
Here is my code:
Form1.cs:
private void Form1_Load(object sender, EventArgs e) {
...
...
MAPList MAP = new MAPList();
comboBox1.DataSource = MAP.All;
comboBox1.ValueMember = "Code";
...
...
}
MAPList.cs:
public class MAPList {
public readonly List<MAP> All;
public MAPList() {
All = new List<MAP>();
var MapData = // Getting map data
foreach(MAP m in MapData) {
All.Add(new Map(m.Name, m.Code));
}
}
}
MAP.cs:
public class MAP {
public readonly string Name;
private string code;
public string Code { get { return code; } }
public RadioCode(string Name, string Code) {
this.Name = Name;
this.code = Code;
}
public override string ToString() {
return String.Format("{0}: {1}", Name, Code);
}
}
ToString will not be called if you set ValueMember. If you do not set ValueMember it will work as expected but then of course Code will not be used as the selected value of the ComboBox.
Alternatively, if you wish to use ValueMember you may also want to set DisplayMember. You can create a property on your MAP that is used for display, i.e.:
public class MAP
{
public readonly string Name;
private string code;
public string Code { get { return code; } }
public string Display { get { return ToString(); } }
public MAP(string Name, string Code)
{
this.Name = Name;
this.code = Code;
}
public override string ToString()
{
return String.Format("{0}: {1}", Name, Code);
}
}
In the form you can then set DisplayMember:
MAPList MAP = new MAPList();
comboBox1.DataSource = MAP.All;
comboBox1.ValueMember = "Code";
comboBox1.DisplayMember = "Display";
This is because you've set your ValueMember property to "Code", so the values in the combobox are not your Map objects but rather the strings corresponding to their Code properties.
If you remove this line:
comboBox1.ValueMember = "Code";
...it will work as you expect.
If you want the ComboBox to display its items according to your Map type's ToString method, then Jakob's answer is right on: create a property on your Map type that provides a string formatted exactly how you want it, and set the DisplayMember property of the ComboBox to the name of this property.
this could be because u r using ValueMember. use DisplayMember Property, add another property on the Map class in the get of this property return the formatted string.
I know this is an old post, but if someone wants to use ToString() without creating a property to just call ToString(), you'll have to explicitly set the DisplayMember value to an empty string like this:
Form1.cs:
private void Form1_Load(object sender, EventArgs e) {
...
...
MAPList MAP = new MAPList();
comboBox1.DataSource = MAP.All;
comboBox1.ValueMember = "Code";
comboBox1.DisplayMember = ""; // Explicitly set it to an empty String
...
...
}
I want a ListBox full of items. Although, each item should have a different value.
So when the user selects an item and presses a button, a method will be called which will use the value the select item has.
I don't want to reveal the item values to the user.
EDIT: This is not for ASP.NET, it's for a Windows Forms application. I just thought the HTML example would be easy to read.
I have the inspiration from HTML:
<form>
<input type="radio" name="sex" value="Value1" /> Male
<br />
<input type="radio" name="sex" value="Value2" /> Female
</form>
This also allows me to use different values than what the user sees.
You can choose what do display using the DisplayMember of the ListBox.
List<SomeData> data = new List<SomeData>();
data.Add(new SomeData() { Value = 1, Text= "Some Text"});
data.Add(new SomeData() { Value = 2, Text = "Some Other Text"});
listBox1.DisplayMember = "Text";
listBox1.DataSource = data;
When the user selects an item, you can read the value (or any other property) from the selected object:
int value = (listBox1.SelectedItem as SomeData).Value;
Update: note that DisplayMember works only with properties, not with fields, so you need to alter your class a bit:
public class SomeData
{
public string Value { get; set; };
public string Text { get; set; };
}
items have a property called 'Tag', which you can use to store any information you want (hidden from the user)
ListViewItem myItem = new ListViewItem();
myItem.Text = "Users see this";
myItem.Tag = "Users don't see this";
(or set the appropriate properties in the property explorer)
Very simple:
foreach(var item in *Your Source List*)
{
ListItem dataItem = new ListItem();
dataItem.Text = "value to show";
dataItem.Value = *another value you want*;
listBox.Items.Add(dataItem);
}
As stated by the 1st answer, the use of DisplayMember works whether you are using asp.net or winforms.
And to comment a bit more, it also works if you are using the rather old fashion Items.add way of adding items to a ListBox.
Just for fun, here is a simple demo of what you need (just create a new form and drop on it a ListBox and a Label):
public partial class Form1 : Form
{
class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public override string ToString()
{
return string.Format("{0} {1}", LastName, FirstName);
}
}
public Form1() { InitializeComponent(); }
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
listBox1.DisplayMember = "LastName";
listBox1.DataSource = GetCustomers();
//listBox1.Items.AddRange(GetCustomers().ToArray());
}
private IEnumerable<Customer> GetCustomers()
{
return new List<Customer>()
{
new Customer() { FirstName = "Gustav", LastName = "MAHLER" },
new Customer() { FirstName = "Johann Sebastian", LastName = "BACH" }
};
}
private void lb_SelectedIndexChanged(object sender, EventArgs e)
{
label1.Text = listBox1.SelectedItem.ToString();
}
}
Enjoy
PS: #2nd post Tag is not available to ListBox: because it accepts an array of object, not a specific item container like ListView... but you don't need any in your case. Tag is useful when you want to carry additional data along with a specific TreeViewItem or ListViewItem for example.
By the way, Tag is defined at the Control level and so exists for Button, Label, and so on... but for my part I think it is rather a bad idea to store business data in it (untyped, UI coupled...) apart from the ListView and TreeView cases for which it is rather convenient.
Easy!
protected void Page_Load(object sender, EventArgs e)
{
llenaListBox(ListBox1, 0, 10);
}
private void llenaListBox(ListBox PoListBox, int PiMinimo, int PiMaximo)
{
int Li;
for (Li = PiMinimo; Li <= PiMaximo; Li++)
{
ListItem obj = new ListItem();
obj.Text = Li.ToString();
obj.Value = Li.ToString();
PoListBox.Items.Add(obj);
}
}