Solved Unkown Cls/Collection Holding An Event

Then, if you have a cTaxEngineFederal class, it might look like...
Code:
Private stg_ As Object
Private year_ As Long
Private fedTax_ As Currency
Private prvTax_ As Currency

Sub Init(Year As Long, Province As String)
    year_ = Year
    Set stg_ = GetTaxStrategy("Calc" & Province & "Tax")
End Sub

Sub CalcTotalTax()
    fedTax_ = CalcFedTax
    prvTax_ = stg_.Execute(fedTax_)
    
    MsgBox "Total Tax is " & fedTax_ + prvTax_
End Sub

Private Function CalcFedTax() As Currency
    ' returns federal tax
End Function
So this allows for runtime selection of an appropriate provincial tax calc strategy based on runtime data. And it keeps your code really tidy because each provincial strategy is discretely encapsulated in snap-in class expressly and independently developed and tested.
 
No, your first function does not create an instance. Somewhere you need to use the New keyword to create an instance. Your first function will raise an error because GetStrategy is not an object yet.
 
I wrapped all textboxes in my frm.description in a cls to assign a right-click CommandBar menu to all textboxes to save me doing it individually. Then added them to user-defined collection so the right-click event would stay around.

The trouble is I was playing around with the cls names & collection names I now no longer know what it's been assigned to; and if it is only a single instance.

How do i list all collections belonging to the form/ wrapped class & tidy up my form? Unsure of the name of the associated cls/ collection.
Somewhere in the CurrentProject/ CurrentDb I have rogue instances of a Collection and or Classes which I need to find. I thought MSys would help me out but they only show Access standard objects I think.

I would've thought there would be a way to list all user-declared Collections & Classes.

Have you tried using the object browser?

ObjectBrowserVBA.Collection.png


You can also query the Name field in the MsysObjects table for Types 1 and 3 objects:

Code:
SELECT vTbl.ObjectType, Count(vTbl.ID) AS CountOfID
FROM (SELECT Switch(Type=-32768,"Forms"
                   ,Type=-32766,"Macros"
                   ,Type=-32764,"Reports"
                   ,Type=-32761,"Modules"
                   ,Type=-32756,"Data Access Page"
                   ,Type In (1,4,6),"Tables (Inc. System Tbls)"
                   ,Type=5,"Queries"
                   ,Type=3,"Containers"
                   ,1=1,"Other Stuff Managed by Access") AS ObjectType, ID
      FROM MSysObjects
      WHERE Left(Name,1)<>"~") AS vTbl
GROUP BY vTbl.ObjectType;
 
Last edited:
I didn't spot Containers, so thanks for that. I have a limited understanding so have found the Object Browser to be most confusing; unable to find logic to it. Yes, Objects, Methods, Properties, Classes... but up until recently I've struggled to find the consistency in VBA. This topic is a big-step in getting a deeper understanding so I'm most grateful.

Trying to get a deeper understanding of memory leaks... I found the following tool; specific for Access to see the allocation of memory which users may find interesting:


1754823535357.png
 
I didn't spot Containers, so thanks for that.

Well, container types are not really applicable to what you're looking for since container collection and container objects are part of the DAO object model. The names Microsoft chose for container collection and container objects is ambiguous, confusing, and poorly documented. Nothing new with MS.

I have a limited understanding so have found the Object Browser to be most confusing; unable to find logic to it. Yes, Objects, Methods, Properties, Classes... but up until recently I've struggled to find the consistency in VBA. This topic is a big-step in getting a deeper understanding so I'm most grateful.

Trying to get a deeper understanding of memory leaks... I found the following tool; specific for Access to see the allocation of memory which users may find interesting:


For finding and analyzing memory leaks I use a tool named Process Explorer,
available for free at: https://learn.microsoft.com/en-us/sysinternals/downloads/process-explorer

AccessMemoryLeaks.png
 
Last edited:
Coolio, to clarify @MajP & @MarkK kindly identified the issue which was not instantiating the cls at each iteration. I've abused their kindness a little (sorry guys) by extending the thread a little & we've gotten into advanced decoupling; which I am extremely grateful for. This goes a massive way forward into understanding moreso how Access works & a better chance of being able to understand & benefit from the Object Inspector a bit more & becoming a more capable developer. At the moment I'm just a donkey 🐴, with the dream of one day becoming a fine stallion 🐎🏇🎠.
:ROFLMAO: :ROFLMAO:
 
Then you cut out a cookie each time. You then customize the newly cut out cookie. And hold it in a collection.
That is the best "soft" explanaiton of a Class Module I have seen to date...
 
Thanks, I'm becoming quite a nuisance, had problem after problem & been experimenting with it so many times & in different ways.
What can cause the collection to die when using the form as a subform? The event fires perpetually (as desired) when viewing the subfrm individually & the collection empties itself with a procedure.
But when using it as a subfrm the event fires only once. The collection does not remain, on emptying it there are no records/ don't think the collection (plain collection; not a collection cls) mCol even exists.

It's confusing that the event fires once at all. I think it fires once because a memory leak as I would think it should not fire at all. Presumably the last instantiation of mRcHandler & clsStrategy still exist (I don't think they should if I'm running properly?). Regardless the main issue I have is the disapeering collection I think.

It's also confusing that one cannot Debug the mCol after the Load event has ended (when viewing subfrm individually). As it has been declared at module lv surely it should be accessible in debug window. It clearly remains as on unload it is found with the same nr of objs I loaded into it. But outside of the procedure it cannot be accessed.
Makes sense that i cannot access when viewing the subfrm from the main frm as there is clearly an issue with the collection's persistence. But not when viewing the subfrm individually.

SubFrm
Code:
Private Sub Form_Load()
  Dim ctrl As Control
  Set mStrat = New clsStrategy
  For Each ctrl In Me.Detail.Controls
    If ctrl.ControlType = acTextBox Then
      Set mRcHandler = New clsTbRcHandler
      mRcHandler.AssignProperties ctrl, mStrat
      mCol.Add mRcHandler
    End If
  Next ctrl
End Sub

clsStrategy
Code:
Public Sub FindOutIfBillOrPage()
  Dim curID As String
  Dim result As eBillOrPage
  curID = Form_TenderBillsAndPagesF.CONCAT_ID
  result = IsItBillOrPage(curID)
  If result = Bill Then
    MsgBox "Bill"
  ElseIf result = Page Then
    MsgBox "Page"
  Else
    MsgBox "Dunno"
  End If
End Sub

clsTbRcHandler
Code:
Private WithEvents mTb As TextBox
Private mStrategy As Object

Public Function AssignProperties(lTb As TextBox, lStrategy As Object) As clsTbRcHandler
  Set mTb = lTb
  Set mStrategy = lStrategy
  lTb.OnMouseUp = "[Event Procedure]"
  Set AssignProperties = Me
End Function

Private Sub mTb_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
 If Button = vbKeyRButton Then mStrategy.FindOutIfBillOrPage
End Sub

Public Sub Dispose()
  Set mTb = Nothing
  Set mStrategy = Nothing
End Sub
 
Last edited:
Hey @dalski, can you post the Db?
• This stuff, as you know, gets tricky right out of the gate.
• It's way easier to understand code by running it.
Cheers,
 
Thanks Mark, I can't post it online as it's a project I've been working on for 8 months. I'll send you a DM.
 
Here is another approach that I have been using for years.

I find it very intuitive, easy to use, and mirrors how you do things in Access.

I am not disagreeing with Marks approach, but that is not how I would do it. The concept of the strategy makes sense to decouple the processing from trapping the event, but not sure why you would need a separate class when working in Access. In Access 9 times out of 10 I am employing the class through a form, and that form will determine what to do with that event. I do not see the utility of creating and managing a separate strategy class to determine what to do with that event since the form employing it is already the strategy class. One form can do X and another Y. Or at least I am not seeing the utility.

I find when working in Access there is a certain group of classes that you almost always are working with many objects. A custom collection class can make it very easy and intuitive to work with this group of objects. This makes it easy to add, remove, and iterate the objects. I discuss this here

The big thing that this does is that you never have to instantiate an object in your form and then add it to a collection. You build the collection class so that you can immediately add to the collection.

In this example I have a class MoveableLabel and a collection class MoveableLabels.
I use clear understandable names like VBA does. (Control, Controls, Field, Fields) My opinion, but if I inherited a database with objects like clsTbRcHandler I would loose interest pretty soon. So my classes are all readable names (even if they are long) and the collection class is a readable name with an s. We all know it is a class, so I do not add unreadable prefixes like cls.

To use my my moveablelabel class on a form I can simply add them to collection class. It is very intuitive
Code:
Private WithEvents MLS As MoveableLabels

Private Sub Form_Load()
  Set MLS = New MoveableLabels
  MLS.Add Me.lbl1
  MLS.Add Me.lbl2
  MLS.Add Me.lbl3
End Sub

This works by wrapping the MoveableLabel Init method

In the collection class the add function calls the initialize

Code:
Public Function Add(TheLabel As Access.Label) As MoveableLabel
  'create a new ML and add to collection
  Dim NewMoveableLabel As New MoveableLabel
  NewMoveableLabel.Initialize TheLabel, Me
  'Set m_MoveableLabel = NewMoveableLabel
  m_MoveableLabels.Add NewMoveableLabel, TheLabel.Name
  Set Add = NewMoveableLabel
End Function

Note that the add methods passes a reference of the collection class to the moveablelabel object. Now if the moveableLabel is in a collection it know the parent collection class.

Now the beauty is that you can still call the class without using a collection if for some reason you are only building one. Instead you could do something like
Code:
Private WithEvents ML As MoveableLabel
Private Sub Form_Load()
  Set ML = New MoveableLabel
  ML.Initialize lbl4
End Sub

This passing a reference of the collection class to the object may not pass the purist test, but it works well. Now I want to be able to add lots of objects to my collection class and then trap their events in the form. The way I do that is make the collection act as a universal listener and have it raise a collection event. It might be more "pure" to create a standalone listener class, but this works well. In other words when a moveableLabel object traps a controls event it calls its parent collection to raise an event. Then the parent collection raises an event and the event can be trapped in the Form.
This allows you to only declare a single collection class with events. Add ojbects directly to the class without creating them first. Then trap any of the object events by turning that into a collection event.

example in moveablelabel
Code:
Function ctlMouseDown(Ctl As Control, X As Single, Y As Single)
    XPos = X
    Ypos = Y
    mseDown = True
    RaiseEvent LabelSelected(Ctl)
    'call method in parent collection that raises an event
    If Not Me.ParentCollection Is Nothing Then Me.ParentCollection.RaiseLabelSelected Ctl
End Function

The moveablelabel raises an event and if using it without a collection class the user could trap that event.
It then calls a procedure in the parent collection class that raises an event. (if it is in a collection class)

In the collection class
Code:
Public Sub RaiseLabelDropped(Ctl As Control)
  RaiseEvent LabelDropped(Ctl)
End Sub
Public Sub RaiseLabelSelected(Ctl As Control)
  RaiseEvent LabelSelected(Ctl)
End Sub

This collection class is all cut and paste. It may sound like a lot but once you do it once you just copy paste and make some tweaks to customize the events.
Just determine the events that you want the class to raise and build mirror procedures in the collection class to raise the same events. It is basically a pass through.

Something to think about.
 

Attachments

What I dislike about using Control.Tag to group controls is you have to leave the IDE, open the Property Sheet, and poke around with individual controls.

What I like more is to explicitly create a control group as a form property, like...
Code:
Private Property Get MyTextboxGroup()
    MyTextboxGroup = Array(Me.Text0, Me.Text2, Me.Text4)
End Property

Private Sub SetGroupVisible(vGroup, State As Boolean)
    Dim var
    For Each var In vGroup
        var.Visible = State
    Next
End Sub
... and then you can just do...
Code:
    SetGroupVisible MyTextboxGroup, True
... as desired.

• To manage a group, you just add/remove a control reference to/from whatever enumerable you like to use to group objects.
 
Here is what the right click would look like.
Form

Code:
Private WithEvents RCTBs As RightClickTextBoxes
Private Sub Form_Load()
  Dim ctrl As Access.Control
  Set RCTBs = New RightClickTextBoxes
  For Each ctrl In Me.Controls
    If ctrl.Tag = "RC" Then RCTBs.Add ctrl
  Next ctrl
  'MsgBox RCTBs.ToString
End Sub
'****************************************************************************************************************************************************************
'-----------------------------------------------------------------------------------   Collection Class Event   -------------------------------------------------------------
'*****************************************************************************************************************************************************************

Private Sub RCTBs_RightClick(Ctl As Control)
  If Ctl.Value = "Bill" Then
    MsgBox "The Control value is BILL trappend from collection"
  ElseIf Ctl.Value = "Page" Then
    MsgBox "he control value is Page trapped from collection"
  Else
    MsgBox "Dunno"
  End If
End Sub

RightClickTextBox class
Code:
Private WithEvents m_RClickTextBox As TextBox
Private m_ParentCollection As RightClickTextBoxes

Public Event RightClick(ctrl As TextBox)

Public Sub Initialize(TheTextBox As TextBox, Optional TheParentCollection As RightClickTextBoxes = Nothing)
  Set Me.RClickTextBox = TheTextBox
  Me.RClickTextBox.OnMouseUp = "[Event Procedure]"
  Set Me.ParentCollection = TheParentCollection
End Sub

Private Sub m_RClickTextBox_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
 If Button = vbKeyRButton Then
  RaiseEvent RightClick(RClickTextBox)
  

  If Not ParentCollection Is Nothing Then
     Me.ParentCollection.RaiseRightClick RClickTextBox
  End If
 End If
End Sub
Private Sub Class_Terminate()
  Set mTb = Nothing
  Set ParentCollection = Nothing
End Sub
Public Property Get RClickTextBox() As TextBox
    Set RClickTextBox = m_RClickTextBox
End Property

Public Property Set RClickTextBox(ByVal objNewValue As TextBox)
    Set m_RClickTextBox = objNewValue
End Property

Public Property Get ParentCollection() As RightClickTextBoxes
    Set ParentCollection = m_ParentCollection
End Property
Public Property Set ParentCollection(ByVal objNewValue As RightClickTextBoxes)
    Set m_ParentCollection = objNewValue
End Property
 

Attachments

What I dislike about using Control.Tag to group controls is you have to leave the IDE, open the Property Sheet, and poke around with individual controls. . .
If you properly plan your design, you identify which objects you want to group and assign them a unique tag name when you begin building your app, and afterwards as you add new objects that warrant belonging to a group. It's no big deal to go all the way to the bottom of the property sheet and assign a tag name to an object. You can also do it in vba code.
 
If you properly plan your design,
Ha, I don't do things properly. Everything I touch needs to be edited, modified, renamed, revised and refactored.

• Exactly why, if I want to add a control to a group, I don't like having to ...
1) open the form in design view, 2) leave the IDE, 3) select the control 4) open the property sheet 5) find the last row on the second last tab 6) type out the entire GroupName 7) and then go back to the IDE.
• I'd rather just ...
1) type , Me. , and 2) select the control name.

• Also, if I want to remove a control from a group, I don't like having to...
1) open the form in design view, 2) leave the IDE, 3) select the control 4) open the property sheet 5) find the last row on the second last tab 6) delete the GroupName, 7) and then go back to the IDE.

• I'd rather just ...
1) delete the control name.

• Don't even get me started about if I have to rename a group.
• But if I rename a control, the compiler will find the mismatch.
 
Ha, I don't do things properly. Everything I touch needs to be edited, modified, renamed, revised and refactored.

• Exactly why, if I want to add a control to a group, I don't like having to ...
1) open the form in design view, 2) leave the IDE, 3) select the control 4) open the property sheet 5) find the last row on the second last tab 6) type out the entire GroupName 7) and then go back to the IDE.
• I'd rather just ...
1) type , Me. , and 2) select the control name.

• Also, if I want to remove a control from a group, I don't like having to...
1) open the form in design view, 2) leave the IDE, 3) select the control 4) open the property sheet 5) find the last row on the second last tab 6) delete the GroupName, 7) and then go back to the IDE.

• I'd rather just ...
1) delete the control name.

• Don't even get me started about if I have to rename a group.
• But if I rename a control, the compiler will find the mismatch.
This the funniest post I've seen all day. I kno using tags is cumbersome, but the beauty of it is that you only have to set it up once, then once in a while maybe add a tag to a new field you created. Remember I previously said you can also CRUD tag properties in vba code. Me thinks it's actually faster to use them than having to write code to create and maintain classes. But I'm not knocking that. If you prefer creating classes, so be it. I have a checklist I use when creating new apps. Setting up group tags, if needed, is one of the tasks.
 

Users who are viewing this thread

Back
Top Bottom