I need to declare an array in VBA that will be used by every function. However, I cannot declare it as a global as I would do in C++.
My code is as follows:
Option Explicit
Dim test(0 to 10) as String
test(0) = "avds"
test(1) = "fdsafs"
....
The following conceptualizes what I am trying to do.
public function store() as boolean
Worksheets("test").cells(1,1) = test(0)
End Function
How can I achieve this functionality?
For global declaration, change Dim to Public like so:
Public test(0 to 10) as String
You can call this like (assuming it is in Module1, else change Module1 to whatever you've named it):
Module1.test(0) = "something"
Or simply:
test(0) = "something"
Why wouldn't you create everything in a class? That's the reason why classes where invented after all.
Consider the Class1 definition
Option Explicit
Private m_data() As String
Private Sub Class_Initialize()
ReDim m_data(0 To 10)
End Sub
Private Sub Class_Terminate()
Erase m_data
End Sub
Public Property Get Count() As Integer
Count = UBound(m_data) - LBound(m_data) + 1
End Property
Public Property Get Data(index As Integer) As String
Data = m_data(index)
End Property
Public Property Let Data(index As Integer, value As String)
m_data(index) = value
End Property
Public Function Store(rng As Range) As Boolean
Store = (rng.value = m_data(0))
End Function
You can add all the functions you want that can access your array just like Store().
with the test code in a worksheet of
Public Sub Test()
Dim c As New Class1
c.Data(0) = "January"
Debug.Print c.Store(Cells(1, 1))
End Sub
You can also cache the location of the cell where it is referencing, or used an assumed named argument and only supply a reference to the worksheet once after class initialization.
You can use the Public keyword to declare a variable that you need to access in any module.
Remember that in vba you cannot declare variables or code outside of procedures.
See here for more information
I have a recommendation that is a bit lighter than a class (although class is a great recommendation)
Option 1
Define your desired constant array as a delimited string constant:
Public Const cstrTest = "String 1;String 2; String 3; String 4; String 5; String 6"
Next, whenever you need it just use Split to create an array with minimal code:
Dim arrStrings
arrStrings = Split (cstrTest, ";")
Option 2
You might replace (or combine with Option 1) a simple public function
Public Function constStringArray() As String()
constStringArray = Split (cstrTest, ";")
End Function
So then, in use...
Dim arrStrings
'Option 1 example
arrStrings = Split (cstrTest, ";")
'Option 2 example
arrStrings = constStringArray()
one can do it (with global initialization) via Static Property quite straight-forward without creating a class or string parsing - as described in detail and with examples here
Related
I'm trying to create a class with arrays in it, and I'm having issues creating the class for it...
CLASS:
Private pST(0 To 2) As String
Public Property Get ST() As String
ST() = pST()
End Property
Public Property Let ST(value() As String) '<---- ERROR HERE
pST() = value()
End Property
CODE RUN:
Sub test()
Dim foo As cPurchaseOrder
Set foo = New cPurchaseOrder
foo.ST(0) = "test"
Debug.Print foo.ST(0)
End Sub
THE ERROR:
Compile error:
Definitions of property procedures for the same property are inconsistent, or property procedure has an optional parameter, a ParamArray, or an invalid Set final parameter.
THE QUESTION:
How can I properly initialize a class with arrays as variables?
EDIT: in relation to Mat's Mug response
CLASS CHANGED:
Private pST As Variant
Public Property Get STContent(ByVal index As Long) As String
STContent = pST(index)
End Property
Public Property Let STContent(ByVal index As Long, ByVal value As String)
pST(index) = value
End Property
Private Sub Class_Initialize()
ReDim pST(0 To 2)
End Sub
CODE RUN TO TEST:
Sub test()
Dim foo As cPurchaseOrder
Set foo = New cPurchaseOrder
foo.STContent(0) = "test" '<--- Type mismatch here
Debug.Print foo.STContent(0)
End Sub
Your getter would need to return a String() array for the types to be consistent:
Public Property Get ST() As String()
However I wouldn't recommend exposing an array like this. First because assigning typed arrays is rather painful, second because the setter (Property Let) is actually cheating here:
Public Property Let ST([ByRef] value() As String)
Unless you specify ByVal explicitly, a parameter is always passed ByRef in VBA... except there's this quirk about Property Let - the RHS/value parameter is always passed ByVal at run-time.
And arrays can only ever be passed ByRef.
Therefore, a property that gets (or assigns, actually) a whole array doesn't make much sense.
A better way would be to encapsulate the array (I'd make it a Variant though), and expose its contents (not the array itself) through an indexed property:
Private internal As Variant 'String array
'...
Public Property Get Content(ByVal index As Long) As String
Content = internal(index)
End Property
Public Property Let Content(ByVal index As Long, ByVal value As String)
internal(index) = value
End Property
You have a lot of issues there.
First, your Property Get needs to return a String array. Second, your array needs to be dynamic, or you need to rewrite the whole thing so that you pass an index value to it, otherwise there is no way to indicate which value you are passing to the array. So, for example, using a dynamic array:
Private pST() As String
Public Property Get ST() As String()
ST = pST
End Property
Public Property Let ST(value() As String)
pST() = value()
End Property
and the calling code:
Sub test()
Dim foo As cPurchaseOrder
Set foo = New cPurchaseOrder
Dim asData() As String
ReDim asData(0)
asData(0) = "test"
foo.ST = asData
Debug.Print foo.ST()(0)
End Sub
Unfortunately, I couldn't be sure form the original what the intent was.
It is getting late here but give it a try. In the module:
Option Explicit
Sub Test()
Dim foo As cPurchaseOrder
Set foo = New cPurchaseOrder
foo.AddValueToSt "test", 1
Debug.Print foo.ST(1)
End Sub
In the Class:
Option Explicit
Private pST
Public Property Get ST() As Variant
ST = pST
End Property
Public Property Let ST(value As Variant)
pST = value
End Property
Public Function AddValueToSt(value As Variant, position As Long)
pST(position) = value
End Function
Private Sub Class_Initialize()
ReDim pST(2)
End Sub
This is my way to use the Factory Method Pattern. When I say "my way", for me this pattern is translated to "Whenever some OOP requires more than 5 minutes of thinking simply add a function."
Problem
When you want to look directly at the arguments of your UDF (not their values, which can be passed directly, but the formula that gave these values), you can use Application.Caller.Formula and parse out the arguments to find out.
Is there any way to see the line of VBA code which called a Function, so that you can parse out its arguments in a similar way?
Background
A while ago I created a UDF which was essentially another approach to array functions*. What I wanted to do was take some statement which evaluates to True/False
LEN(A1)>LEN(B1)
And evaluate it over an array. So say the above function was placed in cell A1, then to evaluate over the array A1:A100 would be the same as creating the array
{LEN(A1)>LEN(B1),LEN(A2)>LEN(B2),[...]} 'you may recognise this as an array formula ={LEN(A1:A100)>LEN(B1:B100)}
*For context, this was before I knew about array formulae
I was frustrated with the syntax of certain array-handling Excel functions, like COUNTIF, which takes the arguments in the following form
COUNTIF(range_To_Evalueate_Over, "string_Representing_Boolean_Test")
The string argument presents the following limitations
Not any boolean returning statement can be used as a test; there is no way of looking at properties of the range which you evaluate over other than their values
So you can't use functions like LEN() to get more data about the range
You can not reference other cells relative to the range (Like B1 relative to A1)
The string is static at runtime, you cannot step-into the function to see what the string will evaluate to for a given cell from the range you are evaluating
I much prefer the versatility of the conditional formatting formulae. They take the form of array formulae, where any offsets (B1 relative to A1) are calculated relative to the TL cell of the range that the conditional formatting is applied to.
That prompted me to create a UDF which has a structure like this
evaluateOverRange(range_to_evalute_over As Range, boolean_test_on_TL_Cell As Boolean) As Boolean() 'returns an array equal in size to the evaluate range
Used like
evaluateOverRange(A1:A100,LEN(A1)<LEN(B1))
Note
Boolean test is not a string, so can be evaluated step by step in Excel
Boolean test is guaranteed to be Boolean thanks to type declaration
Boolean test is relative to the first cell (A1) in the evaluate range (A1:A100)
I.e. B1 is replaced with A1.Offset(0,1)
Since boolean_test_on_TL_Cell is not a string, it tells us nothing about the actual test, it just passes the result of the test on the A1, it is actually useless within the UDF so is ignored
To obtain the test string "LEN(A1)<LEN(B1)", the Application.Caller.Formula is read, and the relevant argument of evaluateOverRange is parsed out
In order to evaluate some worksheet function over an array in VBA, you can use the Evaluate method
Dim colA As Range: Set colA = [A1:A100] 'range_to_evaluate_over in my udf
Dim cellA As Range
Dim cellB as Range
Dim outputArray(1 To 100) As Boolean
For i = 1 To 100
Set cellA = colA(i)
Set cellB = cellA.Offset(0,1) 'all cells that arent the TL cell in colA (i.e., not A1) are set relative to the top left cell
outputArray(i) = Evaluate("LEN(" & cellA.Value & ")>LEN(" & cellB.Value ")")
Next i
Right, so all that was for worksheet functions, and somewhat pointless given array functions do the same thing. But now I want to use the same approach within VBA.
Specifically, I want to filter an array of custom classes based on some function of their properties, using actual VBA Boolean returning code rather than a string.
Sub FilterMyClassArray() 'Prints how many items in arrayToFilter whose properties match certain conditions
Dim arrayToFilter(1 To 100) As New myClass
Dim filteredArray() As myClass
Dim tlClass As myClass 'pretend class used only for intellisense and to create
boolean test
Set filteredArray = filterClassArray(arrayToFilter, tlClass.PropertyA > 3 And
tlClass.PropertyB = "hat")
Debug.Print "Number left after filtering:" ; Ubound(filteredArray)
End Sub
Function filterClassArray(ByVal inutArray() As myClass, classTest As Boolean) As myClass 'returns an output array which is equal to the input array filtered by some test
'Somehow get what classTest actually was
'Evaluate classTest over each item in inputArray
'If boolean test evaluates to true, add to output array, otherwise skip
End Function
I imagine some manipulation of the code modules will be required (both to get the string of code which represents the test, and to actually evaluate it), but I want to check feasibility before I dig too deep.
I've been having a think about this and a solution might be possible if you'd be prepared to use something approximating a Linq syntax.
If I understand the requirements correctly, you need to:
obtain a string value for each property name,
record the evaluation and ultimately run it as a string,
have intellisense access to the properties, and
have the ability to debug the evaluation on each iteration.
Regarding #1 and #3, the only way of doing this in VBA would be to code the values manually. If you code them in your class then the class can become cumbersome and some might say it compromises the single responsibility principle (https://en.wikipedia.org/wiki/Single_responsibility_principle). If you code them in a separate 'container' (eg class, type, collection, etc.), then there's a risk of some being missed or of corruption if you change the property names. An Interface class might mitigate these issues.
For #2, I can't see any way around it: the evaluation must be entered as a string. An enum (and associated intellisense) might alleviate things a bit though.
Item #4 is purely a coding architecture issue.
First the syntax
I'm sure there are VBA solutions on the internet which implement a pretty decent mock-up of Linq, but further down is a skeleton version to give you the idea. The end result is that your query syntax could look like this:
Dim query As cLinq
Dim p As INameable
Dim arrayToFilter(1 To 100) As INameable
Dim filteredArray() As INameable
Set query = New cLinq
With query
.SELECT_USING_INTERFACE p
.FROM arrayToFilter
.WHERE p.PropertyA, EQUAL_TO, 3
.AND_WHERE p.PropertyB, EQUAL_TO, "hat"
filteredArray = .EXECUTE
End With
The interface
As far as VBA is concerned an interface is really just a class module with a list of properties and methods that you want a class to implement. In your case, I've created a class and called it INameable, with the following sample code to match your example:
Option Explicit
Public Property Get PropertyA() As Long
End Property
Public Property Let PropertyA(RHS As Long)
End Property
Public Property Get PropertyB() As String
End Property
Public Property Let PropertyB(RHS As String)
End Property
Your MyClass class then implements this interface. For the sake of consistency, I've called the class cMyClass:
Option Explicit
Implements INameable
Private mA As Long
Private mB As String
Private Property Let INameable_PropertyA(RHS As Long)
mA = RHS
End Property
Private Property Get INameable_PropertyA() As Long
INameable_PropertyA = mA
End Property
Private Property Let INameable_PropertyB(RHS As String)
mB = RHS
End Property
Private Property Get INameable_PropertyB() As String
INameable_PropertyB = mB
End Property
I've created a second class, called cNames, which also implements the interface, and this one produces the string names of the properties. As a quick and dirty method it just stores the name of the last property used:
Option Explicit
Implements INameable
Private mName As String
Private Property Let INameable_PropertyA(RHS As Long)
End Property
Private Property Get INameable_PropertyA() As Long
mName = "PropertyA"
End Property
Private Property Let INameable_PropertyB(RHS As String)
End Property
Private Property Get INameable_PropertyB() As String
mName = "PropertyB"
End Property
Public Property Get CurrentName() As String
CurrentName = mName
End Property
You wouldn't have to use an interface and some might argue it's not necessary or even correct to do so, but at least it gives you an idea of how it could be implemented if you went this route.
The Linq class
The final class is really just a helper class to create the intellisense syntax you need and to process the evaluation. It's by no means thorough, but might get you started if the idea appealed to you. I've called this class cLinq:
Option Explicit
'Enumerator to help with intellisense.
Public Enum Operator
EQUAL_TO
GREATER_THAN
LESS_THAN
GREATER_OR_EQUAL_TO
LESS_OR_EQUAL_TO
NOT_EQUAL_TO
End Enum
Private mP As cNames
Private mQueries As Collection
Private mByAnd As Boolean
Private mFromArray As Variant
Public Sub SELECT_USING_INTERFACE(p As INameable)
'Insantiate the name of properties class.
Set mP = New cNames
Set p = mP
End Sub
Public Sub FROM(val As Variant)
'Array containing objects to be interrogated.
mFromArray = val
End Sub
Public Sub WHERE(p As Variant, opr As Operator, val As Variant)
'First query.
Set mQueries = New Collection
AddQuery opr, val
End Sub
Public Sub AND_WHERE(p As Variant, opr As Operator, val As Variant)
'Subsequent query using AND.
mByAnd = True
AddQuery opr, val
End Sub
Public Sub OR_WHERE(p As Variant, opr As Operator, val As Variant)
'Subsequent query using OR.
mByAnd = False
AddQuery opr, val
End Sub
Public Function EXECUTE() As Variant
Dim o As Object
Dim i As Long
Dim result As Boolean
Dim matches As Collection
Dim output() As Object
'Iterate the array of objects to be checked.
Set matches = New Collection
For i = LBound(mFromArray) To UBound(mFromArray)
Set o = mFromArray(i)
result = EvaluatedQueries(o)
If result Then matches.Add o
Next
'Transfer matched objects to an array.
ReDim output(0 To matches.Count - 1)
i = LBound(output)
For Each o In matches
Set output(i) = o
i = i + 1
Next
EXECUTE = output
End Function
Private Function EvaluatedQueries(o As Object) As Boolean
Dim pep As Variant, val As Variant
Dim evalString As String
Dim result As Boolean
For Each pep In mQueries
'Obtain the property value by its string name
val = CallByName(o, pep(0), VbGet)
'Build the evaluation string.
evalString = ValToString(val) & pep(1)
'Run the evaluation
result = Evaluate(evalString)
'Exit the loop if AND or OR conditions are met.
If mQueries.Count > 1 Then
If (mByAnd And Not result) Or (Not mByAnd And result) Then Exit For
End If
Next
EvaluatedQueries = result
End Function
Private Sub AddQuery(opr As Operator, val As Variant)
Dim pep(1) As Variant
'Create a property/evaluation pair and add to collection,
'eg pep(0): "PropertyA", pep(1): " = 3"
pep(0) = mP.CurrentName
pep(1) = OprToString(opr) & ValToString(val)
mQueries.Add pep
End Sub
Private Function OprToString(opr As Operator) As String
'Convert enum values to string operators
Select Case opr
Case EQUAL_TO
OprToString = " = "
Case GREATER_THAN
OprToString = " > "
Case LESS_THAN
OprToString = " < "
Case GREATER_OR_EQUAL_TO
OprToString = " >= "
Case LESS_OR_EQUAL_TO
OprToString = " <= "
Case NOT_EQUAL_TO
OprToString = " <> "
End Select
End Function
Private Function ValToString(val As Variant) As String
Dim result As String
'Add inverted commas if it's a string.
If VarType(val) = vbString Then
result = """" & val & """"
Else
result = CStr(val)
End If
ValToString = result
End Function
When I tried to declare the array as public from the Main Sub() in Sheet1 it's throwing a compile error:
"Arrays not allowed as Public Members of object Modules".
What I am trying to do is declare an array whose elements can be calculated/manipulated from any modules added in VBA project.
Public Segment() As String
Public SegCount As Integer
Sub EDI()
'calls to subroutines placed in modules
End Sub
the usual workaround woud be using a variant.
Public MyArray as Variant
What can go wrong with that ? well a lot , so preferably pass arrays as arguments (to subs byval a() as string , BUT to functions you can only byval a as variant (could be a byref if you get an error))
note : works too (inside a module):
Option Explicit
Public rott() As String 'for a dynamyc array
'Public rott(1 to 10) As String 'for a fixed size array
Sub ff()
ReDim rott(1 To 10) 'or redim preserve rott (1 to 10) if you want to keep values (note you can only redilm the last dimension of the array).
Debug.Print rott(1)
End Sub
note 2 : anything stoping the code would reset the public values....
Insert a standard coding Module - it will become a Module1 then stick Public Segment() As String on top of that module... such as
I'm designing a dynamic buffer for outgoing messages. The data structure takes the form of a queue of nodes that have a Byte Array buffer as a member. Unfortunately in VBA, Arrays cannot be public members of a class.
For example, this is a no-no and will not compile:
'clsTest
Public Buffer() As Byte
You will get the following error: "Constants, fixed-length strings, arrays, user-defined types and Declare statements not allowed as Public members of object modules"
Well, that's fine, I'll just make it a private member with public Property accessors...
'clsTest
Private m_Buffer() As Byte
Public Property Let Buffer(buf() As Byte)
m_Buffer = buf
End Property
Public Property Get Buffer() As Byte()
Buffer = m_Buffer
End Property
...and then a few tests in a module to make sure it works:
'mdlMain
Public Sub Main()
Dim buf() As Byte
ReDim buf(0 To 4)
buf(0) = 1
buf(1) = 2
buf(2) = 3
buf(3) = 4
Dim oBuffer As clsTest
Set oBuffer = New clsTest
'Test #1, the assignment
oBuffer.Buffer = buf 'Success!
'Test #2, get the value of an index in the array
' Debug.Print oBuffer.Buffer(2) 'Fail
Debug.Print oBuffer.Buffer()(2) 'Success! This is from GSerg's comment
'Test #3, change the value of an index in the array and verify that it is actually modified
oBuffer.Buffer()(2) = 27
Debug.Print oBuffer.Buffer()(2) 'Fail, diplays "3" in the immediate window
End Sub
Test #1 works fine, but Test #2 breaks, Buffer is highlighted, and the error message is "Wrong number of arguments or invalid property assignment"
Test #2 now works! GSerg points out that in order to call the Property Get Buffer() correctly and also refer to a specific index in the buffer, TWO sets of parenthesis are necessary: oBuffer.Buffer()(2)
Test #3 fails - the original value of 3 is printed to the Immediate window. GSerg pointed out in his comment that the Public Property Get Buffer() only returns a copy and not the actual class member array, so modifications are lost.
How can this third issue be resolved make the class member array work as expected?
(I should clarify that the general question is "VBA doesn't allow arrays to be public members of classes. How can I get around this to have an array member of a class that behaves as if it was for all practical purposes including: #1 assigning the array, #2 getting values from the array, #3 assigning values in the array and #4 using the array directly in a call to CopyMemory (#3 and #4 are nearly equivalent)?)"
So it turns out I needed a little help from OleAut32.dll, specifically the 'VariantCopy' function. This function faithfully makes an exact copy of one Variant to another, including when it is ByRef!
'clsTest
Private Declare Sub VariantCopy Lib "OleAut32" (pvarDest As Any, pvargSrc As Any)
Private m_Buffer() As Byte
Public Property Let Buffer(buf As Variant)
m_Buffer = buf
End Property
Public Property Get Buffer() As Variant
Buffer = GetByRefVariant(m_Buffer)
End Property
Private Function GetByRefVariant(ByRef var As Variant) As Variant
VariantCopy GetByRefVariant, var
End Function
With this new definition, all the tests pass!
'mdlMain
Public Sub Main()
Dim buf() As Byte
ReDim buf(0 To 4)
buf(0) = 1
buf(1) = 2
buf(2) = 3
buf(3) = 4
Dim oBuffer As clsTest
Set oBuffer = New clsTest
'Test #1, the assignment
oBuffer.Buffer = buf 'Success!
'Test #2, get the value of an index in the array
Debug.Print oBuffer.Buffer()(2) 'Success! This is from GSerg's comment on the question
'Test #3, change the value of an index in the array and verify that it is actually modified
oBuffer.Buffer()(2) = 27
Debug.Print oBuffer.Buffer()(2) 'Success! Diplays "27" in the immediate window
End Sub
#Blackhawk,
I know it is an old post, but thought I'd post it anyway.
Below is a code I used to add an array of points to a class, I used a subclass to define the individual points, it sounds your challenge is similar:
Mainclass tCurve
Private pMaxAmplitude As Double
Private pCurvePoints() As cCurvePoint
Public cDay As Date
Public MaxGrad As Double
Public GradChange As New intCollection
Public TideMax As New intCollection
Public TideMin As New intCollection
Public TideAmplitude As New intCollection
Public TideLow As New intCollection
Public TideHigh As New intCollection
Private Sub Class_Initialize()
ReDim pCurvePoints(1 To 1500)
ReDim curvePoints(1 To 1500) As cCurvePoint
Dim i As Integer
For i = 1 To 1500
Set Me.curvePoint(i) = New cCurvePoint
Next
End Sub
Public Property Get curvePoint(Index As Integer) As cCurvePoint
Set curvePoint = pCurvePoints(Index)
End Property
Public Property Set curvePoint(Index As Integer, Value As cCurvePoint)
Set pCurvePoints(Index) = Value
End Property
subclass cCurvePoint
Option Explicit
Private pSlope As Double
Private pCurvature As Double
Private pY As Variant
Private pdY As Double
Private pRadius As Double
Private pArcLen As Double
Private pChordLen As Double
Public Property Let Slope(Value As Double)
pSlope = Value
End Property
Public Property Get Slope() As Double
Slope = pSlope
End Property
Public Property Let Curvature(Value As Double)
pCurvature = Value
End Property
Public Property Get Curvature() As Double
Curvature = pCurvature
End Property
Public Property Let valY(Value As Double)
pY = Value
End Property
Public Property Get valY() As Double
valY = pY
End Property
Public Property Let Radius(Value As Double)
pRadius = Value
End Property
Public Property Get Radius() As Double
Radius = pRadius
End Property
Public Property Let ArcLen(Value As Double)
pArcLen = Value
End Property
Public Property Get ArcLen() As Double
ArcLen = pArcLen
End Property
Public Property Let ChordLen(Value As Double)
pChordLen = Value
End Property
Public Property Get ChordLen() As Double
ChordLen = pChordLen
End Property
Public Property Let dY(Value As Double)
pdY = Value
End Property
Public Property Get dY() As Double
dY = pdY
End Property
This will create a tCurve with 1500 tCurve.Curvepoints().dY (for example)
The trick is to get the index process correct in the main class !
Good luck !
Not the most elegant solution, but modeling from the code you provided...
In clsTest:
Option Explicit
Dim ArrayStore() As Byte
Public Sub AssignArray(vInput As Variant, Optional lItemNum As Long = -1)
If Not lItemNum = -1 Then
ArrayStore(lItemNum) = vInput
Else
ArrayStore() = vInput
End If
End Sub
Public Function GetArrayValue(lItemNum As Long) As Byte
GetArrayValue = ArrayStore(lItemNum)
End Function
Public Function GetWholeArray() As Byte()
ReDim GetWholeArray(LBound(ArrayStore) To UBound(ArrayStore))
GetWholeArray = ArrayStore
End Function
And in mdlMain:
Sub test()
Dim buf() As Byte
Dim bufnew() As Byte
Dim oBuffer As New clsTest
ReDim buf(0 To 4)
buf(0) = 1
buf(1) = 2
buf(2) = 3
buf(3) = 4
oBuffer.AssignArray vInput:=buf
Debug.Print oBuffer.GetArrayValue(lItemNum:=2)
oBuffer.AssignArray vInput:=27, lItemNum:=2
Debug.Print oBuffer.GetArrayValue(lItemNum:=2)
bufnew() = oBuffer.GetWholeArray
Debug.Print bufnew(0)
Debug.Print bufnew(1)
Debug.Print bufnew(2)
Debug.Print bufnew(3)
End Sub
I added code to pass the class array to another array to prove accessibility.
Even though VBA won't allow us to pass arrays as properties, we can still use Functions to pick up where properties fall short.
If arrays are returned by reference, why doesn't the following work:
'Class1 class module
Private v() As Double
Public Property Get Vec() As Double()
Vec = v()
End Property
Private Sub Class_Initialize()
ReDim v(0 To 3)
End Sub
' end class module
Sub Test1()
Dim c As Class1
Set c = New Class1
Debug.Print c.Vec()(1) ' prints 0 as expected
c.Vec()(1) = 5.6
Debug.Print c.Vec()(1) ' still prints 0
End Sub
You don't have a let property. Also, the get property is returning the entire array, rather than just the element in question. Change the return type of Property Get from Double() to just plain Double. Add Property Let. Note that it takes two inputs, but only one is passed to it. The last variable (MyValue, in this case) is assumed to get it's value from whatever is after the = sign. Put a break point somewhere early in Test1() and see how the values are affected in the Locals window. Compare the variables created by the original code versus my code:
'Class1 class module
Private v() As Double
Public Property Get Vec(index As Long) As Double
Vec = v(index)
End Property
Public Property Let Vec(index As Long, MyValue As Double)
v(index) = MyValue
End Property
Private Sub Class_Initialize()
ReDim v(0 To 3)
End Sub
' end class module
'Begin module
Sub Test1()
Dim c As Class1
Set c = New Class1
Debug.Print c.Vec(1) ' prints 0 as expected
c.Vec(1) = 5.6
Debug.Print c.Vec(1) ' prints 5.6
End Sub
'End module
In VBA, arrays are never returned by reference unless they are returned through a ByRef parameter. Furthermore, whenever you use = to assign an array to a variable, you've made a new copy of the array, even if you're assigning it to a ByRef argument inside of a procedure, so you're pretty much out of luck trying to make this work.
Some alternative are...
Use a VBA.Collection instead of an array.
Make your own class that encapsulates an array and exposes procedures for indirectly accessing and manipulating the internal array.
I want to suggest another nice way to do this using a Collection and a static Property without the need to use a class:
imagine you want to have the xlCVError enum as an array (or collection), e.g. to loop through it on errors and handle it based on the actual error.
The following is initialized once on access:
'from https://stackoverflow.com/a/56646199/1915920
Static Property Get XlCVErrorColl() As Collection
Dim c As Collection 'will be already initalized after 1st access
'because of "Static Property" above!
Set XlCVErrorColl = c
If Not c Is Nothing Then Exit Property
'initialize once:
Set c = New Collection
c.Add XlCVError.xlErrDiv0
c.Add XlCVError.xlErrNA
c.Add XlCVError.xlErrName
c.Add XlCVError.xlErrNull
c.Add XlCVError.xlErrNum
c.Add XlCVError.xlErrRef
c.Add XlCVError.xlErrValue
Set XlCVErrorColl = c
End Property
Turning this into an array or implementing it as an array is straight forward, but collections seem to be more useful to me, with the disadvantage that their elements are not implicitely typed/(compile-time-)type checked.
So this would e.g. turn it into an (read-only) array (with the in-mem-copy-disadvantage mentioned in other answers/comments):
'from https://stackoverflow.com/a/56646199/1915920
Static Property Get XlCVErrorArr() As XlCVError()
Dim a() As XlCVError
XlCVErrorArr = a
If UBound( a ) > 0 Then Exit Property
'initialize once:
Dim c As Collection: Set c = XlCVErrorColl
ReDim a(c.Count)
Dim i As Integer: For i = 1 To c.Count
a(i) = c(i)
Next i
XlCVErrorArr = a
End Function
So transforming the example from Clayton Ss answer into a static, modifiable module property using some array it would be:
'module (no class required)
'from https://stackoverflow.com/a/56646199/1915920
Private v() As Double
Static Property Get Vec(index As Long) As Double
If UBound(v) < 3 Then 'initialize once:
ReDim v(0 To 3) 'one could initialize it with anyting after here too
end if
Vec = v(index)
End Property
Public Property Let Vec(index As Long, MyValue As Double)
v(index) = MyValue
End Property