Handle many ComboBox_Change Event - excel

Well I'm also new in VBA programming. I'm creating a form which helps me to do quotations, and then there is a part of my form that shows items I've already registered, like this:
My Form with ComboBoxes
So the purpose of those ComboBoxes is to change or delete the correponding item according with the option I choose, and I would have a lot of them in my UserForm, making it hard to create many ComboBox event programs (like ComboBox1_Change, ComboBox2_Change, ... ComboBox50_Change). And then, the main question is: how could I do it in VBA without loosing a lot of time making the same code for different objects? I would like to create just one code for all ComboBoxes.
I understand that I can do in this way below, but I'm sure that it has a better way to do.
Sub ComboBox1_Change()
Call myCode
End Sub
Sub ComboBox2_Change()
Call myCode
End Sub
Sub ComboBox50_Change()
Call MyCode
End Sub
Sub myCode()
For i=1 to 50
If Controls("ComboBox" & i).Value = "Change" Then
Call MySecondCode
End If
Next i
End Sub
I spent about 30 minutes searching about this question, but I didn't find anything good for me. I hope you guys understood my question. Thanks in advance.
Update:
Axel Richter, as I said in comments, I'm having problem in this:
Private Function isNOKTest()
If prod1.Value = "" Or _
prod2.Value = "" Or _
tecido.Value = "" Or _
tamanhos.Value = "" Or _
unitario.Value = "" Or _
quantidade.Value = "" Then
isNOKTest = True
End If
End Function
Private myCBsWithEvents As Collection
Private Sub UserForm_Initialize()
Set myCBsWithEvents = New Collection
For Each c In Me.Controls
If Left(c.Name, 8) = "ComboBox" Then
c.AddItem "CHANGE"
c.AddItem "DELETE"
Set myCBWithEvents = New clsCBWithEvents
Set myCBWithEvents.myCB = c
myCBsWithEvents.Add myCBWithEvents
End If
Next
End Sub
'
'
'
'datatext.Value = Format(Now, "dd/mm/yyyy")
'bordadoqty.Value = 1
'estampaqty.Value = 1
'Itemlab.Caption = 1
'
When any code is added to the project, the event in class module doesn't work, apparently isn't linked with "Events", but I don't know what happened.

This can be achieved using a class module which handles the events.
Insert a class module in your project. Name it clsCBWithEvents. In this class module have the following code:
Public WithEvents myCB As ComboBox
Private Sub myCB_Change()
If Me.myCB.Value = "Change" Then
MsgBox Me.myCB.Name & " has changed to ""Change"""
ElseIf Me.myCB.Value = "Delete" Then
MsgBox Me.myCB.Name & " has changed to ""Delete"""
End If
End Sub
In your user form have the following code:
Private myCBsWithEvents As Collection
Private Sub UserForm_Initialize()
Set myCBsWithEvents = New Collection
For Each c In Me.Controls
If TypeName(c) = "ComboBox" Then
c.AddItem "Change"
c.AddItem "Delete"
Set myCBWithEvents = New clsCBWithEvents
Set myCBWithEvents.myCB = c
myCBsWithEvents.Add myCBWithEvents
End If
Next
End Sub
Now every ComboBox in this user form will use this event handling.

Related

Assigning a With statement to one of two classes depending on a toggle, issues assigning New Object to be the same as another

I'm currently a bit stuck with the "Object Variable or With block variable not set error".
Still fairly new to using With statements to simplify my code, I have two instances on my Class "ContractSelection" Both existing instances (previousContract & currentContract) are both by this time in the code called as public variables, and set with values. In this Sub I am attempting to submit one piece of information depending whether they are looking at the current selection or the previous (a toggle in the userform).
Frankly I'm not sure if contractToUpdate = currentContract is even a valid statement, but i'm finding it difficult to simply google.
(in a Public Variable module)
Public currentContract As ContractSelection
Public previousContract As ContractSelection
(in Userform module)
Private Sub UserForm_Initialize()
Set currentContract = New ContractSelection
Set previousContract = New ContractSelection
End Sub
Values are set in general Subs like this
Sub setThePreviousContractAsTheCurrent()
currentContract.DistrictNumber = previousContract.DistrictNumber
currentContract.ContractName = previousContract.ContractName
currentContract.RegionName = previousContract.RegionName
'...
End Sub
(In a Main Sub module) This Sub is where the issue is.
Sub submitNewCode()
Dim contractToUpdate As ContractSelection, response As Integer
Set contractToUpdate = New ContractSelection
If CDBENC_Form.chkbx_PreviousSearch.value = False Then
'vba stating the issue is here
contractToUpdate = currentContract
Else
contractToUpdate = previousContract
End If
With contractToUpdate
If .CodeOfContract <> "" Then
If isSimilarByOne(.CodeOfContract, CDBENC_Form.txt_Code.value) = False Then
dataSheet.Cells(.TheRowIWasFoundIn, dataMappedColumns.CodeColumnNum).value = CDBENC_Form.txt_Code.value
Else
response = MsgBox("The new code is close to the original, is " & CDBENC_Form.txt_Code.value & " the intended new code?", vbYesNo + vbQuestion, "Confirm Action")
If response = vbYes Then
dataSheet.Cells(.TheRowIWasFoundIn, dataMappedColumns.CodeColumnNum).value = CDBENC_Form.txt_Code.value
Else
Exit Sub
End If
End If
End If
End With
End Sub
I've tried checking for to see if for some reason currentContract is showing as nothing
this returns the else
If currentContract Is Nothing Then
MsgBox "Current Contract is nothing"
Exit Function
Else
MsgBox "Current Contract is not nothing"
End If
I've tried both
Dim contractToUpdate As ContractSelection
Dim contractToUpdate As New ContractSelection
also putting in the public variables as well
Public contractToUpdate As New ContractSelection
Any suggestions help, I feel as though I'm close to the idea but far from the solution.
VBA requires the use of Set when assigning a value to an object-typed variable.
So:
If CDBENC_Form.chkbx_PreviousSearch.value = False Then
Set contractToUpdate = currentContract
Else
Set contractToUpdate = previousContract
End If
If you're interested in why that's the case: https://stackoverflow.com/a/9924325/478884

How to hide option button on Userform if the caption has no text

I have 4 option buttons and their caption is set to different cells. However, sometimes that cell value doesn't contain any text, and I want to hide the option button from the Userform if that is the case. But my code is hiding the option buttons even if the caption contains text. I'm sure this is simple, but I can't solve it.
Call ifBlank
OptionButton1.Caption = qRange1.Value
OptionButton2.Caption = qRange2.Value
OptionButton3.Caption = qRange3.Value
OptionButton4.Caption = qRange4.Value
Sub ifBlank()
If OptionButton3.Caption = "" Then
OptionButton3.Visible = False
If OptionButton4.Caption = "" Then
OptionButton4.Visible = False
End If
End If
a) Call ifBlank after setting the captions, not before.
b) You can simply write
Sub ifBlank()
OptionButton1.Visible = (OptionButton1.Caption <> "")
OptionButton2.Visible = (OptionButton2.Caption <> "")
OptionButton3.Visible = (OptionButton3.Caption <> "")
OptionButton4.Visible = (OptionButton4.Caption <> "")
End If
You can make your like easier by collating your option buttons and QRanges in collections. Your code will be a little more extensive when setting up the user form but later on code becomes simpler.
Option Explicit
Private Type State
Buttons As Collection
QRanges As Collection
End Type
Private s As State
Private Sub UserForm_Initialize()
Set s.Buttons = New Collection
With s.Buttons
.Add OptionButton1
.Add OptionButton2
.Add OptionButton3
.Add OptionButton4
End With
Set s.QRanges = New Collection
With s.QRanges
.Add QRange1
.Add QRange2
.Add QRange3
.Add QRange4
End With
'other initialisation code
End Sub
Public Sub UpdateButtonCaptions()
Dim myIndex As Long
For myIndex = 1 To s.Buttons.Count
' the test for an QRange may need to be more rigourous
If s.QRanges(myIndex) = "" Then
s.Buttons(myIndex).Visible = False
Else
s.Buttons(myIndex).Visible = True
s.Buttons(myIndex) = s.QRanges(myIndex).Value
End If
Next
End Sub
I'm not a regular excel/form user so you may need to tweak some of the code above.

Problem with "Method 'Value' of object 'Range' failed" error

This is a question mainly to understand and learn, then to find a solution, since i know what causes the problem and made a workaround.
I get Run-time error '-2147417848 (80010108)': Method 'Value' of object 'Range' failed.
I have a main UserForm with a ListBox, it gets filled from a table. Then i have a secondary UserForm to make new entries in said table. That table is on my sheet shData("Daten") and is called "mainData". If just run the secondary form to make new entry, all is fine. But if i start the secondary form from main form, i get the error.
This is the code in main form.
Private Sub newEntryButton_Click()
newEntry.Show
End Sub
Private Sub UserForm_Activate()
mainListUpdate
End Sub
This is the code in secondary form
Private Sub doneButton_Click()
Dim n As Long
n = cRow + 1
shData.Range("A" & n).Value = nameBox.Value
shData.Range("B" & n).Value = paraBox.Value
shData.Range("C" & n).Value = hStartBox.Value
shData.Range("D" & n).Value = bdayBox.Value
If OptionButtonStat.Value = True Then
shData.Range("E" & n).Value = "Ja"
shData.Range("G" & n).Value = statPlaceBox.Value
ElseIf OptionButtonAmb.Value = True Then
shData.Range("F" & n).Value = "Ja"
End If
Me.Hide
End Sub
Private Sub OptionButtonStat_Change()
If OptionButtonStat.Value = True Then
Me.statPlaceBox.Visible = True
Me.statPlaceLab.Visible = True
Else
Me.statPlaceBox.Visible = False
Me.statPlaceLab.Visible = False
End If
End Sub
Public Sub UserForm_Activate()
Me.statPlaceBox.Visible = False
Me.statPlaceLab.Visible = False
End Sub
and this is the code in a module.
Public cRow As Long
Public cCol As Long
Public Sub mainListUpdate()
If shData.FilterMode = True Then
shData.ShowAllData
End If
cRow = shData.Range("A1").CurrentRegion.Rows.Count
cCol = shData.Range("A1").CurrentRegion.Columns.Count
formMain.listMain.ColumnCount = cCol
formMain.listMain.ColumnHeads = True
formMain.listMain.RowSource = "mainData"
End Sub
The error occurs on the following line in secondary form code: shData.Range("A" & n).Value = nameBox.Value. If i change formMain.listMain.RowSource = "mainData" in the module code to formMain.listMain.RowSource = "Daten!A2:G", the problem disappears. Again, the code works fine with the table as RowSource, if i just use the secondary form, just need to add cRow = shData.Range("A1").CurrentRegion.Rows.Count.
I hope, i explained it well enough. Can somebody please explain, why i have this problem? It was very frustrating to search for the issue and i really would like to use the original code.

Glitch when using RefEdit_Change Event in a VBA UserForm

The following should happen:
1. UserForm with 2 RefEdit controls is shown
2. The first RefEdit is used to select a range
3. The RefEdit_Change event adjusts the second RefEdit control to .offset(0,1) of the range
Here my code until now:
Module1:
Dim frmSelectXY As New frmSelectImportData
With frmSelectXY
.Show
.DoStuffWithTheSelectedRanges
End With
UserForm: frmSelectImportData
Option Explicit
Private Type TView
IsCancelled As Boolean
xrng As Range
yrng As Range
End Type
Private this As TView
Public Property Get IsCancelled() As Boolean
IsCancelled = this.IsCancelled
End Property
Public Property Get yrng() As Range
Set yrng = this.yrng
End Property
Public Property Get xrng() As Range
Set xrng = this.xrng
End Property
'Here is where the fun happens
Private Sub RefEdit1_Change()
'RefEdit2.Value = RefEdit1.Value
If InStr(1, RefEdit1.Value, "[") <> 0 And InStr(1, RefEdit1.Value, "!") <> 0 Then
RefEdit2.Value = Range(RefEdit1.Value).offset(0, 1).Address(External:=True)
ElseIf InStr(1, RefEdit1.Value, "!") <> 0 Then
RefEdit2.Value = Range(RefEdit1.Value).offset(0, 1).Parent.Name & "!" & Range(RefEdit1.Value).offset(0, 1).Address(External:=False)
Else
RefEdit2.Value = Range(RefEdit1.Value).offset(0, 1).Address(External:=False)
End If
End Sub
Private Sub SaveBTN_Click()
Set this.xrng = Range(RefEdit1.Value)
Set this.yrng = Range(RefEdit2.Value)
If Not validate Then
MsgBox "x-values and y-values need to have the same size."
Else
Me.Hide
End If
End Sub
Function validate() As Boolean
validate = False
If this.xrng.count = this.yrng.count Then validate = True
End Function
RefEdit1_Change should adjust the value of RefEdit2 such that it will show the reference to the column just next to it or better .offest(0,1) to it.
But that isn't what happens.. the value doesn't get changed. As soon as the User clicks into RefEdit2 if RefEdit1 has already been changed, the program aborts without error message. If you Cancle the UserForm I have also experienced hard crashes of excel. I have temporarily fixed the problem by rebuilding the UserForm from scratch and renaming the RefEdits. But at some point it reapeared. It seems as if it is an Excel/VBA inherent problem.
Does anybody know how to fix this?
Ugly hacks and workarounds are welcome, anything is better than, abort without error message.
you need to enclose Range(RefEdit1.Value).offset(0, 1).Parent.Name in ' so
="'" & Range(RefEdit1.Value).offset(0, 1).Parent.Name & "'!"

Excel VBA: Why does event trigger twice?

I'm trying to avoid Event loops by disabling Events at crucial points. However, it doesn't always work. For instance, this code for a Combo box:
Private Sub TempComboS_Change()
Dim e
e = Application.EnableEvents
Application.EnableEvents = False
'
Application.EnableEvents = e
End Sub
The blank line is where the useful code goes; as it stands it obviously doesn't do anything. However, when I run it this way (with the blank line), it reaches "End Sub", then it goes back to the beginning and runs again. (This would make the useful code run twice).
Why is this happening?
EDIT: To clarify for the folks who've been helping me.
I have a macro that opens the dropdown list of the Combo box, activates it, then ends. It works properly. When I select an item from the open list, the Change event runs. This is the current version of the change event:
Private Sub TempComboS_Change()
End Sub
I put a breakpoint on the Private Sub line. It shows that this Change event runs, then runs again. I suspect that it has been doing this all along, and I noticed it now because I need to add code here.
I have no class modules or userforms. The controls are on a worksheet.
I'm going to try the "Run Once" suggestion, and I'll let you know if it works.
I tried the "Run Once" code you suggested. It sort of works, but I seem to have a bigger issue. When I select a drop-down list from a data-validated cell, the TempComboS_Change event triggers -- but not only didn't I touch this combo box, the cell isn't the LinkedCell for the combo box. In other words, it seems to be triggering by actions unconnected to the combo box!
Got to find out about that Call Stack thing...
Here is a bit of code to help investigate "sequence of events" issues
In a Standard Module
Public Enum eNewLine
No
Before
After
Both
End Enum
Public Function timeStamp(Optional d As Double = 0, Optional newLine As eNewLine = No, Optional Indent As Long = 0, _
Optional Caller As String, Optional Context As String, Optional message As String) As String
Dim errorMessage As String
If Err.number <> 0 Then
errorMessage = "ERROR: " & Err.number & ": " & Err.Description
Err.Clear
End If
If d = 0 Then d = Time
With Application.WorksheetFunction
timeStamp = .Text(Hour(d), "00") & ":" & .Text(Minute(d), "00") & ":" & .Text(Second(d), "00") & ":" & .rept(Chr(9), Indent)
End With
If Len(Caller) <> 0 Then timeStamp = timeStamp & Chr(9) & Caller
If Len(Context) <> 0 Then timeStamp = timeStamp & ": " & Chr(9) & Context
If Len(message) <> 0 Then timeStamp = timeStamp & ": " & Chr(9) & message
Select Case newLine
Case Before
timeStamp = Chr(10) & timeStamp
Case After
timeStamp = timeStamp & Chr(10)
Case Both
timeStamp = Chr(10) & timeStamp & Chr(10)
Case Else
End Select
If Len(errorMessage) <> 0 Then
timeStamp = timeStamp & Chr(9) & errorMessage
End If
End Function
At the top of each Module
'Module level Trace Hearder
Const debugEvents as Boolean = True
Const cModuleName As String = "myModuleName"
Const cModuleIndent As Long = 1
You can assign a module level indent for each module to organise the hierarchy an make it easy to understand.
In each Sub or Function (or property if you need)...
sub mySubName()
Const cMyName As String = "mySubName"
If debugEvents Then Debug.Print timeStamp(NewLine:=Before,Indent:=cModuleIndent, Caller:=cModuleName, Context:=cMyName, Message:="Start")
'Do stuff
If debugEvents Then Debug.Print timeStamp(NewLine:=After,Indent:=cModuleIndent, Caller:=cModuleName, Context:=cMyName, Message:="End")
End Sub
...Or you can use Me.Name for the Context if its a form or a sheet etc. and you can put whatever message or variable values you like in the Message.
You can also use a Timer (eg MicroTimer) and put the result in the Message section.
Here is an example output:
15:54:07: Roll-Up Select: Worksheet_Activate: Start: 3.24591834214516E-03
15:54:07: cDataViewSheet: Class_Initialize: Start
15:54:07: cRevealTarget: Class_Initialize: START
15:54:07: cRevealTarget: Class_Initialize: END
15:54:09: cDataViewSheet: startTimer: : START
15:54:09: cDataViewSheet: startTimer: init Timer
15:54:09: cOnTime: Class_Initialize
15:54:09: cOnTime: Let PulseTime: Inheret PulseTime from host sheet
15:54:09: cDataViewSheet: startTimer: : END
15:54:09: Roll-Up Select: Worksheet_Activate: END: 1.38736216780671
Private Sub cmbOrder_Change()
If cmbOrder = "" Then Exit Sub
Dim arr As Variant, maxorder As Integer
arr = Range("rngOrder")
maxorder = WorksheetFunction.Max(arr)
Dim errmsg As String, err As Boolean
err = False
errmsg = "This value must be a whole number between 1 and " & maxorder + 1
Dim v As Variant
v = cmbOrder.Value
If IsNumeric(v) = False Or (IsNumeric(v) = True And (v > maxorder + 1) Or v < 1)
Then
MsgBox errmsg
cmbOrder = ""
err = False
Else
txtOrder.Value = cmbOrder.Value
End If
End Sub
A bit late to the party but the problem of code repetition can be shown here in similar circumstances. Remove the first line of code and any error messages are dished out twice. This is because of the line that clears the ComboBox that is regarded as a change and picks up another error as null input is an error! May help someone with similar issue.
The Combobox_Change() will fire whenever there is a change in the combobox. For example
Option Explicit
Private Sub UserForm_Initialize()
ComboBox1.AddItem "Bah Blah"
End Sub
Private Sub CommandButton1_Click()
'~~> If something is selected in the combo then
'~~> this line will cause ComboBox1_Change to fire
ComboBox1.Clear
End Sub
Private Sub ComboBox1_Change()
MsgBox "A"
End Sub
So if you load the userform and select an item ComboBox1_Change will fire. You then use the commanbutton to clear the combo the ComboBox1_Change will again fire.
There is one more scenario when the change will again fire. When you change the combobox from the ComboBox1_Change event itself. Here is an example. And I believe this is what is happening in your case.
Scenario 1
Private Sub UserForm_Initialize()
ComboBox1.AddItem "Bah Blah"
End Sub
Private Sub ComboBox1_Change()
MsgBox "A"
ComboBox1.Clear
End Sub
Scenario 2
Private Sub UserForm_Initialize()
ComboBox1.AddItem "Bah Blah"
ComboBox1.AddItem "Bah Blah Blah"
End Sub
Private Sub ComboBox1_Change()
MsgBox "A"
ComboBox1.ListIndex = 1
End Sub
In the first scenario you can getaway with
Private Sub UserForm_Initialize()
ComboBox1.AddItem "Bah Blah"
End Sub
Private Sub ComboBox1_Change()
If ComboBox1 <> "" Then
MsgBox "A"
End If
End Sub
In the 2nd Scenario, you can use something like this
Dim boolRunOnce As Boolean
Private Sub UserForm_Initialize()
ComboBox1.AddItem "Bah Blah"
ComboBox1.AddItem "Bah Blah Blah"
End Sub
Private Sub ComboBox1_Change()
If boolRunOnce = False Then
MsgBox "A"
boolRunOnce = True
ComboBox1.ListIndex = 1
Else
boolRunOnce = False
End If
End Sub

Resources