Removing items from listbox using backgroundworker - vb.net

I want to delete all the items of a listbox that does NOT contain "mysite" and here's my code that works fine without backgroundworker.
Do Work Event:
Dim relevantSite As Integer = 0
Do Until relevantSite = lstLinks.Items.Count
If lstLinks.Items.Item(relevantSite).ToString.Contains("mysite") Then
relevantSite += 1
Else
bgWorker.ReportProgress(relevantSite)
End If
Loop
ProgressChanged Event:
lstLinks.Items.RemoveAt(CInt(e.ProgressPercentage))
What it does is, it removes alot of items, sometimes all items. I know I'm making some terrible mistake with e and reportProgress thing.
Please explain them to me, I searched various sites but could not understand it...

Rather than directly changing the items in the list you should create a new list in your background worker. This way you can add remove items from the list and return it to the UI once all the processing is completed and rebind the drop down.

I want to delete all the items of a listbox that does NOT contain
"mysite"
Walk the ListBox backwards and delete the offending items as you go. Wrap the process in BeginUpdate() and EndUpdate() so the ListBox only refreshes once when you're all done:
lstLinks.BeginUpdate()
Dim NumItems As Integer = lstLinks.Items.Count - 1
For i As Integer = NumItems To 0 Step -1
If Not lstLinks.Items(i).ToString.Contains("mysite") Then
lstLinks.Items.RemoveAt(i)
End If
Next
lstLinks.EndUpdate()
lstLinks.Refresh()

You are expecting the code to act as if it is synchronized. But multi-threading does not work that way.
Your code in do work will be processing the next record before report progress has completed. In other words the loop will not pause and wait for report progress to complete. This is a problem because when you call out to remove a item from the list, you reuse the index assuming the item is gone. After a few removals, the index passed in will not indicate the correct item. If you were to use an identifier rather than an index it would work. But the whole thing seems wrong to me since you are not doing any heavy lifting in the do work method.

Related

Visual Basic Form doesn't dispose all items when asked to

hope someone can help..
I'm writing a little Windows application with VB.NET forms,
I've created a subroutine which disposes all the items on the form when it is called:
Sub disposer() 'disposes of all the items in the form
For Each i In Me.Controls
i.dispose
Next
End Sub
The above, if I'm correct should dispose of everything in the form, however, it appears to only get rid of some items on the form, for example, only half the textboxes, for example.
What happens: you're iterating over the collection of Controls of a Form (or another class that inherits Control).
Each time you call Dispose() on one of its members, you actively remove it from the collection, so you're modifying the collection you're iterating over.
When you dispose of the first Control, this is removed from the collection and the next Control in the list takes its place.
Your loop calls Enumerator.MoveNext(), so when you call Dispose() again, you're disposing of the element at index 1, which was previously the element at index 2.
The element which was at index 1, now at index 0, is skipped.
This process goes on, the result is that you're disposing of half of the Controls in the collection.
You can test it with:
For Each ctrl As Control In Me.Controls
ctrl.Dispose()
Console.WriteLine(Me.Controls.Count)
Next
You'll see that the final count is half the measure of the initial count: half of the Controls are still very much alive.
You can use a backwards For loop (from [Collection].Count - 1 to 0), starting from the top of the collection.
In this case, when you dispose of a Control, the Collection is resized from the top so Enumerator.MovePrevious() won't skip any element in the collection.
For i As Integer = Me.Controls.Count - 1 To 0 Step -1
Me.Controls(i).Dispose()
Next
You can also use a forward loop and dispose at Index 0. When the element at Index 0 is disposed of, the next in line will take its place at that index, so again you won't skip any. The elements in the collection are constantly moved towards the bottom in this case, though, so it's quite a slower process.
If the collection has a small amount of items, you won't notice, but keep it in mind.
For i As Integer = 0 To Me.Controls.Count - 1
Me.Controls(0).Dispose()
Next
You can also filter the collection of Controls, to only consider a specific Type.
For example, to dispose of all TextBox Controls child of the Form (or any other class derived from Control):
Dim textBoxes = Me.Controls.OfType(Of TextBox).ToList()
For i As Integer = textBoxes.Count - 1 To 0 Step -1
textBoxes(i).Dispose()
Next

How can I save listbox items to my.settings

Intro
I have looked up how to save the items in a listbox to my.settings for a while now and there are so many different answers. I've tried them all (a bit excessively to say), but none have really worked. Its probably because I'm doing something wrong due to a bad explanation or my new-beginner stage at programming.
So I have a form where the user can set a bunch of settings. All of them are going to stay the way they were when he closes the application and re-opens it again. Textboxes, checkboxes and so on works fine, but for some reason the Listbox is harder than I'd expect to be saved.
My listbox
The user adds items to the listbox like this (Writes something like c:\test in a textbox tbpath1, presses a button btnAdd1 and the text will become a item in the listbox lbchannel1)
Private Sub btnAdd1_Click(sender As Object, e As EventArgs) Handles btnAdd1.Click
Dim str As String = tbPath1.Text
If str.Contains("\") Then
lbchannel1.Items.AddRange(tbPath1.Text.Split(vbNewLine))
tbext1_1.Text = (tbext1_1.Text)
My attempt (probably one out of ten attempts)
So this is one of my attempts so far. I wish it was this easy.
My.Settings._lbchannel1.Clear()
For Each item In lbchannel1.Items
My.Settings._lbchannel1.Add(item)
Next
My.Settings.Save()
At the attempt above, I get error 'NullReferenceException was unhandled : Object reference not set to an object instance'
I'm guessing it has something to do with items not being a string and so on, but I'm not sure where to go with this. Can someone wrap it up in a simple explained way?
If you do not add at least one item in the IDE, VS doesnt initialize the collection you create in Settings because it doesnt look like you are using it.
If My.Settings._lbchannel1 Is Nothing Then
My.Settings._lbchannel1 = New System.Collections.Specialized.StringCollection()
End If
My.Settings._lbchannel1.Clear()
For Each item In lbchannel1.Items
My.Settings._lbchannel1.Add(item)
Next
My.Settings.Save()
You can also "trick" it into initializing it for you. Add an item via the Settings Tab, save the project, then remove the item.
You can also create a List(of String) to store the data. Serialize it yourself with 1-2 lines of code and use it as the DataSource for the listbox. It is simpler than shuttling items from one collection to another and keeping them in synch. This answer shows a serializing a List(Of Class) but the principle is the same.

WinForms context menu really slow adding 150 Toolstrip Menu Items

I have this code that add items to a context menu's sub-menu:
CTX_VALUE.Enabled = True
CTX_VALUE.Visible = True
CTX_VALUE.Text = "List Values"
For k As Integer = 0 To CELL.VALUE_LIST.Count - 1
CTX_VALUE.DropDownItems.Add(CELL.VALUE_LIST(k))
Next k
Where CTX_VALUE is a ToolStripMenuItem
and CELL.VALUE_LIST is an ArrayList (yeah, old code!) of ToolStripMenuItems
When it comes to add about 150 items, it becomes really slow, about 2.5 seconds.
Visibility before adding doesn't matter, i tried moving it after.
BTW, note that the context menu is not on screen when adding items!
I also tried suspending layout of CTX_VALUE before adding. No luck.
you should add these using CTX_VALUE.DropDownItems.AddRange() method
Before the add items loop I used both
ts_filter.DropDown.SuspendDrawing (see addendum note below)
and
ts_filter.DropDown.SuspendLayout
After the loop I used the corresponding resume methods.
This made a huge difference to my program moving it from unworkable to instant.
Addendum:-
The resumedrawing (alone) was preventing my custom textbox (inherited from toolstriptextboxfrom) from showing. I found the suspendlayout and resumelayout to be ok alone though and it kept the speed up.

vb.net listbox- Remove ALL items that DON'T contain specific text

I'm working with a listbox in vb.net and am trying to remove all items from the listbox that don't contain specific text at the click of a button. Here's my code:
Dim i As Integer
For i = 0 To ListBoxPrePublish.Items.Count - 1
If InStr(ListBoxPrePublish.Items(i), "-8-") > 0 = False Then
ListBoxPrePublish.Items.RemoveAt(i)
Exit For
End If
Next
This only removes 1 item at a time though. How can I tweak this to remove all items that don't contain "-8-" at once?
EDIT: in case anyone asks, the listbox items list is growing rather large so I'm adding a sort feature so users can widdle down their options if they want to. That's why I'm not filtering anything before adding to the listbox
Here is the complete code for backward loop I mentioned in the comments - it should work:
For i as Integer = ListBoxPrePublish.Items.Count - 1 To 0 Step -1
If Not ListBoxPrePublish.Items(i).Contains("-8-") Then
ListBoxPrePublish.Items.RemoveAt(i)
End If
Next
No, I do not know of any RemoveRange type functionality. And be advised that you will need to loop through the listbox Items collection backwards as you remove items or you will get index exceptions, because once you remove something it will mess up the index values of all the remaining items in the iterator.

VB.NET web application - Update a data bound Listbox when underlying table / query changes

I have a page in my web application that contains two listboxes with buttons to move items back & forth between them. Each listbox is bound to a SQL query, the results of which change as the selected items are added or removed from the corresponding lists. This all works fine, except I cannot get the list boxes to update their contents on the fly, however if I refresh the web page, the contents update correctly.
Basically the user selects items in LeftListbox and clicks the Add button which calls code to loop through the LeftListbox and for each selected item adds a new record to a table (Score). The RightListbox should then update to show that the items have been added to the table.
Here is a snippet of code from the Click event of the Add button:
Dim i As Integer
For i = 0 To (LeftListbox.Items.Count() - 1)
If LeftListbox.Items(i).Selected Then
Try
DbUtils.StartTransaction()
Dim rec As ScoreRecord = New ScoreRecord
rec.Player_ID = CInt(Me.LeftListbox.Items(i).Value)
rec.Save()
DbUtils.CommitTransaction()
Catch ex As Exception
DbUtils.RollBackTransaction()
Me.Page.ErrorOnPage = True
Finally
DbUtils.EndTransaction()
End Try
End If
Next i
'** Here is where I want to refresh the list **
I've searched quite a bit for a solution, but I can't find anything that works so any help would be much appreciated.
Andrew
Use the same method (or code) used to populate the "right listbox" in the first place. The right ListBox's DataSource will be the same as it was prior to this code snipped being ran, so it must be updated since the underlying data has changed.