How to extract selected item from Listbox as an object - c#

How to extract a selected item from Listbox as an object. Currently, I'm taking all records from DB and I add them to the Listbox in the following way:
var allEmployees = GetAll();
foreach (var emp in allEmployees)
{
var empFull = $"{emp.Id} - {emp.Name} {emp.Surname} - {emp.Email}";
listBoxViewEmp.Items.Add(empFull);
}
However, I find it challenging to make the listBoxViewEmp.SelectedItem to object since I'm taking the entire string that contains id, name, surname, and email.
private void btnUpdate_Click(object sender, EventArgs e)
{
if(listBoxViewEmp.SelectedItem != null)
{
var newForm = new UpdateForm(listBoxViewEmp.SelectedItem);
newForm.Show();
}
else
{
MessageBox.Show("Please select employee first");
}
}
What is the best way to handle this kind of issue?
Update:
Here is my attempt, but I got only objects now in the listbox
var allEmployees = GetAll();
foreach (var emp in allEmployees)
{
var empFull = (Employee)emp;
listBoxViewEmp.Items.Add(empFull);
}
enter image description here

The string that appears for any object added as an item to a ListBox control will be the return value of the ToString method of that object. In your case, you added a string directly to the list. So there's (likely) no way to recover the original object from the formatted string you created for that object.
Instead, override ToString for your Employee object (if appropriate to do so).
Alternatively, you can create a wrapper class whose job it is to remember the original object and to provide a custom ToString implementation for the remembered object. Such a generic formatter class might look like:
public class Formatter<T>
{
public Formatter(T obj, Func<T, string> fnFormat)
{
this.obj = obj;
this.fnFormat = fnFormat;
}
public T obj;
public Func<T, string> fnFormat;
public override string ToString() => fnFormat(obj);
}
Then, when you add items to the list box, add a Formatter wrapping that item instead. Its ToString method will be called, which will call the custom formatter for that item.
foreach (var employee in employees)
{
this.listBox1.Items.Add(new Formatter<Employee>(employee,
emp => $"{emp.Id} - {emp.Name} {emp.Surname} - {emp.Email}"));
}
And to access the original Employee, case the SelectedItem back to the Formatter<Employee> and access its remembered object field obj. Now you have the original Employee object back (or DB record or entity or whatever it was originally.) Example usage:
private void button1_Click(object sender, EventArgs e)
{
Employee employee = (this.listBox1.SelectedItem as Formatter<Employee>)?.obj;
this.label1.Text = employee?.Id.ToString();
}

You can use databinding for this. Make a new readonly property which will return computed string you want. Than ListBox has some properties you can use:
Datasource - source of data which will be displayed. You can set here just simple List<Employee>.
DisplayMember - you set the name of the property, which value you want to see in the ListBox.
ValueMember - you set the name of the property which value you want to get for selected item using ListBox's SelectedValue property.
So you can do it this way:
// You should set these two properties in form's designer.
listBoxViewEmp.DisplayMember = "Info"; // "Info" is that new readonly property.
listBoxViewEmp.ValueMember = "Id";
var allEmployees = GetAll().ToList();
listBoxViewEmp.DataSource = allEmployees;
That's it. Now:
You will see in the listbox what you want to see there.
listBox.SelectedItem returns whole your Eployee object.
listBox.SelectedValue returns the Id value of selected employee.

If you have access to the Employee class, do that :
Main code:
var allEmployees = GetAll();
foreach (var emp in allEmployees)
{
var empFull = (Employee)emp;
listBoxViewEmp.Items.Add(empFull);
}
In class code :
class Employee
{
...
public override string ToString()
{
return $"{this.Id} - {this.Name} {this.Surname} - {this.Email}";
}
}
Now you are able to get the items from the listbox as Employee and it will also replace "JustTest.Models.Employee" in the ToString above.

Related

How do I update underlying list when calling BindingSource.RemoveCurrent

I have the following simple class;
public class MyObject
{
public int Id {get;set;}
public string Name {get;set;}
}
List<MyObject> oList = new List<MyObject>();
My list is populated with some items. I then populate my BindingSource with the list like;
MyBindingSource.DataSource = oList; //contains some items in a list
My BindingSource is linked to a DataGridView (which doesn't really matter in this example), but depending on the selected row in the DataGridView, I then have the following method for my datagrid view clicked button;
private void MyDataGrid_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == btnRemove.Index)
{
MyBindingSource.RemoveCurrent();
}
}
The call
MyBindingSource.RemoveCurrent()
removes the items from the DataGridView, but how do I remove the item from the underlying list which is oList.
I thought that assigning MyBindingSource.DataSource = oList, means the list shown in MyBindingSource.DataSource is actually pointing to oList ?
List<T> isn't smart enough to know things have changed, so try using a BindingList<T> from System.ComponentModel instead:
BindingList<MyObject> oList = new BindingList<MyObject>();

Display specified text for listbox items

I have a list that contains custom objects. These objects have different properties, and I have ~100 of them. I want to create a list of them in a listbox, but the listbox displays only
MyNamespace.MyClass
MyNamespace.MyClass
MyNamespace.MyClass
MyNamespace.MyClass
...
Is it possible to make the listbox display a certain value for each item? Lets say my objects have an ID string value. Can I display the ID for each item without discarding my objects' other properties?
I currently fill the listbox this way:
lbox.Items.Clear();
lbox.Items.AddRange(list.ToArray());
Set the DisplayMember to the property of your class that you'd like the user to see.
lbox.Items.Clear();
lbox.Items.AddRange(list.ToArray());
lbox.DisplayMember = "ID"; // ID is a public property in MyClass
Lets say you MyClass looks like this:
public class MyClass
{
public int Id { get; set; }
}
There are two options available.
You can use DataBinding for that.
Set the DisplayMember to the propertie of your MyClass which you would like to display
lbox.DisplayMember = "Id";
Set the items using the DataSource propertie of your ListBox
lbox.DataSource = list.ToArray();
You can simple override the ToString method of your MyClass object and return the text you would like to display.
Override the ToString method of your MyClass
public class MyClass
{
public int Id { get; set; }
public override string ToString()
{
return Id.ToString();
}
}
Set the items the same way as you mentioned
lbox.Items.AddRange(list.ToArray());
More Information
MSDN: ListControl.DisplayMember Property
MSDN: Object.ToString Method
Without discarding the object you can attach the object to the tag after.
list.ToList().ForEach(item => lbox.Items.Add(new ListItem(item.ID){Tag = item});
then to retreive it :
var myitem = ((ListItem)lbox.SelectedItem).Tag as MyClass;
Try using Linq.
lbox.Items.AddRange(list.Select(x => x.ID).ToArray());
Where ID is a property with the value you want to show.
You can also override ToString() in the class.

How to find an Instance of a class inside a Dictionary?

If I have been researching this correctly, I got some help before and a user said that it would be good to use a Dictionary to store my Country and Places.
So I create my Dictionary:
Dictionary<string, NewCountryClass> NTCD = new Dictionary<string, NewcountryClass>();
When the user clicks the button it will trigger this class where I wanted it to create a instance of newCountryClass inside the Dictionary at runtime. It would add the string which would be the newCountryTitle.Country and then the Class.
public void AddCountryCollection()
{
newCountryClass = new NewCountryClass(newCountry,"");
Collections.Add(newCountryClass);
NTCD.Add(newCountryClass.Country, newCountryClass);
}
So lets say the user has has added the Country which has created this Dictionary at Runtime, and they have added 4 Countries, but now want to go back and add a Place label inside the second Country.
This is the newCountryClass:
private string _country;
public string Country
{
get { return _country; }
set
{
if (_country.Equals(value))
return;
_country= value;
RaisePropertyChanged(() => Country);
}
}
private ICollection<string> _places;
public ICollection<string> Places
{
get
{
if (_places== null)
_places= new ObservableCollection<string>();
return _places;
}
set
{
if (value == _places)
return;
_places= value;
RaisePropertyChanged(() => Places);
}
}
If they have created 4, and they want to add a Place to the list inside that Country of the second one they created, how would I find it?
Simply use the key you stored:
NTCD["GER"].Places.Add("New Place inside GER");
If you only want to know the second one, either take a List<NewCountryClass> and iterate by index with foror take a OrderedDictionary. It takes object key, object value as parameters on add and you can access it via NTCD[3].
OrderedDictionary Class
You're using a Dictionary, so there's no concept on an item being at a particular index. Instead, an item is at a particular key, which in your case is the value of your Country property.
If you want to get an item by a numeric index they you need to use a data structure like a List.

PropertyGrid with possibly-null List

I'm using a PropertyGrid class to edit objects within my application. These are the relevant classes (or rather, simplifications thereof):
public class Inner
{
public int A { get; set; }
public string B { get; set; }
}
public class Outer
{
public List<Inner> InnerData { get; set; }
public int Id { get; set; }
}
I will set an object of type Outer as the SelectedObject field of my property grid. The problem comes when an Outer object has it's InnerData property set to null. Null is considered an acceptable value for this property as the InnerData property represents "optional" data, and not having it specified is not the same thing as specifying an empty list. Ideally I'd like a user to be able to replace a null InnerData property with a real value by specifying the components of the new list, modify an existing non-null InnerData value, and replace an existing InnerData value with null.
Anybody know how to make this happen?
Have a look at creating a UITypeEditor, i think that if you use an editor you will have more control over the list and be able to tell if the current value is null and if so you can have the editor show a blank grid or something where list items can be added or removed, you could also add a checkbox to tell the editor to return null again and set null on the property, the editor is basically a WinForm so you can do almost anything in it.
internal class GenericTypeEditor : UITypeEditor
{
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
IWindowsFormsEditorService winFormEditorSvc = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
using (MyForm editorForm = new MyForm())
{
if (winFormEditorSvc.ShowDialog(editorForm) == System.Windows.Forms.DialogResult.OK)
value = editorForm.ReturnObject;
}
return value; //this can be null if you wish
}
public override UITypeEditorEditStyle GetEditStyle(System.ComponentModel.ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
}
Then just set the attribute on your property
[EditorAttribute(typeof(GenericTypeEditor), typeof(System.Drawing.Design.UITypeEditor))]
public List<Inner> InnerData { get; set; }
This article helped me in the past, maybe it is of help to you:
http://msdn.microsoft.com/en-us/library/ms171840(v=vs.100).aspx
The property grid tries to add new Inner items to the InnerData object, but since you have not initialized it, the property grid has no where to save the added items. You need a constructor in Outter that will initialize InnerData just as a new List. You dont have to put any items into it, the user can do this at runtime, and can empty them back out as well, but the InnerData list Object needs to be initialized.
If you just want an Inner as a property, add System.ComponentModel to your usings and try this
[TypeConverter(typeof(ExpandableTypeConverter))]
public Inner DefaultInner { get; set; }
This will make your object expandable in the property grid so that you can set its nested properties
Try handling the PropertyGrid.SelectedGridItemChanged event:
private void propertyGrid1_SelectedGridItemChanged(object sender, SelectedGridItemChangedEventArgs e)
{
if ((e.NewSelection.Label == "InnerData") && (_outter.InnerData == null)) _outter.InnerData = new List<Inner>();
}
Then whenever the InnerData item is selected, if the collection is null, its initialized to a new list.

Using custom objects for (CheckedListBox).Items.Add()

When I add an item to the CheckedListBox list box I also want to store a reference to another object. I tried adding a new instance of this object to the CheckedListBox.
public class CheckedListBoxExtention : CheckedListBox
{
private ReferenceItem _referenceItem;
public ReferenceItem storedItem
{
get { return _referenceItem; }
set { _referenceItem = value; }
}
public CheckedListBoxExtention(ReferenceItem storedItem)
{
_referenceItem = storedItem;
}
}
This works in that later when I foreach though the items in CheckedListBox I have a reference to the _referenceItem object. However, when I add items like this, CheckedListBox shows up as blank (the list in the GUI itself). So I am trying to find a way to override the item text or something like that.
This is the code I used to fix the problem
class ReferenceItemWrapper
{
private ReferenceItem _item;
public ReferenceItemWrapper(ReferenceItem item)
{
_item = item;
}
public ReferenceItem getItem
{get {return _item;}}
public override string ToString()
{
return _item.ToString();
}
}
I am a bit new to wrappers. Why exactly did it work after it was wrapped when it did not work when I added the ReferenceItem directly to the CheckedListBox?
The CheckedListBox uses the ToString method of the objects in the list to populate the captions in the box. Rather than extend the CheckedListBox, just create a wrapper class that lets you store both your reference and a caption, and implements a ToString method which returns your caption. Just create one of your wrapper objects, stick the text in it, stick your reference in it, then add the wrapper object to the list box.

Categories

Resources