Set the order in which form controls are looped through - vb.net

I'm looping through all of the controls on a certain Tab Page and the grabbing the .tag property for some further actions from checkboxes that are checked.
However, I require these controls to be looped in a certain order (I was hoping they'd be ordered by tab index), but it appears that they are not.
Is there any way to force the order that they are looped? Thanks.
Dim objCtrl As Control
For Each objCtrl In Me.objConfigForm.tabPageGeneral.Controls
If TypeOf objCtrl Is CheckBox AndAlso DirectCast(objCtrl, CheckBox).Checked Then
Dim strProp As String = DirectCast(objCtrl, CheckBox).Tag
Dim strListItem As String = CallByName(objUser, strProp, CallType.Get)
lstGeneral.Add(strListItem)
End If
Next

Use LINQ to sort the controls by TabIndex, like this:
For Each objCtrl As Control In Me.Controls.Cast(Of Control).OrderBy(Function(c) c.TabIndex)
If TypeOf objCtrl Is CheckBox AndAlso DirectCast(objCtrl, CheckBox).Checked Then
Dim strProp As String = DirectCast(objCtrl, CheckBox).Tag
Dim strListItem As String = CallByName(objUser, strProp, CallType.Get)
lstGeneral.Add(strListItem)
End If
Next
Note: The above code requires the System.Linq namespace be imported into your code file to support the Cast() and OrderBy() LINQ extension methods.

Use the .OrderBy() extension to choose an order. Also, while we're at it, you can replace the If block with Where() and OfType() extensions to reduce nesting and casting:
For Each box As CheckBox In Me.Controls.OfType(Of CheckBox)() _
.Where(Function(b) b.Checked) _
.OrderBy(Function(b) b.TabIndex)
lstGeneral.Add(CallByName(objUser, box.Tag, CallType.Get))
Next
Update: this is a bit old, but it came back into my feed today, so it's still indexed by Google somewhere. With that in mind, today I'd write the code like this instead:
Dim selectedCheckboxNames = Me.Controls.
OfType(Of CheckBox)().
Where(Function(b) b.Checked).
OrderBy(Function(b) b.TabIndex).
Select(Function(b) CallByName(objUser, b.Tag, CallType.Get))
lstGeneral.AddRange(selectedCheckboxNames)
One of the important change is introducing a variable for the result of the linq query. This can sometimes add valuable insight to code for future developers, and it always helps make the For Each loop easier to read... assuming you haven't also been able to convert to an AddRange(), as I've done here. I'm also more comfortable now taking advantage of implicit line continuations and Option Infer.

Related

How do I select specific variables based on checkbox state as I iterate through a For Each

I'm working on a project that requires I iterate through a list of controls on a tabpage to find all of the checkboxes. Then depending on the state of the box (checked or unchecked) select individual variables (filenames) to then perform either a batch rename or delete of files on the filesystem (cb.checked = perform action).
I have managed to create the "for each" for the iteration of the controls (thanks google) but I'm struggling to figure out how to pick the variables. They are all named differently, obviously, as are the checkboxes. Also the checkboxes are statically assigned to the form/tabpage. Here's what I have at the moment.
Public Sub delBut_code(ByRef fname As String)
If (Sanity = 1) Then
For Each cb As Control In Form1.Controls
If TypeOf cb Is CheckBox AndAlso DirectCast(cb,
CheckBox).Checked Then
If My.Computer.FileSystem.FileExists(fname) Then
My.Computer.FileSystem.DeleteFile(fname)
End If
End If
Next
MessageBox.Show("All Actions Completed Successfully")
Else
MessageBox.Show("Please select a File To Delete")
End If
End Sub
and here is an example of some of the variables:
Dim castle As String = selPath & "\zm_castle_loadingmovie.txt"
Dim factory As String = selPath &
"\zm_factory_load_factoryloadingmovie.txt"
Dim island As String = selPath & "\zm_island_loadingmovie.txt"
N.B selpath collects a user entered folder path and can be ignored here
I would really appreciate any pointers.
First, you can do much better with the loop:
Public Sub delBut_code(ByRef fname As String)
If Sanity <> 1 Then
MessageBox.Show("Please select a File To Delete")
Exit Sub
End If
Dim checked = Form1.Controls.OfType(Of CheckBox)().Where(Function(c) c.Checked)
For Each box As CheckBox in checked
Try
'A file not existing is only one reason among many this could fail,
' so it needs to be in a Try/Catch block.
' And once you're using a Try/Catch block anyway,
' the FileExists() check becomes a slow and unnecessary extra trip to the disk.
My.Computer.FileSystem.DeleteFile(fname)
Catch
'Do something here to let the user know it failed for this file
End Try
Next
MessageBox.Show("All Actions Completed")
End Sub
But now you need to know how have the right value in that fname variable. There's not enough information in the question for us to fully answer this, but we can give some suggestions. There a number of ways you could do this:
Set the Tag property in the Checkboxes when you build the string variables. Then fname becomes DirectCast(box.Tag, String).
Inherit a custom control from CheckBox to use instead of a normal Checkbox that has an additional String property for the file name. Set this property when you build the string variables.
Name your string variables in a way that you can derive the string variable name from the CheckBox variable name, and then use a Switch to pick the right string variable from each box.Name.
Keep a Dictionary(Of CheckBox, String) that maps the Checkboxes to the right string values.
But without knowing more context of the application, I hesitate to recommend any of these over the others as best for your situation.

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.

Convert Control Using Cast Not Working

i have a control named SuperValidator1 on every form with SuperValidator type. i want to find this control and enable it using its name because the name is consistent in all forms. so this is the code i came up with :
Dim validator As SuperValidator
Dim frm As Form = Me.ParentForm
Dim ctrl As Control()
ctrl = frm.Controls.Find("SuperValidator1", True)
Dim singleCtrl As Control = ctrl(0)
validator = TryCast(singleCtrl, SuperValidator) '< ERROR LINE
it throws editor error : Value of Type 'Control' cannot be converted to 'SuperValidator'
i tried CType and DirectCast but it is the same. according to this i should be able to cast any data type. what is wrong and what should i do ?
btw SuperValidator is from DevComponents.DotNetBar.Validator
thanks
Since SuperValidator is a component you must get it from your form's component collection. However at runtime components don't seem to inherit a name, so finding the exact one might be tricky.
As far as I know your only options are:
A) Get the first SuperValidator you can find, or
B) Match its properties (if possible).
Either way you do it you must iterate through the Me.components.Components collection:
Dim validator As SuperValidator = Nothing
For Each component In Me.components.Components
If component.GetType() Is GetType(SuperValidator) Then
validator = DirectCast(component, SuperValidator)
'Perform additional property checking here if you go with Option B.
End If
Next
Here is a test that uses a control I have on a form. Changed your logic slightly. Give it s try and see what results you have.
Dim validator As RichTextBox ' SuperValidator
Dim frm As Form = Me ' .ParentForm
Dim ctrl() As Control = frm.Controls.Find("RichTextBox1", True) ' ("SuperValidator1", True)
If ctrl.Length > 0 Then
validator = TryCast(ctrl(0), RichTextBox) ' , SuperValidator) < ERROR LINE
Else
Stop
End If

Set a group of controls visible with one line of code?

Is it possible to clump a group of controls together and be able to set it visible with one line rather than having to do each individual control's .visible property? I know it doesn't hurt anything but would like to keep it looking neat and not clump up a function with a page full of .visible control calls.
Just group your controls in a List(Of Control) or an array and set the Visible property using either the ForEach-method or a simple For Each-loop.
e.g.:
Dim toToggle = {OkButton, CancelButton, ControlPanel, SelectionComboBox}
For Each ctrl in toToggle
ctrl.Visible = False
Next
or
Dim toToggle = {OkButton, CancelButton, ControlPanel}.ToList()
toToggle.ForEach(Sub(c) c.Visible = False)
I like Dominic's solution. Another approach (and this depends on how your Winform is laid out) would be to group the controls into a panel:
For Each ctrl as Control in MyPanel.Controls
c.Visible = False
Next
Really all this approach does is keeps you from having to create a new list, but maybe that would be better so you can choose precisely which controls to add.

How to find the TypeOf a VB6 control in VB.NET

I am writing a .NET DLL to iterate through all controls in the a VB6 Form passed byref.
So far it seems to work VB.NET code:
Public Sub AddFormRefLegacy(ByRef strAppName As String, ByRef objForm As Object)
'update the forms caption
objForm.Caption = FindValue(strAppName, objForm.Name, "", "0", objForm.Caption)
'iterate through all the controls on the form
For Each ctl As Object In objForm.Controls
if TypeOf ctl is Label then
'this doesn't pick up any labels
end if
Next
End Sub
Called from this VB6 code:
Dim libDD As New Lib.clsDataDictionary
libDD.AddFormRefLegacy "nnne", Me
but the TypeOf operator does not work. Is there another way to find the type of control?
Could it be you're comparing two different "Label" type objects.
You haven't qualified the LABEL type in the IF TYPEOF line, so you could be comparing a VB6 label to a .net label, and they wouldn't be the same.
You could use TYPENAME, but that might not be exactly what you need iether. I'd make sure you're really comparing the types that you think you're comparing.
Have you tried using the TypeName function? Does it return anything useful for TypeName(ctl)?