1

I need to have a popup form resizable for users whose screen is not as large as others - setting the form to Popup and Modal and BorderStyle Resizable has one major limitation - the code in the form that launches the popup now does not wait for the form to return.

So how does one wait for a form to be made invisible? I tried looping with sleep and doevents, but that makes the popup form not very responsive and chews up cpu cycles. I have tried setting the form.gotfocus event of the launching form but that does not trigger and which means I have to split the code that opened the popup form from the code that executes after the popup form is closed.

What is the best solution?

Thanks

Charlie
  • 11
  • 1
  • 3

3 Answers3

1

I have never had any problems with DoEvents / Sleep 50 loops. CPU load stays minimal and the form responsive.

With a very old computer, perhaps use Sleep 100.

Sample code:

Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)

Sub TestOpenForm()

    If FormOpenWait("frmPopup") Then
        MsgBox "Form was hidden.", vbInformation
    Else
        MsgBox "Form was closed.", vbInformation
    End If

End Sub

' Open fName, wait until it is
' - closed : return False
' - hidden : return True
Public Function FormOpenWait(fName As String, Optional sOpenArgs As String = "") As Boolean

    If IsFormLoaded(fName) Then DoCmd.Close acForm, fName, acSaveNo

    DoCmd.OpenForm FormName:=fName, OpenArgs:=sOpenArgs

    ' default: signal "closed"
    FormOpenWait = False

    ' Wait until form is closed or made invisible
    Do While IsFormLoaded(fName)
        If Not Forms(fName).Visible Then
            ' Signal "hidden"
            FormOpenWait = True
            Exit Do
        End If

        ' Wait 50ms without hogging the CPU
        DoEvents
        Sleep 50
    Loop

End Function

Public Function IsFormLoaded(fName As String) As Boolean
    IsFormLoaded = (SysCmd(acSysCmdGetObjectState, acForm, fName) And acObjStateOpen) > 0
End Function
Andre
  • 26,751
  • 7
  • 36
  • 80
  • I disagree. It works perfectly fine. Splitting up the code is way uglier. And if you e.g. want to wait until a query (datasheet view) is closed, it's the only option. @Enigmativity – Andre Nov 06 '17 at 09:31
  • I see you have a boilerplate for these. :) But you are aware that this question is about MS-Access and VBA? Not C#. There is no https://stackoverflow.com/questions/5721564/multi-threading-in-vba – Andre Nov 06 '17 at 09:44
  • Ah, good point. I did miss that. I might then delete these comments as they are misleading. – Enigmativity Nov 06 '17 at 09:45
  • This code is exactly what I needed to get the modal behaviour and keep the resizing of the opened form available. Thanks! – Carvell Fenton Sep 18 '19 at 14:34
0

You can open the form with acDialog option:

DoCmd.OpenForm "MyFormName", WindowMode:=acDialog

It will wait until the form closed or hidden.

Sergey S.
  • 6,296
  • 1
  • 14
  • 29
0

The problem here is popup and modal forms don’t’ halt calling code.

But worse is your need to halt calling code. That requires a dialog form.

And more worse is a dialog form does NOT allow re-sizing.

You don’t want to confuse the 3 types of forms here.

Modal forms – they are different then popup forms, and very different from dialog forms.

And same goes for Popup forms. They are not dialog forms, and in fact they are also not model.

You also have to take into consideration if you Access application is set to use tabbed documents, or overlapping windows. (this will limit your choices again).

If you using tabbed interface, then you have to use a popup form if you want re-sizable ability – but this type of form will not halt calling code.

If you using overlapping windows, then I recommend a modal form. (But again it will not halt calling code, but will allow re-size).

While you “could” adopt some looping code in the calling form that “waits” for the second form to be dismissed, such loops are processor hogs and often cause a “poor” response in terms of mouse and typing.

So I would suggest that change your code approach. Have the calling form launch the form as popup (or modal if you not using the tabbed interface).

Then when you close the form, it calls some more code that you want to run in the calling form. This does mean you have to split out the code that you want to run after closing that 2nd form – and have the closing form call + run that code.

A “general” approach should avoid hard coding the forms names since then “many” routines and forms can call your second forms and that facilities re-use of such forms.

So try this: In the second form, create a module level form variable like this:

Option Compare Database
Option Explicit

Dim frmPrevious     As Form

In the forms on-open event of this form, GRAB the calling forms refeance like this:

Set frmPrevious = screen.ActiveForm

Now in the forms close event , do this:

frmPrevious.AfterClose

And in the calling form, create a public function called

AfterClose

So in the public function in the first form, you place the code that you want to run when you close the 2nd form. This is "less" ideal then nice procedural code that calls the 2nd form, waits and continues - but I think it still about the best choice.

And before you close, you can pass or “set” values to return to the calling form like:

frmPreviuos.SomePubicVar = me!LastNameSelected
frmPrevious!Company = me!CompanySelected
frmPrevious.AfterClose

In the above the first line sets a public variable in the calling form (if you want to do that and pass values back to module level vars in the first form). And the next line sets the value of a control in the calling form (if you want to pass values back to some controls on the calling form).

And the 3rd line executes the MyClose code in the calling form. And if you have some kind of “ok” or select button in that second form, then move the above code to behind that button – since you may not want such code running if the form has a cancel button (so the cancel button would simply close the form – but not call + run the above code in the first form). And hitting the X would also be assumed to be a cancel or exit. So your "save" or "ok" or "select" button would have the above code.

Albert D. Kallal
  • 42,205
  • 3
  • 34
  • 51
  • Thanks for the thorough explanation - you have provided a workable solution and some hints at better coding - I am going to try to solution provided that shortens the sleep time and see if that works, since the procedural approach lends to more cleaner code, but if the response time or cpu cycling is too high, I will implement your solution - thanks – Charlie Oct 07 '17 at 14:38
  • As a new Access programmer, I need to ask a lot of questions about this technique because I'm struggling to use it. For starters, do I need to put 'Set frmPrevious = Screen.ActiveForm' in BOTH the calling and called forms to make this work correctly? How would I determine or demonstrate for myself which form frmPrevious points to, to make sure the reference is to the correct form? – N.Barrett May 13 '20 at 21:57
  • Screen.ActiveForm is only used in the form you are calling (opening). You can use the on-open event, or even the on-load event. Until these two events are 100% finished, then the active form will be the previous (calling form). So, you don't do anything special in the form that launches the 2nd form. However, consider using a dialog form, as that is somewhat easier, and it halts the calling code. And you would do well to start a new question. – Albert D. Kallal May 14 '20 at 02:20
  • Thank you for all the information. I've started a thread about this: https://stackoverflow.com/q/61806180/10913860. – N.Barrett May 14 '20 at 19:49