Dynamically add checkboxes and event handler to a worksheet - vba

In my sheet in column B I have names of components
In column A I would like to have checkboxes which are dynamically added, as the list of names in column B will increase over time
I need to be able to do the following:
- handle the events (onclick) as ticking/unticking the checkboxes hides/unhides rows in another sheet
- return the status of each checkbox as I cycle through the checkboxes in another module (onclick from a commandbox) and depending on the status an action follows or not
- modify the status of each checkbox as I have 1 commandbox to tick them all and 1 commandbox to untick them all
So far I have a working prototype, but I have 2 problems:
1) the checkboxes are not linked to the cells in column A, they are just positioned when I created them
2) the checkboxes are not dynamic, I created them manually and had to write an event handler (onclick) for each checkbox (> 50 checkboxes)
I have tried to create a code to dynamically add checkboxes and create a class module to handle the events, but I am really stuck..
I copied and modified some code that was originally intended for a userform and I managed to make it work on a userform, but I'd rather have everything on the worksheet as i described above.
Here is the class module code (named: clsBoxEvent)
Option Explicit
Public WithEvents cBox As MSForms.CheckBox
Private Sub cBox_Click()
MsgBox cBox.Name
End Sub
Here is the code I wrote as a module. I plan to put it in an event (onclick) from a command button which I plan to click to update the list of checkboxes. Unless this is not necessary as there is a way that the checkboxes are created as soon as the cell in column B isn't blank ?
I thank you for your input.
Dim chkBoxEvent As clsBoxEvent
Dim chkBox As MSForms.CheckBox
Dim chkBoxColl As Collection
Private Sub chkBox_update()
Dim i As Integer
Set chkBoxColl = New Collection
For i = 1 To 5
' I wrote the code just to add 5 checkboxes as a test. Later I will need to adapt this to the actual required number of checkboxes (the number of products in column B)
Set chkBox = Controls.Add("Forms.CheckBox.1", "ChkBox" & i)
With chkBox
' I work with the position as I did not know how to link it to the cells in colums A
.Left = 126
.Height = 16
.Top = 6 + ((i - 1) * 16)
End With
Set chkBoxEvent = New clsBoxEvent
Set chkBoxEvent.cBox = Controls(chkBox.Name)
chkBoxColl.Add chkBoxEvent
Next i
End Sub

My answer to: Excel VBA script to insert multiple checkboxes linked to cell with yes and no instead of true and false seems like it will work nicely for you.
Sub AddCheckBoxes()
Dim cb As CheckBox
Dim myRange As Range, cel As Range
Dim wks As Worksheet
Set wks = Sheets("Sheet1")
Set myRange = wks.Range("A1:A1000")
For Each cel In myRange
Set cb = wks.CheckBoxes.Add(cel.Left, cel.Top, 30, 6)
With cb
.Caption = ""
.OnAction = "ProcessCheckBox"
End With
Next
End Sub
Sub ProcessCheckBox()
Dim cb As CheckBox
With Sheets("Sheet1")
Set cb = .CheckBoxes(Application.Caller)
If Not cb Is Nothing Then cb.TopLeftCell = IIf(cb.Value = 1, "Cleared", "")
End With
End Sub

Related

Assign macro to a cell corresponding to the row of automatically generated buttons

I've managed to create a form where the user can expand the fields of a pivot table and, once they've completely expanded a field/branch, a button will appear in column E and that pivot field data is concatenated in column J (there are some hidden columns).
What I want is for the user to click an auto-generating button in column E which exports the corresponding data in column J to a list, somewhere on the workbook.
My code below automatically generates the buttons for fully expanded fields, but I have no idea how to write the code to link each button to the corresponding cell in column J - this is probably not very difficult but any help would be appreciated.
Sub buttonGenerator()
Dim btn As Button
Application.ScreenUpdating = False
ActiveSheet.Buttons.Delete
Dim t As Range
Dim size As Integer
size = ActiveSheet.PivotTables("Pivottable1").TableRange2.Rows.Count
For i = 2 To size Step 1
If Not IsEmpty(ActiveSheet.Range(Cells(i, 4), Cells(i, 4))) Then
Set t = ActiveSheet.Range(Cells(i, 5), Cells(i, 5))
Set btn = ActiveSheet.Buttons.Add(t.Left, t.Top, t.Width, t.Height)
With btn
.OnAction = "btnS"
.Caption = "Add to summary" '& i
.Name = "Btn" & i
End With
End If
Next i
Application.ScreenUpdating = False
End Sub
Sub buttonAppCaller()
MsgBox Application.Caller
End Sub
So here is my code .. it is throwing Runtime error 1004 "Unable to get the Buttons property of the worksheet class". Not sure what I've done wrong but I need to get the data from the cell next to the button to copy over to the bottom of a list in sheet 2 when that particular button is clicked. Please help!
Sub btnS()
Dim dest As Range
Dim origin As Range
origin = ActiveSheet.Buttons(Application.Caller).TopLeftCell.Offset(0, 1) 'input data from cell next to button click
dest = Worksheets("Form Output").Range("A1") 'output data to list in sheet 2 - "Form output"
Set dest = origin
End Sub
Don't use Integer for row counts as you did for size. Excel has more rows than Integer can handle. It is recommended always to use Long instead of Integer in VBA there is no benefit in Integer at all.
The procedure every button invokes is called btnS as you defined in .OnAction = "btnS". Therefore you need a Sub with that name in a Module.
You can use Buttons(Application.Caller).TopLeftCell to get the cell under a button and from that cell you can determine the row or column.
Public Sub btnS() 'sub name must match `.OnAction` name
MsgBox ActiveSheet.Buttons(Application.Caller).TopLeftCell.Row
End Sub
Instead of using ActiveSheet I recommend to use a specific worksheet like Worksheets("your-sheet-name") if you plan to use it on a specific sheet only. ActiveSheet can easily change and should be avoided where possible.

Event triggered by ANY checkbox click

I'm going crazy trying to find a way for code to run when I click on ANY of the checkboxes on my sheet. I've seen multiple articles talking about making a class module, but I can't seem to get it to work.
I have code that will populate column B to match column C. Whatever I manually type into C10 will populate into B10, even if C10 is a formula: =D9. So, I can type TRUE into D10 and the formula in C10 will result in: TRUE and then the code populates B10 to say: TRUE. Awesome... the trick is to have a checkbox linked to D10. When I click the checkbox, D10 says TRUE and the formula in C10 says TRUE, but that is as far as it goes. The VBA code does not recognize the checkbox click. If I then click on the sheet (selection change), then the code will run, so I know I need a different event.
It is easy enough to change the event to "Checkbox1_Click()", but I want it to work for ANY checkbox I click. I'm not having ANY luck after days of searching and trying different things.
here is the code I'm running so far
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim i As Long
For i = 3 To 11
Range("B" & i).Value = Range("c" & i)
Next i
End Sub
Any help would be appreciated.
this works
' this goes into sheet code
Private Sub Worksheet_Activate()
activateCheckBoxes
End Sub
.
' put all this code in class a module and name the class module "ChkClass"
Option Explicit
Public WithEvents ChkBoxGroup As MSForms.CheckBox
Private Sub ChkBoxGroup_Change()
Debug.Print "ChkBoxGroup_Change"
End Sub
Private Sub ChkBoxGroup_Click()
Debug.Print "ChkBoxGroup_Click"; vbTab;
Debug.Print ChkBoxGroup.Caption; vbTab; ChkBoxGroup.Value
ChkBoxGroup.TopLeftCell.Offset(0, 2) = ChkBoxGroup.Value
End Sub
.
' this code goes into a module
Option Explicit
Dim CheckBoxes() As New ChkClass
Const numChkBoxes = 20
'
Sub doCheckBoxes()
makeCheckBoxes
activateCheckBoxes
End Sub
Sub makeCheckBoxes() ' creates a column of checkBoxes
Dim sht As Worksheet
Set sht = ActiveSheet
Dim i As Integer
For i = 1 To sht.Shapes.Count
' Debug.Print sht.Shapes(1).Properties
sht.Shapes(1).Delete
DoEvents
Next i
Dim xSize As Integer: xSize = 2 ' horizontal size (number of cells)
Dim ySize As Integer: ySize = 1 ' vertical size
Dim t As Range
Set t = sht.Range("b2").Resize(ySize, xSize)
For i = 1 To numChkBoxes
sht.Shapes.AddOLEObject ClassType:="Forms.CheckBox.1", Left:=t.Left, Top:=t.Top, Width:=t.Width - 2, Height:=t.Height
DoEvents
Set t = t.Offset(ySize)
Next i
End Sub
Sub activateCheckBoxes() ' assigns all checkBoxes on worksheet to ChkClass.ChkBoxGroup
Dim sht As Worksheet
Set sht = ActiveSheet
ReDim CheckBoxes(1 To 1)
Dim i As Integer
For i = 1 To sht.Shapes.Count
ReDim Preserve CheckBoxes(1 To i)
Set CheckBoxes(i).ChkBoxGroup = sht.Shapes(i).OLEFormat.Object.Object
Next i
End Sub
All you need is to let EVERY checkbox's _Click() event know that you want to run the Worksheet_SelectionChange event. To do so you need to add the following line into every _Click() sub:
Call Worksheet_SelectionChange(Range("a1"))
Please note that it is irrelevant what range is passed to the SelectionChange sub since you do not use the Target in your code.

Adding combo box across multiple cells

I need to add combo box(ActiveX Control) or Data Validation as drop down list.
I have a range of 15 values like, high, low, medium,etc...
Have created named range called "priorityvalue".
I can create a dropdown list using combo box by adding named range under ListFillRange in the properties or data validation list by giving named range.
But my concern, I need to dropdown list for 58cells with same values mentioned above. Its tedious job to create combo box for all cells. Please suggest me better option here.
Data validation list serves the purpose. However, it makes user to scroll through dropdown list on each cell unlike combo box it has no input box..
Please suggest
Paste the below code in 'ThisWokbook'
Private Sub Workbook_Open()
Dim oItem As Object
For Each oItem In Worksheets(1).OLEObjects
If TypeName(oItem.Object) = "ComboBox" Then
If Len(oItem.Object.Value) > 0 Then
oItem.Object.Value = ""
End If
End If
Next
Set oItem = Nothing
End Sub
NOTE: There are caveats to this. Above code will reset all comboboxes in your worksheet (also, I've set the worksheet to the first worksheet in the workbook, you might want to make that dynamic). If you don't want it to reset all comboboxes and only do the ones you added via the function, you can use the name format to filter the ones you want to clear
Hope this helps
Try this:
Sub AddComboBoxToColumns(ByVal oRange As Excel.Range)
Dim oOLE As OLEObject
Dim oCell As Object
' Loop through all the cells in the range
For Each oCell In oRange.Cells
' Add ComboBox in each cell
With oCell
Set oOLE = .Parent.OLEObjects.Add("Forms.combobox.1")
oOLE.Top = .Top
oOLE.Left = .Left
oOLE.Width = .Width
oOLE.Height = .Height
oOLE.Name = "ComboBox" & .Address(False, False)
oOLE.Object.List = Array("Test1", "Test2")
End With
Next
Set oOLE = Nothing
End Sub
NOTE: Call the above function with the range of cells you want to add ComboBox to. You will have to change the Array to use the values you want (you can type them in there or give the range where your existing values are)

Excel VBA add handler to every checkbox in form

I have an excel form with a large number of checkboxes that are added at runtime. I would like to add a handler to each one of those checkboxes that will run when the value is changed. I know in other versions of Visual Basic I would use AddHandler, but that doesn't work in Excel VBA.
Following an example, I came up with the following code:
'This is in a class module called CheckboxHandler
Public WithEvents cb As MSForms.CheckBox
Private Sub cb_change()
MsgBox ("test")
end sub
And, in my userform, I have this code:
With CreateObject("Scripting.Dictionary")
.....'Unrelated code omitted
'Variable Checkboxes
'Add Handler to checkboxes
Dim colCBHandlers As Collection
Set colCBHandlers = New Collection
Dim objHandler As CheckboxHandler
Dim i As Long
Dim chkBox As MSForms.CheckBox
For i = 1 To .count - 1
Set chkBox = Me.Controls.Add("Forms.Checkbox.1", "Checkbox" & i)
chkBox.Caption = .Keys()(i)
chkBox.VALUE = False
chkBox.Top = (chkBox.Height + 10) * (i - 1) + 55
chkBox.Left = 725
Set objHandler = New CheckboxHandler
Set objHandler.cb = chkBox
colCBHandlers.Add objHandler
Next i
End With
colCBHandlers will go out of scope as soon as the sub which creates the checkboxes exits.
You need to declare that collection as a global (at the module level) so it doesn't get lost once it has been created and populated.

Name of textbox depends on where it is located in an ArrayList

I'm using VBA to code an application for an Excel file. Put simply, I need the names of my textboxes to change depending on where a certain variable is in an ArrayList.
I have one textbox to start, when someone pushes a button it should add a textbox after the first one, and do this as many times as one presses the button. So the first box should be named tbx1, the second should be tbx2, the third tbx3, and so on.
Now when they press a different button located next to any of the boxes, it deletes that box and button and all boxes after that one are named one lower to make up for it.
Any ideas how to do this? I'm only assuming ArrayList is the best tactic, please correct me if there is a better way.
Here's an example that you can hopefully modify to your needs. I have a userform named UClassList with one commandbutton, cmdAdd, and one textbox, tbxClass_1.
Private mEventButtons As Collection
Public Property Get ClassMax() As Long
ClassMax = 75
End Property
Private Sub cmdAdd_Click()
Dim i As Long
For i = 2 To Me.ClassMax
'find the first invisible control and make it visible
If Not Me.Controls("tbxClass_" & i).Visible Then
Me.Controls("tbxClass_" & i).Visible = True
Me.Controls("cmdClass_" & i).Visible = True
Exit For 'stop after one
End If
Next i
End Sub
Private Sub UserForm_Initialize()
Dim i As Long
Dim tbx As MSForms.TextBox
Dim cmd As MSForms.CommandButton
Dim clsEventClass As CEventClass
Set mEventButtons = New Collection
'Add as many textboxes and commandbuttons as you need
'or you can do this part at design time
For i = 2 To Me.ClassMax
Set tbx = Me.Controls.Add("Forms.TextBox.1", "tbxClass_" & i, False)
tbx.Top = Me.tbxClass_1.Top + ((i - 1) * 25) 'use the first textbox as the anchor
tbx.Left = Me.tbxClass_1.Left
tbx.Width = Me.tbxClass_1.Width
tbx.Height = Me.tbxClass_1.Height
'Create a delete commandbutton
Set cmd = Me.Controls.Add("Forms.CommandButton.1", "cmdClass_" & i, False)
cmd.Top = tbx.Top
cmd.Left = tbx.Left + tbx.Width + 10
cmd.Width = 20
cmd.Height = tbx.Height
cmd.Caption = "X"
'add delete commandbutton to the event class so they all share
'the same click event code
Set clsEventClass = New CEventClass
Set clsEventClass.cmdEvent = cmd
mEventButtons.Add clsEventClass
Next i
End Sub
I have a custom class named CEventClass.
Public WithEvents cmdEvent As MSForms.CommandButton
Private Sub cmdEvent_Click()
Dim i As Long
Dim lThisIndex As Long
Dim tbxThis As MSForms.TextBox
Dim tbxPrev As MSForms.TextBox
Dim uf As UClassList
Set uf = cmdEvent.Parent
'get the number that was clicked
lThisIndex = Val(Split(cmdEvent.Name, "_")(1))
'loop from the next textbox to the end
For i = lThisIndex + 1 To uf.ClassMax
Set tbxThis = uf.Controls("tbxClass_" & i)
Set tbxPrev = uf.Controls("tbxClass_" & i - 1)
'if it's not visible, clear and hide
'the previous textbox
If Not tbxThis.Visible Then
tbxPrev.Text = vbNullString
tbxPrev.Visible = False
uf.Controls("cmdClass_" & i - 1).Visible = False
Else
'if it's visible, copy it's text to the one above
tbxPrev.Text = tbxThis.Text
End If
Next i
End Sub
Instead of adding and deleting and keeping track of a bunch of textboxes, I create all 75 (or fewer) at launch (or design time). Then I just make then visible or hide them as needed.
You can see the workbook I did this on here http://dailydoseofexcel.com/excel/ControlEventClass.xlsm