I have a userform where I put 10 rows of comboboxes for 7 columns. Which means I got 70 comboboxes altogether. To ease your understanding, I will refer the first combobox as (1,1) for (row,column).
What am I trying to do is, when a user input values on any combobox on Row 1, I want the values to be copied on its adjacent combobox at Row 2.
For example, if I select value on (1,3), same value will appear on (2,3). The same thing goes to Row 3 & 4, Row 5 & 6, and so on.
This is the code on my class module clsLineCopy:
Public WithEvents myCbo As msForms.ComboBox
Private Sub myCbo_Change()
Dim i As Integer
'MsgBox "Combo Box " & myCbo.Value & " has changed"
If myCbo.Value <> "" Then
myCbo.Copy
myCbo.Paste
End If
End Sub
This one is my code on my userform_initialize:
Dim myCommonCbo As New Collection
Dim cbo As clsLineCopy
For i = 1 To 70
Set cbo = New clsLineCopy
Set cbo.myCbo = Me.Controls("ComboBox" & i)
myCommonCbo.Add Item:=cbo
Next i
Set cbo = Nothing
I know my code in the class module is wrong as I have no idea about it.
Thanks,
Izz.
In my demo I named the Userform -> FormComboGrid
Here are the changes you need:
Userform: Public CallBack method
Userform: Class level boolean variable used to prevent cascading CallBacks
myCommonCbo has to be elevated to a Class Level Variable. This keeps the references valid after the UserForm_Initialize finishes execution.
clsLineCopy should have an Init method used to pass a reference of the Userform instance and the Combobox that is being hooked.
FormComboGrid:Class
Option Explicit
Private myCommonCbo As New Collection
Private ComboBoxEventEnabled As Boolean
Private Sub UserForm_Initialize()
Dim i As Long
Dim cbo As clsLineCopy
For i = 1 To 70
Set cbo = New clsLineCopy
cbo.Init Me, Me.Controls("ComboBox" & i)
myCommonCbo.Add Item:=cbo
' Me.Controls("ComboBox" & i).List = Array(1, 2, 3, 4, 5, 6, 7)
Next i
ComboBoxEventEnabled = True
End Sub
Public Sub ComboboxChange(cbo As MSForms.ComboBox)
If Not ComboBoxEventEnabled Then Exit Sub
ComboBoxEventEnabled = False
Dim index As Long, r As Long, c As Long
Dim myCbo As MSForms.ComboBox
index = Replace(cbo.Name, "ComboBox", "")
c = index Mod 10
r = Int(index / 10) + 1
If r = 7 Then Exit Sub
index = ((r * 10) + c)
Set myCbo = Me.Controls("ComboBox" & index)
myCbo.Value = cbo.Value
ComboBoxEventEnabled = True
End Sub
clsLineCopy:Class
Option Explicit
Private WithEvents myCbo As MSForms.ComboBox
Private mForm As FormComboGrid
Private Sub myCbo_Change()
mForm.ComboboxChange myCbo
End Sub
Public Sub Init(Form As FormComboGrid, cbo As MSForms.ComboBox)
Set mForm = Form
Set myCbo = cbo
End Sub
Related
I have a UserForm with a MultipageControl (name Controller_MultiPage).
At runtime my code adds pages to the Multipage and creates a newListView on each page.
Every ListView has:
With newListView
.MultiSelect = False
.Width = Controller_MultiPage.Width - 10
.Height = Controller_MultiPage.Height - 20
.View = lvwReport
.HideColumnHeaders = False
.ColumnHeaders.Add Text:="Signal Name", Width:=.Width / 10 * 4
.ColumnHeaders.Add Text:="Type", Width:=.Width / 10
.ColumnHeaders.Add Text:="I/O", Width:=.Width / 10
.ColumnHeaders.Add Text:="Description", Width:=.Width / 10 * 4
.CheckBoxes = True
.FullRowSelect = True
End With
then I populate the newListView with data from an XML file:
For Each node In list
With node.Attributes
Set listItem = newListView.ListItems.Add(Text:=.getNamedItem("Name").Text)
listItem.ListSubItems.Add = .getNamedItem("Type").Text
listItem.ListSubItems.Add = IIf(.getNamedItem("Input").Text = "1", "IN", "OUT")
listItem.ListSubItems.Add = .getNamedItem("Description").Text
listItem.Checked = False
End With
Next
but the checkboxes do not show. I can see the space for them in front of the first column and by clicking that space the checkbox of that particular row then appears. What I also noticed is that if I change the property
listItem.Checked = True
the behavior described above does not change, and when I click the free space in front of the first column (checkboxes space) the chsckbox that then shows up is still unchecked.
Any idea?
The problem seems to be in the behavior of the MultiPage control.
What I noticed was that if I forced the checkboxes' status (checked or unchecked) from the code, using the MultiPage_Change event, then the checkboxes show up.
So what I did was to create a class that holds the status of all checkboxes of all listviews on a single page, instantiate the Class for each ListView and store everything into a Dictionary, using the newListView.Name as Key
Then when the user changes page, the MultiPage_Change event that fires resets all the values of the checkboxes according to the Dictionary stored values.
In the Listview_N_ItemChecked event some other code updates the status of the item stored in the Dictionary.
Kind of cumbersome but it works.
the class (updated):
' Class Name = ComponentsSignalsRecord
Option Explicit
Dim Name As String
' NOTE: Signals(0) will always be empty and status(0) will always be False
Dim Signals() As String
Dim Status() As Boolean
Dim Component As String
Property Let SetComponentName(argName As String)
Component = argName
End Property
Property Get GetComponentName() As String
GetComponentName = Component
End Property
Property Get getSignalName(argIndex) As String
If argIndex >= LBound(Signals) And argIndex <= UBound(Signals) Then
getSignalName = Signals(argIndex)
Else
getSignalName = vbNullString
End If
End Property
Property Get dumpAll() As String()
dumpAll = Signals
End Property
Property Get Count() As Long
Count = UBound(Signals)
End Property
Property Get getStatus(argName As String) As Integer
' returns: -1 = Not Found; 1 = True; 0 = False
getStatus = -1
Dim i As Integer
For i = 0 To UBound(Signals)
If argName = Signals(i) Then getStatus = IIf(Status(i) = True, 1, 0): Exit For
Next
End Property
Property Let setName(argName As String)
Name = argName
End Property
Property Get getName() As String
getName = Name
End Property
Public Sub UncheckAll()
Dim i As Integer
For i = 0 To UBound(Status)
Status(i) = False
Next
End Sub
Public Sub CheckAll()
Dim i As Integer
For i = 0 To UBound(Status)
Status(i) = True
Next
End Sub
Public Sub deleteSignal(argName As String)
Dim spoolSignals() As String
Dim spoolStatus() As Boolean
Dim i As Integer
spoolSignals = Signals
spoolStatus = Status
ReDim Signals(0)
ReDim Status(0)
For i = 1 To UBound(spoolSignals)
If argName <> spoolSignals(i) Then
ReDim Preserve Signals(UBound(Signals) + 1): Signals(UBound(Signals)) = spoolSignals(i)
ReDim Preserve Status(UBound(Status) + 1): Status(UBound(Status)) = spoolStatus(i)
End If
Next
End Sub
Public Sub addSignal(argName As String, argValue As Boolean)
Dim i As Integer
For i = 0 To UBound(Signals)
If argName = Signals(i) Then GoTo bye
Next
ReDim Preserve Signals(UBound(Signals) + 1)
ReDim Preserve Status(UBound(Status) + 1)
Signals(UBound(Signals)) = argName
Status(UBound(Status)) = argValue
bye:
End Sub
Public Sub setStatus(argName As String, argValue As Boolean)
Dim i As Integer
For i = 0 To UBound(Signals)
If argName = Signals(i) Then Status(i) = argValue: Exit For
Next
End Sub
Private Sub Class_Initialize()
ReDim Signals(0)
ReDim Status(0)
End Sub
The Form relevant code. Module level:
Dim myDict As New Dictionary ' the Dictionary
Dim ComponentsSignalsList As ComponentsSignalsRecord ' the Class
for each ListView created, may be one or more for every single MultiPage page :
Set ComponentsSignalsList = New ComponentsSignalsRecord
ComponentsSignalsList.setName = newListView.name
while populating the listview(s) in a loop for each single item added:
ComponentsSignalsList.addSignal List_Item.Text, List_Item.Checked
end of each loop, add the Class instance to the Dictionary:
myDict.Add ComponentsSignalsList.getName, ComponentsSignalsList
Now when changing Page in the MultiPage widget:
Private Sub Controller_MultiPage_Change()
If isLoading Then Exit Sub 'avoid errors and undue behavior while initializing the MultiPage widget
Dim locControl As Control
Dim controlType As String: controlType = "ListView"
With Controller_MultiPage
For Each locControl In .Pages(.value).Controls
If InStr(1, TypeName(locControl), controlType) > 0 Then
Call Check_CheckBoxes(locControl)
End If
Next
End With
End Sub
Private Sub Check_CheckBoxes(argListView As listView)
If argListView.CheckBoxes = False Then Exit Sub 'some ListViews don't have checkboxes
Dim myItem As ListItem
For Each myItem In argListView.ListItems
With myItem
.Checked = myDict.Item(argListView.name).getStatus(.Text)
End With
Next
End Sub
when ticking/unticking a checkbox (note the the ItemChecked event handler is defined in another Class Public WithEvents, where the handler calls this method passing both the ListView ID and the Item object) :
Public Sub ListViewsEvents_ItemCheck(argListView As listView, argItem As MSComctlLib.ListItem)
With argItem
myDict.Item((argListView .name).setStatus argName:=.Text, argValue:=.Checked
End With
End Sub
I just found the answer to the same problem that I also had and I feel so stupid. I had the first column of the Listview set to Width = 0... and thus the checkboxes would no longer show.
I gave it a width and everithing is back to normal...
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:
I have a userform with multiple dependent Comboboxes. I would like to add the following code to 10 of the Comboboxes Change event. The Comboboxes to be coded are Numbered 11 to 20 (Combobox11, Combobox 12, etc) while the dependent Comboboxes are numbered 21 to 30.
I could copy and paste the code 10 times and then find and replace the relevant Combobox Nos.
Is there a way to use a loop through the Combo-Boxes to achieve this?
Any assistance would be most grateful.
Private Sub ComboBox11_Change()
Dim index As Integer
index = ComboBox11.ListIndex
ComboBox21.Clear
Select Case index
Case Is = 0
With ComboBox21
.RowSource = Range("SubCat1").Address(external:=True)
End With
Case Is = 1
With ComboBox21
.RowSource = Range("SubCat6").Address(external:=True)
End With
Case Is = 2
With ComboBox21
.RowSource = Range("SubCat7").Address(external:=True)
End With
Case Is = 3
With ComboBox21
.RowSource = Range("SubCat8").Address(external:=True)
End With
Case Is = 4
With ComboBox21
.RowSource = Range("SubCat9").Address(external:=True)
End With
'and several more case options
End Select
End Sub
You can use a Class Module, and a User_Init Sub to set each ComboBox control in the user form to this class.
In my code I used Main_Form as the name of the User_Form, modify the code according to your User_Form Name.
Add a Calls Module, and add this code below in Class 1:
Public WithEvents ComboBoxEvents As MSForms.ComboBox
' anytime a Change event occurs to any ComboBox, the Sub is triggered
Private Sub ComboBoxEvents_Change()
Dim ComboBox_Index As String
Dim index As Integer
With ComboBoxEvents
' read the index of the ComboBox, as long as the names remain ComboBox1, ComboBox2, ComboBox3, etc...
ComboBox_Index = Mid(.Name, 9)
' run this code if it's ComboBox 11 to 20
If ComboBox_Index >= 11 And ComboBox_Index <= 20 Then
index = .ListIndex
Select Case index
Case Is = 0
With Main_Form.Controls("ComboBox" & ComboBox_Index + 10)
.RowSource = Range("SubCat1").Address(external:=True)
End With
Case Is = 1
With Main_Form.Controls("ComboBox" & ComboBox_Index + 10)
.RowSource = Range("SubCat6").Address(external:=True)
End With
Case Is = 2
With Main_Form.Controls("ComboBox" & ComboBox_Index + 10)
.RowSource = Range("SubCat7").Address(external:=True)
End With
Case Is = 3
With Main_Form.Controls("ComboBox" & ComboBox_Index + 10)
.RowSource = Range("SubCat8").Address(external:=True)
End With
Case Is = 4
With Main_Form.Controls("ComboBox" & ComboBox_Index + 10)
.RowSource = Range("SubCat9").Address(external:=True)
End With
'and several more case options
End Select
End If
End With
End Sub
The code below goes in your User_Form_Init (in my code the name of the User_Form is Main-Form) :
Option Explicit
Dim ComboBoxes() As New Class1
Private Sub UserForm_Initialize()
Dim ComboBoxCounter As Integer, Obj As Control
For Each Obj In Me.Controls
If TypeOf Obj Is MSForms.ComboBox Then
ComboBoxCounter = ComboBoxCounter + 1
ReDim Preserve ComboBoxes(1 To ComboBoxCounter)
Set ComboBoxes(ComboBoxCounter).ComboBoxEvents = Obj
End If
Next Obj
Set Obj = Nothing
End Sub
the way is using Class
add a Class module and name it after "CmbBox" (you can choose whatever name but be consistent with it)
add the following code into the class code pane:
Option Explicit
Public WithEvents Cmb As MSForms.ComboBox
Private Sub Cmb_Change()
Dim index As Long
With Cmb
index = .ListIndex
With .Parent.Controls("ComboBox" & Mid(.Name, 9) + 10)
.Clear
Select Case index
Case 0
.RowSource = Range("SubCat1").Address(external:=True)
Case 1 To 4
.RowSource = Range("SubCat" & index + 5).Address(external:=True)
End Select
End With
End With
End Sub
Then switch to your userfom code pane and add this code:
Dim Cmbs(1 To 10) As New CmbBox '<--| this must be at the very top of your userform code pane
Sub Userform_Initialize()
Dim i As Long
With Me.Controls
For i = 11 To 20
Set Cmbs(i - 10).Cmb = .Item("ComboBox" & i)
Next i
End With
End Sub
and that's it
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
I have created a form where I am dynamically creating Textboxes and corresponding Comboboxes along with Combobox change event. Here is the class creating combobox event handler
Option Explicit
Public WithEvents cbx As MSforms.Combobox
Private avarSplit As Variant
Sub SetCombobox(ctl As MSforms.Combobox)
Set cbx = ctl
End Sub
Private Sub cbx_Change()
Dim i As Integer
If cbx.ListIndex > -1 Then
'MsgBox "You clicked on " & cbx.Name & vbLf & "The value is " & cbx.Value
avarSplit = Split(cbx.Name, "_")
'DecessionOnValue
End If
End Sub
And here is the code on the form which is dynamically creating textboxes and Comboboxes
Function AddTextBox(Frame1 As frame, numberOfColumns As Integer)
Dim counter As Integer
Dim i As Integer
Dim TxtBox As MSforms.TextBox
For counter = 1 To numberOfColumns
'Forms.CommandButton.1
Set TxtBox = Frame1.Controls.Add("Forms.TextBox.1")
TxtBox.Name = "tb_" + CStr(counter)
'Bouton.Caption = "Test"
TxtBox.Visible = True
i = Property.TextBoxDisable(TxtBox)
' Defining coordinates TextBox height is 18
If counter = 1 Then
TxtBox.Top = 23
Else
TxtBox.Top = (18 * counter) + 5 * counter
End If
TxtBox.Left = 50
Next counter
End Function
Function Combobox(Frame1 As frame, numberOfColumns As Integer)
Dim counter As Integer
Dim i As Integer
Dim CbBox As MSforms.Combobox
Dim cbx As ComboWithEvent
If pComboboxes Is Nothing Then Set pComboboxes = New Collection
For counter = 1 To numberOfColumns
'Forms.CommandButton.1
Set CbBox = Frame1.Controls.Add("Forms.ComboBox.1")
CbBox.Name = "cb_" + CStr(counter)
i = AddComboboxValues(CbBox)
' Defining coordinates TextBox height is 18
If counter = 1 Then
CbBox.Top = 23
Else
CbBox.Top = (18 * counter) + 5 * counter
End If
CbBox.Left = 150
Set cbx = New ComboWithEvent
cbx.SetCombobox CbBox
pComboboxes.Add cbx
Next counter
i = AddScrollBar(Frame1, counter)
End Function
Combobox event handler is working fine but my problem is that I dont know that how can I copy the text of textbox or enable disable the textbox according to the value selected in the dynamic combobox.
Thanks,
Jatin
you'll use this for example :
Me.Controls("ControlName").Visible = True or instead of Visible you can use enable, disable etc..