Visual Basic Form doesn't dispose all items when asked to - vb.net

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

Related

Removing Textbox and DateTimePicker in a Panel

I have a code that deletes the textbox and datetimepicker i create in a panel... delete code is working for all the TEXTBOX but when its time to delete the datetimepickers it does not delete all the datetimepicker.
Example: there are 4 textbox and 4 datetimepickers when it runs the code the panel will delete all 4 textbox but deletes 2 datetiepickes only. I really cant find out what is wrong. Please help me... Thanks!
code is here:
For Each ctrlTxt As TextBox In panelGroupDependent.Controls.OfType(Of TextBox)()
ctrlTxt.Dispose()
Next
For Each ctrlDtp As DateTimePicker In panelGroupDependent.Controls.OfType(Of DateTimePicker)()
ctrlDtp.Dispose()
Next
As it stands, you are modifying a collection as you are enumerating it, which is not allowed. A For Each loop enumerates a collection so you are not allowed to add or remove items within the loop. When you dispose a control, it is removed from its parent container and thus you are modifying the Controls collection of that parent.
The OfType method doesn't actually generate a new collection of items but rather enumerates the collection it's called on and yields the items of the specified type one by one. As such, enumerating the result of OfType means enumerating the source collection at the same time.
By calling ToArray, you complete the enumeration of the source collection first and populate an array with the items of the specified type, then enumerate that array using the For Each loop. Disposing the controls has no effect on the array so there's no issue.
For Each ctrlTxt In panelGroupDependent.Controls.
OfType(Of TextBox)().
ToArray()
ctrlTxt.Dispose()
Next
For Each ctrlDtp In panelGroupDependent.Controls.
OfType(Of DateTimePicker)().
ToArray()
ctrlDtp.Dispose()
Next
Note that there is also no need to declare the type of the loop control variables as it can be inferred.

Is there a way to consolidate iteration through form controls for different activities

I just started rewriting an application from vba (Access) to vb.net + SQLServer so not very experienced in .net.
I am creating custom controls (Form + form controls) with a number of extra properties PrevValue, Modified (similar then the one of Textbox), Dirty, DirtyEnabled, SQLColumnName, SQLTableName to enable AutoUpdating and undoing in my forms the form exposes IsDirty, Initialising and Isready properties and an undo method.
Doing so it occurs that I have to write 3 times the same iteration code in different places:
For each Ctrl as Control in frm.Controls ' frm being a reference to the form
if typeOf Ctrl is MyTextBox
with DirectCast(Ctrl, MyTextBox)
' here comes the variable code depending what needs to be done
end with
elseif TypeOf Ctrl is MyComboBox
' etc.... for MyListBox, MyCheckBox etc....
I also have a number of custom controls MyNumBox and MyDateBox that inherit from MyTextBox but with some modified behavior (Formula evaluation, date manipulation, calendar...) how do I avoid doing an extra test on them.
One version of this Iteration is in the SQLProcessClass where the modified controls are added as SQLParameter and after iteration calling the SQLProcessClass Update or Insert, but ... after successful SQL activity I need to iterate through the controles again to reset the modified flag for each control. Elsewhere I need it to implement a form undo to reset all the controls to their previous values.
It seems to me I have two options
1. repeating that iteration code everywhere I need to iterate through the forms controls. I don't like it as every time I would need to create a new custom control I have to add some lines X times in different modules/classes ... very bad programming
2. Creating one form iteration procedure containing all the different activities that normally belong to another class within that "centralised" procedure, that could be better then (1) but I don't like it that much either.
Is there a better way of doing it using some .net functionality I don't master yet ?
Thanks for any advise.
Iterating through from controls can be tricky since controls are often nested. A more controlled approach would be to add another collection object to your form where you keep references to your added controls.....
e.g
Dim My_Widgets as New List(of Your-Control-Class-Name)
Then when you create the controls to the form also add them to that list.
My_Widgets.Add(Widget_Object)
After that it is a simple matter to iterate through that list.
For Each Widget as My_Widget_CLass in My_Widgets
' do what you need to do to Widget
Next
If you need to reference individual controls directly, use a dictionary object instead..
e.g.
Dim My_Named_Widgets as new Dictionary(of String, Your-Control-Class-Name)
Then add your control references to the dictionary by name
My_Named_WIdgets.add("<Whatever_You_USe_To_Identify_It>", Widget_Object)
You can then reference the specific control by the ID or name
My_Names_Widgets("ID").Property = Whatever '... etc
You seem to be indicating you have other controls for other purposes, as such it would be prudent to create similar collections for each type.

me.control.remove is removing every second control in a loop for some reason

I have some dynamically created checkboxes on my form and I want a function to delete them all.
I've got the following function:
Sub delete_checkboxes()
Dim radios = Controls.OfType(Of RadioButton).AsQueryable()
For Each r As RadioButton In radios
Me.Controls.Remove(r)
Next
End Sub
For some reason the above function only deletes every second radio button and leaves the rest.
Just as a test, I changed the function to delete radio buttons which are ticked:
Dim radios = Controls.OfType(Of RadioButton).AsQueryable()
For Each r As RadioButton In radios
If r.Checked Then
Me.Controls.Remove(r)
End If
Next
With the above I can tick each radio button and it will delete them invididually... so what it is in the first function which could be causing it to skip every second radio button?
Change AsQueryable() to ToList()
The reason it fails is that you are not supposed to modify a iterator while you are still looping over it. AsQueryable() is just using a state machine internally to know your current position in the Me.Controls collection. It doesn't actually keep it's own collection of controls, but just knows which controls you need from your original collection.
When you remove a control in the middle of loop, that position state is now wrong... in fact, it's off by one. You then remove the next control, which puts that internal position state off by one again, and so on. After a whole set of off-by-one adjustments and you end up with half of the controls still on your form.
ToList() will work, because it creates a separate collection for your controls, so that you don't have to mess with that state as your remove them from your Me.Controls collection.
this is usually how I accomplish it.
For Each cont As Control In Me.Controls
If cont.GetType().Name = "RadioButton" Then Me.Controls.Remove(cont)
Next

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.

Re-Creating Dynamic Controls

I have a VB.Net WinForm Program.
I dynamically create panels with controls.
Each panel has:
2 Labels
1 DataGridView
1 Button
Everything works fine the first time I create the panels.
Everything gets created, and everything is functional.
If I have to re-create the form, I get rid of the existing panels (and their controls) with this code:
For P = 0 To Panels.Count - 1
For Each PControl In Panels(P).controls
Panels(P).controls.remove(PControl)
Next
Me.Controls.Remove(Panels(P))
Next
Panels.Clear()
DataGrids.Clear()
lblCounts.Clear()
Where:
Panels, DataGrids, & lblCounts are ArrayLists holding controls
When I re-create the panels, I get the panels and all of their controls except Buttons
When I step through the debugger, I see the buttons being removed, and I see them being created, but they don't appear in the panel
Any ideas?
Your question is regarding a button not appearing when you are adding the controls, but you are only showing the removal process, which is flawed.
Make a UserControl that holds your Labels, Grid and Button. Add that to your form. That's what UserControls are for.
Also, when you are done using it, just call:
MyControl.Dispose()
Otherwise, I suspect you are leaking memory. Remove does not destroy the object.
For Each PControl In Panels(P).controls
Panels(P).controls.remove(PControl)
Next
This part may kick you out of your code. The 'For Each' does not like it when its items change during execution. check it with Breakpoints. if is is really a problem , you could do..
lazy method, by just adding .ToList
For Each PControl In Panels(P).controls.ToList
Panels(P).controls.remove(PControl)
Next
similar to:
Dim AllControls as New List(Of control)
AllControls.AddRange(Panels(P).controls)
For Each PControl in AllControls
Panels(P).controls.remove(PControl)
Next
or:
For i as integer = Panels(P).controls.count -1 to 0 step -1
Dim PControl as control = Panels(P).controls(i)
PControl.parent.remove(PControl)
Next
Try this
WHILE Panels(P).controls.count > 0
Panels(P).controls.removeAt(1)