check if textbox exists vba (using name) - vba

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.

Related

Word VBA Userform, Getting Control Object By Name fails sometimes

I have a userform “myUserForm” with dozens of controls whose TypeName() is “CheckBox”. I just hate having dozens of _Click() routines named like “Private Sub Chk1_Click()”, so in order to manage the quantity of _Click() routines, I simplified and made them nearly identical:
Private Sub Chk1_Click()
ProcessClickFor ("Chk1")
End Sub
Private Sub Chk2_A_Click()
ProcessClickFor ("Chk2_A")
End Sub
Private Sub Chk3_Z_Click()
ProcessClickFor ("Chk3_Z")
End Sub
ProcessClickFor() does most of the work.
Sub ProcessClickFor(anyCheckBox As String)
Dim cbControl As Object
Set cbControl = ControlByName(anyCheckBox)
If cbControl.Value Then
cbControl.Value = True
End If
End Sub
Later, when I want to work with any control, I can get the Control object by name, like:
Dim aControl As Object
Set aControl = ControlByName(“Chk3”)
MsgBox “The control named “ & cbControl.Name & “ is “ & cbControl.Visible
Function ControlByName(sName) As Object
Dim objectified As Object
For Each objectified In myUserForm.Controls
If objectified.Name = sName Then
Set ControlByName = objectified
Exit Function
End If
Next objectified
End Function
This works fine, almost, but it FAILS on the same four controls on myUserForm every time.
The failure “mode” is that ControlByName() seems to return successfully, but the first use of the returned control (such as my MsgBox) gives the error:
"Run-time error '91': Object variable or With block variable not set".
I verified that the spelling of the defined control names matches the names in my _Click() routines. Dozens of similarly designed CheckBox controls work perfectly. Could it have to do with the length of the CheckBox names or the number of “_” characters in the CheckBox names? Could there be a corrupt character in a CheckBox name? Can you think of other things for me to try?

How to tell which dynamic control sent to an event?

This is my first attempt at working with dynamically created controls in a user form. The reason is there will always be a different amount of rows returned by some processing.
I have created a class object cControlEvent with the following code. (I cut out the code not pertaining to the checkbox)
Public WithEvents CHK As MSForms.CheckBox
Private Sub CHK_Change()
** tell me which box was changed **
End Sub
in the code module, I have the following code:
Dim CHK_Evts As New Collection
sub Form_Builder()
**non relevant code deleted****
Set Evt = New cControlEvent
If i_Columns = 1 Then
Set Evt.CHK = ctl
CHK_Evts.Add Evt
Else
** more code**
End if
end sub
What do I need to change/add to be able to get the name of the control that is firing off the change event?
EDITED TO ADD:
I have a series of dynamically created checkboxes and textboxes on each line of a user form, with a checkbox before each line, when the checkbox is checked/unchecked, I need to change the backcolor on all the textboxes in that row. Each control is named by it's type, then row then column like this CHX_1_1 would be a checkbox on row 1 column 1, and TXT_1_5 would be row 1 column 5. So, if I know what the name of the checkbox is, I have all I need to change the other controls on that row with a simple for-next loop.
I am not quite sure if I understand your question correctly. But it seems to me that it boils down to "which FormControl (linked to a particular procedure) caused this sub to run". If that's the case then you should be able to make use of the
Application.Caller
Here is a short video to demonstrate it's use in a very simple environment:
Here's hopefully a full solution showing how to get the properties from the check boxes:
Create a blank userform and add a command button to it.
Add this code to the form (note - CommandButton1_Click should be updated to the name of the button you added).
Public CHK_Evts As New Collection
Private Sub CommandButton1_Click()
Dim ChkBox As Variant
For Each ChkBox In CHK_Evts
MsgBox ChkBox.Position & vbCr & _
ChkBox.Status
Next ChkBox
End Sub
Private Sub UserForm_Initialize()
Dim tmpCtrl As Control
Dim cmbEvent As clsControlEvents
Dim X As Long
For X = 1 To 10
Set tmpCtrl = frmNameParser.Controls.Add("Forms.Checkbox.1", "Name" & X)
With tmpCtrl
.Left = 6
.Top = X * 20 + 24
.Height = 18
.Width = 150
End With
Set cmbEvent = New clsControlEvents
Set cmbEvent.CHK = tmpCtrl
CHK_Evts.Add cmbEvent, "Name" & X
Next X
End Sub
Create a class called clsControlEvents and add this code:
Public WithEvents CHK As MSForms.CheckBox
Public Property Get Position() As String
Position = CHK.Top
End Property
Public Property Get Status() As String
Status = CHK.Value
End Property
Private Sub CHK_Click()
MsgBox CHK.Name
End Sub
The two GET procedures pass information back to the CommandButton1_Click procedure so it can list information about all check boxes on the form (held in the CHK_EVTS collection).
The CHK_Click procedure gives immediate information about the check box being clicked.
http://www.cpearson.com/excel/classes.aspx

Manipulating ListBoxes as Objects

I am working on a VBA userform that includes ListBoxes.
So far, when I had to manipulate one or more, I always proceeded like this in my subs, with dlg as the dialogbox name, and it did not pose any problem, given that I never wanted to do anything complicated:
Dim List1 As Object
...
List1 = dlg.GetControl("CBXname")
...
List1.addItem("String",index)
...
Now I would like to do the following in this Sub
...
If (List1.Exists(Cell1.String) = False) Then
List1.addItem(Cell1.String,k)
End If
...
List1.Clear
...
But I can do neither since List1 is an Object. However, if I decide to declare List1 as a Listbox instead, I do not know how to get the proper control on the ListBox from the dialogbox (the current getcontrol gives me an error).
One of the issues with your code is that listbox objects do not have an "exists" property. To check if a value already exists in your listbox items, you will need to loop through the items.
dim i as integer
for i = 0 to List1.listcount - 1
if List1.column(0, i) = myvalue then
'myvalue exists in List1, skip
else
List1.additem myvalue
end if
next i
Where myvalue is whatever value you are trying to add to the listbox. But that brings us to the second issue in your code which is where you add "Cell1.String". If you are trying to add a value from a worksheet range you will need to refer to that range's value, as worksheet ranges do not have a "string" property as you use it here. Ie. Cell1 = Range("A1").value
As for getting control of the listbox, you can simply refer to the objects name as an object of the form. For example, dlg.List1, if the object's name is List1.
Here is a general purpose routine you can call for any list box. The calling code assumes a list box called ListBox1, a text box called TextBox1, and a Command Button called CommandButton. When you click on the button, it searches the listbox for the text from textbox1.
Private Function ExistsInListbox(ByRef aListBox As msforms.ListBox, ByVal Item As String) As Boolean
Dim booFound As Boolean
booFound = False
Dim t As Integer
ExistsInListbox = False
For t = 0 To aListBox.ListCount - 1 'correction, aListBox not ListBox1
If Item = aListBox.List(t) Then
'if we find a match, short-circuit the loop
booFound = True
Exit For
End If
Next
ExistsInListbox = booFound
End Function
private sub CommandButton_click()
Dim answer As String
Dim val As Boolean
val = ExistsInListbox(Me.ListBox1, TextBox1.Text)
If val Then
answer = "found"
Else
answer = "Not Found"
End If
MsgBox "found-" & answer
End Sub

Reading checkbox values with a loop (Microsoft Word VBA)

I'm current trying to write a macro (VBA in Word) that will compile information from a collection of documents into a single document.
I order to do this I have a list of ~20 checkboxes that will determine which documents I want to include in the compilation. My issue is that when writing the macro, I can't figure out a way of checking the state of each checkbox on my list without re-writing the same block of code 20 times, only changing the name of the checkbox. eg CB1 to CB2, CB3 CB4 etc. each time.
This is the block of code in question. It does work if I rewrite it multiple times for the changing check box number but I would prefer it in a loop so the code is more compact and robust:
If ThisDocument.CB1.Value = True Then
Documents.Open(directory).Activate
Selection.WholeStory
Selection.Copy
Documents(NewFile).Activate
Selection.Paste
Documents("file.docx").Close
End If
Ideally I would like to have the check box named something like CBn, where n is a variable that I can redefine at the end of each loop.
There's no option for directly referring to a control by its name - you can wrap that up in a function though:
Sub Tester()
Dim x As Long, cb As Object
For x = 1 To 3
'find the checkbox
Set cb = ControlByName("CB" & x, ThisDocument)
'check we got something back
If Not cb Is Nothing Then
Debug.Print "CB" & x & " is " & cb.Value
End If
Next x
End Sub
Function ControlByName(sName, doc As Document) As Object
Dim obj
For Each obj In doc.InlineShapes
If obj.OLEFormat.Object.Name = sName Then
Set ControlByName = obj.OLEFormat.Object
Exit Function
End If
Next obj
End Function

Get position (in number) of selected item in dropdown list

In a dropdown list I have a few items. Can I, when I select an item, get the position of that item in the list as a number?
If you are looking for the index of a Data Validation list, this is what I'd do:
Put the following code in the ThisWorkbook module:
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Dim ValidationIndex As Long
Dim rngTest As Excel.Range
'assumes the data validation is in a cell named "rngTest"
On Error Resume Next
Set rngTest = Sh.Range("rngTest")
If rngTest Is Nothing Then
Exit Sub
End If
On Error GoTo 0
If Not Intersect(ActiveCell, Sh.Range("rngTest")) Is Nothing Then
ValidationIndex = GetValidationIndex
MsgBox ValidationIndex
End If
End Sub
Put this function in the ThisWorkbook module also, or else in any regular module:
Function GetValidationIndex() As Long
'returns a 1-based index
Dim rngTest As Excel.Range
Dim varValidationString As Variant
Dim ErrNumber As Long
Dim i As Long
With ActiveCell.Validation
If .Type = xlValidateList Then '3
On Error Resume Next
Set rngTest = ActiveCell.Parent.Range(.Formula1)
'I do this goofy thing with ErrNumber to keep my indenting and flow pretty
ErrNumber = Err.Number
On Error GoTo 0
'if the Validation is defined as a range
If ErrNumber = 0 Then
GetValidationIndex = Application.WorksheetFunction.Match(ActiveCell.Value2, rngTest, 0)
Exit Function
'if the validation is defined by comma-separated values
Else
varValidationString = Split(.Formula1, ",")
For i = LBound(varValidationString) To UBound(varValidationString)
If varValidationString(i) = ActiveCell.Value2 Then
GetValidationIndex = i + 1
Exit Function
End If
Next i
End If
End If
End With
End Function
If you are using a list or combo box, ListIndex would seem to be what you are after.
VB Help for ListIndex property: Returns or sets the index number of the currently selected item in a list box or combo box. Read/write Long. Remarks. You cannot use this property with multiselect list boxes.
If nothing is selected, ListIndex's value is -1. If memory serves, it is a zero based index.
ListIndex cannot be set at design time so it is not listed in the properties window.
When entering your code, type the list box name then dot and the editor displays all the available properties. Scroll down the list, note any that look interesting, then look them up.
I think it is not necessary to use a function. You can get it by using only Match function, like in above Doug's answer.
Dim GetValidationIndex as Integer
Dim rngTest as Range
' Get the validation list
With ActiveCell.Validation
Set rngTest = ActiveCell.Parent.Range(.Formula1)
end with
GetValidationIndex = Application.WorksheetFunction.Match(ActiveCell.Value2, rngTest, 0)
The function GetValidationIndex is good.
However, for some regional settings the line varValidationString = Split(.Formula1, ",") is not valid, because the character used to separate the different values is ";"
I suggest use:
varValidationString = Split(.Formula1, Application.International(xlListSeparator))