I found a great post on SO that seems to be exactly what I want: Is it possible to access a parent property from a child that is in a collection? However my adaptation of it is giving me Object doesn't support this property or method.
My code which now works thanks to Mat's Mug and Tomalak:
Parent Class - clsComputer
Option Explicit
Private pCD As clsCD
''''''''''''''''''''''''''''''
' CD property
''''''''''''''''''''''''''''''
Public Property Get CD() As clsCD
If pCD Is Nothing Then
Set pCD = New clsCD
'Per Mat's Mug post, drop the parenthesis
pCD.Initialze Me
End If
Set CD = pCD
End Property
Public Property Set CD(value As clsCD)
pCD = value
End Property
Child class - clsCD
Option Explicit
Private pParent As clsComputer
'''''''''''''''''''''''''''''
' Status property - READ ONLY
'''''''''''''''''''''''''''''
Public Property Get Status(Optional strHost As String) As String
Dim strResult As String
If strHost = "" Then strHost = Me.Parent.HostName
strResult = RunCMD("cmd /c ""winrs -r:" & strHost & _
" reg query hklm\system\currentcontrolset\services\cdrom /v start""")
If InStr(1, strResult, "0x4", vbTextCompare) Then
Status = "Disabled"
Else
Status = "Enabled"
End If
End Property
'''''''''''''''''''''''''
' Parent property
'''''''''''''''''''''''''
Public Property Get Parent() As clsComputer
Set Parent = pParent
End Property
'Because as Tomalak points out, you use Set with Objects.
Public Property Set Parent(Obj As clsComputer)
Set pParent = Obj
End Property
'''''''''''''''''''''''''
' Initialize Method
'''''''''''''''''''''''''
Public Sub Initialize(Obj As clsComputer)
Set Me.Parent = Obj
End Sub
Code Module - Module1
Sub test()
Dim oPC As clsComputer
Set oPC = New clsComputer
Debug.Print "CD Status: " & oPC.CD.Status
End Sub
If I test Me, it is an object (eg, If IsObject(Me) Then Stop evaluates true), and Intellisense shows all the properties and methods in clsComputer when I type Me. The Locals windows shows Me as a clsComputer object. Everything I know to check says Me is a clsComputer object, so what am I doing wrong?
Classic.
pCD.Initialize (Me) 'Error occurs on this line when using F8
Drop the parentheses.
pCD.Initialize Me
Done.
Parentheses around a parameter force it to be evaluated and passed ByVal (regardless of what the procedure's signature says) - and since you probably haven't defined a default property for clsComputer then the evaluation blows up and the runtime doesn't even get to the Initialize method.
That said, there's nothing wrong with passing object reference by value. In fact, that's what C# and VB.NET do by default - consider passing any parameter ByVal.
Public Property Set Parent(ByRef Obj As clsComputer)
Set pParent = Obj
End Property
I'm not by my PC so just coding blind
Try this for your clsComputer class
Option Explicit
Private pCD As clsCD
''''''''''''''''''''''''''''''
' CD property
''''''''''''''''''''''''''''''
Public Property Get CD() As clsCD
Set CD = pCD
End Property
Public Property Set CD(value As clsCD)
pCD = value
End Property
Sub Class_Initialize()
Set pCD = New clsCD
pCD.Initialize(Me)
End Property
Related
I am a novice user of class module.
I don't understand the concept of a class module well.
I want to configure a class module similar to the basic objects of Excel like Worksheet or Cells or ETC..
So I want to control it by creating a parent object and creating its child objects.
Child Class - Defect
Option Explicit
Private pDefectSymptom As String
Private pDefectLevel As Integer
Property Get DefectSymptom() As String
DefectSymptom = pDefectSymptom
End Property
Property Let DefectSymptom(ByVal vDefectSymptom As String)
pDefectSymptom = vDefectSymptom
End Property
Property Get DefectLevel() As Integer
DefectLevel = pDefectLevel
End Property
Property Let DefectLevel(ByVal vDefectLevel As Integer)
pDefectLevel = vDefectLevel
End Property
Function Delete()
'???
End Function
Property Get Parent() As Object
'???
End Property
Parent Class - Defects
Private Defects As New Collection
Function Add(DefectSymptom As String, Optional DefectLevel As Integer) As Defect
Dim NewDefect As Defect
Set NewDefect = New Defect
NewDefect.DefectSymptom = DefectSymptom
NewDefect.DefectLevel = DefectLevel
Defects.Add NewDefect
'Add = NewDefect 'Error! Like the open command of workbook, I want to return an object or just command
End Function
Property Get Count() As Long
Count = Defects.Count
End Property
Property Get Item(Index As Long) As Defect
Item = Defects(Index) 'Error! I don't know what raise Error.
End Property
My question are.
How to add Command like Open Command of workbook. Return or just command.
Why Raise Error Item Property? how to fix that?
Hiding Private variable. Because office office objects seem to be hidden.
enter image description here
If you have time, please help with Delete command and Parent command.
How to add Command like Open Command of workbook. Return or just
command.
You just need to return the newly created object in the function. Keep in mind since we're dealing with objects, we need to Set the object's reference.
Public Function Add(DefectSymptom As String, Optional DefectLevel As Integer) As Defect
Dim NewDefect As Defect
Set NewDefect = New Defect
NewDefect.DefectSymptom = DefectSymptom
NewDefect.DefectLevel = DefectLevel
Defects.Add NewDefect
Set Add = NewDefect '<- here
End Function
Why Raise Error Item Property? how to fix that?
Same as the above, you need to Set the object's reference.
Property Get Item(Index As Long) As Defect
Set Item = Defects(Index)
End Property
To delete, simply supply the index to the function. However, this method must reside where the collection is (parent) since a Defect object cannot delete itself.
Function Delete(ByVal Index As Long)
Defects.Remove Index
End Function
Lastly, to hold a reference to the parent, each child must hold a reference to it in a private variable. Then you need to set the parent when creating a new item using the keyword Me.
So in the Defect class, create a private field.
Private mParent As Defects
Property Set Parent(ByVal objDefects As Defects)
Set mParent = objDefects
End Property
Property Get Parent() As Defects
Set Parent = mParent
End Property
With this done, amend the Add() method to store the reference.
Public Function Add(DefectSymptom As String, Optional DefectLevel As Integer) As Defect
Dim NewDefect As Defect
Set NewDefect = New Defect
NewDefect.DefectSymptom = DefectSymptom
NewDefect.DefectLevel = DefectLevel
Set NewDefect.Parent = Me '<- here
Defects.Add NewDefect
Set Add = NewDefect '<- here
End Function
Not sure this is a good idea though. I tend to avoid circular references altogether, since a child can hold the parent in memory by holding a reference to it. You will need to make sure to clear the reference to the Parent when deleting the item.
Lastly, you should avoid creating the Defects collection like this. Instead, you should make use of the class constructor and destructor.
This method is called automatically when a new class is created:
Private Sub Class_Initialize()
Set Defects = New VBA.Collection
End Sub
This method is called just before the class is destroyed from memory.
Private Sub Class_Terminate()
Set Defects = Nothing
End Sub
Although an experienced VBA programmer it is the first time that I make my own classes (objects). I am surprised to see that all properties are 'duplicated' in the Locals Window. A small example (break at 'End Sub'):
' Class module:
Private pName As String
Public Property Let Name(inValue As String)
pName = inValue
End Property
Public Property Get Name() As String
Name = pName
End Property
' Normal module:
Sub Test()
Dim objTest As cTest
Set objTest = New cTest
objTest.Name = "John Doe"
End Sub
Why are both Name and pName shown in the Locals Window? Can I in some way get rid of pName?
As comments & answers already said, that's just the VBE being helpful.
However if you find it noisy to have the private fields and public members listed in the locals toolwindow, there's a way to nicely clean it up - here I put the Test procedure inside ThisWorkbook, and left the class named Class1:
So what's going on here? What's this?
Here's Class1:
Option Explicit
Private Type TClass1
Name As String
'...other members...
End Type
Private this As TClass1
Public Property Get Name() As String
Name = this.Name
End Property
Public Property Let Name(ByVal value As String)
this.Name = value
End Property
The class only has 1 private field, a user-defined type value named this, which holds all the encapsulated data members.
As a result, the properties' underlying fields are effectively hidden, or rather, they're all regrouped under this, so you won't see the underlying field values unless you want to see them:
And as an additional benefit, you don't need any pseudo-Hungarian prefixes anymore, the properties' implementations are crystal-clear, and best of all the properties have the exact same identifier name as their backing field.
All the Inspection windows not only show the public interface of the objects to you, but also their private members. AFAIK there is nothing you can do about it.
Consider it a nice feature to get even more insights while debugging.
In my experience this is less of an issue in real world objects as they tend to have more fields and properties. Assuming a consistent naming (as your example shows), fields and properties are nicely grouped together.
If you really dont want to see even Mathieu's This you could wrap it into a function. This is a bit more involved, and can be achieved using
a second class that stores the data in public variables. This will be marginally slower then Mattieu's implementation
a collection object that accesses the data using keys. This does not require additional clutter in the project exporer's 'class module' list but will be a little slower if you call the This repeatedly in fast sucession
An example for each is given below. If you break in the Class's Initialisation function, you can add me to the watch window and only the Name property will be listed
Using 2 Objects example
insert a classmodule and name it: InvisibleObjData
Option Explicit
Public Name As String
Public plop
Private Sub Class_Initialize()
Name = "new"
plop = 0
End Sub
insert a classmodule and name it: InvisibleObj
Option Explicit
Private Function This() As InvisibleObjData
Static p As New InvisibleObjData 'static ensures the data object persists at successive calls
Set This = p
End Function
Private Sub Class_Initialize()
This.Name = "invisible man": Debug.Print Name
Me.Name = "test": Debug.Print Name
This.plop = 111: Debug.Print This.plop
End Sub
Property Let Name(aname As String): This.Name = aname: End Property
Property Get Name() As String: Name = This.Name: End Property
'_______________________________________________________________________________________
' in the immediate window type
'
' set x=new invisibleObj
If you dont like splitting the class over two objects, a similar behaviour can be generated using a 'wrapped' collection object:
insert a classmodule and name it: InvisibleCol
Option Explicit
Private Function This() As Collection
Static p As New Collection
'static ensures the collection object persists at successive calls
'different instances will have different collections
'note a better dictionary object may help
Set This = p
End Function
Private Function add2this(s, v)
'a better dictionary object instead of the collection would help...
On Error Resume Next
This.Remove s
This.Add v, s
End Function
Private Sub Class_Initialize()
add2this "name", "invisible man": Debug.Print Name
Me.Name = "test": Debug.Print Name
add2this "plop", 111
Debug.Print This("plop") ' use the key to access your data
Debug.Print This!plop * 2 ' use of the BANG operator to reduce the number of dbl quotes
' Note: This!plop is the same as This("plop")
End Sub
Property Let Name(aname As String): add2this "name", aname: End Property
Property Get Name() As String: Name = This!Name: End Property
'_______________________________________________________________________________________
' in the immediate window type
'
' set x=new invisibleCol
I have created a simple VBA class with a parameterized constructor. The class has the VB_PredeclaredID=True. This development is being done on a Mac in Office 365. The code is below. (The code is not bulletproof. I created this simple example to show the problem that showed up in a more complex class.) When the 5th line of the Make procedure is executed, the Class_Terminate handler is invoked for the object created in the 2nd line, i.e., the one controlling the "with" block. Class_Terminate crashes on exit with an overflow error. (On my more complex example, the error is "with without end.") I've planted debug so I know the Birthday property is never called in line 5. Can someone explain to me what in my code is causing the system to want to destroy the object reference when it is still in use, and how I can work around it? Thanks.
Sub TestClass()
Dim cl As CTest
Set cl = CTest.Make(DateValue("12/6/1946"))
Debug.Print "TestClass", IIf(Not cl Is Nothing, cl.Birthday, "Nothing")
End Sub
Private m_birthday As Date
Private m_otherdata As Variant
Private Sub Class_Initialize()
Debug.Print "Enter Initialize"
If Me Is CTest Then
m_birthday = DateValue("1/1/1800")
Else
m_birthday = Now()
End If
Debug.Print "Exit Initialize", m_birthday
End Sub
Private Sub Class_Terminate()
End Sub
Public Function Make(varparam As Variant) As CTest
If Me Is CTest Then
With New CTest
Select Case VarType(varparam)
Case vbDate:
.Birthday = varparam
Case vbObject:
.Birthday = varparam.Birthday
End Select
Set Make = .Self
End With
ElseIf varparam Is Nothing Then
With New CTest
.Birthday = Me.Birthday
If (VarType(Me.OtherData)) = vbObject Then
Set .OtherData = Me.OtherData
Else
.OtherData = Me.OtherData
End If
Set Make = .Self
End With
Else
Set Make = Nothing
End If
End Function
Public Property Get Self() As CTest
Set Self = Me
End Property
Public Property Get Birthday() As Date
Birthday = m_birthday
End Property
Public Property Let Birthday(val As Date)
m_birthday = val
End Property
Public Property Get OtherData() As Variant
OtherData = m_otherdata
End Property
Public Property Let OtherData(val As Variant)
m_otherdata = val
End Property
Public Property Set OtherData(val As Variant)
Set m_otherdata = val
End Property
I created this simple example to show the problem that showed up in a more complex class
What's missing is code that consumes the class, and the code that actually reproduces the problem, but I wrote a lot of articles on this subject, so let's dig anyway.
Private Sub Class_Initialize()
Debug.Print "Enter Initialize"
If Me Is CTest Then
m_birthday = DateValue("1/1/1800")
Else
m_birthday = Now()
End If
Debug.Print "Exit Initialize", m_birthday
End Sub
A useful piece of information that you're not outputting, is whether the initializing instance is the default instance. Consider:
Debug.Print "Initializing " & TypeName(Me) & IIf(Me Is CTest, " (default instance)", vbNullString)
One problem is this:
If Me Is CTest Then
m_birthday = DateValue("1/1/1800") '<~
Else
m_birthday = Now()
End If
If the current instance is the class' default instance, the internal state is useless. Keeping the default instance stateless is key, in fact: m_birthday is an implementation detail as far as the class' default interface (CTest) is concerned. This would be a better guard clause:
If Me Is CTest Then Exit Sub
m_birthday = Now()
No more nesting, m_birthday is only assigned on a non-default instance, and the intent of keeping the default instance stateless is much more explicitly expressed.
Now, if you type this in the immediate pane:
Set a = New CTest
You'll get this output:
Initializing CTest (default instance)
Initializing CTest
You're missing this trace:
Private Sub Class_Terminate()
Debug.Print "Terminating " & TypeName(Me) & IIf(Me Is CTest, " (default instance)", vbNullString)
End Sub
In the Make factory method, you actually want an even stronger bail-out:
Public Function Make(varparam As Variant) As CTest
If Me Is CTest Then
'...
Consider:
Public Function Make(varparam As Variant) As CTest
If Not Me Is CTest Then Err.Raise 5, TypeName(Me), "Member call is only valid from default/predeclared instance."
And that removes a branch in the conditional path. It also makes me wonder about this:
ElseIf varparam Is Nothing Then
That condition gets evaluated when Me Is CTest is False, i.e. when the factory method is invoked from a user instance... and that should not be allowed to happen.
This is another problem:
Select Case VarType(varparam)
Case vbDate:
.Birthday = varparam
Case vbObject:
.Birthday = varparam.Birthday
vbObject means varparam is an Object reference - not that it's a CTest object: because we're working with a Variant, the member call is late-bound, so if the object doesn't have a Birthday member, we have run-time error 438 raised here. We can keep the member call late-bound but still validate the type:
Case vbObject:
If TypeOf varparam Is CTest Then .Birthday = varparam.Birthday
Or you can get compile-time validation by introducing a variable:
Case vbObject:
Dim typedParam As CTest
If TypeOf varparam Is CTest Then
Set typedParam = varparam
.Birthday = typedParam.Birthday '<~ early-bound member call now
End If
This not only helps the compiler pick up typos (even Option Explicit can't save you from a typo in a late-bound call), it also helps static code analysis tooling like Rubberduck, that now "see" the member call: if the member is renamed, refactoring tools can now update this call site - that's not easily possible with late-bound code.
Public Property Get Self() As CTest
Set Self = Me
End Property
That's syntax sugar that works nicely when there's an explicit interface involved, to cleanly separate the stateless CTest default instance from the ICTest explicit client interface (which could include a Property Get for the birthday, but no Let accessor).
Better syntax sugar that doesn't affect your classes' public interfaces and dramatically cleans up the locals toolwindow in class modules, is shoving the instance state into a Private Type:
Private Type TState
Birthday As Date
OtherData As Variant '<~ note: this breaks strong-typing and gets you back into late-bound land.
End Type
Private this As TState
This Private this instance (module-level) variable replaces all m_-prefixed variables, and now the Birthday property reads like this:
Public Property Get Birthday() As Date
Birthday = this.Birthday
End Property
Public Property Let Birthday(ByVal val As Date)
this.Birthday = val
End Property
...
So, the only convoluted piece of code that looks suspect, is the Make function, which is responsible for too many things.
Write a separate private function that works off a Date, another that works off a CTest object, and conditionally invoke the appropriate one from Make.
With functions that do fewer things, fewer things can go wrong.
Guard your methods - if a method involves instance state, prohibit invoking it from the default/predeclared instance. If a method is supposed to be invoked from the default instance, prohibit calling it from other instances.
See this article for a refresher on the pattern, and this one to see it in action with real code.
I have to doff my cap to #MathieuGuindon and the other chaps and chapesses at Rubberduck as my understanding of VBA has progressed immensely through reading the Rubberduck blogs.
I too have been through some interesting times using the PredeclaredId and therfore offer some of my thoughts on how the OP code should be constructed. As I am still developing my understanding of OOP in VBA folks should feel free to shoot me down in flames if I am wrong or misunderstanding things.
There are two things that I have developed from ideas presented in the rubberduck blogs.
This
I differentiate 'this' into p,s,b and u representing Type definitions of Properties, State,BaseInstance and Using.
Self
I take the construction of a Class instance a step further and pass the Make parameters to the Self method call. In this way the parameters can be used to set up private members of the new instance without the need for public properties.
Option Explicit
Sub TestCTest()
Dim myCTest As CTest
' no errors
Set myCTest = CTest.Make(DateValue("4/6/2020"))
Debug.Print myCTest.Birthday
On Error Resume Next
' Gives "CTest: Expecting Variant/Date or Variant/CTest: Found String"
Set myCTest = CTest.Make("4/6/2020")
Debug.Print Err.Description
On Error GoTo 0
On Error Resume Next
Dim myCtest2 As CTest
'Gives "CTest: Make should only be used with the PredeclaredId"
Set myCtest2 = myCTest.Make(DateValue("4/6/2020"))
Debug.Print Err.Description
On Error GoTo 0
On Error Resume Next
' Gives "New is not permitted outside of the Make Method" error
Dim myCtest3 As CTest
Set myCtest3 = New CTest
Debug.Print Err.Description
On Error GoTo 0
End Sub
Class CTest
Option Explicit
'#PredeclaredId
' Variables used as the private repositories for public properties are located here
Private Type Properties
Birthday As Date
OtherData As Variant ' OP may have a specific type in mind
' NewIsAllowed appears in every instance but we will only ever use
' the value in the predeclared Id to toggle if new is or is not allowed
' via the AllowNew property
NewIsAllowed As Boolean
End Type
Private p As Properties
' If any were present the State type would be used for variables representing
' the state of the instance but which are not intended to be made public through Properties
' Private Type State
' StateVar1 as Typename
' End Type
'
' Private s As State
'
' Used only for PredeclaredId to allow boilerplate code to be written
Private Type BaseInstance
PredeclaredId As CTest
End Type
Private b As BaseInstance
Private Sub Class_Initialize()
' This method runs the **first** time the **PredecalredID** is used in an expression
' and for every subsequent use of New. Therefore managing what happens for the PredeclaredId
' vs instances can become a bit Eulerish.
' Declaring b.predeclaredId allows us to boilerplate code elsewhere
' as it means that the only places that the actual class name is used
' is here ,the Type declaration above and other method declarations.
Set b.PredeclaredId = CTest
' The code to exit on the first use of the PredeclaredID in an expression
If Me Is b.PredeclaredId Then Exit Sub
' Trap the use of New when not used by the Make Function
' the code below means that bad code will be detected at testing time
If Not AllowNew Then
Err.Raise 445 + vbObjectError, TypeName(Me), TypeName(Me) & ": New is not permitted outside of the Make method"
End If
End Sub
Public Function Make(ByVal varparam As Variant) As CTest
' From the OP code we are expecting varparam to be either
' a Date , a CTest object or nothing
If InStr("Date,CTest,Empty,Null,Nothing", TypeName(varparam)) = 0 Then
Err.Raise 13 + vbObjectError, TypeName(Me), TypeName(Me) & ": Expecting Variant/Date or Variant/CTest: Found " & TypeName(varparam)
End If
' In the OP code it is not clear if the OP has
' restricted the use of the Make function to CTest.Make or
' allows the use of <instance>.Make.
' Both uses are legal as Make is a public method but
' in the spirit of declaring a PredecalredId it is
' preferable to restrict the use of Make to CTest.Make
' Thus the code below detects the use of Make by an instance.
If Not Me Is b.PredeclaredId Then
Err.Raise 445 + vbObjectError, TypeName(Me), TypeName(Me) & ": Make should only be used with the PredeclaredId"
End If
' Instruct the PredeclaredId that New is allowed
AllowNew = True
With New CTest
Set Make = .Self(varparam)
End With
' Instruct the PredeclaredId to disallow the use of new
AllowNew = False
End Function
Public Function Self(ByVal varparam As Variant) As CTest
' This code is inside the new instance that is being constructed.
' Therefore there is free access to the private variables of the
' instance 'under construction'
' Its a little difficult to untangle the OP logic for what constitutes
' the birthday so the Case statement below may well be incorrect
Select Case TypeName(varparam)
Case "Empty", "Null", "Nothing"
' The Me in the OP code occurs in the Make function and
' consequently refers to the instance of which Make was called.
' IF make was used as discussed above this implies that Me is b.PredecalredId
' only if the OP has adhered to CTest.Make
' If this is the case????
p.Birthday = DateValue("1/1/1800")
' The OP assigns otherdata in the case of nothing
' using Me.Otherdata. The Me will now refer to the
' the instance under construction so it is likely that a second
' parameter will be required for the Make function
'
Case "Date"
p.Birthday = CDate(varparam)
Case "CTest"
Dim myCTest As CTest
Set myCTest = varparam
p.Birthday = myCTest.Birthday
Case Else
Err.Raise 13 + vbObjectError, TypeName(Me), TypeName(Me) & ": Expecting Variant/Date or Variant/CTest: Found " & TypeName(varparam)
End Select
Set Self = Me
End Function
' The alternative to the AllowNew property is to have a public AllowNew field.
' but as the code below is bolerplate and can be copied to new classes without issue
' I'm happy to use the code below.
' Due to the differentiation of p,s,b
' we have an easily identifiable warning to check if we
' see anything but the p. prefix in Property declarations.
Public Property Get AllowNew() As Boolean
If Me Is b.PredeclaredId Then
AllowNew = p.NewIsAllowed
Else
AllowNew = b.PredeclaredId.AllowNew
End If
End Property
Public Property Let AllowNew(ByVal Value As Boolean)
If Me Is b.PredeclaredId Then
p.NewIsAllowed = Value
Else
b.PredeclaredId.AllowNew = Value
End If
End Property
Public Property Get Birthday() As Date
Birthday = p.Birthday
End Property
Public Property Let Birthday(ByVal val As Date)
p.Birthday = val
End Property
Public Property Get OtherData() As Variant
OtherData = p.OtherData
End Property
Public Property Let OtherData(ByVal val As Variant)
p.OtherData = val
End Property
Public Property Set OtherData(ByVal val As Variant)
Set p.OtherData = val
End Property
I'm currently trying to build a collection of items wherein a collection might contain an another collection as an Item within.
I've set two collections and created a class module for each:
col1 - (linked to Class1); and col2 - (linked to Class2)
Below are my Class Modules:
Class1:
Option Explicit
Private pTestC1A As String
Private pTestC1B As Collection
Public Property Let TestC1A(Value As String)
pTestC1A = Value
End Property
Public Property Get TestC1A() As String
TestC1A = pTestC1A
End Property
Property Set TestC1B(col2 As Collection)
Set pTestC1B = col2
End Property
Property Get TestC1BElements(v As Integer) As String
TestC1B = pTestC1B(v)
End Property
Class2:
Option Explicit
Private pTestC2A As String
Public Property Let TestC2A(Value As String)
pTestC2A = Value
End Property
Public Property Get TestC2A() As String
TestC2A = pTestC2A
End Property
Below is my Module code
Sub Test()
Set col1 = New Collection
Set col2 = New Collection
Set cV = New Class1
cV.TestC1A = "First Collection"
Set aV = New Class2
aV.TestC2A = "Second Collection"
sKey1 = CStr(aV.TestC2A)
col2.Add aV, sKey1
Set cV.TestC1B = col2
sKey2 = CStr(cV.TestC1A)
col1.Add cV, sKey2
If Err.Number = 457 Then
MsgBox "Error Occured"
ElseIf Err.Number <> 0 Then Stop
End If
Err.Clear
Msgbox col1(1).TestC1A ' works fine
Msgbox col2(1).TestC2A ' works file
MsgBox col1(1).TestC1B(1).TestC2A ' does not work - 450 run-time error
End Sub
As per the above code, I'm successfully able to get the values of the items if I reference each collection respectively, however I'm getting a "Wrong number of arguments or invalid property assignment" run-time error if I try to get the item value in a nested fashion.
It would be appreciated if someone can help point out where I'm going wrong, and perhaps shed some light on the way the class module handles the Property Set & Get parameters for a collection.
You are missing a Get TestC1B property in your Class1 class module:
Property Get TestC1B() As Collection
Set TestC1B = pTestC1B
End Property
Once that is present you will be able to make calls to col1(1).TestC1B(1) and access it's .TestC2A property
Background
You did the right thing by using private variables in your classes and using properties to give read/write access to your private variables. You'll get a lot more control this way.
Property Get gives read access to that property (and broadly speaking, the underlying private variable). For example you can use Range.Address to return (read) the address of a range object.
Property Let and Set give write access to the property. Use Set for objects. For example Range.Value = 1 will write the new value to the range.
Consider, Range.Address = $A$1. Since there is no Property Set for the address of a range, this will not change the address of the range. It will consider the Range.Address part a Get call and evaluate something like $A$1 = $A$1 returning TRUE in this example
I am getting runtine error 91: Object vriable or With block variable not set whne I run a simple class manipulation code.
Here are my classes
cTask:
Private pMile As cMile
Public Property Get Mile() As cMile
Set Mile = pMile
End Property
Public Property Set Mile(Value As cMile)
Set pMile = Value
End Property
Private Sub Class_Initializer()
Set Me.Mile = New cMile
End Sub
cMile:
Private pstatus As String
Public Property Get status() As String
status = ppstatus
End Property
Public Property Let status(Value As String)
pstatus = Value
End Property
And the sub:
Sub testt()
Dim ct As New cTasks
ct.Mile.status = "compl"
Debug.Print ct.Mile, ct.Mile.status
End Sub
The code goes from the sub to the get property in cTask. When about to execute the "End Propety" line the error pops up.
I guess something must be wrong with my classes but I don not know what. I have just recently started using classes. Any ideas?
Thank you
You have a typo:
Class_Initializer()
should be
Class_Initialize()
This prevents ct's Mile from being created so accessing it raises the error you see.
ppstatus is also spelt incorrectly.