Re-Creating Dynamic Controls - vb.net

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)

Related

Getting Selected Items from a Listbox

Good Wednesday All.
I am running into a brick wall (easy for a shade tree coder to do) I have a Listbox that i populated with a datatable. I want to get the all LicenseID's from the selected items. In other words, if the user selects 3 out of 8 of the list box, I need to get the LicenseID for each of those 3.
Below is how I populated the listbox
Using cmd As New OleDbCommand(cmdText, conn)
conn.Open()
Dim reader As OleDbDataReader = cmd.ExecuteReader()
dt.Load(reader)
ListBox1License.DataSource = dt
ListBox1License.DisplayMember = "InstitutionTypeAbrev"
ListBox1License.ValueMember = "LicenseID"
End Using
I need to get the selected items from the listbox to use later.
I am thinking of adding the selected Items to an array.
I have searched around STackOverflow for some examples but none seem to work for me.
Any Help Appreciated
I'll show you how to derive the answer for this yourself:
I've set up a form:
Really simple; the listbox is like your listbox. The button is just there to give me an easy way to stop the code and examine what is going on.
I wrote some code to populate some things into my listbox. It's a screenshot because it doesn't matter that you have exactly this code, so you don't need to write this code (hence why I'm making it hard to copy paste):
I've double clicked my button to make a click handler. I haven't written any code, but I have put a breakpoint on the method declaration - see it's red? Click the margin where the dot is, to put breakpoints in your code. When you hit them, the code stops and waits for you to inspect:
I've run my app and clicked my button. The code has stopped and VS has switched to showing me the code, not the app:
I can now point to some variable that is in scope (like ListBox1) and see a tooltip, or I can open the Locals/Autos windows and see variables that are in scope and drill into them:
Expand you ListBox in the Autos/Locals window. It has a lot of properties. Scroll to SelectedItems:
SelectedItems is a collection of things.. We can tell partly because Microsoft is good at naming collections of things with a plural name, and because the inspector says "enumerate the enumerable" .. it means that it is a bunch of things that we can ForEach to look through
Expanding it we see that my selecteditems has only one thing selected (i truly did only have one selected item in my list when I clicked the button)
We can see that an entry in the SelectedItems collection is a DataRowView type of object. We can see that a DataRowView has a Row property that is a DataRow.. This Row is the DataRow in the DataTable to which the list is bound (you set the DataSource to a DataTable; this is a row from that table).
Every time you dig into the tree another level, that's like using either a dot or an indexer in your code. At this level we've gone listbox1.SelectedItems(0).Row..
So from this we can see that we need a code like:
' we will "enumerate the enumerable"
For Each drv as DataRowView in listbox1.SelectedItems
Dim originalRow = drv.Row 'we could do this to get the row...
Dim selectedAnimaId = row("AnimalID") ' ..and then index the row to get the animal ID ..
Dim selectedAnimalId = drv("AnimalID") ' ... or it's actually possible to index a DataRowView directly, so you can skip the row part
Next drv
It can be handy to write code while you're stopped on a breakpoint so you can look at the values of things as you're writing, and check you're going in the right direction. You might need to use F10 (or whatever key is associated with "step over"/"step into") to move the yellow bar along and execute code lines one by one:
You can only move the code execution along if you've written complete, legal code, but it doesn't have to be logically correct. You can back up and execute again by dragging the yellow arrow in the margin (or right clicking and choosing Set Next Statement). Here I've put some dummy statement in to move along to, so i can check that my animalID is correctly set in X like I expect . I point to X to see the value:
The standard ListBox won't help you with that, past getting the DataRowView objects from the SelectedItems collection. As an alternative, here's a custom control that you can use in place of a standard ListBox that will help you:
Public Class ListBoxEx
Inherits ListBox
Public Function GetItemValue(item As Object) As Object
Dim index = Me.Items.IndexOf(item)
If (index <> -1 AndAlso Me.DataManager IsNot Nothing) Then
Return Me.FilterItemOnProperty(Me.DataManager.List(index), Me.ValueMember)
End If
Return Nothing
End Function
End Class
You can then call GetItemValue and pass any item and get the same value back as you would if that was the SelectedItem and you got the SelectedValue. To get all the values in an array:
Dim licenseIDs = myListBoxEx.SelectedItems.
Cast(Of Object)().
Select(Function(o) CInt(myListBoxEx.GetItemValue(o)).
ToArray()
For more information, see here.
In case you're unaware, if you add a class to your project and it is a control or component, once you build, it will appear automatically at the top of the Toolbox window.
If you already have a standard ListBox in place and you don't want to have to delete it and add a new control, you can edit the designer code file by hand to change the existing control. To do that, open the Solution Explorer and select a node within your project, click the Show All Files button, expand the node for your form, double-click the designer code file and then replace ListBox with ListBoxEx (or whatever you call it) in the relevant places. I'd advise creating a backup copy or syncing with source control first, in case you mess it up.

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

Issue with a vb.net panel control loop

I have a loop in vb.net where I am wanting to display 50 panels, all with the same 3 controls. Only the last control is populated with the 3 controls, why is this?
Dim PanelVerticalPoint As Integer = btDF.Height * 6
For counter = 1 To 50 Step +1
Dim ButtonPanel As Panel = New Panel
With ButtonPanel
ButtonPanel.Location = New Point(0, PanelVerticalPoint)
ButtonPanel.Size = New Size(btDF.Width, btDF.Height)
Me.Controls.Add(ButtonPanel)
ButtonPanel.Controls.Add(btCustomButtonMenu)
ButtonPanel.Controls.Add(btCustomTextBox)
ButtonPanel.Controls.Add(btCustomButton)
End With
PanelVerticalPoint = PanelVerticalPoint + btDF.Height
Next counter
You have to add a new instance of the buttons to each panel. You are adding the same button instance to the panels so each add is really moving the button.
It's not easy to clone a control. It looks like your case might be appropriate for a user control instead. Make the user control in the designer with the buttons and text box, then just create many instances of the user control instead of the panel.
Here's a very similar question with that kind of answer
Clone Winform control

Looping through dynamically created controls

I have a number of dynamically created buttons (buttons generated at run time), and also a number of dynamically created panels. I want to do something similar to the code below although i know that this code will not work.
For i = 1 to NumberOfButtons
button(i).top = panel(i).top
next
The buttons are named button1, button2 etc. And the panels are also named panel1, panel2 etc.
This is all being written in VB Express 2008.
As you generate your buttons and panels, you need to store them into an array. Then you can access it via index like your have in your code sample. Another option would be to do it via Me.Controls("button1") and similar, but I would consider this bad coding.
For i As Integer = 1 To theTopNumber
Me.Controls("button" & i.ToString).Top = Me.Controls("panel" & i.ToString).Top
Next
Would't this work?
For Each btn As Button In Me.Controls
For Each pnl In Me.Controls
btn.Top = pnl.Top
Next
Next
Please (if you are satisfied) up vote or mark as answer since my mysteriously got banned for no particular reason. It might help to re-enable it :/
;)

Run through all the controls in a form

I have a form and I am running through all the controls in that form.
My code is OK and is get all the controls with all their properties.
So for example I have a TabControl with 2 TabPages and 2 textboxes in each tabPage.
The problem is that for the tabPage that isn't selected , the textboxes' property visible is False, although I have set it to True.
I tried to solve this problem with Control.Select and Control.Focus , but Visible is still False:
Private Sub createXML(ByVal cnt As Control, ByVal elem As XElement)
Try
cnt.Select()
cnt.Focus()
Select Case cnt.Controls.Count
Case Is = 0
'Code here to write XElement to an XDocument
'Check Controls properties
Case Is > 0
For Each childCnt As Control In cnt.Controls
childCnt.Select()
childCnt.Focus()
Dim childElem As New XElement(childCnt.GetType.ToString)
Select Case childCnt.Controls.Count
Case Is = 0
'Code here to write XElement to an XDocument
'Check Controls properties
Case Is > 0
createXML(childCnt, childElem)
End Select
Next
End Select
Any ideas?
Note that I don't know what controls I have to run through each time
Your problem in this case is that a TabControl sets everything to invisble unless they are present in the currently selected tabpage. And when you change tab the controls are set to visible and previous ones dissapeares. So how does the tabcontrol keep track of a control that is manually set to visible false, so that it doesn't light up when the tab is changed? Well the visible property is not really based on a boolean value. It's merely an easy way to interpret it for us programmers. Either you see it or you don't, no rules to keep in mind or settings to mess up. Visible or not simple as that.
So what to do with your issue. Basically, my first thought when I see this is that you want to create a "open the program so it looks the same as when it was closed"-function. Which of course is not working properly at the moment since your parser probably set everything to visible=false, which at previously stated means not visible ever. Hence not showing after Tab control page change when loaded.
So solutions:
1. Add a tag to the controls in the tab control. This way you can look for the tag when saving. If it's there, set the visible property to true. (Easy to understand for you when maintaining in the future)
2. Use reflection to get the actual visible state. Look at SO thread and read about reflection: Using control.Visible returns False if it's on a tab page that is not selected (Not so easy to understand when maintaining in future)