Unkown Cls/Collection Holding An Event (4 Viewers)

Thank you both so much for extremely detailed high quality help. As mentioned I did many iterations & playing around massively all day long trying to understand what was going on & just to get it to work. I want to understand classes & events amongst other things. The input I have received from you both here is invaluable so I am extremely grateful. I have a long way to go in learning still.

Double check that you do not have a mouse up event in the form itself...

You ran the code and it hung. Then you saved your form from design view and if I now look in your form this is in all the events
=handleRightClickOnBillAndPageF()
You're right as usual. No indication in the menu but debugging the controls individually the mouseUpEvent still assigned to the controls. But I do not understand as how the events stayed there. In the book they die with the termination of the cls.
Is the lesson here that when saving the form in design-view it saves any VBA assigned properties to the frm. I think the event's have stayed around because as you point out I did not assign them to the cls, but wrote them to the property of the form. I'm surprised Access does not display [Event Procedure] in the form's properties to indicate VBA alterations to the properties of said frm.

But unless there is more to it, there is no need for the class...
Thanks, yeah I'm finding it difficult to find where to use classes; desperately want to understand them as I think it's a level up.
You need a collection outside of the class to hold the instances. Now you are declaring a bunch of instances of your cls that has a property of a collection that only holds an instance of itself. Not only does that do nothing, but I guess it is a circular reference. You created a property in a class that references the class..
Thanks - a valuable lesson learned here. I was playing around creating collections all over the place trying to learn. I didn't grasp it but with your help I'm one step further to understanding.

The definition of a memory leak is that the objects are no longer accessible in code. The variables they were assigned to on creation no longer exist, but their references with each other are still valid, so garbage collection does not destroy them. You could only find them back from memory if you used the ObjPtr() function to save the actual memory address of the object before all your known variables go out of scope.

WithEvents object variable maintains a collection of references to its subscribers so it knows what handlers to call when the event fires. If you have multiple WithEvents variables that expose the same instance of an object--like some kind of global notification object--then you have to explicitly destroy your WithEvents variables too.

If I create circular references in code in a form, I handle the Form_Unload() event and destroy that structure explicitly...
Thanks, extremely interesting & valuable lesson learned. Thank you for explaining it so well. So presumably an unterminated WithEvents var costs more memory when leaked than another var. I have not read this anywhere else yet so big thanks.
Correct me if I'm wrong & not trying to justify myself here at all - are the memory leaks terminated at shut-down of the application?

you do not trap the event in the class. You would add it like this in your class.

Code:
Private Sub mObjTxtBox_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)

End Sub

I hate to say it but looks like you took three separate examples and kind of mashed them together all incorrectly.
1. Using a function to handle multiple controls
2. Using a collection outside of the class to hold instances of the class
3. Using with events to trap a controls event in the class.
Thanks for your honest feedback. Your input has really helped me try to understand it. Don't forget the CommandBar, I made a mess of that too :eek:.
I think I get the point you're making. I assigned the event to the form property event itself:
Code:
Private WithEvents mObjTxtBox As TextBox

 mObjTxtBox.OnMouseUp = "=handleRightClickOnBillAndPageF()"
I was wondering why the format .OnMouseUp was giving me intellisence & I thinking why is this intellisence appearing like a property. This now makes sense why the Event is sticking around; I stupidly didn't even assign it to the cls.

the functionality you call at handleRightClickOnBillAndPageF() is encapsulated within the class. This promotes tidy code, because it reduces the pollution of your global namespace...
I would never have figured this out, thank you.

Beyond grateful to you both for the input & generosity in the time you've taken to explain & explain it so well. I have learnt so much from you both here that I simply wouldn't have been able to do on my own. It's embarrassing as I've been studying so hard I should've been able to grasp these concepts. Thanks again.

Two things that I am mystified to still though:
1 - There seems no User-Collection
2 - Ditto with user-classes
:eek::unsure:(n)
 
Last edited:
So presumably an unterminated WithEvents var costs more memory when leaked than another var.
Well, the bigger cost if you leak a WithEvents variable is that it may continue to handle events after its container closes. So maybe you assign the item to a variable on a subform, in which case a valid subform reference is added to the subscriber list of the WE variable. When the mainform closes, the subform--if you don't explicitly destroy its WE variable--never goes out of scope and continues to handle events.

are the memory leaks terminated at shut-down of the application?
Yes. My understanding is that Windows allocates memory in respect the parent application. If that closes, I believe Windows frees up all memory that application consumed. But I am not very knowledgeable in this domain. This is just what I believe.

I have not read this anywhere else yet so big thanks.
This is very much a corner case in VBA, and only occurs if the lifetime of an instance assigned to a WithEvents variable exceeds the lifetime of the object that contains it.
- In some cases it is handy to write a single instance class that raises system-wide notification events, say cNotify. Then, when you create a new instance of an object, say Form1, you assign the single cNotify instance to a WE variable inside Form1. This enables Form1 to handle events raised by objects it knows absolutely nothing about. This is very useful in a responsive UI scenario, but not very commonly implemented in VBA. But in this case, a valid reference to Form1 is added to cNotify's subscriber list and when Form1 closes, if you don't specifically destroy its reference to cNotify, it never goes out of scope and continues to handle events.

Another thing I would say: don't let fear of memory leaks stop you from exploring classes!!! The cost of a memory leak is far less than the payoff of class module solutions.

Two things that I am mystified to still though:
1 - There seems no User-Collection
2 - Ditto with user-classes
What do you mean by "User-Collection" and "User-Classes?" Do you mean as parallels to the Forms collection?
 
leak a WithEvents variable is that it may continue to handle events after its container closes.
How interesting, suprised this info is not more ubiquitous.

write a single instance class that raises system-wide notification events, say cNotify. Then, when you create a new instance of an object, say Form1, you assign the single cNotify instance to a WE variable inside Form1. This enables Form1 to handle events raised by objects it knows absolutely nothing about.
Helpful; thanks.

What do you mean by "User-Collection" and "User-Classes?" Do you mean as parallels to the Forms collection?
Just a user-defined class, regardless of form or not. A user-created class. Not necessarily constrained by a form (if classes can only be created by forms then fine, a parallel to the form). I mean a class that is not one of Access'. One the user has created. It would be handy if Access had a tool to see the instantiations of each class; where they were created in relation to the stack. Kind of like the Locals/ Watches window but more friendly in it's hierarchical structure. I'm forgetting about the watch window here. I think I'm just getting a bit overloaded with classes.

User collection; ditto; one the user has instantiated. Not one of Access' collections. A collection itself that contains all the user-created collections.
 
You need a collection outside of the class to hold the instances. Now you are declaring a bunch of instances of your cls that has a property of a collection that only holds an instance of itself. Not only does that do nothing, but I guess it is a circular reference. You created a property in a class that references the class..
Sorry @MajP I'm utterly lost here. Forgetting the advanced stuff & just trying to learn the basics, even though I'll probably remove it but so I learn. Atm I cannot seem to get the cls to stick around in the collection.
1 - I cannot get the event to fire when tying it to the cls.
2 - If there were no txtboxes found in the form then I imagine I'd run into an error in trying to delete items from an empty collection?

I know the below is frivolous, I'm just trying to learn is all.

ClsDalsTxtBox
Code:
Public WithEvents mObjTxtBox As TextBox

Public Sub fInit(ltBox As TextBox)
  Set mObjTxtBox = ltBox
  mObjTxtBox.OnMouseUp = "[Event Procedure]"
End Sub
Private Sub Class_Terminate()
  Set mObjTxtBox = Nothing
End Sub

Private Sub mObjTxtBox_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
  Debug.Print "y"
End Sub

Frm
Code:
Private dalsObj As clsDalsTxtBox
Private mColRcControls As collection

Private Sub Form_Load()
  Dim ctrl As Control
 
  Set dalsObj = New clsDalsTxtBox
  Set mColRcControls = New collection
 
  For Each ctrl In Me.Section(acDetail).Controls
    If ctrl.ControlType = acTextBox Then
      dalsObj.fInit ctrl
      mColRcControls.Add dalsObj
      Debug.Print ctrl.name
      Debug.Print ctrl.OnMouseUp
    End If
  Next ctrl
End Sub

Private Sub Form_Unload(Cancel As Integer)
  Dim obj As Object
  For Each obj In mColRcControls
    mColRcControls.Remove 1
  Next obj
  Set mColRcControls = Nothing
  Set obj = Nothing
End Sub
 
Last edited:
First look that looks correct. I will test in mine to see if I am not seeing something.
 
Thanks, I don't think the collection is hanging around so the pointer is being erased; alas the event is not firing.
 
Your Frm code needs to create a new instance of clsDalsTxtBox inside the loop. Each TextBox should be loaded into a new unique instance. Your code creates only one instance, and then inside the loop keeps replacing the TextBox it contains with the next one. Consider...
Code:
Private m_col As Collection

Private Sub Form_Load()
    Dim ctrl As control
    Dim ctb As clsDalsTxtBox
 
    Set m_col = New Collection
 
    For Each ctrl In Me.Section(acDetail).Controls
        If ctrl.ControlType = acTextBox Then
            ' create and load new class instance
            ' inside the loop, because you need a
            ' new one for each TextBox
            Set ctb = New clsDalsTxtBox
            ctb.fInit ctrl
            m_col.Add ctb
        End If
    Next ctrl
End Sub

Private Sub Form_Unload(Cancel As Integer)
    ' we can't modify collection contents and
    ' enumerate it at the same time, so we use
    ' Do...While and check count of members
    Do While m_col.Count
        m_col.Remove 1
    Loop
    Set m_col = Nothing
End Sub
See if that gets you closer...
 
I see it now
Code:
Private Sub Form_Load()
  Dim ctrl As Control
 
 
  Set mColRcControls = New Collection
 
  For Each ctrl In Me.Section(acDetail).Controls
    If ctrl.ControlType = acTextBox Then
      Set dalsObj = New clsDalsTxtBox
      dalsObj.fInit ctrl
      mColRcControls.Add dalsObj
      'Debug.Print ctrl.Name
      'Debug.Print ctrl.OnMouseUp
    End If
  Next ctrl
End Sub

You set the instance outside the loop. So you only made 1
 
Thanks guys, greatly appreciated. That is strange, I would've thought as there was an instantiation I could've used that. I would've thought setting the value to the new ltBox in the cls fInit would've caused it to reset regardless. In my little mind there's one instance of the cls used in the frm.

Interestingly if I instantiate the cls after the collection instantiation it still works.

Code:
Private Sub Form_Load()
  Dim ctrl As Control
 
  Set mColRcControls = New collection
  Set dalsObj = New clsDalsTxtBox
  For Each ctrl In Me.Section(acDetail).Controls
  Set dalsObj = New clsDalsTxtBox
    If ctrl.ControlType = acTextBox Then
    
      dalsObj.fInit ctrl
      mColRcControls.Add dalsObj
      Debug.Print ctrl.name
    End If
  Next ctrl
End Sub
 
Last edited:
Remember, a class instance is a discrete and unique object. Your clsDalsTxtBox class is designed as a wrapper around--or container for--a single TextBox. As such, you need a new and different container instance for each different TextBox you wish to contain, and this is why you need a user defined collection.
The Access.Form.Controls collection contains many discrete and unique instances of Access.Control. You are concerned with a subset of those, only the TextBoxes, so you make a custom collection, wrap each TextBox in a new unique container, and then add your containers to a collection for persistence.
Then when a unique TextBox raises an event, its unique container handles that event exclusively.
 
interestingly if I instantiate the cls after the collection instantiation it still works but now and again it does not fire (most of the time it does).
The before and after the collection is irrelevant. The inside and outside the loop is.

Think of decorating Christmas Cookies, where you use a cookie cutter to cut out a christmas tree cookie and then decorate it.

Think of this line
Code:
Set dalsObj = New clsDalsTxtBox
as creating a cookie with a cookie cutter. But that cookie does not have any customized sprinkles, frosting, candies.

This customizes the cookie
Code:
dalsObj.fInit ctrl
If you do this outside the loop
Code:
Set dalsObj = New clsDalsTxtBox
You cut out only one cookie. Inside the loop you customize the same cookie each time overwriting what you did previously. When the loop is finished you have one cookie customized to the last customization. In your case the last control on your form.
It works for one control only and that was the last control "customized"

If you put this in the loop
Code:
Set dalsObj = New clsDalsTxtBox

Then you cut out a cookie each time. You then customize the newly cut out cookie. And hold it in a collection.
 
For even more flexibility, create a cTextBoxRightClickHandler class, and pass in a cBillAndPageFStrategy class ( which exposes an Execute() method ).
At runtime, determine which strategy you want to run, pass the right one to the textbox right click handler, and the rest is simple.

Sorry @MarkK, standard classes clicked yesterday thanks to you both & I understand the benefit of your advanced decoupling (big thanks).
If you get a little time in your day would be most grateful if you could provide input on where I'm going wrong here? Not to worry if not as already taken up a massive amount of y'all time.

1754648722908.png


Frm:
Code:
'Initialization & Demoltion
Private Sub Form_Load()
  Dim ctrl As Control
  Set mColRcControls = New collection

  For Each ctrl In Me.Section(acDetail).Controls
    If ctrl.ControlType = acTextBox Then
      Set mDalsObj = New cTextboxRightClickStrategyHandler
      Set mThisParticularStrategy = New cMyStrategy
     
      mDalsObj.Init mDalsObj, mThisParticularStrategy
      mColRcControls.Add mDalsObj
      Debug.Print ctrl.name
    End If
  Next ctrl
End Sub

cTextboxRightClickStrategyHandler
Code:
Private WithEvents tb_ As Access.TextBox
Private stg_ As Object

Public Function Init(tb As Access.TextBox, stg As Object) As cTextboxRightClickStrategyHandler
  Set tb_ = tb
  Set stg_ = stg
  Set Init = Me
End Function

Private Sub tb__MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
    If Button = vbKeyRButton Then stg_.dalsExecute
End Sub

cMyStrategy
Code:
Private mObj1_ As Object

Public Function Init(lObj1 As Object) As cMyStrategy
  'pass in the objects you need to execute the strategy in this constructor
  Set mObj1_ = lObj1
End Function

Public Sub dalsExecute()
  'execute a strategy here using objects like obj1_
  Debug.Print "So complicated - or a feeble mind?"
End Sub
 
Last edited:

Users who are viewing this thread

Back
Top Bottom