I was under the illusion that if there was a message box open that no program code was executing because the program was waiting for an response from the user.
I was under the same wrong impression about when a modal form was open and the user hadn't performed any actions on it.
However I am now aware that timer events (probably only on other open forms though?) can also fire and other code can execute. Makes sense but I didn't think that this was the case.
I am still a bit unclear on things though.
Say I click on a button and the click event's code fires and in the middle of its code are calls to other subs and functions and maybe objects.
Also say that in none of the code that is called is there a DoEvents or a message box.
Then I am guessing that no timer can fire until all that code has executed.
Is that correct or wrong?
Is there anything else that can cause a pause and allow other code to execute?
As a single threaded application, the timer can't fire while you have active code running (i.e. code that is running in an event handler (calling subs and functions from an event handler is still running code within the event handler)
.
When you create a modal window, the code that opened the window is suspended and the modal window takes over the processing of window events for the application, so events for timers and paint events will still be handled for the whole application, not just the modal window, (you don't want the other forms in your project to not refresh just because a modal window is showing).
But keyboard events and mouse events can only occur in the modal window and once the modal window is closed, your code continues and when your code exits the event handler it was in, then other events can be processed, e.g. timer and paint events as well as other mouse or keyboard inputs.
I don't think this issue is well worked out, at least not for VB6.
Now, as we all know, VB6 is single-threaded. However, with respect to the timer, where that thread is and where it's allowed to go seems to be a bit of a mystery. With a MsgBox or a modal form, we all know that the procedure that invoked the Msgbox or modal form is suspended on the line that called it. And, it resumes when the MsgBox or modal form is closed (or made non-modal in some way).
However, from a larger perspective, that thread is either in one of two places: 1) It's executing some of our code, or 2) It's being handled by Windows, waiting on some event to happen so it can hand the thread back to some of our code.
Now, when a Msgbox or modal form is loaded, the question becomes: which of our code is Windows allowed to hand the thread back to? (Still remembering that some procedure somewhere is suspended that called the MsgBox or modal form). Ok, sure, in the case of a modal form, we could call other procedures in form that invoked the modal form. In fact, we could call pretty much anything we've got access to.
Now, the case of the timer. The timer is just something else that's handled by Windows which hands the thread to one of our procedures (i.e., the timer event). So, when will Windows (via a timer) be allowed to hand the thread to the timer event, and when will it not? That's essentially your question, and I don't have a clear answer, but I'd sure like to see it worked out.
Here was one hypothesis that I made: That all code in a parent that invoked the MsgBox or a child modal form would be suspended (unless specifically called by the child modal form). However, this turned out to be only partially true. For instance, clicking Command1 in the following code suspends the timer:
Code:
Option Explicit
Private Sub Command1_Click()
MsgBox "some message"
End Sub
Private Sub Form_Load()
Timer1.Interval = 3000
Timer1.Enabled = True
End Sub
Private Sub Timer1_Timer()
Debug.Print "timer1"
End Sub
In case it's not obvious, the above is a Form1 with a Timer1 control and a Command1 button.
Now, that made sense to me. The MsgBox was a child of Form1; therefore all the code in Form1 was suspended until the MsgBox closed.
However, the following code did not suspend the timer:
Code:
Option Explicit
Private Sub Command1_Click()
Form2.Show vbModal, Me
End Sub
Private Sub Form_Load()
Timer1.Interval = 3000
Timer1.Enabled = True
End Sub
Private Sub Timer1_Timer()
Debug.Print "timer1"
End Sub
Again, in case it's not obvious, that's a project with a Form1 with a Timer1 and Command1, and a Form2 with nothing.
The fact that the timer was not suspended in the second case was a mystery to me. It makes me want to explore how the API SetTimer call might work, but I'll possibly mess with that a bit later. I'm just wondering if the parent of Form2 is being correctly set for Windows to see it.
I'm curious about what others have to say about this.
Best Regards,
Elroy
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
I'm not sure about the message box as that may be a system modal type window which suspends your whole application until you respond to it (including all events in your application, and even the IDE if you're running from the IDE, i.e. try clicking on the IDE when the message box is showing and see what happens).
A form that is shown modal creates its own message pump, so while the original message pump is suspended the new message pump continues to service the events for the application, which includes all active timers regardless of what form they are on. It is just that the form also captures the focus so you can't click or do keyboard input to any other form.
A modal form can show another modal form which continues the chain.
A modal form can not show a non-modal form.
I'm not sure about the message box as that may be a system modal type window which suspends your whole application until you respond to it (including all events in your application, and even the IDE if you're running from the IDE, i.e. try clicking on the IDE when the message box is showing and see what happens).
I think maybe it suspends everything in the IDE but it doesn't in a compiled exe.
It was some logging code, writing to a file, that showed me the timer events were still firing when a message box was open waiting for user input.
I just took some of my code from above and modified it as follows, and then compiled it:
Code:
Option Explicit
Private Sub Command1_Click()
MsgBox "some message"
End Sub
Private Sub Form_Load()
Timer1.Interval = 3000
Timer1.Enabled = True
End Sub
Private Sub Timer1_Timer()
MsgBox "timer1"
End Sub
Obviously, a Form1 with a Timer1 and Command1.
The timer fires as expected, showing the modal MsgBox. However, when you click Command1, thereby getting the "some message" MsgBox, the timer still fires. In fact, it shows yet another modal MsgBox ("timer1" as message). Now what's particularly weird is that you can click between these two message boxes, as if they're not modal. However, they are modal to Form1. It's almost as if a hierarchy of modality has been created.
Furthermore, once the timer's MsgBox is showing, the timer is suspended until you click on that one. I'm assuming that the timer is disabled until its event terminates.
This is all pretty weird stuff that I hadn't fully appreciated before. And it's quite fascinating that it's possible to have two modal forms on the screen that you can click between. I'm now wondering how to accomplish that with regular VB6 forms.
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
I made up a little project with 2 forms like Elroy has in his example.
It has trace logging built in and in the ide just prints to the debug window but in the compiled version prints to a file.
(The tracer class is based on code in Francesco Balena's vb6 book)
It shows the differences between the way things happen in in the ide vs the exe.
In the exe the timers keep firing, in the ide the timers stop firing if the msgbox is visible.
I just took some of my code from above and modified it as follows, and then compiled it:
Code:
Option Explicit
Private Sub Command1_Click()
MsgBox "some message"
End Sub
Private Sub Form_Load()
Timer1.Interval = 3000
Timer1.Enabled = True
End Sub
Private Sub Timer1_Timer()
MsgBox "timer1"
End Sub
Obviously, a Form1 with a Timer1 and Command1.
The timer fires as expected, showing the modal MsgBox. However, when you click Command1, thereby getting the "some message" MsgBox, the timer still fires. In fact, it shows yet another modal MsgBox ("timer1" as message). Now what's particularly weird is that you can click between these two message boxes, as if they're not modal. However, they are modal to Form1. It's almost as if a hierarchy of modality has been created.
Furthermore, once the timer's MsgBox is showing, the timer is suspended until you click on that one. I'm assuming that the timer is disabled until its event terminates.
This is all pretty weird stuff that I hadn't fully appreciated before. And it's quite fascinating that it's possible to have two modal forms on the screen that you can click between. I'm now wondering how to accomplish that with regular VB6 forms.
I just copied what you did and that's pretty weird all right watching a second msgbox pop up while the other is still there!
Okay, more mystery to the puzzle. I threw together an API timer test just to see what would happen.
Done this way, there doesn't seem to be any difference between the IDE and compiled. But, be forewarned, you have to do some clicking to terminate your program. (I suppose a kill in task manager is also a possibility).
There are two modules to this one, a BAS piece and a Form1 piece. Here's the BAS piece:
Code:
Option Explicit
'
Public Declare Function SetTimer Lib "user32" (ByVal hWnd As Long, ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
Public Declare Function KillTimer Lib "user32" (ByVal hWnd As Long, ByVal nIDEvent As Long) As Long
'
Public Function TimerProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal idEvent As Long, ByVal dwTime As Long) As Long
MsgBox "ApiTimer"
End Function
And here's the Form1 piece:
Code:
Option Explicit
'
Public mlTimerID As Long
'
Private Sub Command1_Click()
MsgBox "Command1 click"
End Sub
Private Sub Form_Load()
mlTimerID = SetTimer(Me.hWnd, 0&, 3000&, AddressOf TimerProc)
End Sub
Private Sub Form_Unload(Cancel As Integer)
KillTimer Me.hWnd, mlTimerID
End Sub
Form1 just has a single Command1 control on it.
This time nothing seems to stop the timer from firing. In other words, it puts a new modal MsgBox on the screen every three seconds, making as many for as long as you let it run. That's where the clicking comes in to get it shut down because you can't close Form1 until you get all the MsgBox's shut down.
Now, regarding Command1, it really makes no difference if that MsgBox is on the screen or not. The timer still fires.
So, in this situation, nothing slows down the timer. And, in a sense, API timers are more predictable than the timer control.
It's still puzzling (and interesting) how we can get multiple modal forms (all at the same hierarchical level) on the screen at the same time.
Okay, I had one more idea. I was thinking that we could manage a series of modal forms (all at the same hierarchical level) with some novel use of the API timer. But no cigar. I used the same above Form1 code, but added a (blank) Form2, and modified the BAS code as follows:
Code:
Option Explicit
'
Public Declare Function SetTimer Lib "user32" (ByVal hWnd As Long, ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
Public Declare Function KillTimer Lib "user32" (ByVal hWnd As Long, ByVal nIDEvent As Long) As Long
'
Dim c As New Collection
Public Function TimerProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal idEvent As Long, ByVal dwTime As Long) As Long
Dim f As Form
Set f = New Form2 ' New copy of form2.
c.Add f ' Put instantiation in collection so a reference is maintained.
f.Show vbModal ' Show it modal.
End Function
Yes, I get lots of copies of Form2 loaded modally. However, this time, the latest loaded Form2 is modal to all the others. All the loaded Form2's are not at the same modal hierarchy level, and must be shut down in the reverse order they were loaded. And this is different from the way the message boxes behaved. However, if I manage to get the Command1 clicked, that MsgBox shares modality with the latest loaded Form2.
All very bizarre.
EDIT1: So, one conclusion ... if you want to load a modal form over another modal form, but it's not code from the first modally loaded form that will load the second, start an API timer, use appropriate global flags, and then load the second modal form from within the timer event. Not sure where/when this'll be useful, but you never know.
Last edited by Elroy; Oct 6th, 2017 at 01:10 PM.
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
It's important to recognize that a modal dialog is still running an active message loop. That means it is still receiving window messages (like WM_TIMER) and dispatching them accordingly. If it didn't do this, all kinds of things would go wrong (e.g. underlying windows couldn't repaint). The main thing that changes is that the modal window's loop gets priority, meaning it can intercept messages before relaying them. It will typically consume input events like keyboard and mouse events, while relaying non-input events (like WM_TIMER) the same way that your main message pump would.
FYI. Another tip regarding modal windows when compiled and when in IDE.
Events are blocked when in IDE and a modal window is displayed (msgbox, common dialog, etc). Those same events, when compiled, are not blocked. For any of you that create usercontrols, RaiseEvent is blocked when in IDE while a modal window is shown. However, methods called via the Implements keyword are not. Regarding the timer, I'm guessing that the timer control, internally, uses a RaiseEvent call.
Insomnia is just a byproduct of, "It can't be done"