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:
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 .
@dalski Not at all. It is a pleasure to find someone as curious and interested as you. I get a huge kick out of coming to understand new code patterns, so having an exchange with someone who shares that pleasure is itself a pleasure.
Cheers,
Mark
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
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,
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
Advanced Class Concepts I have posted several threads on using custom classes to extend the capabilities of controls and make pseudo "user controls". https://www.access-programmers.co.uk/forums/threads/developing-custom-classes-to-simulate-user-controls.309080/ This thread has additional concepts.
www.access-programmers.co.uk
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.
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
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:
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.
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.
The attached utility demonstrates a way of setting the state of a group of controls at the same time using the controls' Tag property. The properties that can be controlled are: .Visible , .Enabled, .Locked However, some control types do not allow all of the properties. For example labels...
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
There is often a lot of confusion about Access Events and often answering questions posed here is difficult because of inaccurate terminology. People often say "Event" when it is unclear if they mean the actual event that takes place, the event procedure, or the event property. This thread...
www.access-programmers.co.uk
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.
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.