Excel VBA declaring an arraylist inside of a class - excel

I'm trying to create a Staff class module, with strings for surname etc, and an arraylist to be used for storing / calling between 0-10 strings dependant on how many are added when used.
The class module is called StaffClass and contains:
Private m_surname As String
Private m_districts as ArrayList
' Surname Prop
Property Get surname() As String
surname = m_surname
End Property
Property Let surname(surname As String)
m_surname = Name
End Property
' District Prop
' This is where i'm getting confused
Private Sub Class_ArrList()
Set m_districts = New ArrayList
End Sub
Property Get districts() As ArrayList
districts = m_districts
End Property
Property Let districts(districts as ArrayList)
m_districts = districts
End Property
The Main Module contains:
Dim newStaff As StaffClass
Set newStaff = New StaffClass
newStaff.surname = "Smith"
' This is where I want to add to the arraylist
newStaff.districts(0) = "50"
I'm aware I'm missing loads, but struggling to find much relating to collections inside classes for VBA.
Hoping you can help!

You can put the arraylist initialization routine in a Class_Initialize, and add a methods to the class to add/insert/etc each item. (Or you could add a method to add the arraylist as a single object).
Also, since ArrayList is an object, you'll need to use the Set keyword when retrieving it.
eg:
Class module
Option Explicit
Private m_surname As String
Private m_districts As ArrayList
' Surname Prop
Property Get surname() As String
surname = m_surname
End Property
Property Let surname(surname As String)
m_surname = surname
End Property
Property Get districts() As ArrayList
Set districts = m_districts
End Property
Function addDistrict(Value As String)
m_districts.Add Value
End Function
Private Sub Class_Initialize()
Set m_districts = New ArrayList
End Sub
Regular Module
Option Explicit
Sub par()
Dim newStaff As StaffClass
Dim V As ArrayList
Set newStaff = New StaffClass
With newStaff
.surname = "Smith"
.addDistrict 50
.addDistrict "xyz"
End With
Set V = newStaff.districts
Stop
End Sub

Related

Managing Collections inside a Class

i'm trying to work with classes in vba and i haven't found any documentation related to managing objects inside objects such as arrays or collections inside a class.
Let's say i have a class person and a class address and i want to manage addresses of a person.
Address
Private pStreet as String
Private pZip as Int
Public Property Let Street(val As String)
pStreet = val
End Property
Public Property Get Street() As String
Street = pStreet
End Property
Public Property Let Zip(val As String)
pZip = val
End Property
Public Property Get Zip() As String
Zip = pZip
End Property
Person
Private pName As String
Private pSurname As String
Private pAddresses As Collection
Public Property Let Name(val As String)
pName = val
End Property
Public Property Get Name() As String
Name = pName
End Property
Public Property Let Surname(val As String)
pSurname = val
End Property
Public Property Get Surname() As String
Surame = pSurname
End Property
Private Sub Class_Initialize()
Set pAddresses = New Collection
End Sub
Private Sub Class_Terminate()
Set pAddresses = Nothing
End Sub
Public Sub addAddress(ByVal val As Address)
pAddresses.Add val
End Sub
Public Property Get Addresses() As Collection
Set Addresses = pAddresses
End Property
Public Property Get Address(ByVal Index As Long) As Address
Set Address = pAddresses(Index)
End Property
Module1
Sub test()
Dim x As Person
Set x = New Person
Dim a1 As Address
Set a1 = New Address
Dim a2 As Address
Set a2 = New Address
x.Name = "Mark"
x.Surname = "Doe"
a1.Street = "first avenue 213"
a1.Zip = "41242"
a2.Street = "second avenue 213"
a2.Zip = "55242"
x.addAddress a1
x.addAddress a2
Debug.Print x.Address(0)
End Sub
how should i deal with the address collection inside the person class?
for instance, how could i retrieve all the addresses or the second address of the collection? x.addresses(1) doesn't work.
The Person class should hold a collection of addresses, to avoid having to expand and preserve an array. The collection should be initialized/terminated as soon as the class is initialized/terminated.
See an example:
Private pAddresses as Collection
Private Sub Class_Initialize()
Set pAddresses = New Collection
End Sub
Private Sub Class_Terminate()
Set pAddresses = Nothing
End Sub
The class can expose the whole address collection via a property for looping, or a single address accessed by index.
Public Property Get Addresses() As Collection
Set Addresses = pAddresses
End property
Public Property Get Address(ByVal Index As Long) As Address
Set Address = pAddresses(Index)
End property
Then you can loop:
Dim p As Person, a As Address
For Each a In p.Addresses
a.Zip = ...
Next
Or get a single address by index:
Set a = p.Addresses(1)
Lastly, a simple Add method, to add an address to the person:
Public Sub AddAddress(ByVal param As Address)
pAddresses.Add param
End Sub
You could also add (or replace) an address using a property by supplying both the address object and the index, but I dont know how useful it is in your case. Of course you need to make sure Index is valid.
Public Property Let Address(ByVal Index As Long, ByVal param As Address)
pAddresses.Add param, Index
End property
Then to call it:
p.Addresses(Index) = a
To enforce the Set keyword, since we're dealing with objects, change Let to Set. Then, you need to set-it:
Set p.Addresses(Index) = a

Excel vba - class constructor method not working, debug error

i have this class and i am trying to make a class constructor or factory method (not sure how's the right name in VBA). When i try to run it i get a dialog with written debug error, and ig highlights the set row of the test module. What's wrong? What is the right way to instantiate the collection in the constructor? is it better to use the keyword this when using let/get ?
Class Address
Private pStreet As String
Private pZip As Integer
Public Property Let Street(val As String)
pStreet = val
End Property
Public Property Get Street() As String
Street = pStreet
End Property
Public Property Let Zip(val As Integer)
pZip = val
End Property
Public Property Get Zip() As Integer
Zip = pZip
End Property
Class Person
Private pName As String
Private pSurname As String
Private pAddresses As New Collection
Public Property Let Name(val As String)
pName = val
End Property
Public Property Get Name() As String
Name = pName
End Property
Public Property Let Surname(val As String)
pSurname = val
End Property
Public Property Get Surname() As String
Surame = pSurname
End Property
Private Sub Class_Initialize()
Set pAddresses = New Collection
End Sub
Private Sub Class_Terminate()
Set pAddresses = Nothing
End Sub
Public Sub addAddress(ByVal val As Address)
pAddresses.Add val
End Sub
Public Property Get Addresses() As Collection
Set Addresses = pAddresses
End Property
Public Property Get Address(ByVal Index As Long) As Address
Set Address = pAddresses(Index)
End Property
Public Function CreatePerson(ByVal Name As String, ByVal Surname As String) As Person
With New Person
.pName = Name
.pSurname = Surname
Set CreatePerson = .Self
instance
End With
End Function
test Module
sub test()
Dim x as Person
Set x = Person.CreatePerson("Mike","Jordan")
end sub
Another option for creating a factory method is to use another class:
PersonFactory Class
Option Explicit
Public Function Create(ByVal Name As String, ByVal Surname As String, ByVal Street As String, ByVal Zip As Integer) As Person
Dim a As Address
Set Create = New Person
Create.Name = Name
Create.Surname = Surname
Set a = New Address
a.Street = Street
a.Zip = Zip
Create.Addresses.Add a
End Function
Test Module
Private Sub Test()
Dim pf As PersonFactory
Dim p As Person
Set pf = New PersonFactory
Set p = pf.Create("Mike", "Jordan", "my street", 11111)
End Sub
You have several errors
Your anonymous new is for GiantCorp and Not Person
You have no self method to return the Me Instance created by the With New Person
3 No idea what 'instance' is doing.
Your address class does not manage a collection of addresses, nor does you person class
Here is updated code for your person class. Don't feel too bad, Factory classes in VBA are actuall a tricky subject when you first encounter them.
Option Explicit
'#PredecalredId
'#exposed
Private Type Properties
Name As String
Surname As String
Address As Address
End Type
Private p As Properties
Public Property Let Name(ipName As String)
p.Name = ipName
End Property
Public Property Get Name() As String
Name = p.Name
End Property
Public Property Let Surname(ipSurname As String)
p.Surname = ipSurname
End Property
Public Property Get Surname() As String
Surame = p.Surname
End Property
' This property will fail as the Address class is not a collection
Public Sub addAddress(ipAddress As Address)
Set p.Address = ipAddress
End Sub
Public Function CreatePerson(ByVal ipName As String, ByVal ipSurname As String) As Person
With New Person 'GiantComp no idea what this GiantComp' class is doing here
' Private fields cannot be accessed here, you need to forward them to the self function
'.pName = ipName
'.pSurname = ipSurname
Set CreatePerson = .Self(ipName, ipSurname)
End With
End Function
Public Function Self(ByVal ipName As String, ByVal ipSurname As String) As Person
' You are now inside the anonymous Person class you created with 'With nEw Person' so you can now access private fields
p.Name = ipName
p.Surname = ipSurname
Set Self = Me
End Function
You will also need to set the PredeclaredId attribute. This involves either exporting you class, editing the relevant attribute and reimporting, or, much more conveniently, using the attribute annotation '#PredecaredId provided by the free and fantastic Rubberduck add in for VBA.
Good luck in creating an addresses collection class to manage you addresses. Lots of examples are available of how to wrap a collection to produce a collection class.

Properly coding a constructor

I am trying to implement a Model-View-Presenter Userinterface in VBA excel. In order to do this I have been writing different Model classes. Here an Example:
Option Explicit
Private Type TModel
FilterCol As Collection
N As Integer
End Type
Private this As TModel
Public Property Get FilterCol() As Collection
Set FilterCol = this.FilterCol
End Property
Public Property Let FilterCol(ByVal value As Collection)
Set this.FilterCol = value
End Property
Public Property Get N() As Integer
Set N = this.N
End Property
Public Property Let N(ByVal value As Integer)
Set this.N = value
End Property
This class called "FilterModel" is a collection of MSFormObjects. In order to use the collection properly I need to new it. So the code where I use it would look a little like this:
Sub testFilter()
Dim Filterm As FilterModel
Dim DefaultFilterLine As New FilterLine
Set Filterm = New FilterModel
Filterm.FilterCol = New Collection
'Set DefaultFilter
Filterm.FilterCol.Add DefaultFilterLine
'DoStuff
With New frmFilter
Set .Model = Filterm
.Show
End With
End Sub
If I don't new the Property FilterCol before I add something, in this case the defaultfilter, it doesn't work. So here is my Question:
Is there a way to overwrite the new statement for my new class in order to have it also new up the collection FilterCol. My research got me as far as I now know that this would be called a constructor.
But how would one properly implement a constructor for a VBA class?
Somthing like:
Private Sub Class_Initialize()
Set this.FilterCol = New Collection
N = 0
End Sub
If I do this then I get an error in the "Property Let N(Byval Value as integer)" Line. The error message reads "object required".
Here is a working solution. I suggest going through the code line-by-line using F8 to understand what is happening there. Debug.print prints values into the Immediate window.
Here is the FilterModel class:
''' FilterModel class
Option Explicit
Private pFilterCol As Collection
Private pN As Integer
Public Property Get FilterCol() As Collection
Set FilterCol = pFilterCol
End Property
Public Property Let FilterCol(ByVal value As Collection)
Set pFilterCol = value
End Property
Public Property Get N() As Integer
N = pN
End Property
Public Property Let N(ByVal value As Integer)
pN = value
End Property
Private Sub Class_Initialize()
Set pFilterCol = New Collection
pN = 0
End Sub
and here is module code to test it:
''' random module
Option Explicit
Sub testFilter()
Dim Filterm As FilterModel
Set Filterm = New FilterModel
Filterm.FilterCol = New Collection
''' default values (specified in Class_Initialize())
Debug.Print Filterm.N
Debug.Print Filterm.FilterCol.Count
''' set the values through Property Let
Filterm.FilterCol.Add "whatever"
Filterm.FilterCol.Add "whenever"
Filterm.N = 6
''' print the new values (through Property Get)
Debug.Print Filterm.N
Debug.Print Filterm.FilterCol.Count
Debug.Print Filterm.FilterCol(1)
Debug.Print Filterm.FilterCol(2)
End Sub

Excel VBA - creating a collection class within a parent class

I've been trying to create a class that is a collection of objects (of another class) and that is within a parent class. I've looked at several questions here but couldn't get it working. So if anyone can post a short code with my parameters, I'd be very grateful.
My parent class is Sample. It should contain a collection SampleFields which should contain objects from the class SampleField. The SampleField objects have only a Name property and it is taken from cells A1 to D1. It should be possible to add and remove items from the SampleFields collection and modify the Name property of the SampleField objects. The SampleFields collection gets its objects upon the initialization of the Sample class.
I need to access it like this - Sample.SampleFields(1).Name
I think it's useless to post my attempt but here it is:
Sub test()
Dim a As New Sample, i As Variant
a.GetFields
For Each i In a.SampleFields
Debug.Print i.Name
Next
End Sub
Sample class:
Private pFields As New SampleFields
Public Property Get SampleFields() As SampleFields
Set SampleFields= pFields
End Property
Public Property Set SampleFields(ByVal value As SampleFields)
Set pFields = value
End Property
Private Sub Initialize_Class()
Set pFields = New SampleFields
End Sub
Public Sub GetFields()
Dim rngHeaders As Range, rngCell As Range
Set rngHeaders = Range("A1").CurrentRegion.Rows(1)
For Each rngCell In rngHeaders.Cells
Dim newField As SampleField
newField.Name = rngCell.Value2
Me.Fields.AddNewField (newField) 'crashes here with Method or data member not found
Next
End Sub
SampleFields class:
Private pFields As New Collection
Public Sub AddNewField(FieldName As SampleField)
Me.AddNewField (FieldName)
End Sub
SampleField class:
Private pName As String
Public Property Let Name(value As String)
pName = value
End Property
Public Property Get Name() As String
Name = pName
End Property
Thanks!
Very old post, but let me at least answer this:
In the sample class, have a Collection. You can forget about the SampleFields class, it's not needed.
Then you only need to have one SampleField class that you pass to this SampleClass method "AddField" that you use to increase the size of the collection.
Sample class should look like this:
Private p_SampleFields as Collection
Private p_SampleField as SampleField
'Initialize this class with it's collection:
Private Sub Class_Initialize()
Set p_SampleFields = New Collection
End Sub
'Allow for adding SampleFields:
Public Sub AddField(field as SampleField)
Set p_SampleField = field
p_sampleFields.add field
End Sub
'Expose the collection:
Public Property Get SampleFields() as Collection
Set SampleFields = p_SampleFields
End Property
In a regular module you can then use the following:
Sub Test()
Dim sField as SampleField
Dim sClass as SampleClass
Set sField = New SampleField
Set sClass = New SampleClass
sField.Name = "SomeName"
sClass.AddField sField 'This adds it to the collection
'Access as per requirement:
msgbox sClass.SampleFields(1).Name 'Pop-up saying "SomeName"
End Sub
With a little change in Rik's answer, we can use a public collection, eliminating the need of AddField and Get methods:
Class SampleClass:
Public SampleFields As Collection
Private Sub Class_Initialize()
Set SampleFields = New Collection
End Sub
Then to use it in your module:
Sub Test()
Dim sField as AnyOtherClass
Dim sClass as SampleClass
Set sField = New AnyOtherClass
Set sClass = New SampleClass
sField.Name = "SomeName"
sClass.SampleFields.add sField 'This adds it to the collection
'Access as per requirement:
msgbox sClass.SampleFields(1).Name 'Pop-up saying "SomeName"
End Sub

VBA Class() object as property of another class

I'm trying to create a class to hold a variable number of items (which are themselves another class object).
So, I have Class 2:
' Class 2 contain each individual quote elements (OTC and MRC)
Private pOTC As String
Private pMRC As String
Public Property Get OTC() As String
OTC = pOTC
End Property
Public Property Let OTC(Value As String)
pOTC = Value
End Property
Public Property Get MRC() As String
MRC = pMRC
End Property
Public Property Let MRC(Value As String)
pMRC = Value
End Property
Then Class 1 contains an array of Class 2:
Private pCurr As String
Private pQuote(20) As Class2
Public Property Get Curr() As String
Curr = pCurr
End Property
Public Property Let Curr(Value As String)
pCurr = Value
End Property
Public Property Set Quote(Index As Integer, cQuote As Class2)
Set pQuote(Index) = cQuote
End Property
Public Property Get Quote(Index As Integer) As Class2
Quote = pQuote(Index)
End Property
And what I would like to do is something like:
Dim myQuotes As Class1
Set myQuotes = New Class1
myQuotes.Curr = "GBP"
myQuotes.Quote(3).OTC = "1200"
The first line setting myQuotes.Curr is no problem, however when I try to set a value inside the array the next line errors with Run-time 91 Object variable or With block variable not set
Any pointers as to what I'm doing wrong and how can I set the values for the elements within the class array?
Thanks in advance!
When you myQuotes.Quote(3) you call Property Get Quote which has an issue.
Your internal array of Class2 is not instantiated so pQuote(Index) refers to an array element of Nothing, when you then myQuotes.Quote(3).OTC = you try to assign to Nothing which fails.
You need to make sure pQuote(Index) is instanced; you can do this on demand:
Public Property Get Quote(Index As Integer) As Class2
If (pQuote(Index) Is Nothing) Then Set pQuote(Index) = New Class2
Set Quote = pQuote(Index)
End Property
(Note the required Set)
Or by adding an intitialisation routine to Class1:
Private Sub Class_Initialize()
Dim Index As Long
For Index = 0 To UBound(pQuote)
Set pQuote(Index) = New Class2
Next
End Sub
You need to set them as New Class2 in Class1:
For intI = LBOUND(pQuote) to UBOUND(pQuote)
Set pQuote(intI) = New Class2
Next IntI
Just as you do with Class1 in your final script.
Maybe it should be
Public Property Let Quote(Index As Integer, cQuote As Class2)
Set pQuote(Index) = cQuote
End Property

Resources