I've got a number of panels, each of which with a number of groupboxes containing various type of controls.
Once in a while, I need to update the texts and values of the controls with fresh data, but I would not like to fire up events connected to every control.
So I'd like to removehandlers for each of the involved controls, change the texts and values, and then add the handlers once again.
I know I have to use CallByName, but its syntax is somewhat obscure to me.
Anyone would like to have a look at the provided meta-code below (example for selectedindexchanged methods) and provide the necessary correction, I'd appreciate.
For Each mpanel In Me.Controls
If TypeOf (mpanel) Is MetroPanel Then
For Each gbox In mpanel.controls
If TypeOf gbox Is GroupBox Then
For Each ctrl In gbox.controls
If TypeOf (ctrl) Is MetroComboBox Then
Dim methodName As String = ctrl.name.ToString & "_Selectedindexchanged"
Dim method = CallByName(ctrl, methodName, CallType.Method)
RemoveHandler DirectCast(ctrl, MetroComboBox).SelectedIndexChanged, method
End If
Next
End If
Next
End If
Next
It is possible to remove eventhandler by name by using the following syntax:
For Each mpanel In Me.Controls
If TypeOf (mpanel) Is MetroPanel Then
For Each gbox In mpanel.controls
If TypeOf gbox Is GroupBox Then
For Each ctrl In gbox.controls
If TypeOf (ctrl) Is MetroComboBox Then
Dim methodName As String = ctrl.name.ToString & "_SelectedIndexChanged"
Dim method As System.Reflection.MethodInfo = Type.GetType("MyClass").GetMethod(methodName)
RemoveHandler DirectCast(ctrl, MetroComboBox).SelectedIndexChanged, Addressof method
End If
Next
End If
Next
End If
Next
Related
Some of my codes are from the answers I read here in stack-overflow, one difficulty I am facing is how to check empty controls inside Tab-control with 3 pages? I have created a function that validate if the input is empty:
VB.Net Function:
Public Shared Function ValidateInput(parent As Control)
Dim ctl As Control = Nothing
For Each ctl In parent.Controls
If TypeOf ctl Is TextBox And ctl.Text.Length = 0 Or TypeOf ctl Is ComboBox And ctl.Text.Length = 0 Then
Return ctl.Name
Exit For
'--------------------------------------------------------------------
'(1.) I want this statement to check if the control is type of Tabcontrol ang check for empty input. (I have 3 pages)
ElseIf TypeOf ctl Is TabControl Then
For Each tp As TabPage In frmClientInfo.TabControl1.TabPages
For Each ctr As Control In tp.Controls
If TypeOf ctr Is TextBox OrElse TypeOf ctr Is ComboBox Then
ctl.Name = ctr.Name
Return ctl.Name
Exit For
End If
Next
Next
'--------------------------------------------------------------------
Else
ctl = Nothing
End If
Next
Return ctl
End Function
Usage:
Dim x = Functions.ValidateInput(Me)
Dim ctrlinput As Object = Me.Controls.Find(x, True).FirstOrDefault()
If TypeOf ctrlinput Is TextBox Then
ctrlinput.focus()
ElseIf TypeOf ctrlinput Is ComboBox Then
ctrlinput.DroppedDown = True
ElseIf TypeOf ctrlinput Is TabControl Then 'If control is Tab-control
Dim y = Functions.ValidateInput(Me)
Dim xinput As Object = TabControl1.Find(y, True).FirstOrDefault()
If TypeOf xinput Is TextBox Then
xinput.focus()
ElseIf TypeOf xinput Is ComboBox Then
xinput.DroppedDown = True
End If
Else
MessageBox.Show("saveeee")
Call SaveTransaction()
End If
My condition that deals with the controls outside the tab-control works. But I cant get the controls inside the tab-control pages. I want to focus the control when the function detects an empty control inside the tab-control. Let say the empty control was detected on page 3 the system will automatically jump from page 1 to page 3 and focus the empty control. Any solution? Thanks.
A problem here is that a tab can have another container inside it, just like your tabcontainer contains... tabs. You can bypass this by going recursive, but then your function will return everything it finds, wherever it finds it.
Here's a recursive function which will return you a list of every textbox or combobox without text it finds where you call it:
Private Function GetEmptyControls(current As Control) As List(Of Control)
Dim list As New List(Of Control)
For Each ctrl As Control In current.Controls
If TypeOf ctrl Is ContainerControl Then
'if this control is a container, add every control this function might find inside it to your list
'this is what makes this function recursive
list.AddRange(GetEmptyControls(ctrl))
Else
'add this control if it's a textbox or combobox and there's no text
If ctrl.Text = "" AndAlso (TypeOf ctrl Is TextBox OrElse TypeOf ctrl Is ComboBox) Then
list.Add(ctrl)
End If
End If
Next
Return list
End Function
You can call it on your form or on a container inside your form, or adapt it to suit your needs. Have fun!
I am making a grade application for a school project and I am wondering how I can loop through and check a value in all combo boxes on a certain form, I have 19 units to check; trying to be efficient without making 19 case statements. I have tried an array and me.controls.
Try this :
For Each c As Control In Me.Controls.OfType(Of ComboBox)()
'You cann acces to ComboBox her by c
Next
Note that if you have controls host by containers (TabControl, SPlitPanel, etc.) you may not find all of your controls.
Here is a recursive call that should return all of your controls:
Sub GetControlList(container As Control, ByVal ctlList As List(Of Control))
For Each child As Control In container.Controls
ctlList.Add(child)
If (child.HasChildren) Then
GetControlList(child, ctlList)
End If
Next
End Sub
I have on occasion had problems with the controls on SplitContainer panels so if you use a splitter make sure you are getting all your controls.
Once you have a complete list of controls you can operate on them. This sample is working with DataGridView controls:
Dim ctrls As New List(Of Control)
GetControlList(Me, ctrls)
For Each dgv As DataGridView In ctrls.OfType(Of DataGridView)()
AddHandler dgv.DataError, AddressOf DataGridView_DataError
Debug.Print(dgv.Name)
Next
FYI the generic data error code:
Private Sub DataGridView_DataError(sender As Object, e As DataGridViewDataErrorEventArgs)
Dim dgv As DataGridView = sender
Dim sGridName As String = dgv.Name.Replace("DataGridView", "")
Dim col As DataGridViewColumn = dgv.Columns(e.ColumnIndex)
Dim sColName As String = col.HeaderText
MsgBox(sGridName & vbNewLine & "Column " & sColName & vbNewLine & e.Exception.Message, MsgBoxStyle.Exclamation)
End Sub
You already have the OfType(OF T) method. You use it like this:
ForEach box As ComboBox In MyForm.Controls.OfType(Of ComboBox)()
Next
But this only checks the direct children of your control. If you have container controls like GroupBox, Panels, FlowControlLayoutPanel, etc, you'll miss the controls nested inside them. But we can build a new OfType() method to search these recursively:
Public Module ControlExtensions
<Extension()>
Public Iterator Function OfTypeRecursive(Of T As Control)(ByVal Controls As ControlCollection) As IEnumerable(Of T)
For Each parent As Control In Controls
If parent.HasChildren Then
For Each child As Control In OfTypeRecursive(Of T)(parent.Controls)
Yield child
Next child
End If
Next parent
For Each item As Control In Controls.OfType(Of T)()
Yield item
Next item
End Function
End Module
And you use it the same way:
ForEach box As ComboBox In MyForm.Controls.OfTypeRecursive(Of ComboBox)()
Next
You'll probably want to check containers for controls of the type you're looking for, here's a little function that should do the trick for you.
Private Function GetControls(Of T)(container As Control, searchChildren As Boolean) As T()
Dim Controls As New List(Of T)
For Each Child As Control In container.Controls
If TypeOf Child Is T Then
DirectCast(Controls, IList).Add(Child)
End If
If searchChildren AndAlso Child.HasChildren Then
Controls.AddRange(GetControls(Of T)(Child, True))
End If
Next
Return Controls.ToArray()
End Function
Here's the usage, if search children is True then all child containers will be searched for the control you're looking for. Also, for the top most container we'll just pass in Me assuming you're calling the code from within your Form, otherwise you could pass the Form instance or a specific Panel, GroupBox, etc.
Dim ComboBoxes As ComboBox() = GetControls(Of ComboBox)(Me, True)
I am trying to databind position 0 as "" in a number of dropdownlists, my code below:
For Each ctrl As Control In Me.Controls
If TypeOf ctrl Is DropDownList Then
ctrl.DataBind()
ctrl.items.insert(0, "")
End If
Next
I get error: Error BC30456 'items' is not a member of 'Control'
I am a bit at a loss... Please help!
Thanks Tim, what would the code then be to add the "" to the dropboxes..
DataBind() is not necessary if the dropDownList is already bounded. you can insert values to required index as like the following code, You are getting the error because the Sysetem.web.UI.Controls doesn't have a items. so you can achieve this by casting the ctrl as a DropDownList
For Each ctrl As Control In Me.Controls
If TypeOf ctrl Is DropDownList Then
Dim tempDropDown As DropDownList = DirectCast(ctrl, DropDownList)
tempDropDown.Items.Insert(0, "")
End If
Next
You have to cast the Control to DropDownList,otherwise you cant use properties or methods of the child-class DropDownList but only of Control.
For Each ctrl As Control In Me.Controls
If TypeOf ctrl Is DropDownList Then ' <--- Type-Check
Dim ddl = DirectCast(ctrl, DropDownList) ' <--- Cast
ddl.DataBind()
ddl.items.insert(0, "")
End If
Next
Here's another way using Enumerable.OfType (add Imports System.Linq at the top of the file):
For Each ddl In Me.Controls.OfType(Of DropDownList)()
ddl.DataBind()
ddl.items.insert(0, "")
Next
Control.Controls does not return controls recursively, so not nested child controls. Maybe your DropDownList is in another container control like a GridView, then you wont find it in this way. In this case the correct way would be to handle the RowDataBound-event and use GridViewRow.FindControl("drpSite") to get the reference. If you can't do that or you want to use a recursive way to find all DropDownLists you can use this extension method:
Public Module Extensions
<Runtime.CompilerServices.Extension()>
Public Function OfTypeRecursive(Of T As Control)(ByVal root As Control) As IEnumerable(Of T)
Dim allControls = New List(Of T)
Dim queue = New Queue(Of Control)
queue.Enqueue(root)
While queue.Count > 0
Dim c As Control = queue.Dequeue()
For Each child As Control In c.Controls
queue.Enqueue(child)
If TypeOf child Is T Then allControls.Add(DirectCast(child, T))
Next
End While
Return allControls
End Function
End Module
Now it's simple:
Dim allDdls As IEnumerable(Of DropDownList) = Me.OfTypeRecursive(Of DropDownList)()
I want to list all names of my buttons that begins with "btn" but these buttons are place in different panels. I have this in my mind
dim obj() as object in frmForm.Controls.Find(True,"btn*")
but I think it might be wrong..
First, the first parameter is the name and the second a bool which indicates whether you want to search recursively or not.
Second, there is no builtin way for this. I would use your own method, one like this:
Public Function FindControlStartsWith(root As Control, name As String, recursive As Boolean, comparison As StringComparison) As Control()
If root Is Nothing Then
Throw New ArgumentNullException("root")
End If
Dim controls = New List(Of Control)
Dim stack = New Stack(Of Control)()
stack.Push(root)
While stack.Count > 0
Dim c As Control = stack.Pop()
If c.Name.StartsWith(name, comparison) Then
controls.Add(c)
End If
If recursive Then
For Each child As Control In root.Controls
stack.Push(child)
Next
End If
End While
Return controls.ToArray()
End Function
Use it in this way:
Dim obj() As Control = FindControlStartsWith(Me, "BUT", True, StringComparison.OrdinalIgnoreCase)
I do something similar with the type of control, but it can easily be modified for name. Try the code below:
Private Sub findcontrols(ByVal root As Control)
For Each cntrl As Control In root.Controls
findcontrols(cntrl)
If cntrl.name.startswith("btn") Then
msgbox(cntrl.name)
End If
End Sub
You can make this even more complex by adding parameters for stuff like controlling recursion and such. Keep in mind that if you want to do any control type-specific stuff with it (ie. anything that is in the control that is not inherited from the Control class), you need to CType that object as the appropriate control. So, if .name was only in the Button class, and did not exist in the Control class, I would have to do the following for this to work:
msgbox(ctype(cntrl, Button).name)
My own personal version of it looks more like this:
Private Sub clearcontrols(ByVal root As Control, ByVal ClearLists As Boolean, Optional ByVal ClearTabPages As Boolean = False)
For Each cntrl As Control In root.Controls
clearcontrols(cntrl, ClearLists, ClearTabPages)
If TypeOf cntrl Is TextBox Then
CType(cntrl, TextBox).Clear()
End If
If TypeOf cntrl Is DataGridView Then
CType(cntrl, DataGridView).Rows.Clear()
End If
If TypeOf cntrl Is ListBox And ClearLists = True Then
CType(cntrl, ListBox).Items.Clear()
End If
If TypeOf cntrl Is TabControl And ClearTabPages = True Then
For Each tp As TabPage In CType(cntrl, TabControl).TabPages
If DynTPList.Contains(tp.Name) Then
tp.Dispose()
End If
Next
End If
Next
End Sub
I wrote a function that empty all TextBox in my form:
Private Sub effacer()
For Each t As TextBox In Me.Controls
t.Text = Nothing
Next
End Sub
But I had this problem :
Unable to cast object of type 'System.Windows.Forms.Button' to type
'System.Windows.Forms.TextBox'.
I tried to add this If TypeOf t Is TextBox Then but I had the same problem
The Controls collection contains all controls of the form not only TextBoxes.
Instead you can use Enumerable.OfType to find and cast all TextBoxes:
For Each txt As TextBox In Me.Controls.OfType(Of TextBox)()
txt.Text = ""
Next
If you want to do the same in the "old-school" way:
For Each ctrl As Object In Me.Controls
If TypeOf ctrl Is TextBox
DirectCast(ctrl, TextBox).Text = ""
End If
Next
For Each t As TextBox In Me.Controls
This line right here tries to cast each control to TextBox.
You need to change that to As Control, or use Me.Controls.OfType(Of TextBox)() to filter the collection before iterating it.
Here is a line of code that will clear all radioButtons from the groupBox, which is attached to the button_click:
groupBoxName.Controls.OfType<RadioButton>().ToList().ForEach(p => p.Checked = false);
Use appropriate changes to adapt it to your needs.