The language in question is Nim, using the wNim library, but really the question is about the win32 api, since wNim is a very lightweight wrapper over Win32 gui.
I have a single threaded program. When the user clicks the button it launches a long-running calculation. On every nth cycle the calculation occasionally updates the window with InvalidateRect
, followed by UpdateWindow
. The WM_PAINT
handler draws some stuff on the screen right away. Sometimes I introduce a sleep
for a few ms thinking this will give the system WM_xxx
messages some time to catch up.
This normally works fine. I can watch the calculation progress graphically in the client area.
However when I try to move the window during this time, (with or without the sleep
) while the thing is doing its updating, not only does it not respond to the move (which is understandable if sleep(...)
is omitted), but the graphics from the onPaint
stop completely and the window turns that ghostly white color that Windows does when the program dies. Usually the debug output in the cli continues anyway. Often, but not always, the cli output stops and the Windows dialog comes up telling me the program crashed. At any time I'm able to hit ctrl-c in the cli and get back to normal. Can someone explain what particular sequence of events and mishandled messages is causing everything to die an ugly death?
Below is the pseudocode. Is there a proper way to allow a long-running foreground computation to update the client area while also responding to other user events, without multi-threading? Or is multithreading the best option here? I suspect my sleep(...)
call is the place to yield some cycles back to the messaging mechanism.
Pseudocode:
proc onButton():
while(stuffNotDone):
doExpensiveStuff()
InvalidateRect(false) # <-- Win32 call
UpdateWindow() # <-- Win32 call
Sleep(10) # <-- Win32 call (optional)
proc onPaint(event):
# Responds to WM_PAINT
var dc = PaintDC(...) # <-- leads to Win32 BeginPaint
dc.blit(...) # <-- leads to Win32 call
SendMessage(hwnd, WM_APP+3,0,0) # <-- Win32 call to clear data elsewhere
If you read SendMessage's documentation, you'll find, that it SendMessage won't return until event is finished being processed. In some cases it my lead to recursion if that would cause invalidation or very long chain of handler calls, if processing the message results in other events being triggered in same window. To allow delayed processing use PostMessage.
UpdateWindow bypasses queue. It essentially calls Window procedure directly with paint event. The same window procedure which called this handler. Sleep in handler happens after the paint complete and it stops event processing.
Foreground processing simply isn't done in Windows GUI. It's a synchronous loop of processing event for each available Window in one or more threads (in comparison, most platforms and multiple GUI frameworks allow that to be done only in main thread). Delayed asynchronous events are created using timers.
To process data without pausing Window procedure and event loop you must have a worker thread, a paradigm known since 1990s. IIRC Nim facilitates async processing. and easier creationof threads, I suppose you have to check there.