Originally Posted by
SamT
GTO, as usual, you are right. Thank you for the lesson and the link. It is always good to learn more bout my favorite subject.
Hi Sam,
My pleasure and of course you are most welcome. I just wish I had your "brain" for relationships and DB design. I would so much like to be able to combine some stuff that I "know" can go together. I know that learning is what makes it interesting and challenging, but some days, I sure would like to be able to just "download" others knowledge. (I realize that life would become quite the bore if that were possible, just saying...)
Well, here we go...
Originally Posted by
SamT
In my "test" in #13, I obviously used way too simple of an experiment and the form was merely loading and unloading without my notice. My bad
This explains why in #14, snb used a public variable in the MacroModule, his sub frmstop doesn't have to refer to the form to see if it is still open.
The research and rereading all the code herein made me realize that the sub KillFormNow in my #9 must actually read:
Private Sub KillFormNow()
'since minWait = maxWait, the procedure will not run even if schedule = True.
Application.OnTime EarliestTime:=minWait, Procedure:="KillTheSplashScreenForm", LatestTime:=maxWait, Schedule:=False
If Application.Workbooks.Count = 1 Then
Application.Quit
Else
ThisWorkbook.Close Savechanges:=False
End If
End Sub
@snb:
No offense intended, but even a blond guy can paste that little bit of code in a workbook accurately and run it. I am awfully confident I did so accurately, and when run, [bleep..........]!
@SamT:
Errr... well... about snb's suggestion (at all: please take this in the light-hearted manner intended) , did you try it? Before you do, close anything else you are working on.
See, yours would continue to call Unload, which in turn (as the form was already unloaded), would re-initialize the form - then re-unload it if-you-will, and during initialize, set another .OnTime. snb's does similarly, just way worse. As snb has called frmstop in QueryClose, if we 'manually' kill the form (the 'X' close button) before .OnTime calls frmstop, then we have already unloaded the form. Now when QueryClose calls frmstop, the UnLoad is run into again, and re-initializes, setting another new .OnTime, before again unloading. Same 'recurse'.
If we let the form run, then the .OnTime calls frmstop before the first QueryClose, so we just started our "evil loop" from a different place.
I have attached a modified version of snb's suggested code to sort of show what is happening, but it is not really spot-on, as collecting the values to variables and writing them to a textfile takes time, so some of the processing appears lost from snb's literal suggestion. If you have everything else closed, you can run snb's code from the vbe windows and watch. (Seriously, don't have anything else open)
In the form's initialize, hit F5. Once the userform disappears, you will be staring at vbe. If the form is displayed, close that window quickly, so that you are staring at:
Private Sub UserForm_Initialize()
c00 = DateAdd("s", 5, Now)
Application.OnTime c00, "frmstop"
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
frmstop
End Sub
Keep your mouse pointer left of
Private Sub UserForm_Initialize()
so that you can click and put a break in. Now watch the titlebar. You will see that code gets run every few seconds, and as far as I can tell, starts adding OnTime's as it starts running more of the time. If you leave it go, pretty soon Excel is unresponsive...
As mentioned, I included a modified copy, to sort of see what happens. Again though, not exactly accurate due to the slowing of overall execution. It does however include a constant that limits how many times we'll 'recurse'.
To try this:
F5 from A_RunIt.
When the form disappears, just wait until the Stop in UserForm_Initialize is reached, Now F8 until all done (the second time the End Sub is reached in frmstop).
Now, F8 or F5 thru Cleanup to close the created text file and reset everything. You can read thru the created text file.
Okay - as to your code, I think KillFormNow is fixed, but we did not handle the user closing the form "manually". In summary, I think we are facing a "bad" combination of three things.
- We start the .OnTime in the form's initialize, which means accidently starting a new .OnTime if we accidently initialize.
- We refer to the form by its CodeName, rather than setting a variable to a new instance of the form.
- We aren't handling if the user closes the form "manually".
Here (included in the zip) is your last, modified a tiny bit. It seems to work for me.
[Option Explicit
'***Post 9
Dim maxWait As Variant
Dim minWait As Variant
Private Sub cmdSplashScreenQuit_Click()
'KillFormNow
Unload Me
End Sub
Private Sub UserForm_click()
'KillFormNow
Unload Me
End Sub
Private Sub UserForm_Initialize()
minWait = Now() + TimeValue("00:00:05") 'do not execute the procedure before this time
maxWait = Now() + TimeValue("00:00:05") 'do not execute the procedure after this time
Application.OnTime EarliestTime:=minWait, Procedure:="KillTheSplashScreenForm", Schedule:=True
End Sub
'***End Post 9
'***Post 16
Private Sub KillFormNow()
If Now < minWait Then 'added _mws
'since minWait = maxWait, the procedure will not run even if schedule = True.
Application.OnTime EarliestTime:=minWait, Procedure:="KillTheSplashScreenForm", LatestTime:=maxWait, Schedule:=False
If Application.Workbooks.Count = 1 Then
'Application.Quit
ThisWorkbook.Close SaveChanges:=False
Else
ThisWorkbook.Close SaveChanges:=False
End If
End If
End Sub
Private Sub UserForm_Terminate()
KillFormNow
End Sub
In gist, have Terminate call KillFormNow, and in that procedure, test to see if the user dismissed the form before the .OnTime's call ran.
FWIW, here is what I came up with (also attached):
In a Standard Module:
Option Explicit
Private MyForm As frmOnTimeTest
Public timeEarliest As Date
Public Sub ButtonClick01()
'//Less problems if we use a variable to reference an instance of the form. //
Set MyForm = New frmOnTimeTest
If IsLoaded(MyForm) Then
timeEarliest = Now + TimeSerial(0, 0, 5)
'// Might well be better ways, but to ensure specific instance of the form is loaded... //
Application.OnTime EarliestTime:=timeEarliest, _
Procedure:=ThisWorkbook.Name & "!'basOnTimeTest.KillSplash GetForm'"
MyForm.Show
End If
End Sub
Private Sub KillSplash(frm As Object)
'// As 'Is Nothing' doesn't seem reliable, test some other way to see if the form is loaded.//
If IsLoaded(frm) Then
Unload frm
'Debug.Print "Unloaded by .OnTime"
End If
End Sub
'// I would imagine this to be similar to a read-only property get? Anyways, it is //
'// what I managed, in order to be able to pass an arg w/the .OnTime //
Public Function GetForm() As Object
Set GetForm = MyForm
End Function
Private Function IsLoaded(ByVal frm As Object) As Boolean
Dim sTemp As String
On Error Resume Next
'// I have seen examples looping through the userforms collection to test against the //
'// form's name, but I was thinking maybe producing an error by testing against a //
'// built-in property. An error should indicate that either no reference has been set,//
'// or the reference has been but unloaded (i.e. Callee...has disappeared). //
sTemp = frm.Name
IsLoaded = Err.Number = 0
On Error GoTo 0
End Function
In a userform named frmOnTimeTest:
[Option Explicit
Private Sub cmdUnload_Click()
Unload Me
End Sub
Private Sub UserForm_Click()
Unload Me
End Sub
Private Sub UserForm_Initialize()
'// Just to see if stepping thru //
End Sub
Private Sub UserForm_Terminate()
'// Ensure the .Ontime hasn't already expired before cancelling //
If timeEarliest > Now Then
Application.OnTime EarliestTime:=timeEarliest, _
Procedure:=ThisWorkbook.Name & "!'basOnTimeTest.KillSplash GetForm'", _
Schedule:=False
'Debug.Print ".Ontime cancelled, user dismissed form"
End If
End Sub
Originally Posted by
SamT
What do you think about the OPs comment in re minWait = maxWait?
It seems neat, but I have not tested yet.
Way past when I should have hit the rack, a great day to you :-)
Mark