Dynamically Removing controls from a form - vb.net

I use a form that has controls that are dynamically added according to the class of object for which it is being used.
In order to load the next object into the form it is necessary to delete and rebuild the dynamic part.
I am experiencing random results when removing the objects using the code below, can you tell me where my error may be?
Sub CntrlKill(KillName As String)
For Each c In Me.Controls
If Strings.InStr(c.name, KillName) > 0 Then
Me.Controls.Remove(c)
End If
Next
End Sub

Translating the second link provided by GSerg to vb.net.
Create a list to hold the controls you want to delete. You can loop through the all the controls and add certain ones to a list. This does not effect the Controls collection. I haven't seen InStr used in a very long time. The .net .Contains will probably do what you need.
The second loop loops through the lstToDelete. This list is not effected by removing controls from the Controls collection.
The rule for For Each loops is don't effect the Collection you are looping through. You can change properties of the items in the collections. Just don't remove any items.
Sub CntrlKill(KillName As String)
Dim lstToDelete As New List(Of Control)
For Each c As Control In Controls
If c.Name.Contains(KillName) Then
lstToDelete.Add(c)
End If
Next
For Each c In lstToDelete
c.Dispose()
Next
End Sub
Or the backwards loop way.
Private Sub NukeControls(KillString As String)
For i = Controls.Count - 1 To 0 Step -1
If Controls(i).Name.Contains(KillString) Then
Controls(i).Dispose()
End If
Next
End Sub

Related

Looping between OpenForms excluding the ActiveMdiChild

I'm trying to make a loop which runs for every form excluding the activeform... I found a solution which I could use ActiveForm.ActiveMdiChild.Name = FormRefresh.Name but the problem is if I've 2 forms of same name, It'll not loop in the not active form...
Public Shared Sub VerificaAlterações()
For Each FormRefresh As Form In Application.OpenForms()
If ActiveForm.ActiveMdiChild = FormRefresh Then Continue For ' Error here
' Do some work...
Next
End Sub
Edit: The problem was the Shared in Sub... I removed it and it worked with the answer below.
There's no need to compare the names of the forms when you can compare the forms themselves:
If FormRefresh Is ActiveMdiChild Then Continue For

Split Form creates a separate collections for each of its parts

I have a split Form in MS Access where users can select records in the datasheet and add it to a listbox in the Form thereby creating custom selection of records.
The procedure is based on a collection. A selected record gets transformed into a custom Object which gets added to the collection which in turn is used to populate the listbox. I have buttons to Add a Record, Remove a Record or Clear All which work fine.
However I thought, that pressing all these buttons is a bit tedious if you create a Selection of more then a dozen records, so i reckoned it should be simple to bind the actions of the Add and Remove buttons to the doubleclick event of a datasheet field and the listbox respectively.
Unfortunately i was mistaken as this broke the form. While a doubleclick on a field in the datasheet part of the form added the record to the listbox it was now unable to remove an item from the underlying collection giving me Run-time error 5: "Invalid procedure call or argument". Furthermore the clear all Button doesn't properly reset the collection anymore. When trying to Clear and adding a record of the previous selection the code returns my custom error that the record is already part of the selection and the listbox gets populated with the whole previous selection, which should be deleted. This leaves me to believe, that for some Reason the collection gets duplicated or something along these lines. Any hints into the underlying problem is apprecciated. Code as Follows:
Option Compare Database
Dim PersColl As New Collection
Private Sub AddPerson_Click()
AddPersToColl Me!ID
FillListbox
End Sub
Private Sub btnClear_Click()
Set PersColl = Nothing
lBoxSelection.RowSource = vbaNullString
End Sub
Private Sub btnRemovePers_Click()
PersColl.Remove CStr(lBoxSelection.Value)
FillListbox
End Sub
Private Sub FillListbox()
Dim Pers As Person
lBoxSelection.RowSource = vbaNullString
For Each Pers In PersColl
lBoxSelection.AddItem Pers.ID & ";" & Pers.FullName
Next Pers
lBoxSelection.Requery
End Sub
Private Function HasKey(coll As Collection, strKey As String) As Boolean
Dim var As Variant
On Error Resume Next
var = IsObject(coll(strKey))
HasKey = Not IsEmpty(var)
Err.Clear
End Function
Private Sub AddPersToColl(PersonId As Long)
Dim Pers As Person
Set Pers = New Person
Pers.ID = PersonId
If HasKey(PersColl, CStr(PersonId)) = False Then
PersColl.Add Item:=Pers, Key:=CStr(PersonId)
Else: MsgBox "Person Bereits ausgewählt"
End If
End Sub
This works alone, but Simply Adding this breaks it as described above.
Private Sub Nachname_DblClick(Cancel As Integer)
AddPersToColl Me!ID
FillListbox
End Sub
Further testing showed that its not working if i simply remove the Private Sub AddPerson_Click()
Edit1:
Clarification: I suspected that having 2 different events calling the same subs would somehow duplicate the collection in memory, therefore removing one event should work. This is however not the case. Having the subs called by a button_Click event works fine but having the same subs called by a double_click event prompts the behaviour described above. The issue seems therefore not in having the subs bound to more than one event, but rather by having them bound to the Double_Click event.
Edit2: I located the issue but I Haven't found a solution yet. Looks like Split-Forms are not really connected when it comes to the underlying vba code. DoubleClicking on the record in the datasheet view creates a Collection while using the buttons on the form part creates another one. When trying to remove a collection item by clicking a button on the form, it prompts an error because this collection is empty. However clicking the clear all button on the form part doesn't clear the collection associated with the datasheet part.
Putting the collection outside into a separate module might be a workaround but i would appreciate any suggestions which would let me keep the Code in the form module.
The behavior is caused by the split form which creates two separate collections for each one of its parts. Depending from where the event which manipulates the collection gets fired one or the other is affected. I suspect that the split form is in essence not a single form, but rather 2 instances of the same form-class.
A Solution is to Declare a collection in a separate module "Coll":
Option Compare Database
Dim mColl as new Collection
Public Function GetColl() as Collection
Set GetColl= mColl
End Function
And then remove the Declaration of the Collection in the SplitFormclass and Declare the Collection in every Function or Sub by referencing the collection in the separate Module like in the following example:
Option Compare Database
Private Sub AddPersToColl(PersonId As Long)
Dim Pers As Person
Dim PersColl as Collection
Set PersColl = Coll.GetColl
Set Pers = New Person
Pers.ID = PersonId
If HasKey(PersColl, CStr(PersonId)) = False Then
PersColl.Add Item:=Pers, Key:=CStr(PersonId)
Else: MsgBox "Person Bereits ausgewählt"
End If
End Sub
This forces the form to use the same Collection regardless if the event is fired from the form or datasheet part of the split-form. Any Further information is appreciated but the matter is solved for now.
Thanks everyone for their time

Assign Several Controls to an Array

I am trying to use Ctype on a few textboxes I have so that I can put them in an array like so
dim textboxes(12) as textbox
for i=0 to 11
textboxes(i) = Ctype(form1.controls("textbox" & i+1), textbox)
next
This works fine for most of my controls. It does not work however, for any control that is within a TabControl. I thought that maybe doing something like
Ctype(form1.tabcontrol.control("textbox" & i+1), textbox)
might work, but it does not seem to help either.
Additional info: This is a winforms project.
TabControls have a collection of TabPage, which have a collection of Control. You'll need to scan all of these. Just scan them all and check if they are indeed checkboxes.
Also, I strongly suggest you use iterators (For Each is syntactic sugar for iterators), indexes get messy at some point.
dim myBoxes as new List(of TextBox)
For each tab as TabPage in form1.tabcontrol.TabPages
For each ctrl as Control in tab.Controls
If ctrl.GetType() Is GetType(TextBox)
myBoxes.Add(Ctype(ctrl, TextBox))
End If
Next
Next
'If you do need an array
return myBoxes.ToArray()
I found the answer to my initial question. As Hans points out in his comment,
The controls on a TabControl are located on one its tab pages, not on the TabControl itself.
So the correct code is something like:
dim textboxes(12) as textbox
for i=0 to 11
textboxes(i) = Ctype(form1.tabcontrol.tabpages(0).controls("textbox" & i+1), textbox)
next
After doing a bit of research and reading the various comments and warnings from other users, I realized that this is not the best way of approaching this problem, and I do not need to initialize an array for my textboxes. It is better not to put them in an array to make it easier to call them, but rather just call them like so
for i=0 to 11
form1.tabcontrol.tabpages(0).controls("textbox" & i+1).text = "sometext"
next

Dynamically looping through picture box controls in Visual Basic 2010 does not seem to follow any order

In a Visual Basic 2010 form application I have the below code snippet:
For Each ctlControl In Me.Panel1.Controls
If TypeName(ctlControl) = "PictureBox" Then
ctlControl.image = Nothing
End If
Next ctlControl
My problem is when it loops through the controls it does not start with the top left control and it seems to go over each picture box in random order.
How do I control the order of which picture box is updated next. Is there a property similar to tab index(in VB 6) which I can manipulate to control the order in which picture boxes are updated by my loop?
As a more proper and sure way, I would get each picture box, keep handles and locations of them, then sort them according to their location. Now they are ready to use. Here is an example:
Public Class Form1
Structure Pbox
Dim handle As IntPtr
Dim top As Integer
Dim left As Integer
End Structure
Dim pboxlist As New List(Of Pbox)
Sub ClearImages()
pboxlist.Clear()
For Each c As Control In Me.Controls
If TypeName(c) = "PictureBox" Then
Dim x As New Pbox
x.top = c.Top
x.left = c.Left
x.handle = c.Handle
End If
Next
pboxlist.OrderByDescending(Function(a) a.top).ThenByDescending(Function(a) a.left)
For Each item In pboxlist
Dim x As PictureBox = PictureBox.FromHandle(item.handle)
x.Image = Nothing
Next
End Sub
End Class
Another approach is using a good naming, so that you can use their names to sort them. For instance, PictureBox1 will come before PictureBox2 if you sort. So you should use PictureBox1 for the very top and left one and PictureBox2 for the next one and so on...
EDIT: Using Tag property, as John Bustos suggested, instead of names is an easier and better idea. So without getting lost in names, you can sort picture boxes according to their Tags which are defined by you.
As some of the other guys have said you could use the TAG property which is probably the BEST shot, whenyou are dynamically creating the picture boxes use a counter and add the counter value to the TAG property. if you added the picture boxes manually then simply start with the top left and work towards the right and add a value in the TAG property field of each one starting with 1 and increasing by one each time and continue until the row is completed then carry on with the next row.
Finally when your ready to loop through the picture boxes simply follow the pattern below..
'Calc number of picture boxes
For Each ctlControl In Me.Panel1.Controls
If TypeName(ctlControl) = "PictureBox" Then
Counter = Counter + 1
End If
Next ctlControl
ThisBox = 1
Do
For Each ctlControl In Me.Panel1.Controls
If TypeName(ctlControl) = "PictureBox" Then
If CInt(ctlControl.Tag) = ThisBox Then
CLEAR-YOUR-IMAGE-HERE
ThisBox = ThisBox + 1
End If
End If
Next ctlControl
Loop Until ThisBox = Counter
Note:
Its IMPORTANT your numbers that you place in the TAG property are consecutive or you will become forver stuck in the DO-LOOP!!!
The order of the controls was determined by the order they were added to the panel and not tabstop index. You can change that by carefully reorganizing the order they were added to the panel in the form's designer file, though I'd not recommend it.
The PictureBox control has a Text property you can use instead of Tag.
It doesn't come up in Intellisense because it's an infrastructure property, but it's there.
http://msdn.microsoft.com/en-us/library/hc9k45f4(v=vs.110).aspx
(I wanted to comment on Zaf Khan's answer but I don't have the rep, yet.)

Not all controls in collection are being copied

I'm a bit confused here. I'm copying all the controls from one form to a panel on the main form and for some reason only about half of them copy.
Private Sub switchComponent()
Dim selection As String = TreeView1.SelectedNode.Text
Panel1.Controls.Clear()
Dim query = From cont In serverDic(selection).Controls
Select cont
For Each copier As Control In query
Panel1.Controls.Add(copier)
Next
End Sub
serverDic is defined as:
Dim serverDic As New Dictionary(Of String, frmServer)
When stepping through the code, serverDic(selection).Controls has 12 elements, but only 6 of them get copied. Next time this gets called, only 3 get copied. Does Panel1.Controls.clear() somehow kill the references?
EDIT: Just to show that there are infact 12 elements in the collection:
The problem here is that you are iterating over a collection that you are changing. When you add a Control to an container it is implicitly removed from it's previous parent and hence query. This is why you see exactly half of the items get removed.
With most collections this would be more apparent because they would throw if modified during an enumeration. The primary source of query here though is ControlCollection which does allow for modifications while enumerating.
To fix this problem just add the following line before the For Each loop.
query = query.ToList()