Meaning of `:=` Syntax in VBA methods - vba

When writing the following VBA, what is the root cause of the error "Expected =" given that we are using the Format:=2.
Workbook.Open (filename, Format:=2)
I understand that this format works when setting the variable as in the following code, but why does it work here and not in the above format?
Set wrkb = Workbook.Open (filename, Format:=2)
Also what is this operator called, := and how is it used?

It's not an operator, it's a named argument.
You can chose the order of the arguments/parameters by directly specifying what they are.
The concept of named arguments also exists in multiple modern languages, such as c# (its optional, just like in VBA) and swift (by default it's required, but you can disable it).
Named arguments also allow you to omit arguments that are optional altogether, but pass an argument that is further back in the list of arguments. A good way to try named arguments out is the messagebox, since it has many optional arguments with default values.
Example: MsgBox only specifying title:
MsgBox Title:="wew lad"
Or, in the more modern way of writing vb(a) code:
Call MsgBox(Title:="wew lad")
The MsgBox is a good example, since you can call it normally, and then specify a parameter further back directly (works with other methods too):
Call MsgBox("Yes!", Title:="wew lad")
Once there are named parameters, you can't add ordered parameters after:
'Call MsgBox (Prompt:="Yes!", vbOkCancel Title:="wew lad")
'this doesnt work!
Now, why does this raise an error:
MsgBox ("Hello", Title:="test")
This is some of the weirder parts of vba. Calling functions with return values but ignoring the value while using parentheses is a bit broken.
You can circumvent this by adding a Call in front of it (vba then knows it can ignore the result).
This would look like this:
Call MsgBox ("Hello", Title:="test")
Honestly, I don't know why this is, but I've had it cause really weird bugs. When you use the parentheses syntax, I can really recommend using the Call keyword before the method call.
As Macro Man mentioned, you can also omit the parentheses, instead of using Call:
MsgBox "Hello", Title:="test"
And it will work, too. This was the original vba coding style and isn't really used anymore.

Related

Format - Expected Array

I keep getting an error when I try to format this number. I've done it in VBA before and I tried to change the SOPID to a variant, a string, and an integer.
Dim SOPID As Integer
SOPID = DMax("file_id", "tblSOP") + 1
'Output test data
MsgBox (format(SOPID, "000"))
I have no idea what I am doing wrong.
Assuming the code was pasted directly from your IDE, the casing of format is suspicious; that would be Format, unless there's a format variable or function that's in-scope, ...and that's being invoked here.
Look for a public (could be implicitly Public, or if it's in the same module then it could also be Private) format function procedure that expects an array argument: that's very likely what's being invoked here.
Rubberduck (free, open-source; I manage this project) can help you easily identify exactly what's being invoked and an inspection would tell you about any shadowed declarations, but to be sure you can fully-qualify the function call to avoid inadvertently invoking another function that's in scope:
MsgBox VBA.Strings.Format$(SOPID, "000")
Note that there are no parentheses around the argument list of a parameterized procedure call in VBA; the parentheses you have there are surrounding the first argument and making the expression be evaluated as a value that is then passed to the invoked function: this isn't necessary.
Also note the $: the VBA.Strings.Format function takes and returns a Variant; VBA.Strings.Format$ takes and returns a String. If you aren't dealing with any Null values (an Integer couldn't be Null), consider using the String-returning alias.

Assign Bang argument programmatically?

I'm not successful in using the Bang (!) operator without it's argument being hardcoded, i.e., Me.VBProject.References!Excel. In this example, the Excel reference is hardcoded.
Out of frustration I've tried all permutations I can think of in an attempt to utilize it:
[Me.VBProject.References!(str)]
[Me.VBProject.References! & (str)]
["Me.VBProject.References!" & str]
["Me.VBProject.References!" & (str)]
and many more with parens added to ensure proper pre-evaluation including the longhand Application.evaluate method. Nada!
No, you can't do that.
The bang operator is just a shortcut for calling the default member of the object, and passing the text after the bang as a string to the first parameter of the default member:
The bang notation:
Me.VBProject.References!Excel
is exactly equivalent to:
Me.VBProject.References.Item("Excel")
and, because it is the default member, you can omit the Item function call:
Me.VBProject.References("Excel")
So, to use your (really badly named) variable str:
str = "Excel"
Debug.Print Me.VBProject.References.Item(str).Name
This is an X-Y problem.
Bang notation is a means to an end.
It's one of the tools made available to you, to retrieve an item from a collection.
Nothing more, nothing less.
What you want isn't to use the bang operator with a variable.
What you want is to retrieve an item from a collection using a variable.
Collection types have a default member, typically named Item. Default members can be specified explicitly, or implicitly accessed:
Dim foo As New Collection
foo.Add Item:=42, Key:="test"
Debug.Print foo.Item("test") 'explicit reference to the default member
Debug.Print foo("test") 'implicit reference to the same default member
The bang operator is just another way to make an implicit call to the collection's default member:
Debug.Print foo!test
All 3 Debug.Print statements above will call the foo.Item default member to output the 42 associated with the key "test".
Square brackets
As you can see, what comes immediately after the ! operator is really a string literal. Because a string literal can contain whitespace, the VB6/VBA parser needed a way to support them.
That's what the [square brackets] are for:
foo.Add 72, "spaces in the key"
Debug.Print foo![spaces in the key]
When they're not delimiting a string literal for bang notation, square brackets are usually1 interpreted as a run-time expression for the host application to evaluate. For example this is legal (though questionably advisable) in Excel VBA:
Debug.Print [A1]
The VBA parser identifies a bracketed expression and defers its evaluation to the host application - here Excel; at run-time, the instruction ultimately equates to:
Debug.Print ActiveSheet.Range("A1").Value
If you don't believe the evaluation of a bracketed expression is deferred to the host application, consider what needs to happen for this instruction to print 4:
Debug.Print [Sum(2,2)]
Therefore, every single one of the attempts/permutations in your post missed the mark and made Excel try to evaluate an expression that only VBA would be able to resolve, because Me.VBProject.References means absolutely nothing to Excel.
Square-bracket expressions should usually be avoided, because you lose compile-time checks and any error can only be caught at run-time.
1Usually, because they can also be used in some identifiers,
for example in Enum types, to make a [_hidden] enum member.
Bottom Line
Bang notation is a way to retrieve an item from a collection by leveraging default members and making string literals look like an identifier. You can't make it work without "hard-coding" the string literal, because it requires a string literal.
If you want to parameterize the collection retrieval, you can't use the bang operator.
It's useful for typing the code faster. If you don't know exactly how it works and what it does for you though, it's a double-edged blade that hides what's really going on and ultimately makes the code harder to read and understand. Code shouldn't be written just to be run. Code should be written to be read and understood.
Note: Bang notation isn't actually only for collections. It actually passes its argument as a string literal to the first parameter of anything that has a default member. I would strongly advise avoiding it for anything other than a collection class though (e.g. Collection.Item, Workbooks.Item, Worksheets.Item, Recordset.Fields, etc.), for the sake of future maintainers' sanity.

What's the conventional way to indicate an error inside defmacro?

What is the conventional, well-behaved way for a macro to indicate that it's been passed invalid arguments?
(defmacro defthisthing [name & definitions] . . .)
I'm writing a macro right now to accept a whole bunch of definitions. If the same thing gets defined twice, the macro should complain. Similarly, if one of the definitions uses a term that's not defined elsewhere in the same macro invocation, the macro should complain, hopefully with line and column numbers so the programmer can see exactly where the error is.
I'm currently thinking that throwing an exception makes the most sense, because invalid macro arguments are in fact a compilation error. Everything should shut down no differently than if the compiler found unbalanced parentheses.
If that's correct, what is the conventional exception to throw? And how do you include the filename and line number of the offending snippet of code?
And if that's not correct, what is the more Clojurely approach?
Throwing exceptions sounds fine. I just checked the Clojure source and this is how it is done there:
(defmacro let ...) calls (defmacro assert-args ...), which throws exceptions if the arguments don’t meet their requirements.

Moving messages with VBA Outlook

What is the difference between these two lines?
Set MyMsg = MyMsg.Move(MyFolder2)
MyMsg.Move(MyFolder2)
The first one works just fine.
The second one usually gives an "Outlook is not responding" error.
The MailItem.Move method returns the MailItem that has been moved. Usually, properties return values and methods don't return anything. But for several methods, the designers decided it would be handy to have a return value, so they made them return a value (or object).
When you assign a method to a variable, any arguments must be in parentheses or you'll get a syntax error. If you call a method without assigning it to a variable (because you don't care what the method returns or it's one of the methods that doesn't return a value), then the arguments must not be in parentheses (kind of).
Parentheses, when used in places that the compiler does not require them, are the equivalent of saying "evaluate this before doing anything else". It's like how you use parentheses in order of operations so you can say "evaluate this addition operation before you do this multiplication even though that's not the normal order".
The (kind of) remark above is because most of the time when you "incorrectly" put parentheses around something, it doesn't matter.
Application.CreateItem 0
and
Application.CreateItem (0)
are the same. The second one evaluates the argument before it passes it to CreateItem, but evaluating a single integer takes no time and has no ill effects. The parentheses aren't necessary because we're not assigning the results to a variable, but they're not really hurting anything either.
In your second example, you're telling the compiler to evaluate the folder, then send it to the Move method. I don't know what evaluating a folder means, but I gather it's not good. It probably does something like create an array of all the objects in that folder, or something equally intensive. When Outlook is not responding, it means you gave it such a big job that it hasn't checked back in with the operating system in a timely fashion.
So: Use parentheses for arguments when it's on the right side of an equal sign. Don't use them when it's not. There are a few exceptions to that rule, but you may never need to know them.
There is no difference between the two (you just ignore the function result) unless you actually use the MyMsg variable afterwards - after the message is moved, you cannot access it anymore.
Use the first version.

In VB6, how do I call a COM object requiring a pointer to an object?

I'm having trouble with a .NET Assembly that is com visible, and calling certain methods from VB6.
What I have found is that if the parameters are well defined types, (e.g. string), calls work fine. If they are higher level objects, it raises a runtime error '438' suggesting that the property or method is not present. I suspect that this is a question of having the correct signature on the call, but I can't see how to do this correctly.
I believe that I've done everything correct on the .NET side (ComVisible, public interfaces, etc. and even have it down to a simple enough case).
Looking at the output from the typelib viewer, I have the following:
dispinterface ISimple {
properties:
methods:
[id(0x60020000)]
void Add([in] ISimpleMember* member);
[id(0x60020001)]
ISimpleMember* Create();
};
OK. So I have 2 methods in my ISimple interface. One takes an ISimpleMember (Add), whilst the other, returns an ISimpleMember.
The corresponding code in VB looks like this:
Dim item As ISimpleMember
Dim simple As simple
Set item = New SimpleMember
item.S1 = "Hello"
item.S2 = "World"
Set simple = New simple
simple.Add (item) <---- This raised the run time error 438
Set item = simple.Create <---- This works fine, returning me an ISimpleMember
I've tried a couple of things:
1. Dim item as SimpleMember (makes no difference)
2. simple.Add(ObjPtr(item)) - Syntax error
3. simple.Add(ByRef item) - Syntax error
Basically, The run time error is the same as if I had
simple.AMethodThatIHaventWritten()
Also, If I browse References in the VB6 Environment, The Add method is well defined:
Sub Add(member As SimpleMember)
I've found the answer I believe. It was very simple:
When calling a SubRoutine, I shouldn't put the name in braces. the call should have been:
simple.add member
rather than
simple.add(member)
If I change it to a function (i.e. return a value rather than void) the braces are necessary
This seems to work
(Probably) The top 3 VB6 coding mistakes made by devs who now mainly code in C#, Javascript etc. Are:-
Placing ; at the end of lines. Its a syntax error very easily spotted and picked up the compiler.
Not placing Then on the other side of an If condition expression. Again its a syntax error.
Calling a method without retrieving a value and yet using ( ) to enclose the parameter list. With multiple parameters this is a syntax error and easily found. With only one parameter the use of ( ) is interpreted as an expression. Its the result of the ( ) expression which is passed as parameter. This causes problems when ByRef is expected by the callee.