search all columns of listview - vb.net

Good day.
Could someone tell me how I search through all the columns of a listview?
At the moment I have a code that only searches the first column (with index 0).
Dim ItemsList As New List(Of ListViewItem)
After filling the listview:
ItemsList.AddRange(ListView1.Items.Cast(Of ListViewItem))
Well, the selection (search) of values ​​by the text field itself:
Private Sub TxtSearchLog_TextChanged(sender As Object, e As EventArgs)
Dim showitems As New List(Of ListViewItem)
For Each i As ListViewItem In ItemsList
If i.Text.ToLower.Contains(TxtSearchLog.Text.ToLower.Trim) Then
showitems.Add(i)
End If
Next
ListView1.Items.Clear()
ListView1.Items.AddRange(showitems.ToArray)
End Sub

Private allItems As New List(Of ListViewItem)
Private Sub TextBox1_TextChanged(sender As Object, e As EventArgs) Handles TextBox1.TextChanged
Timer1.Stop()
Timer1.Start()
End Sub
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
Timer1.Stop()
Dim searchText = TextBox1.Text.Trim()
Dim items = allItems.Where(Function(lvi) lvi.SubItems.Cast(Of ListViewItem.ListViewSubItem).Any(Function(lvsi) lvsi.Text.IndexOf(searchText, StringComparison.CurrentCultureIgnoreCase) <> -1)).ToArray()
ListView1.Items.Clear()
ListView1.Items.AddRange(items)
End Sub
Firstly, you should not filter on the TextChanged event itself. If the user wants to search for a string of ten characters, your code would filter ten times when all but the last were useless. Instead, you should use a Timer with a fairly short Interval that you start/restart each time the Text changes and then only filter when the Timer Ticks. That way, you won't be filtering multiple times while the user is typing. You can experiment to get the best Interval value so that the user doesn't have to wait too long for the filter to happen but also doesn't have to type really quickly to avoid spurious filters.
As for the actual filtering, this code uses LINQ to do that. The Where function is what does the filtering, just like a WHERE clause in SQL. In this case, the result includes only those items that match the condition specified in the Where call. That condition is that the Text of any subitem contains the specified search text. Note that I have actually used a proper case-insensitive comparison, rather than using the hack of changing the case of the text. That hack will generally work and, I think, will always work in English but there are some cases in some languages where it won't work, so it is generally discouraged.
Unfortunately, the .NET Framework version of String.Contains cannot do case-insensitive comparisons, so you have to use IndexOf instead. If you're targeting .NET Core (including .NET 5.0 or later) then you do have an overload of Contains that will take a StringComparison argument.
For the record, if you wanted to modify your existing code as little as possible then you could just change this:
If i.Text.ToLower.Contains(TxtSearchLog.Text.ToLower.Trim) Then
to this:
If i.SubItems.Cast(Of ListViewItem.ListViewSubItem).Any(Function(lvsi) lvsi.Text.ToLower.Contains(TxtSearchLog.Text.ToLower.Trim)) Then

I have looped through all the items and subitems searching for the text. If the text is found in a ListViewItem, it is added to the list and the inner For is exited.
Private Function Search(SearchText As String) As List(Of ListViewItem)
Dim FoundItems As New List(Of ListViewItem)
For Each item As ListViewItem In lvCoffee.Items
For Each si As ListViewItem.ListViewSubItem In item.SubItems
If SearchText = si.Text Then
FoundItems.Add(item)
Exit For
End If
Next
Next
Return FoundItems
End Function

Related

Select all items from Listbox, add them all only once to a Listview through BackgroundWorker

I am having some problems lately with selecting ALL items(ONLY once!) from a listbox and adding them to a listview. I am using a backgroundworker to handle this task due to big content the listview will contain and to avoid GUI freezing while performing this task.
Ok, so here is the BackgroundWorker_ProgressChanged code :
For Each item In ListBox3.SelectedItems
listView1.Items.Add(ListBox3.SelectedItem, ImageList1.Images.Count - 1).SubItems.Add("Test")
ListView1.Items("Test").SubItems.Add("")
Next
For Each item As ListViewItem In ListView1.SelectedItems
Next
End Sub
The above written code displays items in listview, but ONLY if the user selects a certain item from the Listbox3 and displays infinite times the selected items from the listbox, I want it display ONLY ONCE all selected items from the Listbox, in the Listview. I want to select ALL items automatically, without user intervention, I have tried several methods which have failed.
Can someone please provide a solution to this issue ? Thanks.
I just tested and it seems that getting items from a ListBox on a secondary thread is not an issue, so I was wrong about that. Adding/setting items definitely would be though, so you'd need to add the items to the ListView on the UI thread. Here's some example code that just worked for me:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim outputItems As New List(Of ListViewItem)
For Each inputItem As String In ListBox1.Items
outputItems.Add(New ListViewItem(inputItem))
Next
e.Result = outputItems
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
Dim items = DirectCast(e.Result, List(Of ListViewItem))
ListView1.Items.AddRange(items.ToArray())
End Sub
The problem is all the screen redrawing. I am guessing you may not need a background worker if you limit the screen redraw.
I used a list for the ListViewItems since I don't know how many there will be. Had to convert the list to an array to use .AddRange.
As a matter of fact just the .BeginUpdate and .EndUpdate might speed things up enough to be acceptable.
I am not sure about the use of ImageList so you might have to play around with that.
Private Sub OpCode()
Dim items = From itm In ListBox1.Items 'or ListBox1.SelectedItems
Select CStr(itm)
Dim lvItems As New List(Of ListViewItem)
Dim imageIndex As Integer = ImageList1.Images.Count - 1
For Each item In items
Dim lvItem As New ListViewItem(item, imageIndex)
lvItems.Add(lvItem)
Next
'ListView1.BeginUpdate()
ListView1.Items.AddRange(lvItems.ToArray)
'ListView1.EndUpdate()
End Sub

Most efficient way to refer to multiple controls. IE: the multi-WITH

I have had a little look around and cannot seem to find an easy way to refer to multiple controls in stuff like an IF statement, or to set a property to multiple controls etc. (IF exists = true!)
The 'shortcut'if you like; would best be described in illegal code such as:
Public Sub BreakCompiler()
if string.IsNullOrEmpty(Textbox1.text, textbox2.text, textbox3.text) Then .....
'As opposed to
If String.IsNullOrEmpty(PartNumTextBox.Text) Or _
String.IsNullOrEmpty(PartNameTextBox.Text) Or _
String.IsNullOrEmpty(PartGRNTextBox.Text) Or_
String.IsNullOrEmpty(SerialNumTextBox.Text) Then
'Warn user
Else
'do nofin.
End If
Or even more outlandish:
WITH Textbox1.text, textbox2.text, textbox3.text
.ReadOnly = true
END WITH
End sub
The idea is to prevent having to run 3 if statements, or whatever, that basicaly do the same thing to 3 different objects...etc.. etc. Similar to a handler for multiple events that can be separated by a ','.
Im aware of looping through controls (IE for each control in groupbox.controls for eg) but that wouldn't quite achieve what I'm after here. (Say you wanted to skip a couple?)
Just thought id check the collective wisdom.
As far as I know, there isn't anything similar to a handler for control properties in if statements.
You either have to do them separately (or add them all into an array), or, as you mentioned, go through a subset within another control.
However, one thing you can do (say you only wanted to change a certain type of control), is this:
For Each tb As TextBox In Me.Controls.OfType(Of TextBox)
'do stuff here
Next
Another option is to create a Sub that will make the changes for you, and then pass each into the Sub.
Private Sub changeTextBox(tBox as TextBox)
'make changes here
End Sub
You want to process a variable length collection of some type of object. One way to do this is to write helper methods that have an argument decorated with ParamArray keyword.
Public Shared Function AnyIsNullOrEmpty(ParamArray controls As Control()) As Boolean
Dim ret As Boolean
For i As Int32 = 0 to controls.GetUpperBound(0)
ret = String.IsNullOrEmpty(controls(i).Text)
If ret then Exit for
Next
Return ret
End Function
Public Shared Sub SetTBReadOnlyProperty(value As Boolean,ParamArray textboxes As TextBox())
For i As Int32 = 0 to textboxes.GetUpperBound(0)
textboxes(i).ReadOnly=value
Next
End Sub
Possible usage:
Private Sub DemoUsage
If AnyIsNullOrEmpty(TextBox1,TextBox3,TextBox4) then
' at least one is empty
Else
' all have value
End If
SetTBReadOnlyProperty(True,TextBox1,TextBox3,TextBox4)
End Sub
The newer versions of .NET allow you to use the .ForEach Linq extension on a List object. If you build a e.g. a List(Of TextBox) you can use .ForEach with an anonymous method to quickly iterate the controls in the List and manipulate properties and so forth. It's still a loop - but in a much more compact form. You can be selective about which controls are in a List, and have multiple Lists etc.
Here's an example:
Public Class Form1
Private _BoxList As New List(Of TextBox)
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
_BoxList.ForEach(Sub(tb As TextBox) tb.Enabled = Not (tb.Text = String.Empty))
End Sub
Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
_BoxList.Add(Me.TextBox1)
_BoxList.Add(Me.TextBox2)
_BoxList.Add(Me.TextBox3)
End Sub
End Class
That's just 3 TextBoxs and a Button on a vanilla form:
For the particular example I was working on when I thought "There must be a better way" Something like this works perfectly.
It might be easier if trying to do multiple non type specific operations on different things. (Thanks #Sastreen and #the_lotus)
Private Sub Validate_PartDetails() Handles PartNumTextBox.TextChanged, PartNameTextBox.TextChanged, PartGRNTextBox.TextChanged, SerialNumTextBox.TextChanged
For Each tb As TextBox In BasicDetailsCustomGroupBox.Controls.OfType(Of TextBox)
If tb.Tag = "notnull" Then
If String.IsNullOrEmpty(tb.Text) Then
tb.BackColor = Color.MistyRose
Else
tb.BackColor = Control.DefaultBackColor
End If
End If
If tb.Tag = "notnumeric" Then
If not(isnumeric(tb.Text)) Then
tb.BackColor = Color.MistyRose
Else
tb.BackColor = Control.DefaultBackColor
End If
End If
Next
End Sub
Thanks guys.

Checkboxlist applying If statements

I am stating VB and since it is so close to VBScript I have been having fun with it. But now I have come across the "Checkboxlist".
My boss saw me making a Windows Forms Application and asked me to make him a interface (GUI) for one of his batch files. In the batch you start by choosing between lines 1 through 10 and it does the rest. So I made a Checkboxlist and made check-boxes going from 1 to 10. Now I am not sure how to tell it that when I click a button a if statement looks at what has been checked and take to appropriate action.
I think i am suppose to start with something like
If CheckedListBox1.Items() = True then
But i know this does not work.
Anything thing will help.
Thank you.
It sounds like you're looking for the ItemCheck event. This event is fired when the checked state of an item changes.
Private Sub HandleCheckedListBox1ItemCheck(sender As Object, e As ItemCheckEventArgs) Handles CheckedListBox1.ItemCheck
Dim item As Object = Me.CheckedListBox1.Items.Item(e.Index)
Dim text As String = Me.CheckedListBox1.GetItemText(item)
Select Case e.CurrentValue
Case CheckState.Unchecked
'...
Case CheckState.Checked
'...
Case CheckState.Indeterminate
'...
End Select
End Sub
Or iterate all checked items:
Private Sub HandleButton1Click(sender As Object, e As EventArgs) Handles Button1.Click
For Each item As Object In Me.CheckedListBox1.CheckedItems
Dim text As String = Me.CheckedListBox1.GetItemText(item)
'...
Next
End Sub

Eliminate user input of single quote for every field in every form in a project

using VB.net and Visual studio is there a way to globally (across all fields in all forms in a project) prevent a user from entering a single quote character into a field. I'm thinking some sort of modification of the keypress event, but I'm not sure how to go about it. Currently I am currently using the ADDHANDLER, ADDRESSOF technique to assign this special code to a particular field, and I guess I could do a loop over all my contained controls on a particular form and assign the keypress code, but if there is a way to do it once without repeating the code on each form that would be great. Any hints graciously accepted. Thanks.
What about a regex? Stripping or converting to escape character on submit?
Would it be possible to pass an array where TextBox1 is TextBox1,Textbox2, etc. Obviously you would need to edit the expression if you only need to strip the single quote. This for all non alphabet characters. I don't do much with vb.net but thought this might be helpful.
Private Sub TextBox1_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TextBox1.TextChanged
StripNonAlphabetCharacters(TextBox1)
End Sub
Public Sub StripNonAlphabetCharacters(ByVal input As TextBox)
' pattern matches any character that is NOT A-Z (allows upper and lower case alphabets)
Dim rx As New Regex("[^a-zA-Z]")
If (rx.IsMatch(input.Text)) Then
Dim startPosition As Integer = input.SelectionStart - 1
input.Text = rx.Replace(input.Text, "")
input.SelectionStart = startPosition
End If
End Sub
The easiest solution is to create a derived TextBox and use that instead of the standard textbox.
Public Class NoQuoteTextBox
Inherits TextBox
Public Sub New()
AddHandler MyBase.KeyPress, AddressOf Handler
End Sub
Public Sub Handler(ByVal sender As Object, ByVal e As KeyPressEventArgs)
If e.KeyChar = "'" Then
e.Handled = True
End If
End Sub
End Class
Build your project then the NoQuoteTextBox (or whatever you decide to call it) will appear in the Toolbox in the Designer:
Use this new control instead of the standard textbox and it will handle the single quote for you.

while clicking down arrow cursor need to come other control

i given code like this:
Private Sub txtemployeename_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles txtemployeename.KeyDown
keyval = e.KeyData
Dim keyData As System.Windows.Forms.Keys = e.KeyData
If keyData = Keys.Down Then
LstEmployee.Visible = True
LstEmployee.Focus()
End If
End Sub
while i am cliking down arrow first time that is not focusing to listbox,second time am clicking down arrow that is focusing..also once cursor come to tha list box,if i clik enter that should be displayed in text box..for that i given code like this..
Private Sub LstEmployee_Enter(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles LstEmployee.Enter
txtemployeename.Text = LstEmployee.SelectedItem
End Sub
but this is not working properly..for loading list box i given code like this:
Private Sub txtemployeename_KeyPress(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles txtemployeename.KeyPress
Dim s As String = txtemployeename.Text
LstEmployee.Visible = True
loadlistbox(LstEmployee, "select Ename from EmployeeMaster_tbl where Ename LIKE'%" & s & "%' ")
End Sub
You should rely on the KeyUp event rather than on the KeyDown one. Also for the ListBox you just need the SelectedIndexChanged event. Additionally, your code has quite a few errors (wrong query (-> you don't need to call your DB every time to order the items in the ListBox), relies on SelectedIndex rather than on SelectedItem...). Here you have an updated version:
Private Sub txtemployeename_KeyUp(sender As Object, e As System.Windows.Forms.KeyEventArgs) Handles txtemployeename.KeyUp
Dim s As String = txtemployeename.Text
LstEmployee.Visible = True
Dim list = LstEmployee.Items.Cast(Of String)()
Dim query = From item As String In list Where item.Length >= s.Length AndAlso item.ToLower().Substring(0, s.Length) = s.ToLower() Select item
If (query.Count > 0) Then
Dim newItems = New List(Of String)()
For Each result In query
newItems.Add(result)
Next
LstEmployee.Items.Clear()
For Each newItem In newItems
LstEmployee.Items.Add(newItem)
Next
End If
End Sub
Private Sub LstEmployee_SelectedIndexChanged(sender As System.Object, e As System.EventArgs) Handles LstEmployee.SelectedIndexChanged
txtemployeename.Text = LstEmployee.SelectedItem
End Sub
The code above checks for occurrences (i.e., if the whole string in the txtemployeename matches (caps do not matter) the starting substring of, at least, one element in LstEmployee) every time a new character is introduced in txtemployeename. The ListBox is updated with these ocurrences. txtemployeename displays the name of the selected item in LstEmployee.
I hope that this will be enough to help you to build the code required to deliver the exact functionalities you are after.
NOTE: bear in mind that this approach (deleting/adding Items) is incompatible with cases where the ListView is populated with a DataSource. If you rely on a DataSource you would have to update this code accordingly.
NOTE2: the proposed approach deals with the elements in the ListView. You have to introduce these elements at the start from whatever source you are using; this code only updates existing information (items in the ListBox). Also bear in mind that this code is expected to be corrected to match your exact requirements; for example: list has to be associated with the total number of items (the ones retrieved from your datasource at the start), not with the current ones (as displayed in the code; it just represents a simplified version of the problem): every time a new population occurs all the items (other than the target ones) are deleted and thus the ListBox does not represent a reliable source. Example to understand this: at the start, you have "aaaa", "bbbb", "cccc"; if you type "a", all the elements except "aaaa" would be deleted. If you type now "b" and consider the actual elements in the ListBox, no change would occur as far as the only element is "aaaa"; you would have to consider all the original elements (which, as suggested via comment, might be stored at the start in an array/list of strings).