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.
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;
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:
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:
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.
I have seen very few Access developers leverage the tag property, which allows you to group many objects into collections to do things like make all controls in a group visible, hidden, locked, etc.
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
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.
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.