I am novice with Win32, and I have been pursuing a problem (if it can be called a problem at all) with Windows blocking your program's flow during the event when a user grabs the window title bar and moves it around the screen.
I have no legitimate reason to solve this problem, except that it bothers me. A few possibilities include removing the frame altogether, but it seems an inconvenient hack. Some games (single player) do not find this a problem at all. I have read however, that multiplayer games might experience problems when the program freezes as it expects continuous flow of information and can be overwhelmed after such a delay.
I have tried adding this to my WindowProc
switch (uMsg)
{
case WM_SYSCOMMAND:
if (wParam == SC_CLOSE)
PostQuitMessage(0);
return 0;
...
...
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
And this seems a quick hack, except that when I mousedown over the close icon I can pull the mouse away and let go without closing the program, and during that time, when the close icon is held down, the program once again is blocked.
Furthermore, I do not know how to manually include the code necessary to move the window when the user clicks the titlebar and drags the mouse. For starters I do not know which uMsg
's and wParam
's to handle.
My question is then, how do I disallow blocking during the case when the user clicks down the exit button (or minimize/maximize buttons) while still handling the case when the mouse is clicked and released over the button, and how do I allow the user to move/drag the window without it blocking the program (or what message is sent when the title bar is clicked without it being a button or menu)?
I am creating the window with WS_SYSMENU | WS_MINIMIZEBOX
.
I still want the program to respond to minimize, maximize, and exit commands.
If multi-threading can fix it, then that is interesting, but I wonder if I can get it to work on single-core processors. And I have read about hooks, but the MSDN page is still hard for me to interpret.
This phenomenon is not isolated to any particular message. It's a fundamental property of the Windows message loop: when one message is being processed, no other message can be processed at the same time. It's not exactly implemented this way, but you can think of it as a queue, where your app pulls the messages out of the queue to process in the reverse order that they are inserted.
Therefore, spending too long processing any message is going to suspend the processing of other messages, effectively freezing your application (because it cannot process any input). The only way to solve this problem is the obvious one: don't spend too long processing any one message.
Often that will mean delegating the processing to a background thread. You will still need to handle all messages on the main thread, and the background worker threads need to report back to the main method when they are finished. All interaction with the GUI needs to happen on a single thread, and that is almost always the main thread in your application (which is why it is often called the UI thread).
(And to answer an objection raised in your question, yes, you can operate multiple threads on single processor machines. You won't necessarily see any performance improvements, but it will make the UI more responsive. The logic here is that a thread can only do one thing at a time, but a processor can switch between threads extremely rapidly, effectively simulating doing more than one thing at a time.)
More useful information is available here in this MSDN article: Preventing Hangs in Windows Applications
Certain window operations on Windows are modal operations. Modal is a common word in computing that basically refers to locking the user into a particular mode where they cannot do anything else until they change (i.e. get out of that) modes. Whenever a modal operation is begun, a separate new message processing loop is spun up and message handling happens there (instead of your main message loop) for the duration of the mode. Common examples of these modal operations are drag-and-drop, window resizing, and message boxes.
Considering the example here of window resizing, your window receives a WM_NCLBUTTONDOWN
message, which you pass to DefWindowProc
for default processing. DefWindowProc
figures out that the user intends to start a move or resize operation, and entered a moving/sizing message loop located somewhere deep in the bowels of Windows' own code. Thus, your application's message loop is no longer running because you've entered into a new moving/sizing mode.
Windows runs this moving/sizing loop as long as the user is interactively moving/sizing the window. It does this so that it can intercept mouse messages and process them accordingly. When the moving/sizing operation completes (e.g., when the user releases the mouse button or presses the Esc key), control will return to your application code.
It is worth pointing out that you are notified that this mode change has occurred via the WM_ENTERSIZEMOVE
message; the corresponding WM_EXITSIZEMOVE
message indicates that the modal event-processing loop has exited. That allows you to create a timer that will continue to generate WM_TIMER
messages that your application can process. The actual details of how this is implemented are relatively unimportant, but the quick explanation is that DefWindowProc
continues to dispatch WM_TIMER
messages to your application inside of its own modal event processing loop. Use the SetTimer
function to create a timer in response to the WM_ENTERSIZEMOVE
message, and the KillTimer
function to destroy it in response to the WM_EXITSIZEMOVE
message.
I only point that out for completeness, though. In the majority of Windows apps that I've written, I've never needed to do that.
Aside from all of that, the behavior you describe in the question are unusual. If you create a new, blank Win32 application using the Visual Studio template, I doubt you will be able to replicate this behavior. Without seeing the rest of your window procedure, I can't tell if you're blocking on any messages (as discussed above), but the part I can see in the question is wrong. You must always call DefWindowProc
for messages that you do not explicitly process yourself.
In this case, you might be fooled into thinking that you're doing that, but WM_SYSCOMMAND
can have lots of different values for its wParam
. You only handle one of those, SC_CLOSE
. All of the rest of them just get ignored because you return 0
. That includes all of the window moving and resizing functionality (e.g. SC_MOVE
, SC_SIZE
, SC_MINIMIZE
, SC_RESTORE
, SC_MAXIMIZE
, etc. etc.).
And there's really no good reason to handle WM_SYSCOMMAND
yourself; just let DefWindowProc
take care of it for you. The only time you need to handle WM_SYSCOMMAND
is when you've added custom items to the window menu, and even then, you should pass every command that you do not recognize on to DefWindowProc
.
A basic window procedure should look like this:
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_CLOSE:
DestroyWindow(hWnd);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
It is also possible that your message loop is wrong. The idiomatic Win32 message loop (located near the bottom of your WinMain
function) looks like this:
BOOL ret;
MSG msg;
while ((ret = GetMessage(&msg, nullptr, 0, 0)) != 0)
{
if (ret != -1)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
// An error occurred! Handle it and bail out.
MessageBox(nullptr, L"Unexpected Error", nullptr, MB_OK | MB_ICONERROR);
return 1;
}
}
You do not need hooks of any kind. The MSDN documentation on these is very good, but you're right: they're complicated. Stay away until you have a better understanding of the Win32 programming model. It is a rare case indeed where you need the functionality provided by a hook.