Passing a different function into the same iteration loop (VBA) - excel

My overall goal is to translate an Excel based financial model into VBA. The financial model has a number of scenarios (e.g. 3) and development types (e.g. 3 - residential, commercial, industrial). There are two components of the model - revenue and costs - at present but my solution needs to be massively scalable.
The revenue calculation will be the same for each of the nine instances but the inputs will change. I am taking the inputs from the workbook and placing them in collections. I add the result of the calculation to another collection. The costs calculation will be different but will use the exact same iteration loops.
What I'm trying to do is write the iteration code once but pass a different calculation to the loop. I've done it as follows by coding the formula in a Class Object and then passing the Class Object into a function. Please see below a sandbox example.
' Passing different classes into iteration loop
Sub Main()
Dim clsAdd As CAdd
Dim colAdd As Collection
Set clsAdd = New CAdd
Set colAdd = New Collection
Set colAdd = Iteration(clsAdd)
Dim clsMul As CMult
Dim colMul As Collection
Set clsMul = New CMult
Set colMul = New Collection
Set colMul = Iteration(clsMul)
End Sub
' Same iteration loop required for different calculations
Function Iteration(ByRef colClass As Object) As Collection
Dim varArray01() As Variant
Dim varArray02() As Variant
Dim itA As Integer
Set Iteration = New Collection
varArray01 = Array(1, 2, 3, 4)
varArray02 = Array(11, 12, 13, 14)
For itA = 0 To UBound(varArray01)
Iteration.Add colClass.ICalculation_Calculation(varArray01(itA), varArray02(itA))
Next itA
End Function
'Add Class
Public Function Calculation(ByVal intA As Integer, ByVal intB As Integer) As Integer
Calculation = intA + intB
End Function
'Multiply Class
Public Function Calculation(ByVal intA As Integer, ByVal intB As Integer) As Integer
Calculation = intA * intB
End Function
Although this works, I feel that there must be a better solution than creating a new Class Object for each formula I want because every formula must be calculated using the a function called 'Calculation'. Your suggestions are most welcome.

You apparently already have an interface defined for your calculation classes. Just change the parameter of Iteration to take the interface directly.
You have implemented the Command design pattern. Small, single purpose Command objects (your CAdd and CMult) are (IMO) good design..I wouldn't try to convince you to take a different approach.
' Same iteration loop required for different calculations
Function Iteration(ByRef calculator As ICalculation) As Collection
Dim varArray01() As Variant
Dim varArray02() As Variant
Dim itA As Integer
Set Iteration = New Collection
varArray01 = Array(1, 2, 3, 4)
varArray02 = Array(11, 12, 13, 14)
For itA = 0 To UBound(varArray01)
Iteration.Add calculator.Calculation(varArray01(itA), varArray02(itA))
Next itA
End Function
Sub Main becomes:
' Passing different classes into iteration loop
Sub Main()
Dim colAdd As Collection
Set colAdd = Iteration(New CAdd)
Dim colMul As Collection
Set colMul = Iteration(New CMult)
End Sub
If creating several small, 'stateless' classes is not the kind of solution you are looking for, then the Command pattern can also be implemented using a single Class that contains a minimal amount of 'state'.
using a class like Calculator below:
Option Explicit
Implements ICalculation
Public Enum CalcID
Add
Multiply
End Enum
Private mCalculationID As CalcID
Private Sub Class_Initialize()
mCalculationID = Add
End Sub
Public Property Get CalculationID() As CalcID
CalculationID = mCalculationID
End Property
Public Property Let CalculationID(ByVal RHS As CalcID)
Select Case RHS
Case Add, Multiply
mCalculationID = RHS
Case Else
Err.Raise -1, "Invalid calculationID: " & CStr(RHS)
End Select
End Property
Private Function ICalculation_Calculation(ByVal intA As Integer, ByVal intB As Integer) As Integer
Select Case mCalculationID
Case Multiply
ICalculation_Calculation = intA * intB
Case Add
ICalculation_Calculation = intA + intB
Case Else
'Raise an error
End Select
End Function
Now you have one calculator class to manage...and the calling module(s) are responsible for managing the calculator's configuration. The Main subroutine changes slightly and the Iteration function remains unchanged.
Sub Main()
Dim calc As Calculator
Set calc = New Calculator
calc.CalculationID = CalcID.Add
Dim colAdd As Collection
Set colAdd = Iteration(calc)
calc.CalculationID = CalcID.Multiply
Dim colMul As Collection
Set colMul = Iteration(calc)
End Sub

Related

Why can't I declare my Class Object as such?

I am currently creating a Class Object for a VBA file, its objective is to act as a range dictionary that can be passed single cells. If this cell is contained in one of the ranges, it returns the value associated to the corresponding range key. The class name is "rangeDic".
It is in the making so its functionalities are not implemented yet. Here's the code:
Private zone() As String
Private bounds() As String
Private link As Dictionary
Const ContextId = 33
'Init zone
Private Sub Class_Initialize()
Set link = New Dictionary
ReDim zone(0)
ReDim bounds(0)
End Sub
'properties
Property Get linkDico() As Dictionary
Set linkDico = link
End Property
Property Set linkDico(d As Dictionary)
Set link = d
End Property
Property Get pZone() As String()
pZone = zone
End Property
Property Let pZone(a() As String)
Let zone = a
End Property
'methods
Public Sub findBounds()
Dim elmt As String
Dim i As Integer
Dim temp() As String
i = 1
For Each elmt In zone
ReDim Preserve bounds(i)
temp = Split(elmt, ":")
bounds(i - 1) = temp(0)
bounds(i) = temp(1)
i = i + 2
Next elmt
End Sub
I was trying to instanciate it in a test sub in order to debug mid conception. Here's the code:
Sub test()
Dim rd As rangeDic
Dim ran() As String
Dim tabs() As Variant
Dim i As Integer
i = 1
With ThisWorkbook.Worksheets("DataRanges")
While .Cells(i, 1).Value <> none
ReDim Preserve ran(i - 1)
ReDim Preserve tabs(i - 1)
ran(i - 1) = .Cells(i, 1).Value
tabs(i - 1) = .Cells(i, 3).Value
i = i + 1
Wend
End With
Set rd = createRangeDic(ran, tabs)
End Sub
Public Function createRangeDic(zones() As String, vals() As Variant) As rangeDic
Dim obje As Object
Dim zonesL As Integer
Dim valsL As Integer
Dim i As Integer
zonesL = UBound(zones) - LBound(zones)
valsL = UBound(vals) - LBound(vals)
If zonesL <> valsL Then
Err.Raise vbObjectError + 5, "", "The key and value arrays are not the same length.", "", ContextId
End If
Set obje = New rangeDic
obje.pZone = zones()
For i = 0 To 5
obje.linkDico.add zones(i), vals(i)
Next i
Set createRangeDic = obje
End Function
Take a look at line 2 of Public Function createRangeDic. I have to declare my object as "Object", if I try declaring it as "rangeDic", Excel crashes at line obje.pZone = zones(). Upon looking in the Windows Event Log, I can see a "Error 1000" type of application unknown error resulting in the crash, with "VB7.DLL" being the faulty package.
Why so ? Am I doing something wrong ?
Thanks for your help
Edit: I work under Excel 2016
It looks like this is a bug. My Excel does not crash but I get an "Internal Error".
Let's clarify a few things first, since you're coming from a Java background.
Arrays can only be passed by reference
In VBA an array can only be passed by reference to another method (unless you wrap it in a Variant). So, this declaration:
Property Let pZone(a() As String) 'Implicit declaration
is the equivalent of this:
Property Let pZone(ByRef a() As String) 'Explicit declaration
and of course, this:
Public Function createRangeDic(zones() As String, vals() As Variant) As rangeDic
is the equivalent of this:
Public Function createRangeDic(ByRef zones() As String, ByRef vals() As Variant) As rangeDic
If you try to declare a method parameter like this: ByVal a() As String you will simply get a compile error.
Arrays are copied when assigned
Assuming two arrays called a and b, when doing something like a = b a copy of the b array is assigned to a. Let's test this. In a standard module drop this code:
Option Explicit
Sub ArrCopy()
Dim a() As String
Dim b() As String
ReDim b(0 To 0)
b(0) = 1
a = b
a(0) = 2
Debug.Print "a(0) = " & a(0)
Debug.Print "b(0) = " & b(0)
End Sub
After running ArrCopy my immediate window looks like this:
As shown, the contents of array b are not affected when changing array a.
A property Let always receives it's parameters ByVal regardless of whether you specify ByRef
Let's test this. Create a class called Class1 and add this code:
Option Explicit
Public Property Let SArray(ByRef arr() As String)
arr(0) = 1
End Property
Public Function SArray2(ByRef arr() As String)
arr(0) = 2
End Function
Now create a standard module and add this code:
Option Explicit
Sub Test()
Dim c As New Class1
Dim arr() As String: ReDim arr(0 To 0)
arr(0) = 0
Debug.Print arr(0) & " - value before passing to Let Property"
c.SArray = arr
Debug.Print arr(0) & " - value after passing to Let Property"
arr(0) = 1
Debug.Print arr(0) & " - value before passing to Function"
c.SArray2 arr
Debug.Print arr(0) & " - value after passing to Function"
End Sub
After running Test, my immediate window looks like this:
So, this simple test proves that the Property Let does a copy of the array even though arrays can only be passed ByRef.
The bug
Your original ran variable (Sub test) is passed ByRef to createRangeDic under a new name zones which is then passed ByRef again to pZone (the Let property). Under normal circumstances there should be no issue with passing an array ByRef as many times as you want but here it seems it is an issue because the Property Let is trying to make a copy.
Interestingly if we replace this (inside createRangeDic):
obje.pZone = zones()
with this:
Dim x() As String
x = zones
obje.pZone = x
the code runs with no issue even if obje is declared As rangeDic. This works because the x array is a copy of the zones array.
It looks that the Property Let cannot make a copy of an array that has been passed ByRef multiple times but it works perfectly fine if it was passed ByRef just once. Maybe because of the way stack frames are added in the call stack, there is a memory access issue but difficult to say. Regardless what the problem is, this seems to be a bug.
Unrelated to the question but I must add a few things:
Using ReDim Preserve in a loop is a bad idea because each time a new memory is allocated for a new (larger) array and each element is copied from the old array to the new array. This is very slow. Instead use a Collection as
#DanielDuĊĦek suggested in the comments or minimize the number of ReDim Preserve calls (for example if you know how many values you will have then just dimension the array once at the beginning).
Reading a Range cell by cell is super slow. Read the whole Range into an array by using the Range.Value or Range.Value2 property (I prefer the latter). Both methods returns an array as long as the range has more than 1 cell.
Never expose a private member object of a class if that object is responsible for the internal workings of the class. For example you should never expose the private collection inside a custom collection class because it breaks encapsulation. In your case the linkDico exposes the internal dictionary which can the be modified from outside the main class instance. Maybe it does not break anything in your particular example but just worth mentioning. On the other hand Property Get pZone() As String() is safe as this returns a copy of the internal array.
Add Option Explicit to the top of all your modules/classes to make sure you enforce proper variable declaration. Your code failed to compile for me because none does not exist in VBA unless you have it somewhere else in your project. There were a few other issues that I found once I turned the option on.

How do I add multiple Keys and Values into dictionary at the same time?

I am trying to add a column as the key and the column to the right of it as the value.
Can I do this without a loop?
I tried:
analystDict.Add Key:=refWS.Range("A2:A21"), Item:=refWS.Range("B2:B21")
When I try to Debug.Print I get a Type mismatch error:
For Each x In analystDict.Keys
Debug.Print x, analystDict(x)
Next x
You can't do this in VBA without writing a helper function.
Option Explicit
Public Sub AddTest()
Dim analystDict As Scripting.Dictionary
Set analystDict = New Scripting.Dictionary
Dim refWS As Worksheet
Set refWS = ActiveSheet
AddToDictionary _
analystDict, _
Application.WorksheetFunction.Transpose(refWS.Range("A2:A21").Value), _
Application.WorksheetFunction.Transpose(refWS.Range("B2:B21").Value)
End Sub
Public Sub AddToDictionary(ByRef ipDict As Scripting.Dictionary, ByVal ipKeys As Variant, ByVal ipValues As Variant)
If UBound(ipKeys) <> UBound(ipValues) Then
MsgBox "Arrays are not the same size"
Exit Function
End If
Dim myIndex As Long
For myIndex = LBound(ipKeys) To UBound(ipKeys)
ipDict.Add ipKeys(myIndex), ipValues(myIndex)
Next
End Function
You're taking a shortcut that's not allowed; Dictionary.Add is implemented such that it expects one key/value pair, and adds one item to the dictionary. If you need to add multiple items, you need multiple calls to Dictionary.Add - there's no way around it.
A shortcut that would be allowed though, would be to just grab the values in any 2-column Range and turn that into a dictionary, rather than taking any random two arrays that may or may not be the same size.
Make a function that takes a 2D array and turns it into a dictionary by treating the first column as unique keys, and the second column as values.
Public Function ToDictionary(ByVal keyValuePairs As Variant) As Scripting.Dictionary
If Not IsArray(keyValuePairs) Then Err.Raise 5
If GetDimensions(keyValuePairs) <> 2 Then Err.Raise 5 'see https://stackoverflow.com/q/6901991/1188513
Dim result As Scripting.Dictionary
Set result = New Scripting.Dictionary
Const KEYCOL = 1, VALUECOL = 2
Dim i As Long
For i = LBound(keyValuePairs, KEYCOL) To UBound(keyValuePairs, KEYCOL)
If result.Exists(keyValuePairs(i, KEYCOL)) Then Err.Raise 457
result.Add Key:=keyValuePairs(i, KEYCOL), Item:=keyValuePairs(i, VALUECOL)
Next
Set ToDictionary = result
End Function
Now you can turn any 2-column Range into a Dictionary like this:
Dim things As Scripting.Dictionary
Set things = ToDictionary(Sheet1.Range("A2:B21").Value)
Note that Range.Value yields a 1-based, 2D Variant array whenever it refers to multiple cells.
Nice concept, Mathieu and you can even simplify this a bit. If you don't mind that a later key-value pair overwrites the most recent one then you can skip raising an error and do this:
Public Function ToDictionary(ByVal keyValuePairs As Variant) As Scripting.Dictionary
If Not IsArray(keyValuePairs) Then err.Raise 5
If GetDimensions(keyValuePairs) <> 2 Then err.Raise 5 'see https://stackoverflow.com/q/6901991/1188513
Dim result As Scripting.Dictionary
Set result = New Scripting.Dictionary
Const KEYCOL = 1, VALUECOL = 2
Dim i As Long
For i = LBound(keyValuePairs, KEYCOL) To UBound(keyValuePairs, KEYCOL)
' No need to check if you don't mind have subsequent instance of key-value overwrite the
' the current one.
' If result.Exists(keyValuePairs(i, KEYCOL)) Then err.Raise 457
result(keyValuePairs(i, KEYCOL)) = keyValuePairs(i, VALUECOL)
Next
Set ToDictionary = result
End Function

Sorting a collection of objects in VBA by using a comparator

I'm trying to implement my own collection in VBA in which to store a list of objects. I'm new to VBA and I need to find a way in which to implement a solution.
What I have: a list of objects with different properties on which the most important is the duration of a specific action.
What I need: I want a sorted list of objects by using a comparator based on a time property (duration of a specific action).
What kind of Collection do you recommend? I need to create my own helper method in order to sort the elements of the collection? VBA has a comparator interface that I can implement on my own collection object?
I have solved quite similar problem using Types.
Option Compare Database
Option Explicit
Type tPersonalData
FamilyName As String
Name As String
BirthDate As Date
End Type
Type tHuman
PersonalData As tPersonalData
Height As Integer
CashAmount As Integer
End Type
Public Sub subUsage()
Dim Humans(10) As tHuman
Dim HumanCurrent As tHuman
With HumanCurrent
.CashAmount = 100
.Height = 190
.PersonalData.FamilyName = "Smith"
.PersonalData.Name = "John"
.PersonalData.BirthDate = CDate("01.01.1980")
End With
Humans(1) = HumanCurrent
End Sub
So after that you can implement your own sorting functions for an array, using given field.
I have created a sorting method based on Van Ng's, in order to sort my objects like that:
Public Sub sortedCollectionByDuration()
Dim vItm As ClsCron
Dim i As Long, j As Long
Dim vTemp As Variant
Set vTemp = New ClsCron
Set vItm = New ClsCron
For i = 1 To myCol.Count - 1
For j = i + 1 To myCol.Count
If myCol(i).Duration > myCol(j).Duration Then
Set vTemp = myCol(j)
myCol.Remove j
myCol.add vTemp, , i
End If
Next j
Next i
-- testing or logging
For Each vItm In myCol
Debug.Print vItm.ModuleName & " " & vItm.FunctionName & VBA.Format(vItm.Duration, "HH:MM:SS")
Next vItm
End Sub

VBA - create unique ID of String / Hash

First of all I want so say sorry for not showing any code but right now I need some guidelines on how to take out a unique ID of a string.
So I have some problems of how to organize data. Lets say that the data is organized so that each dataID has their unique name. I collect the data into a array that holds it.
The problem I now have is that I want a easy way to search for these nameID. Imagine that the data is a lot bigger and contain more than a few hundred of different unique combinations of nameID's. Therefor I do not think searching for the id itself would be appropriate and I'm thinking of creating an hash that I could use an algorithm on to search the array. I want to do this because later on I will compare the names and add the values to the respective nameID. Keep in mind that the nameID will most of the time have the same structure but eventually a new name like total_air could be implemented and then I need to search in the array to get right value.
Updated:
Example of an code that collect the data from excel:
For Each targetSheet In wb.Worksheets
With targetSheet
'Populate the array
xData(0) = Application.Transpose(Range(Cells(1, 1), Cells(1, 1).End(xlDown)).Value2)
cnt = UBound(xData(0))
End With
Call dData.init(cnt)
'Populate the objectarray
dData.setNameArray = xData(0)
Next targetSheet
Type object:
Private index As Integer
Private id As String
Private nameID() As Variant
Private data() As Variant
Private cnt As Integer
Public Sub init(value As Integer)
index = 0
cnt = value
id = ""
ReDim nameID(0 To cnt)
ReDim data(0 To cnt)
End Sub
Property Let setID(value As String)
id = value
End Property
Property Let setNameArray(value As Variant)
nameID = value
End Property
dList that inherit the dataStruct:
Private xArray() As dataStruct
Private listInd As Integer
Public Sub init(cnt As Integer)
ReDim xArray(1 To cnt)
Dim num As Integer
For num = 1 To cnt
Set xArray(num) = New dataStruct
Next
listInd = 1
End Sub
Property Let addArray(value As dataStruct)
Set xArray(listInd) = value
listInd = listInd + 1
End Property
How the hole list will look like:
I would strongly advocate using a dictionary. Not only is it much faster to find an item (I would assume that it is implemented with some kind of hashing), it has big advantages when it comes to adding or removing items.
When you have an array and want to add an item, you either have always to use redim preserve which is really expensive, or you define the array larger than initially needed and always have to keep the information how many items are really used. And deleting an item from an array is rather complicated.
You cannot add a typed variable as item value into a dictionary, but you can add a object. So instead of your Type definition, create a simple class module, containing only these lines (of course you can create the class with properties, getter and setter but that's irrelevant for this example)
Public id As Long
Public name As String
Public value As Long
Then, dealing with the dictionary is rather simple (note that you have to add a reference to the Microsoft Scripting Runtime
Option Explicit
Dim myList As New Dictionary
Sub AddItemValues(id As Long, name As String, value As Long)
Dim item As New clsMyData
With item
.id = id
.name = name
.value = value
End With
Call AddItem(item)
End Sub
Sub AddItem(item As clsMyData)
If myList.Exists(item.id) Then
set myList(item.id) = item
Else
Call myList.Add(item.id, item)
End If
End Sub
Function SearchItem(id As Long) As clsMyData
If myList.Exists(id) Then
Set SearchItem = myList(id)
Else
Set SearchItem = Nothing
End If
End Function
Function SearchName(name As String) As clsMyData
Dim item As Variant
For Each item In myList.Items
If item.name = name Then
Set SearchName = item
Exit Function
End If
Next item
Set SearchName = Nothing
End Function
So as long as you deal with Id's, the dictionary will do all the work for you. Only if you search for the name, you have to loop over all items of the dictionary, which is as easy as looping over an array.
Some test (of course you should add some error handling)
Sub test()
Call AddItemValues(32, "input_air", 0)
Call AddItemValues(45, "air_Procent", 99)
Call AddItemValues(89, "output_air", 34)
Debug.Print SearchItem(45).name
Debug.Print SearchName("output_air").value
' Change value of output_air
Call AddItemValues(89, "output_air", 1234)
Debug.Print SearchName("output_air").value
End Sub

Is there a way to count elements in a VBA enum?

is there a proper way to count elements of an enum in VBA ?
At the moment, I leave an enum value such as KeepThisOneHere in the following example
Enum TestEnum
ValueA
ValueB
ValueC
KeepThisOneHere
End Enum
I use the last value to know the size... I don't like this solution, because I am not sure I have a guarantee the values will always be indexed the same way, and the code might be changed by a third party who might add values after this last special one, silently breaking the rest of the code.
Not sure on the etiquette here, so I'll post it and if advised, I'll come back and delete it. Chip Pearson posted this code on the Code Cage Forums (http://www.thecodecage.com/forumz/microsoft-excel-forum/170961-loop-enumeration-constants.html). I don't have the TypeLinInfo DLL on my machine, so I can't test it (I'm sure google will turn up places to download TLBINF32.dll). Nonetheless, here is his entire post to save someone else from registering for a forum:
You can do this ONLY IF you have the TypeLibInfo DLL installed on your
computer. In VBA, go to the Tools menu, choose References, and scroll
down to "TypeLib Info". If this item exists, check it. If it does not
exist, then quit reading because you can't do what you want to do. The
file name of the DLL you need is TLBINF32.dll.
The following code shows how to get the names and values in the
XLYesNoGuess enum:
Sub AAA()
Dim TLIApp As TLI.TLIApplication
Dim TLILibInfo As TLI.TypeLibInfo
Dim MemInfo As TLI.MemberInfo
Dim N As Long
Dim S As String
Dim ConstName As String
Set TLIApp = New TLI.TLIApplication
Set TLILibInfo = New TLI.TypeLibInfo
Set TLILibInfo = TLIApp.TypeLibInfoFromFile( _
ThisWorkbook.VBProject.References("EXCEL").FullPath)
ConstName = "XLYesNoGuess"
For Each MemInfo In _
TLILibInfo.Constants.NamedItem(ConstName).Members
S = MemInfo.Name
N = MemInfo.Value
Debug.Print S, CStr(N)
Next MemInfo
End Sub
Using this knowledge, you can create two useful functions. EnumNames
returns an array of strings containing the names of the values in an
enum:
Function EnumNames(EnumGroupName As String) As String()
Dim TLIApp As TLI.TLIApplication
Dim TLILibInfo As TLI.TypeLibInfo
Dim MemInfo As TLI.MemberInfo
Dim Arr() As String
Dim Ndx As Long
Set TLIApp = New TLI.TLIApplication
Set TLILibInfo = New TLI.TypeLibInfo
Set TLILibInfo = TLIApp.TypeLibInfoFromFile( _
ThisWorkbook.VBProject.References("EXCEL").FullPath)
On Error Resume Next
With TLILibInfo.Constants.NamedItem(EnumGroupName)
ReDim Arr(1 To .Members.Count)
For Each MemInfo In .Members
Ndx = Ndx + 1
Arr(Ndx) = MemInfo.Name
Next MemInfo
End With
EnumNames = Arr
End Function
You would call this function with code such as:
Sub ZZZ()
Dim Arr() As String
Dim N As Long
Arr = EnumNames("XLYesNoGuess")
For N = LBound(Arr) To UBound(Arr)
Debug.Print Arr(N)
Next N
End Sub
You can also create a function to test if a value is defined for an
enum:
Function IsValidValue(EnumGroupName As String, Value As Long) As
Boolean
Dim TLIApp As TLI.TLIApplication
Dim TLILibInfo As TLI.TypeLibInfo
Dim MemInfo As TLI.MemberInfo
Dim Ndx As Long
Set TLIApp = New TLI.TLIApplication
Set TLILibInfo = New TLI.TypeLibInfo
Set TLILibInfo = TLIApp.TypeLibInfoFromFile( _
ThisWorkbook.VBProject.References("EXCEL").FullPath)
On Error Resume Next
With TLILibInfo.Constants.NamedItem(EnumGroupName)
For Ndx = 1 To .Members.Count
If .Members(Ndx).Value = Value Then
IsValidValue = True
Exit Function
End If
Next Ndx
End With
IsValidValue = False
End Function
This function returns True if Value is defined for EnumGroupName or
False if it is not defined. You would call this function with code
like the following:
Sub ABC()
Dim B As Boolean
B = IsValidValue("XLYesNoGuess", xlYes)
Debug.Print B ' True for xlYes
B = IsValidValue("XLYesNoGuess", 12345)
Debug.Print B ' False for 12345
End Sub
Cordially,
Chip Pearson
Microsoft MVP 1998 - 2010
Pearson Software Consulting, LLC
www.cpearson.com
[email on web site]
Here's an example of my workaround, which is pretty straightforward:
Enum FileSpecFields
FileSpecFields_Start '(zero-based)
FileNameIdx = FileSpecFields_Start
FolderNameIdx
BasePathIdx
FullPathIdx
CopyStatus
FileSpecFields_End = CopyStatus
End Enum
'...
ReDim FileSpecList(1 To MaxFiles, FileSpecFields_Start To FileSpecFields_End) As String
'...
But note that, if you are using a one-based Enum you may have to adjust the _End value definition, depending on how you're using it. Also, for zero-based Enums, the _End value is not the same as its count of items. And, if you add items at the end, you must update the _End value's definition accordingly. Finally, if your enum is a non-contiguous range of values, all bets are off with this approach!
there isn't a way to get the count.
What you have to do is loop through the elements of the Enum until you get to the last one.
Chip Pearson has some good tips on Enumerated constants: Chip Pearson: Enum Variable Type
If you know the enum type(s) on design-time you could transform them into a Static Property Get MyEnumColl() as Collection ... (no class needed, initialized on 1st access statically) and thus easily loop through them or count them like shown here
Sub count()
Dim n, c
For n = headers.frstItem To headers.lastItem
c = c + 1
Next
Debug.Print c
End Sub

Resources