Object variable as object property - vba

I have a collection of Employee objects in a collection called MyEmployees.
I want to assign one of these objects as the superior of the others.
I have created the property pSuperior in the class cEmployee
Below are the Set and Get methods for the class:
Public Property Set Superior(value As Object)
Set pSuperior = value
End Property
Public Property Get Superior() As Object
Superior = pSuperior
End Property
The Set method gives me no errors at run-time:
Set MyEmployees.Item(1).Superior = MyEmployees.Item(2)
But when I use
Debug.Print MyEmployees.Item(1).Superior.Name
to test I get run-time error 91 object variable or with block not set
Is my Set Method not working?

You always assign objects to variables (including return values of functions and properties) using Set. So the code for your Get method should be:
Public Property Get Superior() As Object
Set Superior = pSuperior
End Property

Looks like you're Get property is missing Set for your object assignment as a return value. I'm not sure what else you may have going on, so here's some sample code of what worked for me (except no collection). It may also be an improvement to define your Get/Set objects as Employees.
Here's example class code:
Private pSuperior As Employee
Private pName As String
Public Property Get Name() As String
Name = pName
End Property
Public Property Let Name(Value As String)
pName = Value
End Property
Public Property Get Superior() As Employee
Set Superior = pSuperior
End Property
Public Property Set Superior(Value As Employee)
Set pSuperior = Value
End Property
And Example using the class as you described:
Sub test()
Set John = New Employee
Set Tim = New Employee
Set Tim.Superior = John
John.Name = "John"
Tim.Name = "Tim"
MsgBox Tim.Name & "'s Superior is " & Tim.Superior.Name
End Sub

Related

How do I apply method of the class to the property of the class?

I have a class ClsAnimal containing the string property species, and also method plural which just returns a string with added "s" at the end of a string. I wonder if it's possible to apply .Plural to Animal.Species directly, as shown in the example below:
Sub Test()
Dim Animal As New ClsAnimal
Animal.Species = "cat"
debug.print Animal.Species
'expected result "cat"
debug.print Animal.Species.Plural
'expected result "cats"
End Sub
ClsAnimal Code:
Option Explicit
Private PSpecies As String
Property Let Species(val As String)
PSpecies = val
End Property
Property Get Species() As String
Species = PSpecies
End Property
'returns the name of an animal + "s"
Private Function Plural(val) As String
Plural = val & "s"
End Function
You can kind of hack your way to the behavior you are describing. They way I could implement this is to create a new class that "extends" strings. I've called mine StringExt and it looks like this:
Option Explicit
Private pValue As String
'#DefaultMember
Public Property Get Value() As String
Value = pValue
End Property
Public Property Let Value(val As String)
pValue = val
End Property
Public Function Pluralize() As String
Dim suffix As String
'Examine last letter of the string value...
Select Case LCase(Right(pValue, 1))
Case "" 'empty string
suffix = ""
Case "s" 'words that end in s are pluralized by "es"
suffix = "es"
'Test for any other special cases you want...
Case Else ' default case
suffix = "s"
End Select
Pluralize = pValue & suffix
End Function
This is a wrapper class that wraps around an inner string value. It has a single method which will try to return the plural of the inner string value. One thing to note here is the use of a DefaultMember. I used a really handy vba editor COM addin called RubberDuck to do all the behind-the-scenes work for me with the Default Member. You can do it manually though. You would need to export the class module and modify it in a text editor, adding the Attribute Value.VB_UserMemId = 0 tag inside the property getter:
...
Public Property Get Value() As String
Attribute Value.VB_UserMemId = 0
Value = pValue
End Property
Public Property Let Value(val As String)
pValue = val
End Property
...
Then, import the module back into your vba project. This attribute is not visible in the vba editor. More on default members here but it basically means this property will be returned if no property is specified.
Next, we change up your animal class a bit, using our new StringExt type for the Species property:
Option Explicit
Private pSpecies As StringExt
Public Property Set Species(val As StringExt)
pSpecies = val
End Property
Public Property Get Species() As StringExt
Set Species = pSpecies
End Property
Private Sub Class_Initialize()
Set pSpecies = New StringExt
End Sub
Note here that you'll now need to make sure the pSpecies field gets instantiated since it is an object type now. I do this in the class Initializer to enure it always happens.
Now, your client code should work as expected.
Sub ClientCode()
Dim myAnimal As Animal
Set myAnimal = New Animal
myAnimal.Species = ""
Debug.Print myAnimal.Species.Pluralize
End Sub
Disclamer:
Substituting a basic string type for an object type might cause unexpected behavior in certain fringe situations. You are probably better off just using some global string helper method that takes a string parameter and returns the plural version. But, my implementation will get the behavior you asked for in this question. :-)

VBA. Access to an array element of the Variant. An array is a class property

I have a class with an ArrSignalCabel variable. In this variable I write an array of objects.
Property Get bottom of my class returns only an array. And I do not have access to the array element.
Option Explicit
Private ArrSignalCabel As Variant
Property Let ArrSignalCab(ByVal ArrValue As Variant)
ArrSignalCabel = ArrValue
End Property
Property Get ArrSignalCab() As Variant
ArrSignalCab = ArrSignalCabel
End Property
Public Property Get ArrSignalCabIn(index As Integer) As Variant ' not work
ArrSignalCabIn(index) = ArrSignalCabel(index)
End Property
////////////////
NameObjTM2 = TempPanel.ArrCabelPan(i).ArrSignalCabIn(0) ' not work
NameObjTM3 = TempPanel.ArrCabelPan(i).ArrSignalCab ' work
NameObjTM4= NameObjTM3(0).NameSig ' work
PS:
TempPanel - an object that contains an array of objects(ArrCabelPan).
ArrCabelPan - the class structure is identical to that described.
Have you tried ArrSignalCabIn = ArrSignalCabel(index)?
The name of the property should match what you declared in the Property Get, like Function.
I find answer
Public Property Get ArrSignalCabIn(Index As Integer) As Variant
If (ArrSignalCabel(Index) Is Nothing) Then Set ArrSignalCabel(Index) = New Signal
Set ArrSignalCabIn = ArrSignalCabel(Index)
End Property
Thanks to those who helped

Linking classes in VBA

I have defined a class in VBA as below
Class Employee(clsEmployee):
Private pEmpName As String
Private pEmpID As Long
Private pDOJ As Date
Private pManager As clsEmployee
Private pOffice As clsOffice
where pManager is a property of the class of type clsEmployee and pOffice is also the property of the class of type clsOffice(another class)
I also have defined the Let and Get method inside the class to read and write properties of the class as below
Public Property Let Name(sName As String)
If sName <> "" Then
pEmpName = sName
End If
End Property
Public Property Get Name() As String
Name = pEmpName
End Property
Public Property Let EmployeeId(lngID As Long)
If IsNumeric(lngID) Then
pEmpID = lngID
End If
End Property
Public Property Get EmployeeId() As Long
EmployeeId = pEmpID
End Property
Public Property Let PDM(obj As clsEmployee)
Set pPDManager = obj
End Property
Public Property Get PDM() As clsEmployee
Set PDM = pPDManager
End Property
Now in the code module I have written a test sub to set some of the properties of my Employee class as below
Sub test()
Dim obj As clsEmployee
Set obj = New clsEmployee
obj.Name = "Employee 100"
obj.EmployeeId = 11111111
obj.PDM.Name = "Employee 1"
When the code executes the statement obj.Name="Employee 100" then the Let property which is Name gets executed and the pEmpName is set, while when the code tries to execute the statemnet obj.PDM.Name="Employee 1 VBA executes the GET method PDM.
My question is why does the get method (PDM in class) gets executed on statement `obj.PDM.Name="Employee 1" when clearly I am trying to set the property instead of retrieving it.
where pManager is a property of the class of type clsEmployee
Each instance of this class clsEmployee has to have a reference to another instance of clsEmployee which denotes the manager. The employee must have its manager.
The property PDM retuns clsEmployee so it needs to use Set
Public Property Set PDM(obj As clsEmployee)
Set pManager = obj
End Property
Public Property Get PDM() As clsEmployee
Set PDM = pManager
End Property
why does the get method (PDM in class) gets executed on statement `obj.PDM.Name="Employee 1"
The employee has its manager. This manager is accessible via the property PDM. If we have reference to employee in variable obj and we want to change name of the manager of this employee, we have to get to its manager. That is why the PDM is called. So we get to the manager and can change its name. HTH
The test method could be now modified as following.
Option Explicit
Sub test()
Dim mgr As clsEmployee
Set mgr = New clsEmployee
Dim obj As clsEmployee
Set obj = New clsEmployee
Set obj.PDM = mgr
obj.Name = "Employee 100"
obj.EmployeeId = 11111111
obj.PDM.Name = "Employee 1"
End Sub
When we want a property which sets a reference to an object we need to use Set-Property. Otherwise Let is enough.
By convention we use Set for object references, like your employee. Employee is composition of data like Name, Id, DateOfBirth etc.
Let is for atomic data like string, integer, bool etc.
Have a look at Worksheet class of Excel library. This class has property Name which is of type string. If we have a variable which references a particular worksheet then we can change name of the worksheet like this:
Dim w as Worksheet
Set w = Worksheets(1)
Let w.Name = "Demo1"
Notice Let here. Because Name is string-Property Let is used. The keyword Let can be omitted though.
Worksheet has property Parent of type Object which is a reference to the parent of the particular workseet. If we would like to change the parent, we will write:
Dim w as Worksheet
Set w = Worksheets(1)
Set w.Parent = new Parent ' Is just example, it won't compile, Parent is read-only :)
In case when Set is needed it can't be omitted like Let.
In your case the properties which work with clsEmployee must use Get-Set where the properties which work with e.g. string use Get-Let.
Here in my
Dropbox
I have created an very ugly picture which should illustrate the
situation with the object references in variables obj and mgr.

Returning Class Object from Inherited class

I'm trying to teach myself reflection and have been googling but I can't wrap my head around it entirely. I created a class called DataClass which contains a method called GetClassFromDB as you can see below, which will be inherited from multiple classes.
What I am attempting to do is have my dataclass read the TableName property that is defined within objResults. Once I pull in the tablename from objResults I would query the SQL database for a dataset. Once I have the dataset I would create a new object of the same TYPE inheriting this class (Which will be different types) and populate it from the dataset. Once I have the newly populated class I will return it for use.
I believe I have gotten most of the way there properly (Please correct me if there is a better way), but my real question is this. How can I create a new class of the type thats deriving that class from that string name that I getting in my code, or the type. I would want to have all the accessible properties from objResults available.
Namespace MyApp
Public Class DataClass
Private _TableName As String
Private _Name As String
Overridable ReadOnly Property TableName As String
Get
Return _TableName
End Get
End Property
Public Overloads Function GetClassFromDB() As Object
Try
Dim BaseObject As New Object
'Get the object name
Dim objName As String = MyBase.GetType().Name
'Gets the type thats calling this method
Dim objDerived As Type = MyBase.GetType()
'Get the property info to request the tablename from the derived class
Dim TableName As PropertyInfo = objDerived.GetProperty("TableName")
Dim TableNameString As String = TableName.GetValue(Me, Nothing).ToString
'Once I get the table name from objResults I can perform the SQL
Dim QueryResults as DataSet = SQLiteCLass.Query("Select * FROM TableNameString")
'Once I get data from the SQL I want to create a new object of the type deriving this method.
'In this example is objResults
Dim NewObject as objDerived
'Now I can fill my new object with the results and return it as an object
'THIS IS MY QUESTION - How can I create a new object of the TYPE that I receive from Reflection
Return False
Catch ex As Exception
Return False
End Try
End Function
End Class
End Namespace
and this is a sample class that would inherit my dataclass.
Public Class objResults
Inherits MyApp.DataClass
Private _GameID As Guid
Public Property GameID As Guid
Get
Return _GameID
End Get
Set(ByVal value As Guid)
_GameID = value
End Set
End Property
Public Overrides ReadOnly Property TableName As String
Get
Return "This is my tablename"
End Get
End Property
End Class
and this is how I would use this in code.
Dim objResult as New objResults
Dim TodaysResult as objResultsCollection
TodaysResult = objResult.GetClassFromDB()

Accessing Items in a Collection from a Class

-EDIT Fixed
I was missing one thing and doing one thing wrong. First I was missing a function to access the collection by index. And I should of been using a for Loop instead of a for each loop in my module code
I forgot to add this to the collection class
Public Function GetPayRecords(ByVal index As Variant) As PayRecords
Set GetPayRecords = pObjCol.item(index)
End Function
and replaced
For Each vItem In .GetPayRecords
....code to do stuff
Next vItem
with this in the module
Dim x As Integer
For x = 1 To .Count
Debug.Print .GetPayRecords(x).PY_PayRecord.CEOCompanyID
Debug.Print .GetPayRecords(x).PY_PayRecord.OrigBankID
Next x
I'm writing a program that has 8 Classes. Each class represents a specific record type.
I have an overall Class that contains those 8 classes which is for simplicity when coding in the Module. I only have to declare one class which gives me access to all 8 classes. I have a collection which contains all the records types. Once all the logic of loading the individual records is complete they get added to the collection. This all works perfectly and I can see all the records in the collection. The final step, which happens to be where i'm having the problem, I need to extract each item within the collection by record type and write it to a csv. The problem I encounter is trying to iterate through each record.
Here's how the structure looks
Classes
clsAllRecordTypes
clsRecordType1
clsRecordType2
...
clsRecordType8
Collection
clsColRecords
The problem is in the retrieval
Module
Dim PayRecord As PayRecords 'Class of Classes
Dim PayRecordList As bankCollection
...code to load all the payrecords
With payrecordlist
Foreach vItem in .pObjCol
debug.print .pObjCol.Item(?) ' not sure why i can't see all 8
next vItem
End With
When I add vItem to the watch I can see each and every record type filled up with information but yet i Can not access it. Below is the Class of classes and collection
Class of Classes
Option Explicit
'This class is a representation of all the record types that apply to our Payment Manager
'It aggregates all the record types (classes) into one class. That one class is used in the main processing module for simplicty
'
Private pPayRecord As New PayRecord
Private pPNAR_OP As New PNAR_OP
Private pPNAR_RP As New PNAR_RP
Private pSuppACHREC As New SuppACHRec
Private pSuppCCRRec As New SuppCCRRec
Private pSuppCHKRec As New SuppCHKRec
Private pDocumentDelieveryRec As New DocumentDeliveryRecord
Private pInvoiceRecords As New InvoiceRecords
Public Property Get PY_PayRecord() As PayRecord
Set PY_PayRecord = pPayRecord
End Property
Public Property Let PY_PayRecord(ByVal newPayRecord As PayRecord)
Set pPayRecord = newPayRecord
End Property
Public Property Get PA_PNAR_OP() As PNAR_OP
Set PA_PNAR_OP = pPNAR_OP
End Property
Public Property Let PA_PNAR_OP(ByVal newPNAR_OP_Record As PNAR_OP)
Set pPNAR_OP = newPNAR_OP_Record
End Property
Public Property Get PA_PNAR_RP() As PNAR_RP
Set PA_PNAR_RP = pPNAR_RP
End Property
Public Property Let PA_PNAR_RP(ByVal newPNAR_RP_Record As PNAR_RP)
Set pPNAR_RP = newPNAR_RP_Record
End Property
Public Property Get AC_SuppACH() As SuppACHRec
Set AC_SuppACH = pSuppACHREC
End Property
Public Property Let AC_SuppACH(ByVal newSuppACH_Record As SuppACHRec)
Set pSuppACHREC = newSuppACH_Record
End Property
Public Property Get AC_SuppCCR() As SuppCCRRec
Set AC_SuppCCR = pSuppCCRRec
End Property
Public Property Let AC_SuppCCR(ByVal newSuppCCR_Record As SuppCCRRec)
Set pSuppCCRRec = newSuppCCR_Record
End Property
Public Property Get AC_SuppCHK() As SuppCHKRec
Set AC_SuppCHK = pSuppCHKRec
End Property
Public Property Let AC_SuppCHK(ByVal newSuppCHK_Record As SuppCHKRec)
Set pSuppCHKRec = newSuppCHK_Record
End Property
Public Property Get DocumentDeliveryRecord() As DocumentDeliveryRecord
Set DocumentDeliveryRecord = pDocumentDelieveryRec
End Property
Public Property Let DocumentDeliveryRecord(ByVal newDocumentDeliveryRecord As DocumentDeliveryRecord)
Set pDocumentDelieveryRec = newDocumentDeliveryRecord
End Property
Public Property Get InvoiceRecords() As InvoiceRecords
Set InvoiceRecords = pInvoiceRecords
End Property
Public Property Let InvoiceRecords(ByVal newInvoiceRecord As InvoiceRecords)
Set pInvoiceRecords = newInvoiceRecord
End Property
Collection Class
Option Explicit
Private pHeaderRec As New HeaderRec
Private pNewPayRecords As New PayRecords
Public pObjCol As Collection
Private pTrailerRec As New TrailerRec
Private Sub Class_Initialize()
Set pObjCol = New Collection
End Sub
Private Sub Class_Terminate()
Set pObjCol = Nothing
End Sub
Public Property Get HD_HeaderRecord() As HeaderRec
Set HD_HeaderRecord = pHeaderRec
End Property
Public Property Let HD_HeaderRecord(ByVal newHeaderRecord As HeaderRec)
Set pHeaderRec = newHeaderRecord
End Property
Sub Add(ByVal newPayRecs As PayRecords)
pObjCol.Add newPayRecs
End Sub
Property Get Count() As Long
Count = pObjCol.Count
End Property
Public Property Get TR_TrailerRecord() As TrailerRec
Set TR_TrailerRecord = pTrailerRec
End Property
Public Property Let TR_TrailerRecord(ByVal newTrailer_Record As TrailerRec)
Set pTrailerRec = newTrailer_Record
End Property
I'm sorry if this doesn't help, because your explanation is hard to follow. But, I'll assume that you are saying that you have an object of type Payrecords, which contains references to seven other objects of types PNAR_OP, PNAR_RP, etc. Each of these latter objects contain "20-30 fields" that you want to get at. You ask how to loop through all of these.
A simple way to do that is to use an array. Yes, you can foreach through Collections or (better yet) Dictionaries, but arrays work, they're easy to understand, and they were iterating through objects when Collections were running around in diapers.
Let your Payrecords have a property of type Object(6). When you initialize it, instantiate one of each of the seven objects and add it to the array (for example, "Set myPayrecordsObjects(3) = New SubCCRRec" and so on). To loop through, just use a for next loop to loop through the 7 objects.
Since you provide no information about how you structure your "fields" within these objects, I'll recommend that you iterate through the Fields collection of the ADO object to loop through those. (If you're not using the ADO Fields collection, well, your attention to detail gets mine in return.)