Object from module sees private variable from class - vba

I don't know if I'm doing wrong, but I think this is not normal.
I have class clsPerson
Private pNameFirst As String
Public Property Get NameFirst() As String
NameFirst = pNameFirst
End Property
Public Property Let NameFirst(sNameFirst As String)
pNameFirst = sNameFirst
End Property
Now in Module I have procedure test
Sub test()
Dim Person As New clsPerson
Person.NameFirst = "test"
End Sub
When I look at Locals and unwrap Person object I can see there my private variable pNameFirst.
Why?

The locals window will still display your Private variables from the class. What you can't do in the module is alter the value directly of the private variable.
The locals window will show all your declared variables and ignore scope.
Edit:
See further discussion here:
Should I have to have duplicate values in VBA class objects?

Related

Add A Method To A Property Of A Class

I am confused. I am new to VBA classes. I want to add multiple methods to a property of a class, or add properties to another property. I may not have the terminology correct?
I can add one property, but I want to drill down deeper.
For instance if I make a class person:
PersonClass.Features.Hair.Texture.Color
PersonClass.Features.Hair.Texture.Style
PersonClass.Features.Hair.Length
I am not sure how to go about this.
e.g.
MyClass.MyProperty.MyMethod1
MyClass.MyProperty.MyMethod2
MyClass.MyProperty.MyMethod3
or
MyClass.MyProperty.MyMethod1.MyMethod2
Here is an example to illustrate the concepts mentioned in the comments:
Main Form
Option Explicit
Private Sub Form_Load()
Dim p As Person
Set p = New Person
p.Features.Hair = "Red"
MsgBox p.Features.Hair
End Sub
Person Class
Option Explicit
Private m_Features As Features
Private Sub Class_Initialize()
Set m_Features = New Features
End Sub
Public Property Get Features() As Features
Set Features = m_Features
End Property
Features Class
Option Explicit
Private m_Hair As String 'this would actually be another class
'in your example
Public Property Get Hair() As String
Hair = m_Hair
End Property
Public Property Let Hair(ByVal Value As String)
m_Hair = Value
End Property

Class Method Can't Use Private Property of Same Class?

What am I misunderstanding about Private Properties in VBA classes? As a novice, I expected Private Properties to be accessible to other methods within the same class (module), but instead I get "Method or data member not found" when trying to compile.
Breaks as Private Property Let lngMarketID
Here's what I have in my standard module:
Option Explicit
Public Model As classModel
Set Model = New classModel
Model.Setup
with this class module named classModel:
Option Explicit
Private plngMarketID As Long
'plngMarketID Properties
Public Property Get lngMarketID() As Long
lngMarketID = plngMarketID
End Property
Private Property Let lngMarketID(ByVal lngMarketID As Long)
plngMarketID = lngMarketID
End Property
Public Sub Setup()
SetuplngMarketID
End Sub
Private Sub SetuplngMarketID()
Model.lngMarketID = CLng(DefaultLogicOptions.textboxMarketID.Value)
End Sub
Works as Public Property Let lngMarketID
The "Method or data member not found" highlights the .lngMarketID of the line in SetuplngMarketID. This compiles fine when I change Private Property Let lngMarketID to Public Property Let lngMarketID.
It's the "Model" in that line. Model is a global variable pointing to some specific instance of your class. From that object, only the public things are visible.
You want to refer to that from "inside" an arbitrary instance of your class, so just drop the Model prefix:
Private Sub SetuplngMarketID()
lngMarketID = CLng(DefaultLogicOptions.textboxMarketID.Value)
End Sub
You are calling "Model.lngMarketId", Model is the name of your variable of classModel object that isn't visible in the object it self.
you have to use "lngMarketId = Clng(something)"

Is there a keyword that can make a class variable readonly from outside the class but not on the inside?

Basically, the readonly keyword doesn't let me modify a field after I first create the class instance. I could use a property but in this case its just extra overhead. Is there a keyword to make a class field readonly from only outside the class?
make the field private, provide getter and setter for it.
Make the setter private.
This way the value can be seen from outside the class by the getter,but, cannot be set/written from outside the class.
this makes the property read-only from outside the class.
As others have stated, use a property. If you don't want to split the property into one Getter and one Setter then make the setter private.
Public Class Foo
Public Property Abc() As Object
Get
Return Me.m_Abc
End Get
Private Set(value As Object)
Me.m_Abc = value
End Set
End Property
Private m_Abc As Object
End Class
However: The common way is to set the access level of the field to Friend making it accessible within the same assembly, but not from outside the assembly.
Public Class Foo
Public ReadOnly Property Abc() As Object
Get
Return Me.m_Abc
End Get
End Property
Friend m_Abc As Object
End Class
No there isn't. This type is scenario is precisely why properties are provided in the first place. You get a whole lot of flexibility.
However, if you insist you want to use a read only field, you can use reflection to change the value:-
Public Class TestClass
Public ReadOnly MyNumber As Integer
Public Sub New()
'Readonly fields can only be changed this way
'in the constructor
Me.MyNumber = 900
End Sub
Public Sub ChangeNumber(ByVal num As Integer)
SetNumber(num)
End Sub
Private Sub SetNumber(ByVal num As Integer)
Dim fi = Me.GetType.GetField("MyNumber")
'Reflection can change the value of
'a read only field after construction
fi.SetValue(Me, num)
End Sub
End Class
Note that this is a very terrible thing. Reflection shouldn't be used for this sort of thing as you're going to take a performance hit. Just use properties and save yourself the trouble.

Using collections as properties of an object in VBA

With class module "Class1Test" as
Private pGreetings As Collection
Public Property Get Greetings() As Collection
Greetings = pGreetings
End Property
Public Property Let Greetings(Value As Collection)
pGreetings = Value
End Property
If I run the sub
Dim MyPhrases As Class1Test
Public Sub Test()
Set MyPhrases = New Class1Test
MyPhrases.Greetings.Add "Have a nice day"
End Sub
I get the a compile error "Argument not optional"
Why can't I add the string to the the collection myphrases.greetings ? Please forgive the newbie question. Just learning VBA.
A few things wrong.
Collection is an object, so you must use the Set keyword when assigning. Also in the Let procedure for consistency in naming conventions, I would use lGreetings instead of Value although that should not really matter.
Private pGreetings As Collection
Public Property Get Greetings() As Collection
Set Greetings = pGreetings
End Property
Public Property Let Greetings(lGreetings As Collection)
Set pGreetings = lGreetings
End Property
This will still raise an 91 error (Object variable or with block not set) because you have not instantiated the collection object. Probably the way you should do this is in the class module's Initialize routine.
Private Sub Class_Initialize()
Set pGreetings = New Collection
End Sub

.net dynamic loading

I've seen some other responses about this and they talk about interfaces but I'm pretty sure you can do this with classes and base classes but I can't this to work.
Public Class Behavior
Private _name As String
Public ReadOnly Property Name As String
Get
Return _name
End Get
End Property
Public Property EditorUpdate As Boolean
Public Sub New(ByVal name As String)
_name = name
EditorUpdate = False
End Sub
Public Overridable Sub Update()
End Sub
' runs right away in editor mode. also runs when in stand alone game mode right away
Public Overridable Sub Start()
End Sub
' runs after game mode is done and right before back in editor mode
Public Overridable Sub Finish()
End Sub
' runs right when put into game mode
Public Overridable Sub Initialize()
End Sub
' runs when the game is complete in stand alone mode to clean up
Public Overridable Sub Destroy()
End Sub
End Class
Public Class CharacterController
Inherits Behavior.Behavior
Public Sub New()
MyBase.New("Character Controller")
End Sub
Public Overrides Sub Update()
' TODO: call UpdateController()
' THINK: how can UpdateController() get the controller entity it's attached to?
' Behaviors need a way to get the entity they are attached to. Have that set when it's assigned in the ctor?
End Sub
End Class
Dim plugins() As String
Dim asm As Assembly
plugins = Directory.GetFileSystemEntries(Path.Combine(Application.StartupPath, "Plugins"), "*.dll")
For i As Integer = 0 To plugins.Length - 1
asm = Assembly.LoadFrom(plugins(i))
For Each t As Type In asm.GetTypes
If t.IsPublic Then
If t.BaseType.Name = "Behavior" Then
behaviorTypes.Add(t.Name, t)
Dim b As Behavior.Behavior
b = CType(Activator.CreateInstance(t), Behavior.Behavior)
'Dim o As Object = Activator.CreateInstance(t)
End If
End If
Next
Next
When it tries to convert whatever Activator.CreateInstance(t) returns to the base class of type Behavior I'm getting invalid cast exception. That type should be of CharacterController which is defined as a child of Behavior so why wouldn't it let me cast that? I've done something like this before but I can't find my code. What am I missing?
This may not be an answer to your question (it also might resolve your exception -- who knows), but it is something that needs to be pointed out. These lines:
If t.IsPublic Then
If t.BaseType.Name = "Behavior" Then
Should really be changed to one conditional like this one:
If t.IsPublic AndAlso (Not t.IsAbstract) AndAlso _
GetType(Behavior.Behavior).IsAssignableFrom(t) Then
Otherwise, if somebody defines a random type called "Behavior" in their own assembly and derives it from another type, your code will think it is a plugin. Additionally, if someone derives your Behavior type and then derives that type (two levels of inheritance) this code will incorrectly skip over that type. Using the IsAssignableFrom method is a quick and easy way to ensure that one type does actually derive from the specific type you want (instead of any type that shares the same name), even if there is another type in between your types in the inheritance tree. The additional check against t.IsAbstract will also ensure that you don't try to instantiate an abstract subtype of your base plugin type.
This works for me:
Dim ctor As Reflection.ConstructorInfo = _
t.GetConstructor(New System.Type() {})
Dim o As Object = ctor.Invoke(New Object() {})
Dim plugin As Plugin = TryCast(o, Plugin)
(If I find t, I invoke the parameterless constructor.)
[I just realized this is probably what Activator.CreateInstance does, so I replaced my code with yours and it worked your way -- so this probably won't help you]