VBA - OptionButton Collection - vba

I'm trying to get a collection of option buttons. I would like the objects of my collection to have the "OptionButton" type. Is there a way to do that ?
Private Sub SetInputs()
Dim OptionButtons As Collection
Set OptionButtons = New Collection
OptionButtons.Add (br_FKG1)
' MsgBox (TypeName(br_FKG1)) : OptionButton
' MsgBox (TypeName(OptionButtons.Item(1))) : Boolean
(...)
End Sub
Thanks,

Got it. If anybody has the same problem some day, just write
OptionButtons.Add br_FKG1
without the parentheses so that the object and not the value is added.
Without parentheses, it is actually the default property of the class that is added. For an OptionButton, it is .Value, but it may be something else for other classes.

Related

Question on multiple checkboxes launching code

I have a user form and a frame with 35 checkboxes in it, numbered 1 to 35. They represent 35 Named Ranges. I test to see if any of the name ranges are not set, if set correctly the checkbox value is set to TRUE.
I found some code that would allow me to trigger a sub if one of the checkboxes is clicked. That code seems to work, but my check code above also triggers the checkbox events, which I do not want. I only want the sub to run when the checkbox is clicked with the mouse? I can post the code I'm using, but though I'd first ask the question to see if what I would like to do is possible.
Thanks,
Jim
Code in class module:
Public WithEvents ChkBox As MSForms.CheckBox
Public Sub AssignClicks(ctrl As Control)
Set ChkBox = ctrl
End Sub
Public Sub chkBox_Click()
If chkBoxProcess = "Y" Then
'ThisWorkbook.Worksheets("Sheet1").Range(ChkBox.Name).Value = Format(Now, "dd.mm.yyyy")
'MsgBox ("check box number = " & ChkBox.Name & " " & ChkBox.Value)
' Else
End If
End Sub
Code in Forms:
Public Sub UserForm_Initialize()
Dim SheetCount, i As Integer
Dim sh As Worksheet
'Public SheetName, SheetName2, StartOldNewTimeA, OldNewTimeAdd As String
'Initialize the form frmChgNameRng
'Set array values of the day options
'Set array values for 12:00 timeframes
'Set array values for 12:30 timeframes
'Set colors used in Checkboxes
'Set array for Checkboxes (boxes are numbered accross the page, 1 corressponds to Mon_1200/Mon_1230, 8 corresponds to Mon_200/Mon_230, etc.)
'Formulas are placed in the time cells on the left of the page, the macro will add the appropriate value into the Mon_1200 time slot and all other cells update off that cell
chkBoxProcess = "N"
Dim ChkBoxes As cls_ChkBox
Dim ctrl As Control
Set colTickBoxes = New Collection
For Each ctrl In Me.Controls
If TypeName(ctrl) = "CheckBox" Then
Set ChkBoxes = New cls_ChkBox
ChkBoxes.AssignClicks ctrl
colTickBoxes.Add ChkBoxes
End If
Next ctrl
'..... lots of code for Range Name Checks, etc.
End Sub
Your code is conflating control state with model data, and so the only way to tell it "named range 32 is ON", or "named range 13 is OFF", is to alter a checkbox' state, which fires that control's Change event.
There's no way around that, it's just how controls work: they fire a Change event whenever their value changes, regardless of how that's done.
Instead of having controls' state be the data, make the controls' state alter the data.
This requires conceptualizing this data, first: looks like you need to associate a number/index to some Boolean value. An array can do this.
Private namedRangeStates(1 To 35) As Boolean
Note that depending on what you're doing, initializing the state should be feasible by iterating the workbook's Names collection in the UserForm_Initialize handler. Or better, the form could expose a method that takes an array of Boolean values, and copies that state into namedRangeStates.
Now, when a checkbox is modified, make it alter the state:
Private Sub Checkbox31_Change()
namedRangeStates(31) = Checkbox31.Value
End Sub
Your form can expose that state as a property:
Public Property Get NamedRangeState(ByVal index As Long) As Boolean
NamedRangeState = namedRangeStates(index)
End Property
Public Property Let NamedRangeState(ByVal index As Long, ByVal value As Boolean)
namedRangeStates(index) = value
End Property
And now you can modify the enapsulated state independently of the combobox values.

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?

check if textbox exists vba (using name)

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.

Removing items from multiple comboboxes

I have 7 comboboxes. All these comboboxes have same source.
With Sheets("Data_Sheet")
Sheets("UI_Interface").ComboBox2.ListFillRange = "Data_Sheet!E2:E" & .Cells(Rows.Count, 5).End(xlUp).Row
End With
Same code has been written for other combobxes.
Now when a value from combobx1 is selected it should not be present in other comoboxes.
When i try to do this with following code but i'm getting error with this code.
j = ComboBox1.ListIndex
ComboBox2.RemoveItem (j)
I tried some different attributes too for removing the value but all gave some exception.
What is incorrect in this code?
The RemoveItem method works properly for me unless I use the .ListFillRange method to populate the combobox. If you use the .List method instead, it should work. To do that, you have to convert the range to an array.
REVISED
Thanks to enderland for pointing out that you are working with form controls on a worksheet, not in a user form. So the approach should be similar but you won't be able to use the ListFillRange method. Not a big deal, we can easily take that range, convert it to a variant/array, and use the List method.
Option Explicit
Private Sub Worksheet_Activate()
'## Sets default list for ComboBoxes on this sheet
SetComboBoxLists ComboBox1
SetComboBoxLists ComboBox2
End Sub
Private Sub ComboBox1_Change()
'## event handler for a combobox, repeat as needed
'## Repopulate the list, otherwise you may get
' an Index out of Range error or Invalid Argument error,
' or the RemoveItem method will remove the wrong item
SetComboBoxList ComboBox2
'## Now, remove the item selected in ComboBox1
ComboBox2.RemoveItem ComboBox1.ListIndex
End Sub
Private Sub SetComboBoxLists(cBox As MSForms.ComboBox)
'## Subroutine to fill the list in each combobox as needed
Dim lstRange As Variant
'## Populate the array variable to use for the combobox.List method:
' double-check that I put the parentheses in the right place!
With Sheets("Data_Sheet")
lstRange = .Range("E2:E" & .Cells(Rows.Count, 5).End(xlUp).Row)
End With
'## Populate the combobox with the list
cBox.List = lstRange
End Sub
Note that if any of your code manipulates (e.g., resizes, removes rows, etc) the range, you'll need to re-apply the List method.

How do I refer to a controls object, on a worksheet, using a variable name?

I have added a ListBox to a SHEET (not to a "UserForm")
I did this using the mouse.
I clicked the little Hammer and Wrench icon.
This ListBox seems to be easily referenced using code such as this:
ListBox1.Clear
or
ListBox1.AddItem("An option")
However, I have three of these ListBoxes (named, conveniently, ListBox1, ListBox2, and ListBox3) and I want to write a function to populate them with array data, like this:
Call populate_listbox(ListBox2, designAreaArray)
Where the first argument is the listbox name, the 2nd is the data.
But I do not know how to send "ListBox2" correctly, or refer to it correctly within the function.
For example:
Dim controlName as string
controlName = "ListBox1"
doesn't work, even if I define the function as follows:
Sub populate_listbox(LB As ListBox, dataArray As Variant)
Dim i As Integer: i = 0
For i = LBound(dataArray, 2) + 1 To UBound(dataArray, 2) ' Skip header row
LB.AddItem (dataArray(index, i))
Next i
End Sub
Clearly it results in a mis-matched data type error. I've tried defining "controlName" as a ListBox, but that didn't work either...
Though perhaps it is my reference to the listBox that is incorrect. I've seen SO MANY ways to refer to a control object...
MSForms.ListBox.
ME.ListBox
Forms.Controls.
Worksheet.Shapes.
The list goes on an on, and nothing has worked for me.
Try this:
Dim cMyListbox As MSForms.ListBox
Set cMyListbox = Sheet1.ListBox1 '// OR Worksheets("YourSheetName").Listbox1
cMyListbox.AddItem("An option")
Also you can populate a listbox without having to loop through the array, try this:
Dim cMyListbox As MSForms.ListBox
Dim vArray As Variant
Set cMyListbox = Sheet1.ListBox1
vArray = Range("A1:A6").Value
cMyListbox.List = vArray
Change the sub signature to match this:
Sub populate_listbox(LB As MSForms.ListBox, dataArray As Variant)
Now you can pass it like you were trying to originally.
NOTE: This only works if you used the "ActiveX" version of the listbox. I'm assuming you are because you are able to call ListBox1 straight from a module.
PS: The ActiveX controls are members off of the parent sheet object. So if you have the listbox1 on sheet1, you can also call it like Sheet1.ListBox1 so you don't get confused if you end up with multiple sheets with multiple listboxes. Also, you may want to change the name just to make it easier on yourself.
Dim controlName As OLEObject
Set controlName = Sheet1.OLEObjects("ListBox1")
Call populate_listbox(controlName, designAreaArray)
Sub populate_listbox(LB As OLEObject, dataArray As Variant)
Dim i As Integer: i = 0
For i = LBound(dataArray, 2) + 1 To UBound(dataArray, 2) ' Skip header row
LB.Object.AddItem (dataArray(Index, i))
Next i
End Sub
To access the state of a checkbox Active-X control on Sheet1:
Dim checkBox1 As Object
Set checkBox1 = Sheet1.OLEObjects("CheckBox1").Object
MsgBox checkBox1.Value