Populate multiple comboboxes from 1 ienumerable - vb.net

Good afternoon,
Is there a way to divide a ienumerable and bind all the different values to the different comboboxes.
Like you can see in the picture below:
I got 10 comboboxes, while the input of these all come from 1 ienumerable.
I do have an option to do a for each and go through the entire database, and add them to the combobox with:
Dim ts As IEnumerable(Of tblLabel)
For Each itms In ts
CmbDescription.Items.Add(itms.Description)
CmbLabelId.Items.Add(itms.LabelID)
...
Next
But I wonder if I can link the different 'columns' of the Ienumerable directly to the datasource of the associated comboboxes.
I'm searching for an option like:
CmbDescription.DataSource = ts.Description
CmbLabelId.DataSource = ts.LabelId
...
Sadly enough, this ienumerable can't be splitsed like this, as far as I can see.
Another workaround would be to create all separate ienumerables for all those comboboxes, but then it is too much code.
Any idea?

I think your original approach is good enough.
But if you want populate ComboBoxes by separated collection of items using DataSource property, then you can simply get needed collection from IEnumerable
CmbLabelId.DataSource =
ts.Select(function(label) label.LabelId).Distinct().ToList()
CmbDescription.DataSource =
ts.Select(function(label) label.Description).Distinct().ToList()
But in this approach you will loop IEnumerable as much times as how much ComboBoxes you have.
Here is my approach, but again want to say that your original approach is simple enough.
' In this class will be collected all distinct value of all columns
' Create own property for every column which used in the ComboBoxes
' With HashSet only distinct values will be collected (thanks to #Ivan Stoev's comment)
Public Class TblLabelProperties
Public Property LabelId As New HashSet(Of Integer)
Public Property Description As New HashSet(Of String)
' Other properties/columns
End Class
' Populate collections from database
Dim ts As IEnumerable(Of tblLabel)
Dim columnsValues As TblLabelProperties =
ts.Aggregate(New TblLabelProperties(),
Function(lists, label)
lists.LabelId.Add(label.LabelId)
lists.Description.Add(label.Description)
'Add other properties
Return lists
End Function)
' Set DataSources of comboboxes
CmbLabelId.DataSource = columnsValues.LabelId.ToList()
CmbDescription.DataSource = columnsValues.Description.ToList()

One way to implement this without putting each data source to each ComboBox is by implementing mapping between the column name in DataGridView.Columns and the combo box name ComboBox.Name.
This can be done by using Dictionary so for each column name, you map to specific ComboBox. Then, you can do the populating by foreach or for loop.
Yet, it might still be preferable in some cases that you really take each ComboBox having its own data source

Related

Getting the Index of an Added ListView.Item

So all I am trying to do is get the index of an Item Programmatically added to a ListView from a Database.
Example:
Index = lvwNames.Items.Add(Player)
Player is a Class that uses Get/Set methods for ID, FirstName, and, LastName.
I have googled my heart out to no avail.
EDIT
So I had a GetInformation() procedure that populated my Player Class. So I was helped by a personal friend, I just needed to pass in my ListViewItem ByRef into there and I can grab the data I needed (SUCH A DUMMY!). Still having an issue with the Index. I would like to have the newly added item selected after it is added.
Getting the Index of an Added ListView.Item as per the title.
Method A: Look at the count
lvwNames.Items.Add(Player.ToString())
Dim ndx = lvwNames.Items.Count-1
Method B: Use an object
Dim lvi As New ListViewItem(Player.ToString())
lvwNames.Items.Add(lvi)
Dim ndx = lvi.Index
However, Player is an Object and there is no matching Add overload, with Option Strict (which all good code should use). The best you can do is pass it as ToString() as shown.
The result will be something like WindowsApplication1.Player unless your class overrides ToString(). In no case will the ListView parse it to put the properties ("Get/Set methods") as sub items which sort of sounds like what you expect.
For that, a DatagridView would be a better choice using a List(Of Player). Each property could be mapped to its own column.

Load comboboxes with one line of code?

I have around 15 comboboxes on my form, all being loaded with the same information pulled from a table(~150 entries). Currently I am taking the information from the table, then looping through the entries and adding them to each textbox. I'm wondering if there's a more efficient way to load these comboboxes then having to individually add the table entry into each combobox, having to list 15 lines of code within the For loop.
I'm not seeing any performance issues with this, but figured I might as well work with the most efficient way possible rather than stick with what works. :)
You can create a list of the combo boxes, and then just loop through them. For instance:
Dim cbos() As ComboBox = {ComboBox1, ComboBox2, ComboBox3}
For Each cbo As ComboBox In cbos
' Load cbo from table
Next
Alternatively, if they are named consistently, you could find the combo box by name:
For i As Integer = 1 to 15
Dim cbo As ComboBox = DirectCast(Controls("ComboBox" & i.ToString())), ComboBox)
' Load cbo from table
Next
Since Combobox items are a collection, if their elements are the same, you can build and array with the objects you want to insert, and then just insert this array to each ComboBox with the method AddRange() (it's a method which exists inside the Combobox.items).
Getting an example from MSDN:
Dim installs() As String = New String() {"Typical", "Compact", "Custom"}
ComboBox1.Items.AddRange(installs)
Then you would only have to do a loop to add the array to each ComboBox. Of course, you will need to build your array first on your own, instead of this easy string array from the example.
Reference:
MSDN - AddRange
You could also do it this way since you mentioned that you already have a table.
Use a datatable
Change your table object into a datatable, which will assist in binding to the comboboxes. It might help if you add the datatable to a dataset too. That way you can attach all ComboBoxes (which are UI elements that let users see information) to the same DataSource, which is the datatable, in the dataset.
Binding
Now all you need to do is loop through all the comboboxes and set the datasource to the same table, that is if you decide to do it programmatically like so:
ComboBox1.DataSource = ds.Tables(0)
ComboBox1.ValueMember = "au_id"
ComboBox1.DisplayMember = "au_lname"
A further tutorial on this with the example above is found here
You can then also get the user selected value with ComboBox1.selectedValue.
On the other hand, if you did this with C# WPF, you can bind each comboBox in the XAML directly, I am unsure if this can be done in VB.net as I tried to look for the option but did not manage to do so, something you might want to try though.
Some very useful tutorials and guides on Data binding, which you might be interested:
~ denotes recommended reading for your question
MSDN: Connect data to objects
DotNetPerls on DataGridView (note this isn't a combobox, just displaying values)
~ VBNet DataTable Usage from DotNetPerls (this is in relation to 1.)
~ SO Q&A on Binding a comboBox to a datasource
Concepts of Databinding

How to add data table items in a combo box?

I have a function which return a list of countries as data table. How do I add these row values to a combo box?
For Each row as DataRow in fooDataTable.Rows
combobox.Items.add(row)
Next
Some how this does not fill up the values on my combo box. The datatable returns 100 rows. Each row has 2 columns. I am trying to add all the 100 datarows with the first column. Any help would be greatly appreciated.
A DataRow is an object and while objects can be added to a Combo or ListBox, in this case the result is not very useful. When you store an Object in .Items, the result of the .ToString function is what displays. In this case it will just display System.Data.Datarow
The best solution is what LarsTech suggested and use the existing datatable as the datasource. This is economical because there is nothing new created for the CBO:
ComboBox.DataSource = fooDataTable
ComboBox.DisplayMember = "column name in DT"
Another way is to modify your loop:
combobox.Items.Add(row("col name").ToString)
This adds the data from the column name to the cbo. If this was something like peoples names and you wanted to show First and Last name:
combobox.Items.Add(String.Format("{0}, {1}", row("LastName").ToString,
row("FirstName").ToString))
This is similar except we are formatting the contents of both columns as we add it to the CBO.
Still another way is to create a class object for each row which returns what you want to display via the ToString override; and add them to the CBO, but that is overkill in this case, given the economy of the first alternative.

VB.Net ComboBox - Enums and Limiting Values Based on Selection in Another ComboBox

The project I'm working on makes extensive use of Enum's to hold unit symbols. Like this...
Public Enum AreaSym
unknown = 0
in2
mm2
End Enum
My Problems began when I realized that my Enum names didn't make good names for the drop downs on my ComboBoxes. I found and implemented a TypeConverter from this C# CodeProject http://www.codeproject.com/KB/cs/LocalizingEnums.aspx?msg=3746572 ... It allows you to assign the Enum Values friendly names in a .Resx file, and it overloads the GetValues function so that it returns the Enums friendly names from the .Resx file. This works well as I can then assign the combobox data source directly to the enum.
ComboBox1.DataSource = System.Enum.GetValues(GetType(AreaSym))
However, I realized that when using a Datasource in this manner, I have to manipulate the data source to create a change in the ComboBox. Such as, if I don't want to display unknown to the user, I'd need another way to load my ComboBoxes. This is when I decided to make a List(Of KeyValuePair(AreaSym, String))
Public Function AreaSymbols() As List(Of KeyValuePair(Of AreaSym, String))
AreaSymbols = New List(Of KeyValuePair(Of AreaSym, String))
AreaSymbols.Add(New KeyValuePair(Of AreaSym, String)(AreaSym.in2, "in²"))
AreaSymbols.Add(New KeyValuePair(Of AreaSym, String)(AreaSym.mm2, "mm²"))
Return AreaSymbols
End Function
This is great because now I can control exactly what goes into the combo box using the list.
ComboBox1.DataSource = UnitNameList.AreaSymbols
ComboBox1.DisplayMember = "Value"
ComboBox1.ValueMember = "Key"
However, I've found that using a DataSource has stopped me from doing two things.
When the form loads, I want the ComboBox itself to show as either empty with no text, or to show the Text I've input in the Text Property. Currently using the DataSource, it always is populated with the first item in the list.
I'd like to be able to easily limit the items in the list, based on the selection of items in another list. Classic example would be two ComboBoxes filled with the exact same values (1,2,3) & (1,2,3). When you choose 1 in ComboBox one, only (2,3) are available for selection in ComboBox 2.
I realize that I could probably easily do that if I was using Items, instead of a data source, I'm just not sure what the best way to do that would be because of my need to have the key, value pair of enums and strings...

Comparing arrays in VB.NET

Let me provide a little detail to explain what I'm trying to accomplish before I get into the nuts and bolts of the question.
I've got two data sources - one is a SQL Server containing part numbers, descriptions, etc. The other is a CAD system that does not have a database in a traditional sense. What I'm trying to do is read out the bill of materials from the SQL Server and compare it to the CAD assembly drawing to ensure that the CAD system contains the same information as the SQL Server.
Getting the data from the SQL Server is fairly straight forward. I query the database and populate a datagrid. Done. Quick. Easy.
Getting the data from the CAD system is a little more involved. I have to load the assembly drawing to get a listing of all the component parts and then load those individual drawings to pull the "Part Number" property from the drawing. This is a somewhat time consuming and slow process (unfortunately) since each of the files must actually be accessed. I load those properties into an array (I guess a list might be more efficient).
So now I have a datagrid and array with part numbers. I need to compare them and colorize the grid accordingly. The grid should remain transparent if the part exists in both, color the row yellow if it only exists in the grid, and add a row colored red if only in the array.
As best I can tell, this means looping through the array on each line of the grid. The thought process is this:
Default the grid to yellow rows.
Loop through the grid and loop through the array to compare. If a match is found, make the row transparent and delete the element from the array.
After step 2 is completed, the array should only contain elements that are not found in the grid. Resize the array to remove the empty elements.
Add the elements of the array to the grid and color those new rows red.
The problems with this logic is that it seems expensive from a performance standpoint. Surely there is a better method? Also, if I modify the grid in some manner (like a resort) I have to go through the process again. I'd really appreciate some advice on this.
Thanks!
Note: written in Visual Studio 2005.
You could load the data from the CAD system in a dictionary (indexed by part number). Then you could go through the grid and check if it exists in the dictionary, which is a fast operation ( O(1) ). You could do exactly as you say, remove the found elements in the dictionary and add the remaining elements in the datagrid.
Here's some code for creating and using a dictionary (used C# style comments to preserve formatting):
//First argument is your key type, second is your item type
Dim cadParts As New Dictionary(Of Integer, Part)
//Add items to the parts dictionary
For Each part As Part In cadPartsArray
cadParts.Add(part.PartNumber,part)
Next
//Check if a part exists
Dim partNumber As Integer = 12345
If cadParts.ContainsKey(partNumber) ...
//Remove a part
cadParts.Remove(partNumber)
//Go through the remaining values
For Each part As Part In cadParts.Values ...
Edit:
1) Yes, if your key (here, part number) is a string, then a Dictionary(Of String,...) would be used.
2) I assumed you had a class named Part, which contained some information about a part. If you just have a part number, and no other info, then you could create a Hashset instead. It is basically the same as a dictionary, but with this structure the value is also your key. You would create a hashset like this:
Dim cadParts As New Hashset(Of String)
I won't go through code examples because it is very close to the Dictionary. ContainsKey becomes Contains, and Add accepts only one argument (which would be your part number here).
3) Yes, loop through them and add them to the hashset.
If part number is unique (it is not repeated in the values you want to search for) then you can use sorted dictionary. Then remove the duplicates and use remaining items.
To compare you can use part number as follows:
if(dictionary.ContainsKey(partNumber))
dictionary.Remove(partNumber)
end if
Given that there will only be a certain number of rows of the data grid visible at one time, it might be quicker to implement some code in the OnPaint method (I'm a little rusty here so apologies if that isn't exactly right) for each of the rows which checks against the array of part information and sets the background color of each row as it becomes visible, perhaps even marking each row as having been processed so the operation doesn't need to be repeated. There might be an initial performance gain here over processing all of the rows at once.
Of no real relevance; but from previous experience this sounds like you are interfacing with AutoDESK Inventor files?
Another solution could be to implement the IComparable(Of T) interface
That would require for you to build a class that you would use for both situation.
Public Class Item
Implements IComparable(Of Item)
Private _Description As String
Public Property Description() As String
Get
Return _Description
End Get
Set(ByVal value As String)
_Description = value
End Set
End Property
Private _PartNo As Integer
Public Property PartNo() As Integer
Get
Return _PartNo
End Get
Set(ByVal value As Integer)
_PartNo = value
End Set
End Property
Public Function CompareTo(ByVal other As Item) As Integer Implements System.IComparable(Of Item).CompareTo
' Your rules for comparing content for objects
If other.PartNo <> Me.PartNo Then Return 1
If other.Description <> Me.Description Then Return 1
' Return 0 if the object are the same
Return 0
End Function
End Class
Here is a small test that works with upper implementation.
Dim item As New Item
item.Description = "Desc"
item.PartNo = 34
Dim item2 As New Item
item2.Description = "Desc"
item2.PartNo = 35
Dim item3 As New Item
item3.Description = "Desc"
item3.PartNo = 36
Dim listFromDatabase As New Generic.List(Of Item)
listFromDatabase.Add(item)
listFromDatabase.Add(item2)
If listFromDatabase.Contains(item2) Then
MessageBox.Show("item2 was found in list")
End If
If Not listFromDatabase.Contains(item3) Then
MessageBox.Show("item3 was NOT found in list")
End If
Hope it helps a bit,
- Dan