VB.NET - Loop skipping Items in ListBox - vb.net

I have a listbox with directories (not local, it is over a network) listed in it.
I am trying to loop through that listbox and remove any empty directories.
However, I noticed that it just skips some empty directories.. I found that if I ran the loop 3 - 5 times it will get them all, but that isn't very efficient.
My Loop:
Dim i As Integer
i = 0
While i < ListBox1.Items.Count
If IO.Directory.GetFiles(ListBox1.Items.Item(i), "*.*").Length = 0 Then
ListBox1.Items.RemoveAt(i)
End If
i = i + 1
End While
So I was just wondering if there is a more efficient way to check the contents of the directory or another way i could achieve this without having to run the loop multiple times.

ListBox1.Items.Count is getting reevaluated every time, making your loop get shorter as time goes on missing some items. A quick solution could be just mark which items you want to remove as you loop.
Dim toRemove As New List(Of ListBoxItem)
For Each item as ListBoxItem in ListBox1.Items
If IO.Directory.GetFiles(item, "*.*").Length = 0 Then
toRemove.Add(item)
End If
Next
For Each item as ListBoxItem in toRemove
ListBox1.Items.Remove(item)
Next
(there is probably a better way, but that's a quick solution off the top of my head)

Related

I just started working on vb.net and came across this I want to know what g.mods.count-1 to 0 step-1 mean in vb.net

I just started working on vb.net and came across this I want to know what g.mods.count-1 to 0 step-1 mean in vb.net
If rsp.ModCnt > 0 AndAlso g.ModsSpecified Then
For i As Integer = g.Mods.Count - 1 To 0 Step -1
Dim itemKey As String = String.Format("1__{0}", g.Mods(i).Name)
g.Mods.RemoveAt(i)
Next
End If
This bit of code has a few interesting things in it.
The AndAlso is a short-circuit. If rsp.ModCnt > 0 evaluates to False, the second condition will never be evaluated.
The start value of i is g.Mods.Count - 1 and the end value is 0. On each iteration the value of i will decrease by 1.
A simpler version might look like...
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim lst As New List(Of Integer) From {1, 2, 3, 4}
For i = lst.Count - 1 To 0 Step -1
lst.RemoveAt(i)
Next
MessageBox.Show(lst.Count.ToString)
End Sub
On the first iteration, i will be 3, lst.Count -1.
On the next iteration i will be 2 and so on until the last iteration where i will be 0.
If the coder tried to start the loop with zero and go up to the highest index, lst.Count -1 then we would get an Index Out of Range error.
For i = 0 To lst.Count - 1 'The default is Step 1
Try it and see. When we remove the first item from the list at index 0 the next item becomes index 0. Second iteration remove index 1. The length (.Count) of the list is now 2 (two items have been removed), the highest index is 1 but on the next iteration we try to remove index 2 which doesn't exist. This is why we use highest index to lowest index and Step -1.
As a side note the line
Dim itemKey As String = String.Format("1__{0}", g.Mods(i).Name)
is really useless because it is just overwriting the value on each iteration and falls out of scope at the end of the For block.
I believe there is an error in this code unrelated to the question. Because ItemKey is defined locally within the scope of the loop, we can reduce everything down to this single line:
If rsp.ModCnt > 0 AndAlso g.ModsSpecified Then g.Mods.Clear()
As for the question, the code loops over the g.Mods collection backwards. That's what Step -1 means.
The reason to loop backwards, rather than from front to back, is because it removes items as it goes. If it went from front to back, as items were removed the index of all the items following would change, causing you to skip items when i is incremented. Additionally, that could mean trying to read past the end of the collection, if g.Mods.Count is not rechecked.
But, again, from the code we can see, the whole thing could be replaced with simple .Clear().
This is a simple for-loop (MSDN).
A for loop runs a set number of times (or should be run as such).
For i As Integer declares an integer variable with name i for the scope of the loop body.
= g.Mods.Count - 1 assigns the value of g.Mods.Count - 1 to the variable i.
g is likely some class object with Mods as a public property. Count is a Property of Mods. Mods is likely an object of a collection class such as List (MSDN) or another class that implements the Enumerable interface (MSDN).
As per the For-Loop syntax, Step is indicating how the counter variable (that's i) is changed after each iteration. In this case it's decreased by 1 every time. Step can be omited, then Step 1 is implied.
In short this loop goes over the indexes of some Enumerable called Mods backwards.
Some other things not directly concerning the question:
A local variable itemKey is created inside the loop but never used. Unless the call of the Property Name does something (it shouldn't, that'd be bad style), this line can be removed.
The index of enumerables starts at 0, thus the last index is Count - 1
The loop goes backwards because it removes the elements of Mods. But this is slow. You can simply use Clear() on objects whose class implements IEnumerable to delete all elements of the collection.
The entire loop can be removed and replaced with g.Mods.Clear()

DataGridView Remove Error

I want to delete the duplicate value and blank lines in the DataGridView.
I am using the following code for this.
Private Sub dgwsil()
On Error Resume Next
For i2 As Integer = DataGridView1.RowCount - 1 To 0 Step -1
If Trim(DataGridView1.Rows(i2).Cells(0).Value.ToString()) = "" Then
DataGridView1.Rows.RemoveAt(i2)
End If
Next
Dim numberOfRows = DataGridView1.Rows.Count
Dim i As Integer = 0
While i < numberOfRows - 2
For ii As Integer = (numberOfRows - 1) To (i + 1) Step -1
If DataGridView1.Rows(i).Cells(0).Value.ToString() = DataGridView1.Rows(ii).Cells(0).Value.ToString() Then
DataGridView1.Rows.Remove(DataGridView1.Rows(ii))
numberOfRows -= 1
End If
Next
i += 1
End While
End Sub
Code sometimes works fine.
But sometimes it gives an error
How can I solve this problem ? Or is there any code you use for it?
It looks like you are going into this problem with a VBA mindset. In VBA, you would usually manipulate the datagridview directly and the On Error Resume Next line would be acceptable.
In VB.NET you don't want to do either of those things. Your datagridview should be bound to a collection of information. That collection can be a datatable or an array or any type of collection really. So instead of using a loop to manipulate the datagridview directly you would either loop through the backing collection or better yet LINQ through the collection and manipulate it however you need. There are a ton of tutorials out there explaining everything you could ever need so googling around for how to bind a datatable to a datagridview should get you what you need.
I would also suggest looking into the Try...Catch...Finally... structure. You should really use that to handle any errors that could possibly come up from your code.

How to remove selected items from a listbox

This is for a VB.NET 4.5 project in VS2015 Community.
I am trying to remove certain selected items from a listbox, but only if the selected item meets a condition. I've found plenty of examples on how to remove selected items. But nothing that works with a condition nested in the loop going through the selected items (at least, I can't get the examples to work with what I'm trying to do...)
Here's my code:
Dim somecondition As Boolean = True
Dim folder As String
For i As Integer = 0 To lstBoxFoldersBackingUp.SelectedItems.Count - 1
If somecondition = True Then
folder = lstBoxFoldersBackingUp.SelectedItems.Item(i)
Console.WriteLine("folder: " & folder)
lstBoxFoldersBackingUp.SelectedItems.Remove(lstBoxFoldersBackingUp.SelectedItems.Item(i))
End If
Next
The console output correctly shows the text for the current iteration's item, but I can't get the Remove() to work. As the code is now, I get the console output, but the listbox doesn't change.
Removing items changes the index position of the items. Lots of ways around this, but from your code, try iterating backwards to avoid that problem. You should also remove the item from the Items collection, not the SelectedItems collection:
For i As Integer = lstBoxFoldersBackingUp.SelectedItems.Count - 1 To 0 Step -1
If somecondition = True Then
folder = lstBoxFoldersBackingUp.SelectedItems.Item(i)
Console.WriteLine("folder: " & folder)
lstBoxFoldersBackingUp.Items.Remove(lstBoxFoldersBackingUp.SelectedItems(i))
End If
Next
You can simply use this in order to remove a selected item from the listbox
ListBox1.Items.Remove(ListBox1.SelectedItem)
I hope this was helpful.
This will not work:
ListBox1.Items.Remove(ListBox1.SelectedItem)
You need to define exactly which items will be deleted, so the next works fine for me:
ListBox1.Items.RemoveAt(ListBox1.SelectedIndex)
However it removes one line every time it is called.

Delete from ListBox items in a Loop

Each Item information get from a file which specified with the name of that item. Even though the selected items will be deleted properly in the listbox but the file of next item will be deleted. I don't know if problem is for Index or anyother part
SourceDir = "c:\"
For Each itemIndex In listHouse.SelectedIndices()
itemIndex = listHouse.SelectedIndices(0)
listHouse.Items.RemoveAt(itemIndex)
MsgBox(listHouse.Items.Item(itemIndex).Text & "R.txt")
File.Delete(SourceDir & listHouse.Items.Item(itemIndex).Text & "R.txt")
Next
You cannot add or remove items from a collection while inside a For/Each loop.
Each time you RemoveAt(n), you change the make-up of the collection you are looping. If you remove item #4, item 5 moves up to its slot. Then after Next, your code will be looking at position 5, and be looking at what was originally #6 - item 5 would be skipped entirely. To prevent this every-other one bug/issue, an exception is thrown when using a For/Each.
Secondly, your File.Delete code is trying to reference the item you just removed!
To iterate a collection and remove some/all items one by one, loop backwards using an indexer. This removes from the end of the collection so nothing can "move up". Like this:
' loop backwards using For n
For n as Integer = listHouse.SelectedIndices.Count -1 to 0 Step-1
itemIndex = listHouse.SelectedIndices(n)
MsgBox(listHouse.Items.Item(n).Text & "R.txt")
File.Delete(SourceDir & listHouse.Items.Item(n).Text & "R.txt")
' also remove the thing AFTER you have used it to delete
listHouse.Items.RemoveAt(n)
Next
If you tried to use a forward For n loop with this sort of action, you'd get the every-other one issue.

VB.NET Is there a quicker way to scan through a RichTextBox?

I have a VB.NET application that I use to load various files into a RichTextBox and then scan through the document to find specific words. It's similar to the Find function in Word. The app was running fine until a 5,150 line .sql document run through it and it's taking upwards of 10 minutes to run to completion.
Can anyone recommend a better way of coding it than I have below?
If sqlText.Contains("GRANT") Then
Dim searchstring As String = "GRANT"
Dim count As New List(Of Integer)()
For i As Integer = 0 To rtbFile.Text.Length - 1
If rtbFile.Text.IndexOf(searchstring, i) <> -1 Then
count.Add(rtbFile.Text.IndexOf(searchstring, i))
End If
Next
Try
For i As Integer = 0 To count.Count - 1
rtbFile.Select(count(i), searchstring.Length)
rtbFile.SelectionBackColor = Color.Yellow
rtbFile.SelectionFont = New Font(rtbFile.Font, FontStyle.Bold)
count.RemoveAt(i)
Next
Catch ex As Exception
End Try
rtbFile.Select(rtbFile.Text.Length, 0)
rtbFile.SelectionBackColor = Color.White
rtbFile.SelectionFont = New Font(rtbFile.Font, FontStyle.Regular)
End If
That first loop is killing the performance, you are calling IndexOf for every character in the string. Also the two loops can be merged in to one. Change it to:
rtbFile.SelectionBackColor = Color.Yellow
rtbFile.SelectionFont = New Font(rtbFile.Font, FontStyle.Bold)
For Each m As Match in Regex.Matches(sertbFile.Text, searchstring)
rtbFile.Select(m.Index, searchstring.Length)
Next
This can also be done with a While loop and RichTextBox.Find():
Dim searchstring As String = "GRANT"
Dim index As Integer = rtbFile.Find(searchstring, 0, RichTextBoxFinds.None)
While index <> -1
rtbFile.Select(index, searchstring.Length)
rtbFile.SelectionBackColor = Color.Yellow
rtbFile.SelectionFont = New Font(rtbFile.Font, FontStyle.Bold)
index = rtbFile.Find(searchstring, index + searchstring.Length, RichTextBoxFinds.None)
End While
You've got a few bad things going on here:
First, the following code:
For i As Integer = 0 To rtbFile.Text.Length - 1
If rtbFile.Text.IndexOf(searchstring, i) <> -1 Then
count.Add(rtbFile.Text.IndexOf(searchstring, i))
End If
Next
This is looping through every character in your string, and calling IndexOf on the entire string from that point forward. So your 50,000-character string is running IndexOf 50,000 times, on large strings.
You only need to call IndexOf as many times as you find a string. When your string is found, you increment your start index to that point, and keep searching only from that point.
Next thing, this code:
For i As Integer = 0 To count.Count - 1
...
count.RemoveAt(i)
Next
The RemoveAt line is unnecessary. You're already looping through a list, so you don't need to remove the items as you go along. The way it stands, your loop will skip every other item in your list.
Whoops. I missed a very important point about the IndexOf (and incorrectly assumed it was fed with the end of the last match). See Magnus's answer.
I am not sure where the bottleneck is (and it might very well be from setting the selection itself), but here are my suggestions, roughly in order of priority:
Invoke rtbFile.Text once to avoid any roundtrips to underlying control (perhaps a native Windows control?) and use a variable to store the resulting string. Once the string is obtained in .NET, just keep using it directly unless/until the text may change. If the control is native then a lot of work may be required to simply "get the text".
Use normal item iteration over the count collection (not indexing) and do not remove from the front-of the List when assigning the selections. Removing from the front of a List is "expensive" in that it must shift all items down internally. Also, removing the element is unneeded here and dubious at best: since the collection being modified is also is being iterated which likely leads to incorrect behavior (skipped items), regardless of performance.
Only call IndexOf once per loop and use a variable to avoid a duplicate search. This likely won't have an overall impact, but it does avoid some "extra" work. IndexOf itself is fine and doesn't need to be replaced.
YMMV.