Call a VBA Function into a Sub Procedure - vba

I know this is a simple question for someone out there, but I have never really used function module at all because I did not understand what they were.
So I have a whole bunch of things I can use this for (cut down on redundancy), but I want to know how I call into a sub (like a button click) procedure from a form.
I tried this...
Sub Command_Click()
Call "pptCreator"
End Sub
I know that is pretty bad, but I have no idea how to bring this into a procedure.

Here are some of the different ways you can call things in Microsoft Access:
To call a form sub or function from a module
The sub in the form you are calling MUST be public, as in:
Public Sub DoSomething()
MsgBox "Foo"
End Sub
Call the sub like this:
Call Forms("form1").DoSomething
The form must be open before you make the call.
To call an event procedure, you should call a public procedure within the form, and call the event procedure within this public procedure.
To call a subroutine in a module from a form
Public Sub DoSomethingElse()
MsgBox "Bar"
End Sub
...just call it directly from your event procedure:
Call DoSomethingElse
To call a subroutine from a form without using an event procedure
If you want, you can actually bind the function to the form control's event without having to create an event procedure under the control. To do this, you first need a public function in the module instead of a sub, like this:
Public Function DoSomethingElse()
MsgBox "Bar"
End Function
Then, if you have a button on the form, instead of putting [Event Procedure] in the OnClick event of the property window, put this:
=DoSomethingElse()
When you click the button, it will call the public function in the module.
To call a function instead of a procedure
If calling a sub looks like this:
Call MySub(MyParameter)
Then calling a function looks like this:
Result=MyFunction(MyFarameter)
where Result is a variable of type returned by the function.
NOTE: You don't always need the Call keyword. Most of the time, you can just call the sub like this:
MySub(MyParameter)

if pptCreator is a function/procedure in the same file, you could call it as below
call pptCreator()

Calling a Sub Procedure – 3 Way technique
Once you have a procedure, whether you created it or it is part of the Visual Basic language, you can use it. Using a procedure is also referred to as calling it.
Before calling a procedure, you should first locate the section of code in which you want to use it. To call a simple procedure, type its name. Here is an example:
Sub CreateCustomer()
Dim strFullName As String
strFullName = "Paul Bertrand Yamaguchi"
msgbox strFullName
End Sub
Sub Exercise()
CreateCustomer
End Sub
Besides using the name of a procedure to call it, you can also precede it with the Call keyword. Here is an example:
Sub CreateCustomer()
Dim strFullName As String
strFullName = "Paul Bertrand Yamaguchi"
End Sub
Sub Exercise()
Call CreateCustomer
End Sub
When calling a procedure, without or without the Call keyword, you can optionally type an opening and a closing parentheses on the right side of its name. Here is an example:
Sub CreateCustomer()
Dim strFullName As String
strFullName = "Paul Bertrand Yamaguchi"
End Sub
Sub Exercise()
CreateCustomer()
End Sub
Procedures and Access Levels
Like a variable access, the access to a procedure can be controlled by an access level. A procedure can be made private or public. To specify the access level of a procedure, precede it with the Private or the Public keyword. Here is an example:
Private Sub CreateCustomer()
Dim strFullName As String
strFullName = "Paul Bertrand Yamaguchi"
End Sub
The rules that were applied to global variables are the same:
Private: If a procedure is made private, it can be called by other procedures of the same module. Procedures of outside modules cannot access such a procedure.
Also, when a procedure is private, its name does not appear in the Macros dialog box
Public: A procedure created as public can be called by procedures of the same module and by procedures of other modules.
Also, if a procedure was created as public, when you access the Macros dialog box, its name appears and you can run it from there

Procedures in a Module start being useful and generic when you pass in arguments.
For example:
Public Function DoSomethingElse(strMessage As String)
MsgBox strMessage
End Function
Can now display any message that is passed in with the string variable called strMessage.

To Add a Function To a new Button on your Form: (and avoid using macro to call function)
After you created your Function (Function MyFunctionName()) and you are in form design view:
Add a new button (I don't think you can reassign an old button - not sure though).
When the button Wizard window opens up click Cancel.
Go to the Button properties Event Tab - On Click - field.
At that fields drop down menu select: Event Procedure.
Now click on button beside drop down menu that has ... in it and you will be taken to a new Private Sub in the forms Visual Basic window.
In that Private Sub type: Call MyFunctionName
It should look something like this:
Private Sub Command23_Click()
Call MyFunctionName
End Sub
Then just save it.

Related

Passing a parameter to an event handler procedure

I am generating a scripting dictionary using one button on a userform, using it to populate a listbox, and then need to use that same dictionary using a second button on the form. I have declared my dictionary either using early binding as so:
Dim ISINDict As New Scripting.Dictionary
or late binding as so
Dim ISINDict as Object
...
Set ISINDict = CreateObject("Scripting.Dictionary")
When I try to pass the dictionary to the other button like so:
Private Sub OKButton_Click(ISINDict as Scripting.Dictionary) 'if early binding
Private Sub OKButton_Click(ISINDict as Object) 'if late binding
I get the following error: "Procedure declaration does not match description of event or procedure having the same name" on that line.
Any ideas what I'm doing wrong?
An event handler has a specific signature, owned by a specific interface: you can't change the signature, otherwise the member won't match the interface-defined signature and that won't compile - as you've observed.
Why is that?
Say you have a CommandButton class, which handles native Win32 messages and dispatches them - might look something like this:
Public Event Click()
Private Sub HandleNativeWin32Click()
RaiseEvent Click
End Sub
Now somewhere else in the code, you want to use that class and handle its Click event:
Private WithEvents MyButton As CommandButton
Private Sub MyButton_Click()
'button was clicked
End Sub
Notice the handler method is named [EventSource]_[EventName] - that's something hard-wired in VBA, and you can't change that. And if you try to make an interface with public members that have underscores in their names, you'll run into problems. That's why everything is PascalCase (without underscores) no matter where you look in the standard libraries.
So the compiler knows you're handling the MyButton.Click event, because there's a method named MyButton_Click. Then it looks at the parameters - if there's a mismatch, something is wrong: that parameter isn't on the interface, so how is the event provider going to supply that parameter?. So it throws a compile-time error, telling you you need to either make the signature match, or rename the procedure so that it doesn't look like it's handling MyButton.Click anymore.
When you drop a control onto a form, you're basically getting a Public WithEvents Button1 As CommandButton module-level variable, for free: that's how you can use Button1 in your code to refer to that specific button, and also how its Click handler procedure is named Button1_Click. Note that if you rename the button but not the handler, the procedure will no longer handle the button's Click event. You can use Rubberduck's refactor/rename tool on the form designer to correctly rename a control without breaking the code.
Variables in VBA can be in one of three scopes: global, module, or procedure level.
When you do:
Sub DoSomething()
Dim foo
End Sub
You're declaring a local-scope variable.
Every module has a declarations section at the top, where you can declare module-scope variables (and other things).
Option Explicit
Private foo
Sub DoSomething()
End Sub
Here foo is a module-scope variable: every single procedure in that module can access it - read and write.
So if you have data you want to pass between procedures and you can't alter their signatures, your next best option is to declare a module-scope variable.
[ignores global scope on purpose]
About As New - consider this:
Public Sub Test()
Dim foo As Collection
Set foo = New Collection
Set foo = Nothing
foo.Add 42
Debug.Print foo.Count
End Sub
This code blows up with run-time error 91 "object variable not set", because when foo.Add executes, foo's reference is Nothing, which means there's no valid object pointer to work with. Now consider this:
Public Sub Test()
Dim foo As New Collection
Set foo = Nothing
foo.Add 42
Debug.Print foo.Count
End Sub
This code outputs 1, because As New keeps the object alive in a weird, unintuitive and confusing way. Avoid As New where possible.
Declare the dictionary at the module level and fill it in button-1-click event handler. Then it can be simply re-used in button-2-click event handler. So there is no need to pass the dictionary to event handlers which is not possible either. HTH
Form module
Option Explicit
' Declare dictionary at the user form module level
Private ISINDict As Scripting.Dictionary
Private Sub CommandButton1_Click()
FillDictionary
End Sub
Private Sub CommandButton2_Click()
' Use the dictionary filled in event handler of CommandButton-1
End Sub
Private Sub FillDictionary()
With ISINDict
.Add "Key-1", "Itm-1"
.Add "Key-2", "Itm-2"
.Add "Key-3", "Itm-3"
End With
End Sub
Private Sub UserForm_Initialize()
Set ISINDict = New Scripting.Dictionary
End Sub

Treat Userform as a Function

I have created a VBA UserForm which is essentially a glorified input box
Just like an input box can be employed like this
Dim returnVal As String
returnVal = InputBox("Write some string")
I would like my userform to run like this
Dim returnVal As customClass
Set returnVal = MyUserForm([some arguments])
ie. the MyUserForm() code passes some arguments to the userform, and when the userform is closed, it gets some arguments back (in the form of a custom class rather than a plain string)
What's the best way of structuring my userform to allow this functionality?
Currently, I'm just declaring some variables and the custom class publicly. I'm catching command button clicks and Query_close() events to hide the form, then I read the outputVal and close the form completely. I don't like this because I'd like my form to be totally self contained, and I think the capturing of events is messy.
In simplified code (read/return a string):
Function myUf(inVal As String) As String
Dim frm As New frmTest
frm.inputval = inVal
frm.Init 'sets caption. We cannot rely on userform initialize as this runs before inputval is set
'We could pass a variable here to save writing to the public variable
frm.Show
myUf = frm.outputVal
Set frm = Nothing
End Function
And in my Userform called frmTest with a textbox called tb1
Public inputval As String
Public outputVal As String
Public Sub Init()
Me.Caption = inputval 'setting caption, but could pass this anywhere
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode <> 1 Then Cancel = 1
outputVal = tb1 'reading value from textbox, but could return anything here
Me.Hide
End Sub
You need to find a way to initiate the UserForm from a ClassObject. Then, you can use a simple factory pattern to create the UserForm exactly the way you want.
In general, I have copied a bit of the code of Mat's Mug somewhere in StackOverflow and I wrote an article about the User Forms. If you take a look here (http://www.vitoshacademy.com/vba-the-perfect-userform-in-vba/) you will find a way to initialize the form with Public Sub ShowMainForm() It's possible to add a parameter to the ShowMainForm, then pass it to the initializer of the class.
In general, take the code from the article, make sure it works, and change the ShowMainForm initializer to the following:
Public Sub ShowMainForm(strText As String, strText2 As String)
If (objPresenter Is Nothing) Then
Set objPresenter = New clsSummaryPresenter
End If
objPresenter.Show
Call objPresenter.ChangeLabelAndCaption(strText, strText2)
End Sub
Then, if you call like this in the immediate window:
call ShowMainForm("Just","testing")
You will get this:
Which is quite what you need. :)
The basic idea is:
Create a Function in witch you combine your arguments to a string like:
strOpenArg = "param1:=value1;param2:=value2;"
than open the form with the OpenArgs
DoCmd.OpenForm "UserForm", acNormal, , , , acDialog, strOpenArgs
get your value and close the Form
Value= Form_UserForm.Value
DoCmd.Close acForm, "UserForm", acSaveNo
in the UserForm set Form_open. Here you can get your parameters.you can devide this by string splitting.
Set also an OK Button, where you make the form just invisible and set the return value
Private Sub Form_Open(Cancel As Integer)
Dim strParameter as String
strParameter = Me.OpenArgs 'Here are your parmeters
End Sub
Private Sub ok_Click()
m_Value = "Your ReturnValue"
Me.Visible = False
End Sub
Private m_Value As String
Public Property Get Value() As String
Value = m_msgBoxResult
End Property
There's no way to one-liner the code like you want to, unfortunately. If all your userform code is self-contained then the only way for it to pass values out is to change the values of public variables. Mat's Mug's answer here is the layout I usually use when trying to simulate functions like 'InputBox' but you still can't get it in one line without writing a separate function. Using userform properties allows you to contain more of your code within the form itself.

Global reference to object on Click() sub (VBA)

I was wondering if there's a way to refer to the object of the "Click()" sub.
To make it clearer, let's say we have a button named foo1 and this button has a click sub "foo1_Click()". Does vba has a keyword to get the reference to foo1 that is global?
Something like:
Public Sub foo1_Click()
GlobalKeyword.Property
End Sub
P.s.: something like the word "this" from java refering to its own instance of class
Edit: In the example, the "GlobalKeyword" would refer to "foo1"
I think you're looking for the Application.Caller property found here.
In your case you would want to do something like....
Public Sub foo1_Click()
Dim button As Shape
Set button = ThisWorkbook.Sheets("sheetname").Shapes(Application.Caller)
End Sub
Of course after that you would want to do some error checking to make sure button is not nothing.
If you want to use the same code for a lot of buttons, then you may be better of using a separate subroutine.
Private Sub foo1_Click()
Call do_something
End Sub
Private Sub foo2_Click()
Call do_something
End Sub
Sub do_something() 'called by the foo _Click event
MsgBox Application.Caller
End Sub
This way, you it is easy to maintain the core functionality for all buttons simply by updating the do_something procedure.

VBA - pass variable between Excel projects

My question: How can I set value of a variable declared in other Excel Project?
Background:
I am working on calling a private sub from a diffrent Excel Project (I don't know if it matters, but the sub I am interested in is a part of Excel Add-In).
In the Add-In I have:
Public sapEEID As String
Private Sub UpdateLetterTemplate
I am able to run the sub using the:
Application.Run ("'Solutions Add-In.xlam'!UpdateLetterTemplate")
HOWEVER, variable sapEEID = ""
Is there a way to pass "17" as sapEEID when running UpdateLetterTemplate private sub?
You may need to modify your Subroutine somewhat but the following steps will work
Add Reference: You need to add a reference to your add-in (VBE -> Tools -> References)
ByRef Parameters: Also, make sure that your Sub can take a ByRef parameter. See sample code below.
Call the Subroutine: You're done, now in your code, once the reference is set, call the sub and pass your variable.
Sample Code for the Subroutine:
Public Sub ChangeToTen(ByRef a as double)
a = 10
End Sub
Calling Code in your main file:
Dim a as double
a = 1023.23
Call ChangeToTen(a)
MsgBox(a) ' It will show 10

When Creating Macro, What Setting Do I Need?

I have a bunch of VBA code that I'd like to throw in a macro.
Basically I call a bunch of subs that update the page.
It looks something like this:
Call Update1("Work", strConn)
Call Update2("Work", strConn)
Call Update3("Work", strConn)
where Update1, Update2, and update3 are all in the same sheet with the form
Public Sub Update1(strPlace, strConn)
SQL code..
End Sub
How would I throw it all in a macro? Could I copy and paste all the code (including the calls?) That hasn't work. Neither has taking only the Subs. Not sure what's next.
All you should need is to insert a module as a container for all this code. Unless each of the
UPDATE subs are specific to a single sheet, then they should probably be in the module as well.
All of your "call" statements will need to be in their own SUB myMain () ... END SUB
Once you've done this, you can either run it from the Tools, Macro list, or hook it into other events (a custom toolbar button or something else)
Edit: to be more precise, this is what your module should look like:
Sub myMainMacro()
'... initialize stuff
Call Update1("Work", strConn)
Call Update2("Work", strConn)
Call Update3("Work", strConn)
'... do more stuff
End Sub
Sub Update1(strPlace, strConn)
SQL code..
End Sub
Sub Update2(strPlace, strConn)
SQL code..
End Sub
'... other UPDATE SUBs here
Function CalculateValue(myInput as integer) as integer
'... do calculations here
CalculateValue = answer
End Function
All code (except for global variable declarations, which we haven't talked about) needs to be inside a SUB or a FUNCTION (a function returns a value, a SUB does not). Since they are all in the same module, the keyword PUBLIC isn't necessary.