Progress bar for populating treeview (wrong thread error) - vb.net

I have a recursive function with some loops that I use to populate a TreeView with a list of files and folders. In some situations this can take a bit of time to execute especially when loading a lot of files.
I wish to show a progress bar to show how far through the loading process I am.
I have searched everywhere for this and tried with a BackgroundWorker and am now very lost.
Sorry I'm not really sure what code to share but please let me know if you need to see any of the functions/routines.
The error I get with the background worker is a System.InvalidOperationException
"Action being performed on this control is being called from the wrong thread."
Background Worker_DoWork:
For i = 0 To 100
PopulateTreeView()
BackgrondWorked.ReportProgress(i)
Next
PopulateTreeView Sub:
Dim node As TreeNode = New TreeNode
node.Text = "Music"
TreeView.Nodes.Add(node)
GetFilesAndFolders("D:\Music", node)
This throws the error described above when trying to add the first node with text "Music"
Any suggestions will be greatly appreciated. Thanks.

Related

Erroneous Duplicate Root Nodes displayed in System.Windows.Forms.TreeView

I'm hoping someone will be able to shed some light on this.
I'm encountering an issue in the System.Windows.Forms.TreeView control that is manifesting when I build an instance of a System.Windows.Forms.TreeView in MS Video Studio 2017.
Basically - The first time it is built, everything looks as expected. However, when I rebuild it at a later time (e.g.. after an update), the root node display gets duplicated. Multiple updates result in the TreeView displaying increasing multiple root notes.
The nodes are cleared each time before the TreeView gets built and rebuilt ( e.g., MyTreeView.Nodes.Clear() ).
I was able to verify that the root nodes do not physically get duplicated. The number of nodes in the TreeView (Nodes collection) remain the same (1) and when i click on the duplicated root nodes and fall into debug (via my MyTreeView_AfterSelect event/routine), I can tell from the tag ID/hex handle that it is in fact the same (single) root node. It's just being displayed twice in the visual aspect of the TreeView.
This tells me that there is a bug in the Treeview control itself. I have researched this issue online in previous StackOverflow articles (as well as in a few other forums) and I have tried a number of suggestions/approaches to try to get around the problem. To date, none have been successful for me.
Any suggestions/insights would, of course, be greatly appreciated. I am at a loss as to what to do next.
Regards,
Christopher H. Fleetwood
I agree with Hel O'Ween, the issue is probably with the way its coded.
The issue I think is with the command "TreeView.Nodes.Clear()" , it will Delete the "RootNode" as well as all other nodes and leave a "Blank" Duplicate entry in the system. If you run it again it will produce another Blank "Rootnode" entry which will cause havoc with you application as you have found.
The way to deal with it is to make the Root-Node Permanent. (It can be Set to ReadOnly - Hidden) Then just clear down the Childnodes that are attached to it.
Here is the vbcode i use to clear a treeview of all child nodes.
Dim lngCount As Integer
For lngCount = TreeView.Nodes.Count To 1 Step -1
If lngCount - 1 = 0 Then Exit Sub
TreeView.SelectedNode = TreeView.Nodes.Item(lngCount - 1)
TreeView.Nodes.RemoveAt(lngCount - 1)
Next
I did see an article on it once about never deleting the rootnode or you'll get a blank entry left! If i find it i'll add a link. It might only be vb.net with this issue.
Don't pass TreeNode objects ByRef.

My.Settings "Destination array was not long enough. Check destIndex and length, and the array's lower bounds."

I have two forms with combo boxes. The combo box values are stored in My.Settings.testDevices. (System.Collections.Specialized.String.Collection) with a scope of User.
The second form adds the ability to add items to testDevices, and then upon exit it updates My.Settings.testDevices.
Now, only if I make a change to the settings (adding items only), when I exit back to the main form (which remains loaded throughout the process), my application crashes with the following message:
"Additional information: Destination array was not long enough. Check destIndex and length, and the array's lower bounds."
As I understand it, this might be a concurrency issue, however I'm not sure.
My code:
In my main form Load event: (to load from My.Settings)
testDevicesComboBoxMain.Items.Clear()
My.Settings.testDevices.CopyTo(mainFormTestDevices, 0)
testDevicesComboBoxMain.Items.AddRange(mainFormTestDevices)
Where "testDevicesComboBoxMain" is the combo box on the main form.
On the secondary form Close Event: (to save to My.Settings)
Dim items(testDevicesComboBox.Items.Count - 1) As String
testDevicesComboBox.Items.CopyTo(items, 0)
My.Settings.testDevices.Clear()
My.Settings.testDevices.AddRange(items)
My.Settings.Save()
I have found similar questions on here, but none with answers that I understand :P
As I am a beginner with vb.net, could any answers be provided in an easy to understand form please!
Thanks.
I forgot to add:
Public items(My.Settings.testDevices.Count - 1) As String
Public mainFormTestDevices(My.Settings.testDevices.Count - 1) As String
I tried setting separate declarations just in case there was some kind of conflict. These obviously do the same thing, just with different names.
I fixed it by adding a For loop to read from My.Settings.
For Each i As String In My.Settings.testDevices
testDevicesComboBoxMain.Items.Add(i)
Next
This seems to have cured the problem, and may perhaps be a more "modern" way of doing it?

Wait for 1 second before starting code again - VB.NET

I desperately need help with a game I am making. For a bit of context, i am making a memory game and i have the following piece of code that is being troublesome. I have a bunch of labels on the form, 16 to be exact, with 1 randomly generated symbol placed in each. Each symbol appears in the labels twice.
------------------------------Continued----------------------------------------
'MsgBox("hello") 'used to check if the second inccorect press shows up - it does show but instantly changes colour
'''''''''''''''''NEED SOME CODE THAT PAUSES IT HERE'''''''''''''''
labels(0).ForeColor = Color.DarkRed
sender.ForeColor = Color.DarkRed
End If
flips = 1
End If
End If
tmrmemory.Enabled = True ' starts the timer after the user clicks the first label
End Sub
What's supposed to happen is that when the labels clicked don't match, it should show both the clicked labels for a short period before changing them both back to "DarkRed" which is the colour of the form's background.
I have tried using a timer but then i can't use sender.forecolor=color.darkred because it is not declared globally.
I have also tried using the command Threading.Thread.Sleep(500) but it still doesn't show the second incorrect click. I know that the code i have used works because when i use the message box, i can see both symbols and when the two clicks are correct, it stays.
Threading.Thread.Sleep(500) will actually pause your code for half a second. However during this time it won't do anything, not even refresh your controls. To get the effect you want, you need to call the YourControl.Refresh method before calling Threading.Thread.Sleep to force the control to redraw immediately.
On a side note, I would advise you not to call Threading.Thread.Sleep on UI thread. It will give a feeling of program hang. Instead do your work on a separate thread. You can either do all the work yourself right from creating a separate thread to destroying it, or use the BackgroundWorker control which has all the functionality built in.
Here is the link to an article I wrote a long time ago regarding BackgroundWorker that might be useful for you:
http://www.vbforums.com/showthread.php?680130-Correct-way-to-use-the-BackgroundWorker
Declare a variable outside the sub that stores what label should be flipped when the timer ends.
Label click sets
storedLabel = sender
Timer tick sets storedLabel.ForeColor = Color.DarkRed

Showing MsgBox() with nowait (no user input) is not the real issue

I had searched a lot how to display a Msgbox that will not wait the user input (pressing ok or cancel).
I found 3 solutiuons to this.
1- Display the MsgBox() in another thread or using BackgroundWorker()
2- Create a form that display the message, then closed the form by a timer and use it instead of Msgbox()
3- Using the API MessageBoxA()
Let say I have a loop from 1 to 100, and I want display a message for the i(counter)
When I test above 3 ways, I found that this is not the right way of doing it, I don't need a msgbox() to close by it self after showing the message, because that will display 100 dialog.
What I realy want is to display ONLY 1 MsgBox() and change the text accordingly.
I managed to do this using a a Form() as class and I did it using Application.DoEvents
I know it can be done using BackgroundWorker or Threading since alot of people advice against using Application.Doevents
Here is my code
Dim oWW As New WaitWindow With {.TopLevel = True,.TopMost = True,.StartPosition = FormStartPosition.CenterScreen}
oWW.Show(Me)
For i = 1 to 100
Threading.Thread.Sleep(500) ' Just to slowdown execution
oWW.SetMessage("Counter = " + i.ToString)
Next
oWW.Dispose()
Public Class WaitWindow
Sub SetMessage(ByVal Message As string)
lbl_message.Text = Message
Application.DoEvents
End Sub
End Class
WaitWindow is not more than a Form base class with a label (lbl_message)
That code works fine (display WaitWindowForm on center of currently displayed form only once, then I change the text)
I have 3 questions :
1- How to display the WaitWindowForm in the top right corner of my working form?
2- Is it possible to display the normal MsgBox() or MessageBox.Show() only once, then capture the text displayed and change it?
3- Which one is suitable for my issue (BackGroundWorker or Threading) and what the code in WaitWindow class I post will be if I decided to use Backgroundworker or Threading instead of Application.DoEvents (Changing the label text not showing new form with new text) ?
3 questions in one post.. humm.. who cares, I am not the one who will answer lol :)
Thanks in advance.
I think the issue that you're really encountering is that you're trying to use a message box for something it's not suited for. If you want to have text that constantly changes just add a text box in the upper right corner of your application and adjust it every time a new message needs to be shown.
You can also look up dialogu windows (search ".showdialog() vb.net" in google) might help as well.

Removing items from listbox using backgroundworker

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.