Populate Various Comboboxes by Function Call - vba

Morning !
I'm trying to make my code more generic by using Functions to populate various ComboBoxes, however, I'm with troubles to do this...
In UserForm:
Private Sub UserForm_Initialize()
Call Lista_Vendedores
ComboBox1.List = Lista_Vendedores
End Sub
In Module:
Public Function Lista_Vendedores() As Variant
Dim Lista As New Collection
Dim Cont_Vendedores As Integer
Cont_Vendedores = Plan1.Cells(1, 8).Value
Sheets("Plan1").Select
For i = 3 To Cont
Lista (Row.Cells(i, 8))
MsgBox Lista
Next i
Set Lista_Vendedores = Lista
Set Lista = Nothing
End Function
When I try to run the code, nothing happens, however, none error is shown.

Your code can be simplified a lot. It doesn't need to be in a function, just give it a range as per the example below:
Userform:
Private Sub UserForm_Initialize()
ComboBox1.List = ThisWorkbook.Worksheets("Sheet1").Range("A1:A8")
End Sub
Module:
Public Sub ShowUserform()
UserForm1.Show
End Sub

You are making lots of mistakes in your code, and while CallumDA33 solution is better, I think you should take a look at this:
First, you are not calling your function correctly. The sub should be:
Private Sub UserForm_Initialize()
ComboBox1.List = Lista_Vendedores
End Sub
The Call line is not necessary. (and the keyword Call itself is almost never required). I really don't get where your syntax is coming from.
In the Lista_Vendedores function, you are not using correctly the Lista Collection.
Cont is undefined. You should always use Option Explicit at the start of your modules.
You are not affecting any value to the collection.
You should also avoid using Select as much as possible.
The List property expect an variant array, not a collection, so the function should return a variant array, not an variant containing a single (empty) collection object, so I also changed the type for Lista.
Follows corrected but untested code:
Public Function Lista_Vendedores() As Variant()
Dim Lista As Variant()
Dim Cont_Vendedores As Integer
With Sheets("Plan1")
Cont_Vendedores = .Cells(1, 8).Value
Redim Lista(0 to Cont_Vendedores-3)
For i = 3 To Cont_Vendedores
Lista(i-3) = .Cells(i,8)
Next i
End With
Lista_Vendedores = Lista
End Function
Edit: corrected typo

Related

Call module with multiple inputs

I've tried finding a solution to this problem but have been unable to.
In generic form:
Module1
Sub Source()
Call Module2.Run
End Sub
Module2
Sub Run()
Value = 10
Some code which uses Value as input
End Sub
What I want to be able to do is to be able to define multiple Values in Module1 and then run Module2.Run() with each value.
Module1
Sub Source()
Value = 10, 20, 30
Call Module2.Run (10)
Call Module2.Run (20)
Call Module2.Run (30)
End Sub
Module2
Sub Run()
Value = Input from Module1.Source()
Some code which uses Value as input
End Sub
Or something along these lines. Any input would be greatly appreciated.
You can pass parameters through arguments like so
Sub Sub1 ()
Dim myVal as Long
myVal = 1000
Sub2 (myVal) 'The "Call" is not necessary
End Sub
Sub Sub2 (myVal as Long) 'This sub requires an input to run (myVal)
MsgBox myVal
End Sub
You can create an array, fill it and pass as an argument.
Avoid using names like Source and Run which are already used by Excel;.
Option Explicit
Sub Sour()
Dim arr_1d() As Variant
arr_1d = Array("val1", "val2", "val3")
Dest arr_1d
End Sub
Sub Dest(arr_1d() As Variant)
Dim y As Long
For y = LBound(arr_1d) To UBound(arr_1d)
Debug.Print arr_1d(y)
Next
End Sub

VBA - Safely store variable reference

I'm familiar with passing an argument to a procedure by reference. Alternately, ParamArray allows me the flexibility of passing 0 or more arguments to a procedure by reference as well. However, that approach made me wonder if there was a way to preserve a reference to one or more variables beyond the scope of a procedure. My first glimmer of hope was the VBA Array function when I saw it was declared like this:
Array(ParamArray ArgList() As Variant)
So, I put together the following test code:
Private Sub Test()
Dim a As Object
Dim b() As Variant
ParamArrayTest a
Debug.Print TypeName(a) ' Output is 'Dictionary'
b = Array(a) ' b should be like ParamArray ArgList()
Set b(0) = Nothing ' This should clear a
Debug.Print TypeName(a) ' Output is still 'Dictionary'
End Sub
Private Sub ParamArrayTest(ParamArray ArgList() As Variant)
Set ArgList(0) = CreateObject("Scripting.Dictionary")
End Sub
Unfortunately, this did not work as I expected. Despite the argument being passed into the Array function via ParamArray, it would appear that the returned array was by value and not by reference.
Further research led me to the undocumented VBA VarPtr / StrPtr / ObjPtr functions. I found numerous examples of using them in conjunction with the API RtlMoveMemory function. However, all of the articles I read strongly urged against using that approach since it could very easily crash the application. Some of my testing did indeed crash Access.
Another idea I had was to see if I could directly assign a reference of one variable to another:
Private Sub Test()
Dim a As Object
Dim b As Variant
b = ByRef a ' Throws a compiler error
End Sub
Suffice it to say, the compiler simply would not allow that. My question then is, can a variable reference be safely stored / preserved beyond the scope of a procedure (preferably in another variable)?
EDIT
I decided it would be more helpful if I shed some light on what I'm trying to build.
I'm currently in the process of creating a wrapper class which will pass all form / control events to a procedure in one of my modules. It will be used with 2 forms which have the same control structure but connect to different source tables. Bear in mind that the code is incomplete but should be sufficient to illustrate the problem I'm trying to overcome. Also, Database is my VBA project name.
There are four portions to the code:
Form_TEST_FORM - Form Module
Private Sub Form_Open(Cancel As Integer)
FormHub.InitForm Me, Cancel
End Sub
FormHub - Module
Public Sub InitForm( _
ByRef Form As Access.Form, _
ByRef Cancel As Integer _
)
Dim Evt As Database.EventHandler
Set Evt = New Database.EventHandler
Evt.InitFormObject Form, Cancel
FormList.Add Evt, Form.Name
End Sub
Private Function FormList() As VBA.Collection
Static Init As Boolean
Static Coll As VBA.Collection
If Not Init Then
Set Coll = New VBA.Collection
Init = True
End If
Set FormList = Coll
End Function
FormControl - Class Module
Public Ptr As Variant ' Pointer to form control variable
Public acType As Access.AcControlType
EventHandler - Class Module
Private WithEvents Form As Access.Form
Private WithEvents SForm As Access.SubForm
Private CtrlList As VBA.Collection
Private Sub Class_Initialize()
InitCtrlList
End Sub
Public Sub InitFormObject(FormObj As Access.Form, ByRef Cancel As Integer)
Dim ErrFlag As Boolean
Dim Ctrl As Access.Control
Dim FCtrl As Database.FormControl
On Error GoTo Proc_Err
Set Form = FormObj
If Form.Controls.Count <> CtrlList.Count Then
Err.Raise 1, , _
"Form has incorrect number of controls"
End If
' This is where I want to validate the form controls
' and also initialize my event variables.
For Each Ctrl In Form.Controls
If Not CtrlExists(FCtrl, Ctrl.Name) Then
Err.Raise 2, , _
"Invalid control name"
ElseIf FCtrl.acType <> Ctrl.ControlType Then
Err.Raise 3, , _
"Invalid control type"
Else
' Initialize the correct variable with it's
' pointer. This is the part I haven't been
' able to figure out yet.
Set FCtrl.Ptr = Ctrl
End If
Next
Proc_End:
On Error Resume Next
If ErrFlag Then
ClearEventVariables
End If
Set Ctrl = Nothing
Set FCtrl = Nothing
Exit Sub
Proc_Err:
ErrFlag = True
Debug.Print "InitFormObject " & _
"Error " & Err & ": " & Err.Description
Resume Proc_End
End Sub
Private Function CtrlExists( _
ByRef FCtrl As Database.FormControl, _
ByRef CtrlName As String _
) As Boolean
On Error Resume Next
Set FCtrl = CtrlList(CtrlName)
CtrlExists = Err = 0
End Function
Private Sub InitCtrlList()
Set CtrlList = New VBA.Collection
CtrlList.Add SetCtrlData(SForm, acSubform), "SForm"
End Sub
Private Function SetCtrlData( _
ByRef Ctrl As Access.Control, _
ByRef acType As Access.AcControlType _
) As Database.FormControl
Set SetCtrlData = New Database.FormControl
With SetCtrlData
' This assignment is where I need to keep a reference
' to the variable in the class. However, it doesn't
' work.
Set .Ptr = Ctrl
.acType = acType
End With
End Function
Private Sub ClearEventVariables()
Dim FormCtrl As Database.FormControl
Set Form = Nothing
For Each FormCtrl In CtrlList
' Assuming I was able to retain a reference to the
' class variable, this would clear it.
Set FormCtrl.Ptr = Nothing
Next
End Sub
Private Sub Class_Terminate()
ClearEventVariables
Set CtrlList = Nothing
End Sub
I only used 1 control in the code example for simplicity sake. But, the idea is to simplify how much code I would need to modify in order to add / remove controls should the form design change. Or, in the event I have to add more forms to the project.
If you need to reference only within a single module, declare as Public in module header. If you want to reference in any module, declare as Global in a general module header. Even array and recordset and connection objects can be declared this way. Be aware these variables will lose their values if code breaks in runtime.
Or look into TempVar object variables. They don't lose values if code breaks. But can only store number or text values, not objects.

VBA ByRef Error Passing a Class Object to a Sub

I'm trying to pass an object to a new sub but keep hitting a ByRef Mismatch error.
I've declared my object as:
Dim targetWorkbook
Set targetWorkbook = New CWorkbooks
I'm calling my sub by using:
checkbook targetWorkbook
And my sub is set as:
Sub checkbook(targetWorkbook As CWorkbooks)
'Checking if passthrough is working
End Sub
Any help is appreciated, my types are aligned and everything so I'm not sure why this is occuring.
Thanks!
Sub Foo()
'single class object
Dim myClass1 As New clsClass
myClass1.StringName = "cls1"
Call Par(myClass1)
'or class array
Dim myClass2(1 To 5) As New clsClass
myClass2(1).StringName = "cls2"
Call Par(myClass2)
End Sub
Sub Par(ByRef lClass As Variant) 'same function call used for both
'Debug.Print lClass.StaffName & vbNewLine 'single class object
'Debug.Print lClass(1).StaffName & vbNewLine 'array version
End Sub
google brought me here for same problem but found the accepted answer lacking & didn't work at all in my case where Foo() was a module & Par() a worksheet, trying to pass class array.
I was able to duplicate your problem with the compiler. The following passes the compiler and runs. You declared TargetWorkbook as Variant, then set it to CWorkbooks - this works, but not when passed to the sub.
Sub main()
Dim TargetWorkbook As CWorkbooks
Set TargetWorkbook = New CWorkbooks
checkbook TargetWorkbook
End Sub
Sub checkbook(ByRef TargetWorkbook As CWorkbooks)
'Checking if passthrough is working
End Sub

VBA variables defined one sub and called in another in multiple subs

I want to call a sub that I've written in another sub, and add a couple new things to it, but use the variables that I've defined in the first sub don't show up in the second, even though the first macro is called within the sub.
For example, I've defined the variables as Public outside of the subs, but the values I define in the first sub, and then call in second sub gets lost. In the code below, running the macro "test" works, but "test1" gives me a "Run-time error '1004'".
Public row1 As Integer
Public col1 As Integer
Sub test()
row1 = 2
col1 = 2
ActiveSheet.Cells(row1, col1).Select
End Sub
Sub test1()
Call test
ActiveSheet.Cells(row1, col1).Resize(6, 5).Select
End Sub
Any guidance on how to get row1 and col1 to work in the test1 sub would be great. Is there a better way to achieve what I'm trying to do?
An alternative option is to pass variables from sub to use a Public Function like this:
Sub MainSubHere()
'Some random code
Dim AddMe1 As Long, AddMe2 As Long, MySum As Long
AddMe1 = 2
AddMe2 = 2
MySum = AddMeSub(AddMe1, AddMe2)
Msgbox MySum
End Sub
Public Function AddMeSub(AddMe1 As Long, AddMe2 As Long) As Long
AddMeSub = AddMe1 + AddMe2
End Sub
You can try, but there's other stuff to improve here, such as using Select rathering then just going to the action statement. I left out that sub out since it served no purpose.
Dim row1 As Integer, col1 As Integer
Private Sub DefineVars()
Set row1 = 2
Set col1 = 2
End Sub
Sub test()
Call DefineVars
ActiveSheet.Cells(row1, col1).Resize(6, 5).Select
End Sub

Get values from collection in one module into combobox in userform

I have a worksheet with data in column 'EGM'. My code saves values from this column in the collection.
If there is only one value in the collection, then variable sSelectedEGM is equal to this value.
But if there is more than one values, a user should has possibility to choose only one value (I wanted to do this in the combobox) and save selected item into variable sSelectedEGM.
My problem is, that I can't get values from this collection into userform.
When my code go into useform, the error "Type mismatch" appear. My code in worksheet:
Public sSelectedEGM As String
Public vElement As Variant
Public cEGMList As New VBA.Collection
Sub kolekcjaproba()
' ===================================
' LOOP THROUGH EGMS AND WRITE THEM INTO COLLECTION
' ===================================
Dim iOpenedFileFirstEGMRow As Integer
Dim iOpenedFileLastEGMRow As Integer
Dim iOpenedFileEGMColumn As Integer
Dim iOpenedFileEGMRow As Integer
Dim sOpenedFileEGMName As String
Dim ws As Worksheet
Dim wb As Workbook
Set wb = ThisWorkbook
Set ws = wb.Worksheets(1)
iOpenedFileFirstEGMRow = Cells.Find("EGM").Offset(1, 0).Row
iOpenedFileLastEGMRow = ActiveSheet.Cells(ActiveSheet.Rows.Count, iOpenedFileFirstEGMRow).End(xlUp).Row
iOpenedFileEGMColumn = Cells.Find("EGM").Column
For iOpenedFileEGMRow = iOpenedFileFirstEGMRow To iOpenedFileLastEGMRow
sOpenedFileEGMName = Cells(iOpenedFileEGMRow, iOpenedFileEGMColumn).Value
For Each vElement In cEGMList
If vElement = sOpenedFileEGMName Then
GoTo NextEGM
End If
Next vElement
cEGMList.Add sOpenedFileEGMName
NextEGM:
Next
If cEGMList.Count = 1 Then
sSelectedEGM = cEGMList.Item(1)
ElseIf cEGMList.Count = 0 Then
MsgBox "No EGM found"
Else
Load UserForm1
UserForm1.Show
End If
End Sub
And my code in a userform (There is only a combobox on it)
Private Sub UserForm_Initialize()
For Each vElement In cEGMList
UserForm1.ComboBox1.AddItem vElement
Next vElement
End Sub
Private Sub ComboBox1_Change()
If ComboBox1.ListIndex <> -1 Then
sSelectedEGM = ComboBox1.List(ComboBox1.ListIndex)
End If
End Sub
you have to declare cEGMList and sSelectedEGM in a standard module as public and not in a worksheet module.
Or even better: create a property on the form for the collection and for the returned values. It's always better to avoid global vars wherever possible.
This is a simplified example. In the form you can define properties and methods like that:
Option Explicit
Public TestProperty As Integer
Public Sub TestMethod()
MsgBox (TestProperty)
End Sub
Public Function TestMethodWithReturn() As Integer
TestMethodWithReturn = TestProperty * 2
End Function
outside the form you can then use this as a normal property/method of the form:
Private Sub Test()
Dim retValue As Integer
UserForm1.TestProperty = 123
UserForm1.Show vbModeless
UserForm1.TestMethod
retValue = UserForm1.TestMethodWithReturn
Debug.Print retValue
End Sub