Using brackets when creating object in vba excel? - vba

I have copied code from a vba project that I found to go from xml to excel but it gives me an error in my vba project, I have checked the reference libraries.
ruta = LCase(CreateObject([explorador]).BrowseForFolder(0, "selecciona la carpeta a procesar", 0, "").items.Item.Path)
I made the following change and it worked
ruta = LCase(CreateObject("shell.application").BrowseForFolder(0, "selecciona la carpeta a procesar", 0, "").Items.Item.Path)
but then it came back to this line
With CreateObject([openFile])
I get the error 13 that the execution times do not match. Variables do not match
I check the variables and they are correctly: unsure:
I don't understand why with the original file it runs smoothly and the replica doesn't. It has been very little I have found it with respect to the syntax of those lines of code when writing it [explorer] and [openFile]

Square brackets in VBA are used for what the language specification calls "foreign identifiers"; they're how you can explicitly invoke a Range object's default member, which is a hidden member named _Default - since VBA identifiers cannot begin with an underscore, doing MsgBox SomeRange._Default would be illegal. So we do MsgBox SomeRange.[_Default] instead and now the code can compile and run. Same with the SomeCollection.[_NewEnum] hidden member, when creating custom collection classes.
When the bracketed identifier doesn't contain any illegal-in-an-identifier characters, then they are purely redundant.
Various VBA hosts also implement some form of Evaluate mechanism; when hosted in Excel, you can do this MsgBox [A1] and you'll get the value of cell A1 from the active worksheet - in this context [A1] is an expression VBA takes and passes to the host application (here Excel), which evaluates it and returns a result to VBA - in this case a Range object reference.
So what CreateObject([explorador]) really does, is really this:
CreateObject(ActiveSheet.Range("explorador").Value)
Except Excel doesn't understand what explorador is referring to (is the sheet supposed to define a explorador named range? I can't imagine why you'd want to do that though), and comes back with what would show up as a #NAME? error on a worksheet, wrapped up in a Variant/Error value:
CreateObject(CVErr(XlErrName)) 'type mismatch error every time!
Lose the square brackets, you'll lose the headache with them!
Assuming explorador and openfile are String variables containing a valid/registered ProgID, CreateObject(explorador) should work as intended.
Consider using either string literals or declared constants with CreateObject: having a layer of indirection here is obscuring exactly what type of COM object is being created. With CreateObject("Scripting.FileSystemObject") can only fail if the Scripting.FileSystemObject isn't in the registry.

Related

Extractng Variable Names from VBA Scripts

I want to Get List of Variables used in the Script i.e. VariableName13, strDLink, strZone.
A single file contains about 150+ events and each project contains about 700-900 files.
In VBA environment, I want to parse through each file, loop through each event and extract the Variable names declared or referenced by the Events.
I did find some material like Roslyn or TypeLib but unable to understand how to use them?
Can someone please share a proper approach to extract the variables?
Environment: VBA 6, SCADA HMI
Private Sub Rect13_Click()
Dim lResult As Long
Dim strDLink As String
Dim strZone As String
On Error GoTo ErrorHandler
lResult = OpenFuncUpdate
If lResult = SomeValue Then
'DoThis
ElseIf lResult = SomeOtherValue Then
strDLink = "FullPathLink"
FuncDisassemblePath strDLink, , , , , , , , , , , , strZone
If Len(strZone) > 0 And (InStr(VariableName13.CurrentValue, "%") = 0) Then
SubLoadIA strZone & "%" & VariableName13.CurrentValue, Me
Else
SubLoadIA VariableName13.CurrentValue, Me
End If
End If
Exit Sub
ErrorHandler:
SubHandleError
End Sub
Depending on how you define what a "variable" is, you can try to parse VBA code with VBA code and regular expressions.
If all your declarations are consistently made, and consistently declare a single variable, and variables are consistently declared (Option Explicit is in every module), then capturing Dim|Private|Public|Friend|Global {identifier} should be good-enough... but that makes a lot of "ifs"
Real-life projects have Dim statements that can declare a list of local or private variables. Or there's a ReDim statement somewhere that's actually declaring an array on-the-spot. Or they don't always specify Option Explicit and variables aren't always even declared at all. Or there's a line continuation in the middle of the statement that breaks the regular expression. Or, or, or... so many things can go wrong, parsing VBA code is a rabbit hole.
For example suppose you need to pick up and list undeclared variables. A regular expression can't tell its usage from a procedure or function call, because they're syntactically identical. Regular expressions are missing the context of the grammar - and it's by tokenizing (aka "lexing") the source code and then parsing the tokens using parser rules that we can be 100% certain of what we're looking at.
Fortunately this is a solved problem, and there's free, open-source VBIDE tooling available for this, and get you 100% correct results every time without writing a single line of code or worrying about what legal declarations might be left unaccounted for.
Rubberduck (I manage this OSS project and own its website) will correctly parse any legal VB6/vBA code (and if it doesn't, we're extremely interested in a repro!), and then you can simply click a "copy" button to instantly have every single declaration in the clipboard:
Ctrl+V /paste onto a worksheet (or a Word document, or in Notepad!) and then you can easily turn it into a filter-enabled table; in your case you'd want to filter the [Declaration Type] for "Variable":
Above, the exported declarations for a VBA project that has a Sheet1 module with a test procedure that uses (but doesn't declare) a variable named undeclared:
Sub test()
undeclared = 42
Debug.Print undeclared
End Sub
Here's the same table for the code you've provided:
Note that SubHandleError and other Sub and Function calls would parse as and resolve to a procedure/function in your project. Here they're being picked up as undeclared variables because I didn't parse anything other than the code you supplied, so these identifiers are undefined.

32 VB appl automating with 32 bit MS Office

When running a 32 bit VB application on Win 7 64 bit with 32 bit office, everything works fine. When running with 64 bit Office (specifically Word), most of the code works fine, but the .Paste method of the Word.selection class fails with 'This command is not available'. The user can use ctrl-V at that point to paste the contents. How can the VB code be changed to support both 32 and 64 bit Word?
oWorkDoc = oWord.Documents.Open(filename, , True, False)
oWord.Selection.Copy()
cell.Select() <--- refers to cell in table in another document
Try
oWord.Selection.Paste() <--- failing statement
Catch ex As Exception
oWord.Visible = True
MsgBox(ex.Message)
End Try
Update: the user is NOT using Office 64 bit. He is using 32 bit. So the mystery thickens. Also, the code sequence is used in other portions of the application, and work fine. Also the Try/Catch block has been changed to just ignore the exception and the paste action is occurring.
... selections that include table cells can also lead to
unpredictable behavior. The Information property will tell you if a
selection is inside a table.
Because Range objects share many of the same methods and properties as
Selection objects, using Range objects is preferable for manipulating
a document when there isn't a reason to physically change the current
selection.
Source: Selection interface - Remarks
I advise avoiding use of the Selection object as much as possible. Instead use a typed reference of the appropriate type; in this case a Word.Range. Copy and Paste are methods on the Word.Range object and should be available on a valid reference. Assuming that cell is a Word.Cell object reference, use its Range property.
So instead of:
cell.Select() <--- refers to cell in table in another document
oWord.Selection.Paste() <--- failing statement
use:
cell.Range.Paste

Why must Set be used to hold objects?

While working on my PowerPoint macro, I noticed the following:
To obtain the current, active slide:
Dim currSlide As Slide
Set currSlide = Application.ActiveWindow.View.Slide
To obtain a newly created Textbox:
Dim textbox As Shape
Set textbox = currSlide.Shapes.AddTextbox(...)
I'm new to VBA, having worked with Java, C++ & C#. Why must Set be used above? Why does using Slide & Shape instead generate errors? In what way is VBA different in how variable declaration work in this respect?
This is taken from Byte Comb - Values and References in VBA
In VBA, the difference between value types and reference types is made explicit by requiring the keyword Set when assigning a reference. In addition, you will often see assigning a reference referred to as “binding”.
Byte Comb goes into great detail about the inter working of the VBA. I highly recommend that any serious about programming using the VBA read through it.
In layman's terms: Set is used so that the compiler knows that the programming wants to have a variable reference an Object type. In this way, the compiler will know to throw an error when you are trying to assign a value to an object.
Another reason is that objects can have default values.
In this example I have a variable named value that is of a Variant type. Variants can be of any type in the VBA. Notice how the compiler knows that to assign the value of the Range when value = Range("A1") and to set a reference to the Range when the keyword Set is used in Set value = Range("A1").

VBA error in saving file using Format (date) function

I'm trying to save an activeworkbook but when I use the following code, I keep getting the error "compile error: expected function or variable" with the word "format" highlighted.
It boggles my mind because I used the exact same function and format in another macro and it saved the file perfectly. I also made sure they had the same types of variables defined already...
Here's the one line code
ActiveWorkbook.SaveAs Filename:=SavedPath & format(Date, "mmddyyyy") & " 4512 GLUpload.xlsm"
The variable savedpath is fine because when I run this line without the format part, it saves the file, but not sure why this screw it up. Also noticed in my other code, format is capitalized but it's not here.
The compiler error you are getting indicates that VBA is expecting an assignable value (either a literal, a variable, or the return value of a function). This means that one of the identifiers in the statement to the right of the equals sign doesn't fall into those categories. So, either SavedPath is defined somewhere as Sub SavedPath(), or there is a Sub Format(arg1, arg2) defined somewhere (if it had a different number of arguments you would get a "Wrong number of arguments or invalid property assignment" error). The second clue (in the comments) is that changing format to the strongly typed Format$ gave a "Type-declaration character does not match declared data type" error. This points to the compiler not treating the symbol format as a function call (Format$() is the strongly typed version of Format()). The solution is to track down the errant use of the VBA function name and re-name it.
A perfect example of why avoiding VBA keywords and function names is good practice.

Returning the Default Property

I fill A1 and A2 as follows:
I then run:
Sub WhatIsGoingOn()
Dim r As Range, sh As Worksheet
Set r = Range(Cells(1, 1))
Set sh = Sheets(Cells(2, 1))
End Sub
I expected that in both cases, VBA would use the default property of Cells (the Value) property to Set each variable. However I get a runtime error 13 on the last line of code!
In order to avoid errors, I must use:
Sub WhatIsGoingOn2()
Dim r As Range, sh As Worksheet
Set r = Range(Cells(1, 1))
Set sh = Sheets(Cells(2, 1).Value)
End Sub
What is going on here ??
The difference is in how the input to their default properties is handled by the implementation of the Range and Sheets objects.
The default property of both the Range and the Sheets object takes a parameter of type Variant. You can pass anything to it, so no type coercion will be necessary. In your first example you pass a Range object to both.
How the default properties handle the input is up to themselves. Apparently the property of the Range tries to retrieve the default value of the passed parameter, in your example an address as String. The Sheets object doesn't seem to be so forgiving and raises an error because you neither passed a number nor a String.
Inconsistency is one of the strengths of VBA...
Btw., passing CStr(Cells(2, 1)) would also work, because you explicitly cast to String before passing as a parameter.
Perhaps Leviathan's comment that "Inconsistency is one of the strengths of VBA..." may ring true, but there are some contextual details that his answer neglects (and is technically incorrect on some subtle points). He is correct that for the given code that all of the parameters are variants, but the statement that "no type coercion will be necessary" can be misleading and perhaps just wrong in many cases. Even if many objects and methods are programmed to handle multiple types and default values, this very question reveals that it is a mistake to avoid purposely ensuring (or coercing) the correct data type. Avoiding default properties altogether (by always typing out the full reference) can avoid many headaches.
A significant difference between the lines of code for this particular question is this: Range is a property that takes parameters, while Sheets is also a property but has no parameters. Range and Sheets are NOT objects in this context, even though they are properties which do return Range and Sheets objects, respectively. They are properties of an (automatic global) object defined for the particular module or Excel workbook instance. This detail is not trivial for understanding what the code is actually doing.
The Obect Browser in the VBA window reveals the following metadata for the two properties:
Property Range(Cell1, [Cell2]) As Range
Property Sheets As Sheets
For Range(Cells(1, 1)), the argument Cells(1,1) is passed to the parameter Cell1. Cells itself is a property of the Excel.Global hidden object and it returns a Range object, so that Cells(rowindex, colindex) is calling a hidden default property of the Range class equivalent to Cells._Default(rowindex, colindex). The return type of the property _Default() is not declared, so technically it could return any type in a variant, but inspection shows that it returns a Range object. Apparently passing a Range object to its default property will attempt to take the default value and if it is a valid range value, like a string with a range expression, then it will execute without error.
The default property for the Sheets class is the parameterized hidden _Default(Index) method. Thus, Sheets(Cells(2, 1)) is equivalent to Sheets._Default(Cells(2, 1)). More importantly, it means that Sheets._Default(Cells(2, 1)) is passing a Range object as an index value, but documentation says that it expects an integer or string value. We already mentioned that the index parameter is variant... and when passing an object to a variant, it always passes the actual object and never its default property. So we know that Sheets.Item obtains a Range object in that call. Here is were Levithan was correct in that Sheets.Item can decide what to do with it. It is likely that it could have been smart enough to get the single string value and continue without error. Other collection objects (with a default Item(index) property) in MS Office objects do not seem to exhibit this same "pickiness", so it appears that Sheets._Default() (and perhaps Sheets.Item()) is being rather strict on validating its arguments. But this is just a particular design issue with this method only... not necessarily an overall issue with VBA.
What can be difficult is determining exactly what the source objects of the properties are. Inside the ThisWorkbook module, Me.Sheets reveals that Sheets is a property of the particular Workbook for the module. But Me.Range is not valid in the Workbook module, but right-clicking on the Range property (without the Me qualifier) and choosing "Definition" results in the message "Cannot jump to Range because it is hidden". However, once in the Object Browser, righ-clicking within the browser one can choose "Show Hidden Members" which will then allow navigating to the hidden Global objects and other hidden members.
Why the inconsistency and the hidden properties? In an attempt to make the current instance of Excel and all of its components accessible in a "natural" way, Excel (and all Office applications) implement these various hidden properties to avoid the "complexity" of having to repeatedly discover and type out full references. For instance, the automatic global Application object also has both Range and Sheets properties. Actual documentation for Application.Sheets, for example, says "Using this property without an object qualifier is equivalent to using ActiveWorkbook.Sheets". Even that documentation fails to say is that ActiveWorkbook is in turn a property of the global Excel Application object'.