VBA Parameter Error - vba

I have been fiddling with this code for a few hours now and I can't get rid of the compile error.
Set objWord = CreateObject("Word.Application")
''more code
objWord.Selection.TypeText (FDS.Cells(2, 8).Value)
objWord.Selection.HomeKey Unit:=wdLine, Extend:=wdMove (error occurs here)
ActiveDocument.Indexes.MarkEntry Range:=objWord.Selection.Range, Entry:=("Device")
objWord.Selection.EndKey Unit:=wdLine
''more code
(I left out a lot of code since the module is huge)
What I want the code to do in the end is look in a specific excel cell and take that value and place it in the currently open word document. Then that value has to be selected and given a marking and then going to the end of the line.
I have tried all solutions that I know of and changing Extend:=wdMove to Extend:=wdExtend but nothing seems to work.
The only way I can get rid of the error is removing Unit:=wdLine. But this also makes sure it doesn't select the Value anymore.
Im hopeless at this point. So I will take any help.
Thnx in advance.

Trying using 5 instead of wdLine and 0 in place of wdMove. – Darren Bartrup-Cook
If you don't have a reference to the Microsoft Word type library, wdLine and wdMove are undefined.
If Option Explicit isn't specified at the top of the module, then they become undeclared variables with a default type of Variant, and since they're passed as arguments to members that expect enum values, they'll be passed as 0 - which may or may not be a valid value for the invoked member, and may or may not work as intended.
If Option Explicit is specified at the top of the module, the code doesn't compile, and the VBE highlights wdLine and wdMove as undeclared identifiers.
Since more people are going to use this I'm sticking with the numerical ones. But I appreciate the help! – MateoVD
The best solution (assuming you're keeping the MS Word reference late-bound) is to maintain the readability of wdLine and wdMove values. Inlining their respective underlying integer value will work, but then you have 5 and 0 and no idea whatsoever about what these values mean: these are known as magic numbers, and they're a great source of hard-to-debug issues.
Declare the constants yourself. wdLine and wdMove are members of the WdUnits and WdMovementType enumerations, respectively.
You can define these enums yourself, at the top of any module - I'd recommend defining them in their own standard/procedural module (.bas), so that these values are accessible from anywhere in your code (they wouldn't be, if you defined them in e.g. a Worksheet class module):
'Module: WordConsts
'#Folder("Microsoft Word Constants")
Option Explicit
Public Enum WdUnits
wdCell = 12
wdCharacter = 1
wdCharacterFormatting = 13
wdColumn = 9
wdItem = 16
wdLine = 5
wdParagraph = 4
wdParagraphFormatting = 14
wdRow = 10
wdScreen = 7
wdSection = 8
wdSentence = 3
wdStory = 6
wdTable = 15
wdWindow = 11
wdWord = 2
End Enum
Public Enum WdMovementType
wdMove = 0
wdExtend = 1
End Enum
And now you can keep your code as-is, and use wdMove and wdLine and any other possible legal values, without having to dig them and their underlying values up online.
If you then add a reference to the Microsoft Word type library, the constants defined in your WordConsts module will take precedence over the same-name identifiers defined in the type library: this is called shadowing, or hiding - anything defined in your VBA project will always have higher precedence in identifier resolution.
TL;DR: Write code that says what it does, and does what it says. Magic numbers don't say anything - avoid them. If a number means something, give it a meaningful name. When a name exists for that value, use it.

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.

Using brackets when creating object in vba excel?

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.

Excel 2016 VBA says "ambiguous name" but function declaration unique (was working before upgrade)

I've got a VBA macro in an Excel 2016 workbook (Windows 10) which has existed and worked for years (it was written when I was still using Office 2010). I've since upgraded to Office 2016, but now the macro fails with the error: "Ambiguous name detected: bIgnore". This is the failing line of code
If Not bIgnore(strDesc) Then
The bIgnore function is declared only once across all of my modules, and looks like this:
Function bIgnore(pDesc As String)
'If the description is in the "Ignore List" sheet then return True
Dim nRow As Integer
Set wIgnore = Worksheets("Ignore List")
nRow = 1
While wIgnore.Range("A" & nRow).Value <> ""
If InStr(1, pDesc, wIgnore.Range("A" & nRow).Value) > 0 Then
bIgnore = True
Exit Function
End If
nRow = nRow + 1
Wend
bIgnore = False
End Function
Any idea why it is now being regarded as ambiguous by the compiler?
I'd try replacing all instances of "bIgnore" with "blIgnore" and see if it still conflicts or not.
If the error goes away then surely it is keyword conflict.
An ambiguous name is a compile error that always means VBA is seeing at least two identifiers with the same name, in the same scope.
Normally the compile error should just take you there, and highlight the problematic declaration.
Other than that, you can use the VBE's search (Ctrl+F) functionality and try to locate the faulty declaration, but the tool is rather basic/weak, and won't list all results - it iterates them instead... which can be painful.
Alternatively you could try to use Rubberduck's Find Symbol (Ctrl+T) command (assuming the code can be parsed... Rubberduck mostly assumes the code is compilable in the first place), and see every declaration by that name - and that's if the Code Explorer (Ctrl+R) doesn't already reveal the problem:
Disclaimer: I'm heavily involved with the development of the Rubberduck VBIDE add-in.
Another alternative, since Rubberduck uses a symbol table and scoping rules to resolve identifier references, would be to refactor/rename (Ctrl+Shift+R) the function to, say, IsIgnored, and then use Find Symbol again to locate the bIgnore declaration that remained: that should be your culprit.

Simplify VBA Code

I have a macro which reads and writes data from two sheets in the same workbook.
Is it possible to clean up and simplify the code/statements to improve readability and aid in debugging efforts?
The statements have become so long they are confusing to read even when using the space-underscore method to use more than a single line.
Example of a statement which has become unwieldy:
Range("mx_plan").Cells(WorksheetFunction.Match(sortedAircraft.Item(i).tailNumber, Range("aircraft")), WorksheetFunction.Match(currentWeekId, Range("week_id")) + weekly_hours_col_offset) = (acft_hoursDNE / acft_weeksRemaining)
I've intentionally tried to avoid making explicit references to individual cells or ranges.
Your statement is 225 characters!
Debugging it will be impossible, because it's one instruction doing way too many things, and you can only place a breakpoint on a line of code... so you can't break and inspect any of the intermediary values you're using.
Break it down:
tailNumber = sortedAircraft.Item(i).tailNumber
aircraft = someSheet.Range("aircraft").Value
planRow = WorksheetFunction.Match(tailNumber, aircraft)
weekId = someSheet.Range("week_id").Value
planColumn = WorksheetFunction.Match(currentWeekId, weekId)
Set target = someSheet.Range("mx_plan").Cells(planRow, planColumn + weekly_hours_col_offset)
target.Value = acft_hoursDNE / acft_weeksRemaining
Remember to declare (Dim) all variables you're using (use Option Explicit to make sure the code won't compile if you make a typo with a variable name), use meaningful names for all identifiers (names that tell the reader what they're for - use comments when the why isn't obvious from the code alone).
By breaking it down into multiple smaller steps, you're not only making it easier to read/maintain, you're also making it easier to debug, because a runtime error will be raised in a specific instruction on a specific line, and you'll be able to more easily pinpoint the faulty inputs.
Use With ... End With statements to localize any Range.Parent property.
Declare and Set a variable to the Excel Application object that can be used as a replacement for the WorksheetFunction object. This should make repeated calls to worksheet functions more readable.
Bring everything to the right of the equals sign down to the next line by supplying a _ (e.g. chr(95)). This acts like a concatenation character and allows single code lines to be spread over two or more lines. I've also use it to line up the two MATCH functions which return row and column to the Range.Cells property.
Dim app As Application
Set app = Application
With Worksheets("Sheet1").Range("mx_plan")
.Cells(app.Match(sortedAircraft.Item(i).tailNumber, Range("aircraft"), 0), _
app.Match(currentWeekId, Range("week_id"), 0) + weekly_hours_col_offset) = _
(acft_hoursDNE / acft_weeksRemaining)
End With
Set app = Nothing
That looks significantly more readable to my eye. Your use of named ranges may also be improved but it is hard to make suggestions without knowing the parent worksheets that each belongs to.
Note: I added a , 0 to each of the MATCH functions to force an exact match on unsorted data. I do not know if this was your intention but without them the data in the aircraft and week_id named ranges must be sorted (see MATCH function).

Is it possible to write a UDF in VBA that contains a period in the name?

Excel 2010 ships with some functions that contain a period in their name. For example STDEV.S and PERCENTILE.EXC
Is it possible to assign a name to my own function with a name such as PERCENTILE.CUSTOM using VBA?
For example, I would like to reference the following function using the formula =NAME.SUFFIX()
Option Explicit
Function NAMEdotSUFFIX() As Variant
NAMEdotSUFFIX = 42
End Function
As #SiddharthRout already suggested, external libraries have fewer restrictions than VBA when it comes to declaring function names, so periods are permitted in external names.
Despite the MSDN documentation, it is possible to use some special characters in VBA identifiers, including UDFs. The special characters can even be the first/only character in the identifier. The special characters will vary in their availability, according to the code-page of the VBA project and VBE.
This is a perfectly valid VBA function:
Public Function Foo·Bar()
Foo·Bar = 5
End Function
And can be used in an Excel formula:
=Foo·Bar()
5
Likewise, this gem of a function uses a non-breaking space as the function name:
Public Function  ()
  = 6
End Function
But while the function with an NBSP is valid, sadly, it can't be used in an Excel formula.
It is possible to use:
Public Function ·()
· = 5
End Function
BONUS HACK
Somebody forgot to tell the Enum team about the identifier rules. And while Enums can't be used in Excel formulas, you can use any character except a carriage return or square-brackets inside square brackets, as an enum name or member.
This is perfectly valid VBA:
Enum [Foo : Bar = 5 : End Enum]
[.B!a r"] = 6
End Enum
And the VBE pretty-printer turns it into this unparseable mess:
Enum Foo : Bar = 5 : End Enum
[.B!a r"] = 6
End Enum
It is not possible from VBA. This MSDN article shows how to define a function in VBA, and specifically this article lays out the rules for element names.