How to pass formula to another procedure? - vba

Sorry if this question looks quite random, as I am still learning VBA.
So thing is that my procedure requires input variable for the conditional formatting formula, and I am not quite sure if it requires me to pass on the formula from another procedure.
i.e.
Sub Procedure1 (FormulaX As ??)
With Worksheets("Sheet1").Range("Range1").FormatConditions. _
Add(Type:=xlExpression, Formula1=FormulaX)
...
End With
End Sub
Sub Procedure2
Call Procedure1 ("=$A1<5")
End Sub
I am very beginner into the VBA and I am quite confused with passing variables for conditional formatting formula. (Maybe I am finding it hard to understand the sentence clearly)
Or if there is any other way to approach this, what will that be?
Edit: Instruction given is "Your procedure definition should require input variables for the conditional formatting formula"

You can just do:
Sub Procedure1 (FormulaX As String)
....
The instructions simply asks for an input, which can be realized with a String.

Related

Passing arguments from Visio ShapeSheet to procedure in VBA

I have been working with Visio VBA for a couple of years. I pass arguments from the ShapeSheet of a shape to several procedures in VBA using either RUNMACRO() or CALLTHIS() Functions.
I either pass pass plain strings or the value of different formulas in the ShapeSheet. The most common argument that I pass to my procedures is the ID() of the shape.
After a patch update of Windows: https://support.microsoft.com/en-us/topic/march-15-2021-kb5001566-os-build-18363-1441-out-of-band-23c4c824-8638-43e9-a381-ff58213ae6fe, I am no longer able to pass arguments from the ShapeSheet of a Shape to my procedures in VBA. Whenever I use RUNMACRO or CALLTHIS, all the arguments that I pass, even if they are plain strings, in my procedure side everything that I get is "", blank information.
Is there a way to know if this is a bug or if this is on purpose, maybe as a security measure?
Here is a simple example procedure:
Public Function HelloWorld(Number As Integer)
If Number = 1 Then
MsgBox ("Hello World 1")
End If
If Number = 2 Then
MsgBox ("Hello World 2")
End If
End Function
And this procedure is called in the EventDblClick of a Shape:
RUNMACRO("HelloWorld(1)","Test")
When the EventDblClick is triggered the value of Number stays as null, ignoring the argument sent in the RUNMACRO function, which is a 1. Since the argument of Number in the HelloWorld() function is not optional, it triggers a Compile error message
"Argument not optional"
My programs were also recently affected by this update. Previously, I was able to structure the ‘macroname’ string in RUNMACRO (macroname [,projname_opt]) to include arguments to my functions and subroutines (as in Alexis’ HelloWorld example). However, that suddenly seems to be no longer possible. It is almost as if RUNMACRO is now modifying the ‘macroname’ string to exclude any arguments prior to making the call to the Function or Subroutine.
One workaround that I have found is to use CALLTHIS and restructure the VBA Function or Subroutine slightly as shown below. That said, it is going to be a huge hassle for me to execute this change in every cell, in every shape, in every file using the RUNMACRO formula. I’m going to have to write a separate macro just to execute this change.
Workaround:
CALLTHIS(HelloWorld,,1)
[NOTE: The two commas are intentional.]
Public Function HelloWorld(callingShape as Visio.Shape, number as Integer)
‘Insert Code Here
End Function

Name error when trying to call a function

I'm still learning VBA so I might be doing a lot of very basic mistakes. At the moment I'm trying to make a macro which could count the rows of a table. This sub works perfectly (with a table named "Tab").
Sub AddRowTable()
ActiveSheet.ListObjects("Tab").ListRows.Add (2)
End Sub
However, when I try to convert this sub to a function so I can call it with the name of the table as variable, I get a name error when writing "=AddRowTableFunction(Tab)" in a cell.
Function AddRowTableFunction(TableName)
ActiveSheet.ListObjects(TableName).ListRows.Add (3)
End Function
I understand that it is just a problem of type, but I just cant find how to do it properly.
Thanks.
At the moment I'm trying to make a macro which could count the rows of a table.
First off, that is not what your code is doing. Your code is adding a row to the table. The number of rows would be accessed using ListRows.Count.
when I try to convert this sub to a function so I can call it with the name of the table as variable...
You don't need a Function in order to include a variable. The differance between a Sub and a Function is that a Function returns a variable, while a Sub does not (i.e. a Function gives back a variable to the code that used it). Both Subs and Functions can take variables.
A Function that returns the number of rows in a given table would be something like this:
Function AddRowTableFunction(TableName As String)
AddRowTableFunction = ActiveSheet.ListObjects(TableName).ListRows.Count
End Function
For adding a row, you would probably use a Sub, because the action of adding a row doesn't return any information:
Sub AddRowTable(TableName As String)
ActiveSheet.ListObjects(TableName).ListRows.Add
End Sub
Finally, when using the Function in a formula, as Apafey pointed out, you need to write "Tab" (in quotes), not just Tab. "Tab" tells Excel to pass the text of the word Tab, while Tab tells Excel to look for a range named Tab, which probably doesn't exist.
You should try:
=AddRowTableFunction("Tab")
You wrote:
=AddRowTableFunction(Tab)
that is not fine.
As FaneDuru said, an UDF function can't change other cells, which explains the error). Thnaks !

How can I run several macros where the Macro name begins with a specific string of characters?

I've created several custom functions which I would like to Register. Currently, I have a different procedure for which I specify the registration for each function (there's no issue with that piece). However, the only way I know of to registering all these functions is by calling each Macro by name in another procedure like this:
Sub spRegisterFunctions()
Call spRegisterCUSTOMAfunction
Call spRegisterCUSTOMBfunction
Call spRegisterCUSTOMCfunction
Call spRegisterCUSTOMDfunction
End Sub
I'm actually looking for something more dynamic so that every time I create a new function, and it's corresponding "spRegister..." procedure, I don't have to remember to add the "Call" code to the "Sub spRegisterFunction()" procedure for that specific function.
Here's an example of what attempting to do:
Sub spRegisterFunctions()
Dim mc as Macro
For Each mc in VBProject("NameOfProject").Module("NameOfModule")
If Left(mc.Name,10)="spRegister" then
Call mc
End If
Next mc
End Sub
As you can see, I'm attempting to run any macro in a specific module who's name begins with "spRegister". Obviously the code above will not work, partly because some of those objects don't even exist.
Is there any way to do this?

How do I pass a range obj variable to a sub in Excel VBA (2016) [duplicate]

This question already has an answer here:
Array argument must be ByRef
(1 answer)
Closed 6 years ago.
Given the following code:
I can not seem to successfully pass a Range Object Variable from one sub-function to another. I spent an entire day researching, and experimenting before I swallowed pride and came here.
Please read the comments below, and reply with any ideas you have regarding why the LAST two lines will not behave.
Public Sub doSomethingToRows(ROI As Range)
*'do Something with the cell values within the supplied range*
End Sub
'
Public Sub testDoAltRows()
Dim RegionOfInterest As Range 'is this an object or not?
'*The following yields: Class doesn't support Automation (Error 430)*
'*Set RegionOfInterest = New Worksheet 'this just gives an error*
Set RegionOfInterest = Worksheets("Sheet1").Range("A1")
RegionOfInterest.Value = 1234.56 '*okay, updates cell A1*
Set RegionOfInterest = Worksheets("Sheet1").Range("B5:D15")
RegionOfInterest.Columns(2).Value = "~~~~~~" '*okay*
'doSomethingToRows (RegionOfInterest) 'why do I get "OBJECT IS REQUIRED" error?
doSomethingToRows (Worksheets("Sheet1").Range("B5:C15")) 'but this executes okay
End Sub
From the msdn documentation of the Call keyword statement,
Remarks
You are not required to use the Call keyword when calling a procedure.
However, if you use the Call keyword to call a procedure that requires
arguments, argumentlist must be enclosed in parentheses. If you omit
the Call keyword, you also must omit the parentheses around
argumentlist. If you use either Call syntax to call any intrinsic or
user-defined function, the function's return value is discarded.
To pass a whole array to a procedure, use the array name followed by
empty parentheses.
From a practical standpoint, even though Subs can be called with or without the "Call" keyword, it makes sense to pick one way and stick with it as part of your coding style. I agree with Comintern - it is my opinion, based on observation of modern VBA code, that using the "Call" keyword should be considered deprecated. Instead, invoke Subs without parenthesis around the argument list.
And now the answer to the important question:
Why does your code throw an error?
Take for example the following Subroutine:
Public Sub ShowSum(arg1 As Long, arg2 As Long)
MsgBox arg1 + arg2
End Sub
We have established that, if not using the Call keyword, Subs must be invoked like so:
ShowSum 45, 37
What happens if it were instead called like ShowSum(45, 37)? Well, you wouldn't even be able to compile as VBA immediately complains "Expected =". This is because the VBA parser sees the parenthesis and decides that this must be a Function call, and it therefore expects you to be handling the return value with an "=" assignment statement.
What about a Sub with only one argument? For example:
Public Sub ShowNum(arg1 As Long)
MsgBox arg1
End Sub
The correct way to call this Sub is ShowNum 45. But what if you typed this into the VBA IDE: ShowNum(45)? As soon as you move the cursor off of the line, you'll notice that VBA adds a space between the Sub name and the opening parenthesis, giving you a crucial clue as to how the line of code is actually being interpreted:
ShowNum (45)
VBA is not treating those parenthesis as if they surrounded the argument list - it is instead treating them as grouping parenthesis. MOST of the time, this wouldn't matter, but it does in the case of Objects which have a default member.
To see the problem this causes, try running the following:
Dim v As Variant
Set v = Range("A1")
Set v = (Range("A1")) '<--- type mismatch here
Notice that you get a "Type Mismatch" on the marked line. Now add those two statements to the watch window and look at the "Type" column:
+-------------+-----+--------------+
| Expression |Value| Type |
+-------------+-----+--------------+
|Range("A1") | |Object/Range |
|(Range("A1"))| |Variant/String|
+-------------+-----+--------------+
When you surround an Object with grouping parenthesis, its default property is evaluated - in the case of the Range object, it is the Value property.
So it's really just a coincidence that VBA allowed you to get away with "putting parenthesis around the argumentlist" - really, VBA just interprets this as grouping parenthesis and evaluates the value accordingly. You can see by trying the same thing on a Sub with multiple parameters that it is invalid in VBA to invoke a Sub with parenthesis around the argument list.
#PaulG
Try this:
Public Sub Main()
Debug.Print TypeName(Range("A1"))
Debug.Print TypeName((Range("A1")))
End Sub
okay, I knew after I posted this question I'd be struck by lighting and receive an answer.
When passing an object VARIABLE to a sub-function and wishing to use parentheses "()", one must use CALL! Thus the correction to my code sample is:
**CALL doSomethingToRows(RegionOfInterest)**
Thank you!
Maybe we're talking about different things, but here's an example to make it a bit clearer what I mean.
Option Explicit
Sub TestDisplay()
Dim r As Range
'Create some range object
Set r = Range("A1")
'Invoke with Call.
Call DisplaySomething(r)
'Invoke without Call.
DisplaySomething r
End Sub
Sub DisplaySomething(ByVal Data As Range)
Debug.Print "Hi my type is " & TypeName(Data)
End Sub
Both calls work perfectly. One with Call and the other without.
Edit:
#Conintern. Thanks for explaining that. I see what is meant now.
However, I still respectively disagree.
If I declare the following:
Function DisplaySomething(ByVal Data As String)
DisplaySomething = "Hi my type is " & TypeName(Data)
End Function
and invoke it:
Debug.print DisplaySomething(Range("A1"))
I believe that Excel has been clever and converted to a string. It can do that by invoking the Default Parameter and can convert to a string.
However, as in the original parameter example, If I declare the following:
Function DisplaySomething(ByVal Data As Range)
DisplaySomething = "Hi my type is " & TypeName(Data)
End Function
There is no call on the Default Parameter, however it is called, because Excel was able to resolve it to that type.
Function DisplaySomething(ByVal Data As Double)
DisplaySomething = "Hi my type is " & TypeName(Data)
End Function
will return a double because it was able to coerce to a double.
Indeed in those examples the Default was called.
But in this example we are defining as Range. No Default called there however it is invoked - brackets or no brackets.
I believe this is more to do with Excel and data coercion.
Similar to the following:
Public Function Test(ByVal i As String) As Integer
Test = i
End Function
and invoking with:
Debug.print Test("1")
BTW, yes I know this isn't an object without a Default parmeter. Im pointing out data coercion. Excel does its best to resolve it.
Could be wrong mind you...

Addin Function, reference active worksheet

I have created an addin which has 1 ribbon control, which performs a SQL statement on an Access DB and pastes the result, one cell to the right. This works perfectly. It is called SubTest(c as iribboncontrol) I also have another sub, called GetData(arg1,arg2) which I intended to use from the worksheet, as =GetData("01/02/2016","Open") and then one to the right again, the customised SQL returned is presented. This all works ok, until the pasting ActiveCell.Offset(1, 1).CopyFromRecordset rstData works fine from the ribbon, but not from the function call, am I missing something simple.
Having get data, like so, still gives errors
public sub get_data()
activecell.offset(1,1).value="TEST"
end sub
Many thanks
A function takes inputs and returns a value. It cannot have side-effects1. Hence, you can't call a procedure that alters cell values inside a function that's called from a worksheet cell, as Scott Craner already mentioned.
There are a number of things that are wrong with this though:
Public Sub get_data()
ActiveCell.Offset(1,1).Value="TEST"
End Sub
Code that relies on ActiveCell means you have other code that uses Select and Activate. This makes your code extremely frail and error/bug prone, avoid them (see how).
Avoid underscores in procedure names; use PascalCase instead. This isn't just "because it's convention" - VBA will refuse to compile more advanced code that has underscores in interface member names; so even if you don't ever use interfaces and the Implements keyword, make it a habit to avoid underscores in member names. In VBA the underscore has a special meaning, such as you can see in event handler procedures: ObjectName_MemberName, e.g. Button1_Click.
Name things for what they do. "get data" would be the name of a function, that "gets" a value [and returns it]. A Sub that "gets" something makes no sense. Your procedure doesn't "get" anything, it "sets" a value if anything.
1 Function procedures called from VBA code can definitely have side-effects. The fact is, in an ideal world they shouldn't; use procedures for side-effecting code instead. Excel worksheet functions are pure functions, and user-defined functions are required to be more or less pure functions as well, else they simply can't be used in a worksheet. Take some input, compute a value, return that value: that's all a function does.