Using a Class and WithEvents to detect changes in textboxes - vba

I have a userform in the VBE with 12 textboxes (1 for each month of the year). There's an additional textbox that is supposed to display the sum of the values in each of the first 12 boxes, updating and recalculating itself as the values in the other textboxes change.
Some help in implementing a solution to this problem is much appreciated.
The code I'm trying to implement is taken from the post at:
VBA: Detect changes in any textbox of the userform
However I'm unsure what to put in the event section below of the class.
NB: This is my first attempt at using classes so I'm just learning.

If Class1 is the name of your class module, Userform1 the name of your userform and if you want the sum of all textbox values to be in TextBox13 then,
in your Class1 module insert
Private WithEvents txtbox As MSForms.TextBox
Dim ctlr As Control
Public sum As Integer
Public Property Set TextBox(ByVal t As MSForms.TextBox)
Set txtbox = t
End Property
Private Sub txtbox_Change()
sum = 0
For Each ctlr In UserForm1.Controls
sum = sum + Val(ctlr)
Next ctlr
UserForm1.TextBox13 = sum - Val(UserForm1.TextBox13)
End Sub
and in the UserForm1 module insert
Private myEventHandlers As Collection
Private Sub UserForm_Initialize()
Dim txtbox As Class1
Set myEventHandlers = New Collection
Dim c As Control
For Each c In Me.Controls
If TypeName(c) = "TextBox" Then
Set txtbox = New Class1
Set txtbox.TextBox = c
myEventHandlers.Add txtbox
End If
Next c
End Sub

Related

Retrieve active control name from an excel user form after it's moused over

I am trying to dynamically create an application that will use the name of a button (created at runtime) on the form, to extract a number from the end of it's name:
FundNo = Right(Me.ActiveControl.Name, 1)
It's not working and I suspect it's because my button isn't the active control as I am using the mouse over event to trigger it which I'm guessing doesn't give it the focus.
It's nothing to do with the data type it's just not returning the name of the control.
Does anyone have any ideas as to how I could do this as it will be really cool if I can get it to work?
Thanks and regards, Mark
You are right, the CommandButton is not the active Control when the mouse moves over it. It is possible to do what you want, but not in a single line of code. You need a WithEvents variable in a Class module to return the events from the CommandButtons ... the following is the process, assuming you already have your UserForm set up and it is correctly adding your CommandButtons ... the following uses "MyUserFormName" as the assumed name of your UserForm, change this in the following to the actual name of your UserForm:
Add the following into the code-behind of your UserForm (the Debug.Print is just to test the code works and you can remove it when it is, if you want to):
Sub CommandButtonMovement(cb As MSForms.CommandButton)
FundNo = Right(cb.Name, 1)
Debug.Print Now, FundNo
End Sub
Add a Class module (must be a Class module ... not standard module), name it CCommandButtonEvents, add this code
Option Explicit
Private WithEvents mCommandButton As MSForms.CommandButton
Private mUserForm As MyUserFormName
Sub SetUpCommandButton(cb As MSForms.CommandButton, uf As MyUserFormName)
Set mCommandButton = cb
Set mUserForm = uf
End Sub
Private Sub mCommandButton_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
mUserForm.CommandButtonMovement mCommandButton
End Sub
... remember to change "MyUserFormName" to the actual name of your UserForm.
Back in your UserForm code-behind, for each CommandButton you create, you need to add the following at the module level:
Dim cbe1 As CCommandButtonEvents
Dim cbe2 As CCommandButtonEvents
... as many as you are adding CommandButtons
Finally, assuming you are dynamically creating your CommandButtons in UserForm_Activate(), then add (where cb1, cb2 is assumed to be the names of the CommandButton ... change this as required) in that method:
Set cbe1 = New CCommandButtonEvents
cbe1.SetUpCommandButton cb1, Me
Set cbe2 = New CCommandButtonEvents
cbe2.SetUpCommandButton cb2, Me
... again, as many as you are adding CommandButtons
Run your code. That's a lot of changes ... I thought it might help to post a full code example:
Class CCommandButtonEvents - as above
UserForm MyUserFormName
Option Explicit
Dim FundNo As Variant
Dim cbe1 As CCommandButtonEvents
Dim cbe2 As CCommandButtonEvents
Private Sub UserForm_Activate()
Dim cb1 As MSForms.CommandButton
Set cb1 = Me.Controls.Add("Forms.CommandButton.1", "CommandButton1", True)
cb1.Caption = "First button"
cb1.Top = 20
cb1.Left = 20
Set cbe1 = New CCommandButtonEvents
cbe1.SetUpCommandButton cb1, Me
Dim cb2 As MSForms.CommandButton
Set cb2 = Me.Controls.Add("Forms.CommandButton.1", "CommandButton2", True)
cb2.Caption = "Second button"
cb2.Top = 20
cb2.Left = 100
Set cbe2 = New CCommandButtonEvents
cbe2.SetUpCommandButton cb2, Me
End Sub
Sub CommandButtonMovement(cb As MSForms.CommandButton)
FundNo = Right(cb.Name, 1)
Debug.Print Now, FundNo
End Sub
If you are adding your CommandButtons in a loop then the example code will need adjusting for that eg to use an Array instead of individual CCommandButtonEvents variables at point 3 etc
EDIT: updated code per the questioners comments, to handle adding CommandButtons in an array and to handle multiple events (MouseMove and Click)
The UserForm code-behind needs some fairly serious re-working though the core principles are the same ... this is the new code that includes an array to hold the CommandButtons and a loop to add them all (change the value of mBtnCount to the number of CommandButtons you want added ... ensure the UserForm is large enough that you can see them all!)
Option Explicit
Const mBtnCount As Long = 5
Dim FundNo As Variant
Dim mCmdBtns() As CCommandButtonEvents
Private Sub UserForm_Activate()
Dim i As Long
ReDim mCmdBtns(1 To mBtnCount)
For i = 1 To mBtnCount
Dim cmdBtn As MSForms.CommandButton
Set cmdBtn = Me.Controls.Add("Forms.CommandButton.1", "CommandButton" & CStr(i), True)
cmdBtn.Caption = "Button " & CStr(i)
cmdBtn.Top = 4 + 26 * (i - 1)
cmdBtn.Left = 4
Set mCmdBtns(i) = New CCommandButtonEvents
mCmdBtns(i).SetUpCommandButton cmdBtn, Me
Next i
End Sub
Sub CommandButtonMovement(cb As MSForms.CommandButton)
FundNo = Right(cb.Name, 1)
Debug.Print Now, "Movement " & FundNo
End Sub
Sub CommandButtonClick(cb As MSForms.CommandButton)
FundNo = Right(cb.Name, 1)
Debug.Print Now, "Click " & FundNo
End Sub
... note there is a new Sub 'CommandButtonClick' that the code in CCommandButtonEvents will call.
Finally, to handle the Click event, in the CCommandButtonEvents Class module (you should normally use the drop-downs at the top of the code window to add the new event handlers to ensure the event 'signature' is correct), add this Sub which calls the new Sub in the UserForm:
Private Sub mCommandButton_Click()
mUserForm.CommandButtonClick mCommandButton
End Sub

VBA check if all userform comboboxes have an option selected

I have a userform with many comboboxes and an "Ok" button. What I need is that when pressing that "Ok" button, VBA to check if there's no empty comboboxes. If all comboboxes have some value selected - close the userform otherwise return a message box and clicking "Ok" on that messagebox returns me to the userform with no filled values lost.
I've tried all the methods I could think of:
If PackageOuterRadius = null Then
if PackageOuterRadius is nothing Then
If PackageOuterRadius.value = 0 Then
If IsNull(PackageOuterRadius) = True Then
If IsNull(PackageOuterRadius.value) Then
What I've been trying to do is:
Private Sub Rigid_Filme_Ok_Button_Click()
If PackageOuterRadius.ListCount = 0 Then
MsgBox "Select a ""Package Outer Radius!"
End If
And absolutelly nothing actually checks if the combobox is empty and keeps returning a positive (That there's a value selected)
What could be the solution to this problem? Could someone, please, help me?
If you have few ComboBoxes only, you may insert lines underneath the OK button to check if all the ComboBoxes are filled but if you have too many ComboBoxes on UserForm, you may achieve this with the help of a Class Module and to do so, follow these steps...
Insert a Class Module and rename it to clsUserForm and the place the following code on Class Module...
Code for Class Module:
Public WithEvents mCMB As msforms.ComboBox
Public Sub CheckComboBoxes()
Dim cCtl As msforms.Control
Dim cntCBX As Long
Dim cnt As Long
For Each cCtl In frmMyUserForm.Controls
If TypeName(cCtl) = "ComboBox" Then
cntCBX = cntCBX + 1
If cCtl.Value <> "" Then
cnt = cnt + 1
End If
End If
Next cCtl
If cnt = cntCBX Then
Unload frmMyUserForm
Else
MsgBox "All the ComboBoxes are mandatory.", vbExclamation
End If
End Sub
Then place the following code on UserForm Module. The code assumes that the name of the userForm is frmMyUserForm and the name of the CommandButton is cmdOK.
Code for UserForm Module:
Dim GroupCBX() As New clsUserForm
Dim frm As New clsUserForm
Private Sub cmdOK_Click()
frm.CheckComboBoxes
End Sub
Private Sub UserForm_Initialize()
Dim i As Long
Dim ctl As Control
For Each ctl In Me.Controls
If TypeName(ctl) = "ComboBox" Then
i = i + 1
ReDim Preserve GroupCBX(1 To i)
Set GroupCBX(i).mCMB = ctl
End If
Next ctl
End Sub
And you will be good to go.

Excel VBA: How to call "on change" for a dynamically created Object?

I'm trying to call a change event for any Combo Box on a userform.
UPDATE
So following what others have said (For Text Boxes, which I've tested and works for Textboxes), I've create a Class Module Called: 'clsCombo_Update'
Private WithEvents MyComboBox As MSForms.comboBox
Public Property Set Control(cb As MSForms.comboBox)
Set MyComboBox = cb
End Property
Private Sub MyComboBox_Change()
Debug.Print "Change"
End Sub
And in the UserForm I have:
Private Sub UserForm_Initialize()
Dim ctrl As MSForms.Control
Dim obj As clsCombo_Update
Set cbCollection = New Collection
For Each ctrl In Me.Controls
If TypeOf ctrl Is MSForms.comboBox Then
Set obj = New clsCombo_Update
Set obj.Control = ctrl
cbCollection.Add obj
End If
Next ctrl
Set obj = Nothing
End Sub
This setup worked for textboxes but doesn't when I tried to make it work for Combo Boxes

Unable to modify object by another - runtime Error 91

Let's say I have a user form with two combo boxes created at runtime. First combo sources items from A1:A2 (Fruits, Vegetables). I want the second combo to source either from B1:B2 (Apple,Orange) or from C1:C2 (Carrot,Tomato) deppending what user chose in first combo. Below my class named "FoodClass"
Option Explicit
Public WithEvents MyCombo1 As MSForms.ComboBox
Public WithEvents MyCombo2 As MSForms.ComboBox
Public Sub DrawCombo1(oParent)
Set MyCombo1 = oParent.Controls.Add("Forms.ComboBox.1", "Combo1", True)
With MyCombo1
.Top = 20
'(...) setting position
.RowSource = "A1:A2" 'Fruits;Vegetables
End With
End Sub
Public Sub DrawCombo2(oParent)
Set MyCombo2 = oParent.Controls.Add("Forms.ComboBox.1", "Combo2", True)
With MyCombo2
'(seting position)
End With
End Sub
Public Sub MyCombo1_Change()'When user chose from combo 1...
If MyCombo1.Value = "Fruits" Then MyCombo2.RowSource = "B1:B2" 'Apple;Orange
If MyCombo1.Value = "Vegetables" Then MyCombo2.RowSource = "C1:C2"'Carrot;Tomato
End Sub
Then there is my code in user form:
Option Explicit
Public ComboBox1 As FoodClass
Public Combobox2 As FoodClass
Private Sub UserForm_Initialize()
Set ComboBox1 = New FoodClass
Call ComboBox1.DrawCombo1(UserForm)
Set Combobox2 = New FoodClass
Call Combobox2.DrawCombo2(UserForm)
End Sub
Each time user changes the first combobox Error 91 appears. How can I modify one object by another?
In UserForm_Initialize create just one instance of FoodClass so this instance will have both combo boxes initialized:
dim food as FoodClass
Set food = New FoodClass
call food.DrawCombo1(UserForm)
call food.DrawCombo2(UserForm)
Note: Instead of UserForm the keyword Me can be used inside of the UserForm class. Me refers to current instance of the UserForm.
call food.DrawCombo1(Me)

VBA: Detect changes in any textbox of the userform

There is a userform that has many textboxes and I need to detect changes in each. So I have write a subroutine for every textbox in the form and it turns out a large piece of code.
As the code for every textbox is the same I want to optimize it. So is it possible to write just one subroutine that detect changes in any textbox of the form?
The only way do achieve that is to use a class along with WithEvents
Here's a minimal example:
Code for the class module named mytextbox:
Private WithEvents txtbox As MSForms.TextBox
Public Property Set TextBox(ByVal t As MSForms.TextBox)
Set txtbox = t
End Property
Private Sub txtbox_Change()
' code for handling the event
End Sub
And the code inside the Userform, assuming you want to handle the events of every Textbox
Private myEventHandlers As Collection
Private Sub UserForm_Initialize()
Dim txtbox As mytextbox
Set myEventHandlers = New Collection
Dim c As Control
For Each c In Me.Controls
If TypeName(c) = "TextBox" Then
Set txtbox = New mytextbox
Set txtbox.TextBox = c
myEventHandlers.Add txtbox
End If
Next c
End Sub