Holy cow, I wrote a book!
NPR reports on an amazing last-rock shot at the Canadian women's curling championship (includes link to CBC video). It's odd hearing Melissa Block discussing curling in decidedly non-curling terms (for the benefit of the decidedly non-curling US listenership). One of the advantages of living in the Seattle area is that you can get all the curling coverage you want from the CBC just over the border.
The National Post has a report loaded with curling jargon, for those who prefer their curling coverage filled with discussion of draw weight, stolen deuces, and in-turns.
Sometimes you need a quick and dirty window and you don't want to go through all the hassle of registering a class for it. For example, you might need a window to do a brief snippet of DDE, or you just need a window to own a message box.
To save yourself the trouble of registering a class for every single weenie thing you might need a window for, you can get lazy and register a single "scratch window" class and simply subclass it on an as-needed basis.
ATOM RegisterScratchWindowClass(void) { WNDCLASS wc = { 0, // style DefWindowProc, // lpfnWndProc 0, // cbClsExtra 0, // cbWndExtra g_hinst, // this file's HINSTANCE NULL, // hIcon LoadCursor(NULL, IDC_ARROW), // hCursor (HBRUSH)(COLOR_BTNFACE+1), // hbrBackground NULL, // lpszMenuName TEXT("Scratch"), // lpszClassName }; return RegisterClass(&wc); } HWND CreateScratchWindow(HWND hwndParent, WNDPROC wp) { HWND hwnd; hwnd = CreateWindow(TEXT("Scratch"), NULL, hwndParent ? WS_CHILD : WS_OVERLAPPED, 0, 0, 0, 0, hwndParent, NULL, NULL, NULL); if (hwnd) { SubclassWindow(hwnd, wp); } return hwnd; }
Now if you need a quick one-off window, you can just create a scratch window instead of creating a custom window class just to handle that specific task.
We'll see the scratch window in action soon.
Post suggestions for future topics here instead of posting off-topic comments. Note that the suggestion box is emptied and read periodically so don't be surprised if your suggestion vanishes. (Note also that I am under no obligation to accept any suggestion.)
Topics I are more inclined to cover:
Topics I am not inclined to cover:
(Due to the way the blog server is set up, a new suggestion box gets set up every 30 days, assuming I don't forget to create a new one. If I forget, you can send me a reminder via the Contact page. You can also peek at the previous suggestion box.)
As we noted at the end of part 3, now that you know the conventions surrounding the WM_QUIT message you can put them to your advantage.
WM_QUIT
The more robust you want the TimedMessageBox function to be, the more work you need to do. Here's the cheap version, based on the sample in the Knowledge Base, but with some additional bug fixes.
TimedMessageBox
static BOOL s_fTimedOut; static HWND s_hwndMBOwnerEnable; void CALLBACK CheapMsgBoxTooLateProc(HWND hWnd, UINT uiMsg, UINT_PTR idEvent, DWORD dwTime) { s_fTimedOut = TRUE; if (s_hwndMBOwnerEnable) EnableWindow(s_hwndMBOwnerEnable, TRUE); PostQuitMessage(42); // value not important } // Warning! Not thread-safe! See discussion. int CheapTimedMessageBox(HWND hwndOwner, LPCTSTR ptszText, LPCTSTR ptszCaption, UINT uType, DWORD dwTimeout) { s_fTimedOut = FALSE; s_hwndMBOwnerEnable = NULL; if (hwndOwner && IsWindowEnabled(hwndOwner)) { s_hwndMBOwnerEnable = hwndOwner; } UINT idTimer = SetTimer(NULL, 0, dwTimeout, CheapMsgBoxTooLateProc); int iResult = MessageBox(hwndOwner, ptszText, ptszCaption, uType); if (idTimer) KillTimer(NULL, idTimer); if (s_fTimedOut) { // We timed out MSG msg; // Eat the fake WM_QUIT message we generated PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE); iResult = -1; } return iResult; }
This CheapTimedMessageBox function acts just like the MessageBox function, except that if the user doesn't respond within dwTimeout milliseconds, we return -1. The limitation is that only one timed message box can be active at a time. If your program is single-threaded, this is not a serious limitation, but if your program is multi-threaded, this will be a problem.
CheapTimedMessageBox
MessageBox
dwTimeout
Do you see how it works?
The global static variable s_fTimedOut tells us whether we generated a fake WM_QUIT message as a result of a timeout. When the MessageBox function returns, and we indeed timed out, we use the PeekMessage function to remove the fake WM_QUIT message from the queue before returning.
s_fTimedOut
PeekMessage
Note that we remove the WM_QUIT message only if we were the ones who generated it. In this way, WM_QUIT messages generated by other parts of the program remain in the queue for processing by the main message loop.
Note also that when we decide that the timeout has occurred, we re-enable the original owner window before we cause the message box to bail out of its message loop by posting a quit message. Those are the rules for the correct order for disabling and enabling windows.
Note also that we used a thread timer rather than a window timer. That's because we don't own the window being passed in and therefore don't know what timer IDs are safe to use. Any timer ID we pick might happen to collide with a timer ID being used by that window, resulting in erratic behavior.
Recall that when you pass NULL as the hwnd parameter to the SetTimer function and also pass zero as the nIDEvent parameter, then the SetTimer function creates a brand new timer, assigns it a unique ID, and returns the ID. Most people, when they read that part of the specification for SetTimer, scratch their heads and ask themselves, "Why would anybody want to use this?"
NULL
hwnd
SetTimer
nIDEvent
Well, this is one scenario where this is exactly what you want.
Next comes the job of making the function a tad more robust. But before we do that, we'll need two quick sidebars.