Assign Bang argument programmatically? - vba

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.

Related

String template to set the default value of PARAMETER

Is it possible, in ABAP, to evaluate string templates dynamically?
Normally, you will have some string template in code that will be checked by the compiler. (The variables in the curly brackets are checked by the compiler at compile time).
However, is it possible to have a string evaluated at runtime?
So, instead of:
data(val) = |System ID: { sy-sysid }|.
I would like the string to be interpolated to come from elsewhere, for example:
parameter: p_file type string lower case default '/mnt/{ sy-sysid }/file.txt'.
In this case, I would like to have the value of p_file to be evaluated at runtime to substitute the variable (sy-sysid) with the runtime value.
You could, of course, program your own substitution by finding all occurrences of variables with curly brackets with a regex expression, then evaluate the variable values with ASSIGN and substitute them back into the string, but I am looking for a built-in way to do this.
Sorry, this is maybe a stupid example, but hopefully you understand what I mean. (If not, please let me know in the comments and I will try and clarify).
The problem in your snippet is not with string template but with PARAMETER behavior. It does not allow dynamics in DEFAULT clause.
To achieve what you want you should use INITIALIZATION and set path value in runtime:
parameter: p_file type string lower case.
INITIALIZATION.
p_file = | /mnt/{ sy-sysid }/file.txt |.
Unfortunately, the example you gave, does not make any sense to me. ABAP String templates are evaluated at run-time and type-checked at compile-time.
In your example, it is always the run-time value of SY-SYSID that will be written to the variable.
I guess what you want to do is circumvent compile-time checks for expressions inside a string template.
Please try to give us your actual use case, so maybe we find an even better solution to your problem.
However, here is what I think could help you:
Personally, I do not recommend to write code like the one below, because it is extremely error-prone likely to mislead other programmers and because there is very likely a better solution.
Given that you know the name of a variable at run-time, try this:
".. say LV_VARNAME is a charlike variable that contains a
" variable name at runtime.
"NOTE that the variable LV_VARNAME must be visible in the scope of the
"following code.
FIELD-SYMBOLS: <my_var> TYPE any.
ASSIGN (lv_varname) TO <my_var>.
DATA(lv_str) = |The value is { <my_var> }|.

Meaning of `:=` Syntax in VBA methods

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.

Difference between "!" and "." operator

What is the exact difference between the vba operators ! and . ?
I have noticed that often they can be used interchangable, but sometimes one works and one doesn't (Forms-Collection in Access e.g.)
Aswell, when you accidentially write !optSomething (some optionbuttion) instead of Not optSomething withing a with clause, it will not break at compile but at runtime, because ! is used - my company had some broken vba after On Error Resume Next with this, and i only realised when having "brake on every error" activated.
The ! character invisibly calls the default member for that object.
For a Recordset, the default member is the Fields method, and the default member of a field object is the Value property, so these lines are identical:
sEmp = rst.Fields("emp_id")
sEmp = rst.Fields("emp_id").Value
sEmp = rst!emp_id
So, the reason you'll see it work for some objects and not others, is probably because those objects don't have the default member that you expect.

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.

Exclamation Marks in a Query SQL

I'm reading over this query, and I came upon a line where I don't understand heres the line
[FETT List]![FETT Search]
FETT List is a table
FETT Search is a column in FETT List
Can someone explain what the exclamation mark means?
Thanks
Well, you learn something new every day!
I had originally planned to explain that if you'd said the reference was [Forms]![FETT List]![FETT Search], then it would be easy to explain, as a reference to the [FETT Search] control on the [FETT List] form. But without a parent collection (either Reports of Forms), it doesn't look like a valid reference in any context within a SQL statement.
But then I thought to test it, and discovered (to my surprise) that this SQL statement is treated as valid in an Access form:
SELECT [tblCustomer]![LastName] AS LastName
FROM tblCustomer;
In Access, that is 100% equivalent to this SQL statement:
SELECT tblCustomer.LastName
FROM tblCustomer;
…so I don't understand why anyone would write it, except if they forgot the context (or never understood it in the first place). It could be a case of aliasing gone wrong, but it's not what I consider good form.
Now, the long answer to the general question of ! (bang) vs. . (dot):
In general, in Access, the bang operator delineates the default collection of an object and its items. The dot operator delineates an object and its methods, properties and members.
That is for Access, and applies to Access objects and the object model for Access.
But you also use SQL in Access, and so you also have TableName.FieldName in SQL, where the dot operator separates an item in a default collection. TableName.FieldName could be considered to be short for TableName.Fields("FieldName"), as you find with Forms!MyForm!MyControl being equivalent to Forms!MyForm.Controls("MyControl"). But this rule doesn't apply in SQL -- TableName.Fields("FieldName") is not valid SQL, only TableName.FieldName is.
So, you have to keep straight which paradigm is controlling the namespace you're working in, i.e., whether it's an Access namespace or a SQL namespace.
Forms!MyForm is also equivalent to Forms.Item("MyForm"), so the ultra-long form would be Forms.Items("MyForm").Controls("MyControl"). Note how the bang operator is a shortcut for the longer form version with the dot operator, so the bang operator is quite frequently used in preference to the dot operator. Note also that the longer form ends up being used when you need to refer to an item whose name is stored in a variable, which is not possible with the bang operator:
Dim strForm As String
strForm = "MyForm"
' This is OK
Debug.Print Forms(strForm).Controls.Count
' This is not
Debug.Print Forms!strForm.Controls.Count
Also, in VBA code, Microsoft has engineered things to obfuscate this distinction in Forms and Reports, where it used to be that Me!MyFavoriteControl was legal as a control reference, and Me.MyFavoriteControl would have been legal only as a reference to a custom property (or module-level variable, which would be member of the object). You could also unwisely name a function or sub "MyFavoriteControl" and it could be referred to with the dot operator.
But with the introduction of VBA, MS introduced implicitly-created (and maintained) hidden property wrappers around all controls so that you could use the dot operator. This had one huge advantage, and that is compile-time checking of control references. That is, if you type Me.MyFavoriteControl and there is no control by that name and no other member of any kind with that name within the form/report's namespace, then you would get a compile-time error (indeed, you'd be informed of the error as soon as you left the line of code where you made the error). So, if you had this code:
Debug.Print Me.Control1
... and you renamed Control1 to be MyControl, you'd get an error the next time you compiled the code.
What could be the downside of compile-time checking? Well, several things:
code becomes harder for the programmer to understand on sight. In the past, Me!Reference meant an item in the default collection of a form/report (which is a union of the Fields and Controls collections). But Me.Reference could be a control or a field or a custom property or a public module-level variable or a public sub/function or, or, or... So, it sacrifices immediate code comprehensibility.
you are depending on implicit behavior of VBA and its compilation. While this is usually an OK thing to do (particularly if you take good care of your code), VBA compilation is very complex and subject to corruption. Over the years, experienced developers have reported that using the dot operator makes code more subject to corruption, since it adds another layer of hidden code that can get out of synch with the parts of the the application that you can alter explicitly.
since you can't control those implicit property wrappers, when they go wrong, you have to recreate your module-bearing object from scratch (usually SaveAsText is sufficient to clear the corruption without losing anything).
So, many experienced developers (myself included) do not use the dot operator for controls on forms/reports.
It's not such a big sacrifice as some may think if you use a standard set of naming conventions. For instance, with bound controls on forms, a let them use the default names (i.e., the name of the field the control is bound to). If I don't refer to the control in code, I never change its name. But the first time I refer to it in code, I change its name so that the control name is distinct from the name of the field it is bound to (this disambiguation is crucial in certain contexts). So, a textbox called MyField becomes txtMyField at the time I decide to refer to it in code. The only time I'd ever change the field name after code is written is if I somehow decided that the field was misnamed. In that case, it's easy enough to do a Find/Replace.
Some argue that they can't give up the Intellisense, but it's not true that you entirely give it up when you use the bang operator. Yes, you give up the "really intelligent" Intellisense, i.e., the version that limits the Intellisense list to the methods/properties/members of the selected object, but I don't need it for that -- I need Intellisense to save keystrokes, and with Ctrl+SPACEBAR you get a full Intellisense list that autocompletes just like the context-specific Intellisense, and can then short-circuit the typing.
Another area of dot/bang confusion is with DAO recordsets in VBA code, in which you use the dot operator for the SQL that you use to open your recordset and the bang operator to refer to fields in the resulting recordset:
Dim rs As DAO.Recordset
Set rs = CurrentDB.OpenRecordset("SELECT MyTable.MyField FROM MyTable;")
rs.MoveFirst
Debug.Print rs!MyField
rs.Close
Set rs = Nothing
If you keep in mind which namespace you're working in, this is not so confusing -- the dot is used in the SQL statement and the bang in the DAO code.
So, to summarize:
in SQL, you use the dot operator for fields in tables.
in forms and reports, you use the bang operator for controls and the dot operator for properties/methods (though you can also use the dot operator, but it's not necessarily advisable).
in VBA code, references to controls on forms and reports may use either dot or bang, though the dot may be prone to possible code corruption.
in SQL, you may see the bang operator used, but only if there is a reference to a control on an Access form or report, of the form "Form!FormName!ControlName" or "Report!ReportName!ControlName".
in VBA code working with DAO recordsets, you may see both the dot and bang operator, the former in defining the SQL that is used to open the recordset, and the latter to refer to fields in the resulting recordset once it is open.
Is that complicated enough for you?
Generally you see this in MS Access code (for the exclamation mark, a period for SQL server). You can refer to a column by table.column or if you give the table an alias, then by alias.column. You might do this if you want to be specific when using joins, or you may have to do it when two (or more) tables in a query/join have the same column name in each table.
I think that the esclamation mark is only a conventional separator.
In Oracle PL/SQL you use dot:
[FETT List].[FETT Search]
Any other clues?!