I'm making a Userform in VBA where I have Text Box's where I can search items. I have about 30 Text Box's so I want to cut down the code using a loop instead of copy and pasting the same code 30 times.
Problem: I don't know how to loop through a public variable
Public Variable: Public oEventHandler(Number) As New clsSearchableDropdown
oEventHandler would go from 1 to 30 (e.g oEventHandler2,oEventHandler3...oEventHandler30)
clsSearchableDropdown is the Class Module for the search feature
Text Box: TextBox(Number)
ListBox: ListBox(Number)
Here is the original code (No Issue Just to Compare):
With oEventHandler1
' Attach the textbox and listbox to the class
Set .SearchListBox = Me.ListBox1
Set .SearchTextBox = Me.TextBox1
' Default settings
.MaxRows = 10
.ShowAllMatches = True
.CompareMethod = vbTextCompare
.WindowsVersion = False
End With
This is what I'm trying to do:
Dim i As Integer
for i = 1 to 30
With Me.Controls.Item("oEventHandler" & i)
' Attach the textbox and listbox to the class
Set .SearchListBox = Me.Controls.Item("ListBox" & i)
Set .SearchTextBox = Me.Controls.Item("TextBox" & i)
' Default settings
.MaxRows = 10
.ShowAllMatches = True
.CompareMethod = vbTextCompare
.WindowsVersion = False
End With
Next i
I know that oEventHandler is not a control but is there a similar code I can use to loop through a public variable?
Here is the code that worked for me:
' Make a New Collection
Dim coll As New Collection
' Add all Public Variables to Collection (n = Number 1 to 30)
coll.Add uQuote.oEventHandler(n)
(e.g oEventHandler1, oEventHandler2... oEventHandler30)
Dim i As Integer
for i = 1 to 30
With coll(i)
' Attach the textbox and listbox to the class
Set .SearchListBox = Me.Controls.Item("ListBox" & i)
Set .SearchTextBox = Me.Controls.Item("TextBox" & i)
' Default settings
.MaxRows = 10
.ShowAllMatches = True
.CompareMethod = vbTextCompare
.WindowsVersion = False
End With
Next i
If I understand you correctly, in the userform you have 30 Textboxes and 30 Listboxes, where each Textbox(N) is to search the value in the Listbox(N) located under that TextBox(N). So it looks something like this :
On the left side is TextBox01, under TextBox01 is ListBox01
On the right side is TextBox02, under TextBox02 is ListBox02
If the animation is similar with your expectation....
Preparation :
Make a named range (as many as needed) with something like List01, List02, List03, and so on for the value to populate each ListBox.
Name each ListBox with something like ListBox01, ListBox02, and so on.
Name each TextBox with something like TextBox01, TextBox02, and so on.
In the Userform module:
Dim MyTextBoxes As Collection
Private Sub UserForm_Initialize()
'populate the ListBoxes with value in a named range
Dim LBname As String: Dim RGname As String: Dim i As Integer
For i = 1 To 2
LBname = "ListBox" & Format(i, "00")
RGname = "List" & Format(i, "00")
Controls(LBname).List = Application.Transpose(Range(RGname))
Next i
'add each TextBox to class
Set MyTextBoxes = New Collection
For Each ctl In Me.Controls
Set TextBoxClass = New Class1
If TypeName(ctl) = "TextBox" And InStr(ctl.Name, "TextBox") Then Set TextBoxClass.obj = ctl
MyTextBoxes.Add TextBoxClass
Next
End Sub
In the Class Module named Class1:
Private WithEvents tb As MSForms.TextBox
Property Set obj(t As MSForms.TextBox)
Set tb = t
End Property
Private Sub tb_Change()
Dim idx As String: Dim LBname As String: Dim arr
idx = Right(tb.Name, 2)
LBname = "ListBox" & idx
arr = Application.Transpose(Range("List" & idx))
With Userform1.Controls(LBname)
If tb.text = "" Then
.Clear
.List = arr
Else
.Clear
For i = LBound(arr, 1) To UBound(arr, 1)
If LCase(arr(i)) Like "*" & LCase(tb.value) & "*" Then .AddItem arr(i)
Next i
End If
End With
End Sub
If in your userform you have another textbox which not to use as a search of the items in respective listbox, then maybe don't name the textbox with "TextBox" but something else, for example "blablabla".
if your existing textbox and listbox already named something like ListBox1, ListBox2, ListBox3 and so on, TextBox1, TextBox2, TextBox3 and so on... then name the named range like List1, List2, List3 and so on. In the class module, change the code for idx using the replace method, something like idx = replace(tb.name,"TextBox",""). Also in the Userform module for LBname and RGname use the replace method.
Because I'm limited in English language, I'm sorry I can't detail the code for further explanation.
Related
So I have a form called "Print_Form" that has 20 checkboxes that upon form initialization take on the sheet names of the first 20 sheets of my workbook.
(no issue with the UserForm_Initialize() sub, this works fine)
Private Sub UserForm_Initialize()
CheckBox1.Caption = Sheets(1).Name
CheckBox2.Caption = Sheets(2).Name
CheckBox3.Caption = Sheets(3).Name
CheckBox4.Caption = Sheets(4).Name
CheckBox5.Caption = Sheets(5).Name
CheckBox6.Caption = Sheets(6).Name
CheckBox7.Caption = Sheets(7).Name
CheckBox8.Caption = Sheets(8).Name
CheckBox9.Caption = Sheets(9).Name
CheckBox10.Caption = Sheets(10).Name
CheckBox11.Caption = Sheets(11).Name
CheckBox12.Caption = Sheets(12).Name
CheckBox13.Caption = Sheets(13).Name
CheckBox14.Caption = Sheets(14).Name
CheckBox15.Caption = Sheets(15).Name
CheckBox16.Caption = Sheets(16).Name
CheckBox17.Caption = Sheets(17).Name
CheckBox18.Caption = Sheets(18).Name
CheckBox19.Caption = Sheets(19).Name
CheckBox20.Caption = Sheets(20).Name
End Sub
Where I am running into issues is in the following sub routine when the user clicks the print button in the form. The intention behind this button is to print all the sheets that the user has selected (i.e. the sheets that had their corresponding checkbox checked by the user). Currently, when I select multiple checkboxes and then click on the print button I get the following error; "Run-Time error '9': Subscript out of range.
Private Sub cmdPrint_Click()
Dim i As Integer
Dim cb As MSForms.Control
Dim SheetArray() As String
i = 0
'Search form for a checkbox
For Each cb In Me.Controls
i = i + 1
ReDim Preserve SheetArray(i)
'If the control is a checkbox
If TypeName(cb) = "CheckBox" Then
'and the checkbox is checked
If cb.Value = True Then
'Add the sheet to the sheet array (sheet name string was already added to the checkbox property caption; see UserForm_initialize)
SheetArray(i) = cb.Caption
End If
End If
Next cb
'Print Sheet Array
Sheets(SheetArray()).PrintOut
Unload Me
End Sub
If anyone has any ideas that would help me get this to work I would be very appreciative. Thank you in advance. :)
Try this:
Private Sub UserForm_Initialize()
Dim i As Long
For i = 1 To 20 'less typing....
Me.Controls("CheckBox" & i).Caption = Sheets(i).Name
Next i
End Sub
Private Sub cmdPrint_Click()
Dim i As Integer, s As String, sep
For i = 1 To 20
With Me.Controls("CheckBox" & i)
If .Value Then
s = s & sep & .Caption
sep = "," 'add delimiter after first item
End If
End With
Next i
Sheets(Split(s, ",")).PrintOut
Unload Me
End Sub
I have a Userform with multiple Controls (Textbox). Those Textboxes will be populated by selecting a ListBox item.
When initializing the UserForm those TextBoxes will be assigned to a specific Class which handles them.
I want VBA to change the background color of those textboxes only when a real change of the value was performed. What I have is that the BackgroundColor is always changed as soon as a change was performed, but that's not what I want.
Example #1:
Textbox value before change: "test"
Textbox value after change: "test2"
--> BackgroundColor should be changed
Example #2:
Textbox value before change: "test"
Textbox value after change: "test bla" but then I am typing "test" again.
--> BackgroundColor should not be changed, because initial Value is in the TextBox again.
What I have so far:
' **************************************************************
' Module: clsTextbox Typ = Class Module
' **************************************************************
Public WithEvents mTextBoxs As MSForms.TextBox
Private Sub mTextBoxs_Change()
If mTextBoxs.Text = strInitialVal Then
Reset_BackColor
Else
mTextBoxs.BackColor = RGB(255, 255, 153)
End If
End Sub
Public Sub Reset_BackColor()
mTextBoxs.BackColor = RGB(255, 255, 255)
End Sub
' **************************************************************
' Module: frmEmployee Type = Userform
' **************************************************************
Dim arrLabels() As New clsLabel, UBoundarrLabels As Integer
Dim arrTextBoxs() As New clsTextbox, UBoundarrTextBoxs As Integer
Private Sub UserForm_Initialize()
Dim Ctrl As Control, obLabel As MSForms.Label, obTextbox As MSForms.TextBox
tblName = "tblMitarbeiter"
Set wb = ThisWorkbook
Set ws = wb.Sheets("Mitarbeiter")
i = 0
For Each Ctrl In Me.Controls
If Left(Ctrl.Name, 7) = "TextBox" Then
i = i + 1
ReDim Preserve arrTextBoxs(i)
Set obTextbox = Me.Controls("TextBox" & i)
Set arrTextBoxs(i).mTextBoxs = obTextbox
End If
Next Ctrl
' Fill Listbox1 with values (Vorname & Nachname) from Table [tblMitarbeiter]
Dim lngLastRow As Long: lngLastRow = getListLastRow(ws, tblName)
Dim vArrListBox1() As Variant
ReDim vArrListBox1(0 To lngLastRow - 1, 0 To 2)
For j = 1 To lngLastRow
vArrListBox1(j - 1, 0) = ws.ListObjects("tblMitarbeiter").DataBodyRange(j, 1).Value
vArrListBox1(j - 1, 1) = ws.ListObjects("tblMitarbeiter").DataBodyRange(j, 2).Value
vArrListBox1(j - 1, 2) = ws.ListObjects("tblMitarbeiter").DataBodyRange(j, 3).Value
Next j
For t = 1 To 4
Me.Controls("TextBox" & t) = vArrEmployee(t - 1)
Next t
strInitialVal = Me.Controls("TextBox2")
End Sub
My thoughts are:
As you can see I tried to declare a public variable (strInitialVal) in a Module which gets the initial value of a textbox (e.g. TextBox2) and when performing the mTextBoxs_Change() Event it checks whether the strInitialVal is the same as the value in the Textbox and so on.
--> this works, but only for a 1:1 relation of the variable and a textbox.
How can I manage to load all textbox values into an Array? and Check the values in the TextBox Class afterwards.
If you need more information please let me know. I hope I did not violate any SO-rules.
You could use collections to hold your textboxes.
After option explicit, declare these collections
Private ColTxtBox As New Collection
Then when dynamically creating textboxes, here is one example:
`'Create Unit size; textbox
Set TxtBox = New DynamicTxtbox
TxtBox.Row = FormRows
TxtBox.Column = A_Deliv.F_UnitSize
Call TxtBox.InitText(frm_delivery.Frame1, "txtbox" & TxtBox.Row) 'this is the
constructor
ColTxtBox.Add TxtBox`
However, if you already created all textboxes on your form and they are fixed, it is sufficient to run through all controls and add these to your collection (if it's a textbox!). Then in your textbox class, you can easily loop through all your texboxes - and access their current values with something like
`Private Sub mTextBoxs_Change()
For Each TxtBox In ColTxtBox
If InStr(Me.Value,TxtBox.Value) > 0 Then
Reset_BackColor
Else
mTextBoxs.BackColor = RGB(255, 255, 153)
End If
Next TxtBox
EndS Sub`
I've created a code in VBA to collect data using a multi-page control. In each page, I've added checkboxes dynamically based on rows from the worksheet in Excel and, for each checkbox, there's a textbox and 2 command buttons, just like the image below:
Input Window:
The code to automatically add controls is:
Private Sub UserForm_Initialize()
fmat_disp.Value = 0
fmat_set.Value = 0
'---------------------------------------------------------------------------------------------
'Inspeção de Mecânica
Sheets("Mecânica").Activate
n_anom = Application.WorksheetFunction.CountA(Range("1:1")) - 1
AreasInspecao.mecanica.ScrollHeight = 10 + 18 * (n_anom)
For i = 1 To n_anom
'Selecionar anomalia
Set SelAnom = AreasInspecao.mecanica.Controls.Add("Forms.CheckBox.1", "sel_anom_" & i)
SelAnom.Caption = Worksheets("Mecânica").Cells(1, i + 1)
SelAnom.AutoSize = True
SelAnom.Height = 18
SelAnom.Left = 5
SelAnom.Top = 5 + (SelAnom.Height) * (i - 1)
SelAnom.Tag = i
Same goes to the textbox and plus/minus buttons, only changing the captions.
What I want is:
1) when CHECKBOX is CHECKED, respective TEXTBOX must show 1
2) when MINUS sign is PRESSED, respective TEXTBOX must decrement
3) when PLUS sign is PRESSED, respective TEXTBOX must increment
4) when "Finalizar Inspeção" is PRESSED, all data collected must be sent to Excel, filling a worksheet.
I simply don't know how to link each button/checkbox to your respective textbox without creating a subroutine for each one! I'll have ~500 subroutines....that's impossible to manage manually....
OK here's a rough outline for handling the click events on the checkboxes and buttons.
First two custom classes for capturing the clicks: each of these is very simple - all they do is call a method on the userform with the clicked control as an argument.
'clsCheck
Public WithEvents chk As MSForms.CheckBox
Private Sub chk_Click()
frmExample.HandleClick chk
End Sub
'clsButton
Public WithEvents btn As MSForms.CommandButton
Private Sub btn_Click()
frmExample.HandleClick btn
End Sub
Userform code - my form is named "frmExample".
Note the naming convention which allows groups of controls to be treated as a "unit".
Option Explicit
'These two global collections hold instances of the custom classes
Dim colCheckBoxes As Collection
Dim colButtons As Collection
Private Sub UserForm_Activate()
Const CON_HT As Long = 18
Dim x As Long, cbx As MSForms.CheckBox, t
Dim btn As MSForms.CommandButton, txt As MSForms.TextBox
Dim oCheck As clsCheck, oButton As clsButton
Set colCheckBoxes = New Collection
Set colButtons = New Collection
For x = 1 To 10
t = 5 + CON_HT * (x - 1)
Set cbx = Me.Controls.Add("Forms.CheckBox.1", "cbox_" & x)
cbx.Caption = "Checkbox" & x
cbx.Width = 80
cbx.Height = CON_HT
cbx.Left = 5
cbx.Top = t
colCheckBoxes.Add GetCheckHandler(cbx) '<< save in collection
Set btn = Me.Controls.Add("Forms.CommandButton.1", "btnplus_" & x)
btn.Caption = "+"
btn.Height = CON_HT
btn.Width = 20
btn.Left = 90
btn.Top = t
btn.Enabled = False '<<buttons start off disabled
colButtons.Add GetButtonHandler(btn) '<< save in collection
Set btn = Me.Controls.Add("Forms.CommandButton.1", "btnminus_" & x)
btn.Caption = "-"
btn.Height = CON_HT
btn.Width = 20
btn.Left = 130
btn.Top = t
btn.Enabled = False '<<buttons start off disabled
colButtons.Add GetButtonHandler(btn) '<< save in collection
'no events are captured for the textboxes...
Set txt = Me.Controls.Add("Forms.Textbox.1", "txt_" & x)
txt.Width = 30
txt.Height = CON_HT
txt.Left = 170
txt.Top = t
Next x
End Sub
'All "clicked" controls saved in instances of the custom classes
' get passed here. Handle based on control type/name
Public Sub HandleClick(ctrl As MSForms.Control)
Dim num
num = Split(ctrl.Name, "_")(1) 'which set of controls are we working with?
Dim txt As MSForms.TextBox
'get the matching text box...
Set txt = Me.Controls("txt_" & num)
If ctrl.Name Like "cbox_*" Then
If ctrl.Value Then txt.Value = 1
Me.Controls("btnplus_" & num).Enabled = ctrl.Value
Me.Controls("btnminus_" & num).Enabled = ctrl.Value
ElseIf ctrl.Name Like "btnplus_*" Then
txt.Value = txt.Value + 1
ElseIf ctrl.Name Like "btnminus_*" Then
txt.Value = txt.Value - 1
End If
End Sub
'couple of "factory" functions for the event-handling classes
Private Function GetCheckHandler(cb As MSForms.CheckBox)
Dim rv As New clsCheck
Set rv.chk = cb
Set GetCheckHandler = rv
End Function
Private Function GetButtonHandler(btn As MSForms.CommandButton)
Dim rv As New clsButton
Set rv.btn = btn
Set GetButtonHandler = rv
End Function
Sample file: https://www.dropbox.com/s/k74c08m0zkwn9l7/tmpFormEvents.xlsm?dl=0
Background:
I have a collection of objects (for this example Listbox objects) in a userform using standardized names, I would like to rename them dynamically using a counter cycle.
Problem:
I have not figured a way if what I am asking is even possible, however, I would like to confirm it.
Solution approach:
Nothing so far, like I said (refer to the image above) I need a way to set the values of the objects within the for cycle, something like this:
For CounterItems = 1 To 18 'Hours in Template
ListBox_Time(CounterItems).Value="Dummy" & CounterItems
Next CounterHours
However, I am clueless on how to do so (or if it is achievable).
Question:
Is there any way to use a counter to cast a variable/object?
No, you can't edit the name while the userform is in use, you'll get error 382
What you'd like to do is this
Option Explicit
Sub test()
Dim myForm As UserForm
Set myForm = UserForm1
Dim myCtrl As Control
Dim i As Long
Dim myCount As Long
myCount = 1
For Each myCtrl In myForm.Controls
If TypeName(myCtrl) = "ListBox" Then
myCtrl.Name = "Dummy" & myCount 'error
myCount = myCount + 1
End If
Next
End Sub
But you'll error when you try to write to the name property. You can print the names or set other properties, but this isn't something you can do as far as I know.
For use with ListBox controls on a UserForm
If you want to change only certain ListBox controls by number:
Public Sub ListBoxNameChange()
Dim ctrl As Control
Dim ctrlName As String, ctrlNum As Integer
For Each ctrl In Me.Controls
If TypeName(ctrl) = "ListBox" Then
ctrlName = ctrl.Name
ctrlNum = CInt(Replace(ctrlName, "ListBox_Time", ""))
If ctrlNum > 0 And ctrlNum < 19 Then
ctrl.AddItem "Dummy" & ctrlNum, 0
End If
End If
Next ctrl
End Sub
If you want to change ALL ListBox controls:
Public Sub ListBoxNameChange2()
Dim ctrl As Control
Dim ctrlName As String
For Each ctrl In Me.Controls
If TypeName(ctrl) = "ListBox" Then _
ctrl.AddItem "Dummy" & Replace(ctrl.Name, "ListBox_Time", ""), 0
Next ctrl
End Sub
I treat them like Shapes and test their pre-defined Names:
Sub ShapeRenamer()
Dim s As Shape
For Each s In ActiveSheet.Shapes
If s.Name = "List Box 6" Then s.Name = "Sixth"
Next s
End Sub
Before:
and after:
You would update this to examine the Shapes in your userform.
You could also do this with an indexing counter.
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