Closing Form Instances

riktek

Member
Local time
Today, 06:43
Joined
Dec 15, 2023
Messages
163
A few of us were putting heads together the other day to resolve some difficulties around closing form instances and I thought I'd report back.

What's new recently in the experience of several of us is that apparently, clearing a reference to a form instance is no longer sufficient to close that instance, although it remains necessary. The default behavior, as noted by Allen Browne, had been that removing references to a form instance closes it.

So, apparently, one now must also close the form instance expressly. This requires a DoCmd.Close call but this poses a few difficulties:
- DoCmd.Close only permits object identification by name.
- Each instance of a form has the same name.
- When provided a name, DoCmd.Close operates on the first member of Application.Forms with that name, not the last. This poses no difficulty when closing all instances so long as one gets the number right. One can't specify a specific instance or subset of instances, however, so calling DoCmd.Close, even from the form's module, most likely will close another, or an unintended, instance.

The solution lies in the fact that all DoCmd.Close arguments are optional. If omitted (except perhaps for the Save parameter), the command operates on the active window, but note:
- A form's window does not become active simply because code is executing in its module.
- So, we first must set focus to the form instance intended to be closed.

What we arrived at is:

Code:
Function CloseMe(Optional ByRef objMe As Object) As Boolean

    Dim blnReset As Boolean
   
    If (objMe Is Nothing) Then
        blnReset = True
        Set objMe = Access.Application.CodeContextObject
    End If
   
    objMe.SetFocus
    DoCmd.Close , , acSaveNo

    If blnReset Then Set objMe = Nothing
   
    CloseMe = (Err.Number = 0&)

End Function    'CloseMe()

This is intended to be callable either (a) without an argument as a private function in a form module; or (b) with an argument as a public function in a standard module.

Of course, we may have gotten something wrong, so comments and corrections are welcome.
 
Last edited:
> What's new recently in the experience of several of us is that apparently, clearing a reference to a form instance is no longer sufficient to close that instance
That is not my experience. The Northwind 2 Developer Edition has several such forms, e.g. OrderDetails, and there is no DoCmd.Close to close any instance.
Can you please post a database showing all of your relevant code, and that when we comment out your DoCmd.Close line, the form no longer closes? And/or compare your code with that of NW2? Maybe the discrepancy will then become obvious.
 
Try this: -

Code:
Public Function CloseMe(Optional ByRef objMe As Object) As Boolean
    On Error GoTo ErrHandler

    Dim blnReset As Boolean

    ' If no object passed, assume we're in a form module
    If objMe Is Nothing Then
        blnReset = True
        Set objMe = Access.Application.CodeContextObject
    End If

    ' If it's a control, get its parent form
    If TypeOf objMe Is Access.Control Then
        Set objMe = objMe.Parent
    End If

    ' Ensure it's a form before proceeding
    If Not TypeOf objMe Is Access.Form Then
        Err.Raise vbObjectError + 1, , "CloseMe: Object is not a Form."
    End If

    ' Try to set focus to avoid DoCmd.Close issues
    On Error Resume Next
    objMe.SetFocus
    On Error GoTo ErrHandler

    ' Close the form by name
    DoCmd.Close acForm, objMe.Name, acSaveNo

    ' Clean up if we created the reference
    If blnReset Then Set objMe = Nothing

    CloseMe = True
    Exit Function

ErrHandler:
    CloseMe = False
End Function

Example Usage
From a Form Module: -
Code:
Private Sub btnClose_Click()
    Call CloseMe()
End Sub

From a Standard Module: -
Code:
Public Sub CloseFormByName()
    Call CloseMe(Form_MyForm)
End Sub
 
Last edited:
> What's new recently in the experience of several of us is that apparently, clearing a reference to a form instance is no longer sufficient to close that instance
That is not my experience. The Northwind 2 Developer Edition has several such forms, e.g. OrderDetails, and there is no DoCmd.Close to close any instance.
Can you please post a database showing all of your relevant code, and that when we comment out your DoCmd.Close line, the form no longer closes? And/or compare your code with that of NW2? Maybe the discrepancy will then become obvious.
It was another developer's code, actually, so I can't directly. He was spawning custom message box forms in A365, though, popups but not modal or dialogs. While clearing references in his 32-bit apps continued to work, a client of his flagged that in 64-bit, those forms began remaining open.

My own experience in 64-bit (A2019) is with a timer form. This is an element of a filter class deeply embedded in an application framework without which it won't run, so posting is impractical. The idea, though, is to exploit a form solely for its Timer event. Each keystroke in a FAYT scenario assigns a new timer form instance to a module-level form variable declared WithEvents. The module then sinks that variable's Timer event to run the form or list filter. The effect is to defer filtering until the user stops typing. What happened in my case, concurrently with the other developer's cases, was that filtering often left one or more timer form instances both open and visible.

The same fix worked in both cases.

I put this in the category of "here's the fix if you need it but ignore it otherwise." It's intended to be helpful to anyone who encounters the issue, but doesn't guarantee that everyone will. I wouldn't think to post it if the behavior wasn't confirmed and there weren't two MVPs on the thread.
 
The default behavior, as noted by Allen Browne, had been that removing references to a form instance closes it.
This should still be correct.
Can you provide a reproducible example were it fails?

So, apparently, one now must also close the form instance expressly. This requires a DoCmd.Close call but this poses a few difficulties:
Just send a WM_CLOSE message to the form window.
 
@sonic8 , I thought I had responded the other day but I must not have clicked "Post reply" because it doesn't appear. Apologies, and trying again:

This should still be correct.
Can you provide a reproducible example were it fails?
We thought so, too.

I can't provide the other developer's code directly and mine is embedded deeply in an application framework that can't be abstracted simply, but I detailed the context in both cases in my response to @tvanstiphout . We also know that it seems only to manifest in 64-bit Access, however
Just send a WM_CLOSE message to the form window.
Yes, of course, and thanks. We should patch you in next time. This approach hadn't occurred to any of us but actually is quite elegant because it allows one to exploit Form.hWnd to specify a specific instance or instances, which is precisely the difficulty one has in crafting a fix solely in VBA.
 
My guess is you still have an open pointer to the instances. The code probably sets more than one variable.
 
- So, we first must set focus to the form instance intended to be closed.

If, when each new instance of a form is established, it is assigned to a collection, a specific instance of the form can be referenced by its ordinal position in the collection. Focus can then be set to that instance and the Close method of the DoCmd object called without any further arguments.

The tricky part is knowing the ordinal position of each instance at any one time, as its position in the collection will change as other instances of the form are closed.
 
as its position in the collection will change as other instances of the form are closed.
You can store your forms in an array, collection, dictionary, or custom object. Each option has its quirks. You might even consider creating a custom form store using a class with methods designed for handling multiple form instances.

Anyway, it is a lazy Sunday. I am just passing by to suggest that the OP should use the debugger to check where the forms are being persisted, rather than guessing. Or, like the others say, provide a reproducible example.
 
You should also be able to create a non-default instance of a form that reference-counts itself to stay open, like...
Code:
Private me_ As Form

Private Sub Form_Open(Cancel As Integer)
    set me_ = Me
End Sub

Public Sub CloseMe()
    Set me_ = nothing
End Sub
... which frees up your consumers from having to manage instancing. Calling the CloseMe() method should close only that instance.
 
@MarkK,
I am trying to see a case where that would be useful. Most of the time you use form instances you open a variable amount of instances. You use a collection or an array that is dynamic instead of a set variable for each instance. So how would you call closeMe without defining a variable pointing to that instance?
Can you give an example where you would use it? I guess if you only ever open a second instance this could be of use, but that does not seem like the norm when using form instances.
 
I did have a issue with 2 forms that held a reference to each other. My solution was to add a public "Close" sub to each form, allowing closing the form though the reference before setting it to nothing.
 
I am trying to see a case where that would be useful. Most of the time you use form instances you open a variable amount of instances. You use a collection or an array that is dynamic instead of a set variable for each instance. So how would you call closeMe without defining a variable pointing to that instance?
Can you give an example where you would use it? I guess if you only ever open a second instance this could be of use, but that does not seem like the norm when using form instances.
Maybe there's a misunderstanding. This is the only way I open non-default instances, and I never actually call the CloseMe method, I just close the form using the close button. This frees me up from having manage instances externally. Because the form is a circular reference memory leak, it stays open by itself. You create an instance like....
Code:
Sub ShowTransaction(ID as long)
    With New Form_NonDefaultTrans
        .GoToID ID
        .Visible = True
    End With
End Sub
...and that is all. The consumer/creator doesn't need to manage instance references because the form sets an object reference to itself. And every time you call ShowTransaction(), a new instance pops open to whatever record. You can open as many as you want.

Here's a v. simple sample of how you can do non-default instances. And you can close them with the built-in close button or by setting me_ to nothing...
 

Attachments

Maybe there's a misunderstanding.
Yes I was confused because you showed this in your original and not the event procedure you put in the example.
Code:
Public Sub CloseMe()
    Set me_ = nothing
End Sub

Since the original thread seemed to be referring to closing instances external from the form, I was trying to understand how you would call that method. Now I understand the point was not about the close but about the form maintaining its own pointer to keep itself open. That is a pretty interesting concept.
 
Hum, I not found a issue with simply tossing the form reference out of scope.

So, assuming a collection used to hold the multiple instances?

Then this should suffice (assuming we use caption).

So, then this:

Code:
Private Sub Form_Close()

   Call RemoveForm(Me.Caption)

End Sub

And the routine to remove the form from the collection:


Code:
Public Sub RemoveForm(sCaption As String)

    Dim i       As Integer
   
    For i = 1 To colForms.Count
        If colForms(i).Caption = sCaption Then
           colForms.Remove (i)
           Exit Sub
        End If
    Next i

End Sub

Thus, in most cases, one can use this if a button is used to close the form:

Code:
Private Sub cmdOk_Click()
     
    DoCmd.Close
   
End Sub

The above will thus trigger the form close event, which of course removes the form ref from the collection.

And to be fair, I have found it rather consistent that removing the form ref from the collection (as per above) has worked rather consistent for me, even without a docmd.close form....

R
Albert
 
That is a pretty interesting concept.
I think so.
• It's fire-and-forget, and simpler than maintaining an external reference list to manage lifetimes and instances. The object itself becomes the single-source-of-truth regarding how and when it goes out of scope.
• And you can enable the Form's built-in close button because you don't have to reconcile its pending closure with some external list. If the user closes it directly, fine.
:)
 

Users who are viewing this thread

Back
Top Bottom