Call VBA Class Module in Excel formula - vba

I know that a function of a Module can be called in a formula like this:
=GetMyModuleFunction()
Now I would like to call a Class Module function. I don't know if a Class Module can be instantiated in a formula at all so I've created a function in a Module so I can call it just like =GetMyModuleFunction():
' Class Module MyClassModule:
Public Property Get MyProperty() As String
MyProperty = "Hello World!"
End Property
Public Function GetMyFunction() As String
GetMyFunction = "Hello World!"
End Function
' End Class Module MyClassModule
' Module MyModule:
Public Function GetMyClassModule() As MyClassModule
Set GetMyClassModule = New MyClassModule
End Function
' End Module MyModule
So after that I tried in the formula bar:
=GetMyClassModule().GetMyFunction()
=GetMyClassModule().MyProperty
Which shows an error dialog that the formula is invalid. Is it not possible what I'm trying to achieve here? Currently I use Modules instead but functions and subs with duplicate names are confusing and error prone to use in Modules..

Your question is similar to the question asked here: Call VBA function that returns custom type from spreadsheet
You can only return data types that Excel understands from a user-defined function. Excel does not understand custom data types.
But you can wrap your class properties or functions with a regular module function (UDF) like so:
Public Function GetMyClassModuleFunction() As String
GetMyClassModuleFunction = GetMyClassModule.GetMyFunction()
End Function

Related

Custom Function Disable in Excel

I have created a custom function that I am using excplicitly in another module in VBA. Function looks something like this:
Function Coverts(ByVal inputString As String) As String
'Coverts code here
End Function
It works perfectly fine in both VBA and Excel UI. However, I do not want it to work or appear in Excel UI as I only want to use it in VBA. Is there a way to do this?
Thanks
Put
Option Private Module
at the top of the module containing your function.
From MSDN:
When a module contains Option Private Module, the public parts, for example, variables, objects, and user-defined types declared at module level, are still available within the project containing the module, but they are not available to other applications or projects.
you can add the keywords Public or Private to your functions, subs or global variable to have that specified.
so if you want to only want this function to be accessible by your code and not in excel sheets you can add private:
Private Function Coverts(ByVal inputString As String) As String
'Coverts code here
End Function
You can make the function inoperable if called from the worksheet by identifying the Application.Caller. If called as a function from the XL UI, this will be Range (i.e. the cell the function is within).
Function Coverts(ByVal inputString As String) As String
If TypeName(Application.Caller) = "Range" then
Coverts = cverr(xlerrna)
exit function
end if
'Coverts code here
End Function

Declaring a Sub/Function in another module accessible by other modules but not by user

I have a module ModuleA containing a Public function that the user will use on the spreadsheet:
Public Function UserCanSeeThis() As String
UserCanSeeThis = "Hello, " & UserCannotSeeThisButModuleACan() & " user"
End Function
I have a module ModuleB which contains a function that I want to use in ModuleA, but I don't want the user to see in the list of available functions when writing = into a cell or when reading the formulae dictionary:
Function UserCannotSeeThisButModuleACan() As String
UserCannotSeeThisButModuleACan = "my dear"
End Function
How should I declare the function in ModuleB so that ModuleA can see it, but the user cannot?
p.s. I've searched the site and only found the "solution" for Sub, I can declare the Sub as Private and then call it from the other module with Application.Run "mySub".
But I was hoping there was some more developed concept of "friendship" in VBA though.
Specify Option Private Module at the top of ModuleB; public members won't be (visibly) exposed to the user, but will be readily available from anywhere inside the VBA project.
Your Private+Application.Run hack is.. well, a hack. Don't do that - make/leave public members Public, and hide the module from the macros list with Option Private Module.

VBA global class variable

My obstacle is trying to get multiple subs to recognize class variables. When I try to declare them globally, I get a compile error: "Invalid outside procedure". Then, when I run a public function or sub to declare the variables, they remain undefined in the other subs. I want multiple subs to recognize the variables because their values are supposed to be altered via UserForm, and then utilized in a different sub.
If it could work in this manner, great, but I understand that my design could fundamentally be flawed. Please advise!
This is my Class definition, inserted as a Class module named "cRSM":
Option Explicit
Private pName As String
Private pDesiredGrowth As Double
'Name of RSM
Public Property Get Name() As String
Name = pName
End Property
Public Property Let Name(Value As String)
pName = Value
End Property
'Growth property
Public Property Get DesiredGrowth() As Double
DesiredGrowth = pDesiredGrowth
End Property
Public Property Let DesiredGrowth(Value As Double)
If Value > 0 And Value < 1 Then
pDesiredGrowth = Value
End If
End Property
This is invalid procedure error (which I put in the Global Declarations section):
'Bedoya
Dim Bedoya As cRSM
Set Bedoya = New cRSM
Bedoya.Name = "Bedoya"
And this is the "variable not defined error" (within a private sub):
Private Sub Add_Click()
**Bedoya.DesiredGrowth** = Txt2.Value
Thank you for your time
In a standard module (I name mine MGlobals), put
Public Bedoya As cRSM
Then in another standard module (I name mine MOpenClose), put
Sub Initialize()
If Not Bedoya Is Nothing Then
Set Bedoya = New cRSM
End If
End Sub
Any default properties you want set should be set in the Class_Initialize procedure. In any procedure that you want to use Bedoya, use
Initialize
and it will instantiate the global variable if necessary. The only difference between this and the New keyword is that you can't accidentally instantiate the variable with this method. You either call Initialize or you don't. A lot of VBA developers use New, but almost never do for that reason.
If I understood well You want a global object.
You can put the declaration in module like
public Bedoya As cRSM
then you have create the object ... you can use a global event inside the Workbook like
Private Sub Workbook_Open()
Set Bedoya = New cRSM
Bedoya.initialize("Bedoya") 'a method to initialize private variables
End Sub
Now you can use the global object. You have to restart the excel file or run this method manually.
Is not good style to use global variables, but sometimes is the more easy to do :P
What you want to do nowadays is done using singleton Software Pattern, but this is for other day hehehe

Scoped to be visible to any module but not outside the VBAProject

I have these function:
Function funcX() As Integer
funcX = 2
End Function
Public Function funcY() As Integer
funcY = 3
End Function
In a separate workbook I can run the following to access both of the above:
Sub xxx()
Excel.Application.Run "'myBook.xlsm'!funcX"
Excel.Application.Run "'myBook.xlsm'!funcY"
End Sub
How can I create a function that is scoped so it is available in all modules of its workbook but is not available outside that workbook?
To simply make the routines in a Standard Module private to the particular VBA Project, declare them as Public and add the statement:
Option Private Module
to the header of the Module.
For other considerations on controlling the visibility of VBA routines see here.

Calling a Sub or Function contained in a module using "CallByName" in VB/VBA

It is easy to call a function inside a classModule using CallByName
How about functions inside standard module?
''#inside class module
''#classModule name: clsExample
Function classFunc1()
MsgBox "I'm class module 1"
End Function
''#
''#inside standard module
''#Module name: module1
Function Func1()
MsgBox "I'm standard module 1"
End Function
''#
''# The main sub
Sub Main()
''# to call function inside class module
dim clsObj as New clsExample
Call CallByName(clsObj,"ClassFunc1")
''# here's the question... how to call a function inside a standard module
''# how to declare the object "stdObj" in reference to module1?
Call CallByName(stdObj,"Func1") ''# is this correct?
End Sub
I think jtolle's response addressed the question best - the small reference to Application.Run may be the answer. The questioner doesn't want to use simply func1 or Module1.func1 - the reason one would want to use CallByName in the first place is that the desired function.sub name is not known at compile time. In this case, Application.Run does work, e.g.:
Dim ModuleName As String
Dim FuncName As String
Module1Name = "Module1"
FuncName = "func1"
Application.Run ModuleName & "." & FuncName
You can also prepend the Project Name before the ModuleName and add another period ".".
Unfortunately, Application.Run does not return any values, so while you can call a function, you won't get its return value.
Although it is an old question and OP asked for CallByName in a standard module, the correct pieces of advice are scattered through answers and comments, and some may not be that accurate, at least in 2020.
As SlowLearner stated, Application.run DOES return a Variant, and in that way both branchs below are equivalent, except by handling errors, as commented around Horowitz's answer:
Dim LoadEnumAndDataFrom as Variant
'FunctionName returns a Variant Array
if fCallByName then
LoadEnumAndDataFrom = CallByName(ClassObj, "FunctionNameAtClass", VbMethod)
else
'After moving back function for a standard module
LoadEnumAndDataFrom = Application.Run("StandardModuleName" & "." & "FunctionNameAtStandard")
endif
I actually just did this above and had no errors at all, tested in Word, Excel and Access, and all return the same Array.
Unfortunately, there is an exception: Outlook's object Model is too protected and it does not have the Run method.
CallByName works only with class objects.
If your subroutine is in a standard module, you can do this:
Sub Main()
Module1.Func1
End Sub
If it's a function, then you'll probably want to capture the return value; something like this:
Sub Main()
Dim var
var = Module1.Func1
End Sub
Modules in VB6 and VBA are something like static classes, but unfortunately VB doesn't accept Module1 as an object. You can write Module1.Func1 like C.Func1 (C being an instance of some Class1), but this is obviously done by the Compiler, not at runtime.
Idea: Convert the Module1 to a class, Create a "Public Module1 as Module1" in your Startup-module and "Set Module1 = New Module1" in your "Sub Main".
Unfortunately it is not possible to prepend the ProjectName before the ModuleName and add another period "." In MS Word this throws a runtime error 438. The call is restricted to the use of simply ModuleName.ProcName.