I have an object which creates a line in an sql table.
using that object i can create and modify the record.
I want to add a sub that would delete the record and than dispose the object.
Something like this:
class myRecord
int recordId
sub new(con as sqlconnection,col1,col2,col3)
sqlcommand ("insert into....")
recordId=.....
end sub
sub modify(colname,newvalue)
sqlcommand("update.... where...")
end sub
sub delete()
sqlcommand(delete from....where recordId....)
HERE I WANT TO TURN THE OBJECT into null or nothing
end sub
end class
dim record as myrecord(con,"val1","val2","val3")
myrecord.delete()
can I destroy or turn object to null from within the object?
Related
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?
I'm trying to loop a collection. I can expand and see "the elements" inside VBE but get the error:
"Object doesn't support this property or method".
In a standard module I have
Public collItems As Collection
The collection is populated inside a userform module on initialization
collItems.add cItems
cItems is an object made from New clsItems, which is the class module.
The class module consists of many userform controls like this:
Private WithEvents frm As msforms.Frame
Private WithEvents lbl As msforms.Label
Private WithEvents cmd As msforms.CommandButton
Private WithEvents txt1 As msforms.TextBox
Private WithEvents txt2 As msforms.TextBox
+++
Not sure if Private is the way to go on this.
When the userform finishes loading, a dynamic number of frames with all these textboxes inside each frame appears. It looks like a spreadsheet made from data inside MS Project. The command buttons job is to change a lot of the textbox's attributes/properties.
I want to loop the collItems collection but then I get an error.
I don't have any get or let in my class, just a single set property.
It might look stupid to add 10 unique textboxes inside the class, but that's how I made it work so far. All the form objects are given names that refer to row and column during creation and helps me identify them.
The failing code looks like this:
Sub changeBox(ByRef name As String)
For Each item in collItems.item(CLng(Replace(name, "cmd", "")))
'blabla
Next item
End Sub
This test works and shows all the elements I want to loop:
Set test = collItems.item(3) 'Meaning row 3 in userform
How do I loop my specific textboxes and change their attributes?
So now I want to loop the collItems collection and write my code. But then I get an error...
The collection holds a sequence of objects of type cItems. Thus to loop the collection items, declare an object of this type to be used as the iterator.
Note, the class members must be publicly exposed in order to be accessible outside the cItems class. Not sure how you instantiate them, but they could be exposed through a public read-only property.
Public Property Get Form() As msforms.Frame
Set Form = frm
End property
To loop:
Dim objCurrent As cItems, frm As msforms.Frame
For Each objCurrent in collItems
'here you can use the objCurrent to access the current item.
Set frm = objCurrent.Form
'...
Next objCurrent
To be able to get a control by name, create a dictionary in the cItems class to store the controls and use the key to retrieve the objects.
See an example below:
'dictionary
Private m_boxes As Object
Public Sub AddTextBox(ByVal Key As String, ByVal obj As msforms.TextBox)
m_boxes.Add Key, obj
End Sub
Public Function GetTextBox(ByVal Key As String) As msforms.TextBox
Set GetTextBox = m_boxes(Key)
End Function
Private Sub Class_Initialize()
Set m_boxes = CreateObject("Scripting.Dictionary")
End Sub
Private Sub Class_Terminate()
Set m_boxes = Nothing
End Sub
The to call it:
Dim txt As msforms.TextBox
Set txt = objCurrent.GetTextBox("txt1")
I have created a class where its children hold a necessary reference to their parent. This means, when the parent class goes out of scope in the routine which calls it, its terminate code is not called, as the children still hold a reference to it.
Is there a way of terminating the parent without having to manually terminate the children first too?
Current work around is to make the parent's terminate code (which contains code to terminate the children) public and to call it from the routine, but this is not ideal as then the parent is terminated twice (once manually, once when it leaves the caller sub's scope). But mainly I don't like having to call it manually
'Caller code
Sub runTest()
Dim testClass As parentClass
Set testClass = New parentClass
Set testClass = Nothing
End Sub
'parentClass
Private childrenGroup As New Collection
Private Sub Class_Initialize()
Dim childA As New childClass
Dim childB As New childClass
childA.Setup "childA", Me 'give name and parent reference to child class
childB.Setup "childB", Me
childrenGroup.Add childA 'add children to collection
childrenGroup.Add childB
End Sub
Public Sub Class_Terminate()
Set childrenGroup = Nothing
Debug.Print "Parent terminated"
End Sub
'childClass
Private className As String
Private parent As classParent
Public Sub Setup(itemName As String, parentObj As classParent)
Set parent = parentObj 'set the reference
className = itemName
End Sub
Public Property Get Name() As String
Name = className
End Property
Private Sub Class_Terminate()
Debug.Print Name;" was terminated" 'only called when parent terminates child
End Sub
Calling runTest() prints
childA was terminated
childB was terminated
Parent terminated
Parent terminated 'to get around this, you could just make another sub in the parent class
'to terminate its children
'but that still requires a manual call
Having reviewed your comments, I'm still not convinced you need to pass the parent into the child class. If your only reason for doing so is to create a kind of callback then you'd probably be better off passing an event delegate class to the child instead of the parent class and then simply handle the delegate's events in your parent class. As you've seen, your current structure is causing some object disposal issues and these can be avoided.
Simply create a class containing your child events. In this example I've called the class clsChildEvents:
Option Explicit
Public Event NameChange(obj As clsChild)
Public Sub NotifyNameChange(obj As clsChild)
RaiseEvent NameChange(obj)
End Sub
Now your code remains pretty much as is. The key difference is that you are passing the delegate instead of the parent to the child objects.
Parent class:
Option Explicit
Private WithEvents mChildEvents As clsChildEvents
Private mChildren As Collection
Public Sub SetUp()
Dim child As clsChild
Set child = New clsChild
child.SetUp "ChildA", mChildEvents
mChildren.Add child
Set child = New clsChild
child.SetUp "ChildB", mChildEvents
mChildren.Add child
End Sub
Private Sub mChildEvents_NameChange(obj As clsChild)
Debug.Print "New name for "; obj.Name
End Sub
Private Sub Class_Initialize()
Set mChildEvents = New clsChildEvents
Set mChildren = New Collection
End Sub
Private Sub Class_Terminate()
Debug.Print "Parent terminated."
End Sub
Child class:
Option Explicit
Private mClassName As String
Private mEvents As clsChildEvents
Public Sub SetUp(className As String, delegate As clsChildEvents)
Set mEvents = delegate
Me.Name = className
End Sub
Public Property Get Name() As String
Name = mClassName
End Property
Public Property Let Name(RHS As String)
mClassName = RHS
mEvents.NotifyNameChange Me
End Property
Private Sub Class_Terminate()
Debug.Print mClassName; " terminated."
End Sub
And then your module code:
Option Explicit
Public Sub Test()
Dim parent As clsParent
Set parent = New clsParent
parent.SetUp
Set parent = Nothing
End Sub
The immediate window output is as follows:
New name for ChildA
New name for ChildB
Parent terminated.
ChildA terminated.
ChildB terminated.
No, you can't automatically terminate the children by simply terminating the collection:
Public Sub Class_Terminate()
Set childrenGroup = Nothing
The Collection object does not clean up any object references when it is set equal to Nothing, and it doesn't have a 'RemoveAll' mechanism which does.
...And I wouldn't bet on 'Remove' doing that either, one item at a time. So you do indeed need to loop backwards through the members, in exactly the 'manual' process you're trying to avoid:
Public Sub Class_Terminate()
Dim i As Integer
For i = childrenGroup.Count To 1 Step - 1
Set childrenGroup(i) = Nothing
childrenGroup.Remove i
Next I
Set childrenGroup = Nothing
My advice would be: use a Scripting.Dictionary object instead of the VBA Collection:
Private childrenGroup As New Scripting.Dictionary
You'll need a reference to the Scripting Runtime (or to the Windows Scripting Host Object Model) and you may be surprised by the changed order of .Add Item, Key - and you will definitely be surprised by what happens when you request an item with a non-existent key.
Nevertheless, this works. The Dictionary does have a RemoveAll method, and this will clear all the references in its .Items when you call it:
Public Sub Class_Terminate()
childrenGroup.RemoveAll
You do need to call RemoveAll - it doesn't happen automatically if the dictionary goes out of scope - but that's as close as you'll get.
Note, also, that VBA runs on reference counting: if anything else has a reference to the children, the objects won't be released.
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
Is it possible to recall a string or object that was stored in a previous sub?
The below code gives you an idea as to what I am trying to do.
Sub StoreUserData()
Dim StorName as Object
End
Sub WriteUserFile()
'Recall StorName here
End Sub
You need to make it into a field:
Dim StorName as Object
Sub StoreUserData()
'Do stuff with StoreName
End
Sub WriteUserFile()
'Recall StorName here
End Sub
If it is declared within a method, it is a local variable and not visible outside the method.
I suggest reading about Scope in Visual Basic.
Local variables are only accessible within their respective code block. To be able to access it you would have to widen its scope like this:
Class MyClass
Dim storName as Object
Sub StoreUserData()
storName = something
End Sub
Sub WriteUserFile()
' Use storName here
End Sub
End Class