Single event handler for multiple ComboBox controls in VBA - excel

I have a spreadsheet with 72 activex comboboxes. Ideally, when I change the value of one of the comboboxes I would like to pass the name (or other unique identifier) of that combobox to a subroutine RespondToChange that uses the name/identifier. For example, if the combobox is named cbXYZ, I would pass that name to the subroutine. I could use some code such as
Private Sub cbXYZ_Change()
RespondToChange "cbXYZ"
End Sub
However, this requires inserting code for all 72 comboboxes, which is tedious.
Is there a simple way of detecting which combobox has been changed and passing that information to RespondToChange?

You need a global event handler. Look into WithEvents.
Here's how you could do it.
Add a new Class and put this code inside that class.
Public WithEvents cmb As ComboBox
Private Sub cmb_Change()
'/ Do whatever you want to do when the event occurs.
MsgBox cmb.Name
End Sub
Add a module and put this code inside it.
Option Explicit
'/ an array to hold all the comboboxes
'/ Class1 is the class name. Change if your class name is different.
Dim comboBoxes() As New Class1
Sub HookEvents()
Dim lCtr As Long
Dim cmb
'/ Sheet1 is sheet's code name. Change accordingly for your sheet's name.
For Each cmb In Sheet1.OLEObjects
'/ Loop all controls on the sheet and check it its a combobox.
If TypeName(cmb.Object) = "ComboBox" Then
lCtr = lCtr + 1
ReDim Preserve comboBoxes(1 To lCtr)
'/ Add to keep it alive
Set comboBoxes(lCtr).cmb = cmb.Object
End If
Next
End Sub
Make sure you call HookEvents first ( may be on workbook_open or sheet activate) and then any ComboBox, when changed, will fire Class1's cmb_Change event.

Related

In VBA, is there a global Frame Event that could react to a click on any of its contained object?

Within EXCEL VBA a form is created containing 2 frames.
In each frame there are 20 control buttons. For a total of 40 control buttons on that form.
Each gang of 20 controls have a simple task to define the value of a global string called strLocation.
The strLocation can then have a total of 40 different string values.
Each button has one single code line like such as:
strLocation= “Something”
The form is displayed only when the user click on some particular cells in a sheet for which some conditions define if the form will be displayed. If the condition of the cell is not met, the form remain hidden and the user can just continue to other tasks.
If the condition in met, the form is displayed, showing 40 buttons within 2 frames.
The user task is to select one of the 40 buttons and the strLocation will be defined.
The global event ( when any one of the 40 buttons is clicked ) would then hide the form and set the cell value to the strLocation string value.
All this is working fine except that I cannot find how to trigger an event that would react to any of the 40 buttons.
I’d like to implement something like this in the form code:
Private Sub ( one event that would encompasses any of the 40 buttons being clicked )
Selection = strLocation
formLOCATION.Hide
End Sub
Of course I could include an extra 40 lines, one on each button code, calling one single Sub that would do the job.
I suspect there exist a more elegant way of capturing a global event that would be related to each button included in the form or included in each frame.
Since a frame is a global container for all its objects, is there not an event that exist to capture a click on any of its contained object?
Something similar than what is available in an EXCEL Worksheet capturing any Cell click, or EXCEL Workbook capturing any Worksheet SheetActivate event.
You could use a custom class to create a "control array" of all of the buttons in a frame. See (eg) https://bettersolutions.com/excel/macros/vba-control-arrays.htm
Something like this:
In the worksheet code module - showing the form if a cell is selected in C3:C13
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim frm As frmSelect, c As Range
If Target.Cells.CountLarge > 1 Then Exit Sub
Set c = Application.Intersect(Me.Range("C3:C13"), Target)
If c Is Nothing Then Exit Sub
Set frm = New frmSelect 'create userform
Set frm.theCell = c 'assign the selected cell
frm.Show 'show the form
Unload frm
End Sub
The userform frmSelect
Option Explicit
Dim rng As Range
Dim col As Collection 'must be Global (to the form)
Private Sub UserForm_Activate()
Dim con As Object, evt As Object
Set col = New Collection
For Each con In Me.Frame1.Controls 'buttons are in Frame1
Set evt = New clsButton 'assuming all contained controls are buttons...
evt.Init con, Me 'set up the class instance
col.Add evt 'store the object in the collection
Next con
End Sub
'the cell to be populated
Property Set theCell(c As Range)
Set rng = c
End Property
'called from the event class on clicking a button
Public Sub SetCell(txt As String)
rng.Value = txt
Me.Hide
End Sub
Finally the class clsButton to manage the events
Option Explicit
Private WithEvents btn As MSForms.CommandButton
Private frm As frmSelect
'set up the class instance
Public Sub Init(objBtn As MSForms.CommandButton, objFrm As Object)
Set btn = objBtn
Set frm = objFrm
End Sub
'handle the click event
Private Sub btn_Click()
frm.SetCell btn.Caption 'call Sub in the form
End Sub

how do I change the code in a sheet by applying a module

I made a module that I want to be a plugin. My issue is I want it to rerun every time I open the sheet and only the sheet I activated it on.
I found a solution on how to do it by using the
Private Sub Worksheet_Activate()
Call Func
End sub
inside the sheet, I applied the macro to. How can I make it apply this code snipped to the currently active sheet when automatically when I activate the macro.
Basically, when I use my plugin while I am on sheet x I want it to apply
Private Sub Worksheet_Activate()
Call Func
End sub
this function to that specific sheet and that specific sheet only
Just to clarify better.
I want to sit on the sheet that has NO VBA code associated,
activate my add-in
and have a predefined code-block run in the context of the activated sheet.
You need to add the following codes to your Add-In, it is important to add them to the right module (as indicated by the comments in the code).
' ThisWorkbook
Option Explicit
Private Sub Workbook_Open()
InitializeMyEventHandler ThisWorkbook.Application
End Sub
' modMyEventHandler
Option Explicit
Public g_mehHandler As clsMyEventHandler
Public Sub InitializeMyEventHandler(eapApplication As Application)
Set g_mehHandler = New clsMyEventHandler
Set g_mehHandler.eapApplication = eapApplication
End Sub
' clsMyEventHandler
Option Explicit
Public WithEvents eapApplication As Application
Private Sub eapApplication_SheetActivate(ByVal Sh As Object)
If TypeName(Sh) = "Worksheet" Then
Dim ewsSheet As Worksheet: Set ewsSheet = Sh
Debug.Print "Worksheet activated. Workbook: " & ewsSheet.Parent.Name & ", Worksheet: " & ewsSheet.Name
End If
End Sub
The first part in the Add-In's ThisWorkbook module makes sure that our event handler class will be initialized each time the Add-In is loaded (when a new Excel Application is opened).
The second part is a normal public module, which is capable of holding global public objects, in our case an object of class clsMyEventHandler. At initialization g_mehHandler will be set to a new instance of clsMyEventHandler, and its member, eapApplication will be assigned to the Application object received from the Add-In's Workbook_Open function.
The third part must be added as a class module. It has a variable that is declared with WithEvents, this means that any time an event (e.g. activation of a new sheet) happens, the appropriate functions of this class module will be called. If a SheetActivate event is fired in eapApplication, then eapApplication_SheetActivate function of this class will be called. The function is selected based on its name (object name + underscore + event name). After you declared eapApplication (Public WithEvents eapApplication As Application), you will be able to select eapApplication from the ComboBox above the VBA code, which usually contains the word (General). If you selected eapApplication there, you will be able to select events to which you want to react from the ComboBox next to it.

Call same SUB from multiple buttons in VBA

I have multiple buttons (active x) on a spreadhseet in same column but different rows. These buttons capture the start time of an activity.
If button 1 is pressed cell next to it should be populated by current time.
If button 2 is pressed cell next to it should be populated by current time. and so on.....
I have written a SUB in VBA as follows:
Private Sub StartTimer_Click()
Range("I4").Value = Now
End Sub
I do not want to repeat this code for each button action. Please let me know how it can be made dynamic.
A simple WithEvents example:
in a class (named clsButtons):
Private WithEvents Bt As MSForms.CommandButton
Property Set obj(b As MSForms.CommandButton)
Set Bt = b
End Property
Private Sub Bt_Click()
'uses the right of the name of the CommandButton
Cells(1 + Right(Bt.Name, 1) * 3, 9).Value = Now
End Sub
In the sheetcode (the one with the buttons):
Dim myButtons As Collection
Private Sub Worksheet_Activate()
Dim ctl As OLEObject
Dim ButtonClass As clsButtons
Set myButtons = New Collection
For Each ctl In Sheet1.OLEObjects
If ctl.progID = "Forms.CommandButton.1" Then
Set ButtonClass = New clsButtons
Set ButtonClass.obj = ctl.Object
myButtons.Add ButtonClass
End If
Next ctl
End Sub
Create a standard module and put the procedure in there.
While it is possible to share a procedure in a private module, it's best practice to put any shared procedures in a shared module.
In the VBA Editor click Insert > Module,
Paste into there, and give it a unique name. Using your example you could do something like:
Public Sub SetTimeValue()
Range("I4").Value = Now
End Sub
...then call this public stub from your other one, like:
Private Sub StartTimer_Click()
SetTimeValue
End Sub
...and from any other locations where you need to call your code.
I assume that you have more than one line of code for the actual procedure you're concerned about, otherwise copying it repeatedly isn't really a concern.
More Information:
MSDN : Understanding Scope and Visibility
Office Support : Scope of variables in Visual Basic for Applications
Chip Pearson : Understanding Scope Of Variables And Procedures
PowerSpreadsheets : Excel VBA Sub Procedures: The Complete Tutorial
MVP : Cut out repetition using subs and functions with arguments

Get Selected value of a Combobox

I have a thousands of cells in an Excel worksheet which are ComboBoxes. The user will select one at random and populate it.
How do I get the selected ComboBox value? Is there a way to trigger a function (i.e. an event handler) when the ComboxBoxes has been selected?
You can use the below change event to which will trigger when the combobox value will change.
Private Sub ComboBox1_Change()
'your code here
End Sub
Also you can get the selected value using below
ComboBox1.Value
If you're dealing with Data Validation lists, you can use the Worksheet_Change event. Right click on the sheet with the data validation and choose View Code. Then type in this:
Private Sub Worksheet_Change(ByVal Target As Range)
MsgBox Target.Value
End Sub
If you're dealing with ActiveX comboboxes, it's a little more complicated. You need to create a custom class module to hook up the events. First, create a class module named CComboEvent and put this code in it.
Public WithEvents Cbx As MSForms.ComboBox
Private Sub Cbx_Change()
MsgBox Cbx.Value
End Sub
Next, create another class module named CComboEvents. This will hold all of our CComboEvent instances and keep them in scope. Put this code in CComboEvents.
Private mcolComboEvents As Collection
Private Sub Class_Initialize()
Set mcolComboEvents = New Collection
End Sub
Private Sub Class_Terminate()
Set mcolComboEvents = Nothing
End Sub
Public Sub Add(clsComboEvent As CComboEvent)
mcolComboEvents.Add clsComboEvent, clsComboEvent.Cbx.Name
End Sub
Finally, create a standard module (not a class module). You'll need code to put all of your comboboxes into the class modules. You might put this in an Auto_Open procedure so it happens whenever the workbook is opened, but that's up to you.
You'll need a Public variable to hold an instance of CComboEvents. Making it Public will kepp it, and all of its children, in scope. You need them in scope so that the events are triggered. In the procedure, loop through all of the comboboxes, creating a new CComboEvent instance for each one, and adding that to CComboEvents.
Public gclsComboEvents As CComboEvents
Public Sub AddCombox()
Dim oleo As OLEObject
Dim clsComboEvent As CComboEvent
Set gclsComboEvents = New CComboEvents
For Each oleo In Sheet1.OLEObjects
If TypeName(oleo.Object) = "ComboBox" Then
Set clsComboEvent = New CComboEvent
Set clsComboEvent.Cbx = oleo.Object
gclsComboEvents.Add clsComboEvent
End If
Next oleo
End Sub
Now, whenever a combobox is changed, the event will fire and, in this example, a message box will show.
You can see an example at https://www.dropbox.com/s/sfj4kyzolfy03qe/ComboboxEvents.xlsm
A simpler way to get the selected value from a ComboBox control is:
Private Sub myComboBox_Change()
msgbox "You selected: " + myComboBox.SelText
End Sub
Maybe you'll be able to set the event handlers programmatically, using something like (pseudocode)
sub myhandler(eventsource)
process(eventsource.value)
end sub
for each cell
cell.setEventHandler(myHandler)
But i dont know the syntax for achieving this in VB/VBA, or if is even possible.

Excel vba -get ActiveX Control checkbox when event handler is triggered

I have an Excel spreadsheet that is separated into different sections with named ranges. I want to hide a named range when a checkbox is clicked. I can do this for one checkbox, but I would like to have a single function that can hide the appropriate section based on the calling checkbox. I was planning on calling that function from the event_handlers for when the checkboxes are clicked, and to pass the checkbox as an argument.
Is there a way to access the checkbox object that calls the event handler?
This works:
Sub chkDogsInContest_Click()
ActiveSheet.Names("DogsInContest").RefersToRange.EntireRow.Hidden = Not chkMemberData.Value
End Sub
But this is what I would like to do:
Sub chkDogsInContest_Click()
Module1.Show_Hide_Section (<calling checkbox>)
End Sub
These functions are defined in a different module:
'The format for the the names of the checkbox controls is
'CHECKBOX_NAME_PREFIX + <name>
'where "name" is also the name of the associated Named Range
Public Const CHECKBOX_NAME_PREFIX As String = "chk"
Public Function CheckName_To_SectionName(ByRef strCheckName As String)
CheckName_To_SectionName = Mid(strCheckName, CHECKBOX_NAME_PREFIX.Length() + 1)
End Function
Public Sub Show_Hide_Section(ByRef chkBox As CheckBox)
ActiveSheet.Names(CheckName_To_SectionName(chkBox.Name())).RefersTo.EntireRow.Hidden = True
End Sub
Since you're using regular (Active-X) checkboxes on a normal worksheet, then your best bet is to create a Click event for each sub, then call one routine for the Hide with the parameter of the checkbox name, like:
Private Sub chkCheckBox1_Click()
If chkCheckBox1.Value = True Then
Call RangeHide("CheckBox1")
End If
End Sub
Private Sub RangeHide(rangetohide As String)
Range(rangetohide).EntireRow.Hidden = True
End Sub
I think the answer is to create another class that has a checkbox object as a part of it and declare that object "WithEvents" Then I can create a method chkBox_clicked() that will be called whenever any checkbox that is a member of that class is clicked. I can also store the range within the object.
http://www.cpearson.com/excel/Events.aspx
Has more info... Great site btw for excel VBA.
EDIT: This does not work. See my comment below.

Resources