Detecting Event on ComboBoxes Added at Runtime on Excel - vba

I have a problem with my VBA script in Excel. What I do is basically creating buttons that when pressed will create a set of two extra comboboxes in one of the sheet. This button can be pressed continuously to add more comboboxes.
These newly created comboboxes will behave like this:
Created 2 Combobox
Combobox1 will load some list in Control sheet
Whenever an item in Combobox1 is selected, Combobox2 will load list of items to be added to Combobox2
The code for adding the button is like this
Sub Add_Criteria()
Dim controlNum As Integer
Dim name1 As String
Dim name2 As String
Dim oOle1 As OLEObject
Dim oOle2 As OLEObject
Dim uniqueString As String
Dim cb1 As Object
controlNum = Sheets("Controls").Range("A16").Value
'adding Control
Set oOle1 = Sheets("System").OLEObjects _
.Add(ClassType:="Forms.ComboBox.1", Left:=10, _
Top:=75 + (controlNum * 20), Width:=100, Height:=18)
Set oOle2 = Sheets("System").OLEObjects _
.Add(ClassType:="Forms.ComboBox.1", Left:=120, _
Top:=75 + (controlNum * 20), Width:=100, Height:=18)
'adding properties
oOle1.Name = "Criteria" & controlNum * 2 - 1
oOle2.Name = "Criteria" & controlNum * 2
'adding control var
Sheets("Controls").Range("A16").Value = controlNum + 1
With oOle1.Object
.List = Sheets("Controls").Range("A5:A13").Value
End With
End Sub
The question is, I cannot detect events on it. I need to change the value shown on the second combobox created when value in combobox1 changed. I tried to use below reference and I still can't. Can anyone guide me on how to do this
Reference (Been on this Problem for days):
http://www.dbforums.com/microsoft-excel/1641165-detecting-click-event-dynamically-created-controls.html (This is for userform, I don't know why I can't replicate this in Sheet)

You can add Events programmatically. The code below adds an event for each combobox
This reference from Pearson Programming The VBA Editor may also be useful.
Sub Add_Criteria()
Dim controlNum As Integer
Dim name1 As String
Dim name2 As String
Dim oOle1 As OLEObject
Dim oOle2 As OLEObject
Dim uniqueString As String
Dim cb1 As Object
Dim strCode As String
Dim vbProj As Object
Dim vbCodeMod As Object
Set vbProj = ActiveWorkbook.VBProject
Set vbCodeMod = vbProj.vbcomponents(ActiveSheet.CodeName).codemodule
controlNum = Sheets("Controls").Range("A16").Value
'adding Control
Set oOle1 = Sheets("System").OLEObjects.Add(ClassType:="Forms.ComboBox.1", Left:=10, Top:=75 + (controlNum * 20), Width:=100, Height:=18)
Set oOle2 = Sheets("System").OLEObjects.Add(ClassType:="Forms.ComboBox.1", Left:=120, Top:=75 + (controlNum * 20), Width:=100, Height:=18)
vbCodeMod.AddFromString AddEvent(oOle1.Name)
vbCodeMod.AddFromString AddEvent(oOle2.Name)
'adding properties
oOle1.Name = "Criteria" & controlNum * 2 - 1
oOle2.Name = "Criteria" & controlNum * 2
'adding control var
Sheets("Controls").Range("A16").Value = controlNum + 1
With oOle1.Object
.List = Sheets("Controls").Range("A5:A13").Value
End With
End Sub
Function AddEvent(strIn As String) As String
AddEvent = "Private Sub " & strIn & "_Click()" & Chr(10) & _
"MsgBox ""Event Added""" & Chr(10) & _
"End Sub"
End Function

Open a fresh Workbook and rename sheets 1 and 2 "System" and "Controls", respectively.
Open the VBA Editor and paste YOUR code code above into a general module.
Run your code.
Return to Excel. (Alt+F11)
Right-click on the System sheet tab and select View Code.
Paste the following in the module:
Sub FillCombo()
With Sheets("System").Criteria299 'Change the name of the control as needed.
.AddItem 1
.AddItem 2
End With
End Sub
Private Sub Criteria299_Change()
'Example of triggering the Change Event using Select Case
With Sheets("System")
Select Case .Criteria299.Value
Case 1
.Criteria300 = "Dog" 'Change the name of the control as needed.
Case 2
.Criteria300 = "Cat"
End Select
End With
End Sub
Look at Project Explorer and you'll see that the code is in the System worksheet module, not in a general module.
Any event procedures for controls added to a worksheet must be in stored in that worksheet's module.
The FillCombo sub can be put in a general module as long as you refer to the sheet name that has the control as shown.

Related

Improve 33 checkbox code subs to few? (Checkbox for auto-date in bookmarks)

:)
Im new to VBA!
I have a working code for inserting date where i have a bookmark when using a checkbox (ActiveX). Problem is i have 33 checkboxes (I actually wish for 33x2. one for yes and one for no). So i ended up with 33 Subs and 33 bookmarks. I bet this code can be more efficient braking it down to just a few subs. Annyone has anny idea if it can be done?
The code under is the first of 33 repeating subs where Sub and bookmark name is agi1, agi2 agi3.....
Private Sub agi1_Click()
Dim rngFormat As Range
Set rngFormat = ActiveDocument.Range( _
Start:=ActiveDocument.Bookmarks("agi1").Range.Start, _
End:=ActiveDocument.Bookmarks("agi1").Range.End)
With rngFormat
.Font.Size = 8
End With
Dim v
Dim BMRange As Range
v = ThisDocument.agi1.Value
'Sjekke om boks er sjekket eller ikke
If v = True Then
'Sett inn dato i bokmerke
Set BMRange = ActiveDocument.Bookmarks("agi1").Range
With Selection.Font
.Size = 9
End With
BMRange.Text = (Format(Date, "dd.mm.yyyy"))
Else
'Erstatte dato med tom tekst hvis boks ikke er sjekket
Set BMRange = ActiveDocument.Bookmarks("agi1").Range
BMRange.Text = " "
End If
'Sett inn bokmerke på nytt
ActiveDocument.Bookmarks.Add "agi1", BMRange
End Sub
You could use event sinking, maybe to.
In an normal module, create a collection and populate it to hold the classes that will control the check box events.
In this have the code, this will need to be run on opening the document, something early in it's life to populate the collection.
Public col As Collection
Public Sub SETUP()
Dim o As InlineShape
Dim c As MSForms.CheckBox
Dim cust As clsCustomCheckBox
Set col = New Collection
For Each o In ActiveDocument.InlineShapes
Set c = o.OLEFormat.Object
Set cust = New clsCustomCheckBox
cust.INIT c
col.Add cust
Next o
End Sub
and then have a class module called clsCustomCheckBox and have it's code as
Private WithEvents c As MSForms.CheckBox
Public Function INIT(cmdIN As MSForms.CheckBox)
Set c = cmdIN
End Function
Private Sub c_Click()
MsgBox "Here you can get the name " & c.Name
End Sub
This will divert each checkbox click to the classes c_click rather than it's own.
So for you
Dim rngFormat As Range
Set rngFormat = ActiveDocument.Range( _
Start:=ActiveDocument.Bookmarks(c.name).Range.Start, _
End:=ActiveDocument.Bookmarks(c.name).Range.End)
With rngFormat
.Font.Size = 8
End With
.......
ActiveX controls always register their event handlers like so:
Private Sub NameOfTheControl_NameOfTheEvent({args})
If you rename the handler, the control stops working - because the name of the handler must be formed as above, with an underscore separating the name of the control and the name of the handled event.
So if your controls must exist at compile-time, there's no way around it: for 33 controls you need 33 handlers.
That doesn't mean you need that huge procedure repeated 33 times!
Extract a procedure. Select the entire body of that handler, cut it.
Now make a new procedure prototype:
Private Sub HandleCheckBoxClick(ByVal controlName As String)
End Sub
And paste the body in there. Then replace all the places you have a hard-coded "agi1" with a reference to this controlName parameter:
Dim rngFormat As Range
Set rngFormat = ActiveDocument.Range( _
Start:=ActiveDocument.Bookmarks(controlName).Range.Start, _
End:=ActiveDocument.Bookmarks(controlName).Range.End)
With rngFormat
.Font.Size = 8
End With
'...
The places where you're referring to the control using its programmatic name will be a bit harder:
v = ThisDocument.agi1.Value
You can get the MSForms.CheckBox control through the ThisDocument.InlineShapes collection, but that won't let you find a checkbox by its name, so you need a function that can do it for you:
Private Function FindCheckBoxByName(ByVal controlName As String) As MSForms.CheckBox
Dim sh As InlineShape
For Each sh In ThisDocument.InlineShapes
If TypeOf sh.OLEFormat.Object Is MSForms.CheckBox Then
If sh.OLEFormat.Object.Name = controlName Then
'return the MSForms control:
Set FindControlByName = sh.OLEFormat.Object
End If
End If
Next
And now you can do this:
Dim cb As MSForms.ChecBox
Set cb = FindCheckBoxByName(controlName)
If cb Is Nothing Then
MsgBox "No ActiveX CheckBox control named '" & controlName & "' was found in ThisDocument."
Exit Sub
End If
v = cb.Value
Once all references to the ActiveX control are parameterized, your 33 handlers can now look like this:
Private Sub agi1_Click()
HandleCheckBoxClick "agi1"
End Sub
Private Sub agi2_Click()
HandleCheckBoxClick "agi2"
End Sub
'...
Private Sub agi33_Click()
HandleCheckBoxClick "agi33"
End Sub
Alternatively, you could have the checkboxes created at run-time, and then have their Click event handled in a dedicated class module, but that's a little bit more involved ;-)

VBA - Error While Programming a Class to Operate all Checkboxes on Userform

Here is a bit of background on what I'm trying to do: I'm creating a userform to track Inventory items and prices, using checkboxes in a multipage object. The clerk checks off everything put into an order and uses a submit button, which will take some actions.
In order for the project not to require a coding person every time Inventory items change, the checkboxes are being dynamically generated when the userform is activated, from cell values on an Inventory sheet. The clerks just adjust the Inventory sheet and the form automatically adjusts for them.
This is my code to dynamically create all the checkboxes (currently this form can accommodate up to 160 possible checkboxes), in case this is effecting my issue (side note, each tab on the multipage has a frame on it, and all checkboxes are within the frame, so I could change background colors, the frame in this example being titled "frmreg"):
Sub StoreFrmRegCheckboxGenerator()
'Works with the store userform
Dim curColumn As Long
Dim LastRow As Long
Dim i As Long
Dim chkBox As msforms.CheckBox
'This sub dynamically creates checkboxes on the Regular Items tab based
'on values in Column A of the Inventory sheet
curColumn = 1 'Set your column index here
LastRow = Worksheets("Inventory").Cells(Rows.Count, curColumn).End(xlUp).Row
For i = 2 To 9
If Worksheets("Inventory").Cells(i, curColumn).Value <> "" Then
Set chkBox = store.frmreg.Controls.Add("Forms.CheckBox.1", "CheckBox_" & i)
chkBox.Caption = Worksheets("Inventory").Cells(i, curColumn).Value & " - $" & Worksheets("Inventory").Cells(i, curColumn).Offset(0, 1).Value
chkBox.AutoSize = True
chkBox.WordWrap = True
chkBox.Left = 5
chkBox.Top = 1 + ((i - 1) * 25)
End If
Next i
'Cut some code out here identical to this previous section, but for the rest of the cells in column A up to Row 33, in blocks of 8
End Sub
The above code is in the Userform_Initialize sub, and it works perfectly.
However, since the number of checkboxes is not static, and can be as many as 160, I'm trying to write one sub to take the same set of actions any time any checkbox is clicked.
The closest solution I've found is from this question: Excel Macro Userform - single code handling multiple checkboxes, from sous2817.
Here is his code that I'm trying to use:
In a new class module:
Option Explicit
Public WithEvents aCheckBox As msforms.CheckBox
Private Sub aCheckBox_Click()
MsgBox aCheckBox.Name & " was clicked" & vbCrLf & vbCrLf & _
"Its Checked State is currently " & aCheckBox.Value, vbInformation + vbOKOnly, _
"Check Box # & State"
End Sub
The "store" userform, at the top, right under Option Explicit:
Dim myCheckBoxes() As clsUFCheckBox
At the bottom of the Userform_Initialize sub, AFTER I call the all the subs that dynamically create all the checkboxes:
Dim ctl As Object, pointer As Long
ReDim myCheckBoxes(1 To Me.Controls.Count)
For Each ctl In Me.Controls
If TypeName(ctl) = "CheckBox" Then
pointer = pointer + 1
Set myCheckBoxes(pointer) = New clsUFCheckBox
Set myCheckBoxes(pointer).aCheckBox = ctl
End If
Next ctl
ReDim Preserve myCheckBoxes(1 To pointer)
When I try to open the userform I get this error:
"Compile Error: User-defined type not defined"
Pointing to this line:
Dim myCheckBoxes() As clsUFCheckBox
Am I missing a library reference? I haven't been able to figure this out.

Scroll to view buttons where there are a varying number of buttons

I have selected a Sentence.
1) The sentence can vary.
2) I have split each word of the sentence.
The code below creates a list of Word array from Selection.
Sub Seperate_Words()
Dim WrdArray() As String
WrdArray() = Split(Selection)
For i = LBound(WrdArray) To UBound(WrdArray)
strg = strg & vbNewLine & WrdArray(i)
Next i
MsgBox strg
End Sub
Now I want to add a Search button in front of each word.
In every situation, the length of a sentence would change and Userforms are Pre-specified that's why I can't use them.
Following Image shows how output should be
Now problem I am facing is adding a scroll bar in frame which dynamically changes if needed.
I have found a very interesting solution to this:
Create a UserForm (I have named mine "frmSearchForm")
Create a Frame on it (mine is "framTest")
Create a Classmodule and name it "clsUserFormEvents"
Add this Code to it:
Public WithEvents mButtonGroup As msforms.CommandButton
Private Sub mButtonGroup_Click()
'This is where you add your routine to do something when the button is pressed
MsgBox mButtonGroup.Caption & " has been pressed" 'Just Example Code
End Sub
Then in the ThisDocument Module, add this code:
Dim mcolEvents As New Collection
Sub createButtonsOnForm()
Dim Cmd As msforms.CommandButton
'create instance of class
Dim cBtnEvents As clsUserFormEvents
'array for selection
Dim wordArr() As String
'get selection into array
wordArr = Split(Selection, " ")
Dim i As Integer
'counter for the top position of buttons
Dim topcounter As Integer
topcounter = 10
'loop through array
For i = LBound(wordArr) To UBound(wordArr) Step 1
'create button
Set Cmd = frmSearchForm.framTest.Controls.Add("Forms.CommandButton.1", "Test")
'Adjust properties of it
With Cmd
.Caption = wordArr(i)
.Left = 100
.Top = topcounter
.Width = 50
.Height = 20
End With
'Instantiate Class
Set cBtnEvents = New clsUserFormEvents
'Add cmd to event in class
Set cBtnEvents.mButtonGroup = Cmd
'Add buttonevent to collection so it won't get deleted in next iteration of the loop
mcolEvents.Add cBtnEvents
'increase the top position
topcounter = topcounter + 25
Next i
'show userform
frmSearchForm.Show
End Sub
Then if you run this sub, the selection gets splitted into the array, a button for every element is created(selection part as caption) and if you press the button, the method inside the class gets called, where you can use the mButtonGroup.Caption propery to get the value of button.
Example:
I have selected the Words "Test1" and "Test2", now when I run the Sub, the Form opens with 2 Buttons(Test1 and Test2):

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