excel function syntax calling error - vba

I've created this function:
Public Function getLastCellValue(ByVal row As Integer, ByVal column As String) As String
If (Cells(row, column).Value = "") Then
getLastCellValue = getLastCellValue(row - 1, column)
Else: getLastCellValue = Cells.Item(row, column).Value
End If
End Function
When I use it in a cell this:
=getLastCellValue(1,"C")
Excel tells me that the function contains an error and focuses on the second parameter: "C".
I'm going crazy because I do not understand the mistake.

Cells(row, column) expects numeric parameter values. If you mean to refer to cell addresses (like, "A1") then it might be simpler to use Range instead.
That said I strongly recommend taking this implementation over to Code Review once you get it to work as expected, ...I'd have several things to point out ;)
Your code works as is on my machine. My bet is on #Matteo's comment
Make sure your Excel language is using the comma (,) to separate functions parameters; if your name tells me something about your nationality, I guess you're using an Italian/Spanish system (which means you should separate inputs by semi-column (;)

Related

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...

Mystery Excel semicolon mode for data validation, stops recognizeing standard comma for unknown reason

In Excel VBA how can I set cell validation so that it works in all locales?
This code works in my locale, semicolon separated
Dim mySheet As Worksheet
Set mySheet = ThisWorkbook.Worksheets("Sheet 1")
Dim myRange As Range
Dim validValues as String
validValues = "a;b;c"
Set myRange = mySheet.Cells(2, 2)
myRange.Validation.Add _
Type:=xlValidateList, _
AlertStyle:=xlValidAlertStop, _
Formula1:=validValues
But I've seen that other locales needs comma separated
validValues = "a,b,c"
Essentially what I need is this, but I don't know how to determine 'id' in way that works in all locales.
' Internationalized delimiter
Dim id as String
id = ???
validValues = "a" & id & "b" & id & "c"
Edit:
There seems to be a "semicolon mode" of Excel that I do not understand and which I cannot reproduce reliably. I had a certain instance of Excel that got into this mode and all code run inside it worked with semicolon and not comma. I wrote a project in this instance and it worked, I would have thought I just made a mistake if not for all the code that I now have to rewrite...
Any hints in order to figure this out is appreciated.
Edit2:
After restarting Excel it changed it's mode to comma, I've been using it for a while but now this problem hit me again, Excel started using ';' (semicolon) instead of ',' (comma) as delimiter for my data validations.
This time I took a screenshot. Data validation is set to List.
Source is set to:
zero,one,both
This is the result, as you can see Excel does not care about my commas and displays all on the same line:
With source set like this it works:
zero;one;both
Result:
It is a mystery to me why Excel does this but I really need to find a way to make sure this never happens to my customer. Any help appreciated!
I have just dealed with a similar problem. I couldn't find a reason for why this happens, so I don't have a solution for good. Yet, I found a way around the problem that could be of use.
The only thing that you would need is to assign a valid value to the validated range before adding the validation.
After running your code, if the .Formula1 argument worked with semicolons, then there is no problem at all. If it didn't, the .Validation.Value property of the range returns False and you can use that as a trigger to rerun your code, replacing the semicolon separated list by a comma separated one. Then just rerun the validation.
For example:
myRange.value = "a" 'Assuming "a" is a valid value contained in the validvalues string
If myRange.Validation.Value = False Then
validvalues = replace(validvalues,";",",")
myRange.validation.modify Formula1:= validvalues
End if
Then you can clear the range value if you do not wish to have a default value shown.
I believe you are looking for a delimiter that would work on multiple scenarios (please let me know if this is not your query).
comma, semicolon, are sometimes good delimiter but often they pose serious syntax and some other string related errors .
Triple question mark ??? is also not a good choice in this case.
I prefer to go with | (the pipe) in these situations.
Sorry for the long answer :)
Microsoft says you need to use commas in VBA
https://support.microsoft.com/en-us/help/299490/data-validation-list-entries-all-on-one-line-in-excel

VBA error: A value used in the formula is the wrong data type?

I'm trying to create a function which generates a secret encoded message. It takes in three things: a string, for example, "testingtestingonetwothree", as well as the desired number of characters, for example 5, and the desired number of words, for example 5. It generates the message by starting at the first character, and extracting every fifth character through the string, putting these characters into a codeword, then starting at the second character and extracting every fifth character through the string, putting these into a second codeword, and so on. It just outputs a string, with the codewords separated by a space. So for this example it would produce: "tntnt egieh stntr tegwe isooe".
I'm okay at coding but new to VBA. I've made what I think is a valid function, but when it's used in the spreadsheet I get a #VALUE! error: "A value used in the formula is the wrong data type". This is the user defined function I made:
Function encode(strng, numchars, numwords)
Dim word As Integer
Dim step As Integer
Dim temp As String
Dim output As String
For word = 1 To numchars
step = word
temp = ""
Do While step <= Len(strng)
temp = temp & Mid(strng, 1, step)
step = step + numchars
Loop
If word = 1 Then output = temp Else output = output & " " & temp
Next word
encode = output
End Function
And when it's used in the spreadsheet I just call it, as in
=encode(A16,A7,A10)
Where A16 contains testingtestingonetwothree, A7 contains 5 and A10 contains 5.
Does my function seem okay? And is there anything you guys can see which could be giving the value error? Any help would be greatly appreciated, thanks a lot for reading.
EDIT: This now outputs a value, but the wrong value. It outputs: "ttestintestingtesttestingtestingontestingtestingonetwot tetestingtestingtestitestingtestingonetestingtestingonetwoth ", when it should output: "tntnt egieh stntr tegwe isooe". Is there anything you guys can see that my function is doing wrong?
EDIT2: After fixing the Mid function, to
temp = temp & Mid(strng, step, 1)
as per vacip's answer, the function now produces the correct answer.
Ok, everyone says it works, but for me, it doesn't produce the desired output. What the...???
Anyway, I think your Mid function is in the wrong order, try it like this:
temp = temp & Mid(strng, step, 1)
Also, make sure to properly declare your variables, like this:
Function encode(strng As String, numchars As Integer, numwords As Integer) As String
I have also rewritten your IF statement, that one-line thing is strange for me...
If word = 1 Then
output = temp
Else
output = output & " " & temp
End If
This way it worked for me.
Other people have addressed the type problem. Here is a different suggestion. The cipher that you are describing is a simple transposition cipher, specifically a columnar transposition ( https://en.wikipedia.org/wiki/Transposition_cipher#Columnar_transposition )
The way people did this pre-computer was to write the characters into a grid row by row then read them off column by column. In fact -- this is probably still the easiest way to implement it even with computers. Declare a variant which can be redimensioned to be an array with e.g. 5 columns (where 5 is the skip between letters) and the number of rows is chosen to be large enough so that the grid can hold the string. After you load up the characters row by row, read them off column by column using nested for loops.
Once you get a basic example working, you can try to implement a version which uses a key to determine the order that you read off the columns for added security.
Coding classical cryptography/cryptanalysis as an excellent way to learn a programming language. Almost the first thing I do when I try to learn a new language is to implement a Vigenere cipher in it. Even though it is long out of print and can be somewhat tricky to translate to modern dialects of Basic the book "Cryptanalysis for Microcomputers" by Caxton Foster is great fun and can be purchased for just a few dollars from online used bookstores.
You need to define your Function's type. So in this case I believe you would want
Function encode(strng, numchars, numwords) As String
I tested your code exactly as it is, and it worked fine.
So, your problem may be:
A certain argument of your function is not the right type. (I bet the len method is the problem in there).
Check if A16 is really a string. If not, consider converting it to a string before if you want to pass numbers too:
Function encode(strng as variant, numchars as integer, numwords as integer) as string
strng = str(strng)
Check also if A7 and A10 are really integers.

Execute a user-defined function into another cell VBA Excel

I need to automatize a process which execute various (a lot) user-defined function with different input parameters.
I am using the solution of timer API found in I don't want my Excel Add-In to return an array (instead I need a UDF to change other cells) .
My question is the following: "Does anybody can explain to me HOW IT IS WORKING?" If I debug this code in order to understand and change what I need, I simply go crazy.
1) Let say that I am passing to the public function AddTwoNumbers 14 and 45. While inside AddTwoNumber, the Application.Caller and the Application.Caller.Address are chached into a collection (ok, easier than vectors in order not to bother with type). Application.Caller is kind of a structured object where I can find the function called as a string (in this case "my_function"), for example in Application.Caller.Formula.
!!! Nowhere in the collection mCalculatedCells I can find the result 59 stored.
2)Ok, fair enough. Now I pass through the two UDF routines, set the timers, kill the timers.
As soon as I am inside the AfterUDFRoutine2 sub, the mCalculatedCell(1) (the first -- and sole -- item of my collection) has MAGICALLY (??!?!?!!!??) obtained in its Text field exactly the result "59" and apparently the command Set Cell = mCalculatedCells(1) (where on the left I have a Range and on the right I have ... I don't know) is able to put this result "59" into the variable Cell that afterward I can write with the .Offset(0,1) Range property on the cell to the right.
I would like to understand this point because I would like to give MORE task to to inside a single collection or able to wait for the current task to be finished before asking for a new one (otherwise I am over-writing the 59 with the other result). Indeed I read somewhere that all the tasks scheduled with the API setTimer will wait for all the callback to be execute before execute itself (or something like this).
As you can see I am at a loss. Any help would be really really welcomed.
In the following I try to be more specific on what (as a whole)
I am planning to achieved.
To be more specific, I have the function
public function my_function(input1 as string, Field2 as string) as double
/*some code */
end function
I have (let's say) 10 different strings to be given as Field2.
My strategy is as follow:
1)I defined (with a xlw wrapper from a C++ code) the grid of all my input values
2)define as string all the functions "my_function" with the various inputs
3)use the nested API timer as in the link to write my functions IN THE RIGHT CELLS as FORMULAS (not string anymore)
3)use a macro to build the entire worksheet and then retrieve the results.
4)use my xlw wrapper xll to process further my data.
You may wonder WHY should I pass through Excel instead of doing everything in C++. (Sometime I ask myself the same thing...) The prototype my_function that I gave above has inside some Excel Add-In that I need to use and they work only inside Excel.
It is working pretty well IN THE CASE I HAVE ONLY 1 "instance" of my_function to write for the give grid of input. I can even put inside the same worksheet more grids, then use the API trick to write various different my_functions for the different grids and then make a full calculation rebuild of the entire worksheet to obtain the result. It works.
However, as soon as I want to give more tasks inside the same API trick (because for the same grid of input I need more calls to my_function) I am not able to proceed any further.
After Axel Richter's comment I would like to ad some other information
#Axel Richter
Thank you very much for your reply.
Sorry for that, almost surely I wasn't clear with my purposes.
Here I try to sketch an example, I use integer for simplicity and let's say that my_function works pretty much as the SUM function of Excel (even if being an Excel native function I could call SUM directly into VBA but it is for the sake of an example).
If I have these inputs:
input1 = "14.5"
a vector of different values for Field2, for instance (11;0.52;45139)
and then I want to write somewhere my_function (which makes the sum of the two values given as input).
I have to write down in a cell =my_function(14.5;11), in the other =my_function(14.5;0.52) and in a third one =my_function(14.5;45139).
These input changes any time I need to refresh my data, then I cannot use directly a sub (I think) and, in any case, as far as I understand, in writing directly without the trick I linked, I will always obtain strings : something like '=my_function(14.5;0.52). Once evaluated (for example by a full rebuild or going over the written cell and make F2 + return) will give me only the string "=my_function(14.5;0.52)" and not its result.
I tried at the beginning to use an Evaluate method which works well as soon as I write something like 14.5+0.52, but it doesn't work as soon as a function (nor a user-defined function) is used instead.
This is "as far as I can understand". In the case you can enlighten me (and maybe show an easier track to follow), it would be simply GREAT.
So far the comments are correct in that they repeat the simple point that a User-Defined Function called a worksheet can only return a value, and all other actions that might inject values elsewhere into the worksheet calculation tree are forbidden.
That's not the end of the story. You'll notice that there are add-ins, like the Reuters Eikon market data service and Bloomberg for Excel, that provide functions which exist in a single cell but which write blocks of data onto the sheet outside the calling cell.
These functions use the RTD (Real Time Data) API, which is documented on MSDN:
How to create a RTD server for Excel
How to set up and use the RTD function in Excel
You may find this link useful, too:
Excel RTD Servers: Minimal C# Implementation
However, RTD is all about COM servers outside Excel.exe, you have to write them in another language (usually C# or C++), and that isn't the question you asked: you want to do this in VBA.
But I have, at least, made a token effort to give the 'right' answer.
Now for the 'wrong' answer, and actually doing something Microsoft would rather you didn't do. You can't just call a function, call a subroutine or method from the function, and write to the secondary target using the subroutine: Excel will follow the chain and detect that you're injecting values into the sheet calculation, and the write will fail.
You have to insert a break into that chain; and this means using events, or a timer call, or (as in RTD) an external process.
I can suggest two methods that will actually work:
1: Monitor the cell in the Worksheet_Change event:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim strFunc As String strFunc = "NukeThePrimaryTargets" If Left(Target.Formula, Len(strFunc) + 1) = strFunc Then Call NukeTheSecondaryTargets End If End Sub
Alternatively...
2: Use the Timer callback API:
However, I'm not posting code for that: it's complex, clunky, and it takes a lot of testing (so I'd end up posting untested code on StackOverflow). But it does actually work.
I can give you an example of a tested Timer Callback in VBA:
Using the VBA InputBox for passwords and hiding the user's keyboard input with asterisks.
But this is for an unrelated task. Feel free to adapt it if you wish.
Edited with following requirements: It is necessary to run a user defined worksheet function, because there are addins called in this function and those work only within a Excel sheet. The function has to run multiple times with different parameters and its results have to be gotten from the sheet.
So this is my solution now:
Public Function my_function(input1 As Double, input2 As Double) As Double
my_function = input1 + input2
End Function
Private Function getMy_Function_Results(input1 As Double, input2() As Double) As Variant
Dim results() As Double
'set the Formulas
With Worksheets(1)
For i = LBound(input2) To UBound(input2)
strFormula = "=my_function(" & Str(input1) & ", " & Str(input2(i)) & ")"
.Cells(1, i + 1).Formula = strFormula
Next
'get the Results
.Calculate
For i = LBound(input2) To UBound(input2)
ReDim Preserve results(i)
results(i) = .Cells(1, i + 1).Value
Next
End With
getMy_Function_Results = results
End Function
Sub test()
Dim dFieldInput2() As Double
Dim dInput1 As Double
dInput1 = Val(InputBox("Value for input1"))
dInput = 0
iIter = 0
Do
dInput = InputBox("Values for fieldInput2; 0=END")
If Val(dInput) <> 0 Then
ReDim Preserve dFieldInput2(iIter)
dFieldInput2(iIter) = Val(dInput)
iIter = iIter + 1
End If
Loop While dInput <> 0
On Error GoTo noFieldInput2
i = UBound(dFieldInput2)
On Error GoTo 0
vResults = getMy_Function_Results(dInput1, dFieldInput2)
For i = LBound(vResults) To UBound(vResults)
MsgBox vResults(i)
Next
noFieldInput2:
End Sub
The user can input first a value input1 and then input multiple fieldInput2 until he inputs the value 0. Then the results will be calculated and presented.
Greetings
Axel

MS-Access: Replace "bracket"

In one of the ms-access table I work with we have a text field with a set size.
At the end of this field there is some extra code that varies depending on the situation.
I'm looking for a way to remove one of these code but even when the last part is truncated by the field maximum size.
Let's call the field "field" and the code I'm looking to remove "abc-longcode".
If I use the replace SQL function with the string abc-longcode the query will only work when the code is complete.
If I also want my update query (that does nothing but remove this specific code at the end of my field) to work on incomplete codes how would that translate into ms-SQL?
It would have to remove (or replace with "" to be precise) all of the following (example of course, not the real codes):
abc-longcode
abc-longcod
abc-longco
abc-longc
abc-long
abc-lon
abc-lo
abc-l
Obviously I could do that with several queries. Each one replacing one of the expected truncated codes... but it doesn't sound optimal.
Also, when the field is big enough to get all of the code, there can sometime be extra details at the end that I'll also want to keep so I cannot either just look for "abc-l" and delete everything that follows :\
This query (or queries if I can't find a better way) will be held directly into the .mdb database.
So while I can think of several ways to do this outside of a ms-sql query, it doesn't help me.
Any help?
Thanks.
You can write a custom VBA replace method that will replace any of the given cases {"abc-longcode", ... "abc-l"}. This is essentially the same tack as your "several queries" idea, except it would only be one query. My VBA is rusty, but something like:
public function ReplaceCodes(str as string) as string
dim returnString as string
returnString = str
returnString = replace(returnString,"abc-longcode","")
// ... etc...
ReplaceCodes = returnString
end function
I may have gotten the parameter order wrong on replace :)
I would use my own custom function to do this using the split function to get the first part of the string. You can then use that value in the update query.
Public Function FirstPart(thetext As String) As String
Dim ret As String
Dim arrSplitText As Variant
arrSplitText = Split(thetext, "-")
ret = arrSplitText(0)
FirstPart = ret
End Function
Can you use:
Left(FieldX,InStr(FieldX,"abc-")-1)
EDIT re Comment
If there is a space or other standard delimiter:
IIf(InStr(InStr(FieldX, "abc-"), FieldX, " ") = 0, Left(FieldX, InStr(FieldX, "abc-") - 1), Replace(FieldX, Mid(FieldX, InStr(FieldX, "abc-"), InStr(InStr(FieldX, "abc-"), FieldX, " ") - InStr(FieldX, "abc-")), ""))