Custom event whine (1 Viewer)

pdanes

Registered User.
Local time
Today, 05:57
Joined
Apr 12, 2011
Messages
211
I have recently changed an app to make heavy use of custom events. There is a class module containing all the events, and subroutines containing generally the single line of code to raise the event, since VBA does not allow events to be raised directly, but insists that they must be raised only in the class module containing the event definition. I have to call a separate routine in the class that then raises the event, instead of raising it directly via the class module variable. I think that is a rather pointless restriction, but that is another issue.

Forms that make changes to auxiliary tables all call these routines via a global event variable pointing to a single instance of this class. I instantiate that by setting a global variable upon app startup, I prevent erroneous multiple instantiations in the class by checking the global variable's status in the Initialize procedure, and all code raising or handling custom events uses that one instance:
  • any time something happens that requires external notification, the appropriate sub from this class is called via that global variable, and every custom event is raised in that one class module;
  • every form that must react to such changes has a local WithEvents class variable, and on form load, that local WithEvents variable is set to point to the global one, again, so that everything goes through that one instance of the class.
It is fast, clean and allows essentially unlimited expansion with very little effort, since everything goes through that one 'central clearing house' class. Any new code that manipulates a table, I just add the appropriate call to the routine that raises whichever event informs about changes to that table, and any form containing a control that must react to such changes is updated from the custom event handler in that form.

I tested all this very thoroughly before deploying it, and everything worked great. But I've suddenly started running into a strange problem, which didn't happen when I first wrote and tested it all, where the WithEvents variable is seemingly not properly disposed of when the form closes. On the first use of a form, all is fine. On the second and subsequent uses of a form, any action that raises a custom event from the datasheet causes the event procedure in the edit form to be called multiple times – as many times as the parent form has previously been opened. It does not work the other direction - events from the edit form do not trigger multiple calls in the datasheet form, despite the coding being pretty much identical. I am of course familiar with the double call to the OnCurrent event code, but this is new to me. There is no doubt about what is happening – even when the very first line in the custom event handler is simply:
Code:
Debug.Print "Ext.";:Exit Sub
so that there is no possibility of recursive calling, it prints this, as many times as the form has been opened in this session. Code referring to the form's instance, like Me.Name, throws error 2467, reference to a closed or non-existent object on every superfluous call, while the base call continues to work properly. For example, if the form is on its third time being called, the event handler cycles through three times. The first and second time through, the Me... references fail, but the third time, they all work normally. Apparently the 'extra' calls go first, and the 'real' one is last. Checking the WithEvents variable on form load confirms that it is nothing, no matter how many times the form has been called before. It is always a local variable in that form, of course, defined in the first few lines of the form, before any procedures.

The entire app structure is one main form, with command buttons that open new forms in dialog mode. Those new forms are unbound, always with two subforms – one bound and the other not. It is the classic, read-only bound datasheet view in one, unbound editing of a single record in the other – what Access's split form feature was supposed to accomplish, but I could never get that to work properly.

I wanted to experiment more with custom events, now that I finally got them all set up the way I liked, so pretty much everything is run by events. Specifically, when the user clicks on a record in the datasheet subform, it fires a custom event that the edit form reacts to. The datasheet passes some information about the current record via parameters of the event, and the edit form's custom event handler loads that record. When the user finishes editing that record and clicks a button to save his work, the record is written back to the table and an event is raised to announce that the table has changed. The datasheet reacts to that event by reloading the datasheet, and any other forms that are open right then also react, doing things like reloading comboboxes, if they are sourced from that table. All this works great, except for this sudden compounding of phantom event variables on repeated form openings. And only some forms do this. I have 12 such popup forms in the app, all editing different aux tables, and they all work as close to identically as I was able to make them. They have the same appearance, the same layout of controls, the same code handling the same events, but 9 of them do this weird stacking and 3 do not.

Despite the WithEvents variable being nothing on form startup, explicitly setting it to nothing on the prior form close makes this ghost instancing stop accumulating. I suppose that is a solution, but it seems to me this should not be happening. This is exactly the kind of nonsense that leads to 'voodoo programming', like the notion, I think now antiquated, that all object variables should be explicitly manually destroyed on module exit. I still see advice to that effect, and I also see advice that claims this is rubbish, that this many years in, VBA knows when something goes out of scope and cleans up properly. But it does not appear to be doing so here.

Naturally, all this resets when the app is closed and reopened.
 
It's hard to follow your explanations just mentally, but what immediately popped into my head was whether you might have created circular references of two or more objects that prevent each other from being deleted by the garbage collection...
 
It's hard to follow your explanations just mentally, but what immediately popped into my head was whether you might have created circular references of two or more objects that prevent each other from being deleted by the garbage collection...
I don't see how. Each subform of the pop-up forms has one Set command on the subform's Load event, which sets the local WithEvents variable to point to that one global variable. The datasheet and edit subforms each have their own WithEvents variable, and both of them point to the global one. I've stepped through the code, and it simply jumps to the event-handling routine, repopulates whatever control needs to refresh and exits. None of the event-handling code contains any references to any form of the event-handling class, nor any variables pointing to it.
 
I've encountered that issue and although I don't know the why, I can confirm that manually destroying the variable resolves it. IIRC it was rather sporadic. I've just gotten into the habit of destroying them when done.
 
I've encountered that issue and although I don't know the why, I can confirm that manually destroying the variable resolves it. IIRC it was rather sporadic. I've just gotten into the habit of destroying them when done.
Glad to hear it's not just me. Yes, putting a Close event in each subform and manually setting the WithEvents variable to Nothing on form close fixes it. But it's a PIA, and should not be necessary. Is there some procedure for bitching to MS about this? I doubt if they will do anything, but at least I will have tried.
 
– what Access's split form feature was supposed to accomplish, but I could never get that to work properly.
No. It was always supposed to edit two bound forms.

Sounds like you'd be happier working with some other platform where you were not using a RAD tool. When you use a RAD tool, you get what they give you. You seem to be very dissatisfied with that. You're even using unbound forms which is the central feature of Access and what actually makes it a great development tool. However, in the case of Access, the original MS developers went above and beyond and even gave you the ability to work around their features. Don't expect it to be easy. Don't expect it to be clean. Be happy you can do it at all.

Bottom line - you've added a great deal of overhead to your forms. Make sure you test with large recordsets to ensure that the app will be functional in production.
 
Sounds interesting.
Could you set up a minimally reproducible sample file to see what's happening?
 
No. It was always supposed to edit two bound forms.

Sounds like you'd be happier working with some other platform where you were not using a RAD tool. When you use a RAD tool, you get what they give you. You seem to be very dissatisfied with that. You're even using unbound forms which is the central feature of Access and what actually makes it a great development tool. However, in the case of Access, the original MS developers went above and beyond and even gave you the ability to work around their features. Don't expect it to be easy. Don't expect it to be clean. Be happy you can do it at all.

Bottom line - you've added a great deal of overhead to your forms. Make sure you test with large recordsets to ensure that the app will be functional in production.
Bound or unbound is not the point - the point was a datasheet overview and a single record edit area in one form. It works as long as you don't try to do much trickery with code, but there are issues with code not being clear on which part of the form is being referenced. I don't remember the details - it was long ago, but I was by no means the only one who found it lacking and went back to doing it myself.

I don't know what you mean by bagging on 'a RAD tool'. With EVERY tool, you get what they give you. Access does some things well, some not, exactly like every other tool on the planet. I use it because it suits my needs, but that does not mean the designers did not make a few mistakes. This appears to be one - being annoyed that a local variable does not get properly destroyed when its parent is closed is not 'expecting it to be easy'. It is expecting that the product does what it says it does.

And how do you imagine that raising an event is 'a great deal of overhead'?
 
Sounds interesting.
Could you set up a minimally reproducible sample file to see what's happening?
I don't know, I can try, but it seems like it might by a huge amount of work for little benefit. I have no way to reliably generate the problem - it started on its own and does not manifest in every form, even though they are almost identical, and Moke123 also wrote that his experience with it was sporadic. I could easily spend a great deal of effort and never see it crop up. Even this app did not do it when I first constructed all this.
 
being annoyed that a local variable does not get properly destroyed when its parent is closed is not 'expecting it to be easy'
You are doing something outside the norm. RAD tools are all about doing it their way. I've used a number of RAD tools. Access is actually the only one I've been even close to being happy with. Yes, there are things I would like Access to do a little differently but not enough for me to try to bend it to my will. You seem to have gotten your overrides to work except for the dangling object. At least this is a solvable problem as you discovered. The more you use Access, the more subtle inconsistencies you run into. One of them was recently "fixed" except that the fix broke the object that was made consistent. The report object rewrites your recordsource query and eliminates any field that is not bound to a control and to use the sorting options specified by the report. It has done this since the late 90's so A97 at least. That's the first time I discovered the problem. You don't see the changed version of the query but it is there. The form object did not do that until recently. Not sure when the change actually happened. Old stuff doesn't break until you change it. I updated a form on an old app and discovered that my code wouldn't compile. In the BeforeUpdate event of the form, I update the ChangeBy and ChangeDT fields but the form almost never shows these. The fix was to bind the two fields to controls. I made them tiny because I had no room and made them hidden. Then the code worked again.

So, take this as a sort of warning, If you are doing something out of the norm like writing code in the form's class module that references fields from the recordsource which are not bound to controls. the functionality could break at any time.

but I was by no means the only one who found it lacking and went back to doing it myself.
Many things in Access are there for the use of people who can't do it themselves. That why people use RAD tools. I don't use the split form. You have no control over the single view. At least if you make your own, you have control over how it looks.
 
I could easily spend a great deal of effort and never see it crop up
Don't make the file, no problem. I understand, I've experienced those zombie entities too, they should be dead and they emerge again for some reason out of nowhere. I just thought I could get the joy of seeing them in action now that I'm messing around with the Windows API. I used to get that behavior from time to time but, for some reason, I haven't seen it recently.
 
Don't make the file, no problem. I understand, I've experienced those zombie entities too, they should be dead and they emerge again for some reason out of nowhere. I just thought I could get the joy of seeing them in action now that I'm messing around with the Windows API. I used to get that behavior from time to time but, for some reason, I haven't seen it recently.
If I can figure out a reliable way to make this happen, I'll post it.
 
You are doing something outside the norm. RAD tools are all about doing it their way. I've used a number of RAD tools. Access is actually the only one I've been even close to being happy with. Yes, there are things I would like Access to do a little differently but not enough for me to try to bend it to my will. You seem to have gotten your overrides to work except for the dangling object. At least this is a solvable problem as you discovered. The more you use Access, the more subtle inconsistencies you run into. One of them was recently "fixed" except that the fix broke the object that was made consistent. The report object rewrites your recordsource query and eliminates any field that is not bound to a control and to use the sorting options specified by the report. It has done this since the late 90's so A97 at least. That's the first time I discovered the problem. You don't see the changed version of the query but it is there. The form object did not do that until recently. Not sure when the change actually happened. Old stuff doesn't break until you change it. I updated a form on an old app and discovered that my code wouldn't compile. In the BeforeUpdate event of the form, I update the ChangeBy and ChangeDT fields but the form almost never shows these. The fix was to bind the two fields to controls. I made them tiny because I had no room and made them hidden. Then the code worked again.

So, take this as a sort of warning, If you are doing something out of the norm like writing code in the form's class module that references fields from the recordsource which are not bound to controls. the functionality could break at any time.


Many things in Access are there for the use of people who can't do it themselves. That why people use RAD tools. I don't use the split form. You have no control over the single view. At least if you make your own, you have control over how it looks.
What do you imagine is 'outside the norm'? Custom events are a completely normal, documented feature of Access/VBA. It's not something I dreamed up and just happened to ran across an oddity that just happened to work. The entire user interface runs on events, and user-created events are a trivial extension of that core functionality.

There is a dearth of good documentation and examples for creating and using custom events, and much of what I have found and read over the years is wrong, incomplete or badly explained, which is why it took me a while to put together a really practical and nicely functional system, making proper use of them. It's not a trivial topic - it has several components that have to be set up just right, and leaving out any of them makes nothing work right. But I've got it now, and it is all bone-stock VBA.

And what are you calling 'my overrides'? I'm not overriding anything - I'm using a normal, standard, fully documented feature of the language, and have run into an error that is decidedly NOT proper behavior. Variables are supposed to be destroyed when they go out of scope. There is literally NOTHING in this code that is the least bit unorthodox. I declare a variable, I use it in the manner for which that type of it is intended, and I expect that variable to go away, completely, when its containing object is closed.
 
And you've done an bang up job so enjoy your success.
 
Could you set up a minimally reproducible sample file to see what's happening?
Here is a simple sample, but I cannot recreate the issue.

Open Forms red, blue, green. Move so you can see them. Then select a color and see that the centralized event raiser raising two events and all forms listen for those events.

1. A single global CentralizedEventRaiser is created.
2. Each form instantiates a CER variable set to the global CER.
3. Each form traps the events of the CER.

The event raiser has two events. If the form selects and passes in its own color it announces it loves that color to all other forms listening to the CER. If it passes in another color the CER announces the form hates that color and all forms trap that event.

According to @pdanes this could be created by closing one of the form and reopening it. When reopening the event should fire multiple times. However, it works fine. So there must be more to create the condition.
 

Attachments

Here is a simple sample, but I cannot recreate the issue.

Open Forms red, blue, green. Move so you can see them. Then select a color and see that the centralized event raiser raising two events and all forms listen for those events.

1. A single global CentralizedEventRaiser is created.
2. Each form instantiates a CER variable set to the global CER.
3. Each form traps the events of the CER.

The event raiser has two events. If the form selects and passes in its own color it announces it loves that color to all other forms listening to the CER. If it passes in another color the CER announces the form hates that color and all forms trap that event.

According to @pdanes this could be created by closing one of the form and reopening it. When reopening the event should fire multiple times. However, it works fine. So there must be more to create the condition.
I don't even know that I could confidently state that there is more to it. I have not been able to identify anything specific that I am doing to cause it, although it is not completely random - the forms that do it now do it reliably. But they did not do it when I first created all this. I was opening and closing the forms constantly, refining various details, and I certainly would have noticed this, since it manifests almost immediately. As soon as I click on a record in the datasheet subform, in order to bring up that record in the edit subform, the event-handling routine in the edit subform that is supposed to load that record throws an error. And again, not all forms in the app do this. 9 do, 3 do not, yet I can see no substantive differences between them. And only events from the datasheet subforms cause the edit subforms to err. In none of the forms does an event in the edit subform cause this problem in the datasheet subform. But to forestall any future complications, I have placed a Form_Close procedure in every subform, and explicitly destroy the local WithEvents variable in it.

When I first created these aux forms, they were all over the place, as the user asked for various things and I knocked together what she needed. But the app eventually settled into a reasonably stable configuration, and in the process of setting up these events, I decided to tidy up these aux forms. They are simple enough - one table, one to three fields, with no additional bindings or other complications. I settled on one background color, one layout for all controls, all controls and procedures named the same and in the same order, even all elements in the code laid out in the same order, with the same names, as much as was possible to do, given that each form services a different table. This organization helped me find a number of errors and inconsistencies between the forms - as I found a problem in one, it was a simple matter to find and correct the same problem in any others, since both code and controls were laid out the same way in all.

I suppose I can try trimming down a copy of the app until I get to a manageable size. If I can do that and retain this odd behavior, I will post it here, but it contains a lot of graphics and even completely empty, it is over 80MB. Another problem is that it is built on a Czech machine. The Czech language has what are called diacritical marks on some letters, and these generally raise havoc in English-only machines. For instance, the ribbon will not load, even though I am very careful to use such letters only in captions and descriptions, never in things like module or control names.
 
I don't even know that I could confidently state that there is more to it. I have not been able to identify anything specific that I am doing to cause it, although it is not completely random - the forms that do it now do it reliably. But they did not do it when I first created all this.
Perhaps you have decompiled and compacted your database file in the meantime? This cleans up some stuff. It wouldn't be the first time this has been a cure for fancy problems.
 
Perhaps you have decompiled and compacted your database file in the meantime? This cleans up some stuff. It wouldn't be the first time this has been a cure for fancy problems.
I wish it were that simple. But I do that constantly. In fact, I have a specific routine for it. I close the db, then call a VBScript module that opens the db with the /Decompile switch, and holding Shift to prevent the app from starting up. I then do a C & R, compile, then C & R again, then close. Been doing it that way for years, and so far, that has always chased out all gremlins. I do it always before shipping an app, and regularly during development, whenever something starts acting strangely.

What isn't fixed by that generally can't be fixed, and I have to invent some other way of doing it. For instance, a while back I was trying to use the .RecordCount property of the TableDef object. It doesn't work - it kept giving me incorrect counts. C & R would fix it for a little while, but it would very quickly start misbehaving again. Nobody here or in other forums had any explanation, so I eventually gave up and went to an active method of counting records.
 
In fact, I have a specific routine for it. I close the db, then call a VBScript module that opens the db with the /Decompile switch, and holding Shift to prevent the app from starting up. I then do a C & R, compile, then C & R again, then close. Been doing it that way for years, and so far, that has always chased out all gremlins. I do it always before shipping an app, and regularly during development, whenever something starts acting strangely.
This could have been written by me... :) (y)
 
Close event in each subform and manually setting the WithEvents variable to Nothing on form close fixes it. But it's a PIA
Really? This is what we are complaining about. I mean come on. If I had 50 forms I figure I could do this manually for all forms in less than 5-10 minutes. I am sure far quicker than for you to write the post. If I had 10,000 forms I could write code in extensibility to do this.
1. Check if Close form method exists
2. If not add a close form method
3. Add code to set the event raiser to nothing
I assume in form you use the same variable to hold the event raiser instance.
 

Users who are viewing this thread

Back
Top Bottom