Filtering binding source - vb.net

This should be easy but I am having a headbanging of a time trying to get this to work! I have done a search and tried all most EVERY SINGLE ONE. Nothing works. I have a datagrid with a binding source. A user will type text into a textbox and the grid is SUPPOSED to change to only show records that contain what user typed in the name. Simple right? NOPE! Not for me! What am I doing wrong? Code below.
Private Sub SearchButton_Click(sender As Object, e As EventArgs) Handles SearchButton.Click
Dim Found As Boolean = False
Dim StringToSearch As String = ""
Dim ValueToSearchFor As String = "%" & SearchTextBox.Text.Trim.ToLower & "%"
Dim CurrentRowIndex As Integer = 0
Try
If ReferencesGrid.Rows.Count = 0 Then
CurrentRowIndex = 0
Else
CurrentRowIndex = ReferencesGrid.CurrentRow.Index + 1
End If
If CurrentRowIndex > ReferencesGrid.Rows.Count Then
CurrentRowIndex = ReferencesGrid.Rows.Count - 1
End If
If ReferencesGrid.Rows.Count > 0 Then
For Each gRow As DataGridViewRow In ReferencesGrid.Rows
StringToSearch = gRow.Cells(1).Value.ToString.Trim.ToLower
If InStr(1, StringToSearch, LCase(Trim(SearchTextBox.Text)), vbTextCompare) Then
TrainingItemBindingSource.Filter = String.Format("Name LIKE '{0}'", ValueToSearchFor)
Exit For
End If
Next
End If
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub

You should get rid of pretty much all that code. If you want to filter the data then just filter the data. There's no conditional statements required and loops required. Just set the Filter property and any records that don't match the filter will be hidden:
Private Sub SearchButton_Click(sender As Object, e As EventArgs) Handles SearchButton.Click
TrainingItemBindingSource.Filter = $"Name LIKE '%{SearchTextBox.Text.Trim()}%'"
End Sub
As you can see, it is simple. I've no real idea what you were actually trying to achieve with the rest of that code. That will exclude any records where the Name column does not contain the search text.
Note that there is no need to try to force case-insensitivity by using ToLower or the like. Just like in real SQL, comparisons done this way in a DataTable are case-insensitive by default. You have to explicitly set the CaseSensitive property of the DataTable or its parent DataSet to True to make such comparisons case-sensitive.
I should also point out that the ability to filter is predicated on the data source implementing certain interfaces. If the data source is a DataTable then you have those interfaces for free. If you have actually bound to something else, e.g. a List(Of T), then you won't be able to filter this way because the required members do not exist.

Related

Pasting new records to a Bound DataGridView

Apologies if this has already been asked. If so, I am unable to find a simple solution. I am trying to allow a user to copy/paste multiple records in a DataGridView (the in memory copy of the data, to be saved later when the user clicks the save button) and cannot find anything that works. It probably is because there is something I do not understand about all of this.
I set up a standard edit form with Visual Studio's drag/table into a form, so it's using a BindingSource control and all the other controls that come with doing that. It works just fine when manually entering something in the new row one by one, so it seems to be set up correctly, but when it comes to adding a record (or multiples) using code, nothing seems to work.
I tried a few things as outline in the code below. Could someone please at least steer me in the right direction? It cannot be that difficult to paste multiple records.
I run this when the user presses Control-V (the clipboard correctly holds the delimited strings):
Private Sub PasteClipboard()
If Clipboard.ContainsText Then
Dim sLines() As String = Clipboard.GetText.Split(vbCrLf)
For Each sLine As String In sLines
Dim Items() As String = sLine.Split(vbTab)
Dim drv As DataRowView = AdjustmentsBindingSource.AddNew()
drv.Item(1) = Items(0)
drv.Item(2) = Items(1)
drv.Item(3) = Items(2)
drv.Item(4) = Items(3)
'Error on next line : Cannot add external objects to this list.
AdjustmentsBindingSource.Add(drv)
Next
End If
End Sub
EDIT
(the bindingsource is bound to a dataadapter, which is bound to a table in an mdb file, if that helps understand)
I adjusted the inner part of the code to this:
If (RowHasData(Items)) Then
Dim drv As DataRowView = AdjustmentsBindingSource.AddNew()
drv.Item("FontName") = Items(0)
drv.Item("FontSize") = Items(1)
drv.Item("LetterCombo") = Items(2)
drv.Item("Adjustment") = Items(3)
drv.Item("HorV") = Items(4)
End If
It kinda works, but it also adds a blank row before the 2 new rows. Not sure where that is coming from, as I have even included your RowHasData() routine...
I would think that “attemp3” SHOULD work, however, it is unclear “what” the AdjustmentsBindingSource’s DataSource is. Is it a List<T> or DataTable?
If I set the BinngSource.DataSource to a DataTable, then attempt 3 appears to work. Below is an example that worked.
Private Sub PasteClipboard2()
If Clipboard.ContainsText Then
Dim sLines() As String = Clipboard.GetText.Split(vbCrLf)
For Each sLine As String In sLines
Dim Items() As String = sLine.Split(vbTab)
If (RowHasData(Items)) Then
Dim drv As DataRowView = AdjustmentsBindingSource.AddNew()
drv.Item("FontName") = Items(0)
drv.Item("FontSize") = Items(1)
drv.Item("LetterCombo") = Items(2)
drv.Item("Adjustment") = Items(3)
drv.Item("HorV") = Items(4)
End If
Next
End If
End Sub
This appears to work in my tests. I added a small function (RowHasData) to avoid malformed strings causing problems. It simply checks the size (at least 5 items) and also checks to make sure a row actually has “some” data. If a row is just empty strings, then it is ignored.
Private Function RowHasData(items As String())
If (items.Count >= 5) Then
For Each item In items
If (item <> "") Then Return True
Next
End If
Return False
End Function
I am guessing it would be just as easy to add the new rows “directly” to the BindingSource’s DataSource. In the example below, the code is adding the row “directly” to the DataTable that is used as a DataSource to the BindingSource. I am confident you could do the same thing with a List<T> by simply adding a new object to the list. Below is a complete example using a BindingSource and a DataTable. This simply adds the rows to the bottom of the table.
Dim gridTable1 As DataTable
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
PasteClipboard()
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
gridTable1 = GetTable()
FillTable(gridTable1)
AdjustmentsBindingSource.DataSource = gridTable1
AdjustmentsDataGridView.DataSource = AdjustmentsBindingSource
End Sub
Private Function GetTable() As DataTable
Dim dt = New DataTable()
dt.Columns.Add("FontName", GetType(String))
dt.Columns.Add("FontSize", GetType(String))
dt.Columns.Add("LetterCombo", GetType(String))
dt.Columns.Add("Adjustment", GetType(String))
dt.Columns.Add("HorV", GetType(String))
Return dt
End Function
Private Sub FillTable(dt As DataTable)
For index = 1 To 10
dt.Rows.Add("Name_" + index.ToString(), "Size_" + index.ToString(), "Combo_" + index.ToString(), "Adjust_" + index.ToString(), "HorV_" + index.ToString())
Next
End Sub
Private Sub PasteClipboard()
If Clipboard.ContainsText Then
Dim sLines() As String = Clipboard.GetText.Split(vbCrLf)
Try
Dim dataRow As DataRow
For Each sLine As String In sLines
Dim Items() As String = sLine.Split(vbTab)
If (RowHasData(Items)) Then
dataRow = gridTable1.NewRow()
dataRow("FontName") = Items(0)
dataRow("FontSize") = Items(1)
dataRow("LetterCombo") = Items(2)
dataRow("Adjustment") = Items(3)
dataRow("HorV") = Items(4)
gridTable1.Rows.Add(dataRow)
End If
Next
Catch ex As Exception
MessageBox.Show("Error: " + ex.Message)
End Try
End If
End Sub
Private Function RowHasData(items As String())
If (items.Count >= 5) Then
For Each item In items
If (item <> "") Then Return True
Next
End If
Return False
End Function
Hope the code helps…
Last but important, I am only guessing that you may have not “TESTED” the different ways users can “SELECT” data and “how” other applications “copy” that selected data. My previous tests using the WIN-OS “Clipboard” can sometimes give unexpected results. Example, if the user selects multiple items using the ”Ctrl” key to “ADD” to the selection, extra rows appeared in the Clipboard if the selection was not contiguous. My important point is that using the OS clipboard is quirky IMHO. I recommend LOTS of testing on the “different” ways the user can select the data. If this is not an issue then the code above should work.

user assigning non-repeating values in a numerical list

I'm creating an output ordering function. The user needs the ability to change the order frequently on the fly (it's used to order pick lists for our order pickers). I have created a Windows Form, but i need to do some validation on it. The validation itself is simple... the numbers have to be in order and can't repeat. If they repeat, the SQL report bombs out... I'd also like to validate not skipping any values, but that's not really necessary.
is there a better way than:
if NumericUpDown1.value = NumericUpDown2.value then
error goes here
end if
if NumericUpDown1.value = NumericUpDown3.value then
error goes here
end if
if NumericUpDown1.value = NumericUpDown4.value then
error goes here
end if ...
there is a large list, and this would be thousands of lines of code. I know there has got to be a simple solution. It's not coming to me, and i've been stuck on it for a couple of days. (yea I know i probably could have just done it already)
Just like Serg said, put the values into an array and use Linq to group by the values. Then find any duplicates.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim numericList = New Decimal() {
NumericUpDown1.Value,
NumericUpDown2.Value,
NumericUpDown3.Value,
NumericUpDown4.Value,
NumericUpDown5.Value
}
Dim duplicatesExist = numericList _
.GroupBy(Function(n) n) _
.Any(Function(g) g.Count() > 1)
If duplicatesExist Then
MessageBox.Show("Duplicates exist")
Else
MessageBox.Show("No Duplicates")
End If
End Sub

I need to know how to avoid the System.Data.DataRowview in vb 2010

I have an issue with my code; and would appreciate some assistance getting her to do what I want without an annoying error. Read the databound listbox strings, find the user selected string and write said string to a file.
Private Sub btnContinue_Click(sender As System.Object, e
As System.EventArgs) Handles btnContinue.Click
' 1st file and mySQL Update to testing table
If txtLocation.Text.ToString <> "" And txtUnitTested.Text.ToString <> "" Then
Dim filewriter As New System.IO.StreamWriter(
"C:\Users\OER\Documents\Visual Studio 2010\Projects\frmTest1_0.txt")
Dim curItem = lstCustomer.SelectedItem
Dim now As DateTime = DateTime.Now
With filewriter
.WriteLine(vbCrLf + (now.ToString("F"))) ' Write the DateTime to the file
End With
For Each objDataRowView As DataRowView In lstCustomer.SelectedItems
Dim item As String
For Each item As DataRowView In Me.lstCustomer.Items.Item("customer")
item = String.Parse(lstCustomer.SelectedValue)
WriteLine(curItem(item))
Next item
Next ' THIS IS THE FOR LOOP I AM HAVING ISSUES WITH!!!!!!!
With filewriter
.WriteLine(vbCrLf + txtLocation.Text + vbCrLf + txtUnitTested.Text)
End With
filewriter.Close()
frmTest1.Show()
Else
MsgBox("Please Type the Location and Unit Tested!!!", vbCritical)
txtLocation.Clear()
txtUnitTested.Clear()
txtLocation.Focus()
End If
These loops:
For Each objDataRowView As DataRowView In lstCustomer.SelectedItems
Dim item As String
For Each item As DataRowView In Me.lstCustomer.Items.Item("customer")
item = String.Parse(lstCustomer.SelectedValue)
WriteLine(curItem(item))
Next item
Next
Are going to cause you some problems. You have declared a loop variable with the same name as another variable. And then you are assigning a value to it.
Also, I am not very clear on what the purpose of the two loops are. I am somewhat confused about what you are trying to accomplish in this section of code. I would start by renaming the variable in this line:
Dim item as String
I am going to use this for an example:
Dim result as string
Make that something else, and you cannot then change the value of item (since that is the loop variable) in the loop. That will cause an error.
Changing the code inside the loop to:
result = String.Parse(lstCustomer.SelectedValue)
Odds are this still won't get you to where you are trying to go, but it is a good start. Unless each item in your lstCustomer contains multiple items to iterate through I think you probably only need that first, outer loop, anyway.
If your loop starts like this:
For Each objDataRowView As DataRowView In lstCustomer.SelectedItems
Then I think you will want:
objDataRowView.Item("customer")
as what is output. I think that will be the value of the customer field from your table.
You are basically saying that this row has a column called customer.... I would like that value please.

VB.NET Iterating Form Labels

I have several label boxes on my design form that all share the naming convention lbl_#.text where # ranges from 1 to 60. I want to make a loop that iterates through each lbl_#.text adding some incremental value, let's say multiples of 2 for this question's theoretical purpose.
Something such that the end result would amount to the following:
lbl_1.text = "2"
lbl_2.text = "4"
lbl_3.text = "6"
...
lbl_60.text = "120"
I'm not sure how to access each of these labels through the coding side, I only know how to explicitly mention each label and assign a value :/
There are a few options here.
In this situation the labels will often have a common container, such as panel or groupbox control. In that case:
Dim formLabels = myContainerControl.Controls.OfType(Of Label)()
For Each formLabel As Label In formLabels
'...
Next formLabel
Of course, this mixes logical groups with visual groupings. Those two things don't always align well, so you can also...
Add them all to a Label array (or List(Of Label) or any other enumerable):
Dim formLabels(60) As Label = {lbl_1, lbl_2, lbl_3 .... }
For Each formLabel As Label in formLabels
'...
Next formLabel
But sometimes that's more trouble than it's worth, even if you use a loop to create the collection, and so you can also
Use the .Name property (in conjunction with a naming convention to identify your desired controls):
Dim formLabels = Controls.Where(Function(c) c.Name.StartsWith("lbl_"))
For Each formLabel As Label In formLabels
'...
Next formLabel
Some combination of the above (for example, code in the form load event to create a list based on the name property).
Notice the actual For Each loop is exactly the same in all of those options. No matter what you do, get to the point where you can write a single expression to identify the label control, and then run a simple loop over the expression result.
This points to a final strategy: think in terms of binding to a data source. With a data source, your labels are created as part of a DataGridView, FlowLayoutPanel, or similar control. Then you can iterate the rows in the grid or panel.
Use the Controls collection:
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim i As Integer
For i = 1 To 3
Dim myLabel As Label = CType(Me.Controls("lbl_" & i), Label)
myLabel.Text = ...whatever value you want to put here
Next
End Sub
End Class
If you don't know how many labels there are, one option is to use a Do Loop.
Dim lblTarget As Label = Nothing
Dim intCursor As Integer = 1
Dim bolFirstIteration As Boolean = True
Do Until lblTarget Is Nothing AndAlso Not bolFirstIteration
If bolFirstIteration Then
bolFirstIteration = False
End If
lblTarget = CType(Me.Controls("lbl_" & intCursor.ToString()), Label)
If Not lblTarget Is Nothing Then
lblTarget.Text = (intCursor * 2).ToString()
End If
intCursor += 1
Loop

VB.NET Combobox - Auto-complete behaviour for numeric values

I have a problem with the auto-complete behaviour of comboboxes in VB.NET (with the .NET framework 2.0).
I am using a combobox to type in numeric values, and its DropDown list to suggest possible numeric values. This list is sorted in ascending order, for example {"10","92", "9000", "9001"}.
The combobox properties are set as follow:
AutoCompleteMode: SuggestAppend
AutoCompleteSource: ListItems
DropDownStyle: DropDown
Sorted: False
The DropDown list is simply filled like this:
myCombobox.Items.Add("10")
myCombobox.Items.Add("92")
myCombobox.Items.Add("9000")
myCombobox.Items.Add("9001")
When I don't type anything, the order of values of the DropDown list is correct, in original/ascending order. However, when I start typing something, the suggested values in the DropDown list get sorted (alphanumerically): if I type "9", the list of suggestions becomes {"9000", "9001", "92"}.
I would like to prevent this behaviour to get the values of the list in the original/ascending order. I can't figure out how...
A possible work-around would be to pad with zeroes the values in the list, e.g. {"0010", "0092", "9000", "9001"} but I would like to avoid this.
Edit:
As suggested by bendataclear, one can use a list box to display the suggestions.
This will work for small lists but doesn't scale well to large lists. It may be useful for some applications. Based on the code given by bendataclear, I made it work this way:
Private Sub ComboBox1_KeyUp(sender As System.Object, e As System.Windows.Forms.KeyEventArgs) Handles ComboBox1.KeyUp
Dim cursorPos As Integer = ComboBox1.SelectionStart
ListBox1.Items.Clear()
For Each s In ComboBox1.Items
If s.StartsWith(ComboBox1.Text) Then
ListBox1.Items.Add(s)
End If
Next
If ListBox1.Items.Count > 0 And ComboBox1.Text.Length > 0 Then
ComboBox1.Text = ListBox1.Items(0)
ComboBox1.SelectionStart = cursorPos
ComboBox1.SelectionLength = 0
End If
End Sub
The code has not been thoroughly tested and can be improved, but the main idea is there.
Edit 2:
Using DataGridView leads to better performance; it was sufficient for me. Thanks bendataclear.
Just out of curiosity, any other answer is welcomed :)
Seems to be an issue when the combo box displays the data, as even if you set a custom source it re-orders alphabetically:
ComboBox1.Items.Add("10")
ComboBox1.Items.Add("92")
ComboBox1.Items.Add("9000")
ComboBox1.Items.Add("9001")
ComboBox1.AutoCompleteCustomSource.Add("10")
ComboBox1.AutoCompleteCustomSource.Add("92")
ComboBox1.AutoCompleteCustomSource.Add("9000")
ComboBox1.AutoCompleteCustomSource.Add("9001")
ComboBox1.AutoCompleteSource = AutoCompleteSource.CustomSource
I think the only way I can think of is to create your own autocomplete something like (untested):
Dim cbotxt As String = ComboBox1.Text
Dim key As String
key = ChrW(e.KeyCode)
ListBox1.Items.Clear()
For Each i In ComboBox1.Items
Dim s As String = i.ToString()
If s.StartsWith(ComboBox1.Text & key) Then
ListBox1.Items.Add(s)
End If
Next
If ListBox1.Items.Count > 0 Then
ListBox1.Visible = True
ComboBox1.Text = ListBox1.Items(0)
End If
Edit:
A good approach for many items (I'm using for 10000+ in an application):
First change from a list box to a datagridview.
Then declare a list of strings and fill with values you want to autocomplete
Dim Numberlist as List<Of String>
' Fill List using Numberlist.Add("String")
Then in the text change property:
Filter = NumberList.FindAll(AddressOf checkNum)
DataGridView1.DataSource = Filter
And add the function to check the strings.
Function checkNum(ByVal b As String) As Boolean
If b.StartsWith(ComboBox1.Text) Then
Return True
Else
Return False
End If
End Function
This method runs on my machine with 10k items faster than I can type.