Pass multiple arguments to Excel Macro from Access? - excel

I have a sub (macro) in Excel that I want to be able to call from Access, which looks like this:
Sub myMacro(param1 as string, param2 as string)
...
End Sub
In Access, I have:
xlBook.Application.Run "myMacro", string1, string2
But I get the error :
Runtime Error 450:
Wrong number of arguments or invaluid property assignment
How do I pass multiple parameter to Excel?

As per #Remou's comment, I checked the variable types being passed.
In this case, string2 was a Variant that was supposed to hold a String of numbers, but when the string of numbers got stored in string2 (the Variant), it was converted to a number (stored within a variant [?]).
Nonetheless, stricter (and thus correct) type declarations solved the issue.

Related

Dictionary lookup fails

I am writing an Excel VBA program that validates a school course schedule. A key component is a global dictionary object that keeps track of the course number (the key) and the number of times that course is scheduled (the item). I have successfully created and loaded the dictionary. I'm trying to lookup the value associated with the course key, but have been unable to do so using the one-line examples I've found at this site. I'd like to use this line of code:
intCourseCnt = gdicCourses("BAAC 100")
or
intCourseCnt = gdicCourses.Item("BAAC 100")
but neither work (actually, the "BAAC 100" part is a string variable, but it won't even work if I hardcode a course in.) Instead, I have to use the kludgy loop code below to lookup the course count:
Private Function Check_Course_Dup_Helper(strCourse As String) As Boolean
Dim k As Variant
Check_Course_Dup_Helper = False
' Read thru dictionary. Look to see if only 1 occurrence then jump out.
For Each k In gdicCourses.Keys
If k = strCourse Then
If gdicCourses.Item(k) = 1 Then
Check_Course_Dup_Helper = True
Exit Function
End If
Exit Function
End If
Next
End Function
Is there a way to rewrite this so that I can lookup of the item value without the loop?
Thank you.
Thanks for the prompt replies. Answers below:
David, the gdicCourses("BAAC 100") code value while the program is running is "empty" which makes the receiving variable equal to 0. The result is the same if I use strCourse variable. Also, the dictionary populating code is shown below. I do not believe it is a problem because I can correctly access the values elsewhere in the program where For-Each-Next loops that use a range variable are employed. Whitespace and non-printable characters are not present.
My guess is that I need to use a range to reference the position in the dictionary rather than a string. I've tried pretty much every combination of this that I can think of, but the value is still "empty".
Set gdicCourses = New Scripting.Dictionary
For Each c In Worksheets("Tables").Range("combined_courses").Cells
If Not (gdicCourses.Exists(c)) Then
gdicCourses.Add c, (Application.WorksheetFunction.CountIF(Range("MWF_Table_Full"), c
(Application.WorksheetFunction.CountIf(Range("TTh_Table_Full"), c)))
End If
Next

VBA Access Arguments Passing Values

Access 2013
I'm calling a formula to modify a string and it's changing the values w/in the parent sub.
Example:
Debug.Print Str 'Hello World my name is bob
BOBexists = InStringChceck(Str,"bob")
Debug.Print Str 'HELLO WORLD MY NAME IS BOB
Debug.Print BOBexists 'TRUE
I've used this function, InStringCheck, in Excel VBA before (and it's just an example, all of my string tools are doing this same thing now and I don't know why)
Function InStringCheck(Phrase as string, Term as string) as Boolean
Phrase = UCase(Phrase)
Term = UCase(Term)
if instr(1, Phrase, Term) then InStringCheck = True else InStringCheck = False
end function
In several of my functions I manipulate the input variables, to arrive at a solution, but I don't want those manipulations to persist outside of the function unless I pass them back up - some how they're being passed up, but they're not dimed as public variables
VBA parameters are implicitly passed by reference (ByRef). This means you're passing a reference to the value, not the value itself: mutating that value inside the procedure will result in that mutated value being visible to the calling code.
This is often used as a trick to return multiple values from a function/procedure:
Public Sub DoSomething(ByVal inValue1 As Integer, ByRef outResult1 As Integer, ...)
You have two options:
Pass the parameters by value (ByVal)
Introduce local variables and mutate them instead of mutating the paramters (and heck, pass the parameters ByRef explicitly)
If you have lots of occurrences of parameters being implicitly passed ByRef in your project, fixing them everywhere can easily get tedious. With Rubberduck you can easily locate all occurrences, navigate there, and apply appropriate fixes:
Disclaimer: I'm heavily involved in the Rubberduck project.
Building a little on #Sorcer's answer, VBA has default Sub/Functions parameters passing "by reference" (i. e.: "ByRef" keyword assumed if not specified) so that if you don't want their "inside" modifications survive outside them you have to explicitly type "ByVal" keyword before them in the arguments list.
But you have the option to avoid such modifications take place altoghether by using StrComp():
Function InStringCheck(Phrase as string, Term as string) as Boolean
InStringCheck = StrComp(Phrase, Term, vbTextCompare) = 0
End Function
Which could also lead you to avoid the use of InStringCheck() in favour of a direct use of StrComp() in your code

XLL issue with xlfEvaluate

I have this problem with a very simple function written in an XLL, using VS2012. I have tried reading up in MSDN and Steve Dalton's book, and I cannot see what I am doing wrong.
The tricky bit is that I need my function to read values in worksheet cells other than the one from which it is called. The function takes no arguments, and returns an integer. I have declared it as J# (the # signifying that it can call XLM functionality as advised by Dalton...although I still get the same problem without the #). I have not included the declaration of my function to save space, but it is simple and I do not think it is the cause of the problem.
This first block of code works fine. I wrote it just to build confidence.
//This block works correctly. A trial copied from the old Excel 97 documentation
XLOPER12 xlInput1, xlOutput2;
/* Evaluate the string "2+3" */
xlInput1.xltype = xltypeStr;
xlInput1.val.str = L"\0032+3"; //prefix with string length in Octal
Excel12(xlfEvaluate, &xlOutput2, 1, (LPXLOPER12) &xlInput1);
//works OK, and xlOutput2 contains 5
But this second block does not work. I cannot see why. I am trying to read a value from a cell, which is a different cell from that from which the function was called. What I get is an return XLOPER12 that contains an error (xltypeErr) and junk values in the val.num field (the worksheet cell does contain an integer value).
//This block does not work
XLOPER12 xlInput3, xlOutput3;
/* Look up the name Tst on the active sheet called Sht */
xlInput3.xltype = xltypeStr;
xlInput3.val.str = L"\003Tst"; //this also gives problems regardless of whether the string is defined as \004!Tst or \007Sht!Tst
Excel12(xlfEvaluate, &xlOutput3, 1, (LPXLOPER12) &xlInput3); //xlOutput3 now has a type of xltypeErr, rather than the correct integer value on the worksheet
Can you kindly explain what is going wrong?
If you're trying to read a value from a cell that is different from the cell calling the function you'll need a parameter to refer to that different cell. For example in A1 you may have '=myfunc(A2)'. Then your C++ extension func will need to be declared 'JP#', with the P corresponding to the A2 cell reference parameter. If Excel can resolve the 'A2' reference it will pass in an XLOPER with that value as xltypeNum, xltypeInt or xltypeStr depending on the the contents of A2. If not you may get an xltypeSRef.
xlfEvaluate: here's the MS doc https://msdn.microsoft.com/en-us/library/office/bb687913(v=office.15).aspx
Note that MS specify that the string passed to xlfEvaluate must 'contain only functions, not command equivalents'. I suspect L"\003Tst" doesn't correspond to any function known to your Excel. There's no built in function called Tst in my Excel 2013. It's possible you have an addin that supplies a function called Tst, but I'm guessing not. So try changing xlInput3.val.str to L"\006RAND()" and see what happens.

VBA: Use a variable as a reference to a user defined type

How can I reference a user defined type using a local variable without creating a copy of the type instance?
As an example, in the code below what I would ideally like to do is in MySub3 where I create a local variable, MT, and reference a data structure nested inside another struct ... but VBA doesn't allow this. It allows it for objects but not for user defined types (arrggg!) ... and for no apparent reason ... it just doesn't allow it.
MySub1 shows how to reference the nested struct in a long clunky way.
MySub2 shows how to do this by passing in the nested struct, but this clutters up the calling routine, and having multiple such nested structs gets ugly.
MySub2 demonstrates that VBA can do what I want, it just doesn't seem to provide a way to do it. I'm hoping there is a method I just haven't stumbled upon.
Note that my actual code is MUCH more complicated than this example, with multiple independent structs providing indices to many arrays as struct elements. Using these local reference variables would make the code much more readable and manageable.
Also Note that I am aware of the "with" statement, and it does help, but can only be used on one struct at a time.
Also Note that I am aware that I could use an actual object class. My code started out using an object but I quickly found out that VBA places limitations on arrays as property members ... a limitation that user defined types don't have.
Type tMyType
VariableA As Single
End Type
Type tMyOtherType
MyTypeArray() As tMyType
End Type
Type tOneMoreType
MyOtherType As tMyOtherType
End Type
Dim GlobalIndex As Integer
Sub TopLevel()
Dim TopLevelType As tOneMoreType
ReDim TopLevelType.MyOtherType.MyTypeArray(0 To 10)
Call MySub1(TopLevelType)
Call MySub2(TopLevelType.MyOtherType.MyTypeArray(GlobalIndex))
Call MySub3(TopLevelType)
End Sub
Sub MySub1(OMT As tOneMoreType)
Dim VarA As Single
VarA = OMT.MyOtherType.MyTypeArray(GlobalIndex).VariableA
End Sub
Sub MySub2(MT As tMyType)
Dim VarA As Single
VarA = MT.VariableA
End Sub
Sub MySub3(OMT As tOneMoreType)
Dim VarA As Single
Dim MT
Set MT = OMT.MyOtherType.MyTypeArray(GlobalIndex)
VarA = MT.VariableA
End Sub
From my point of view you have made it vary complicated. But I believe you have the reason for that.
The example you submitted generate the error you mentioned. But, when I changed some lines there is no error. I am not sure if my suggestion is the result you expected (while the question isn't fully clear to me) but try this instead of your MySub3:
Sub MySub3(OMT As tOneMoreType)
Dim VarA As Single
Dim MT
MT = OMT.MyOtherType.MyTypeArray(GlobalIndex).VariableA
VarA = MT
End Sub
Generally, this way I'm able to read any element im MySub3 passed from TopLevel.
If it is not the answer please clarify more.
I think here you have hit one of the limitations of VBA. I know of no way round the limitation on partial dereferencing of nested user types.
I think you would be best using classes containing private arrays with getter and setter functions (sadly, VBA doesn't have operator overloading either).

Nested structures in VBA

How can I create a nested structure in VBA? When I try the following code in VB Editor it is saying "Statement invalid inside type block".
Type Functiondetails
Function As String
Sites(7) As String
Values(7) As Integer
End Type
Type Financialdetails
Metrics As String
Dim f(10) As Functiondetials
End Type
Remove the Dim from the 2nd Type (and spell Functiondetails correctly!);
Type Financialdetails
Metrics As String
f(10) As Functiondetails
End Type
You should be ok but it also may not like Function as an identifier.

Resources