How to restrict VBA function only on module - excel

I have very few knowledge of VBA and trying to learn it. I have made a VBA script which is something like this:
Function doIt()
Dim c As int...
.............
.............
.............
c = callFunction
...............
..............
End Function
Function callFunction(byVal num As Int)
..........
..........
..........
End Function
as you can see callFunction is a function which is called from the main function doIt. Suppose callFunction calculates square of one integer. The whole VBA scripts is kept on an addIn Module under respective AddIns folder in C drive. The function doIt works well while called from an excel worksheet. But the problem is if I call the function callFunction from worksheet it also works. How can I restrict callFunction only to the addIn module so that only module can use it and if someone call callFunction(2) from worksheet it will not give the square of 2 in worksheet?
Note: even if I make it Private, it can still be called from the worksheet.

I don't think you can block function functionality which allows to access it both in VBA and Excel cell.
However, I have some workaround idea which allows you to create function which gives different results when it is called in Cell, e.g. some info (or standard error) could be returned instead of calculation result.
Here is the code presenting this functionality. I think it's quite clear and understandable so you would not need additional comments.
Function callFunction(ByVal num As Integer)
On Error Resume Next
Dim tmpAdd
tmpAdd = Application.ThisCell.Address
If Err.Number = 0 Then
If Left(Application.ThisCell.Formula, 13) = "=callFunction" Then
'called from excel cell, but this function is called!
'returns any type of standard function error
callFunction = CVErr(XlCVError.xlErrNull)
Exit Function
End If
End If
'called from VBA/IDE environment or from other function
'standard calculation
callFunction = num ^ num
End Function
Edit Inspired by #DanielDusek answer (but a bit incomplete one) I mixed both Daniel and mine solution into one. So, new and rather complete code:
Function callFunction(ByVal num As Integer)
If TypeName(Application.Caller) = "Range" Then
If Left(Application.ThisCell.Formula, 13) = "=callFunction" Then
'called from excel cell, but this function is called!
'returns any type of standard function error
callFunction = CVErr(XlCVError.xlErrNull)
Exit Function
End If
End If
'called from VBA/IDE environment or from other function
'standard calculation
callFunction = num ^ num
End Function
Both solution will give num ^ num result if used in any VBA function/subroutine whichever the place of calling (indirect use). It will give Error value when called in Excel cell (direct use).

With Application.Caller property you can determine who called your function and if it was called from worksheet you can raise error or return what ever you want but different from you propper calculated result.
Private Function callFunction(ByVal num As Integer) As Variant
If (TypeName(Application.Caller) = "Range") Then
' function was called from worksheet
callFunction = "Invalid procedure call" ' or raise error Err.Raise Number:=5
Exit Function
End If
' continue ...
callFunction = num * num
End Function
About Application.Caller: http://msdn.microsoft.com/en-us/library/office/ff193687.aspx

Related

How to read another cell from within a UDF?

I am aware that it's very hard to change the value of another cell using a User Defined Function - forbbiden by MS, even. However, I'd like to just read from another cell and do something with that information, but the function never runs thoroughly. For instance, in
Public Function ADD(arg1 as Double, arg2 as Double) as Double
If Worksheets("sheet1").Cells(1,1).Value = 0 Then
ADD = -1
Exit Function
End If
MsgBox "I got here"
ADD = arg1 + arg2
End Function
the message box does not show up. It can only be the if-statement. When I remove it, it works. Is there any way to read from another fixed cell without crashing the function? The real function I'm working with does all the reading from within if-statements.
Also, this can be found on the MS website, but I don't understand why it prevents me from reading..
For correct calculation, all ranges that are used in the calculation should be passed to the function as arguments. If you do not pass the calculation ranges as arguments, instead of referring to the ranges within the VBA code of the function, Excel cannot account for them within the calculation engine. Therefore, Excel may not adequately calculate the workbook to make sure that all precedents are calculated before calculating the user-defined function.
Thank you in advance!
I changed the UDF name to ADDD() and it works fine:
Public Function ADDD(arg1 As Double, arg2 As Double) As Double
If Worksheets("Sheet1").Cells(1, 1).Value = 0 Then
ADDD = -1
Exit Function
End If
MsgBox "I got here"
ADDD = arg1 + arg2
End Function
You cannot run a message box with a UDF. Can you image how many times you would have to click "OK" if you could and your sheet recalculated frequently?
Instead, you can use a debug.print statement, and you can run your UDF from the VBA IDE to check the debug values. For example:
Private Function TestMyUDF()
Dim examplevalue1 as Double
Dim examplevalue2 as Double
MyUDF examplevalue1, examplevalue2
End Function
Off-question, but if your referenced (read-only) cell is significant, then use a named range (and make it a workbook name, not a sheet one). Let us call it "CheckSumCell" for the sake of an example.
Public Function MyUDF(arg1 as Double, arg2 as Double) as Double
If ThisWorkbook.Names("CheckSumCell").Value = 0 Then '<-- assumes single cell
MyUDF = -1
Exit Function
End If
MyUDF= arg1 + arg2
End Function
This will then better survive cell relocation (insertion of rows or columns)! If you decide to use another cell, then just change the definition of the named range and you will not require to edit the macro/UDF at all.

VBA reading global from cell

I am a complete VBA newbie, so apologies in advance for the trivial question. Consider the following code:
Dim frog As Double
frog = Range("A1").Value
Function test_func(ByVal a As Double, ByVal b As Double)
test_func = a ^ b
End Function
Private Sub btnAddNumbersFunction_Click()
MsgBox test_func(frog, 3)
End Sub
When I try to compile this, I get an error of "invalid outside procedure" with the "A1" highlighted. (I am trying to define a model with some user-settable parameters, and so this would be useful to have).
This code sample will compile, I have moved the variable assignment into the sub:
Dim frog As Double
Function test_func(ByVal a As Double, ByVal b As Double)
test_func = a ^ b
End Function
Private Sub btnAddNumbersFunction_Click()
'Assign values inside subs or functions
frog = Range("A1").Value
MsgBox test_func(frog, 3)
End Sub
It seems that introducing another "initialization" sub, where the globals are initialized, does the trick. The suggestions in the other answers are not satisfactory for the reasons explained in the question: having to initialize (or even mention) the parameters in every procedure that implicitly uses them defeats the purpose. I am a bit mystified about why this is the design (this is unlike any other language I know), but MSFT moves in mysterious ways its wonders to perform.

Why declare function's type?

Sorry if this has been asked, but seriously can't find anything, so would also appreciate on how to search for this stuff.
So my question: what is the point of declaring the function's type in general? E.g. here 'as double'
Function myFunction(ByVal j As Integer) As Double
Return 3.87 * j
End Function
For a normal variable it has tons of benefits, like less memory, easier to see typos, but why here?
Edit: so, it's good because we can avoid errors, like it giving back a different type of values than expected.
Functions RETURN something. That type is the type of the return.
In your function:
Function myFunction(ByVal j As Integer) As Double
Return 3.87 * j
End Function
You are returning a decimal, so type Double make sense.
If you don't return anything, then you can declare it as a Sub.
And, for clarification, your function would throw a compile error. Unlike other languages, in VBA to return, we set the function name's value to the thing we want to return:
Function myFunction(ByVal j As Integer) As Double
myFunction=3.87 * j
End Function
Now we can call this function to get the Double value that it creates:
Sub testSub()
msgbox("This is the result of the function: " & myFunction(10))
End Sub
Which would launch a message box saying "This is the result of the function: 38.7"
Since I can't mark a comment the answer, let me quote:
#John Coleman
My opinion is that it a good thing to declare your return types because it increases the likelihood that the compiler will complain when you are doing something that really doesn't make sense.
Excel VBA is different from other programming languages in that it centers around a particular application: Excel.
Functions are useful in Excel VBA primarily because they can be typed directly into a cell on a sheet by an end user. User defined functions provide near infinite flexibility. The value the user defined function prints to Excel is formatted based on the function's type--and in a program which is about data visualization, formatting is a huge part.
For example, try putting these four functions into a blank worksheet module:
Function myInt(x, y) As Integer
myInt = x / y
End Function
Function myDouble(x, y) As Double
myDouble = x / y
End Function
Function myString(x, y) As String
myString = x / y
End Function
Function myVariant(x, y)
myVariant = x / y
End Function
Next, enter each of these functions into a different cell in the workbook. Use x=1 and y=2.
myInt produces "=0"
myDouble produces "=0.5"
myString produces "'0"
myVariant produces "=0.5"
If you're okay with Excel deciding how to format your result, that's your choice, but specifying the type offers an entire new level of control. For example, by simply declaring a function an integer, you can avoid having to devote a line of code to rounding. By declaring a function to be a string, you can avoid several lines of formatting code trying to get a number to be saved as text instead.

How to make VBA function "VBA only" and disable it as UDF

I'm writing a VBA function which I want to be publically available in other VBA modules within the same document, however I don't want it to be available as a UDF (User defined function).
If I use the public access modifier however my function is also be available as a UDF-formula that can be called from the cells within the workbook. I don't want this.
Is there an access modifier or other way that can help me obtain this "VBA only" behaviour?
Kind regards
If you use Option Private Module in the module in which the function appears, the function can be declared as Public and used in any of your other modules within your VBA project, but won't be accessible by other applications or projects, including Excel itself.
This will return a #VALUE error if used in Excel.
Function VBAOnly() As Variant
If TypeName(Application.Caller) <> "Range" Then
VBAOnly = 1 'or some other return value
Else
VBAOnly = CVErr(xlErrValue)
End If
End Function
There's a little bit of confusion among answers, so here's a more encompassing explanation:
Technically... all functions in the standard VBA Modules can be called if the exact formula is entered. Even Formulas in Private Functions within Private Modules
Example
Option Private Module
Private Function hiddenEverythingExample() As String
hiddenEverythingExample= "NOPE!!!"
End Function
Will still return a value if a cell has =hiddenEverythingExample()
However, I believe the objective of the OP was to avoid having the itellisense populate these functions in the Excel Formula bar.
The most frequent way I accomplish this is create a specific Module for all VBA-Only functions and put Option Private Module in the module definition (area above the functions).
This ensures all functions in this module will NOT appear in itellisense, but still be accessible to other modules along with itellisense.
Defining asPrivate Function also accomplishes this, but then the function is scoped exclusively to that Module, which may or may not be the requirement.
Note that YowE3K infers that a function must be both Private Function AND Option Private Module, but only one OR the other is necessary to eliminate the itellisense.
Instead of writing a Function, write a Sub, and set the return via a ByRef argument. This way your function will be invisible to Excel (except via Alt F8, or Developer tab > Macros) and won't appear in Excel's intellisense.
Instead of
Function Add(Num1 As Double, Num2 As Double)
Add = Num1 + Num2
End Function
use
Sub AddInvisible(ByRef Result As Double, Num1 As Double, Num2 As Double)
Result = Num1 + Num2
End Sub
Note
That ByRef is not strictly necessary (since arguments are by default by reference in VBA) but it serves as a useful reminder that Result carries the return value.
You will need to make the necessary changes to your code, for example:
z = Add(x,y)
would become
AddInvisible z,x,y
as demonstrated below:
Sub DemoAddInvisible()
Dim Num1 As Double
Dim Num2 As Double
Dim Result As Double 'Result initialises to 0
Num1 = 1
Num2 = 2
AddInvisible Result, Num1, Num2
MsgBox Result ' See that Result has become 3
End Sub
A downside of all this is that the new code is somewhat harder to understand.
pass a parameter that only allows the function to run if a "magic* value is given to it.
Example - This will give the error #NAME! unless you know what the key is:
Function VBAOnly(key As Long)
If key <> 12345 Then
VBAOnly = CVErr(xlErrName)
Exit Function
End If
VBAOnly = True
End Function
Using the Private modifier should only allow the execution in the module the function exists in.

What is the great wisdom in defining your own object if I can't do this?

This is for Excel and VBA. Assume that BondClass has been properly defined in a Class Module. I get a #VALUE! when I type "=GetBondPrincipal()" into an Excel cell. Have I done something syntactically wrong or is this just not possible in Excel/VBA? I ask because what I really want to do is this:
Return a User Defined Data Type in an Excel Cell
but am unable to find a solution. So at the very least, I want to know if what I want to do below is possible.
Option Explicit
Function InitializeBond(ir As Double, p As Double) As BondClass
Dim mybond As BondClass
Set mybond = New BondClass
Call mybond.Initialize(ir, p)
InitializeBond = mybond
End Function
Function GetBondPrincipal()
Dim b As BondClass
Set b = New BondClass
b = InitializeBond(0.03, 100) //the code quits here,
//it doesn't like the BondClass return type?
GetBondPrincipal = b.GetPrincipal()
End Function
I know that in the example I have provided, I don't have to call InitializeBond and can simply type "Call b.Initialize(.03,100)". The code will work fine if I do this. But I can't seem to be able to get a UDF to return a type other than the built-in types. Any way to do any of this? Do I have to define assignment for non-built-in types?
Hint: use "SET".
(most common error made by VB programmers in VB6 and VBA).
Anyway, I don't find that very elegant.
I would rather write:
Function GetBondPrincipal()
Dim b As BondClass
Set b = New BondClass
with b
.param1 = 0.03
.param2 = 100
.InitializeBond
GetBondPrincipal = .GetPrincipal()
end with
End Function

Resources