The Application.Reset Event (1 Viewer)

riktek

Member
Local time
Today, 08:08
Joined
Dec 15, 2023
Messages
165
The Access.Application class has no events, unlike the Excel.Application class, so Access users are left to their own devices. The most critically important application behavior occurs on a project reset because this instantly and thoroughly disables an Access application, clearing all its variables and thereby destroying virtually all its runtime objects. The ability to trap this behavior and raise an event application-wide offers the opportunity for an application to become self-healing. Here’s how.

Among the runtime objects a project reset destroys are all event sources and sinks because both must be objects defined in class modules and such objects can exist only so long as pointers to them. The variables ultimately containing those pointers will survive a reset but the pointers will not. So, we must accommodate these difficulties, which will require a suitable runtime environment. It must of course be durable and lightweight, and also autostart and self heal.

Form_Unload() and Form_Close() will run after a project reset even if Class_Terminate() does not, so to trap a project reset, we create frmCanary and a Form_Unload() in its module.

We then create an application module named My to contain application properties and methods. My must be a standard module because it must be impervious to instancing so as to enforce a singleton pattern, and its procedures and variables must survive a reset.

We then create an application form, frmMain, which together with My constitute a durable application runtime environment. frmMain cannot be the application display (startup) form because it must open hidden, so we also create frmStartup and designate it as the application display form. The sole utility of any display form is its Form_Open() event procedure because it is the first event procedure that can run after application startup, and that event procedure itself must be cancelled because the form will become visible if Form_Load() is allowed to run. We thus must condition frmStartup.Form_Open()’s Cancel argument on frmMain loading successfully, as further described.

In My, we then create Main as an application property to return frmMain. We back Public Property Get Main() with a static local variable because it is identical to a private module-level variable in that it retains its value during the lifetime of its module. We also employ a self-healing object variable (SHOV) pattern, conditionally setting the variable on the procedure’s call if it is Nothing, to avoid the necessity of a Property Set procedure.

Elsewhere, we create AppIsLoaded(), a convenience function for readability to test My.Main().

Code:
    Function AppIsLoaded() As Boolean   

        AppIsLoaded = (Not (My.Main Is Nothing))

    End Function    'AppIsLoaded()

Returning to frmStartup.Form_Open(), we therein do:

Code:
Cancel = AppIsLoaded

We then create clsAppEventSource to define an application event source and therein declare an Access.Form variable named Canary, assign an instance of frmCanary to that variable in its Class_Initialize(), and destroy it in its Class_Terminate(). We also declare event Reset() and define a public OnReset() method to raise the Reset event.

Returning to My, we then create AppEventSource() as an application property, also implementing a SHOV pattern to avoid the necessity of a Property Set procedure.

We then create clsAppEventSink to define a default application event sink. Therein, we declare WithEvents a clsAppEventSource variable named oAppEventSource, assign My.AppEventSource() to it in Class_Initialize(), and clear that variable in Class_Terminate(). We go on to create oAppEventSource_Reset() to sink the event.

In frmMain, we create a public Init() and therein do:

Code:
Set My.AppEventSink = New clsAppEventSink

In frmMain.Form_Open(), we also do:

Code:
Init

Back again to My, we create OnReset() and therein do:

Code:
    If gblnUnloading Then       'Case application closing.
    '   Do nothing.  Exit gracefully without reloading.
    Else                        'Case project reset.
        Main.Init
        AppEvents.OnReset
    End If

Also in My, we create a public Boolean variable gblnUnloading and in frmMain.Form_Unload() do:

Code:
gblnUnloading = True

Finally, we return to frmCanary.Form_Unload(), and therein do:

Code:
My.OnReset

With this, the startup event progression is:
• Startup opens frmStartup.
frmStartup.Form_Open() opens frmMain by testing My.Main().
frmMain.Form_Open() calls frmMain.Init().
frmMain.Init() initializes clsAppEventSink and assigns it to My.AppEventSink.
clsAppEventSink.Class_Initialize() assigns My.AppEventSource() to clsAppEventSink.oAppEventSource.
My.AppEventSource() initializes clsAppEventSource and returns an instance to clsAppEventSink.Class_Initialize().
clsAppEventSource.Class_Initialize() initializes frmCanary and assigns it to clsAppEventSource.Canary.

The Reset event progression is:
• A project reset destroys the clsAppEventSource instance and its Canary variable.
frmCanary.Form_Unload() calls My.OnReset(), which is unaffected by the reset because it is in a standard module.
My.OnReset() calls frmMain.Init() via My.Main().
My.Main() reinitializes its static form variable lfrmMain.
frmMain.Init() reinitializes My.AppEventSink.
clsAppEventSink.Class_Initialize() calls My.AppEventSource().
My.AppEventSource() reinitializes clsAppEventSource.
clsAppEventSource.Class_Initialize() reinitializes frmCanary.
My.OnReset() calls My.AppEventSource.OnReset().
clsAppEventSource.OnReset() raises clsAppEventSource.Reset().
• The clsAppEventSink instance assigned to My.AppEventSink sinks and handles clsAppEventSource.Reset().

The shutdown event progression is:
frmMain.Form_Unload() is the first event to fire, so in it, we set My.gblnUnloading.
My.OnReset() is triggered as objects collapse but skips reinitialization because gblnUnloading = True.

So, with this, we have created:
• An application events framework that permits any behavior trap to trigger an application event.
• A reset trap.
• In a standard module:
• A self-healing application event source property that any form, report, or class can use to raise or sink application events.
• A public variable to contain the default application event sink.
• Ultimately, a durable, lightweight, self-healing, and autostarting runtime environment.

The code in toto is attached.

- Eric Blomquist

EDIT:
• Zipped modules now incorporate Debug.Print lines to demonstrate event progression.
• Zipped ACCDB incorporates zipped modules and is 32-bit A2007, so should run in all subsequent versions.
 

Attachments

Last edited:
I'm not sure I would personally benefit from this approach, for 2 reasons:
  1. My end users always run my applications in runtime mode, which means any unhandled error would immediately crash the application anyway; and
  2. I use vbWatchdog as my error handler of last resort, so I never have to worry about unhandled errors in the first place.
Where I might find something like this useful is during development, if there is some way to preserve application state when clicking the square VBA reset button. However, that is essentially a nuclear option. Clicking that button would immediately cease all VBA execution, including the form unload event in frmCanary, which kicks off the process to save application state (if I understand correctly). Am I missing an obvious use case?
 
I'm not sure I would personally benefit from this approach, for 2 reasons:
  1. My end users always run my applications in runtime mode, which means any unhandled error would immediately crash the application anyway; and
  2. I use vbWatchdog as my error handler of last resort, so I never have to worry about unhandled errors in the first place.
Where I might find something like this useful is during development, if there is some way to preserve application state when clicking the square VBA reset button. However, that is essentially a nuclear option. Clicking that button would immediately cease all VBA execution, including the form unload event in frmCanary, which kicks off the process to save application state (if I understand correctly). Am I missing an obvious use case?
Thanks for your thoughts.

You're correct that frm.Canary.Form_Unload() triggers the event. Pressing the square VBA reset button triggers the event overtly.

I also typically have universal error handling in an app, and certainly have it in my base library. I don't currently use vbWatchdog although that may simply be a matter of time. This said, one use case could be a generic way to address unhandled errors.

The use case, really, is up to the developer, however. To the extent of the runtime framework as presented, reset will not preserve application state, i.e., the runtime framework will self-heal. Beyond that, what happens is merely what the developer codes in the Application.Reset event procedure, wherever sunk.

What this demonstrates is several things, the possibility of an Application.Reset event procedure being only one of them. Application_Reset() may be useful or not. Otherwise, this demonstrates:
• An application runtime environment (frmMain and My) and an application event framework built on it. This benefits from Application.Reset because it is self-healing as presented but need not be. Having the event framework up at all times can have utility, however.
• The application event framework is not limited to an Application.Reset event. The framework will support any event one might imagine. Any procedure in any module can trap behavior and then can raise any application event by calling, e.g., My.AppEventSource.OnEvent.
• In addition to the default event sink, any form, report, or class also can sink any application event by declaring WithEvents a clsAppEventSource variable and assigning My.AppEventSource to it.
• Coincidentally, that properties and variables in a standard module can have utility for event handling.

The specific use case that I had in mind when I got started with this was subscribing to the window message queue. Then, a WndProc callback can trap a particular window message, e.g., ActivateApp, and raise a corresponding application event, e.g., to restore focus to the last-opened a PopUp form that otherwise wouldn't get it when tabbing back into Access from another application. Other use cases might include anything in Win32 that one might declare, or .NET that one might wrap for COM. So, e.g., socket activation might come in handy.

I hope that answers some of your questions.
 
  1. My end users always run my applications in runtime mode, which means any unhandled error would immediately crash the application anyway; and

The above is not quite true!

If you use a accDB (non complied) application then YES the above is true.

However, if you use a compiled accDE, then the above is NOT the case.

In fact, any un-handled error will NEVER re-set local or global vars.

Hence, we always deploy a accDE.

Thus, even application that don't have error handling everywhere (which is not worth to do so IMHO anyway), the 2nd added bonus is that such errors don't stop the runtime EVER.

Needless to say, this then gives you 100% freedom as a developer to use some global vars, or even some global class with variables, and they will NEVER re-set - NEVER!

This gives your application the reliability of a un-stoppable freight train.......

So, by adopting a accDE, then all worries about some un-handled error goes away.

And as noted, the 2nd bonus is that the runtime ALSO never stops, or shuts down due to such un-handled errors.

The end result?
You actually don't have to worry about some re-set, since it will NEVER occur, and your VBA variables will NEVER under any case I am aware of go out of scope...


R
Albert
 
Just as an additional note:
It is not enough to rename an accdB to accdE. It must definitely be a database compiled to accdE.
The accdE can also be renamed to accdR.
 

Users who are viewing this thread

  • Back
    Top Bottom