Constant in VBA referencing a label - vba

New to VBA. Was trying to create a constant that references a named column in my worksheet and am getting an error. Is this something you can do in VBA or is my syntax just wrong?
Example:
Public Const ColNum As Integer = [SomeColumn].Column

A constant must be able to be evaluated at the time the code is compiled (ie. before it runs)
This is OK:
Const A as Long = 10 'constant value
and this:
Const B As Long = A 'from another constant
or even
Const B As Long = A * 10 'constant expression
but not this:
Const B As Long = ActiveSheet.Columns.Count 'errors
because ActiveSheet.Columns.Count can only be determined at runtime

The compile error tells you what's wrong: Constant expression required
In other words, as #roryap mentions, you can only use a literal value for a Constant expression, you can't assign it anything that must be evaluated at runtime. A possible workaround is to use constant strings (i.e., your range's Name) and assign elsewhere as needed
From your parent/main procedure, call on another procedure which will assign to the module-level or public variables
Option Explicit
Const MyColumnName as String = "Dave_Column"
Dim ColNum as Integer
Sub main()
Call InitializeVariables
'The rest of your code ...
MsgBox ColNum
End Sub
Sub InitializeVariables()
'Use this procedure to assign public/module scope variables if needed
ColNum = Range(MyColumnName).Column
End Sub
Alternatively, ColNum can be a function with optional parameters, which when left blank would return the range based on the Constant string, or you could specify a different range name/address to return another column number:
Option Explicit
Const MyColumnName as String = "Dave_Column"
Sub main()
MsgBox ColNum
MsgBox ColNum("H1")
End Sub
Function ColNum(Optional name$) As Integer
If name = vbNullString Then
name = MyColumnName
End If
ColNum = Range(name).Column
End Function
Note: this will fail if the named range doesn't exist :)

Related

Error In Function Name

I get an error when trying to use the below function. The strange part is that the error occurs in the name
Code:
Function DATECHECK(rng As Range, date_time As Date) As Boolean
For Row = 1 To rng.Rows.Count
'Stuff'
Next Row
End Function
Syntax:
DateCheck(Sheet2!A2:B561, Sheet1!A2)
Error:
#NAME
#NAME(Sheet2!A2:B561, Sheet1!A2)
Make sure the function is in a public module; not a private worksheet code sheet.
DateCheck should return something. Add DateCheck = 1 before End Function.
You cannot manipulate values of other cells with a worksheet UDF. You decided that showing the relevant code wasn't important; it was important.
In your function's context, Row is a variable. You need to declare it as a long (e.g. dim row as long) if you are using Option Explicit.

EXCEL VBA how to use functions and split to extract integer from string

I'm working on a piece of code to extract the nominal size of a pipeline from it's tagname. For example: L-P-50-00XX-0000-000. The 50 would be it's nominal size (2") which I would like to extract. I know I could do it like this:
TagnameArray() = Split("L-P-50-00XX-0000-000", "-")
DNSize = TagnameArray(2)
But I would like it to be a function because it's a small part of my whole macro and I don't need it for all the plants I'm working on just this one. My current code is:
Sub WBDA_XXX()
Dim a As Range, b As Range
Dim TagnameArray() As String
Dim DNMaat As String
Dim DN As String
Set a = Selection
For Each b In a.Rows
IntRow = b.Row
TagnameArray() = Split(Cells(IntRow, 2).Value, "-")
DN = DNMaat(IntRow, TagnameArray())
Cells(IntRow, 3).Value = DN
Next b
End Sub
Function DNMaat(IntRow As Integer, TagnameArray() As String) As Integer
For i = LBound(TagnameArray()) To UBound(TagnameArray())
If IsNumeric(TagnameArray(i)) = True Then
DNMaat = TagnameArray(i)
Exit For
End If
Next i
End Function
However this code gives me a matrix expected error which I don't know how to resolve. I would also like to use the nominal size in further calculations so it will have to be converted to an integer after extracting it from the tagname. Does anyone see where I made a mistake in my code?
This is easy enough to do with a split, and a little help from the 'Like' evaluation.
A bit of background on 'Like' - It will return TRUE or FALSE based on whether an input variable matches a given pattern. In the pattern [A-Z] means it can be any uppercase letter between A and Z, and # means any number.
The code:
' Function declared to return variant strictly for returning a Null string or a Long
Public Function PipeSize(ByVal TagName As String) As Variant
' If TagName doesn't meet the tag formatting requirements, return a null string
If Not TagName Like "[A-Z]-[A-Z]-##-##[A-Z]-####-###" Then
PipeSize = vbNullString
Exit Function
End If
' This will hold our split pipecodes
Dim PipeCodes As Variant
PipeCodes = Split(TagName, "-")
' Return the code in position 2 (Split returns a 0 based array by default)
PipeSize = PipeCodes(2)
End Function
You will want to consider changing the return type of the function depending on your needs. It will return a null string if the input tag doesnt match the pattern, otherwise it returns a long (number). You can change it to return a string if needed, or you can write a second function to interpret the number to it's length.
Here's a refactored version of your code that finds just the first numeric tag. I cleaned up your code a bit, and I think I found the bug as well. You were declaring DNMAAT as a String but also calling it as a Function. This was likely causing your Array expected error.
Here's the code:
' Don't use underscores '_' in names. These hold special value in VBA.
Sub WBDAXXX()
Dim a As Range, b As Range
Dim IntRow As Long
Set a = Selection
For Each b In a.Rows
IntRow = b.Row
' No need to a middleman here. I directly pass the split values
' since the middleman was only used for the function. Same goes for cutting DN.
' Also, be sure to qualify these 'Cells' ranges. Relying on implicit
' Activesheet is dangerous and unpredictable.
Cells(IntRow, 3).value = DNMaat(Split(Cells(IntRow, 2).value, "-"))
Next b
End Sub
' By telling the function to expect a normal variant, we can input any
' value we like. This can be dangerous if you dont anticipate the errors
' caused by Variants. Thus, I check for Arrayness on the first line and
' exit the function if an input value will cause an issue.
Function DNMaat(TagnameArray As Variant) As Long
If Not IsArray(TagnameArray) Then Exit Function
Dim i As Long
For i = LBound(TagnameArray) To UBound(TagnameArray)
If IsNumeric(TagnameArray(i)) = True Then
DNMaat = TagnameArray(i)
Exit Function
End If
Next i
End Function
The error matrix expected is thrown by the compiler because you have defined DNMaat twice: Once as string variable and once as a function. Remove the definition as variable.
Another thing: Your function will return an integer, but you assigning it to a string (and this string is used just to write the result into a cell). Get rid of the variable DN and assign it directly:
Cells(IntRow, 3).Value = DNMaat(IntRow, TagnameArray())
Plus the global advice to use option explicit to enforce definition of all used variables and to define a variable holding a row/column number always as long and not as integer

Pass name created in Name Manager Excel to Function VBA

I created a name in Name Manager. How to pass the name "MyRange1" parameter for my function in VBA code?
In Excel:
=MyFunction(MyRange1)
MyFunction is:
Public Function MyFunction(nameDefined As Variant) As String
'How get value of nameDefined ??
End Function
There are two ways to pass a Named Range:
as a String
as a Range Object
so a UDF() in a worksheet cell would have either:
=myudf("Name1")
or
=myudf(Name1)
Naturally, the UDF() would have to be coded to expect one or the other,
Note that there could be volatility problems with using only a String.
EDIT#1:
Here is an example of passing a Range rather than a String. Say we create MyRange1 like:
and the UDF() is like:
Public Function MyFunction(rng As Range) As String
Dim r As Range
For Each r In rng
MyFunction = MyFunction & "..." & r.Text
Next r
End Function
Then we can use it in a worksheet cell like:
=MyFunction(MyRange1)
Once the UDF() has the range, it can get the list of items contained therein.
To figure out similar questions, you can put a Breakpoint in the code and analyse the Locals window:
From the Locals window you can notice that you can access "Str2" with nameDefined(2,1)
( or in your version of Excel it might be nameDefined(2) for horizontal array )
You can also check the run-time type with some of the VBA functions:
t = VBA.TypeName(nameDefined) ' t = "Variant()"
v = VBA.VarType(nameDefined) ' v = vbArray + vbVariant ( = 8204 )
b = VBA.IsArray(nameDefined) ' b = True ( also True for range with more than one cell )

Argument not Optional Error

I am building a sheet to extract data from a list of job openings that I need to sort and filter by location and by BU. I need the code to count the number of openings then pass that information back to the main sub to use in creating additional pages and to loop the sub. I keep getting the above error in this segment. Any thoughts on what I am doing wrong?
Sub Organize_Data()
Dim A As Integer
Dim B As Integer
Dim C As Integer
Worksheets.Add().Name = "Calculations"
Find_Unit
Find_Locations
Count_BU_Data
Count_Country_Data
Count_Raw_Data
End Sub
Function Count_BU_Data(A As Integer)
ActiveWorkbook.Worksheets("Calculations").Range("B3", Worksheets("Calculations").Range("B3").End(xlDown)).Rows.Count
End Function
Your UDF:
Function Count_BU_Data(A As Integer)
Takes an argument (A As Integer) which you haven't specified as being an Optional argument. You call the function from your other routine without supplying this argument:
Sub Organize_Data()
Dim A As Integer
Dim B As Integer
Dim C As Integer
Worksheets.Add().Name = "Calculations"
Find_Unit
Find_Locations
Count_BU_Data '// <~~ No argument passed to function.
Count_Country_Data
Count_Raw_Data
End Sub
Hence the 'Argument not optional' error.
Seeing as that function doesn't appear to actually use the argument, you can either remove it from the function header:
Function Count_BU_Data() As Long '// Note I've included a return value...
Or make it an optional argument
Function Count_BU_Data(Optional A As Integer) As Long
You can also specify a default value if the optional argument isn't supplied
Function Count_BU_Data(Optional A As Integer = 1) As Long

Syntax options creating errors in VBA Macro for Excel

I'm having some trouble with syntax options while writing a VBA Macro for Excel. In VBA you can call a method on an object in two different ways:
foo.bar(arg1, arg2)
or
foo.bar arg1, arg2
I absolutely detest the second sort of syntax because I find it lacks any sort of clarity, so I normally adhere to the first option. However, I've come across a situation where using the first option creates an error, while the second executes fine. (This may perhaps be an indicator of other problems in my code.) Here is the culprit code:
Function GetFundList() As Collection
Dim newFund As FundValues
Range("A5").Select
Set GetFundList = New Collection
While Len(Selection.Value)
Set newFund = New FundValues
' I set the fields of newFund and move Selection
The problem is in this next line:
GetFundList.Add newFund
Wend
End Function
FundValues is a class I created that is essentially just a struct; it has three properties which get set during the loop.
Basically, when I call GetFundList.Add(newFund) I get the following error:
Run-time error '438':
Object doesn't support this property or method
But calling GetFundList.Add newFund is perfectly fine.
Does anyone understand the intricacies of VBA well enough to explain why this is happening?
EDIT: Thanks much for the explanations!
Adding items to a collection is not defined as a function returning a value, but as a sub routine:
Public Sub Add( _
ByVal Item As Object, _
Optional ByVal Key As String, _
Optional ByVal { Before | After } As Object = Nothing _
)
When calling another sub routine by name and sending arguments (without adding the "Call" statement), you are not required to add parentheses.
You need to add parentheses when you call a function that returns a value to a variable.
Example:
Sub Test_1()
Dim iCnt As Integer
Dim iCnt_B As Integer
Dim iResult As Integer
iCnt = 2
iCnt_B = 3
fTest_1 iCnt, iResult, iCnt_B
End Sub
Public Function fTest_1(iCnt, iResult, iCnt_B)
iResult = iCnt * 2 + iCnt_B * 2
End Function
Sub Test_2()
Dim iCnt As Integer
Dim iCnt_B As Integer
Dim iResult As Integer
iCnt = 2
iCnt_B = 3
iResult = fTest_2(iCnt, iCnt_B)
End Sub
Public Function fTest_2(iCnt, iCnt_B)
fTest_2 = iCnt * 2 + iCnt_B * 2
End Function
Let me know if not clear.
This Daily Dose of Excel conversation will be helpful.
When you use the parentheses you are forcing VBA to evaluate what's inside them and adding the result to the collection. Since NewFund has no default property - I assume - the evaluation yields nothing, so can't be added. Without the parentheses it evaluates to the instance of the class, which is what you want.
Another example. This:
Dim coll As Collection
Set coll = New Collection
coll.Add Range("A1")
Debug.Print coll(1); TypeName(coll(1))
and this ...
coll.Add (Range("A1"))
Debug.Print coll(1); TypeName(coll(1))
... both yield whatever is in A1 in the debug.window, because Value is Range's default property. However, the first will yield a type of "Range", whereas the type in the 2nd example is the data type in A1. In other words, the first adds a range to the collection, the 2nd the contents of the range.
On the other hand, this works:
Dim coll As Collection
Set coll = New Collection
coll.Add ActiveSheet
Debug.Print coll(1).Name
... and this doesn't:
coll.Add (ActiveSheet)
Debug.Print coll(1).Name
because ActiveSheet has no default property. You'll get an runtime error 438, just like in your question.
Here's another way of looking at the same thing.
Let assume that cell A1 contains the string Hi!
Function SomeFunc(item1, item2)
SomeFunc = 4
End Function
Sub Mac()
' here in both of the following two lines of code,
' item1 will be Variant/Object/Range, while item2 will be Variant/String:
SomeFunc Range("A1"), (Range("A1"))
Let i = SomeFunc(Range("A1"), (Range("A1")))
'this following is a compile syntax error
SomeFunc(Range("A1"), (Range("A1")))
' while here in both the following two lines of code,
' item1 will be Variant/String while item2 will be Variant/Object/Range:
SomeFunc ((Range("A1")), Range("A1")
Let j = SomeFunc((Range("A1")), Range("A1"))
'this following is a compile syntax error
SomeFunc((Range("A1")), Range("A1"))
Set r = Range("A1") ' sets r to Variant/Object/Range
Set r = (Range("A1")) ' runtime error 13, type mismatch; cannot SET r (as reference) to string "Hi!" -- Strings are not objects in VBA
Set r = Range("A1").Value ' runtime error (same)
Let r = Range("A1") ' set r to "Hi!" e.g. contents of A1 aka Range("A1").Value; conversion to value during let = assignment
Let r = (Range("A1")) ' set r to "Hi!" e.g. contents of A1 aka Range("A1").Value; conversion to value by extra ()'s
Let r = Range("A1").Value ' set r to "Hi!" by explicit use of .Value
End Sub
I only add this to help illustrate that there are two things going on here, which could be conflated.
The first is that the () in an expression that converts the item to its Value property as stated above in other answers.
The second is that functions invoked with intent to capture or use the return value require extra () surrounding the whole argument list, whereas functions (or sub's) invoked without intent to capture or use the return value (e.g. as statements) must be called without those same () surrounding the argument list. These surrounding () do not convert the argument list using .Value. When the argument list has only one parameter, this distinction can be particularly confusing.