Select all items from Listbox, add them all only once to a Listview through BackgroundWorker - vb.net

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

Related

search all columns of listview

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

Delete line of structure array and update it in tab vb.net

So I have a listbox, filled with informations from a structure tab as follows :
Private Sub Modifier_Load(sender As Object, e As EventArgs) Handles MyBase.Load
For i As Integer = 0 To frmConnecter.TabPolyFlix.Length - 1
ListBox1.Items.Add(frmConnecter.TabPolyFlix(i).strTitre)
Next
End Sub
And I want the user to choose to delete the TabPolyFlix(ListBox1.SelectedIndex) and it has to get updated in the original Tab and thus in the Listbox
P.S I tried this but it only updates it in the listbox, not in the original tab
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
Dim Temp As New List(Of ListViewItem)
For i As Integer = 0 To ListBox1.Items.Count - 1
If ListBox1.SelectedIndices.Contains(i) = False Then
Temp.Add(ListBox1.Items(i))
End If
Next
ListBox1.Items.Clear()
For i As Integer = 0 To Temp.Count - 1
ListBox1.Items.Add(Temp(i))
Next i
End Sub
Arrays have a fixed size. Better use a List(Of ListViewItem) for TabPolyFlix. Then assign the List directly to the DataSource of the listbox:
'Fill the listbox
ListBox1.DisplayMember = "strTitre" 'You can also set this in the property grid.
ListBox1.DataSource = frmConnecter.TabPolyFlix
You can also override the ToString method of ListViewItem in order to display anything you want in the listbox and keep ListBox1.DisplayMember empty.
Now you can directly delete the item in the list:
frmConnecter.TabPolyFlix.Remove(ListBox1.SelectedItem)
or
frmConnecter.TabPolyFlix.RemoveAt(ListBox1.SelectedIndex)
or if you want to delete several items at once
For Each i As Integer In ListBox1.SelectedIndices.Cast(Of Integer)()
frmConnecter.TabPolyFlix.Remove(ListBox1.Items(i))
Next
and re-display the list:
ListBox1.DataSource = Nothing
ListBox1.DataSource = frmConnecter.TabPolyFlix
The moral of the story is: don't use controls (the ListBox in this case) as your primary data structure. Use it only for display and user interaction. Perform the logic (the so called business logic) on display-independent data structures and objects (so called business objects) and when finished, display the result.

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

horizontal scrollbar not showing in listbox

I have a usercontrol that contains a listbox (listbox1).
The horizontalscrollbar setting is set to TRUE.
There is also an handler on another listbox (selectionchanged) that sets the values for the listbox1 (in case this might cause the problem).
I add this usercontrol to a tabpage in a tabcontrol.
The problem I am facing is that the horizontalscrollbar is not shown even if the items displayed in listbox1 are bigger than the width.
Anyone have an idea how I can fix this?
Thanks
-EDIT-
as requested as much code as I can show
dim tabpage as new Tabpage
dim dict as new dictionary(of String, list(of MyObject))
'fill dict
tabpage.add(usercontrol(dict))
tabcontrol1.tabpages.add(tabpage)
usercontrol:
class UserControl
public sub new(dict)
Dim bs As BindingSource = New BindingSource(dict, Nothing)
ListBox1.DataSource = bs
ListBox1.DisplayMember = "Key"
end sub
Private Sub ListBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ListBox1.SelectedIndexChanged
ListBox2.DataSource = New BindingSource(CType(ListBox1.SelectedItem, KeyValuePair(Of String, List(Of MyObject))).Value, Nothing)
End Sub
End Class
I have figured out why it didnt work. Was just a small setting I overlooked.
Am posting this here as it might help other people.
If the listbox has multiColumn set to True then the horizontal scrollbar will not be displayed as you(I) would like to.
Turning off the multiColumn (when not needed) or fixing the column width will fix this issue.
Topic can be closed.

How can I iterate over a listview in a background thread?

I've got an application with a ListView and I want to loop through each item in the ListView. But I want to do this in a separate thread.
This is a very simple version of the code - but its error is the same:
Private Sub StartToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles StartToolStripMenuItem.Click
pingThread = New Thread(AddressOf loopingRoutine)
pingThread.Start()
End Sub
Public Sub loopingRoutine()
For Each item As ListViewItem In ListView1.Items
MsgBox(item.Text)
Next
End Sub
That causes the following error:
Cross-thread operation not valid: Control 'ListView1' accessed from a thread other than the thread it was created on.
Why is this the case? I've never had this problem before when using DataGridViews.
Can anyone shed some light on it?
You cannot access WinForms items from a background thread. They are affinitized to the UI thread. If you've been able to do this in the past with a different type then you were unlucky that it worked.
Whenever you want to work with a specific controls you need to Invoke back to the UI thread to access it's members. Doing an action such as looping on the members is just not possible on the background thread.
There is a way! Hopefully I can help someone else looking through the net trying to solve this!
You need to call the items and store them as a list using a delegate thread. and then use that... if that makes sense!
Like so:
Private Sub StartToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles StartToolStripMenuItem.Click
pingThread = New Thread(AddressOf loopingRoutine)
pingThread.Start()
End Sub
Public Sub loopingRoutine()
Dim myList = DirectCast(Invoke(New GetListViewDelegate(AddressOf GetListViewItems)), List(Of String))
For Each item As String In myList
Dim array() As String = item.Split(";")
MsgBox(array(0) & vbCrLf & array(1))
Next
End Sub
Private Delegate Function GetListViewDelegate() As List(Of String)
Public Function GetListViewItems() As List(Of String)
Return the list back to the delegate
Dim pathList = New List(Of String)
For Each item As ListViewItem In ListView1.Items
pathList.Add(item.Text & ";" & item.Index)
Next
Return pathList
End Function
It may not be the best way to do it - but for a quick and dirty program, it should suffice :)