5

I created an Ms-Access userform which has a number of Textboxes. 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 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?

I could use On Error but I 'd rather capture this specific instance with code.

5
  • 2
    I'm suprised any of the iterations work on that Conrtols object :P Commented May 30, 2017 at 8:44
  • 1
    @MacroMan - its probably User Defined :} Commented May 30, 2017 at 8:46
  • 2
    Isn't this a really bad way to use forms in Access? That you're looking for the last control suggests you're adding text boxes to the form at run-time which I was always under the impression that it was a bad idea (in Access) as you need to open the form in design mode before adding the control, add code so you can use the button (rather than a class hooked into the control) - isn't this question pointing out that the form design could be better, or there may be a better solution? Commented May 30, 2017 at 10:49
  • @DarrenBartrup-Cook - I need may forms with a similar initial design to pull data from the same place. But very different on how this data will be analysed. Each box will take a user defined identifier. So this is part of a module that will be used on many forms. The number of boxes can range from 5 - 15 across different forms. So I thought of giving them the same name (wont be Box in actual implementation). I guess I could always have 20 boxes on all forms and hide the inactive ones, but I would rather not. Me.Controls will also have to change eventually, but is irrelevant at the time being Commented May 31, 2017 at 10:00
  • 1
    If the form design is similar enough I'd probably use a single form with the 20 boxes and write all the analysis code to work on just that form with some kind of identifier telling it which analysis to perform. So most of the code won't actually sit in the form, it'll be in normal modules which are called from the form. Or a Switchboard style form - have a single button on a continuous form connected to a table holding the button text and arguments. Commented May 31, 2017 at 10:34

5 Answers 5

5

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Looping through all controls only when initialising the form is a good idea. You added a lot of info which is good for learning. But why not (for this purpose) abbreviate everything by just adding MyBoxes = MyBoxes + 1 instead of Call PullBoxes.Add(Control)? MyBoxes could be a Public Variable or a hidden Label on the form.
@George, sorry, but what is MyBoxes variable? A Long variable representing a counter? If it is, then you can check a Count property of a Collection/Dictionary instead. If you're about make a Public collection instead of Property - it's up to you, there's no difference in behaviour with those two (but depends on where you would place your Public variable). If it's some sort of iterator/current index - then please eleborate for what reason do you need one?
I was asking if it was better to replace Public Function PullBoxes() as collection, with Public Function MyBoxes() as integer. MyBoxes would run when initializing the form and count the number of Boxes I have. Shortening everything to the for each control in me.controls loop. Existswidget, getboxes and lastcreatedbox would no longer be required.
2

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

2 Comments

Separate function might be an answer. And go for Else Exit For. It seems that it is either that or loop though all controls only when initialising the form and get the end value of the loop
@George - the idea of the code was to show how the fnBlnExists works. Thus, I have put the debug.print.
0

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.

Comments

0

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.

3 Comments

Geia sou Kosta :). Thanks for the answer. That indeed works, but it loops through all controls. I also need an additional condition there so that if ctl.controltype = acTextBox and LEFT(ctl.name, 3) = "Box" Then. Which is why I only want to loop through controls named Box1, Box2 and so on
Geia sou Giwrgo :). I cant help to ask why you need to loop only on the textboxes named "Box" . How many controls does the form have?
Not that many to make it a must. I am currently looping through all controls and no delay is noted. But I am new to programming and I am trying to make it a habit to work efficiently. So to answer your question a) I want to know if its possible and learn how to do and b) I want to make it a habit to at least think of more efficient ways, so that I can at least comment my code and revise at a later stage if needed. Bear in mind that by asking I also got the helpful comment of better form design and answer on collections as an alternative way of doing things
0

I found this post from a search for "ms word 365 vba how to determine if a specific textbox exists in document".

In my case, I am creating a drawing in MS Word. I have several "Drawing Objects" that are grouped together & multiple copies of similar groups that contain different text (ID #'s).

So in an effort to avoid re-inventing the wheel every time I produce a new drawing, I open an existing drawing, SaveAs w/ a new name, then use VBA to re-position my drawing objects on the side of the doc before altering the drawing & placing the ID #'s in their correct locations on the new doc.

Depending on the type of drawing that I'm creating, some drawings will have a certain "Drawing Object", while others will not. So I use the following to determine if that specific object exists or not ...

Private Sub sort_Nums()
  Dim grp As Shape, shp As Object
  Dim id As String    '<-- object id - could be a # &/or a letter
  Dim flg_It_Exists As Boolean

  Set doc = Application.ActiveDocument
  flg_It_Exists = False

  For Each grp In doc.Shapes
    If grp.Type = 6 Then    '<-- if shape is a group then
      For Each shp In grp.GroupItems
        If shp.Type = 17 Then    '<-- if group contains a textbox then
          'pop var "id" to get ID, trim & strip all vbCR/vbLR/vbCRLF
          If IsNumeric(id) Then
            'manage stuff for # ID
          ElseIf [id=string ID] Then
            'manage stuff for string ID
          ElseIf [id=specific ID] Then
            'manage stuff for specific ID
            flg_It_Exists = True
          End If
        End If
      Next shp
    End If
  Next grp

  'reposition specific object
  If flg_It_Exists Then
    'move specific object to its default starting position
  End If

  'code to manage other objects here
  
  Set doc = Nothing
End Sub

I realize that the OP is asking about a UserForm in MS Access, but the underlying logic might still apply.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.