Loop through all Checkboxes on a form [duplicate] - vb.net

This question already has answers here:
loop over all textboxes in a form, including those inside a groupbox
(4 answers)
Closed 2 years ago.
I need to loop through all Checkboxes on a form and get the Tag property for each. The Checkboxes are in Groupboxes, and nested Groupboxes. Code I have works for the main form, it is not getting the values from the Checkboxes in the Groupboxes
...
i = 0
For Each ctrl As Control In Me.Controls
If (TypeOf ctrl Is CheckBox) Then
'Resize array
ReDim Preserve g_str_Array(i)
'Add Tag to array
g_str_Array(i) = CStr(ctrl.Tag)
i += 1
End If
Next
...

Here's a method that you can add to a form that provides access to every control on the form via the Tab order:
Public Iterator Function GetControls() As IEnumerable(Of Control)
Dim ctrl = GetNextControl(Me, True)
Do Until ctrl Is Nothing
Yield ctrl
ctrl = GetNextControl(ctrl, True)
Loop
End Function
Because that is an iterator, you can chain other LINQ methods to it. To get the Tag of each CheckBox into an array:
Dim checkBoxTags = GetControls().OfType(Of CheckBox)().
Select(Function(cb) CStr(cb.Tag)).
ToArray()
If you wanted to use this method for multiple forms then, rather than repeating the code in each of them, you can add a single extension method:
Imports System.Runtime.CompilerServices
Public Module FormExtensions
<Extension>
Public Iterator Function GetControls(source As Form) As IEnumerable(Of Control)
Dim ctrl = source.GetNextControl(source, True)
Do Until ctrl Is Nothing
Yield ctrl
ctrl = source.GetNextControl(ctrl, True)
Loop
End Function
End Module
and then call it in each form as though it were a member.

Related

Change Label Text with Controls in VB

I'm trying to change a Label Text with Controls command, using this line
Controls("C_" & 0).Text = "Conta:"
But I get this error
"System.NullReferenceException"
If I delete this label and change it for a textbox (with same name "C_0"), it works! But I need to do this with a Label not a textbox...
This is because you do not have a control named C_0. I would suggest using ControlCollection.Find to get your control and then use a conditional If statement to check if the returned control exists:
Dim desiredControls() As Control = Me.Controls.Find("C_" & 0, True)
If desiredControls.Count = 0 Then
'No controls named C_0 found
ElseIf desiredControls.Count > 1 Then
'Multiple controls named C_0 found
Else
desiredControls(0).Text = "Conta:"
End If
Or if you simply wanted a one-liner then you would use:
Me.Controls.Find("C_" & 0, True).First().Text = "Conta:"
However, I would highly recommend that you use the conditional If statements so that you an exception isn't thrown if 0 controls are found.
Ok, I find the problem... This command wasn't working because it was inside a GroupBox.
Then the right code is
Me.Controls("GroupBox1").Controls("C_" & 0).Text = "123"
Thanks for everyone help!
Your problem is control.Controls only returns the controls directly inside the control. So you can use these extension methods. Put this in a Module:
<Extension>
Public Function ChildControls(parent As Control) As IEnumerable(Of Control)
Return ChildControls(Of Control)(parent)
End Function
<Extension>
Public Function ChildControls(Of TControl As Control)(parent As Control) As IEnumerable(Of TControl)
Dim result As New List(Of TControl)
For Each ctrl As Control In parent.Controls
If TypeOf ctrl Is TControl Then result.Add(CType(ctrl, TControl))
result.AddRange(ctrl.ChildControls(Of TControl)())
Next
Return result
End Function
Here is how you would use it:
' general option to return all controls, filter on name
Me.ChildControls().Single(Function(c) c.Name = "C_" & 0)).Text = "Conta:"
' generic option to return only Labels, filter on name
Me.ChildControls(Of Label)().Single(Function(c) c.Name = "C_" & 0)).Text = "Conta:"
This will work whether the Label is in the GroupBox or not, and if you move it to a different GroupBox, Panel, or back to the Form, without needing to change your code.

Grouping form controls using dictionary, collection or class

I am trying to categorize/group my form controls in order to be able to apply changes to several of them in one go. For instance, I may want to enable/disable them.
Should I add the form controls to collections, dictionaries or should I create classes?
Ideally I would like to create categories and sub-categories. I would define properties for the categories and the sub-categories. The properties of the categories would be passed to the "child" sub-categories.
For instance, if the font of CategoryA is "arial" the font of subcategories A1, A2 ect would also be "arial".
I would only "store" the objects in the sub-categories. If you have ideas how I could such architecture please make some suggestions.
At this stage I have created a dictionary. I am quite sure that I cannot create the categories/sub-categories I would like to with dictionaries and collections but it's still a first step in the right direction (bulk changes).
The problem I am facing with dictionaries is that the properties/methods specific to the controls do not display in the IntelliSense. How can I make them available?
Public dct As New Dictionary(Of Object, Integer)
Dim ctlr As Control
Dim i As Integer
i = 1
For Each ctlr In Controls
dct.Add(ctlr.Name, i)
i = i + 1
Next
For Each Item In dct
'Enabled is not available
Item.Enabled = False
Next
I would dump the dictionary and use a List(Of T). Add the actual object to the list and the properties should be available. In any case here is the dictionary code. Comments and explanation in-line.
Public dct As New Dictionary(Of String, Integer)
'I changed Object to String because that is what you are
'adding to the dictionary. I don't see where the .Value
'is ever used so I suggest changint to List(Of Button) or List(Of Control)
Private Sub OpCode2()
Dim ctlr As Control
Dim i As Integer
i = 1
'This saves the name of the control to the key of the dictionary
'This is just a string unrelated to a control as far as the dictionary knows
For Each ctlr In Controls
dct.Add(ctlr.Name, i)
i = i + 1
Next
For Each Item As KeyValuePair(Of String, Integer) In dct
'The .Find method returns a control using the control name
Dim ctrl As Control = Controls.Find(Item.Key, True).FirstOrDefault()
'The properties inherited from Control will be available in intellisense
'If you need a property of a particular type of control
'you will need to cast ctrl to the type you need
ctrl.Enabled = False
Next
End Sub
The following code uses List(Of T) and is much shorter and easier to read.
Public lst As New List(Of Control)
Private Sub OpCode2()
Dim ctlr As Control
For Each ctlr In Controls
lst.Add(ctlr)
Next
For Each Item In lst
'The list actually contains a reference to the control
'so properties of Control are available
Item.Enabled = False
Next
End Sub

Loop - set Textboxes to ReadOnly in all Groupboxes

I have groupboxes that contain textboxes and I want to set all of them to ReadOnly=True. Here is what I tried (doesn't work):
EDIT (I forgot about Split container):
For Each SplitCon As Control In Me.Controls
If TypeOf SplitCon Is SplitContainer Then
For Each GBox As Control In SplitCon.Controls
If TypeOf GBox Is GroupBox Then
For Each ctrl As Control In GBox.Controls
If TypeOf (ctrl) Is TextBox Then
CType(ctrl, TextBox).ReadOnly = True
End If
Next
End If
Next
End If
Next
You can simplify things a great deal. Rather than looking thru all sorts of Control collections, create an array to act as a ToDo list. Then you can get rid of all the TypeOf and CType in the loop used.
' the ToDo list
Dim GrpBoxes = New GroupBox() {Groupbox1, Groupbox2,
Groupbox3, Groupbox4}
For Each grp In GrpBoxes
For Each tb As TextBox In grp.Controls.OfType(Of TextBox)()
tb.ReadOnly = True
Next
Next
Your code no longer depends on the form layout of the moment. The only thing you have to remember is to add any new GroupBox items to your list. You can also declare the array once ever for the whole form if you prefer (or even in the For Each statement)
Rather than working with Control objects, Controls.OfType(Of T) filters the collection and returns an object variable of that type, so there is no need to cast it or skip over controls you are not interested in. You can also tack on a Where method to further refine the list to include only do those with a certain name or Tag.

Handle containers inside containers in for each control vb 2008

I have created a function to translate my forms. I can loop through every control in a form to call this function, but I have made a situation, I cannot handle.
In one of my forms, I have groupbox in a groupbox.
This source works if I only have one groupbox.
Public Function translate_form(ByVal form As Form)
Dim control As Object
Dim controlname As String
form.Text = Get_Control_Name(form.Name, "Form")
Try
For i = 0 To form.Controls.Count - 1
control = form.Controls(i)
If TypeOf (control) Is MenuStrip Then
For j = 0 To control.items.count - 1
control.items(j).text = Get_Control_Name(form.Name, "MenuItem" & j)
Next
Else
controlname = Get_Control_Name(form.Name, control.Name)
control.Text = IIf(controlname Is Nothing, control.Text, controlname)
If TypeOf (control) Is GroupBox Then
For j = 0 To control.Controls.Count - 1
controlname = Get_Control_Name(form.Name, control.Controls(j).Name)
control.Controls(j).Text = IIf(controlname Is Nothing, control.Controls(j).Text, controlname)
If TypeOf (control.Controls(j)) Is Button Then
control.Controls(j).AutoSize = True
End If
Next
End If
If TypeOf (control) Is Button And UCase(control.Text) <> "X" Then
control.AutoSize = True
End If
End If
Next
Catch ex As Exception
MsgBox(ex.Message, MsgBoxStyle.Critical)
End Try
End Function
But in some cases I want to sperate controls inside a container. I could have one more loop if the
control.Controls(j)
is a groupbox but I want to make this function to handle any kind of "container pyramid", if you know what I mean. Maybe I will have a container which has one also and that one too etc... Or is there any control I can use as a groupbox but it doesn't count as a contaier, so I can see it with:
form.Controls
Any suggestions?
Thanks in advance.
The reason why your code does not deliver what you want is that you are not performing a recursive search of controls. Bear in mind that Form.Controls contains only the parent controls (not the children controls eventually contained by the parents; like the situation you refer of controls contained by a GroupBox). Additionally, I see various not-too-right issues (you should writeOption Strict On on the top of your file) and that's why this answer intends to provide you with a somehow better framework to work with (you have just to fill in the blanks with your code):
Public Sub translate_form2(ByVal form As Form)
Try
For Each ctrl As Control In form.Controls
actionsCurrentControl(ctrl)
recursiveControls(ctrl)
Next
Catch ex As Exception
End Try
End Sub
'Accounting for all the child controls (if any)
Public Sub recursiveControls(parentControl As Control)
If (parentControl.HasChildren) Then
For Each ctrl As Control In parentControl.Controls
actionsCurrentControl(ctrl)
recursiveControls(ctrl)
Next
End If
End Sub
Public Sub actionsCurrentControl(curControl As Control)
If TypeOf curControl Is MenuStrip Then
Else
If TypeOf (curControl) Is GroupBox Then
End If
If TypeOf (curControl) Is Button And UCase(curControl.Text) <> "X" Then
End If
End If
End Sub
translate_form2 iterates through all the parent controls as in your code (but by relying on a set of Subs (you are wrongly using a Function without returning any value, what is wrong), making the structure more adaptable); it also calls recursiveControls (which also calls itself for each control it analyses) to take care of any child control which might be present. I am also including actionsCurrentControl which contains all the actions to perform for each control (you have to populate it with your code).

What is wrong with this LINQ code?

First, I have function to flatten all controls on a Control:
Protected Function GetAllControls(Optional ownerControl As Control = Nothing) As IEnumerable(Of Control)
Dim ret = New List(Of Control)()
For Each child As Control In If(ownerControl, Me).Controls
ret.AddRange(GetAllControls(child))
Next
ret.Add(ownerControl)
Return ret
End Function
Then, I want to hide certain buttons on a control using this code:
Dim buttons = GetAllControls().Where(Function(c) c.Name.StartsWith("subButton"))
For Each ctrl As Control In buttons
ctrl.Visible = False
Debug.WriteLine("Hid button " & ctrl.Name)
Next
Yet, after four buttons - the correct count - have been hidden, I get a NullReferenceException, with VS2012 highlighting the lambda expression.
What could possibly cause this?
The last line in your first function adds ownerControl, which is null the first time you call it, so its adding a Nothing to the list. In your lambda you're doing a c.Name which will throw an exception when c is Nothing.