Resizing an array inside of an object in VBScript - arrays

In VB script, I'm trying to resize an array inside of an object (named itemList()). ReDim works for normal arrays, but I get an error when resizing an array inside of an object. I'm trying to imitate a struct, so my goal is to have some type of object/struct that has a dynamic array inside..
Class Person
Public name
Dim itemList()
End Class
Set person1 = new Person
person1.itemList(0) = "football" 'Works fine
ReDim person1.itemList(7) 'Error: "Expected "("

You cannot resize member variables of an object that way. A better approach to handling a list of items is to initialize the member variable as an empty array and append to it:
Class Person
Public name
Public itemList
Private Sub Class_Initialize()
itemList = Array()
End Sub
Public Sub Take(item)
ReDim Preserve itemList(UBound(itemList)+1)
itemList(UBound(itemList)) = item
End Sub
End Class
Set person1 = new Person
person1.Take "football"

Related

Initializing Object with Arrays VBA

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."

VBA Dynamic array : change size and use of Get Let Property

The problem: I would like to create a dynamic array that change size when an event occurs (Event Calculate), by preserving its content and extending by one its size. Let's say I want to do this for a vector of double (and also a date type). The data is updating in a specific cell.
My understanding: I am coding in the Event "Calculate of Excel". I cannot use "Public", I have to declare the array as private and then use Get and Let Property... But, how can I use Redim Preserve in this case ? Also, I think I am probably missing some points on how it has to be used: Here s a sample of the code I wrote:
Name of the Classe: "Class1"
Code of the class:
Code:
Option Explicit
Private IntradayValueSerie1() As Double 'Dynamic vector which will contain Y1 Values
Public Counter As Long
Public Property Let vIntradayValueSerie1(ByVal Counter_Value As Long)
IntradayValueSerie1(Counter_Value) = Sheets("Sheet1").Range("C5")
End Property
Public Property Get vIntradayValueSerie1(ByVal Counter_Value As Long) As Variant
vIntradayValueSerie1(Counterr) = IntradayValueSerie1
End Property
So I want "Let" To attribute the new value of my extended array
I want "Get" to return the array (extended and updated)
Remark: Counter will be updated and increase at then end of the in the section Event "Worksheet.Calculate"
Test code (HAS TO BE in the section Event "Worksheet.Calculate")
Code:
counter = 1
Dim Serie1 As New Class1
Serie1.IntradayValueSerie1(Counter) = ??? I don't know how to use the property to initialize the vector
counter = counter +1
ReDim Preserve IntradayValueSerie1(Counter)
Also, As I want to return an array, do I have to set Variant for the Get property ? As you can see, some points make me confused, either on the use and the structure.
Thank you for you time !
edited to match the following assumptions
a Sub in any of your modules initializes and uses a variable of Class1 type
I'll call it after Sub ExploitClass1(), but you can rename it as you like
that Sub writes into any cell of the relavant worksheet whose calculate event you want to use to update the dynamic array property of your variable of Class1 type
I'll assume that relevant worksheet is named after "CalculateClass": you can rename it as you like but be sure to fill its code pane with what you'll find in "your relevant worksheet code pane" section of this answer
then proceed like follows:
your Class1 code pane
Option Explicit
Private IntradayValueSerie1() As Double 'Dynamic array which will contain Y1 Values
Private counter As Long '<-- counter to track the size of the dynamic array
Public Sub WriteValue(ByVal Value As Variant) '<-- class method to write a value in the last dynamic array slot
IntradayValueSerie1(counter) = Value
Extend
End Sub
Private Sub Extend() '<-- class method to extend dynamic array size by one
counter = counter + 1 '<-- update the dynamic array size counter by one
ReDim Preserve IntradayValueSerie1(1 To counter) '<-- increase the dynamic array size
End Sub
Private Sub Class_Initialize()
counter = 1
ReDim IntradayValueSerie1(1 To counter) '<-- at class instantiating, initialize the dynamic array
End Sub
'-----------------------------------------------
' added methods to "query" some dynamic array related values
'-----------------------------------------------
Public Function GetCounter() As Long '<-- class method to retrive the current counter (i.e. the dynamic array size) value
GetCounter = counter '<-- return counter
End Function
Public Function GetPenultimateArrayValue() As Variant '<-- class method to retrive the current counter (i.e. the dynamic array size) value
GetPenultimateArrayValue = IntradayValueSerie1(counter - 1) '<-- return dynamic array one before second to last element
End Function
your Sub
Option Explicit
Public Serie1 As Class1 '<-- declare a Public variable of type Class1
Sub ExploitClass1()
Set Serie1 = New Class1 '<-- instantiate a new public object of type Class1
Worksheets("CalculateClass").Range("A1") = 1 ' make something that triggers calulate event in the relevant worksheet: in this case I had cell "A2" of that worksheet with a formula `= A1+1`
MsgBox Serie1.GetPenultimateArrayValue & " - " & Serie1.GetCounter 'show your Class1 dynamic array has been updated exploiting those "querying" methods we added at the bottom of your class
End Sub
your relevant worksheet code pane
it'll use the Public object of Class1 type we declared and initialized in ExploitClass1() sub
Option Explicit
Private Sub Worksheet_Calculate()
Serie1.WriteValue Worksheets("Sheet01").Range("C5").Value '<-- this will write the passed value to your class dynamic array last slot
End Sub

I am getting error when accessing variable on another module?

I have a module that is getting values from a datagrid and puts in the tags from each rows into the array of strings. I'm calling that array string on another module but i'm getting object not set to instance of an object. Why? What i'm trying to accomplish is combine all the tag into a array of strings or collection and be able to access it on another module.
'my main module
Public Class myMainModule
Public Shared myArray() As String
......
.......
Public sub doSomething()
Dim myArray As New List(Of String)
For Each row As DataGridViewRow In mydatagrid.Rows
If row.Cells("mycheckbox").Value = True Then
myArray.Add(row.Tag)
End If
Next
End Sub
End Class
'....then i'm calling it from another module:
Public Class myOtherModule
Public sub doit()
For Each value As String In myMainModule.myArray
Debug.Print(value)
Next
End Sub
End Class
You need to initialize your Array before you try to call it. Currently it is Nothing.
Public Class MyMainModule
Public Shared MyArray() As String
Public Shared Sub DoSomething()
Dim myList As New List(Of String)
For Each row As DataGridViewRow In mydatagrid.Rows
If row.Cells("mycheckbox").Value = True Then
myList.Add(row.Tag)
End If
Next
MyArray = myList.ToArray()
End Sub
End Class
Public Class MyOtherModule
Public Sub Foo()
MyMainModule.DoSomething()
For Each value As String In MyMainModule.MyArray
Debug.Print(value)
Next
End Sub
End Class
The other thing too is that you need to watch out for naming. I believe you got confused because you had a field called myArray, but also had a local variabled called myArray. You were working with the local variable that you newed up as List(Of T), not an array.

Array as a Class Member

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.

VBA: Arrays and Global Variable Declarations

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

Resources