Constant UDT at Class scope - excel

Is it possible to define a user-defined type constant at module level?
Type MyType
name as String
description as String
End Type
' Something like this
Private Const OneType as MyType = "Name" "Description"

No. Const does not work with user-defined-types. The closest you can come is to create a class that only has property gets.
Public Property Get Name() As String
Name = "Name"
End Property
Public Property Get Description() As String
Description = "Description"
End Property
If you want to have multiple instances of the class with different values but still want the values to be constant, then add an initialize routine that can only be used once.
Private sName As String
Private sDescription As String
Private Sub Class_Initialize()
sName = ""
End Sub
Public Sub Initialize(Name As String, Description As String)
If Len(sName) = 0 Then
sName = Name
sDescription = Description
Else
MsgBox "This instance of MyClass is already initialized!"
End If
End Sub
Public Property Get Name() As String
Name = sName
End Property
Public Property Get Description() As String
Description = sDescription
End Property
then declare an instance of your class.
Dim cMyClass1 As New MyClass, cMyClass2 as New MyClass
cMyClass1.Initialize("Name","Description")
cMyClass2.Initialize("DiffName","OtherDescription")

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.

How to view the values of Class members in the Locals window in VBA

Is it possible to view the members/properties of a Class within the Locals window?
When I try to view the values, the Locals window simply states "Object doesn't support this property or method."
Is there some adjustment I need to make to my Classes in order for the members/properties to be visible in the Locals window?
Here is a basic example of how I am creating a class (using a Class, an Interface and a Factory):
My Class called: "clsMyClass"
Option Explicit
Implements IMyClass
Private Type TMyClass
MyID As Long
MyName As String
End Type
Private This As TMyClass
Private Property Get IMyClass_MyID() As Long
IMyClass_MyID = This.MyID
End Property
Private Property Get IMyClass_MyName() As String
IMyClass_MyName = This.MyName
End Property
Private Function IMyClass_ChangeMyID(NewID As Long) As Long
IMyClass_ChangeMyID = This.MyID
This.MyID = NewID
End Function
Private Function IMyClass_ChangeMyName(NewMyName As String) As String
IMyClass_ChangeMyName = This.MyName
This.MyName = NewMyName
End Function
Public Sub FillData( _
MyID As Long, _
MyName As String _
)
This.MyID = MyID
This.MyName = MyName
End Sub
My Interface: IMyClass
Public Property Get MyID() As Long
End Property
Public Property Get MyName() As String
End Property
Public Function ChangeMyID(NewID As Long) As Long
End Function
Public Function ChangeMyName(NewMyName As String) As String
End Function
My Class Factory: "MyClassFactory"
Option Explicit
Public Function Create( _
MyID As Long, _
MyName As String _
) As IMyClass
Dim newMyClass As clsMyClass
Set newMyClass = New clsMyClass
newMyClass.FillData MyID, MyName
Set Create = newMyClass
End Function
My Test sub:
Public Sub test()
Dim myTestClass As IMyClass
Set myTestClass = MyClassFactory.Create(1, "Some Name")
End Sub
If I put a break point after myTestClass is created, this is what my Locals window displays:
If I Debug.Print myTestClass.MyID it prints the correct value in the Immediate window. But it would be nice to be able to explore my entire Class at a break point without having to manually print all of the values. Is this possible with VBA? If so, what do I need to change?

Excel VBA declaring an arraylist inside of a class

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

Acessing a parent's get property gives "Object variable or with block..." error

A have a series of objects, a complex data structure, a parent, and a child. The child contains an instance of the parent, has Let and Get Properties, and one method. When the method requests one of the ComplexData objects from the parent, I'm given the old RunTime Error 91 - Object Variable or With Variable not set message. The child is packaging up all the ComplexData objects from iteself and its parent. The error is generated when the Parent Property Get TitleField() is called by the child.
These are the classes (in bold):
ComplexData
Private sName As String
Private vValue As Variant
Public Property Let Name(sInput As String)
sName = sInput
End Property
Public Property Get Name() As String
Name = sName
End Property
Public Property Let Value(vInput)
vValue = vInput
End Property
Public Property Get Value()
Value = vValue
End Property
ParentClass
Private oTitle As ComplexData
Private Sub Class_Initialize()
Set oTitle = New ComplexData
oTitle.Name = "title"
End Sub
Public Property Let Title(vInput)
oTitle.Value = "Lorum"
End Property
Public Property Get Title()
Value = oTitle.Value
End Property
Public Property Set TitleField(oInput As ComplexData)
Set oTitle = oInput
End Property
Public Property Get TitleField() As ComplexData
TitleField = oTitle 'GENERATES ERROR
End Property
ChildClass
Private oParent As ParentClass
Private oContentData As ComplexData
Private Sub Class_Initialize()
Set oParent = New ParentClass
Set oContentData = New ComplexData
oContentData.Name = "content"
End Sub
Public Property Let Content(sInput As String)
oContentData.Value = sInput
End Property
Public Property Get Content() As String
Content = oContentData.Value
End Property
Public Function getFields()
getFields = Array(oContentData, oParent.TitleField)
End Function
I can get around this by setting oTitle to Public in the parent class, and requesting the object directly instead of using the Property.
I'm calling this from a spreadsheet using the following:
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Set MyChild = New ChildClass
Fields = MyChild.getFields
field0 = Fields(0).Name
field1 = Fields(1).Name
MsgBox field0 & field1
End Sub
As #Vityata mentioned, adding a “Set” within the Get property fixes the issue.

Resources