As far as I understand, WM_SIZING is a modal message, meaning that the event processing will enter an inner loop and only return to the user when the resizing is over. I also know that this event begins with WM_ENTERSIZEMOVE and ends with WM_EXITSIZEMOVE.
My question is: is there any way to disable this behavior so I can return to my own message loop after every single sizing event?
I know usually updates can be performed by setting a timer - and then killing it at the end of the modal message/event, but I'd like to return the control to my message loop.
Related
I would like to do a simple thing — in the unit with the logic of handling the window, I want to update the window (the first event loop), and then in another unit with the logic of e.g. keyboard handling, update its state (second event loop). Window events must be put back in the SDL queue to be visible when the keyboard is updated (to be able to check if the window is in focus or not).
In short, there is one event queue (internal to SDL), but there are many loops that read events. If an event needs to be visible in several loops, it must return to the queue after handling it. To make this possible, each loop should iterate over the contents of the queue in such a way as not to process the events that was pushed back to the queue after handling.
For this, I am using SDL_PeepEvents which returns the number of queued events and iterates through the for loop this many times. The test program code looks like this:
uses
SDL2;
procedure UpdateWindow();
var
Event: TSDL_Event;
Index: Integer;
begin
for Index := 0 to SDL_PeepEvents(nil, -1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT) - 1 do
begin
SDL_PollEvent(#Event);
if Event.Type_ = SDL_WINDOWEVENT then
case Event.Window.Event of
SDL_WINDOWEVENT_FOCUS_GAINED: WriteLn('focus gain - window');
SDL_WINDOWEVENT_FOCUS_LOST: WriteLn('focus lost - window');
end;
// put all events back in the queue to be seen in other event loops
SDL_PushEvent(#Event);
end;
end;
procedure UpdateKeyboard();
var
Event: TSDL_Event;
Index: Integer;
begin
for Index := 0 to SDL_PeepEvents(nil, -1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT) - 1 do
begin
SDL_PollEvent(#Event);
// reading window events a second time
if Event.Type_ = SDL_WINDOWEVENT then
case Event.Window.Event of
SDL_WINDOWEVENT_FOCUS_GAINED: WriteLn('focus gain - keyboard');
SDL_WINDOWEVENT_FOCUS_LOST: WriteLn('focus lost - keyboard');
end;
SDL_PushEvent(#Event);
end;
end;
var
Window: PSDL_Window;
Done: Boolean = False;
begin
SDL_Init(SDL_INIT_EVERYTHING);
Window := SDL_CreateWindow('Events test', SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_SHOWN);
while not Done do
begin
SDL_PumpEvents();
UpdateWindow();
UpdateKeyboard();
// clear events queue
SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT);
SDL_Delay(20);
end;
SDL_DestroyWindow(Window);
SDL_Quit();
end.
The above code works, but only sometimes. When the program is started and the window appears, sometimes it says that the window and keyboard are in focus, and sometimes it's just that the window has focus only. This means that the SDL_WINDOWEVENT_FOCUS_GAINED event was handled in the first loop but not seen in the second loop (even though it was added back to the queue).
With the console window and the SDL window visible, I click alternately to both, activating and deactivating the SDL window. Each time the SDL window is activated, two lines should be added to the console (one for the event loop) and the same when deactivating the window. Unfortunately, the content of the console when clicking looks like this:
focus gain - window
focus gain - keyboard
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
focus gain - window
focus gain - keyboard
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
{...}
As you can see, sometimes when activating a window, the second loop doesn't see the SDL_WINDOWEVENT_FOCUS_GAINED event, so it doesn't add the focus gained - keyboard line to the console. This event is lost somewhere even though every event in the first loop is queued back using SDL_PushEvent.
Question: how to properly handle SDL events in multiple loops and how to put handled events back in the queue so that they are visible to multiple loops?
Lazarus 2.2.0, FPC 3.2.2, SDL2-for-Pascal headers and SDL 2.0.22.0 — all 64-bit.
If possible, don't do that at all. Best solution is to fetch events only once. If you want to process events multiple times, nothing prevents you from fetching all events you're interested in into single array and iterating this array as many times as you want. You can fetch all events into big array with single SDL_PeepEvents(SDL_GETEVENT) call.
The problem you have is based on two factors: SDL_PollEvent's return value is 0 when queue is drained; but nowhere in documentation it is stated that SDL_PushEvent puts event into the same processing frame - i.e. PollEvent may indicate queue is drained and return your newly pushed events on next frame. If that happens, in your second loop you process one event incorrectly (as PollEvent said 0 and no data is filled into Event structure), and skip one last event.
But, leaving higher level logic as it is, you have two options:
Use SDL_PeepEvent to fetch event, not SDL_PollEvent. I.e. replace SDL_PollEvent(#Event) with SDL_PeepEvent(#Event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT).
Check return value of SDL_PollEvent and if it returns 0 - you'll have to call it again, as it means no event is popped from queue. This solution is prone to race conditions and you can't guarantee it wouldn't call SDL_PumpEvents under the hood, and your last SDL_FlushEvents may kill some unprocessed events.
I needed to create a application where the Timage can be hide after a certain key is pressed under a certain situation.
I have set KeyPreview to true, and the image can hide within the onKeyPress method, everything worked properly. But as soon as I implemented a while loop inside the onKeyPress method, the image doesn't hide anymore after the appropraite key is being pressed.
What should I do to fix that?
You shouldn't do a WHILE loop inside an event handler. The event handlers should do a simple update to UI and then exit, to allow Delphi to reflect the UI update on screen.
Move your WHILE loop out of the KeyPress event, preferably into a background thread, alternately into the Application.OnIdle event (where you then perform ONE iteration of the WHILE loop within the OnIdle event and turn off processing when the WHILE condition isn't valid anymore).
The "poor man"'s solution (and not one that is recommended, but can be used if all other options are too complicated) is to call
Application.ProcessMessages
either within the WHILE loop (if you are altering UI elements in the loop) or just before starting the WHILE loop (if you're not).
Note, however, that while the "Application.Processmessages" code is running, all other event handlers are capable of being run, so if you, f.ex., run an Application.ProcessMessages within a Timer Event, the Timer Event may be called again before the currently running Timer Event has finished running (leading to an infinite recursive loop and eventually a stack overflow and termination of your program).
My understanding is any modal dialog automatically has its own message-pump, running on a thread dedicated to that dialog - is that right?
If so, how does a modal dialog's existence affect the main application's message loop? Do both run in parallel, does one take priority?
I have a situation where a modal dialog seems to get stuck for several seconds waiting for something, and wondered if it's possible the dialog is forced to wait until the main application thread is not busy?
As IInspectable explained, the modal dialog will run in the same thread as the caller. Therefore if you run the dialog from the main UI thread that has your main message loop, you'll end up with a nested message loop. The stack would look something like:
WinMain
YourMainMessageLoop
DispatchMessage
SomeMessageHandler
DoModal
and DoModal spins in its own GetMessage/TranslateMessage/DispatchMessage loop. The main message loop (YourMainMessageLoop in the sample stack above) is "active" in the sense that it's still running, but it's blocked by the dialog's message loop. Execution won't return to YourMainMessageLoop until DoModal exits.
Note that even if you're within the modal dialog's message loop, your other windows will still handle messages because GetMessage and DispatchMessage will still retrieve and direct messages to those windows and invoke their WndProcs.
For a long time I have been wondering how modal dialog is implemented.
Let me take Qt as an example. (Nearly all GUI toolkit has this mechanism)
In the main event loop, a slot is called, and in this slot a modal dialog is opened. Before the dialog is closed, the slot doesn't return control to the main event loop. So I thought that the main event loop is blocked and become unresponsive. Apparently this is not true, since when you open a modal dialog, the background main window is still working, like repainting its UI or keep displaying a curve or some graph. It just becomes not to accept any user input.
I did an experiment. I didn't open a modal dialog in the slot, but start a new thread there, and wait for the thread to finish in that slot. This definitely blocked the main event loop.
How modal dialog is implemented after all? How does it keep main event loop unblocked but at the same time blocked the calling slot?
There is only ever a need for a single event loop, and it does not block when a modal dialog appears. Though, I suppose, different toolkits may handle this differently. You would need to consult the documentation to know for sure. Conceptually, however, it all works in the same way.
Every event has a source where the event occured. When a modal dialog appears, the event loop either ignores or redirects all events that originate outside of the dialog. There's really no magic to it. Generally speaking, it's like an if statement in the event loop code that says "if (modal_is_shown() and !event_is_in_modal_window()) {ignore_and_wait_for_next_event()}". Of course, the logic is a bit more complicated, but that's the gist of it.
If you're looking for examples here's another one:
In Tk, there is only ever one event loop. Modal behavior (doesn't have to be dialog, can also be tooltips, textbox etc) is simply implemented by making the main window ignore mouse and keyboard events. All other events like redraws etc. can still be serviced because the event loop is still running.
Tk implements this via the [grab] function. Calling grab on a UI object makes it the only object able to respond to keyboard and mouse events. Essentially blocking all other objects. This doesn't mess with the event loop. It merely temporarily disables event handlers until the grab is released.
It should be noted that Unix-like operating systems running X also has grab built in to the windowing system. So it's not necessarily implemented merely by UI toolkit libraries but is sometimes also a built in feature of the OS. Again, this is implemented by simple blocking/disabling of events instead of instantiating separate event loops. I believe this also used to be the case for the older MacOS before OSX. Not sure about OSX or Windows though. Even though modality is often implemented by the OS itself, toolkits like Qt and Tk often implement their own mechanisms to standardize behaviors across different platforms.
So the conclusion is, it is not necessary to block the main event loop to implement modality. You just need to block the events and/or event handlers.
The answer by https://stackoverflow.com/users/893/greg-hewgill is correct.
However, reading the follow-up discussion between him and https://stackoverflow.com/users/188326/solotim , I feel that there is room for further clarification, by means of prose and some pseudo-code.
I'll handle the prose part with a fact-list:
The main message loop does not run until the modal activity is finished
However, events are still delivered while the modal activity is running
This is because there is a nested event loop within the modal activity.
So far I just repeated Greg's answer, please bear with me as it is for continuity's sake. Below is where I hope to contribute additional, useful info.
The nested event loop is part of the GUI toolkit, and as such, it knows the callback functions related to every window in existence
When the nested event loop raises an event (such as a repaint event directed to the main window), it invokes the callback function associated with that event. Note that "callback" here may refer to a method of a class representing a window, in object-oriented systems.
the callback function does what is needed (e.g., repaint), and returns right back to the nested message loop (the one within the modal activity)
Last, but not least, here's pseudo-code to hopefully illustrate further, using a fictitious "GuiToolkit":
void GuiToolkit::RunModal( ModalWindow *m )
{
// main event loop
while( !GuiToolkit::IsFinished() && m->IsOpen() )
{
GuiToolkit::ProcessEvent(); // this will call
// MainWindow::OnRepaint
// as needed, via the virtual
// method of the base class
// NonModalWindow::OnRepaint
}
}
class AboutDialog: public ModalWindow
{
}
class MainWindow: public NonModalWindow
{
virtual void OnRepaint()
{
...
}
virtual void OnAboutBox()
{
AboutDialog about;
GuiToolkit::RunModal(&about); // blocks here!!
}
}
main()
{
MainWindow window;
GuiToolkit::Register( &window ) // GuiToolkit knows how to
// invoke methods of window
// main event loop
while( !GuiToolkit::IsFinished() )
{
GuiToolkit::ProcessEvent(); // this will call
// MainWindow::OnAboutBox
// at some point
}
}
In general, a modal dialog box of this type is implemented by running its own message loop instead of your application's message loop. Messages directed to your main window (such as timer or paint messages) will still get delivered, even during the modal operation.
In some situations, you may have to be careful that you don't recursively do the same thing repeatedly. For example, if you trigger a modal dialog box on a timer message combined with some persistent flag, you'll want to make sure you don't keep bringing up the same dialog box repeatedly whenever the timer message fires.
In Windows especially, is it true that the thread will exit if the event is signaled. What does it mean when an event is signaled?
First of all, you must understand what an event is. Any performance of UI or programmatic action can be called as event. For eg, clicking a mouse button or pressing a keyboard key, etc.
Now, an event is signalled means the occurance of such an event.