Quite simple; I've made a class with a method that's public since another class calls this method.
I would like the method only to show up on intellisense in the 2nd class which holds some special reference to the 1st one. Any other class or module should not be able to see the method.
Something along the lines of
Semi-Private (except for Class2) Sub ...
in the method of Class1, or in Class2
Can See Semi-Private methods of Class1
Instead of getting complicated with it, and using a roundabout approach, just use the tools meant for the job:
Class Module IFoo
Public Sub Bar()
' Interface methods are empty
End Sub
Class Module Foo
Private Type TFoo
Baz As String
End Type
Private this As TFoo
Implements IFoo
Private Sub Class_Initialize()
this.Baz = "I am a class that implements IFoo."
End Sub
Private Sub IFoo_Bar()
' Do Something
Debug.Print this.Baz
End Sub
Module Baz
Sub RunBaz()
Dim MyFoo As IFoo
' Note that this WILL NOT work. Nothing happens.
Set MyFoo = New IFoo
Debug.Print MyFoo.Bar
' Set MyFoo to be equal to a Foo (which implements IFoo)
Set MyFoo = New Foo
Debug.Print MyFoo.Baz
End Sub
This makes the methods only visible when the methods are being accessed through an interface which makes them public. Therefore, in order to use the methods in Foo we must first create an instance of Foo using an IFoo variable type.
Then, it is as simple as creating a new class which creates a Foo from an IFoo for its own use.
Class Module IImportantWorker
Public Sub DoSomethingImportant()
End Sub
Class Module ImportantWorker
Private Type TImportantWorker
Implementation As IFoo
End Type
Private this As TImportantWorker
Implements IImportantWorker
Private Sub Class_Initialize()
Set this.Implementation = New Foo
End Sub
Public Sub IImportantWorker_DoSomethingImportant()
this.Implementation.Bar
End Sub
You could get fancy from here and make a property of Foo that is exposed by IFoo that tells it whether or not it can work. This would have to be exposed through the IFoo interface (or, alternatively a separate interface so that the two interfaces must be used in conjunction).
Without locking the class (which I wouldnt recommend anyways, it seems foolish and pointless) Foo will still allow Bar if it is created as a IFoo. But if you just make Foo = New Foo then Foo will do nothing (or rather, expose nothing).
For additional resources, I highly recommend reading these excellent posts that go into greater depth about the processes:
Is VBA an OOP language, and does it support polymorphism?
https://rubberduckvba.wordpress.com/2016/06/16/oop-vba-pt-1-debunking-stuff/
https://rubberduckvba.wordpress.com/2016/07/05/oop-vba-pt-2-factories-and-cheap-hotels/
You may create the class that contains all properties and methods, name it BigClass:
Option Explicit
Dim i1 As Integer
Dim i2 As Integer
Dim i3 As Integer
Property Let var1(v As Integer)
i1 = v
End Property
Property Get var1() As Integer
var1 = i1
End Property
Property Let var2(v As Integer)
i2 = v
End Property
Property Get var2() As Integer
var2 = i2
End Property
Property Let var3(v As Integer)
i3 = v
End Property
Property Get var3() As Integer
var3 = i3
End Property
Then you can make classes that inherits BigClass methods, but not necessary all of them. For example, SmallClass1 that will use only var3 variable.
Option Explicit
Dim BigOne As BigClass
Private Sub Class_Initialize()
Set BigOne = New BigClass
End Sub
Private Sub Class_Terminate()
Set BigOne = Nothing
End Sub
Property Let var3(v As Integer)
BigOne.var3 = v
End Property
Property Get var3() As Integer
var3 = BigOne.var3
End Property
Then you can create SmallClass2 that use var2 and var1 but not var3. You get the point.
An clear example would be involved and lengthy and I have deposited one on my blog here VBA - Difference between Friend and Public with inter project references.
Essentially you use the Friend keyword but nothing happens until you split your code across workbooks which itself brings issues such as Instancing and factory functions. Example given should work though. Enjoy!
Related
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
I was wondering if there's a way in which I can share variables between instances of separate class modules?
I have two classes:
Class 1
Class 2
Inside class 1, I have multiple global variables which I would like Class 2 to have access to once instantiated.
I could use get and set properties for each of the variables but I have about 40/50 so it just seems a bit tedious.
So, instead, I'm trying to pass the current instance of Class 1 to Class 2 using set property.
I've created a minimal example to illustrate my current efforts:
Class 1:
Public test As String
Private Sub Class_Initialize()
Call setTest
Dim b As Class2
Set b = New Class2
End Sub
Public Property Set Classed(ByRef vClass As Class1)
Set vClass = Me
End Property
Public Sub setTest(t As String)
test = "Sam"
End Sub
Class 2:
Private Sub Class_Initialize()
Dim newClass As Class1
newClass.Classed = newClass
' Want to be able to access the test String from class 1
End Sub
Obviously what I am doing at the moment is incorrect, so am wondering if someone could point out where I'm going wrong and show me how to achieve this class sharing?
Just to add: when running the code, I receive a compile error at line: newClass.Classed = newClass. Error: Invalid use of property
Not too sure but I sense a bit of a Circular Reference in your example?
What Are Circular References?
A circular reference occurs when two objects hold references to each other.
You could try an alternative by exposing a Dictionary object through your class, where the Key will be your "variable name", and the Value will hold the actual value.
An example could be:
Class1
Option Explicit
Private mList As Object
Public Property Get List() As Object
Set List = mList
End Property
Private Sub Class_Initialize()
Set mList = CreateObject("Scripting.Dictionary")
End Sub
Private Sub Class_Terminate()
Set mList = Nothing
End Sub
Implementation:
Sub ClassTest()
Dim a As Class1
Set a = New Class1
Dim b As Class1
Set b = New Class1
a.List("VarName") = "Sam" 'Set
b.List("VarName") = a.List("VarName") 'Get / Set
Debug.Print b.List("VarName") 'Get
Set a = Nothing
Set b = Nothing
End Sub
'Output
'Sam
Assume the following example class which mimics the type of class generated from an XSD file:
Public Class MyClass
Public Class MyInnerClass1
Public Class MyInnerInnerClass1
Public Property MyProp1 as string
Public Property MyProp2 as string
...
End Class
...
Public Property MyInnerInnerClassProp1 as MyInnerInnerClass1
End Class
Public property MyInnerClassProp1 as MyInnerClass1
Public property MyInnerClassProp2 as MyInnerClass2
...
End Class
Notice that there are no constructors. The level of inner classes, in this particular case, can go 5 levels deep, possibly circularly, before hitting a base property such as Property MyProp1 as string.
How can I recursively iterate through ALL of the public writable properties and initialize them as new instances of that object type without constructors?
For example, here is my current code which only goes one level deep at the moment?
Private Shared Sub InitProperties(obj As Object)
For Each prop As Object In obj.[GetType]().GetProperties(BindingFlags.[Public] Or BindingFlags.Instance).Where(Function(p) p.CanWrite)
Dim type__1 = prop.PropertyType
Dim constr = type__1.GetConstructor(Type.EmptyTypes)
'find paramless const
If type__1.IsClass Then
Dim propertyInstance = DirectCast(FormatterServices.GetUninitializedObject(type__1.GetType()), Object)
'Dim propInst = Activator.CreateInstance(type__1)
'prop.SetValue(obj, propInst, Nothing)
InitProperties(propertyInstance)
End If
Next
End Sub
I did some small edits to your code to get it to work on the example class you provided. (Although I did change the string properties to Integer to avoid one error.) I also added an argument for limiting the number of recursive calls, and a check that a property is equal to nothing before initializing it. (This check will only make a difference if you have circular references between static classes.)
Private Shared Sub InitProperties(obj As Object, Optional ByVal depth As Integer = 5)
For Each prop As PropertyInfo In obj.GetType().GetProperties(BindingFlags.Public Or BindingFlags.Instance).Where(Function(p) p.CanWrite)
Dim type__1 As Type = prop.PropertyType
If type__1.IsClass And IsNothing(prop.GetValue(obj, Nothing)) And depth > 0 Then
Dim propertyInstance As Object = System.Runtime.Serialization.FormatterServices.GetUninitializedObject(type__1)
prop.SetValue(obj, propertyInstance, Nothing)
InitProperties(propertyInstance, depth - 1)
End If
Next
End Sub
I'm having trouble creating a sub that can create objects of a variable type on the fly. Here's an example of what I'm trying to achieve:
class systemSettings
'some properties
end class
Class fireSystemSettings
inherits systemSettings
'some additional properties
end class
Class windSystemSettings
inherits systemSettings
'some additional properties
end class
sub createSystem(systemType as Type, arg1 as object, arg2 as object)
Dim newSystem as New systemType(arg1, arg2)
systemCollection.add(newSystem)
end sub
I can't get it to work. I've done a fair bit of research, and looked at generic types, reflection, and other tools, but I'm having trouble determining how best to tackle this problem.
You're looking for Activator.CreateInstance(systemType)
Use generics for this:
Sub createSystem(Of T As {New, systemSettings})()
Dim newSystem As New T
systemCollection.add(newSystem)
End Sub
And call it with:
createSystem(Of windSystem)
To explain:
The term Of T lets you create a method that can be used for any type. Every time you call it for a new value of T, a new method is created in memory.
The term As {New, systemSettings} constrains T. It says that T must represent a type that is or derives from systemSettings. It also says that T must contain a default constructor: New() which is required for the command New T. Note that you cannot specify a more elaborate constructor as a generics constraint.
If you require parameters in your constructor, you can create an Initialise method in the base class. Because T is constrained to systemSettings, it is guaranteed that the Initialise method exists.
Class systemSettings
Public Overridable Sub Initialise(arg1 As Object, arg2 As Object)
'initialise properties
End Sub
'some properties
End class
Class fireSystemSettings
Inherits systemSettings
Public Overrides Sub Initialise(arg1 As Object, arg2 As Object)
'initialise properties
End Sub
'some additional properties
End Class
Class windSystemSettings
Inherits systemSettings
Public Overrides Sub Initialise(arg1 As Object, arg2 As Object)
'initialise properties
End Sub
'some additional properties
End Class
Sub createSystem(Of T As {New, systemSettings})(arg1 As Object, arg2 As Object)
Dim newSystem As New T
newSystem.Initialise(arg1, arg2)
systemCollection.add(newSystem)
End Sub
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]