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 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
 
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

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.
 
Thanks guys, learning a lot. I'm new to classes & this is probably a bit much for me at this early stage but I think if I can get a good understanding of them that's 90% of VBA (OOP) & less likely to come across issues later. It's interesting no matter how much you read you come across issues (MSN seriously lacking).

A problem here @MarkK pointed out that I was using a Form_FrmName reference; which can cause problems. I thought it would be nice to reference the form directly as it's object; allowing auto-update on it's name should i change it. But was unaware that if the form was not found it would then create a reference of it. He also kindly pointed out that an external reference outside the class to a procedure was the main issue. Obviously only Mark got handed the db so no one else could have been aware of this.

My original goal here was to assign a right-click event raiser globally to controls in the Detail section of a form & have a single event/ function/ whatever handle them. A way of assigning an ObjVar_ObjVarEvent of the user's type. The benefit being I may add/ delete controls... later to this frm & this coding pattern would save me in the future (more importantly I'm trying to learn). Was tyring to use classes to learn & get the most of out the exercise as I have little experience with classes.

Disregarding classes for a moment, I was thinking of using a function to handle multiple events from @MajP 's fine https://www.access-programmers.co.u...-you-need-to-know-about-access-events.308963/ in the form directly. But I cannot see how one could pass an argument (Button) of the MouseUp event if one was to assign a custom function? Seems that the function name must be a literal; "=fnName()" with a set of parentheses. Cannot handle concatenation, even from VBA. Be great if it is possible to use parameters with the function when assigning the function to handle multiple events.
I just discovered that there is a:
Code:
Private Sub Detail_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
but suprisingly does not seem to work.

The perfect solution would be if VBA supported below in pseudo code:

SubFrm:
mctrl as Control
Code:
Private Sub Form_Load:
  Dim mctrlAs Control
  For Each ctrl In Me.Detail.Controls
    If mctrl.ControlType = acTextBox Then
      mctrl.OnMouseUp = "[Event Procedure]"
    End If
  Next ctrl
End Sub

Private Sub mctrl_MouseUp (Button As Integer, Shift As Integer, X As Single, Y As Single)
  If Button = vbKeyRButton Then
    MsgBox "Honour the coders"
End Sub

It's quite difficult to see when to use a class over the data in tables... My main take at this point is they are of benefit in forms in grouping things... Done a few tutorials relating to data... but I think it's hard for a user to comprehend when a class prevails/ complements over the schema design... double-work as you've now got data in classes & data in tables which require updating...

It seems the options are:
  1. You separate the object itself as a class (object) & the object handler as another class (strategy). You merge them together & refer to the clsStrategy.FunctionName.
  2. You use a Collection Class.
 
Last edited:
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.
I am sorry but this is 10 times funnier, because it is two completely unrelated topics. This is such a complete "apples and oranges" comparison that it makes no sense at all. Have you not been following the conversation? Explain again how the tag property will replace the capability of a class to
1. Trap events of multiple controls
2. Extend the properties of those controls
3. Encapsulate multiple functionality to avoid writing new code
4. Raise custom events
5. Allow to define the functionality when an event occurs

Using the tag property to group controls is a pretty well known and well used, but not really related to the conversation. Some times you need to read the room. Just saying.
 
I mentioned tag properties because it'similar to what the OP wants to accomplish with his global right click controls.
You will have to pull the thread on that one for me because I am not seeing it. The OP wants to trap the right click on a mouse down/up for multiple controls. This one is complicated because the event procedure requires parameters to be passed from the application
Code:
Private Sub lbl1_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)

End Sub

If this was just a click event where you do not have parameters (or do not care) then the solution is easy and requires neither tag properties or some class. Simply on the onClick property for each control put the name of a function. The function traps the event for all of the controls
See discussion on events
and handling multiple events with one function

But in this case where I need to have the application provide values for those parameters, I know of no other way then to build a class as demonstrated. If someone knows another way to trap multiple mouse up/down and determine a right click without a class I would be interested because I cannot think how to do it. And I highly doubt a simple tag is going to get me there.
 
I had in my other thread applicable to what OP wants to accomplish?
Ahh... No.

fml-sylvester.gif


All you are showing is a bunch of hard wired events procedures. So what? Not even related. The point was to handle of multiple events without writing multiple Event Procedure. More apples and oranges. Now I am confused why Dalski would give you a thumbs up, because I cannot see any value to the conversation from that code. Maybe he sees something I do not or is just a nicer person.
 

Users who are viewing this thread

Back
Top Bottom