How to access public class property and preserve/modify an instance of class property value in several modules - vba

Class LoanApplicant
Private mstrPropertyName As String
Property Get Name() As String
Name = mstrPropertyName
End Property
Property Let Name(rData As String)
mstrPropertyName = rData
End Property
Sub to initialise the class and assign a value to the property
Public ApplicantName As String
Sub Initilise()
Dim Applicant1 As LoanApplicant
ApplicantName = "Steve"
Set Applicant1 = New LoanApplicant
Applicant1.Name = "Frank"
End Sub
Sub to print, first message box works as it is a public variable but second does not.
Sub CreateClass(Applicant1 As Object)
Call Initilise
MsgBox (ApplicantName)
MsgBox (Applicant1.Name)
End Sub
Create a class with a property
Then, I have two modules. let's say in the first module, I initilise the class and create an instance of a class, assigned value to the class property etc.
Question is, how do I access the same instance property value in another module? I have tested in my example that an instance of the class would be destroyed when exit a sub. the value of the class property is not preserved.
Thanks
Rational for the question is that in real world, when create an applicant class, it can have multiple applicant instance, applicant1, applicant2 etc. At various modules, the program would have to modify the same applicant's attributes(properties).

In your Initilise procedure, once you create an instance of LoanApplicant and assign the Name property a name, you need to call your CreateClass procedure and pass it your newly created object.
Here's an example for you to look at. Notice, though, I changed the name of your procedure from CreateClass to DisplayMessage, in order to reflect its true purpose.
Public ApplicantName As String
Sub Initilise()
ApplicantName = "Steve"
Dim Applicant1 As LoanApplicant
Set Applicant1 = New LoanApplicant
Applicant1.Name = "Frank"
DisplayMessage Applicant1
End Sub
Sub DisplayMessage(Applicant As Object)
MsgBox ApplicantName
MsgBox Applicant.Name
End Sub

So I got a new version of what I want.
Overall module and subs
Sub Main()
Dim Applicant1 As LoanApplicant
Set Applicant1 = New LoanApplicant
ApplicantCreate Applicant1
IncomeEntry Applicant1
ExpenseEntry Applicant1
MsgBox (Applicant1.name)
MsgBox (Applicant1.Income)
MsgBox (Applicant1.Expense)
End Sub
Sub ApplicantCreate(Applicant As Object)
Applicant.name = "Frank"
End Sub
Sub IncomeEntry(Applicant As Object)
Applicant.Income = 100000
End Sub
Sub ExpenseEntry(Applicant As Object)
Applicant.Expense = 5000
End Sub
Each sub can be placed into different module that does not change the results of what I wanted.
This is just a simplified version of the business logic it would apply in each sub. In reality, it can be much more complex. So, I got that.
Next question in line is whether it is possible to have an alternative approach? I mean, this works fine when I know there is only ONE applicant. What if I do not know how many applicants would be generated in Main? Also, do I have to create all the instant of different object in Main? If I use this approach, I would think I have to but I just like to know if there are other options. For example, if I can create dynamic object variable in any of the sub?

Related

vba Class Property Values, setting default values, storing values to string

another stumped newb here, trying to wrap my head around a problem.
I'm trying to use a class module, instead of public variables, with property get and set.
I want to set these values in a userform with text boxes, and update a listbox in the userform as they are entered, preferably on a text box event _afterupdate,
When the user hits the save button I'd like the profile attributes to be stored to a range.
No doubt this is going to be a laughable mess, but I've been stumped for days, and I'm coming here hat in hand. I just can't figure it out.
Class Mod Example:
Private mProfileName As String
Private mStartDate As Date
Private mEndDate As Date
Private mOngoing As Boolean
Sub Class_Initialise()
'Set default values for properties
mLastName = "Enter Last Name"
mStartDate = "Enter Date"
mEndDate = Date
mOngoing = True
End Sub
'********************************
'The relevant property procedures:
'********************************
Property Get ProfileName() As String
ProfileName = mProfileName
End Property
Property Let ProfileName(Value As String)
mProfileName = Value
End Property
Property Get EndDate() As Date
EndDate = mEndDate
End Property
Property Let EndDate (Value As Date)
mEndDate = Value
End Property
Property Get Ongoing() As Boolean
Ongoing = mOngoing
End Property
Property Let Ongoing(Value As Boolean)
If mEndDate = Date Then
mOngoing = True
End If
End Property
In the Userform I currently have:
Option Explicit
'hopefully not needed:
'Private mTextBoxUpdated As Boolean
'Private mListBoxUpdated As Boolean
'Private mEnteredText As String
'Private mIndexText As String
Private DictThisForm As Dictionary
Private ProfileData As clsProfileData
Private Sub UserForm_Initialize()
Debug.Print "UserForm Intialised"
Set DictThisForm = New Dictionary
Debug.Print "DictThisForm Created"
Set ProfileData = New clsProfileData
Call UserForm_UpdateListBox
And this laughable mess: (not even close to working)
Sub UserForm_UpdateListBox()
With lbxListBox1
.Clear
.ColumnCount = 2
.AddItem
'.List(0, 1) = "Profile Name",ProfileData.ProfileName '
'another attempt
'the below throws a Type Mismatch Error
.AddItem ProfileData.ProfileName, "Profile Name"
.AddItem ProfileData.StartDate, "Start Date"
.AddItem ProfileData.EndDate, "End Date"
.AddItem ProfileData.OnGoing, "Ongoing?"
And (with privately declared module level variables)
Private Sub tbxProfileName_AfterUpdate()
ProfileData.FirstName = tbxProfileName.Text
enter code here
Call UserForm_UpdateListBox
End Sub
At the moment I'm just trying to test the class module and see if I can get the properties values into a variable, or better, get those property values into a range on a hidden page, maybe via a dictionary, and update the list from there? Getting the property values into a listbox seems to be an unwieldy mess...
Sub testClassProfile()
Dim getPropertyAsStringVar As String
Dim NewProfile As clsProfileData
Set NewProfile = New clsProfileData
getPropertyAsStringVar = NewProfile.FirstName
Debug.Print getPropertyAsStringVar
End Sub
Current Debug.Print output is "" ie zip. No default values.
Any advice greatly appreciated.
Please let me know if I'm asking too much at once and I'll try to narrow the scope of the question just to my current issue, I thought the context might be helpful...
Not really an "answer", but it's too hard to respond to your comment with another comment!
Here is an example of how you can pass a class to a userform. Might help!
The TestModule Code
Option Explicit
Public Sub test()
'//Basic declaration of a UserForm
Dim testUF As UserForm1
Set testUF = New UserForm1
'//Now we create a test class - see the Property Get/Set for the string.
Dim testC1 As Class1
Set testC1 = New Class1
testC1.TestString = "Hello"
'//In the UserForm, I've created a property that accepts a class. Assign the
"TestClass" instance tothe UserForm Instance
testUF.Class1 = testC1
testUF.Show
End Sub
...And here is the userform. Note the property that I've added to accept a class instance.
Option Explicit
Private classInstance As Class1
Public Property Get Class1() As Variant
Set Class1 = classInstance
End Property
Public Property Let Class1(ByVal vNewValue As Variant)
Set classInstance = vNewValue
End Property
Private Sub UserForm_Click()
Me.Caption = classInstance.TestString
End Sub
And the class:
Option Explicit
Private mtest As String
Public Property Get TestString() As String
TestString = mtest
End Property
Public Property Let TestString(ByVal vNewValue As String)
mtest = vNewValue
End Property
When you run the "Test" code, you will see that instances of a class and a userform (which is actually just a "Class" as well) are created, and one instance is passed to another. It is (behind the scenes) passed "by reference", so any changes made to TestCl within the userform would be retained outside of the class, allowing it to be modified by the UserForm, and then returned back to the controlling code for use later.
The signature for the Initialize method is
Private Sub Class_Initialize()
... that's not what you have so it will not be run when an object based on the class is created. To make sure it's correct, inside your class code module select "Class" from the left-hand drop-down at the top, then select the required method from the right-hand selection.
Edit: this looks off -
Property Get Ongoing() As Boolean
Ongoing = mOngoing
End Property
Property Let Ongoing(Value As Boolean)
If mEndDate = Date Then
mOngoing = True
End If
End Property
The Ongoing property doesn't really depend on the backing field or need to be set (since its value depends only on mEndDate, so you can drop the Let and just use a Get something like this -
Property Get Ongoing() As Boolean
Ongoing = (mEndDate <= Date)
End Property

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

VBA get instance name inside of class

Sub Main()
Dim test As New Class1
End sub
Class1:
Private Sub Class_Initialize()
msgbox(Name_of_Class_Instance)
End Sub
I want the msgbox to show "Test"
Can that be done in VBA?
As per my understanding, you are trying to get the class instance name which is declared in module.
First of all you need to do is create a class like below.
Public classname As String
'Below method is going to get the value from module.
Public Sub Class_Initialize()
MsgBox classname
End Sub
Second, create a module like below.
Sub getname()
Dim test As Class1
Set test = New Class1
'Here we are passing the class name as 'test' and executing the 'Class_Initialize' method
With test
.classname = "test"
.Class_Initialize
End With
End Sub
Now you will get the instance name of the class.

Access a class modules variables within a second class

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

Does setting an object to nothing also set it's child objects to nothing?

Say I have an object, Email, one of whose properties is an object called EmailSkinner.
The EmailSkinner is instantiated in the class_initialize subroutine like this.
private sub class_initialize()
set EmailSkinner = new MyEmailSkinner
end sub
Must I explicitly set the EmailSkinner object to nothing in the class_terminate subroutine of Email?
private sub class_terminate()
set EmailSkinner = nothing
end sub
Or does this happen automatically when I set the Email object itself to nothing?
This is an interesting question. Your assumption is correct any object's you instantiate inside the scope of the parent class will be released when the parent class is released from memory.
However as with all object instantiation in VBScript (and by extension Classic ASP) there is nothing wrong with explicitly releasing objects using the Class_Terminate event.
Remember though that "scope" is important here.
If your EmailSkinner object reference is declared outside of the parent class (regardless of whether it is instantiated inside the class) the reference will remain and will require Class_Terminate() to force the object reference to be released.
Examples
Object Reference is declared inside Class scope.
Class ParentObject
Private _ChildObject
Private Sub Class_Initialize()
Set _Object = new ChildObject()
End Sub
End Class
Object Reference is declared outside Class scope (wouldn't recommend this approach).
Dim GlobalObject
Class ParentObject
Private Sub Class_Initialize()
Set GlobalObject = new ChildObject()
End Sub
'GlobalObject reference will remain so we need to
'force it to be released.
Private Sub Class_Terminate()
Set GlobalObject = Nothing
End Sub
End Class
By default, Class objects are auto destroyed, but if you create new objects outside, you will need to release them from memory .
Is always recommended that we clean memory in all scenarios .
I made a small piece of code for you to test ( I hope this would be similar to what you are trying to explain, since you didn't show us your code ) .
This code help us to check if something remains in memory after some steps of execution and declaration ( just take out the apostrophes at bottom to test the code ) :
Class EmailSkinner
public color
public size
Private Sub Class_Initialize
color = "blue"
size = 300
End Sub
End Class
Class Email
public details
public name
Private Sub Class_Initialize
Set details = New EmailSkinner '//Module Scope
End Sub
Private Sub Class_Terminate
Set details = Nothing
End Sub
End Class
Set email1 = New Email '//Global Scope
With email1
.details.color = "black"
.details.size = 400
End With
''//Take out the apostrophe to test one of the next lines
'Response.Write email1.details.color '//ASP only
'wscript.echo email1.details.color '//Wscript only
Set email1 = Nothing