Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Pause event handler, go enter another one, then come back

34 views
Skip to first unread message

Frederick Virchanza Gotham

unread,
Nov 30, 2022, 8:47:04 AM11/30/22
to

I was looking through old code of mine that I wrote 10 - 15 years ago, and I saw this:

void Dialog_Main::OnButtonClick_SeeDetails( wxCommandEvent& event )
{
(new Dialog_Details(this,p))->ShowModal();

/* This function call doesn't return until the
dialog disappears, however wxWidgets has some
sort of sorcery going on in the background that lets it
jump out and process other events, then come
back to finish this one. */

/* Do more processing after dialog disappears */
}

There is only one GUI thread in wxWidgets. Any event is always processed inside an event handler by the GUI thread.

But some how, some way, wxWidget is able to pause one event handler half way through, then go execute another event, then come back to the original event handler and continue where it left off.

I posted on the wxWidgets forum ages ago about this: https://forums.wxwidgets.org/viewtopic.php?f=27&t=48833

And it seems that wxWidgets gets this behaviour from the underlying native API. So in the case of Microsoft Windows, it would be the Win32 API.

Years and years and years ago I remember writing Win32 code with a message loop with "GetMessage" to do the processing for a dialog box, but I never recall the possibility of event handlers being 'interwoven' so to speak.

Has anyone seen this kind of thing before? The only way I can fathom this working, i.e. for a thread to interrupt itself and execute some other code, then return to where it was previously, is if a signal is raised -- but there are major restrictions on what can happen inside a signal handler.

Does anyone know how this works?

Paavo Helde

unread,
Nov 30, 2022, 9:41:55 AM11/30/22
to
Something like what you describe could be done by coroutines. But here,
with modal dialogs the things are simpler, it's just a recursive call to
some kind of PumpMessage loop. In Windows all events are placed in the
window event queue, there are functions for processing them, typically
called something like PumpMessage, and when processing some event, the
handler code can contain a new message loop which will process all the
messages by itself for a while.

Here is a call stack from an event handler inside a modal dialog, with
many frames omitted for brevity. This is from MFC, not wxWidgets, but
they should be similar enough here.

Note how e.g. CWinThread::PumpMessage() appears twice in the call stack,
meaning that it is called recursively.

The first event processing loop is apparently in CWinThread::Run() in
the bottom of the stack, and the second one is in CWnd::RunModalLoop().


[....]
mfc140ud.dll!AfxPreTranslateMessage(tagMSG * pMsg) Line 252 C++
mfc140ud.dll!AfxInternalPumpMessage() Line 178 C++
mfc140ud.dll!CWinThread::PumpMessage() Line 900 C++
mfc140ud.dll!AfxPumpMessage() Line 190 C++
> mfc140ud.dll!CWnd::RunModalLoop(unsigned long dwFlags) Line 4661 C++
mfc140ud.dll!CWnd::CreateRunDlgIndirect(const DLGTEMPLATE *
lpDialogTemplate, CWnd * pParentWnd, HINSTANCE__ * hInst) Line 485 C++
mfc140ud.dll!CDialog::DoModal() Line 652 C++
[..........]
mfc140ud.dll!CWnd::IsDialogMessageW(tagMSG * lpMsg) Line 194 C++
mfc140ud.dll!CWnd::PreTranslateInput(tagMSG * lpMsg) Line 4607 C++
mfc140ud.dll!CControlBar::PreTranslateMessage(tagMSG * pMsg) Line 440 C++
mfc140ud.dll!CControlBar::PreTranslateMessage(tagMSG * pMsg) Line 431 C++
mfc140ud.dll!CWnd::WalkPreTranslateTree(HWND__ * hWndStop, tagMSG *
pMsg) Line 3379 C++
mfc140ud.dll!AfxInternalPreTranslateMessage(tagMSG * pMsg) Line 233 C++
mfc140ud.dll!CWinThread::PreTranslateMessage(tagMSG * pMsg) Line 778 C++
mfc140ud.dll!AfxPreTranslateMessage(tagMSG * pMsg) Line 252 C++
mfc140ud.dll!AfxInternalPumpMessage() Line 178 C++
mfc140ud.dll!CWinThread::PumpMessage() Line 900 C++
mfc140ud.dll!CWinThread::Run() Line 629 C++
mfc140ud.dll!CWinApp::Run() Line 787 C++
EvoShellD.exe!CShellApp::Run() Line 1051 C++
mfc140ud.dll!AfxWinMain(HINSTANCE__ * hInstance, HINSTANCE__ *
hPrevInstance, wchar_t * lpCmdLine, int nCmdShow) Line 47 C++
EvoShellD.exe!wWinMain(HINSTANCE__ * hInstance, HINSTANCE__ *
hPrevInstance, wchar_t * lpCmdLine, int nCmdShow) Line 26 C++
EvoShellD.exe!invoke_main() Line 123 C++
EvoShellD.exe!__scrt_common_main_seh() Line 288 C++
EvoShellD.exe!__scrt_common_main() Line 331 C++
EvoShellD.exe!wWinMainCRTStartup(void * __formal) Line 17 C++
kernel32.dll!00007ffc7f5e74b4() Unknown
ntdll.dll!00007ffc815026a1() Unknown

Ralf Fassel

unread,
Nov 30, 2022, 11:49:01 AM11/30/22
to
* Frederick Virchanza Gotham <cauldwel...@gmail.com>
| I was looking through old code of mine that I wrote 10 - 15 years ago, and I saw this:
>
| void Dialog_Main::OnButtonClick_SeeDetails( wxCommandEvent& event )
| {
| (new Dialog_Details(this,p))->ShowModal();
|
| /* This function call doesn't return until the
| dialog disappears, however wxWidgets has some
| sort of sorcery going on in the background that lets it
| jump out and process other events, then come
| back to finish this one. */
|
| /* Do more processing after dialog disappears */
| }
--<snip-snip>--
| But some how, some way, wxWidget is able to pause one event handler
| half way through, then go execute another event, then come back to the
| original event handler and continue where it left off.
--<snip-snip>--
| Does anyone know how this works?

I don't know for sure for wxWidgets, but how should it process the
dialog (button presses etc) if not by entering another event loop?

If you look at the source of wxDialog::ShowModal() (which seems to be
the workhorse for all the modals):

int wxDialog::ShowModal()
{
...
gtk_grab_add( m_widget );

wxEventLoop().Run();

gtk_grab_remove( m_widget );
...
}

which looks just like re-entering the event-loop.

As an aside, e.g. the TCL/Tk scripting language has explicit commands in
scripting and C-level to enter recursive event loops ('update', 'vwait',
Tcl_DoOneEvent()), so that does not seem very uncommon.

R'

Frederick Virchanza Gotham

unread,
Nov 30, 2022, 4:16:52 PM11/30/22
to
On Wednesday, November 30, 2022 at 4:49:01 PM UTC, Ralf Fassel wrote:
>
> int wxDialog::ShowModal()
> {
> ...
> gtk_grab_add( m_widget );
>
> wxEventLoop().Run();
>
> gtk_grab_remove( m_widget );
> ...
> }
>
> which looks just like re-entering the event-loop.


Ok I see how that would work. I think I'd prevent re-entry of an event handler as follows:

#include <atomic>

void Dialog_Main::OnClick_Button(wxCommandEvent &e)
{
static std::atomic_flag lock = ATOMIC_FLAG_INIT;

if ( lock.test_and_set() ) { e.Skip(); return; }

try
{
// The main body of the event handler goes here
}
catch(...)
{
lock.clear();
throw;
}
}

Scott Lurndal

unread,
Nov 30, 2022, 4:54:06 PM11/30/22
to
Generally GUI event handlers are all called in the context of
a single thread. Locking the handler should not
be required, however, protecting[*] any data which may be shared
by the event handler with other threads is mandatory.

[*] either explicitly with a synchronization primitive or implicitly
by using the appropriate atomic operations, if applicable.

Scott Lurndal

unread,
Nov 30, 2022, 4:55:21 PM11/30/22
to
As for pausing the event handler, the canonical way is to
enter the event handler, queue the event to another thread,
then return from the event handler.

Paavo Helde

unread,
Dec 1, 2022, 1:45:15 AM12/1/22
to
30.11.2022 23:16 Frederick Virchanza Gotham kirjutas:
> On Wednesday, November 30, 2022 at 4:49:01 PM UTC, Ralf Fassel wrote:
>>
>> int wxDialog::ShowModal()
>> {
>> ...
>> gtk_grab_add( m_widget );
>>
>> wxEventLoop().Run();
>>
>> gtk_grab_remove( m_widget );
>> ...
>> }
>>
>> which looks just like re-entering the event-loop.
>
>
> Ok I see how that would work. I think I'd prevent re-entry of an event handler as follows:

Ok, I see you are worrying about a scenario where the user clicks on the
same button twice. But this is not a common scenario. Typically, in a
modal dialog, another window (the dialog) is now active, and most events
go to that window, so it is not possible to click again on the same
button in the original window. That's what the word "modal" means.

Nowadays there appear also a lot of non-modal windows like all kind of
toolbars or other helpers. These do not block the functionality of the
original window, and these do not run their own event loops either, so
the question of preventing a re-entry in the handler does not arise.



Chris Vine

unread,
Dec 1, 2022, 7:42:32 PM12/1/22
to
I don't know about windows, but wxWidgets when running on unix-like
systems uses glib/GTK+ as the underlying toolkit, and for modal dialogs
of the kind you have described that generally means using recursive main
loop invocations (in other words, recursive blocking calls to
gtk_main()) which has the effect of multiplexing the single-threaded
event loop: see https://docs.gtk.org/glib/main-loop.html.

All GTK+ events (including I believe all GDK rendering) execute in the
main loop thread: GTK+ itself is single threaded (GTK+2 did make some
limited provision for it to run under multiple threads but this was
deprecated in GTK+3 and removed in GTK4). However glib/gio provides
mechanisms for launching tasks on worker threads, which when complete
will post an event to the main loop so that its continuation runs on
the main loop thread. Some asynchronous gio i/o operates in this way.

I presume (but do not know) that wxWidgets operates in a similar way
when using windows as it backend.
0 new messages