Writing code into an Event Procedure via VBA - Access
-
Friday, December 09, 2011 8:41 PM
I have an Access form used for data entry of written application forms. It has a lot of fields (in fact the max 255 approx.). I need to put some simple code into the AfterUpdate event on all 255 controls.
Doing that manually seems a bit dumb so I made a procedure to loop through all the relevant controls to create an Event Procedure on AfterUpdate. Now I need to put one simple line of code into each of those Event Procedures.
Does anyone know how I do that programmatically?
I can't seem to find the answer anywhere.
Thanks for any tips anyone can throw me on this.
All Replies
-
Friday, December 09, 2011 9:18 PM
Chip Pearson has a webpage for excel, but I think it will also work with Access
http://www.cpearson.com/Excel/VBE.aspx
What may be easier is to write a macro that outputs to a text file the 256 functions, then copy the text file back into your VBA module. I've done this before many times.
- Edited by Joel EngineerMicrosoft Community Contributor Friday, December 09, 2011 9:25 PM
-
Friday, December 09, 2011 10:02 PM
You may be able to use something a bit easier to manage.
Private Sub Form_Open(ByRef intCancel As Integer) ' Set the After Update Handler for all Controls ' with 'IncludeInHandler' in their Tag Property. Dim Ctl As Access.Control For Each Ctl In Me If Ctl.Tag = "IncludeInHandler" Then Ctl.AfterUpdate = "=AfterUpdateHandler([" & Ctl.Name & "])" End If Next Ctl End Sub Private Function AfterUpdateHandler(ByRef Ctl As Access.Control) ' Common After Update Handler for all Controls ' with 'IncludeInHandler' in their Tag Property. With Ctl MsgBox .Name MsgBox Nz(.Value, "") ' Etcetera End With End Function
Regards,
Chris.
-
Friday, December 09, 2011 11:22 PM
Joel
Thanks for that.
I can see how that could work and it could turn out to be quite straightforwards.
I was stuck on finding some way to reference the procedures via an object model but maybe that's not the most useful way to think of it.
I'll try it and see what happens.
Thanks again,
Ed
-
Friday, December 09, 2011 11:29 PM
Chris
Thanks for this too.
If I can set the tag property as I loop through my controls, maybe I could just directly set the Afterupdate property to =AfterUpdateHandler([" & Ctl.Name & "])? I guess there's some reason to go via the tag first but I can't see it yet.
Anyway thanks for another practical take on this.
Time to go and try it all and see what works.
Thanks again
Regards
Ed
-
Friday, December 09, 2011 11:37 PM
If the Controls were named Control_1, Control_2…Control_255 then there would be no reason to use the Tag Property. (Index the Control name with the number.)
If the Controls are not named sequentially then you can select them all in design view and enter IncludeInHandler in their Tag property.
Regards,
Chris.
-
Friday, December 09, 2011 11:56 PM
Chris
That makes sense. I've got some ways to tackle this now which is great.
However I've just noticed that some of these controls already have some afterupdate() code in them which I need to preserve so now I need to add my new line to what's already there rather than overwrite it.
If you happen to have that figured too then please let me know how you'd do it!
Meanwhile I'll try and figure something out.
Regards
Ed
-
Saturday, December 10, 2011 12:05 AM
Well, without knowing what ‘other’ code is already there it is impossible to say.
Can you post the ‘other’ code that is already there?
Regards,
Chris.
-
Saturday, December 10, 2011 12:10 AM
Another way to write this sort of thing, provided there is no other code behind the Form, is to also remove its Class Module. (Set HasModule to No in design view.)
Then set the Form OnOpen Property:-
=SetEventHandlers([Form])
And in a Standard Module:-
Public Function SetEventHandlers(ByRef Frm As Access.Form) ' Set the After Update Handler for all Controls ' with 'IncludeInHandler' in their Tag Property. Dim Ctl As Access.Control For Each Ctl In Frm If Ctl.Tag = "IncludeInHandler" Then Ctl.AfterUpdate = "=AfterUpdateHandler([" & Ctl.Name & "])" End If Next Ctl End Function Public Function AfterUpdateHandler(ByRef Ctl As Access.Control) ' Common After Update Handler for all Controls ' with 'IncludeInHandler' in their Tag Property. With Ctl MsgBox .Name MsgBox Nz(.Value, "") ' Etcetera End With End Function
No code behind the Form...Regards,
Chris.
-
Saturday, December 10, 2011 12:33 AM
Interesting.
But time for bed here in the UK. Not sure where you are but I need to take a break now.
Thanks for all your help.
Best regards
Ed
-
Saturday, December 10, 2011 1:40 AM
' I'm in Brisbane but, because I'm getting old, I might have a nap too. :-) ' ' If you get the impression I'm trying to avoid 255+ Event Handlers you are correct. ' Consider processing the exceptions rather than the rule... ' ' The Control Tag Property can now look like this (Pipe delimited):- ' ' No exception: ' IncludeInHandler|HandleException_0 ' ' First exception: ' IncludeInHandler|HandleException_1 ' ' Second exception: ' IncludeInHandler|HandleException_2 ' ' Etcetera ' ' You should have less exceptions than the rule. ' ' Again with no Class Module behind the Form(s) if there is no other code behind the Form(s):- ' Public Function SetEventHandlers(ByRef Frm As Access.Form) ' Set the After Update Handler for all Controls ' with 'IncludeInHandler' in their Tag Property. Dim Ctl As Access.Control For Each Ctl In Frm If InStr(Ctl.Tag, "IncludeInHandler") Then Ctl.AfterUpdate = "=AfterUpdateHandler([" & Ctl.Name & "])" End If Next Ctl End Function Public Function AfterUpdateHandler(ByRef Ctl As Access.Control) ' Common After Update Handler for all Controls ' with 'IncludeInHandler' in their Tag Property. If InStr(Ctl.Tag, "|") Then Application.Run Split(Ctl.Tag, "|")(1), Ctl Else MsgBox "Invalid Tag argument to Call." End If End Function Public Sub HandleException_0(ByRef Ctl As Access.Control) With Ctl MsgBox "Processing " & Split(.Tag, "|")(1) & " Handler for " & .Name & " on Form " & .Parent.Name MsgBox .Name MsgBox Nz(.Value, "") MsgBox .Parent.Name ' Etcetera End With End Sub Public Sub HandleException_1(ByRef Ctl As Access.Control) With Ctl MsgBox "Processing " & Split(.Tag, "|")(1) & " Handler for " & .Name & " on Form " & .Parent.Name ' Etcetera End With End Sub Public Sub HandleException_2(ByRef Ctl As Access.Control) With Ctl MsgBox "Processing " & Split(.Tag, "|")(1) & " Handler for " & .Name & " on Form " & .Parent.Name ' Etcetera End With End Sub ' Regards, ' Chris. zzzzzzzzzzzzzzz
-
Saturday, December 10, 2011 8:24 PM
you can use the Form's Module and work with that
Dim frm as Form, mde as Module, ctl as Control Set frm = Forms("MyForm") Set mde = frm.Module
Then loop through all the controls and Identify the ones you wantFor each ctl in frm.Controls
then use the the control's name to contruct the Event
strTarget = "Private Sub " & ctl.Name & "_AfterUpdate()"
strCode = "Whatever code you want to insert"
strEnd = "End Sub"Now you can seach the mde to see if the code and target procedure exists and if it doesn't then insert it
With mde ' note - the find method Uses ByRef to return the positions for start and end so we need to reset them to 1 lngStartLine = 1: lngStartColumn = 1: lngEndLine = 1: lngEndColumn = 1 If .Find(strCode, lngStartLine, lngStartColumn, lngEndLine, lngEndColumn) Then ' the code already exists GoTo Skip_Control End If If .Find(strTarget, lngStartLine, lngStartColumn, lngEndLine, lngEndColumn) Then ' see if there is an After Update Event .InsertLines lngEndLine + 1, strCode Else ' if not then insert it after the last procedure If .Find("End Sub", lngStartLine, lngStartColumn, lngEndLine, lngEndColumn) Then .InsertLines lngEndLine + 1, "" .InsertLines lngEndLine + 2, strTarget .InsertLines lngEndLine + 3, strCode .InsertLines lngEndLine + 4, strEnd Else ' or after the initial declarations .InsertLines lngEndLine + 3, "" .InsertLines lngEndLine + 4, strTarget .InsertLines lngEndLine + 5, strCode .InsertLines lngEndLine + 6, strEnd End If End If<br/><br/>That should get you started. <br/>
This code does assume that the code will be different for each control (I wrote it for a different application). so you may need to drop the first .Find(strCode ... and just look for the target.
Kent- Edited by KentGorrell Saturday, December 10, 2011 8:29 PM
- Marked As Answer by Ed_Secretan Wednesday, December 14, 2011 8:00 AM
-
Wednesday, December 14, 2011 8:15 AM
Thanks to everyone for your suggestions.
It was all useful learning for me and the new procedure works and created the right module.I didn't need to avoid code behind the form so went the route of creating a module from VBA.
I found that using "Find" to search for procedure start lines returns line '0' if it doesn't find the procedure name it's looking for. That resulted in some new lines of code going in right at the top before "Option Compare database". I could have trapped for NoMatch I suppose but in the end I got it to right the whole thing from scratch. Also had some issues with valid control names not being valid procedure names and Access substituting underscores for invalid characters.
The thing that slowed down the debugging the most was that you cannot break the code once you have already inserted a line of code. That means you can't step through to where the problem is in the normal way.
I guess all examples can be useful to others so I'll copy the whole thing here,
Public Function AddCodeToControlEP() 'this adds code to the Event Procedures of various controls in a given form 'NB there is a method to search for the start line number of a procedure 'and then add lines after it 'However it turns out to be very prone to slips so writting the entire module from code 'seems much easier............. On Error GoTo AddCodeToControlEP_Err Dim ctrlname As String Dim ctrlnameFull As String Dim ctrltype As String Set frm = Forms![F_RD_AppDetails] Set mdl = frm.Module For Each control In frm.Controls ctrlname = control.Name 'skip the subforms If ctrlname = "F_RD_Terminals" Or ctrlname = "F_RD_OtherServices" Then Else 'skip the labels If InStr(ctrlname, "label") = 0 Then 'replace Paul's spaces with '_' to match what is needed for the EP name ctrlnameorig = ctrlname ctrlname = ReplaceChar(" ", "_", ctrlname) ctrlname = ReplaceChar("%", "_", ctrlname) ctrlname = ReplaceChar("-", "_", ctrlname) ctrlname = ReplaceChar("&", "_", ctrlname) ctrlname = ReplaceChar("/", "_", ctrlname) 'Debug.Print ctrlname 'add the new EPs ctrltype = Right(ctrlname, 5) Select Case ctrltype Case "Query" ' Add event procedure. lngReturn = mdl.CreateEventProc("AfterUpdate", ctrlname) NewCodeline = "Forms![F_RDcompleteApp]![F_RD_AppDetails].Form![" & ctrlnameorig & "].Tag = " & Chr$(34) & "Edited" & Chr$(34) & "" mdl.InsertLines lngReturn + 1, NewCodeline NewCodeline = "If Forms![F_RDcompleteApp]![F_RD_AppDetails].Form![" & ctrlnameorig & "] = " & Chr$(34) & raised & Chr$(34) & "Then " mdl.InsertLines lngReturn + 2, NewCodeline NewCodeline = "Forms![F_RDcompleteApp]![F_RD_AppDetails].Form![" & ctrlnameorig & "] = True" mdl.InsertLines lngReturn + 3, NewCodeline NewCodeline = "end if" mdl.InsertLines lngReturn + 4, NewCodeline NewCodeline = "AddRDNotes" mdl.InsertLines lngReturn + 5, NewCodeline Case "Notes" ' Add event procedure, AfterUpdate. lngReturn = mdl.CreateEventProc("AfterUpdate", ctrlname) NewCodeline = "Forms![F_RDcompleteApp]![F_RD_AppDetails].Form![" & ctrlnameorig & "].Tag = " & Chr$(34) & "Edited" & Chr$(34) & "" mdl.InsertLines lngReturn + 1, NewCodeline ' Add event procedure, OnClick. lngReturn = mdl.CreateEventProc("Click", ctrlname) NewCodeline = "AddRDNotes" mdl.InsertLines lngReturn + 1, NewCodeline Case Else ' Add event procedure. NewCodeline = "Forms![F_RDcompleteApp]![F_RD_AppDetails].Form![" & ctrlnameorig & "].Tag = " & Chr$(34) & "Edited" & Chr$(34) & "" lngReturn = mdl.CreateEventProc("AfterUpdate", ctrlname) mdl.InsertLines lngReturn + 1, NewCodeline End Select End If End If Next control Exit Function AddCodeToControlEP_Err: MsgBox Err.Description & ctrlname Resume Next End Function
-
Thursday, December 15, 2011 2:57 AM
re your commment - I found that using "Find" to search for procedure start lines returns line '0'
that's right. but remember 0 = False so
If .Find(strTarget, lngStartLine, lngStartColumn, lngEndLine, lngEndColumn) Thenif the same as
If .Find(strTarget, lngStartLine, lngStartColumn, lngEndLine, lngEndColumn) <> 0 Then
Also you could use the property ctl.ControlType eg
Select Case ctl.ControlType
case acTextBox, acListBox etc.
But I was rather fascinated with your line
lngReturn = mdl.CreateEventProc("Click", ctrlname)I didn't know about that one.
Kent- Edited by KentGorrell Thursday, December 15, 2011 11:03 PM stupid error
-
Thursday, December 15, 2011 2:50 PM
Kent
Thanks for those useful points.
mdl.CreateEventProc was useful in as much as I found I was spending too much time de-bugging the cases where "Find" didn't give the desired result. Not that "Find" is intrinsically a problem, more that many control names in my modules had names that aren't valid as part of a procedure name and so I had to search for the 'translated' versions. At one point I just thought I'd try the other route of removing all the procedures and creating them from scratch using mdl.CreateEventProc. That way I at least now know exactly what is in the module.
Anyway, it was all new stuff to me and I'm very grateful to all who gave tips as I didn't find much on this topic on the web.
All the best
Ed
-
Monday, April 30, 2012 7:02 AM
Hi Chris,
I really enjoyed your tip, but how can I use it with others events like double click?
Chalton.
-
Monday, April 30, 2012 8:11 AM
Hi Chalton.
That is a very broad question so perhaps a new thread is required.
In the mean time a posting from a few years ago might explain it better.
http://www.access-programmers.co.uk/forums/showthread.php?t=166799
That thread is locked so if you have any questions please post them in this forum.
More specific information would be appreciated.
Regards,
Chris.Brisbane Australia, GMT+10, Access 2003, Public at SkyDrive

