I have 44 text boxes on a screen (to be precise, RadNumericTextBoxes but that's not germane). They follow a common naming pattern (rntb_[NameOfDBField]) which can't be programmatically replicated.
How can I set .Value to Nothing for each control which has a name ^= rntb_? I have attempted the following:
Private Sub ClearValues()
For Each c as Control in Controls
If TypeOf c Is RadNumericTextBox Then
TryCast(c, RadNumericTextBox).Value = Nothing
End If
Next
End Sub
However, Controls.Count = 1 and contains just the name of the master page.
Do I need to pass an argument to Controls, or do I need to do something else altogether? It's "only" 44 text boxes so I could clear each one manually but I'd rather do it programmatically if possible.
If the RadNumericTextBoxes are on the Form and not in a container, then something along the lines of
Private Sub ClearValues()
For Each c As Control In Me.Controls
If TypeOf c Is RadNumericTextBox Then
Dim rntb = DirectCast(c, RadNumericTextBox)
If rntb.Name.StartsWith("rntb_") Then
rntb.Value = Nothing
End If
End If
Next
End Sub
But if they are in, say, GroupBox1 then you would replace Me.Controls in the above with GroupBox1.Controls.
And what is the End For in your code? A For..Next loop has Next at the end of its body in VB.NET.
Related
I'm dealing with this issue. I have a userform that addapts after the user enters a value (for example, if he wants to add 3 members, the user form creates 3 different pages with the same boxes, after renaming them).
I have this right now:
After pressing "Lanzar", the userform will create more pages with "Datos Educando 2", "Datos Educando 3"... and so on, taking the value introduced on "Educandos a inscribir:"
The code is the following:
Private Sub lanzar_numero_educandos_Click()
Dim l As Double, r As Double
Dim ctl As Control
Me.MultiPage1.Pages(1).Visible = True
If Me.MultiPage1.Pages.Count > 2 Then
For a = Me.MultiPage1.Pages.Count - 1 To 2 Step -1
Me.MultiPage1.Pages.Remove a
Next a
End If
Me.MultiPage1.Pages(1).Visible = True
If educandos_a_inscribir.Value <> 1 Then
For a = 1 To educandos_a_inscribir.Value
MultiPage1.Pages.Add
MultiPage1.Pages(a).Controls.Copy
MultiPage1.Pages(a + 1).Paste
For Each ctl In MultiPage1.Pages(a).Controls
If TypeOf ctl Is MSForms.Frame Then
l = ctl.Left
r = ctl.Top
Exit For
End If
Next
For Each ctl In MultiPage1.Pages(a + 1).Controls
If TypeOf ctl Is MSForms.Frame Then
ctl.Left = l
ctl.Top = r
Exit For
End If
Next
Me.MultiPage1.Pages(a + 1).Caption = "Datos Educando " & a
Next a
Me.MultiPage1.Pages(1).Visible = False
End If
End Sub
Now, the problem I have is that the pages generated with this code have random names on each TextBox, so I'm not able to locate the information introduced from the user.
For example, this is the first page (the one that has the names I already know):
Here, the TextBox "Nombre Educando" is called "nombre_educando_1", so I can locate it easily on code:
The Textboxes created when copying the first page, have random names (like "TextBox 34", "TextBox 35"... and so on), so I'm not able to controll how are they called.
There is a way of generate the pages editing the TextBox names? Like, for example, for the second page generated, the TextBox in the example above should be "nombre_educando_2" and so on.
Thanks!
Each control on the first page has an attribute named Tag. You can value these with unique and meaningful values. When you paste the controls to the new page the Tag values will follow. Then as you loop through the controls on any page, use a Select Ctl.tag statement to identify what to do with the value of the control.
I know the title is a bit confusing so let me make myself as clear as possible.
In an Access form (2010), I have a set of text fields meant to contain dates. Those fields are all optional, the user can fill in any number of dates.
To make it more user-friendly, I want to associate each field to a toggle button. Then I want 2 things to happen :
On CURRENT event :
If a text field has a value, then it is visible and the toggle button
associated with it is pressed.
If a text field is empty, then it is not visible and the toggle button isn't pressed.
On CLICK of a toggle button :
If the text field associated with it has a value, then this field gets cleared (and invisible) and the toggle button gets de-pressed ;
If the text field associated with it is empty, then the focus is set on it and the toggle button gets pressed (and stay that way if a value is entered in the text field, otherwise everything gets back the way it was, invisible and unpressed).
So far I've achieved the first step by setting two collections of controls based on some examples I found online.
So, when the form is loaded, I call this :
Private mcolGroupTxToggle As New Collection
Private mcolGroupBuToggle As New Collection
Private Sub InitializeCollections()
Dim ctl As Control
If mcolGroupTxToggle.Count = 0 Then
For Each ctl In Me.Controls
If ctl.Tag = "txtoggle" Then
mcolGroupTxToggle.Add ctl, ctl.Name
End If
Next ctl
Set ctl = Nothing
End If
If mcolGroupBuToggle.Count = 0 Then
For Each ctl In Me.Controls
If ctl.Tag = "butoggle" Then
mcolGroupBuToggle.Add ctl, ctl.Name
End If
Next ctl
Set ctl = Nothing
End If
End Sub
And on Form_Current event, I call that :
Private Sub OnLoadToggles(mcol As Collection, pcol As Collection)
Dim ctl As Control
Dim btn As Control
Dim strBtn As String
For Each ctl In mcol
'Every button has the same name than the textbox + "b"
strBtn = ctl.Name & "b"
For Each btn In pcol
If btn.Name = strBtn Then
If IsNull(ctl) Then
ctl.Visible = False
btn.Value = False
Else
ctl.Visible = True
btn.Value = True
End If
End If
Next btn
Next ctl
Set ctl = Nothing
End Sub
Everything works well so far, but I'm not sure that's the best way to do it and I figured I would need to repeat some lines in step 2.
Making the distinction between text boxes and buttons in the procedure seems weird, I feel like it should be done prior so that I don't have to do it in every procedure. I also feel like it would be better to loop through each pair of controls (text + button) instead of each control in both collections.
Basically, I'm wondering if it would be (1) better and (2) possible to have something as simple as this :
Private Sub OnLoadToggles(Collection of pairs)
for each [pair of txt and btn]
if isnull(txt) Then
txt.visible = false
btn.value = false
else
...
end if
...
My guess is that I would need to make a public sub where I set a collection of pairs of buttons and text fields based on their tags (there are other controls in my form that need to be left alone) and names, but I'm not sure how, I'm a beginner in VBA.
Any suggestions please ?
-- Edit step 2 --
Thanks to Andre's answer the second part was easier than I thought. I've updated my sample database. So on click events I call this :
Private Sub ClickToggles()
Dim ctl As Control
Dim btn As Control
Dim strBtn As String
Dim strCtl As String
strBtn = Me.ActiveControl.Name
strCtl = Left(strBtn, Len(strBtn) - 1)
Set ctl = Me(strCtl)
Set btn = Me(strBtn)
If IsNull(ctl) Then
btn.Value = True
ctl.Visible = True
ctl.Enabled = True
ctl.SetFocus
Else
ctl.Value = ""
btn.Value = False
ctl.Visible = False
End If
End Sub
It's not perfect but it works. Probably not a good idea to clear the data at this point because misclicks may happen. It would be better to loop through the textboxes right before saving the form and clear values of invisible and/or disabled controls from the collection. I might to that later.
I had to add the .enabled property next to the .visible one because on lostfocus events I was getting an error saying the control was still active so it couldn't make it not visible.
Right now I'm more concerned about the amount of click and lost focus events. I'd rather have some public functions and event handlers dealing with it but it's getting too complicated for me. I'll get back to it when I know more about... everything.
Suggestions are still welcome anyway =) !
Since your control pairs are "paired" by their name anyway, you don't need any fancy constructs, or even the second collection. Just address the matching control directly by its name.
Instead of using If/Else for setting boolean properties, it is usually easier to assign a variable to the property.
Private Sub OnLoadToggles(mcol As Collection)
Dim ctl As Control
Dim btn As Control
Dim strBtn As String
Dim bShowIt As Boolean
For Each ctl In mcol
'Every button has the same name than the textbox + "b"
strBtn = ctl.Name & "b"
' If you have the control name, you can get the control directly:
Set btn = Me(strBtn)
' Using a variable you don't need If/Else
bShowIt = Not IsNull(ctl.Value)
ctl.Visible = bShowIt
btn.Value = bShowIt
Next ctl
' Note: this is not necessary for local variables - they go automatically out of scope
Set ctl = Nothing
End Sub
I am using Ms-Access and I created a userform which has a number of Textboxes on it. The boxes are named: Box1, Box2, Box3 ...
I need to loop through all boxes, but I don't know which is the last one. To avoid looping through all userform controls I thought of trying the following:
For i =1 To 20
If Me.Controls("Box" & i).value = MyCondition Then
'do stuff
End If
Next i
This errors at Box6, which is the first box not found. Is there a way to capture this error and exit the loop when it happens.
I know I could use On Error but I 'd rather capture this specific instance with code instead.
Thanks,
George
A Controls collection is a simplified collection of controls (obviously) and share a same order as a placement order of controls.
First of all, even a creatable collection object lacks methods such as Exists or Contains , hence you need a function with error handling to checking/pulling widget from a collection.
Public Function ExistsWidget(ByVal Name As String) As Boolean
On Error Resume Next
ExistsWidget = Not Me.Controls(Name) Is Nothing
On Error GoTo 0
End Function
If you really doesnt like "ask forgiveness not permission" option you can pull entire ordered collection of your textboxes (and/or check existance by name in another loop with similar logic).
Public Function PullBoxes() As Collection
Dim Control As MSForms.Control
Set PullBoxes = New Collection
For Each Control In Me.Controls
If TypeOf Control Is MSForms.TextBox And _
Left(Control.Name, 3) = "Box" Then
Call PullBoxes.Add(Control)
End If
Next
End Function
Since names of widgets are unique - you can return a Dictionary from that function with (Control.Name, Control) pairs inside and able to check existance of widget by name properly w/o an error suppression.
There's a good guide to Dictionary if it's a new information for you.
Anyway, no matter what object you choose, if user (or code) is unable to create more of thoose textboxes - you can convert this Function above to a Static Property Get or just to a Property Get with Static collection inside, so you iterate over all controls only once (e.g. on UserForm_Initialize event)!
Public Property Get Boxes() As Collection
Static PreservedBoxes As Collection
'There's no loop, but call to PullBoxes to reduce duplicate code in answer
If PreservedBoxes Is Nothing Then _
Set PreservedBoxes = PullBoxes
Set Boxes = PreservedBoxes
End Property
After all, the last created TextBox with name Box* will be:
Public Function LastCreatedBox() As MSForms.TextBox
Dim Boxes As Collection
Set Boxes = PullBoxes
With Boxes
If .Count <> 0 Then _
Set LastCreatedBox = Boxes(.Count)
End With
End Function
I think that now things are clearer to you! Cheers!
Note: All code are definitely a bunch of methods/properties of your form, hence all stuff should be placed inside of form module.
Long story short - you cannot do what you want with VBA.
However, there is a good way to go around it - make a boolean formula, that checks whether the object exists, using the On Error. Thus, your code will not be spoiled with it.
Function ControlExists(ControlName As String, FormCheck As Form) As Boolean
Dim strTest As String
On Error Resume Next
strTest = FormCheck(ControlName).Name
ControlExists = (Err.Number = 0)
End Function
Taken from here:http://www.tek-tips.com/viewthread.cfm?qid=1029435
To see the whole code working, check it like this:
Option Explicit
Sub TestMe()
Dim i As Long
For i = 1 To 20
If fnBlnExists("Label" & i, UserForm1) Then
Debug.Print UserForm1.Controls(CStr("Label" & i)).Name & " EXISTS"
Else
Debug.Print "Does Not exist!"
End If
Next i
End Sub
Public Function fnBlnExists(ControlName As String, ByRef FormCheck As UserForm) As Boolean
Dim strTest As String
On Error Resume Next
strTest = FormCheck(ControlName).Name
fnBlnExists = (Err.Number = 0)
End Function
I would suggest testing the existence in another procedure per below: -
Private Sub Command1_Click()
Dim i As Long
i = 1
Do Until Not BoxExists(i)
If Me.Conrtols("Box" & i).Value = MyCondition Then
'Do stuff
End If
i = i + 1
Next
End Sub
Private Function BoxExists(ByVal LngID As Long) As Boolean
Dim Ctrl As Control
On Error GoTo ErrorHandle
Set Ctrl = Me.Controls("BoX" & LngID)
Set Ctrl = Nothing
BoxExists = True
Exit Function
ErrorHandle:
Err.Clear
End Function
In the above, BoxExists only returns true if the box does exists.
You have taken an incorrect approach here.
If you want to limit the loop, you can loop only in the section your controls reside e.g. Detail. You can use the ControlType property to limit controls to TextBox.
Dim ctl As Control
For Each ctl In Me.Detail.Controls
If ctl.ControlType = acTextBox Then
If ctl.Value = MyCondition Then
'do stuff
End If
End If
Next ctl
I believe the loop will be faster than checking if the control name exists through a helper function and an On Error Resume Next.
But this only a personal opinion.
I have a DropDown and a DropDownList on my form. I am aware that a DropDown can hold a placeholder text and a DropDownList cannot, however; I would like some code or a work around to allow either:
DropDown as read-only, therefore not allowing a user to type
But preferably a DropDownList with placeholder text (context menu option, or not)
Is this possible?
Thanks.
But preferably a DropDownList with placeholder text (context menu option, or not)
By definition, the text displayed in this control is always the text of the selected item. You can add a "fake" item to the list if you want (e.g. "Select Property Code"), but you will have to check that this item isn't selected later.
To display one of the items (fake or not), simply set the SelectedIndex to the appropriate value once the list is loaded.
DropDown as read-only, therefore not allowing a user to type
This will actively make sure that the text is either in the list or a default value, effectively making it read only. (Written for a ComboBox, but the behavior should be identical with a DropDown.)
Private Sub ComboBox1_TextChanged(sender As Object, e As EventArgs) Handles ComboBox1.TextChanged
Static recursion As Boolean = False
Dim defaultText As String = "My Default Text"
If recursion Then
recursion = False
Else
If ComboBox1.Items.Count > 0 Then
For i As Integer = 0 To ComboBox1.Items.Count - 1
If ComboBox1.Items(i).ToString = ComboBox1.Text Then
Exit Sub
End If
Next
recursion = True
ComboBox1.Text = defaultText
End If
End Sub
Alternatively, here is a sub I call whenever a "strict" ComboBox looses focus to accomplish the same thing. The difference is that doing it this way allows you to keep the AutoComplete functionality:
Public Sub EnforceList(ByRef box As ComboBox) 'FORCES .TEXT TO BEST (OR FIRST) MATCH IN .ITEMS
'If list contains item whose name begins another item's, the shorter must be listed first, e.g. "sew" must preceed "sewer"
If box.Items.Count = 0 Then Exit Sub 'Can't enforce a list that doesn't exist
Dim txt As String = box.Text
Do
For i As Integer = 0 To box.Items.Count - 1
If box.Items(i).ToString Like txt & "*" Then
box.Text = box.Items(i).ToString
Exit Sub
End If
Next
txt = Left(txt, Len(txt) - 1)
Loop
End Sub
Probably a silly question with a simple answer but I am a real novice when it comes to userforms.
I have "Frame 3" with 5 different option buttons (Dest1, Dest2, Dest3, Dest4, Dest5) After an option is selected, where is the caption value of the selected option stored? How can I access that with vba.
Thank you,
Josh
Here's just some example code you can use. Add your Option Buttons to groups, and then you can go from there. I used groups since you had multiple frames, and you can check based on group, and have multiple groups, and check which one's selected for each group.
Private Sub CommandButton1_Click()
Dim x As Control
' Loop through ALL the controls on the UserForm.
For Each x In Me.Controls
' Check to see if "Option" is in the Name of each control.
If InStr(x.Name, "Option") Then
' Check Group name.
If x.GroupName = "Grp1" Then
' Check the status of the OptionButton.
If x.Value = True Then
MsgBox x.Caption
Exit For
End If
End If
End If
Next
End Sub
You can also access the option buttons through the frame-ojbect that holds them (if you have other frames and controls you don't want to go through):
Option Explicit
Sub Test()
Dim oCtrl As Control
'***** Try only controls in Frame3
For Each oCtrl In Frame3.Controls
'***** Try only option buttons
If TypeName(oCtrl) = "OptionButton" Then
'***** Which one is checked?
If oCtrl.Value = True Then
'***** What's the caption?
Debug.Print "You have checked option " & oCtrl.Caption
Exit For
End If
End If
Next
End Sub
The Label Text associated with an Option Button is obtainable by using OptionButton1.Caption
If you are using a loop, just substitute the OptionButton1 with your variable for option buttons and it will pull through the one you need when conditions are met. eg:
For xitem = 1 To 5
xFrm = "OptionButton" & xitem
For Each fItem In Me.Controls
If fItem.Name Like xFrm Then
If fItem.Value Then
k = fitem.Caption
End If
End If
Next fItem
Next xitem
In my case, I wanted the caption of the toggle that was selected in an option group to be passed on to a subform filter. e.g. choosing toggle "black" filters subform to all cars where strColour = "black".
I ended up with this:
Private Sub OptionGroupName_Click()
Dim Caption As String
Caption = OptionGroupName.Controls.Item(OptionGroupName.Value - 1).Caption
Me.SubformName.Form.Filter = "[SubformField] = """ & Caption & """"
Me.SubformName.Form.FilterOn = True
End Sub
Not to dog pile on everyone else's options but I created a function that takes the radio group name and spits out the selected radios coresponding label caption. Was using it in Access not Excel.
Only works provided you name your controls similarly....
i.e. (lblRadioButton1 & optRadioButton1)
Function GetSelectedRadioButtonCaption(ByVal optionGroupName As OptionGroup) As String
Dim oCtrl As Control
Dim oCtrl2 As Control
Dim optionLabelName As String
Dim optionLabelObject As Label
Dim optionButtonObject As OptionButton
For Each oCtrl In optionGroupName.Controls
'***** Try only option buttons
If TypeOf oCtrl Is OptionButton Then
'***** Which one is checked?
Set optionButtonObject = oCtrl
If optionButtonObject.OptionValue = optionGroupName.Value Then
'***** What's the caption?
optionLabelName = Replace(oCtrl.Name, "opt", "lbl")
For Each oCtrl2 In optionGroupName.Controls
If oCtrl2.Name = optionLabelName Then
Set optionLabelObject = oCtrl2
GetSelectedRadioButtonCaption = optionLabelObject.caption
Exit For
End If
Next
End If
If GetSelectedRadioButtonCaption <> "" Then
Exit For
End If
End If
Next
Exit_GetSelectedRadioButtonCaption:
End Function