Change the visibility of UserForm elements via For loop - vba

I have an array of constant size populated with ComboBoxes. When the user clicks on another UserForm element (a CheckBox in my case) the visibility each ComboBox in this array should change. When I run the below snippet of code, I get 424 Object Required error. I am most likely making a simple syntactic mistake, but any guidance on how to properly write this would be appreciated.
Private Sub MyCheckBox_Click()
Dim CheckBoxStatus as Boolean: CheckBoxStatus = MyUserForm.MyCheckBox.Value
Dim ComboBoxArray(0 To 4)
ComboBoxArray(0) = MyUserForm.ComboBox1 ' This line repeated and modified for the other 4 array members
If CheckBoxStatus Then
For i = LBound(ComboBoxArray) To UBound(ComboBoxArray)
CompetitorComboBoxes(i).Visible = True ' THIS IS WHERE THE ERROR IS THROWN
Next i
Else
' Appropriate else code to set the visibilities to False
End Sub

You need to Set object references so:
Set ComboBoxArray(0) = MyUserForm.ComboBox1
Dim ComboBoxArray(0 To 4) creates an array with 5 elements not the 4 you would need for your 4 boxes so you should Dim ComboBoxArray(0 To 3) (or just Dim ComboBoxArray(3)) otherwise the last element will be empty and calling .Visible on it will fail.
(I'm assuming CompetitorComboBoxes is a typo for ComboBoxArray)

Related

Check if variant is Null or an array

I have a dynamic range that is being used to set a combobox in vba.
The range starts as A3 (which will contain nothing to start) and goes all the way to A3:A9999, depending on how many elements are in the range.
The code then pulls in the data from the range and stores it in a local variant.
My code in VBA is this:
If tempj <> Null Then
cmb_JobNum.List = tempj
End If
When there are 0 elements in the array, tempj = Null, so it does not attempt to set the list.
When there is 1 element in the array, tempj = [Value of cell], so it will set the list to that single element.
When there is 2 or more elements in the array, tempj is now an array, so trying to equate it to a single element throws a 'type mismatch' error. I have no clue how to update the code so that it doesn't get caught out by that error, since every time that equate is run it will crash.
You could try like this:
Dim i As Long
For i = LBound(tempj) To UBound(templ)
cmb_JobNum.AddItem tempj(i)
Next
This code will loop through your array and add every element in it to the combobox. Thus, if array is empy, then no elelements will be added, when there's >0 elements, then all of them will be added.
Here is an example using a dynamic named range to set the fill
Option Explicit
Public Sub test()
With ThisWorkbook.Worksheets("Sheet6") '<== change as appropriate
.ComboBox1.ListFillRange = .Range("dynRange").Address
End With
End Sub
dynRange formula added via name manager (Ctrl + F3)
=OFFSET(Sheet6!$A$3,0,0,COUNTA(Sheet6!$A:$A),1)
Using a worksheet change event to automatically update the combobox:
You could tie this into a Worksheet_Change event on the range A3:A9999 to update automatically the Combobox.
If tying to an event in the code pane of the sheet containing the combobox you could have the following:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("A3:A9999")) Is Nothing Then
Application.EnableEvents = False
Me.ComboBox1.ListFillRange = ThisWorkbook.Worksheets("Sheet6").Range("dynRange").Address
Application.EnableEvents = True
End If
End Sub
Example code run:
Code pane for sheet containing Combobox:
Note:
This is assuming an ActiveX combobox but can easily be update for a Form Control ComboBox.
For a form control swop out lines and use:
With Shapes("Drop Down 2").ControlFormat '<== change to appropriate name
.ListFillRange = ThisWorkbook.Worksheets("Sheet6").Range("dynRange").Address
End With
Edit: For UserForm combobox you can populate in the initialize e.g.
Private Sub UserForm_Initialize()
cb1.RowSource = Sheet1.Range("dynRange").Address
End Sub
Figured it out
If VarType(tempj) <> 0 Then
If VarType(tempj) = 8 Then
cmb_JobNum.AddItem tempj
Else
cmb_JobNum.List = tempj
End If
End If

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

VBA Programmatically change object name

I've created a date picker which allows you to select multiple dates, and toggle them so no-one can book anything on those days.
In order to do this, I have 31 toggle buttons, and a month selector. When you select the month, each of the 31 buttons has to update, and get the value (toggled on or or) from a DLookup.
At the moment, I've got the code for this as a long list in the update event of the month picker
Private Sub cmbMonth_AfterUpdate()
If IsNull(Me.cmbMonth) Then
GoTo Subexit
Else
Imonth = CInt(Me.cmbMonth)
End If
Call Update_toggle(Me.Toggle1)
Call Update_toggle(Me.Toggle2)
Call Update_toggle(Me.Toggle3)
Call Update_toggle(Me.Toggle4)
etc - up to toggle 31.
Is there a way of doing this with a loop?
I tried something along the lines of:
Dim toggle as Togglebutton
Dim I as integer
Dim strTogglename as String
set toggle = new togglebutton
I = 1
for 1 = 1 to 32
strtogglename = "Me.Toggle" & I
set toggle.name = strtogglename
Call Update_toggle(Toggle)
next I
But I can't get it to work. Playing around with byref and byval don't seem to help.
You can use the Form's Controls collection and check the type of each control as you loop. You're looking for acToggleButtons.
Dim cntrl As Control
For Each cntrl In Me.Controls
If cntrl.ControlType = acToggleButton Then
Update_toggle cntrl
End If
Next
You need to store your objects inside a global collection at initialize time, for example:
Global Collection declaration
Dim allToggles As Collection
Initialize - storing objects in collection
Set allToggles = New Collection
With allToggles
.Add Me.Toggle1
.Add Me.Toggle2
'...
.Add Me.Toggle31
End With
Calling in loop
for I = 1 to 32
Update_toggle allToggles(I)
next I
Within the loop, you are able to derive the names of your controls as "Toggle" & I. So you can target the corresponding control object by referencing that item by name in the form's Controls collection.
'for 1 = 1 to 32
For I = 1 to 31
Call Update_toggle(Me.Controls("Toggle" & I))
Next I
As much as I know it is not possible to programmatically edit a control's name. To achieve that, one must be inside the VBA editor.
Only a caption may be edited in code.

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

Forcing the unloading forms from memory

I am writing a solution in Excel that uses a number of linked data entry forms. To move between he sequence of forms, the user can click a "Previous" or "Next button. The current form is unloaded and the new one loaded and opened.
Sub NextForm(curForm As MSForms.UserForm, strFormName As String)
Dim intCurPos As Integer
Dim strNewForm As String
Dim newForm As Object
intCurPos = WorksheetFunction.Match(strFormName, Range("SYS.formlist"), 0)
If intCurPos = WorksheetFunction.CountA(Range("SYS.formlist")) Then
Debug.Print "No"
Else
Unload curForm
strNewForm = WorksheetFunction.Index(Range("SYS.formlist"), intCurPos + 1)
Set newForm = VBA.UserForms.Add(strNewForm)
newForm.Show
End Sub
The code as is allows new forms to be added into the sequence at any time through the editing of the range "SYS.formlist".
One problem I have noticed is that even after the current form is unloaded, it still remains in the VBA.Userforms collection. I would presume this is because this code has been called from that userform.
Is there a way to force the removal of that form from the VBA.Userforms collection? What is occuring is that if the user moves forward and then back, two copies of the form appear in memory and and excel throws exceptions about two modal forms being open.
Cheers,
Nick
The answer was (sadly) quite simple and inspired by bugtussle's answer.
The subroutine was passing the curForm variable as an MSForms.Userform object, but the form is held in memory as its own object type. (As an example, you can access a form through Set form = new formName)
So by changing the curForm paramater type to Variant, it will pass the actual object through rather than a copy of the object. Unload was only unloading the copy, not the actual object.
Thanks bugtussle!
So, corrected code is:
Sub NextForm(curForm As Variant, strFormName As String)
Dim intCurPos As Integer
Dim strNewForm As String
Dim newForm As Object
intCurPos = WorksheetFunction.Match(strFormName, Range("SYS.formlist"), 0)
If intCurPos = WorksheetFunction.CountA(Range("SYS.formlist")) Then
Debug.Print "No"
Else
Unload curForm
strNewForm = WorksheetFunction.Index(Range("SYS.formlist"), intCurPos + 1)
Set newForm = VBA.UserForms.Add(strNewForm)
newForm.Show
End Sub
I'm thinking that unloading from the collection object instead of the variable will really get rid of it. Try something like this:
For i = VBA.UserForms.Count - 1 To 0 Step -1
if VBA.UserForms(i).Name = curForm.name
Unload VBA.UserForms(i)
end if
Next i