Dynamically add Items to Combobox VB.NET - vb.net

I have 2 combobox in windows application. I want to programmatically add Items to second combobox based on what is selected in the first combobox.
This is what I do in the selected index change of the combobox:
Private Sub ComboBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ComboBox1.SelectedIndexChanged
Try
If ComboBox1.SelectedIndex = 0 Then
ComboBox2.Items.Add("MM")
ComboBox2.Items.Add("PP")
ElseIf ComboBox1.SelectedIndex = 1 Then
ComboBox2.Items.Add("SMS")
ElseIf ComboBox1.SelectedIndex = 2 Then
ComboBox2.Items.Add("MMS")
ComboBox2.Items.Add("SSSS")
End If
Catch ex As Exception
End Try
End Sub
It works fine, however, if I keep selecting different items it's keep adding the value over and over. I would like to add those values only once.
Also, when I add an item I would prefer to add an ID with the item description. I tried:
ComboBox2.Items.Add("SSSS", "1")
It seems that it's not working.
Any suggestions?

try this
Private Sub ComboBox1_SelectedIndexChanged(ByVal sender As Object, ByVal e As EventArgs) Handles ComboBox1.SelectedIndexChanged
Try
If ComboBox1.SelectedIndex = 0 Then
If Not (ComboBox2.Items.Contains("MM")) And Not (ComboBox2.Items.Contains("PP")) Then
ComboBox2.Items.Add("MM")
ComboBox2.Items.Add("PP")
End If
ElseIf ComboBox1.SelectedIndex = 1 Then
If Not (ComboBox2.Items.Contains("SMS")) Then
ComboBox2.Items.Add("SMS")
End If
ElseIf ComboBox1.SelectedIndex = 2 Then
If Not (ComboBox2.Items.Contains("MMS")) And Not (ComboBox2.Items.Contains("SSSS")) Then
ComboBox2.Items.Add("MMS")
ComboBox2.Items.Add("SSSS")
End If
End If
Catch ex As Exception
End Try

Regarding:
"Also, when I add an item I would prefer to add an ID with the item
description"
You can use the AddRange function of the ComboBox or make a list as I show here and use it as a datasource - as #Plutonix mentioned in his comment.
With it you can add an array of objects of a type that holds a value and a description, given that you override the ToString base function (of Object class).
Here is such a class:
Public Class CodeAndDescription
Public code As String
Public description As String
Public Overrides Function ToString() As String
Return Me.code + " - " + description
End Function
End Class
Now make a list of that upper class and add it to the combobox. Something like:
Dim lstItems As List(Of CodeAndDescription) = GetList()
yourComboBox.Items.Clear()
yourComboBox.Items.AddRange(lstItems.ToArray)
Don't forget to Cast the retrieved object when you take it out of the combo:
Dim codeDesc As CodeAndDescription = TryCast(yourComboBox.Items(0), CodeAndDescription)
I've done this on a check list, but I think the principle is the same for a ComboBox.

Related

VB.NET: Add\remove row indexes of datagridview checkboxes rows

I am working with forms in VB.NET
There is a DatagridView table with a checkbox column.
See the picture below:
I am interested in the question: how to add the line index to the list when clicking in the checkbox (when we activate the checked status), and remove it from the list when we uncheck the checkbox?
Tried the following but this is not the correct solution:
If e.ColumnIndex = chk_column.Index Then
If e.RowIndex >= 0 Then
Try
For Each row As DataGridViewRow In dataGridNames.Rows
Dim cell As DataGridViewCheckBoxCell = TryCast(row.Cells(5), DataGridViewCheckBoxCell)
If cell.Value Is cell.FalseValue Then
bList_indexes.Add(DataGridnames.CurrentCell.RowIndex)
Exit For
Else 'If cell.Value Is cell.TrueValue Then
bList_indexes.RemoveAt(DataGridnames.CurrentCell.RowIndex)
End If
Next
Catch ex As Exception
'Show the exception's message.
'MessageBox.Show(ex.Message)
'Throw New Exception("Something happened.")
End try
End If
End If
Using DataSources allows you to take the logic out of mucking around in DataGridView events. You shouldn't perform [much] business logic on the UI anyways.
Here is the class I used to represent your data.
Public Class ClassWithSelect
Public Property [Select] As Boolean
Public Property Name As String
Public Sub New(s As Boolean, n As String)
Me.Select = s
Me.Name = n
End Sub
End Class
And all the code to set DataSources
Private myDataSource As List(Of ClassWithSelect)
Private selectedIndices As List(Of Integer)
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
myDataSource = Enumerable.Range(65, 10).Select(Function(i) New ClassWithSelect(False, Chr(i).ToString())).ToList()
DataGridView1.DataSource = myDataSource
updateSelectedIndices()
End Sub
Private Sub DataGridView1_CellValueChanged(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellValueChanged
updateSelectedIndices()
End Sub
Private Sub DataGridView1_CellContentClick(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellContentClick
DataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit)
End Sub
Private Sub updateSelectedIndices()
selectedIndices = New List(Of Integer)()
For i = 0 To myDataSource.Count - 1
If myDataSource(i).Select Then selectedIndices.Add(i)
Next
ListBox1.DataSource = selectedIndices
End Sub
And the end result
Now you don't need to access the UI to get the indices for further processing as they are in the class-level variable selectedIndices. The UI is meant for user I/O, NOT for storing state.
Note: The event handler was taken from this answer but this answer is also linked as an improvement to the check change handler, but I felt the complexity would distract from my answer. If you find you need to click fast, look into the latter.
Also Note: The method updateSelectedIndices() should have inside it an InvokeRequired check if you plan to perform work off the UI thread

How to properly use .SelectedIndex property for listbox using arrays

Public Module Inv
Public Item(4) as String
End Module
Private Sub Inventory_SelectedIndexChanged(sender As Object, e As EventArgs) Handles Inventory.SelectedIndexChanged
If Inventory.SelectedIndex.ToString(Item(0)) Then
MessageBox.Show("Item Selected!")
playerDMG *= 3
End If
End Sub
Private Sub Button3_Click(sender As Object, e As EventArgs)
Item(0) = "Plasma"
for add = 0 to 0
inventory.items.add(item(add))
End Sub
What I want is that if I click that button it adds Item(0) to the listbox, and if I click on the item in the listbox it will triple playerDMG.
Problem here is that it is telling me I can't convert "Plasma Gun" to type 'Boolean'
What's going wrong here? Is there a better way to do this?
I think you would need to do something like this:
If Inventory.SelectedItem.ToString() = Item(0) Then ...
Alternatively, you could use SelectedValue I guess, but in order for that to work you'd need to use DataSource property of combobox.

How to remove an item in a two dimensional array with a list box

Hi I have an assignment for coding and I am having a hard time to figure out how to code it. My teacher wanted us to build a program that uses a list box that holds product names and a 2-D array that holds quantity in stock and price. Then in the one of the buttons in the application, which is the remove button, the item in the list box as well as the data from the array should be removed. When the user deletes an item, not only must the list loose the name of the item but the 2-D array must also be readjusted.
I am sorry. My brain just doesn't want to do what your teacher wants. If you can make sense of the following, maybe you can convince your teacher not to ask you to use 2D arrays in this instance.
Public Class Product
'These are automatic properties
'The compiler provides the getter, setter and backer fields.
Public Property Name As String
Public Property Quantiy As Integer
Public Property Price As Double
'ToString is a method inherited from Object. It will return a fully
'qualified name of the Type, not what we want in a ListBox
'The ListBox will call .ToString on Product and we will get the Name
'property of the Product object.
Public Overrides Function ToString() As String
Return Name
End Function
Public Sub New(prodName As String, prodQuantity As Integer, prodPrice As Double)
'We add a parameterized Constructor to make it easy to add our Product objects
'to the ListBox. Intelisense will help out once we type the New keyword
Name = prodName
Quantiy = prodQuantity
Price = prodPrice
End Sub
End Class
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'The New keyword calls the Constructor of the Product class
'so a Product object is added to the ListBox
ListBox1.Items.Add(New Product("Bell", 30, 2.98))
ListBox1.Items.Add(New Product("Book", 7, 200))
ListBox1.Items.Add(New Product("Candle", 42, 14.99))
End Sub
Private Sub ListBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ListBox1.SelectedIndexChanged
'When a Product is deleted from the ListBoX with the RemoveItem button
'this event will fire. (The index changed) -1 means nothing is selected which is
'what happens after the delete. If Nothing is selected our cast will fail and
'we will get an exception.
If ListBox1.SelectedIndex <> -1 Then
'The ListBox items are objects but underneath the objects are Products
'so we can Cast the object back to a Product
Dim p As Product = CType(ListBox1.SelectedItem, Product)
'All the properties of the Procuct are then available
MessageBox.Show($"Product Name is {p.Name}. There are {p.Quantiy} on hand. The price is {p.Price:N2}")
End If
End Sub
Private Sub btnRemoveItem_Click(sender As Object, e As EventArgs) Handles btnRemoveItem.Click
ListBox1.Items.Remove(ListBox1.SelectedItem)
End Sub
End Class
This should work, though it's really the wrong way to approach this:
LBProducts = Form ListBox
lblQuantity = Form Label
lblPrice = Form Label
btnDelete = Form Button
Public Class Form1
'5 Rows, (Price, Qty)
Private ProductArray(5, 1) As Integer
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
LBProducts.Items.Add("Apples")
LBProducts.Items.Add("Bananas")
LBProducts.Items.Add("Grapes")
LBProducts.Items.Add("Oranges")
LBProducts.Items.Add("Peaches")
For x = 0 To 5
ProductArray(x, 0) = x
ProductArray(x, 1) = 1
Next
End Sub
Private Sub LBProducts_SelectedIndexChanged(sender As Object, e As EventArgs) Handles LBProducts.SelectedIndexChanged
Dim Index = LBProducts.SelectedIndex()
If Index >= 0 Then
lblPrice.Text = "Price: " & ProductArray(Index, 0)
lblQuantity.Text = "Qty: " & ProductArray(Index, 1)
End If
End Sub
Private Sub btnDelete_Click(sender As Object, e As EventArgs) Handles btnDelete.Click
Dim Index = LBProducts.SelectedIndex()
If Index >= 0 Then
LBProducts.Items.RemoveAt(Index)
Dim NewArray(UBound(ProductArray) - 1, 1) As Integer
Dim i As Integer = 0
For x = 0 To UBound(ProductArray)
If x <> Index Then
NewArray(i, 0) = ProductArray(x, 0)
NewArray(i, 1) = ProductArray(x, 1)
i += 1
End If
Next
ProductArray = NewArray
End If
End Sub
End Class

VB.NET datagridview one-to-one mapping of combobox

I have a datagridview with two textbox columns and one combobox column. The combobox's DataSource is bound to enums for the values of the dropdown. The datagridview's DataSource is bound to a custom class with datatypes of string, string and enum.
The first two columns are pre-populated with values and in the third column the user must select a value from the dropdown. All this is working excellent so far except....
The combobox field should be a one-to-one kind of mapping, meaning no two comboboxes should have the same value. I am really not sure how to implement this kind of behavior. Should the chosen value be removed from the remaining dropdowns? should the chosen value remain in the dropdown and just give an error when two of the same are selected?
Any ideas and how to implement these ideas will be of great help.
Thanks
note the only acceptable value that can be used more than once is 'None'
I have an idea based off your intent to possibly remove the chosen value from the remaining dropdowns.
I have a class called Runner which has a similar setup to your example.
Public Class Runner
Public Property FirstName As String
Public Property LastName As String
Public Property Placement As Result
Public Sub New(fn As String, ln As String)
FirstName = fn
LastName = ln
Placement = Result.None
End Sub
End Class
I also have an enum called Result which will get populated into the ComboBoxCell:
Public Enum Result
None = 0
Bronze = 1
Silver = 2
Gold = 3
End Enum
When I create data objects and bind them to the DataGridView, I do so like this (note - Placements is a global List(Of Result) and Runners is a global List(Of Runner):
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Placements.Add(Result.None)
Placements.Add(Result.Bronze)
Placements.Add(Result.Silver)
Placements.Add(Result.Gold)
Runners.Add(New Runner("John", "Smith"))
Runners.Add(New Runner("Jane", "Doe"))
Runners.Add(New Runner("Bill", "Jones"))
Column1.DataPropertyName = "FirstName"
Column2.DataPropertyName = "LastName"
Column3.DataPropertyName = "Placement"
Column3.DataSource = Placements
DataGridView1.DataSource = Runners
End Sub
Now, whenever the value of a cell in the ComboBoxColumn changes, you remove the new value from the list that contains the enums:
Private Sub DataGridView1_CellValueChanged(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellValueChanged
If (e.RowIndex > -1 And e.ColumnIndex = 2) Then
Dim currentvalue As Result = DataGridView1.Rows(e.RowIndex).Cells(e.ColumnIndex).Value
If currentvalue <> Result.None Then
Placements.Remove(currentvalue)
End If
End If
End Sub
Be careful when changing drop down selections... as per this Discussion, you have to trick the DataGridView into committing values as soon as the ComboBox value changes. I used an answer from that discussion and did something like this:
Private Sub DataGridView1_CurrentCellDirtyStateChanged(sender As Object, e As EventArgs) Handles DataGridView1.CurrentCellDirtyStateChanged
Dim col As DataGridViewColumn = DataGridView1.Columns(DataGridView1.CurrentCell.ColumnIndex)
If col.Name = "Column3" Then
DataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit)
Dim selected As DataGridViewCell = DataGridView1.CurrentCell
DataGridView1.CurrentCell = Nothing //This line and the next one simply hide
DataGridView1.CurrentCell = selected //an odd display effect that occurs
//because we remove a value and change the
//selection at the same time
End If
End Sub
Finally, you want to handle the DataError event for the DataGridView and leave it blank so that you don't get Exceptions thrown at you when removing values from your list of enums.
This gets you about 90% of the way there. It will not re-add items to the list when you change a value. For example if I change from Gold to Silver, Gold should be added back to the list. You can probably figure out what events to handle to get the old value and the new one, and insert the old value back into the list at the correct index based on its enum value.
I decided to use the CellValidating event of the DataGridView to check whether or not the same value is selected more than once. If it is then a error message is displayed in the row header column.
The combobox in error will not lose focus until the error is resolved.
Private Sub DataGridView1_CellValidating(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellValidatingEventArgs) Handles DataGridView1.CellValidating
Dim headerText As String = DataGridView1.Columns(e.ColumnIndex).HeaderText
'Abort validation if cell is not in the Mapping column.
If Not headerText.Equals("Column Mapping") Then Return
'Clear error on current row.
DataGridView1.Rows(e.RowIndex).ErrorText = Nothing
e.Cancel = False
Dim newMappingValue As XmlElement = DirectCast([Enum].Parse(GetType(XmlElement), e.FormattedValue), XmlElement)
' Abort validation if cell value equal XmlElement.None
If newMappingValue.Equals(XmlElement.None) Then Return
For Each dgvRow As DataGridViewRow In DataGridView1.Rows
Dim currentMappingValue As XmlElement = dgvRow.Cells.Item(headerText).Value
If dgvRow.Index <> e.RowIndex Then
If currentMappingValue.Equals(newMappingValue) Then
DataGridView1.Rows(e.RowIndex).ErrorText = "Value already selected, please select a different value."
e.Cancel = True
End If
End If
Next
End Sub

Search ListBox elements in VB.Net

I'm migrating an application from VB6 to VB.Net and I found a change in the behavior of the ListBox and I'm not sure of how to make it equal to VB6.
The problem is this:
In the VB6 app, when the ListBox is focused and I type into it, the list selects the element that matches what I type. e.g. If the list contains a list of countries and I type "ita", "Italy" will be selected in the listbox.
The problem is that with the .Net version of the control if I type "ita" it will select the first element that starts with i, then the first element that starts with "t" and finally the first element that starts with "a".
So, any idea on how to get the original behavior? (I'm thinking in some property that I'm not seeing by some reason or something like that)
I really don't want to write an event handler for this (which btw, wouldn't be trivial).
Thanks a lot!
I shared willw's frustration. This is what I came up with. Add a class called ListBoxTypeAhead to your project and include this code. Then use this class as a control on your form. It traps keyboard input and moves the selected item they way the old VB6 listbox did. You can take out the timer if you wish. It mimics the behavior of keyboard input in Windows explorer.
Public Class ListBoxTypeAhead
Inherits ListBox
Dim Buffer As String
Dim WithEvents Timer1 As New Timer
Private Sub ListBoxTypeAhead_KeyDown(sender As Object, _
e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
Select Case e.KeyCode
Case Keys.A To Keys.Z, Keys.NumPad0 To Keys.NumPad9
e.SuppressKeyPress = True
Buffer &= Chr(e.KeyValue)
Me.SelectedIndex = Me.FindString(Buffer)
Timer1.Start()
Case Else
Timer1.Stop()
Buffer = ""
End Select
End Sub
Private Sub ListBoxTypeAhead_LostFocus(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.LostFocus
Timer1.Stop()
Buffer = ""
End Sub
Public Sub New()
Timer1.Interval = 2000
End Sub
Private Sub Timer1_Tick(sender As Object, e As System.EventArgs) Handles Timer1.Tick
Timer1.Stop()
Buffer = ""
End Sub
End Class
As you probably know, this feature is called 'type ahead,' and it's not built into the Winform ListBox (so you're not missing a property).
You can get the type-ahead functionality on the ListView control if you set its View property to List.
Public Function CheckIfExistInCombo(ByVal objCombo As Object, ByVal TextToFind As String) As Boolean
Dim NumOfItems As Object 'The Number Of Items In ComboBox
Dim IndexNum As Integer 'Index
NumOfItems = objCombo.ListCount
For IndexNum = 0 To NumOfItems - 1
If objCombo.List(IndexNum) = TextToFind Then
CheckIfExistInCombo = True
Exit Function
End If
Next IndexNum
CheckIfExistInCombo = False
End Function