click event not working on programmatically / dynamically created optionbutton - excel

I have the following code that programmatically / dynamically creates a frame and adds an option button:
Private Sub ComboBox1_Change()
Dim cb1234Frame As MsForms.Frame
Dim opbtn1 As MsForms.OptionButton
Set cb1234Frame = RT_Graph_Form.Controls.Add("Forms.Frame.1")
With cb1234Frame
.Top = 132
.Left = 12
.Height = 30
.Width = 144
.Caption = "Number of Graphs to Display"
End With
Set opbtn1 = cb1234Frame.Controls.Add("Forms.OptionButton.1")
With opbtn1
.Top = 6
.Left = 6
.Height = 18
.Width = 21.75
.Caption = "1"
End With
End Sub
But then this does not work:
Private Sub opbtn1_Click()
MsgBox "Test Successful!!"
End Sub

The problem is that event handlers need to be bound at compile-time: you cannot create an event handler for a dynamically created control.
Add a new class module to your project, call it DynamicOptionButton. The role of this class is to wrap the MSForms control and have a compile-time reference to it:
Option Explicit
Private WithEvents Wrapper As MSForms.OptionButton
Public Sub Initialize(ByVal ctrl As MSForms.OptionButton)
Set Wrapper = ctrl
End Sub
Private Sub Wrapper_Click()
MsgBox "Works!"
End Sub
Note that only a subset of the events will be available to handle: what events are available, depend on the interface you're declaring the wrapper reference with - MSForms.Control has a number of events (and properties), MSForms.OptionButton has another set: you may need to declare both interfaces (i.e. 2 wrappers for the same object) in order to access all the members.
Now in your form's declarations section, you'll need to hold a reference to all wrappers, otherwise the objects just fall out of scope and the handlers won't work. A Collection can do that:
Option Explicit
Private ControlWrappers As Collection
Private Sub UserForm_Initialize()
Set ControlWrappers = New Collection
End Sub
'...
Private Sub CreateOptionButton()
Dim ctrl As MSForms.OptionButton
Set ctrl = Me.Controls.Add("Forms.OptionButton.1")
'set properties...
Dim wrap As DynamicOptionButton
Set wrap = New DynamicOptionButton
wrap.Initialize ctrl
ControlWrappers.Add wrap
End Sub
Be careful to never reference the form's class name in the form's own code-behind: the global-scope RT_Graph_Form identifier refers to a VBA-controlled "default instance" auto-instantiated object that may or may not be the actual form instance that's being shown. You want to add your dynamic controls to Me.Controls, not RT_Graph_Form.Controls.
Now, we can handle events of controls spawned at run-time, but there's another problem: the event handler in the DynamicOptionButton class has no reference to the form it's on!
Or does it?
Every MSForms control has a Parent property; you can get ahold of the parent UserForm by recursively going up the Parent property until the returned reference is a UserForm - and from there you can access everything that's publicly exposed.

I'm not sure it's appropriate, but I managed to do-ish it.
I'm creating the userform from thisworkbook direcly so everything is stored there. I did not need the parent property anywhere.
Option Explicit
Const FolderPath As String = "C:"
Public TESTS As New Collection
Public CONTROLWRAPPERS As New Collection
Sub gotothere()
On Error GoTo bleh
Call Shell("explorer.exe" & " " & FolderPath & "\" & ThisWorkbook.ActiveSheet.Range("C14").Value, vbNormalFocus)
Exit Sub
bleh: Call Shell("explorer.exe" & " " & FolderPath, vbNormalFocus)
End Sub
Sub ChooseFolder()
Call Createform
End Sub
Private Sub Createform()
Set TESTS = Nothing
Call listalltests
Call Module1.MakeUserForm
Dim i As Integer
For i = 1 To TESTS.Count
Call CreateCommandbuttonButton(i)
Next i
Formol.Show vbModeless
End Sub
Private Sub listalltests()
Dim objFSO As Object
Dim objFolder As Object
Dim objSubFolder As Object
Dim i As Integer
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFolder = objFSO.GetFolder(FolderPath & "\")
i = 1
For Each objSubFolder In objFolder.subfolders
TESTS.Add objSubFolder.Name
i = i + 1
Next objSubFolder
End Sub
Private Sub CreateCommandbuttonButton(pos As Integer)
Dim ctrl As MSForms.CommandButton
Set ctrl = Formol.Controls.Add("Forms.commandbutton.1")
With ctrl
.Caption = TESTS(pos)
If (pos * 20 + 2) > 600 Then
.Left = 130
.Top = (pos - 29) * 26 + 2
.Width = 102
Else
.Left = 12
.Top = pos * 26 + 2
.Width = 102
End If
End With
Dim wrap As DynamicOptionButton
Set wrap = New DynamicOptionButton
wrap.Initialize ctrl
CONTROLWRAPPERS.Add wrap
End Sub
The MakeUserForm function is stored in a module and just check if there is a form named formol and if not create it with a certain width & height. it's an empty form.
The class is the exact same as the one made by mathieu except for the Wrapper_click event.

Related

How to create _Change() Event for dynamically created TextBox in VBA UserForm?

I am trying to add _Change() event to dynamically created TextBox using classes in VBA. However there is nothing happening, when I try to run my code. Could you please point me where I am wrong?
I have got class conditionEventClass
Public WithEvents conditionEvent As MSForms.textBox
Public Property Let textBox(boxValue As MSForms.textBox)
Set conditionEvent = boxValue
End Property
Public Sub conditionEvent_Change()
MsgBox conditionEvent.Name & " changed."
End Sub
I have got following code in my module:
Sub addConditions()
Dim conditionCommand As conditionEventClass
Dim newTextBox As MSForms.textBox
Set newTextBox = commandRequestForm.MultiPage1(1).Controls.Add("Forms.TextBox.1", "conditionValue", True)
With newTextBox
.Name = "conditionValue"
.Left = 750
.height = 15
.Width = 100
.Top = 20
End With
Set conditionCommand = New conditionEventClass
conditionCommand.textBox = newTextBox
End Sub
I expect that my sub conditionEvent_Change() is going to show msgBox. But unfortunately nothing happens.
Talking about only a single Text Box, you can use the next simpler way:
1.Declare a private variable on top of the form code module (in the declarations area):
Private WithEvents myTextBox As MSForms.TextBox
Then, create the event for the above declared variable:
Private Sub myTextBox_Change()
MsgBox activecontrol.name & " changed."
End Sub
Use your adapted code as:
Sub addConditions()
Dim newTextBox As MSForms.TextBox
Set newTextBox = commandRequestForm.MultiPage1(1).Controls.Add("Forms.TextBox.1", "myTextBox", True)
With newTextBox
.left = 10
.height = 15
.width = 100
.top = 20
End With
Set myTextBox = newTextBox
End Sub
For 1 to 3, 4 such controls you can use the simpler (above shown) way. If you need creating on the fly a lot of such controls, I can show you how to adapt your code...
Edited:
Please, use the next working way using a class to be assigned to many text boxes created on the fly:
Copy the next code in a class module and name it 'clsTBox':
Option Explicit
Public WithEvents newTBox As MSForms.TextBox
Private Sub newTBox_Change()
MsgBox newTBox.name & " changed."
End Sub
2.Declare a Private variable on top of the form code module:
Private TBox() As New clsTBox
Use the next Sub to create three text boxes and assign the Click event to them:
Private Sub CreateThreeTB()
Dim i As Long, txtBox01 As MSForms.TextBox, leftX As Double, tWidth As Double, k As Long
leftX = 20: tWidth = 50
ReDim TBox(100) 'use here the maximum number of text boxes you intend creating
For i = 1 To 3
Set txtBox01 = Me.Controls.Add("Forms.TextBox.1", "dynTxtBox_" & i)
With txtBox01
.top = 10
.left = leftX: leftX = leftX + tWidth
.width = tWidth
.Text = "something" & i
End With
Set TBox(k).newTBox = txtBox01: k = k + 1
Next i
ReDim Preserve TBox(k - 1)
End Sub
Call the above Sub from Initialize event or from another control, play with the newly created text boxes value and see how the change event is triggered...

It is possible to access a dynamic object of Excel UserForm1 from code of UserForm2? Not from code of a module

I have a "Class" Module whit declaration and events of my dynamics labels
Option Explicit
Public WithEvents ClassLabel As MSForms.Label
Public Sub ClassLabel_Click()
If InStr(ClassLabel.Name, "LB_Label") Then
Set CurrentLabel = ClassLabel
'Bla Bla Bla
End If
End Sub
And I have created 15 dynamic labels, in the "FR_Runtime" frame of UserForm1, which I have saved in an array in the Controls_Init() Sub of a normal Module1 as follows
Option Explicit
Public gArrayClassLabel() As New Class
Public CurrentLabel As MSForms.Label
Public Sub Controls_Init()
Dim Row As Integer
Dim nRow As Integer
Dim H As Integer
Dim LB_Label As MSForms.Label
nRow = 15
H = 30
ReDim gArrayClassLabel(1 To nRow)
For Row = 1 To nRow
Set LB_Label = UserForm1.FR_Runtime.Controls.Add("Forms.Label.1")
With LB_Label
.Name = "LB_Label" & Row
.Caption = " Label " & Row & ", 2"
.Left = 100
.Top = H
.Width = 75
.Height = 18
.ForeColor = vbRed
.BackColor = vbWindowBackground
.BorderStyle = fmBorderStyleSingle
.SpecialEffect = fmSpecialEffectSunken
End With
Set gArrayClassLabel(Row).ClassLabel = LB_Label
H = H + 30
Next Row
End Sub
From my UserForm1.FR_Runtime_Exit() event (or any other UserForm1, Module1 code), I have access to the fifth dynamic label as follows
Private Sub FR_Runtime_Exit(ByVal Cancel As MSForms.ReturnBoolean)
Set glbCurrentLabel = gArrayClassLabel(5).ClassLabel
With UserForm1.TextBox1
.Text = Trim$(glbCurrentLabel.Caption)
.Left = glbCurrentLabel.Left
.Top = glbCurrentLabel.Top
.SelStart = 0
.SelLength = Len(glbCurrentLabel.Caption)
End With
End Sub
From UserForm2, I can access to a label created at design time: UserForm1.Label3.Caption = "This is an design time Label"
Can I acces the gArrayClassLabel(5).ClassLabel from a Sub of The UserForm2? Not from Module1 code.
Thanks Tragamor, I had not thought about the class modules properties. This is the new "Class" Module
Public WithEvents ClassLabel As MSForms.Label
Private sContent As String
'new Get property
Property Get Content() As String
Content= sContent
End Property
'new Let property
Property Let Content(NumLbl As String)
sContent = gArrayClassLabel(NumLbl).ClassLabel.Caption
End Property
Public Sub ClassLabel_Click()
If InStr(ClassLabel.Name, "LB_Label") Then
Set CurrentLabel = ClassLabel
'Bla Bla Bla
End If
End Sub
And I can access the fifth dynamic label Caption from anywhere in the code as follows
Dim NumDynamicLabel As New Class
'I use Let Content property to obtain the fifth dynamic label Caption
NumDynamicLabel.Content = 5
Dim CaptionDynamicLabel As String
'I use the Get Content Property to retrieve Caption of fifth dynamic label
CaptionDynamicLabel = NumDynamicLabel.Content

Trying to add Textboxes to a userform dynamically?

I have code inside a excel workbook that helps me create mass emails to send to users of various programs. I have a userform that pops up and the user populates all the info needed. but that only counts for one app at a time. Can someone share code with me that dynamically adds textboxes to a userform dependant on what checkboxes are ticked ?
In the first frame I have check boxes that indicate what applications are affected, second frame I have option buttons to describe what type of incident and then I would like the textboxes to appear according to what has been ticked.
Any guidance much appreciated as I think this is way too deep for me at the moment
I've reverse engineered this code it adds the boxes I want but I need to be able to populate them with cell data and then use it in the emails:
Option Explicit
Dim SpnColct As Collection
Private Sub CommandButton2_Click()
Dim cSpnEvnt As cControlEvent
Dim ctlSB As Control
Dim ctlTXT As Control
Dim lngCounter As Long
For lngCounter = 1 To 7
Set ctlTXT = Me.Frame7.Controls.Add("Forms.TextBox.1", "Text" & lngCounter)
ctlTXT.Name = "Text" & lngCounter
ctlTXT.Left = 5
ctlTXT.Height = 125: ctlTXT.Width = 280
ctlTXT.Top = (lngCounter - 1) * 125 + 2
Set cSpnEvnt = New cControlEvent
Set cSpnEvnt.SP = ctlSB
Set cSpnEvnt.TXT = ctlTXT
SpnColct.Add cSpnEvnt
Next lngCounter
Me.Frame1.ScrollHeight = (lngCounter - 1) * 17 + 2
End Sub
This added to a class module:
Option Explicit
Public WithEvents SP As MSForms.SpinButton
Public WithEvents TXT As MSForms.TextBox
Private Sub SP_SpinDown()
SP.Value = SP.Value - 1
MsgBox "Spin Down to " & SP.Value
End Sub
Private Sub SP_SpinUp()
SP.Value = SP.Value + 1
MsgBox "Spin Up to " & SP.Value
End Sub
Private Sub TXT_Change()
MsgBox "You changed the value."
End Sub
Updated This is going to be a bit of a long one - step through it see if you understand it. Have changed it to create the textboxes on the CheckBox_Click event but change to the commandbutton if you wish. Any more then this and I think you'll need to start a new question.
I've been doing something similar recently and found that the reason you're having issues is due to the order of loading objects. I unfortunately can't find the link that explains it at the moment (will update if can) but briefly to be able to achieve this you need an additional Class that does the loading of the objects, otherwise the Userform can't see them. This is the kind of solution that I came up with (using your example)
Userform:
Option Explicit
Private WithEvents cControls As EventController
Private Sub cControls_Click(ctrl As CheckBoxControl)
Dim tBox As TextBoxControl
Dim i As Long
Dim NextTop As Long, FrameHeight As Long
For i = 1 To cControls.GetControls.Count
Debug.Print TypeName(cControls.GetControl(i))
If TypeName(cControls.GetControl(i)) = "TextBoxControl" Then
Set tBox = cControls.GetControl(i)
If tBox.TXT.Parent Is Me.Frame7 Then
NextTop = tBox.Top + tBox.Height
End If
End If
Next i
Set tBox = cControls.AddTextBox
With tBox
.Height = 125
.Width = 280
.Left = 5
.Top = NextTop
.TXT.Text = ctrl.cBox.Caption
FrameHeight = NextTop + .Height
End With
If FrameHeight > Me.Frame7.InsideHeight Then
With Me.Frame7
.ScrollBars = fmScrollBarsVertical
.ScrollHeight = FrameHeight
.Scroll yAction:=6
End With
End If
End Sub
Private Sub UserForm_Initialize()
Dim i As Long
Dim cBox As CheckBoxControl
Set cControls = New EventController
' This can be set to a userform or a frame
Set cControls.UserForm = Me
For i = 1 To 8
Set cBox = cControls.AddCheckBox
cBox.cBox.Left = 5
With cBox.cBox
.Top = 5 + (i - 1) * .Height
.Caption = IIf(i = 8, "App Unknown", "App " & i)
End With
Next i
End Sub
Private Sub cControls_Change(ctrl As TextBoxControl)
' This can be handled in the class instead as you were - just doing it in the userform to show the exposing of the event
MsgBox ctrl.TXT.Name & " Change"
End Sub
Private Sub cControls_SpinDown(ctrl As TextBoxControl)
' This can be handled in the class instead as you were - just doing it in the userform to show the exposing of the event
With ctrl.SP
If .Value >0 Then
.Value = .Value - 1
End If
End With
MsgBox ctrl.SP.Name & " Spin Down"
End Sub
Private Sub cControls_SpinUp(ctrl As TextBoxControl)
' This can be handled in the class instead as you were - just doing it in the userform to show the exposing of the event
With ctrl.SP
.Value = .Value + 1
End With
MsgBox ctrl.SP.Name & " Spin Up"
End Sub
Classes - These need to be named as in bold
EventControl
Option Explicit
Private CtrlCollection As Collection
Private cUserForm As UserForm1
Public Event SpinDown(ctrl As TextBoxControl)
Public Event SpinUp(ctrl As TextBoxControl)
Public Event Change(ctrl As TextBoxControl)
Public Event Click(ctrl As CheckBoxControl)
Public Property Set UserForm(v As UserForm1)
Set cUserForm = v
End Property
Public Property Get UserForm() As UserForm1
Set UserForm = cUserForm
End Property
Public Function AddTextBox() As TextBoxControl
Dim tBox As TextBoxControl
Set tBox = New TextBoxControl
tBox.Initialize Me
CtrlCollection.Add tBox
Set AddTextBox = tBox
End Function
Public Function AddCheckBox() As CheckBoxControl
Dim cBox As New CheckBoxControl
cBox.Initalize Me
CtrlCollection.Add cBox
Set AddCheckBox = cBox
End Function
Public Function GetControl(Index As Long)
Set GetControl = CtrlCollection(Index)
End Function
Public Function GetControls() As Collection
Set GetControls = CtrlCollection
End Function
Private Sub Class_Initialize()
Set CtrlCollection = New Collection
End Sub
Public Sub SpinDown(ctrl As TextBoxControl)
RaiseEvent SpinDown(ctrl)
End Sub
Public Sub SpinUp(ctrl As TextBoxControl)
RaiseEvent SpinUp(ctrl)
End Sub
Public Sub Change(ctrl As TextBoxControl)
RaiseEvent Change(ctrl)
End Sub
Public Sub Click(ctrl As CheckBoxControl)
RaiseEvent Click(ctrl)
End Sub
CheckBoxControl
Option Explicit
Public WithEvents cBox As MSForms.CheckBox
Private cParent As EventController
Public Property Set Parent(v As EventController)
Set cParent = v
End Property
Public Property Get Parent() As EventController
Set Parent = cParent
End Property
Public Sub Initalize(Parent As EventController)
Set Me.Parent = Parent
Set cBox = Parent.UserForm.Frame1.Controls.Add("Forms.CheckBox.1")
End Sub
Private Sub cBox_Click()
Parent.Click Me
End Sub
TextBoxControl
Option Explicit
Public WithEvents SP As MSForms.SpinButton
Public WithEvents TXT As MSForms.TextBox
Private cParent As EventController
Public Sub Initialize(Parent As EventController)
Set Me.Parent = Parent
With Parent.UserForm.Frame7.Controls
Set SP = .Add("Forms.SpinButton.1")
Set TXT = .Add("Forms.TextBox.1")
End With
End Sub
Public Property Set Parent(v As EventController)
Set cParent = v
End Property
Public Property Get Parent() As EventController
Set Parent = cParent
End Property
Public Property Let Left(v As Single)
TXT.Left = v
SP.Left = TXT.Left + TXT.Width
End Property
Public Property Get Left() As Single
Left = TXT.Left
End Property
Public Property Let Top(v As Single)
TXT.Top = v
SP.Top = v
End Property
Public Property Get Top() As Single
Top = TXT.Top
End Property
Public Property Let Height(v As Single)
TXT.Height = v
SP.Height = v
End Property
Public Property Get Height() As Single
Height = TXT.Height
End Property
Public Property Let Width(v As Single)
TXT.Width = v - SP.Width
SP.Left = TXT.Left + TXT.Width
End Property
Public Property Get Width() As Single
Width = TXT.Width + SP.Width
End Property
Public Sub SP_SpinDown()
Parent.SpinDown Me
' SP.Value = SP.Value - 1
' MsgBox "Spin Down to " & SP.Value
End Sub
' The commented out lines below you can either leave in here, or handle in the Userform
Public Sub SP_SpinUp()
Parent.SpinUp Me
' SP.Value = SP.Value + 1
' MsgBox "Spin Up to " & SP.Value
End Sub
Public Sub TXT_Change()
Parent.Change Me
' MsgBox "You changed the value."
End Sub
The issue is stemmed from that when the Userform is loaded the controls aren't loaded and therefore the Userform hasn't registered that they're something that has an Event. By using the intermediary class the Userform recognises that that class has an Event and we load this statically on initialize of the Userform. We can then add in whatever Controls we want to this Class and the Userform will handle them.
Demo:

Class Object TextBox on Userform available methods

I have noticed that when I create a Class Module for a Textbox and use that on my forms - by adding via VBA in the form init event - neither the Enter or Exit methods are available. Of course if I just add a textbox to the form they are.
I can get the DblClick method to work fine so my class is setup correctly and my form.init code is also ok.
When I look in the Object browser for MSForms.TextBox I see it doesn't have Enter or Exit methods and presume this is the reason.
There is no urgency for me on this because I noticed it when building my first form that uses my own classes for textbox - Fortunately for what I'm working on now - I don't actually need the Enter or Exit methods, but thought someone might know if there is a work-around because I may need them in the future and for textboxes they are very useful methods
Here is my Class Code named nxTxtV
Option Explicit
Public WithEvents oTxtV As MSForms.TextBox
Private Sub oTxtV_Enter()
' This method never fires
MsgBox "Hello World from the Enter Method"
End Sub
Private Sub oTxtV_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
' This method works fine
MsgBox "Hello World from the DoubleClick Method for " & oTxtV.Value
End Sub
and here's my form initialise code
Private Sub UserForm_Initialize()
Dim xItm As Control, i As Long
Dim dItm As nxTxtV ' nxTxtV is the name of my class
For i = 1 To 5
Set xItm = Me.Controls.Add("Forms.TextBox.1", "Column" & i, True)
With xItm
.Value = "P" & i
.AutoSize = False
.Font.Size = 9
.Width = 25
.Height = 250
.TextAlign = 2 ' Centred
.SpecialEffect = 0
.BackColor = RGB(255, 128, 0)
.WordWrap = True
.Left = 200 + (i * 27)
.Top = 5
.Enabled = True
.Visible = True
End With
Set dItm = New nxTxtV ' nxTxtV is the name of my class
Set dItm.oTxtV = xItm
CodedObjs.Add dItm ' CodedObjs is declared at module level (form level) as a Collection
Next i
End Sub
In short, can I get my class to react to Enter & Exit events ?
Addendum - the CodedObjs Declaration
Public CodedObjs As New Collection

Use VBA to assign all checkboxes to class module

I'm having a problem assigning VBA generated ActiveX checkboxes to a class module. When a user clicks a button, the goal of what I am trying to achieve is: 1st - delete all the checkboxes on the excel sheet; 2nd - auto generate a bunch of checkboxes; 3rd - assign a class module to these new checkboxes so when the user subsequently clicks one of them, the class module runs.
I've borrowed heavily from previous posts Make vba code work for all boxes
The problem I've having is that the 3rd routine (to assign a class module to the new checkboxes) doesn't work when run subsequently to the first 2 routines. It runs fine if run standalone after the checkboxes have been created. From the best I can tell, it appears VBA isn't "releasing" the checkboxes after they have been created to allow the class module to be assigned.
The below code is the simplified code that demonstrates this problem. In this code, I use a button on "Sheet1" to run Sub RunMyCheckBoxes(). When button 1 is clicked, the class module did not get assigned to the newly generated checkboxes. I use button 2 on "Sheet1" to run Sub RunAfter(). If button 2 is clicked after button 1 has been clicked, the checkboxes will be assigned to the class module. I can't figure out why the class module won't be assigned if just the first button is clicked. Help please.
Module1:
Public mcolEvents As Collection
Sub RunMyCheckboxes()
Dim i As Double
Call DeleteAllCheckboxesOnSheet("Sheet1")
For i = 1 To 10
Call InsertCheckBoxes("Sheet1", i, 1, "CB" & i & "1")
Call InsertCheckBoxes("Sheet1", i, 2, "CB" & i & "2")
Next
Call SetCBAction("Sheet1")
End Sub
Sub DeleteAllCheckboxesOnSheet(SheetName As String)
Dim obj As OLEObject
For Each obj In Sheets(SheetName).OLEObjects
If TypeOf obj.Object Is MSForms.CheckBox Then
obj.Delete
End If
Next
End Sub
Sub InsertCheckBoxes(SheetName As String, CellRow As Double, CellColumn As Double, CBName As String)
Dim CellLeft As Double
Dim CellWidth As Double
Dim CellTop As Double
Dim CellHeight As Double
Dim CellHCenter As Double
Dim CellVCenter As Double
CellLeft = Sheets(SheetName).Cells(CellRow, CellColumn).Left
CellWidth = Sheets(SheetName).Cells(CellRow, CellColumn).Width
CellTop = Sheets(SheetName).Cells(CellRow, CellColumn).Top
CellHeight = Sheets(SheetName).Cells(CellRow, CellColumn).Height
CellHCenter = CellLeft + CellWidth / 2
CellVCenter = CellTop + CellHeight / 2
With Sheets(SheetName).OLEObjects.Add(classtype:="Forms.CheckBox.1", Link:=False, DisplayAsIcon:=False, Left:=CellHCenter - 8, Top:=CellVCenter - 8, Width:=16, Height:=16)
.Name = CBName
.Object.Caption = ""
.Object.BackStyle = 0
.ShapeRange.Fill.Transparency = 1#
End With
End Sub
Sub SetCBAction(SheetName)
Dim cCBEvents As clsActiveXEvents
Dim o As OLEObject
Set mcolEvents = New Collection
For Each o In Sheets(SheetName).OLEObjects
If TypeName(o.Object) = "CheckBox" Then
Set cCBEvents = New clsActiveXEvents
Set cCBEvents.mCheckBoxes = o.Object
mcolEvents.Add cCBEvents
End If
Next
End Sub
Sub RunAfter()
Call SetCBAction("Sheet1")
End Sub
Class Module (clsActiveXEvents):
Option Explicit
Public WithEvents mCheckBoxes As MSForms.CheckBox
Private Sub mCheckBoxes_click()
MsgBox "test"
End Sub
UPDATE:
On further research, there is a solution posted in the bottom answer here:
Creating events for checkbox at runtime Excel VBA
Apparently you need to force Excel VBA to run on time now:
Application.OnTime Now ""
Edited lines of code that works to resolve this issue:
Sub RunMyCheckboxes()
Dim i As Double
Call DeleteAllCheckboxesOnSheet("Sheet1")
For i = 1 To 10
Call InsertCheckBoxes("Sheet1", i, 1, "CB" & i & "1")
Call InsertCheckBoxes("Sheet1", i, 2, "CB" & i & "2")
Next
Application.OnTime Now, "SetCBAction" '''This is the line that changed
End Sub
And, with this new formatting:
Sub SetCBAction() ''''no longer passing sheet name with new format
Dim cCBEvents As clsActiveXEvents
Dim o As OLEObject
Set mcolEvents = New Collection
For Each o In Sheets("Sheet1").OLEObjects '''''No longer passing sheet name with new format
If TypeName(o.Object) = "CheckBox" Then
Set cCBEvents = New clsActiveXEvents
Set cCBEvents.mCheckBoxes = o.Object
mcolEvents.Add cCBEvents
End If
Next
End Sub
If OLE objects suit your needs then I'm glad you've found a solution.
Are you aware, though, that Excel's Checkbox object could make this task considerably simpler ... and faster? Its simplicity lies in the fact that you can easily iterate the Checkboxes collection and that you can access its .OnAction property. It is also easy to identify the 'sender' by exploiting the Evaluate function. It has some formatting functions if you need to tailor its appearance.
If you're after something quick and easy then the sample below will give you an idea of how your entire task could be codified:
Public Sub RunMe()
Const BOX_SIZE As Integer = 16
Dim ws As Worksheet
Dim cell As Range
Dim cbox As CheckBox
Dim i As Integer, j As Integer
Dim boxLeft As Double, boxTop As Double
Set ws = ThisWorkbook.Worksheets("Sheet1")
'Delete checkboxes
For Each cbox In ws.CheckBoxes
cbox.Delete
Next
'Add checkboxes
For i = 1 To 10
For j = 1 To 2
Set cell = ws.Cells(i, j)
With cell
boxLeft = .Width / 2 - BOX_SIZE / 2 + .Left
boxTop = .Height / 2 - BOX_SIZE / 2 + .Top
End With
Set cbox = ws.CheckBoxes.Add(boxLeft, boxTop, BOX_SIZE, BOX_SIZE)
With cbox
.Name = "CB" & i & j
.Caption = ""
.OnAction = "CheckBox_Clicked"
End With
Next
Next
End Sub
Sub CheckBox_Clicked()
Dim sender As CheckBox
Set sender = Evaluate(Application.Caller)
MsgBox sender.Name & " now " & IIf(sender.Value = 1, "Checked", "Unchecked")
End Sub

Resources