VB.NET ComboBox Verify if Value Exists - vb.net

I am currently working on an application that was coded in VB. I am making modifications and adding features to it.
The issue I have is that I want to run a verification check for the ComboBox based on if the value exists before I attempt to select it.
The combo box is populated from a sql query with a dictionary data source
Dim TimerComboSource As New Dictionary(Of String, String)()
TimerComboSource.Add(varSQLReader("ID").ToString, varSQLReader("Name").ToString)
'Binds the values to the comboboxes
cmbTimer.DataSource = New BindingSource(TimerComboSource, Nothing)
cmbTimer.DisplayMember = "Value"
cmbTimer.ValueMember = "Key"
I select a value from a different ComboBox which is populated with a different SQL Query/SQL Table.
When I select the second ComboBox value, the table it comes from contains the ID of the first ComboBox. I want to verify if the Value Exists in the first ComboBox before I select it.
The following does not work:
If cmbTechnician.Items.Contains(varSQLReader("Tech_ID").ToString) Then
cmbTechnician.SelectedValue = varSQLReader("Tech_ID").ToString
End If
Is there a specific way in VB to make this work without it being overly complicated? The other work around would to make a more complicated SQL query but I rather not do that if there's a simpler way.

Since you are using a BindingSource on a Dictionary, you should use the DataSource to determine of things exist. If you tried to add to cmbTimer.Items or delete from it directly, you'd get an error telling you to use the DataSource. So do the same for checking if something exists (dont use a dictionary with local scope):
' form or class level collection
Private cboSource As New Dictionary(Of String, String)
cboSource.Add("red", "red")
cboSource.Add("blue", "blue")
cboSource.Add("green", "green")
cbo.DataSource = New BindingSource(cboSource, Nothing)
cbo.DisplayMember = "Value"
cbo.ValueMember = "Key"
If cbo.Items.Contains("red") Then
Console.Beep() ' wont hit
End If
If cboSource.ContainsValue("red") Then
Console.Beep() ' hits!
End If
The suggestion in comments suggests casting the DataSource of the BindingSource back to dictionary:
Dim tempBS As BindingSource = CType(cbo.DataSource, BindingSource)
Dim newCol As Dictionary(Of String, String) = CType(tempBS.DataSource, Dictionary(Of String, String))
If newCol.ContainsValue("red") Then
Console.Beep() ' hits!
End If
It is easier and more direct to retain a reference to the dictionary, but recreating it will work.

Here are another way =>
If cmbTechnician.FindString(varSQLReader("Tech_ID").ToString) <= -1 Then
'Do Not Exist
End If
This will find the display member and will return an index of the row if there exist value, otherwise will return -1.
More Info are here=>ComboBox.FindString

Related

Checking 3rd character with Char.IsNumber in vb.net

I am searching a database and pulling results from it. Before displaying it in WPF I am checking the contents of the text in a field called PrimarySponsor which can be (1) blank/null (2) number at 3rd character (3) persons name. I am currently using Char.IsNumber to check option 2 if there is a number at position 4.
If reader("PrimarySponsor") Is DBNull.Value Then
resultxPrimSpon = ""
ElseIf Char.IsNumber(reader("PrimarySponsor"), 3) Then
resultxPrimSpon = "Terminated"
Else
resultxPrimSpon = reader("PrimarySponsor")
End If
Before I put the Char.IsNumber check in I was getting 4 results displaying. When i add the Char.IsNumber code I only get 2 results along with the error;
Error while connecting to SQLServer.Sepcified argument was out of the range of valid values. Parameter name: index.
Anyone have any ideas about why this is happening or how to work around it?
It's not clear where you get that error because Char.IsNumber is not a sql-server method. So i assume that it's a custom message from you. However, the "Specified argument was out of the range" is documented:
ArgumentOutOfRangeException: index is less than zero or greater than
the last position in s.
So it seems that at least one of the strings is shorter than 4 characters. In general you should store the reader values in a variable of the correct type if you have to access it more than once. In this case in a String-variable.
But it's also a good idea to create a custom type that has all properties. Then you can add all to a List(Of T). Here's an example:
Public Class Sponsor
Public Property PrimarySponsor As String
End Class
Of course you could also fill a List(Of String) instead of a List(Of Sponsor). Then you don't need to create a new type. But i assume that you have more than one column in the table. Using a custom type increases readability and maintainability much.
....
Dim allSponsors As New List(Of Sponsor)
Using reader = command.ExecuteReader()
If reader.HasRows Then
Dim primSponsorColumnIndex = reader.GetOrdinal("PrimarySponsor")
While reader.Read
Dim sponsor As New Sponsor()
If reader.IsDBNull(primSponsorColumnIndex) Then
sponsor.PrimarySponsor = ""
Else
sponsor.PrimarySponsor = reader.GetString(primSponsorColumnIndex)
If sponsor.PrimarySponsor.Length >= 4 AndAlso _
Char.IsDigit(sponsor.PrimarySponsor(3)) Then
sponsor.PrimarySponsor = "Terminated"
End If
End If
allSponsors.Add(sponsor)
End While
End If
End Using
First use DataReader.IsDBNull to check if the value is Null. Then check if Length >= 4 before you use Char.IsDigit(sponsor.PrimarySponsor(3)) to avoid the ArgumentOutOfRangeException.
Difference between Char.IsDigit() and Char.IsNumber() in C#

How to get specific index of a Dictionary?

I'm working on a code that returns a query result like MySqlCommand, all working well but what I'm trying to do is insert the result inside a ComboBox. The way for achieve this is the following:
Form load event execute the GetAvailableCategories function
The function executed download all the values and insert it into a dictionary
Now the dictionary returned need an iteration for each Items to insert in the ComboBox
Practice example:
1,3. Event that fire the function
Private Sub Service_Load(sender As Object, e As EventArgs) Handles MyBase.Load
For Each categoria In Categories.GetAvailableCategories()
service_category.Items.Add(categoria)
Next
End Sub
GetAvailableCategories function
Dim dic As New Dictionary(Of Integer, String)
For Each row In table.Rows
dic.Add(row(0), row(1))
Next
Return dic
How you can see in the 1,3 points I call the function that return the result. What I want to do is insert the row(0) as value of the item and row(1) as Item name. But Actually I get this result in the ComboBox:
[1, Hair cut]
and also I can't access to a specific position of the current item in the iteration. Maybe the dictionary isn't a good choice for this operation?
Sorry if the question could be stupid, but it's a long time since I don't program in vb.net and now I need to brush up a bit.
UPDATE
I've understood that I can assign the value access to the .key of my dictionary, so the result that I want achieve is correct if I do:
cateogoria.key (return the id of record taken from the db)
categoria.value (is the item name that'll display in the ComboBox)
now the problem's that: How to assign the value of the current item without create any other new class? For example:
service_category.Items.Add(categoria.key, categoria.value)
But I can't do this, any idea?
A List as a DataSource sounds like what you are really after. Relying on relative indices in different arrays is sort of flaky. There is not a lot about what these are, but a class would keep the related info together:
Public Class Service
Public Property Name As String
Public Property Category As String
Public Property Id As Int32
End Class
This will keep the different bits of information together. Use them to store the info read from the db and use a List to store all of them:\
Private Services As New List(of Service)
...
For Each row In table.Rows
Dim s As New Service
s.Name = row(0).ToString() '???
s.Category =...
s.Id = ...
Services.Add(s) ' add this item to list
Next
Finally, bind the List to the CBO:
myCbo.DataSource = Services
myCbo.DisplayMember = "Name" ' what to show in cbo
myCbo.ValueMember = "Id" ' what to use for SelectedValue
I dont really know what you want to show or what the db fields read are, so I am guessing. But the larger point is that a Class will keep the different bits of info together better than an array. The List can be the DataSource so that you dont even have to populate the CBO directly. The List can also be Sorted, searched, Filtered and so forth with linq.
When the user picks something, myCbo.SelectedItem should be that item (though it will need to be cast), or you can use SelectedIndex to find it in the list:
thisOne = Services(myCbo.SelectedIndex)
It is also usually a good idea to override ToString in the item/service class. This will determine what shows when a DisplayMember mapping is not available. Without this, WindowsApp2.Service might show for your items:
Public Overrides ToString() As String
Return String.Format("{0} ({1})", Name, Price)
End Sub
This would show something like
Haircut ($12.30)

Finding distinct lines in large datatables

Currently we have a large DataTable (~152k rows) and are doing a for each over this to find a sub set of distinct entries (~124K rows). This is currently taking about 14 minutes to run which is just far too long.
As we are stuck in .NET 2.0 as our reporting won't work with VS 2008+ I can't use linq, though I don't know if this will be any faster in fairness.
Is there a better way to find the distinct lines (invoice numbers in this case) other than this for each loop?
This is the code:
Public Shared Function SelectDistinctList(ByVal SourceTable As DataTable, _
ByVal FieldName As String) As List(Of String)
Dim list As New List(Of String)
For Each row As DataRow In SourceTable.Rows
Dim value As String = CStr(row(FieldName))
If Not list.Contains(value) Then
list.Add(value)
End If
Next
Return list
End Function
Using a Dictionary rather than a List will be quicker:
Dim seen As New Dictionary(Of String, String)
...
If Not seen.ContainsKey(value) Then
seen.Add(value, "")
End If
When you search a List, you're comparing each entry with value, so by the end of the process you're doing ~124K comparisons for each record. A Dictionary, on the other hand, uses hashing to make the lookups much quicker.
When you want to return the list of unique values, use seen.Keys.
(Note that you'd ideally use a Set type for this, but .NET 2.0 doesn't have one.)

Listbox Control Item - Multiple Names (and/or variables)?

I have a lot of data that's coming from a database and being adding to a listbox. The data that's coming from the database is a unique ID, and a name.
Is there any way I can make the item contain both the ID and name? I know I can append it, that's not what I'm looking to do. I need a way to be able to either get the ID, or get the name, while displaying them both.
I have gotten as far as creating a class:
Class Item
Property ID as Integer
Property Name as String
Sub New(ID as Integer, Name as String)
Me.ID = ID
Me.Name = Name
End Sub
Overrides Function ToString() as String
Return Name
End Function
End Class
That looks like it should do the trick, but I'm having trouble getting the ID, instead of the name. To put it simply, I wish I could do this: listbox1.selecteditem(id) to get the id, or listbox1.selecteditem(name) to get the name.
Any ideas on how to implement this?
You can bind to a List(Of Item) like this:
Dim ItemList = New List(Of Item)
' Fill the list with appropiate values.'
listbox1.DataSource = ItemList
listbox1.DisplayMember = "Name"
listbox1.ValueMember = "ID"
Then listbox1.SelectedValue holds the ID and you can access the name like this:
DirectCast(listbox1.SelectedItem, Item).Name
If you want to show both the ID and the Name, then I suggest you add a property to be the displayed value in the Itemclass:
Public ReadOnly Property DisplayedValue() as String
Get
Return Me.Name & " (" & Me.ID.ToString & ")"
End Get
End Property
Then when binding the list make
listbox1.DisplayMember = "DisplayedValue"
Update:
Based on your comments below I'd say my solution still works. However with this methodology the items must be added to the list and then the list bound to the object. The items can not be added individually and directly to the list box (as you would be separating data from presentation I don't see that as a problem).
To show a message box with the selected item then you just need to do:
MessageBox.Show(DirectCast(listbox1.SelectedItem, Item).ID.ToString))
I think you'll have to write a helper method to do this. If you're using VB 3.5 or newer (part of VS2008 and newer) you can write an extension method so that you can at least get nice syntax. You could write one such that it looked like:
listbox1.SelectByID(123)
listbox1.SelectByName("hello")
In the methods you'd have some search algorithm that went through the items and found the right one.

ComboBox DataBinding DisplayMember and LINQ queries

Update
I decided to iterate through the Data.DataTable and trimmed the values there.
Utilizing SirDemon's post, I have updated the code a little bit:
Sub test(ByVal path As String)
Dim oData As GSDataObject = GetDataObj(path)
EmptyComboBoxes()
Dim oDT As New Data.DataTable
Try
Dim t = From r In oData.GetTable(String.Format("SELECT * FROM {0}gsobj\paths ORDER BY keyid", AddBS(path))) Select r
If t.Count > 0 Then
oDT = t.CopyToDataTable
For Each dr As Data.DataRow In oDT.Rows
dr.Item("key_code") = dr.Item("key_code").ToString.Trim
dr.Item("descript") = dr.Item("descript").ToString.Trim
Next
dataPathComboBox.DataSource = oDT
dataPathComboBox.DisplayMember = "descript"
dataPathComboBox.ValueMember = "key_code"
dataPathComboBox.SelectedIndex = 0
dataPathComboBox.Enabled = True
End If
Catch ex As Exception
End Try
End Sub
This works almost as I need it to, the data is originally from a foxpro table, so the strings it returns are <value> plus (<Field>.maxlength-<value>.length) of trailing whitespace characters. For example, a field with a 12 character length has a value of bob. When I query the database, I get "bob_________", where _ is a space.
I have tried a couple of different things to get rid of the whitespace such as:
dataPathComboBox.DisplayMember.Trim()
dataPathComboBox.DisplayMember = "descript".Trim.
But nothing has worked yet. Other than iterating through the Data.DataTable or creating a custom CopyToDataTable method, is there any way I can trim the values? Perhaps it can be done in-line with the LINQ query?
Here is the code I have so far, I have no problem querying the database and getting the information, but I cannot figure out how to display the proper text in the ComboBox list. I always get System.Data.DataRow :
Try
Dim t = From r In oData.GetTable("SELECT * FROM ../gsobj/paths ORDER BY keyid") _
Select r
dataPathComboBox.DataSource = t.ToList
dataPathComboBox.SelectedIndex = 0
'dataPathComboBox.DisplayMember = t.ToList.First.Item("descript")
dataPathComboBox.Enabled = True
Catch ex As Exception
Stop
End Try
I know that on the DisplayMember line the .First.Item() part is wrong, I just wanted to show what row I am trying to designate as the DisplayMember.
I'm pretty sure your code tries to set an entire DataRow to a property that is simply the name of the Field (in a strongly type class) or a Column (in a DataTable).
dataPathComboBox.DisplayMember = "descript"
Should work if the DataTable contains a retrieved column of that name.
Also, I'd suggest setting your SelectedIndex only AFTER you've done the DataBinding and you know you actually have items, otherwise SelectedIndex = 0 may throw an exception.
EDIT: Trimming the name of the bound column will trim just that, not the actual bound value string. You either have to go through all the items after they've been bound and do something like:
dataPathComboBox.Item[i].Text = dataPathComboBox.Item[i].Text.Trim()
For each one of the items. Not sure what ComboBox control you're using, so the item collection name might be something else.
Another solution is doing that for each item when it is bound if the ComboBox control exposes an onItemDataBound event of some kind.
There are plenty of other ways to do this, depending on what the control itself offers and what you choose to do.
DisplayMember is intended to indicate the name of the property holding the value to be displayed.
In your case, I'm not sure what the syntax will by since you seem to be using a DataSet, but that should be
... DisplayMember="Item['descript']" ...
in Xaml, unless you need to switch that at runtime in which case you can do it in code with
dataPathComboBox.DisplayMember = "Item['descript']"
Again, not 100% sure on the syntax. If you are using a strongly typed DataSet it's even easier since you should have a "descript" property on your row, but given hat your error indicates "System.DataRow" and not a custom type, I guess you are not.
Because I can't figure out the underlying type of the datasource you are using I suggest you to change commented string to
dataPathComboBox.DisplayMember = t.ElementType.GetProperties.GetValue(0).Name
and try to determine correct index (initially it is zero) in practice.