Add Items to ComboBox from an IEnumerable object - c#

I have a System.Timers.Timer that updates my win form application components in every 5 seconds.
I have a comboBox and global IEnumerable<Person> list that updated also in everty 5 seconds.
I need to add persons name to combobox. If the name is already in the list, i should not add.
How can I proceed?
Here is the code inside the timer event. This adds multiple times and i am not sure to do that with foreach, maybe IEnumareble interface has an easier way.
foreach (Persons person in personsList)
{
comboBox.Items.Add(person.Name);
}

This is one of the simpler solutions to this problem, assuming you're using .NET 3.5 or greater:
foreach(Person person in personsList)
{
if(!comboBox.Items.Cast<string>().Contains(person.Name))
{
comboBox.Items.Add(person.Name);
}
}
If you're using 3.0 or earlier, you'll have to do the search yourself:
foreach(Person person in personsList)
{
bool contains = false;
foreach(string item in comboBox.Items)
{
contains = string.Equals(item, person.Name);
if(contains) break;
}
if(!contains) comboBox.Items.Add(person.Name);
}

If possible using DataBinding is usually good. WPF has even nicer binding allowing for MVVM. WPF would actually make modifications as you modify the original collection (realtime) and don't have to readd all at every pass.
Readding all items at every pass is a bad approach, but its the easy way out. It would be better to either modify the listbox directly if the code allows it (not too many updates, not too time critical) or to make a copy of the list and perform only differences. (Pass 1: Remove any items in combobox that doesn't exist in new list. Pass 2: Add any items in new list that doesn't exist in combobox)

A couple approaches could be to walk all the items in the combobox, or you could keep track of a List of names that you've already added. Do you have any performance requirements?
Easier would be to just bind directly to the list of Persons and set your DisplayMember appropriately...

If I bind the data cmb.DataSource = personsList; cmb.DisplayMember = "Subject"; This wont work
It didn't work for me also. After some trying found this solution, maybe it will help someone:
IEnumerable<ICustomer> customers = GetCustomers(); //fill Ienumerable object list
_comboBox.DataSource = customers.ToList(); //convert it to list and it binds fine
_comboBox.DisplayMember = "Name"; // field Name from ICustomer
_comboBox.ValueMember = "CustomerID"; // field CustomerID from ICustomer

A more simpler approach is:
comboBox.Items.Clear();
comboBox.Items.AddRange(personsList.Select(p => p.Name));
All that does is clears the comboBox and adds the entire list again. Or if you don't like clearing the comboBox:
comboBox.Items.AddRange(personsList.Where(p => !comboBox.Items.Cast<string>().Contains(p.Name)).Select(p => p.Name));
You don't need the foreach anymore. Simply replace all your code with this!

Related

Binding Dictionary to ComboBox in C# .net winforms

This should be a duplicate question but I'm posting it because none of the answers anywhere are working.
I have a dictionary of the types:
private Dictionary<IModule, AssemblyLoadContext> ModuleList = new Dictionary<IModule, AssemblyLoadContext>();
I am trying to bind the names of the IModules (IModule.Handle, for everything that implements IModule) to a combobox.
I've tried many things and searched through every answer on google but nothing works. This is apparently the way you are supposed to do it:
comboBox1.DataSource = new BindingSource(ModuleList, null);
comboBox1.DisplayMember = "Value";
comboBox1.ValueMember = "Key";
When I do this I get a RUNTIME error: (System.ArgumentException: 'Cannot bind to the new display member. (Parameter 'newDisplayMember')'
)
When I try swapping key and value I get this same error: (System.ArgumentException: 'Cannot bind to the new display member. (Parameter 'newDisplayMember')'
)
When I try other combinations of key/value, I get random results. Sometimes it will show the entire class name (not helpful), sometimes it will show the ToString representation (overloaded and works perfectly except doesn't UPDATE after startup), and sometimes it just shows nothing or the program gives an error during runtime.
However no combination of things I have tried actually gets the BOX contents to UPDATE when modules are loaded and unloaded (the modules themselves are definitely loading/unloading and work fine).
This is supposedly working as of many years ago and I can only imagine microsoft broke something in one of their updates because the intended method does NOT work for me.
This is using .NET core 3.1 modules and .NET 5.0 application (required in order for modules to work because microsoft 5.0 exe does not work with microsoft 5.0 dll).
The overloaded ToString method of IModule returns Handle which is a string that names the module, IE "ConsoleModule", and works as intended. Everything else is working except the data binding.
Can anyone else at least confirm this data binding method actually works in .NET 5.0 and/or 3.1? Rapidly losing sanity.
Whenever you have a sequence of similar items, that you want to show in a ComboBox, you need to tell the ComboBox which property of the items should be used to display each item. You were right, this is done using ComboBox.DisplayMember
Your Dictionary<IModule, AssemblyLoadContext> implements IEnumerable<KeyValuePair<IModule, AssemblyLoadContext>, so you can regard it as if it is a sequence of KeyValuePairs. Every KeyValuePair has a Key of type IModule, and a Value of type AssemblyLoadContext.
The IModule and the AssemblyLoadContext have several properties. You need to decide which property of them you want to show.
I am trying to bind the names of the IModules (IModule.Handle)
I guess that every IModule has a property Handle, and you want to display this Handle in the ComboBox.
comboBox1.DisplayMember = nameof(IModule.Handle);
If you need a display only, so no updates, it is enough to convert your original sequence into a list:
Dictionary<IModule, AssemblyLoadContext> myData = ...
comboBox.DataSource = myData.ToList();
However, if you want to update the displayed data, you need an object that implements IBindingList, like (surprise!) BindingList<T>. See BindingList.
You can make a BindingList<KeyValuePair<IModule, AssemblyLoadContext>>, but this is hard to read, hard to understand, difficult to unit test, difficult to reuse and maintain. My advice would be to create a special class for this.
I haven't got a clue what's in the IModule, so you'll have to find a proper class name. I'll stick with:
class DisplayedModule
{
public string DisplayText => this.Module.Handle;
public IModule Module {get; set;}
public AssemblyLoadContext AssemblyLoadContext{get; set;}
}
And in the constructor of your form:
public MyForm()
{
InitializeComponent();
this.ComboBox1.DisplayMember = nameof(DisplayedModule.DisplayText);
This way, if you want to change the text that needs to be displayed, all you have to do is change property DisplayText.
public BindingList<DisplayedModule> DisplayedItems
{
get => (BindingList<DisplayedModule>)this.comboBox1.DataSource;
set => this.comboBox1.DataSource = value;
}
You need procedures to get the initial data:
private Dictionary<IModule, AssemblyLoadContext> GetOriginalData() {...} // out of scope of this question
private IEnumerable<DisplayedModule> OriginalDataToDisplay =>
this.GetOriginalData().Select(keyValuePair => new DisplayedModule
{
Module = keyValuePair.Key,
AssemblyLoadcontext = keyValuePair.Value;
});
I have put this in separate procedures, to make it very flexible. Easy to understand, easy to unit test, easy to change and to maintain. If for instance your Original data is not in a Dictionary, but in a List, or an Array, or from a database, only one procedure needs to change.
To initially fill the comboBox is now a one-liner:
private ShowInitialComboBoxData()
{
this.DisplayedItems = new BindingList<DisplayedModule>
(this.OriginalDataToDisplay.ToList());
}
private void OnFormLoad(object sender, ...)
{
this.ShowInitialComboBoxData();
... // other inits during load form
}
If the operator adds / removed an element to the list, the bindinglist is automatically updated. If something happens, after which you know that the dictionary has been changed, you can simply change the bindingList For small lists that do not change often, I would make a complete new BindingList. If the List changes often, or it is a big list, consider to Add / Remove the original BindingList.
private void AddDisplayedModule(DisplayedModule module)
{
this.DisplayedItems.Add(module);
}
private void RemoveDisplayedMOdule(DisplayedModule module)
{
this.DisplayedItems.Remove(module);
}
private void ModuleAddedToDictionary(IModule module, AssemblyLoadContext assembly)
{
this.AddDisplayedModule(new DisplayedModule
{
Module = module,
AssemblyLoadContext = assembly,
})
}
If the operator makes some changes, and indicates he finished editing the comboBox, for instance by pressing the "Apply Now" button, you can simply get the edited data:
private void ButtonApplyNowClicked(object sender, ...)
{
// get the edited data from the combobox and convert to a Dictionary:
Dictionary<IModule, AssemblyLoadContext> editedData = this.DisplayedItems
.ToDictionary(displayedItem => displayedItem.Module, // Key
displayedItem => displayedItem.AssemblyLoadContext); // Value;
this.ProcesEditedData(editedData);
}
To access the Selected item of the comboBox
DisplayedModule SelectedModule => (DisplayedModule)this.comboBox1.SelectedItem;
Conclusion
By separating you data from the way that it is displayed, changes will be minimal if you decide to change your view: change Combobox into a ListBox, or even a DataGridView. Or if you decide to change your data: not a Dictionary, but a sequence from a Database

How to populate dropdown box with indices list

Was hoping this could be a quick answer. New to C# and NEST (as is probably obvious from my previous posts).
I am using NEST to query my ES instance and have built a small winform application to help accomplish the task. I would like to have one of the comboboxes on my winform populate dynamically with the names of the indices from my cluster.
So far I have used:
var node = new Uri("http://xxx.xxx.x.xxx:xxx");
var settings = new ConnectionSettings(node);
var client = new ElasticClient(settings);
var myindexes = client.CatIndices();
I cannot for the life of me figure out how to populate the values of my combobox with the values store in "myindexes"
I have tried
combobox1.value = myindexes.ToList();
but can't see to figure out how to go one level deeper.
I know it is a simple question, but I would really appreciate the help if somebody could spare a few moments.
Thanks very much for the help as always!
Mick
Supposing myindexes is an object that implement IList or an Array. You can use either of these options:
ComboBox.DataSource
this.comboBox1.DataSource = myindexes;
ComboBox.Items.AddRange
this.comboBox1.Items.AddRange(myindexes.Cast<object>().ToArray());
Note:
The text that will be shown for items in ComboBox is the result of ToString method.
If you are showing a complex object, using DataSource way, you can set DisplayMember to one of properties of your complex object to show in ComboBox.
The object that you want to set to DataSource property, should be an object that implements the IList interface or an Array. If it's not, you should first convert it to the expected mentioned types.
you may use BindingSource
BindingSource bs = new BindingSource();
bs.DataSource = myindexes.ToList();
comboBox1.DataSource = bs;

Merging ILists to bind on datagridview to avoid using a database view

In the form we have this where IntaktsBudgetsType is a poorly named enum that only specifies wether to populate the datagridview after customer or product (You do the budgeting either after product or customer)
private void UpdateGridView() {
bs = new BindingSource();
bs.DataSource = intaktsbudget.GetDataSource(this.comboBoxKundID.Text, IntaktsBudgetsType.PerKund);
dataGridViewIntaktPerKund.DataSource = bs;
}
This populates the datagridview with a database view that merge the product, budget and customer tables.
The logic has the following method to get the correct set of IList from the repository which only does GetTable<T>.ToList<T>
public IEnumerable<IntaktsBudgetView> GetDataSource(string id, IntaktsBudgetsType type)
{
IList<IntaktsBudgetView> list = repository.SelectTable<IntaktsBudgetView>();
switch (type)
{
case IntaktsBudgetsType.PerKund:
return from i in list
where i.kundId == id
select i;
case IntaktsBudgetsType.PerProdukt:
return from i in list
where i.produktId == id
select i;
}
return null;
}
Now I don't want to use a database view since that is read-only and I want to be able to perform CRUD actions on the datagridview.
I could build a class that acts as a wrapper for the whole thing and bind the different table values to class properties but that doesn't seem quite right since I would have to do this for every single thing that requires "the merge".
Something pretty important (and probably basic) is missing the the thought process but after spending a weekend on google and in books I give up and turn to the SO community.
I do not exaclly see your problem. YOu can make a generic IList that supports an arary of ILists as constructor and basically exposes them merged. This runs down to a lot of plumbing code, but is very generic. THe IList only needs to track which original list every item is related to, so it can properly forward delete operations.

Re-ordering collection C#

I have a problem which I cant seem to find answer to through searches (either that or I am searching for the completely wrong thing!). I have a list of items called "Top 10" in a sortedlist item that is populated from my DB (SortedList where int is position and string is item). I want to be able to move items up & down the list order at the click of a button and then save the new order back to the DB. I am OK with the DB part it is just the re-ordering I am really struggling with - is a sortedlist the correct collection for this?
Many thanks for any advice offered.
A SortedList is for maintaining order within your SortedList as you add or remove items from it.
You should create a custom list of your objects and then sort on property of that object.
So if your entry in your database was like this you could place it in an object, add it to a list and then sort it using Lambda on which ever criteria you like
public class LeaguePosition
{
public int Position { get; set; }
public string Team { get; set; }
}
List<LeaguePosition> League = new List<LeaguePosition>();
League.Add(new LeaguePosition() { Position = 2, Team = "Wolves" });
League.Add(new LeaguePosition() { Position = 3, Team = "Spurs" });
League.Add(new LeaguePosition() { Position = 1, Team = "Villa" });
League.Sort((teamA, teamB) => teamA.Position.CompareTo(teamB.Position));
You can also then use RemoveAt() and Insert() to move items about to custom positions within the list.
LeaguePosition teamToMove = League[1];
League.RemoveAt(1);
League.Insert(2, teamToMove);
No, a SortedList will keep things in sorted (alpha/numeric) order. You want a plain list that will let you pull things out and insert them at different positions.
A SortedList is going to force each item to keep track of its position within the list relative to the other items (your int position). This should be a responsiblity of the Collection, so you should use some sort of Collection that will allow you to move things up/down/around without having to manually correct each item.
I would say a SortedList is exactly what you want. When you get data back from the database you want to keep it in order based on the position field (what I can tell from your OP). I'm assuming you will have a class that contains the data you're getting back from the DB. To keep the data in the right order you will need to implement the IComparable interface in your custom class so that the SortedList knows what values to use to keep the list in order. This way as you add/remove items you will keep them in the right order.
You could use a generic List<> but then you have to write all the code to sort your items yourself, the SortedList already does this so why re-invent the wheel?
Take a look at this page for more info:
http://www.dotnetspider.com/resources/4679-Applying-custom-sort-SortedList.aspx

How do I pass a property changed event to an ancestor?

I'm sure I am missing some of the basics here so please bear with me.
I have a two objects, a SalesOrder and a SalesOrderLineItem. The lineitems are kept in an Observablecollection<SalesOrderLineItem> of SalesOrder, and if I add line items my databound listView knows about the new items.
Now where I am having a problem is here: The SalesOrder has a read only property called "OrderTotal", which knows what the total price from all the line items combined are.
If I change the quantity, or price of a line item, I don't seem to know how to get change notification to bubble up to "OrderTotal".
Both classes inherit from, and fire INotifyPropertyChanged.
Wham am I missing?
The ObservableCollection only notifies when an Item is added, removed, or when the list is refreshed, not when a property of an Item within the collection changes.
The SalesOrder class needs to be listening to each SalesOrderLineItem's PropertyChanged event.
-
The OrderTotal property could also be dynamic, generating the total each time...
If you feel adventurous, you can try BindableLinq or something similar.
It allows you to expose the result of a query that listens to changes.
But it's not out of beta yet, and I'm not sure it will ever be...
ContinuousLinq is another one, but it does not support Silverlight at all yet.
Other than that, you'll have to listen to each child, and add/remove handlers whenever the collection changes.
public ObservableCollection<TestClass> List
{
get { return _List; }
set {
if (_List!= value)
{
_List = value;
raisePropertyChange("List");
List.ToList().ForEach(i=> i.PropertyChanged+= (o,e)=>
{
//PropertyChanged.Raise(() => List);
raisePropertyChange("List");
//or what ever you want
}
);
}
}
}

Categories