VB6 - Get accessor not working for array property - arrays

I have two properties in my VB6 code:
Public Property Get PropFileID() As Long
PropFileID = m_FileID
End Property
Public Property Get PropFileIDArray() As Long()
PropFileIDArray = m_FileIDArray
End Property
While debugging, I can see the first property (PropFileID) being assigned a value without error. m_FileID has a value, and after passing through the Get accessor, PropFileID gets the same value.
While debugging the second property (PropFileIDArray), I can see that m_FileIDArray has a valid array value. However, after passing through the Get accessor, PropFileIDArray remains empty.
Am I making some kind of error in the syntax?
Any suggestions would be greatly appreciated

The class code looks ok. Maybe something is wrong in the consuming part? Here is an example that works for me:
'Class1
Private m_FileIDArray(2) As Long
Public Sub SetValues()
m_FileIDArray(0) = 0
m_FileIDArray(1) = 1
m_FileIDArray(2) = 2
End Sub
Public Property Get PropFileIDArray() As Long()
PropFileIDArray = m_FileIDArray
End Property
'Form
Private Sub Form_Load()
Dim class1 As class1
Set class1 = New class1
class1.SetValues
Dim pa As Variant
pa = class1.PropFileIDArray
MsgBox pa(0)
MsgBox pa(1)
MsgBox pa(2)
Set class1 = Nothing
End Sub

Related

Classic ASP assign class object to array

Having a legacy Classic ASP project and need to do the following. I have created a class:
Class MeView
Private m_x
Public Property Get X()
X = m_x
End Property
Public Property Let X(value)
m_x = value
End Property
Private m_y
Public Property Get Y()
Y = m_y
End Property
Public Property Let Y(value)
m_y = value
End Property
End Class
Now I define variables:
Dim Me
Dim MeList()
Dim index : index = 0
Then I loop through a resultset and assign values to the Me Class. After that, I put the Me object in an array.
if not rs.eof then
while not rs.EOF
Redim Preserve MeList(index)
Set Me = New MeView
Me.X = Sanitize(rs("X"))
Me.Y = Sanitize(rs("Y"))
Set MeList(index) = Me
index = index + 1
rs.Movenext
wend
end if
So far so good, if I check the UBound of MeList, it's showing the correct value. The problem arises here:
Dim i
For i = 0 to Ubound(MeList)
Set Me = New MeView
Me = MeList(i)
Response.write Me(i).X
Next
I get the following error
Microsoft VBScript runtime error '800a01a8'
Object required: '[undefined]'
I'm looking at this already for 2 days and just can't see what the issue is.
Any input to point me in the right direction, would be much appreciated.

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

Object Required Error - Why would this be out of scope?

I want to use a global array of objects of my own class (Markers class), which have data loaded in from a recordset. I can load the data from the recordset into the objects in the array just fine it seems, but when I try to access the values in the one of the objects in the array, it gives an "Object Required" error. I don't understand why my Markers() array of Marker class objects is getting destroyed or going out of scope.
Dim Markers(6)
Public Function GetItemSet(ByVal item)
'gets user input and returns a recordset object (just 1 record/row) from a SQL database
'working properly
End Function
Public Sub LoadMarkers(ByVal rs)
For i = 0 to 6
Set Markers(i) = New Marker
Next
MsgBox rs.Fields.Item("TextLine1").Value
Markers(0).TextLine(0) = rs.Fields.Item("TextLine1").Value
Markers(0).TextLine(1) = rs.Fields.Item("TextLine2").Value
'the above is just what I'm using to test functionality, no errors so far
End Sub
Public Function GetMarkerText(ByVal mrkr, ByVal line)
GetMarkerText = Markers(mrkr).TextLine(line)
End Function
In the other script I've tried both using Markers(0).TextLine(0) directly as well as calling GetMarkerText(0,0) to get the value... both methods result in object required error either on the line I directly try to access it or in the one line of code for GetMarkerText. LoadMarkers sub seems to have no issues accessing the Markers() array of Marker class objects, but then it seems to get destroyed after that sub ends? I'm new to VBScript so maybe I just don't quite understand how the scope is working but I can't see why this shouldn't work. Any ideas?
EDIT: Am I just a noob with Classes? Here's the relevant portion of the Markers class definition:
Class Marker
Private m_Name
Private m_TxtLines(6)
Private m_ItemNum
Private m_FontSize
Private m_FontType
Private m_Length
Private Sub Class_Initialize( )
m_Name = "Unnamed"
m_ItemNum = 0
m_Length = 1
For i = 0 To 6
m_TxtLines(i) = ""
Next
m_FontSize = 8
m_FontType = "Arial"
End Sub
'Name Property
Public Property Get Name
Name = m_Name
End Property
Public Property Let Name(marker)
m_Name = marker
End Property
'TextLine Property for holding up to 7 lines of marker text
Public Property Get TextLine(index)
TextLine(index) = m_TxtLines(index)
End Property
Public Property Let TextLine(index, txt)
m_TxtLines(index) = txt
End Property
'ItemNum Property
Public Property Get ItemNum
ItemNum = m_ItemNum
End Property
Public Property Let ItemNum(num)
m_ItemNum = num
End Property
'Length Property
Public Property Get Length
Length = m_Length
End Property
Public Property Let Length(len)
m_Length = len
End Property
'FontSize Property
Public Property Get FontSize
FontSize = m_FontSize
End Property
Public Property Let FontSize(pts)
m_FontSize = pts
End Property
'FontType Property
Public Property Get FontType
FontType = m_FontType
End Property
Public Property Let FontType(font)
m_FontType = font
End Property
'Haven't added my methods in yet
End Class
After staring at the class definition for a while think I may have spotted the culprit.
The assignment in...
Public Property Get TextLine(index)
is not correct. It should just point to...
TextLine = m_TxtLine(index)
not
TextLine(index) = m_TxtLine(index)

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 - Store an array inside of a class - Type mismatch

I've been searching all over but nothing seems to do the trick for me. Here is the problem:
I want to store an array of "keys"
Here is my simple class:
Private pkeys_length As Integer
Private pkeys() As String
Public Property Get keys_length() As Integer
keys_length = pkeys_length
End Property
Public Property Let keys_length(arg As Integer)
pkeys_length = arg
End Property
Public Property Get Keys() As String
Keys = pkeys()
End Property
Public Property Let Keys(ByVal arg As String)
ReDim pkeys(0 To pkeys_length) As String
pkeys = arg
End Property
And here is what I am trying to store:
Dim str_pkeys() As String
Dim pkey_count As Integer
pkey_count = CountPrimaryKeys(stbl)
'Store the keys of that table
ReDim str_pkeys(pkey_count) As String
keyset_1.keys_length = pkey_count
str_pkeys = FindPrimaryKeys(keyset_1.Table)
keyset_1.Keys = str_pkeys
As it stands, it Gives the error Compile Error: Type mismatch
I have had several problems while storing the array, I'm not sure if I am actually getting anywhere. This is the only error I haven't been able to fix. All I need to be able to do is store the array of strings in the class.
Anyone familiar with how to go about this?
I think you need to use String() in your Get property and remove the ByVal in the Let:
Private pkeys_length As Integer
Private pkeys() As String
Public Property Get keys_length() As Integer
keys_length = pkeys_length
End Property
Public Property Let keys_length(arg As Integer)
pkeys_length = arg
End Property
Public Property Get Keys() As String()
Keys = pkeys
End Property
Public Property Let Keys(arg() As String)
ReDim pkeys(0 To pkeys_length) As String
pkeys = arg
End Property
Apart from this a small design suggestion: do you really need a Set for the array length? Why not include this in the set of the array - and only provide the Get instead?

Resources