When trying to use a class in VBA I keep getting a subscript out of range error and could use some help here!
This is the class -
Option Explicit
Private buildWs As String
Public Property Get affBuild() As String
affBuild = buildWs
End Property
Public Property Let affBuild(value As String)
buildWs = affBuild
End Property
Public Function activate()
Sheets(buildWs).activate
End Function
This is the call -
Sub SetWs()
Dim current As CBuildSheet
Set current = New CBuildSheet
current.affBuild = "Resource Entry"
current.activate
End Sub
Your Let method should be using the value parameter:
Public Property Let affBuild(value As String)
buildWs = value
End Property
Your Public Function activate() is not returning any value.
Perhaps, you should use Public Sub activate().
this is wrong...
Public Property Let affBuild(value As String)
buildWs = affBuild
End Property
and should be...
Public Property Let affBuild(value As String)
buildWs = value
End Property
see . http://ramblings.mcpher.com/Home/excelquirks/snippets/classes for getting started with classes.
Bruce
Related
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.
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?
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.
Question: Why do I get nothing but 0 out of this code? (via the MsgBox)
Context: Newbie trying to learn to use my own classes in Excel VBA.
Class1 is a class Module which has the code here:
Private pPhone As Integer
Public Property Get Phone() As Integer
Phone = pPhone
End Property
Public Property Let Phone(Value As Integer)
pPhone = Phone
End Property
And Test() is a sub in Module1 as you see here
Dim Home As Class1
Public Sub Test()
Set Home = New Class1
Home.Phone = 3
MsgBox Home.Phone
End Sub
The code executes, but Home.Phone is only reported as 0 (I guess the initial value) What am I doing wrong?
Change pPhone = Phone to pPhone = Value
It should be
Public Property Let Phone(Value As Integer)
pPhone = Value
End Property
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