Can You Add a Class to a Class Property VBA - excel

So I have two Class modules, ClassA and ClassB, and a sub in a module in which I try to add an object from Class B to a property of an object in ClassA.
According to the VBE Glossary, classes are objects and the property set statement can add object types, so it seems like this should be possible. However, when I run the test sub I get an "object variable or with block variable not set" error
The Sub:
Sub test()
Dim Class_A_Object As ClassA
Dim Class_B_Object As ClassB
Set Class_A_Object = New ClassA
Set Class_B_Object = New ClassB
Class_B_Object.Class_B_Property = 42
Class_A_Object.Class_A_Property = Class_B_Object
End Sub
ClassA:
Private a_Class_A_Property As Object
Public Property Set Class_A_Property(pClass_A_Property As Object)
a_ClassA_Property = pClass_A_Property
End Property
Public Property Get Class_A_Property() As Object
Class_A_Property = a_Class_A_Property
End Property
Class B:
Private b_Class_B_Property As Integer
Public Property Let Class_B_Property(pClass_B_Property As Integer)
b_Class_B_Property = pClass_B_Property
End Property
Public Property Get Class_B_Property() As Integer
Class_B_Property = b_Class_B_Property
End Property

Here is the code after I made changes necessary for it to be able to add a class object to my property. As well as making the change pinkfloydx33 suggested, I added the "Set" keyword to the statements of the properties intending to hold the objects and changed data type of said properties to the name of the class to be held.
'The Sub:
Sub test()
Dim Class_A_Object As ClassA
Dim Class_B_Object As ClassB
Set Class_A_Object = New ClassA
Set Class_B_Object = New ClassB
Class_B_Object.Class_B_Property = 42
Set Class_A_Object.Class_A_Property = Class_B_Object
End Sub
'ClassA:
Private a_Class_A_Property As Class B
Public Property Set Class_A_Property(pClass_A_Property As ClassB)
Set a_ClassA_Property = pClass_A_Property
End Property
Public Property Get Class_A_Property() As ClassB
Set Class_A_Property = a_Class_A_Property
End Property
'Class B:
Private b_Class_B_Property As Integer
Public Property Let Class_B_Property(pClass_B_Property As Integer)
b_Class_B_Property = pClass_B_Property
End Property
Public Property Get Class_B_Property() As Integer
Class_B_Property = b_Class_B_Property
End Property

Related

How to incorporate Excel VBA class collection into interface/factory method?

I've been using class modules for almost a year, and I'm just now comfortable with them. Now I'm trying to incorporate factory methods into data extraction from workbook tables. I found some great guides on the topic here, here, and here, but I'm unsure where to incorporate a collection of the class.
Up until now, I've setup my class modules with self-contained collections in this format:
Class module OrigClass
Option Explicit
'Col position references for input table, only includes cols with relevant data
Private Enum icrColRef
icrName = 2
icrCost = 4
End Enum
'UDT mirrors class properties
Private Type TTestClass
Name As String
Cost As Long
End Type
Const WS_NAME As String = "Sheet1"
Const NR_TBL As String = "Table1"
Private msTestClass As Collection
Private TestClass As TTestClass
Private Sub Class_Initialize()
Set msTestClass = New Collection
End Sub
Public Sub Add(Item As OrigClass)
msTestClass.Add _
Item:=Item, _
Key:=Item.Name
End Sub
Public Function Extract() As OrigClass
Dim tblInputs As ListObject
Dim i As Integer
Dim Item As OrigClass
Set tblInputs = ThisWorkbook.Worksheets(WS_NAME).ListObjects(NR_TBL)
For i = 1 To tblInputs.DataBodyRange.Rows.Count
Set Item = New OrigClass
With Item
.Name = tblInputs.DataBodyRange(i, icrName).Value
.Cost = tblInputs.DataBodyRange(i, icrCost).Value
End With
msTestClass.Add Item
Next i
End Function
Public Function Item(i As Variant) As OrigClass
Set Item = msTestClass.Item(i)
End Function
Public Function Count() As Integer
Count = msTestClass.Count
End Function
Friend Property Let Name(Val As String)
TestClass.Name = Val
End Property
Public Property Get Name() As String
Name = TestClass.Name
End Property
Friend Property Let Cost(Val As Long)
TestClass.Cost = Val
End Property
Public Property Get Cost() As Long
Cost = TestClass.Cost
End Property
This structure works well when I build functions that pass a ranges/table, loop through the rows, and assign a column value to each property. The address is almost always constant and only the values and record count will vary.
I just started building an interface for a class while also trying to retain the collection component, but I'm stumbling on runtime errors... I could possibly create a separate collection class, but I think my problem is more about mismanaging scope rather than encapsulation:
Class module CTestClass
Option Explicit
'Col position references for input table, only includes cols with relevant data
Private Enum icrColRef
icrName = 2
icrCost = 4
End Enum
''UDT mirrors class properties
Private Type TTestClass
Name As String
Cost As Long
End Type
Const WS_NAME As String = "Sheet1"
Const NR_TBL As String = "Table1"
Private msTestClass As Collection
Private TestClass As TTestClass
Implements ITestClass
Implements FTestClass
Private Sub Class_Initialize()
Set msTestClass = New Collection
End Sub
Public Sub Add(Item As CTestClass)
msTestClass.Add _
Item:=Item, _
Key:=Item.Name
End Sub
Public Function Create() As ITestClass
With New CTestClass
.Extract
' 2) now in Locals window, Me.msTestClass is <No Variables>
Set Create = .Self
' 4) Me.msTestClass is again <No Variables>, and
' Create (as Type ITextClass) is Nothing
' Create (as Type ITextClass/ITextClass) lists property values as
' <Object doesn't support this property or method>, aka runtime error 438
End With
End Function
Private Function FTestClass_Create() As ITestClass
Set FTestClass_Create = Create
End Function
Public Function Extract() As ITestClass
Dim tblInputs As ListObject
Dim i As Integer
Dim Item As CTestClass
Set tblInputs = ThisWorkbook.Worksheets(WS_NAME).ListObjects(NR_TBL)
For i = 1 To tblInputs.DataBodyRange.Rows.Count
Set Item = New CTestClass
With Item
.Name = tblInputs.DataBodyRange(i, icrName).Value
.Cost = tblInputs.DataBodyRange(i, icrCost).Value
End With
msTestClass.Add Item
Next i
' 1) in Locals window, Me.msTestClass is populated with all table records
End Function
Public Function ITestClass_Item(i As Variant) As ITestClass
Set ITestClass_Item = msTestClass.Item(i)
End Function
Public Function ITestClass_Count() As Integer
ITestClass_Count = msTestClass.Count
End Function
Friend Property Let Name(Val As String)
TestClass.Name = Val
End Property
Public Property Get Name() As String
Name = TestClass.Name
End Property
Friend Property Let Cost(Val As Long)
TestClass.Cost = Val
End Property
Public Property Get Cost() As Long
Cost = TestClass.Cost
End Property
Public Property Get Self() As ITestClass
Set Self = Me
' 3) Me.msTestClass is again populated with all table records (scope shift?), but
' Self is set to Nothing
End Property
Private Property Get ITestClass_Name() As String
ITestClass_Name = Name
End Property
Private Property Get ITestClass_Cost() As Long
ITestClass_Cost = Cost
End Property
Interface module ITestClass
'Attribute VB_PredeclaredId = False <-- revised in text editor
Option Explicit
Public Function Item(i As Variant) As ITestClass
End Function
Public Function Count() As Integer
End Function
Public Property Get Name() As String
End Property
Public Property Get Cost() As Long
End Property
Factory module FTestClass
'Attribute VB_PredeclaredId = False <-- revised in text editor
Option Explicit
Public Function Create() As ITestClass
End Function
Standard module
Sub TestFactory()
Dim i As ITestClass
Dim oTest As FTestClass
Set oTest = CTestClass.Create
' 5) oTest is <No Variables>, no properties are present
' as if the variable was never set
For Each i In oTest ' <-- Runtime error 438, Object doesn't support this property or method
Debug.Print
Debug.Print i.Name
Debug.Print i.Cost
Next i
End Sub
What am I doing wrong here?
EDIT:
#freeflow pointed out that I didn't state my intentions for introducing an interface.
My office uses several workbook "models" to compile pricing data into a single output table that is then delivered to a downstream customer for importing into a database.
My goal is to standardize the calculations using these various models. The side goal is to understand how to properly implement a factory method.
Each model has one or more input tables, and each table contains a unique collection of 10-30 fields/columns. The output data calculations vary, along with the dependencies on various input fields. However, the output data is the same format all across the board and always contains the same dozen fields.
The example I've shown is intended to be a single interface ITestClass for writing data to the output table. The class that implements it CTestClass can be considered as just one of the several tables (within the several models) containing the input data. I plan on modeling more class objects, one for each input table.
Based on:
Sub TestFactory()
Dim i As ITestClass
Dim oTest As FTestClass
Set oTest = CTestClass.Create
' 5) oTest is <No Variables>, no properties are present
' as if the variable was never set
For Each i In oTest ' <-- Runtime error 438, Object doesn't support this property or method
Debug.Print
Debug.Print i.Name
Debug.Print i.Cost
Next i
End Sub
It would appear that you are interested in making your class iterable like a collection. I would point you towards this SO question. The short of it is...it's difficult.
WIth regard to the error: The result of statement Set oTest = CTestClass.Create is the acquisition of a FTestClass interface that exposes a single method: Public Function Create() As ITestClass. Which, provides nothing to iterate on and results in an error.
Other Observations:
In the code as provided, there is no need to declare a factory interface.
(Sidebar: Interface classes typically begin with the letter "I". In this case, a better interface name for FTestClass would be "ITestClassFactory")
Since CTestClass has its VB_PredeclaredId attribute set to 'True', any Public method (or field) declared in CTestClass is exposed...and is considered its default interface. CTestClass.Create() is the Factory method you are interested in.
One purpose of creating a Factory method (in VBA) is to support the parameterized creation of a class instance. Since the Create function currently has no parameters, it is unclear what else could be going on during creation other than Set tClass = new CTestClass. But, there are parameters that would indicate what is going on during Create.
Public Function Create(ByVal tblInputs As ListObject, OPtional ByVal nameColumn As Long = 2, Optional ByVal costColumn As Long = 4) As ITestClass
In other words, CTestClass has a dependency on a ListObject in order to become a valid instance of a CTestClass. A factory method's signature typically contains dependencies of the class. With the above factory method, there is no longer a need to have an Extract function - Public or otherwise. Notice also (in the code below) that the ThisWorkbook reference is no longer part of the object. Now, the tblInputs ListObject can be from anywhere. And the important column numbers can be easily modified. This parameter list allows you to test this class using worksheets with fake data.
Reorganizing:
CTestClass contains a Collection of CTestClass instances. It would seem clearer to declare a TestClassContainer class that exposes the Create function above. The container class can then expose a NameCostPairs property which simply exposes the msTestClass Collection. Creating a container class reduces the TestClass to essentially a data object (all Properties, no methods) which results in a useful separation of concerns. Let the calling objects handle the iteration of the collection.
TestClassContainer
Option Explicit
Private Type TTestClassContainer
msTestClass As Collection
End Type
Private this As TTestClassContainer
'TestContainer Factory method
Public Function Create(ByVal tblInputs As ListObject, Optional ByVal nameCol As Long = 2, Optional ByVal costCol As Long = 4) As TestClassContainer
Dim i As Integer
Dim nameCostPair As CTestClass
Dim newInstance As TestClassContainer
With New TestClassContainer
Set newInstance = .Self
For i = 1 To tblInputs.DataBodyRange.Rows.Count
Set nameCostPair = New CTestClass
nameCostPair.Name = tblInputs.DataBodyRange(i, nameCol).Value
nameCostPair.Cost = tblInputs.DataBodyRange(i, costCol).Value
newInstance.AddTestClass nameCostPair
Next i
End With
Set Create = newInstance
End Function
Public Sub AddTestClass(ByVal tstClass As CTestClass)
this.msTestClass.Add tstClass
End Sub
Public Property Get Self() As CTestClass
Set Self = Me
End Property
Public Property Get NameCostPairs() As Collection
Set NameCostPairs = this.msTestClass
End Property
CTestClass (no longer needs VB_PredeclaredId set to 'True')
Option Explicit
Implements ITestClass
''UDT mirrors class properties
Private Type TTestClass
Name As String
Cost As Long
End Type
Private this As TTestClass
Public Property Let Name(Val As String)
this.Name = Val
End Property
Public Property Get Name() As String
Name = this.Name
End Property
Public Property Let Cost(Val As Long)
this.Cost = Val
End Property
Public Property Get Cost() As Long
Cost = this.Cost
End Property
Private Property Get ITestClass_Name() As String
ITestClass_Name = Name
End Property
Private Property Get ITestClass_Cost() As Long
ITestClass_Cost = Cost
End Property
And Finally:
Option Explicit
Sub TestFactory()
Const WS_NAME As String = "Sheet1"
Const NR_TBL As String = "Table1"
Dim tblInputs As ListObject
Set tblInputs = ThisWorkbook.Worksheets(WS_NAME).ListObjects(NR_TBL)
Dim container As TestClassContainer
Set container = TestClassContainer.Create(tblInputs)
Dim nameCostPair As ITestClass
Dim containerItem As Variant
For Each containerItem In container.NameCostPairs
Set nameCostPair = containerItem
Debug.Print
Debug.Print nameCostPair.Name
Debug.Print nameCostPair.Cost
Next
End Sub
I see #BZgr has provided a solution but as I'd also written one I provide the answer below as analternative.
I think there are several problems with th OP code.
The origclass and collection of origclasses is conflated, they should be separate. Disentangling this wasn't made easier by the poor naming of the origclass UDT.
Its not clear what needs to be a factory. I've put the factory method in the origclasses class so that an 'immutable' collection of origclass is created.
Its not clear what the op is trying to achieve by introducing an interface. In general, interfaces are used when a number of different object must provide that same set of methods. In VBA the interface declaration allows the compiler to check if each object that claims to implement the interface has the correct methods and parameter lists. (but i do accept that there may be some special VBA cases where this is not the case)
The code below compiles and has no significant Rubberduck inspections. However, I am not a user of Excel VBA so I apologise in advance if my code makes mistakes in this area.
a. We have a separate and very simple OrigClass
Option Explicit
Private Type Properties
Name As String
Cost As Long
End Type
Private p As Properties
Public Property Get Name() As String
Name = p.Name
End Property
Public Property Let Name(ByVal ipString As String)
p.Name = ipString
End Property
Public Property Get Cost() As Long
Cost = p.Cost
End Property
Public Property Let Cost(ByVal ipCost As Long)
p.Cost = ipCost
End Property
2 The OrigClaases class which is a collection of origclass
Option Explicit
'#PredeclaredId
'#Exposed
'Col position references for input table, only includes cols with relevant data
Private Enum icrColRef
icrName = 2
icrCost = 4
End Enum
Private Type State
'TestClass As Collection
Host As Collection
ExternalData As Excel.Worksheet
TableName As String
End Type
Private s As State
Public Function Deb(ByVal ipWorksheet As Excel.Worksheet, ByVal ipTableName As String) As OrigClasses
With New OrigClasses
Set Deb = .ReadyToUseInstance(ipWorksheet, ipTableName)
End With
End Function
Friend Function ReadyToUseInstance(ByVal ipWorksheet As Excel.Worksheet, ByVal ipTableName As String) As OrigClasses
Set s.Host = New Collection
Set s.ExternalData = ipWorksheet
s.TableName = ipTableName
PopulateHost
Set ReadyToUseInstance = Me
End Function
' The fact that you are using the collection Key suggests
' you might be better of using a scripting.dictioanry
' Also given that you populate host doirectly from the worksheet
' this add method may now be redundant.
Public Sub Add(ByVal ipItem As OrigClass)
s.Host.Add _
Item:=ipItem, _
Key:=ipItem.Name
End Sub
Public Sub Extract()
' Extract is restricted to re extracting data
' should the worksheet have been changed.
' If you need to work on a new sheet then
' create a new OrigClasses object
Set s.Host = New Collection
PopulateHost
End Sub
Private Sub PopulateHost()
Dim tblInputs As ListObject
Set tblInputs = s.ExternalData.ListObjects(s.TableName)
Dim myRow As Long
For myRow = 1 To tblInputs.DataBodyRange.Rows.Count
Dim myItem As OrigClass
Set myItem = New OrigClass
With myItem
.Name = tblInputs.DataBodyRange(myRow, icrName).Value
.Cost = tblInputs.DataBodyRange(myRow, icrCost).Value
End With
s.Host.Add myItem, myItem.Name
Next
End Sub
Public Function Item(ByVal ipIndex As Variant) As OrigClass
Set Item = s.Host.Item(ipIndex)
End Function
Public Function Count() As Long
Count = s.Host.Count
End Function
Public Function Name(ByVal ipIndex As Long) As String
Name = s.Host.Item(ipIndex).Name
End Function
Public Function Cost(ByVal ipIndex As Long) As Long
Cost = s.Host.Item(ipIndex).Cost
End Function
Public Function SheetName() As String
SheetName = s.ExternalData.Name
End Function
Public Function TableName() As String
TableName = s.TableName
End Function
'#Enumerator
Public Function NewEnum() As IUnknown
Set NewEnum = s.Host.[_NewEnum]
End Function
c. The testing code
Option Explicit
Const WS_NAME As String = "Sheet1"
Const NR_TBL As String = "Table1"
Sub TestFactory()
Dim oTest As OrigClasses
'#Ignore UnassignedVariableUsage
Set oTest = OrigClasses.Deb(ThisWorkbook.Worksheets(WS_NAME), NR_TBL)
Dim myOrigClass As Variant
For Each myOrigClass In oTest
Debug.Print
Debug.Print myOrigClass.Name
Debug.Print myOrigClass.Cost
Next
End Sub
For the factory method, following feeback from Rubberduck, I now use the method name 'Deb' which is short for Debut (or Debutante) meaning something that is presented which is ready to be used. Which of course leads to why I use the method name 'readytoUseInstance'.
I Use UDT of Properties and State (with variables p and s) to separate extenal properties from internal state.
Within methods I prefix variables with the prefix 'my'.
For method parameters i use the prefixed ip, op and iop for input only, output only, and imput that is mutated and output.
A side benefit of these prefixes p,s,my,ip,op,iop is that they also remove some the majority of the issues encountered when trying to name variables/parameters.

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.

VBA inheritance via construction, constructor not working? [duplicate]

This question already has an answer here:
Can't seem to use Interfaces with properties in VBA and I can't work out why
(1 answer)
Closed 6 years ago.
I am just getting started using classes in VBA and I'm following the "Inheritance by construction" method outlined here. My example uses a simple class that contains a value (as a variant) and a value type (as a string). I created a subclass in which value type is set to string in the constructor. Here is my code:
Interface Class (IVal)
'IVal interface class (from https://www.theartofquantfinance.com/inheritance-by-construction-in-vba/)
Option Explicit
'-----------------------------------
'Accessor methods for ValType
'-----------------------------------
Public Property Get ValType() As String
End Property
Public Property Let ValType(ByVal RHS As String)
End Property
'-----------------------------------
'Accessor methods for Val
'-----------------------------------
Public Property Get Val() As Variant
End Property
Public Property Let Val(ByVal RHS As Variant)
End Property
'Add other methods here as "Public Sub"
'i.e.
'Public Sub HonkHorn()
'End Sub
Base Class (CBaseVal)
'CBaseVal base class - implements IVal interface (from https://www.theartofquantfinance.com/inheritance-by-construction-in-vba/)
Option Explicit
Implements IVal
'------------------------------
'Private Fields
'------------------------------
Private valType_ As String
Private val_ As Variant
'------------------------------
'Constructors and destructors
'------------------------------
Private Sub Class_Initialization()
valType_ = "Base Val"
val_ = Nothing
End Sub
'------------------------------
'Class Properties And methods - public, visible to all modules
'These would be declared in the interface class
'------------------------------
Public Property Get ValType() As String
ValType = valType_
End Property
Public Property Let ValType(ByVal RHS As String)
valType_ = RHS
End Property
Public Property Get Val() As Variant
Val = val_
End Property
Public Property Let Val(ByVal RHS As Variant)
val_ = RHS
End Property
'Add additional class methods here
'e.g.
'Public Sub HonkHorn()
' MsgBox prompt:="Beep!!!"
'End Sub
'------------------------------
'Interface property and method implementation - private, only visible to this module
'Delegate interface properties and methods to this (base) class using "Me"
'------------------------------
Private Property Let IVal_Val(ByVal RHS As Variant)
Me.Val = RHS
End Property
Private Property Get IVal_Val() As Variant
IVal_Val = Me.Val
End Property
Private Property Let IVal_ValType(ByVal RHS As String)
Me.ValType = RHS
End Property
Private Property Get IVal_ValType() As String
IVal_ValType = Me.ValType
End Property
'End Property
'Add additional IF methods here
'i.e.
'Private Sub ICar_HonkHorn()
' Call Me.HonkHorn
'End Sub
Child Class (CStringVal)
'CStringVal class - implements IVal interface and delegates to CVal as appropriate (from https://www.theartofquantfinance.com/inheritance-by-construction-in-vba/)
Option Explicit
Implements IVal
'------------------------------
'Private Fields
'------------------------------
Private baseVal_ As IVal 'contains an instance of the IVal class (why isn't this "As CBaseVal"?)
'------------------------------
'Constructors and destructors
'------------------------------
Private Sub Class_Initialization()
Set baseVal_ = New CBaseVal 'initialize private field of type "val class"
baseVal_.ValType = "string"
baseVal_.Val = Nothing
End Sub
'------------------------------
'Class Properties And methods (here's where you over-ride the base class implementation)
'These would be declared in the base class and the interface implementation below will delegate to either here or base class
'------------------------------
'e.g.
'Public Sub HonkHorn()
' MsgBox prompt:="Beep!!!"
'End Sub
'------------------------------
'Interface property and method implementation - declared in base class and either:
'=> most delegate to base class
'=> some may override base class and delegate here
'------------------------------
Private Property Let IVal_Val(ByVal RHS As Variant)
baseVal_.Val = RHS 'Delegate to base class
End Property
Private Property Get IVal_Val() As Variant
IVal_Val = baseVal_.Val
End Property
Private Property Let IVal_ValType(ByVal RHS As String)
baseVal_.Val = RHS 'Delegate to base class
End Property
Private Property Get IVal_ValType() As String
IVal_ValType = baseVal_.ValType 'Delegate to base class
End Property
'Add additional interface methods here
'i.e.
'Private Sub ICar_HonkHorn()
' Call Me.HonkHorn 'Overrides base class implementation, delegates to class method above
'End Sub
And here is the code I'm using to test it:
Public Sub testStringValClass()
Dim interfaceClassVal As IVal
Dim baseClassVal As CBaseVal
Dim stringClassVal As CStringVal
Set interfaceClassVal = New IVal
Set baseClassVal = New CBaseVal
Set stringClassVal = New CStringVal
a = interfaceClassVal.ValType
b = baseClassVal.ValType
c = stringClassVal.ValType
End Sub
The first problem is a compile error that "Method or data member not found" for the line
c = stringClassVal.ValType
If I comment out that line, the code runs but using Watches it appears that valType_ is not being set, either as "Base Val" for the base class object or as "string" for the string class object. Additionally, there are "Object variable or With block variable not set" errors for the following properties:
stringClassVal.IVal_Val
stringClassVal.IVal_ValType
I'm kind of at a loss here... I assume it's something simple like a misspelling but I can't find it.
That's simply because your class CStringValdoes not implement the ValType property. Ok, your implemented the property ValType of the interface IVal but not the explicit ValType of the class itself.
To access the interface's method, you need somehow to cast your object to the interface's type. For example:
Dim itf As IVal
Dim stringClassVal As New CStringVal
Set itf = stringClassVal '<-- casts the object to the interface's type
' and now:
c = itf.ValType
or you can use the defined name:
c = stringClassVal.IVal_ValType
But notice you did not initialize the fields of the variable yet (use the letters before the getters).
Yes, inheritance in VBA/VB6 is somehow tricky and not very natural. When a class implements an interface, the interface's methods are accessible through a reference to the interface, not to the implementor object, unless the latter explicitly redefines the method/property
Also notice that the initializer val_ = Nothing is useless and somehow wrong. An uninitialized Variant is created as an empty variant. Nothing basically is used for objects, not for basic variables (including strings).
Also as #TimWilliams said the constructor's name is Class_Initialize not Class_Initialization.

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