The other method of using a window-procedure-like dialog box is
to change the rules of the game.
Normally, the window procedure for a dialog box is
the DefDlgProc
function, which calls the dialog
procedure and then takes action if the dialog procedure indicated
that it desired the default action to take place.
The dialog procedure is subservient to DefDlgProc
,
providing advice when requested.
The kernel of the idea for this technique is to “turn the tables”.
Make DefDlgProc
be the one who gives advice and you
be the one that asks for the advice when you want it.
We do this by making the window procedure be our own function
which decides whether or not it wants the default action to happen.
If so, it calls DefDlgProc
to do it, after giving
the dialog a dummy dialog procedure that always says “Just do the
default”.
Here’s the flow diagram:
Message delivered -> WLWndProc -> your WLDlgProc decide what to do want to do the default action -> DefDlgProc -> dummy dialog procedure <- always returns FALSE DefDlgProc does default action <- returns result of default behavior you do other stuff (perhaps modify default behavior after it occurred) <- returns result <- returns result
To do this, we need to register a custom dialog class. You always wondered what that was for: Now you know.
BOOL InitApp(void) { WNDCLASS wc; wc.style = CS_DBLCLKS | CS_SAVEBITS | CS_BYTEALIGNWINDOW; wc.lpfnWndProc = WLWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = DLGWINDOWEXTRA + sizeof(WLDLGPROC); wc.hInstance = g_hinst; wc.hIcon = NULL; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = NULL; wc.lpszMenuName = NULL; wc.lpszClassName = TEXT("WLDialog"); if (!RegisterClass(&wc)) return FALSE; return TRUE; }
This creates a new window class called “WLDialog” which we will use as our custom dialog class. When you create a custom dialog class, you must set the cbWndExtra to DLGWINDOWEXTRA bytes, plus any additional bytes you wish to use for yourself. We need to store an extra WLDLGPROC, so we add that in.
To use our custom dialog procedure, the dialog template must use the “CLASS” keyword to specify the custom dialog class:
1 DIALOGEX DISCARDABLE 0, 0, 200,200 STYLE DS_SHELLFONT | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CLASS "WLDialog" CAPTION "sample" FONT 8, "MS Shell Dlg" BEGIN DEFPUSHBUTTON "&Bye", IDCANCEL, 7,4,50,14, WS_TABSTOP END
This is exactly the same as a regular dialog box template, except
that there is a “CLASS” entry which specifies that this dialog box
should use our new class. Paralleling the DialogBoxParam
function we have our own:
typedef LRESULT (CALLBACK* WLDLGPROC)(HWND, UINT, WPARAM, LPARAM); struct WLDIALOGINFO { WLDLGPROC wldp; LPARAM lParam; }; INT_PTR WLDialogBoxParam(HINSTANCE hinst, LPCTSTR pszTemplate, HWND hwndParent, WLDLGPROC wldp, LPARAM lParam) { WLDIALOGINFO wldi = { wldp, lParam }; return DialogBoxParam(hinst, pszTemplate, hwndParent, WLDlgProc, (LPARAM)&wldi); }
This packages up the WndProc-Like dialog procedure and its reference data so we can recover it in our window procedure:
LRESULT CALLBACK WLWndProc(HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam) { if (uiMsg == WM_INITDIALOG) { WLDIALOGINFO *pwldi = (WLDIALOGINFO*)lParam; SetWindowLongPtr(hdlg, DLGWINDOWEXTRA, (LONG_PTR)pwldi->wldp); lParam = pwldi->lParam; } WLDLGPROC wldp = (WLDLGPROC)GetWindowLongPtr(hdlg, DLGWINDOWEXTRA); if (wldp) { return wldp(hdlg, uiMsg, wParam, lParam); } else { return DefDlgProc(hdlg, uiMsg, wParam, lParam); } }
This is the window procedure for the custom dialog. When the
WM_INITDIALOG
message comes in, we recover the original
parameters to WLDialogBoxParam
.
The WLDLGPROC
we save in the extra bytes we reserved,
and the original LPARAM
becomes the lParam
that we pass to the WLDLGPROC
. Then for each message
that comes in, we pass the message and its parameters directly to
the WLDLGPROC
and return the value directly.
No DWLP_MSGRESULT
necessary.
The last piece of the puzzle is the dialog procedure we actually hand to the dialog manager:
INT_PTR CALLBACK WLDlgProc(HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam) { return FALSE; }
All it says is, “Do the default thing.”
Okay so let’s write yet another version of our sample program, using this new architecture:
LRESULT CALLBACK SampleWLDialogProc( HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam) { switch (uiMsg) { case WM_INITDIALOG: break; case WM_COMMAND: switch (GET_WM_COMMAND_ID(wParam, lParam)) { case IDCANCEL: MessageBox(hdlg, TEXT("Bye"), TEXT("Title"), MB_OK); EndDialog(hdlg, 1); break; } break; case WM_SETCURSOR: if (LOWORD(lParam) == HTCAPTION) { SetCursor(LoadCursor(NULL, IDC_SIZEALL)); return TRUE; } break; } return DefDlgProc(hdlg, uiMsg, wParam, lParam); } int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpCmdLine, int nShowCmd) { InitApp(); WLDialogBoxParam(hinst, MAKEINTRESOURCE(1), NULL, SampleWLDialogProc, 0); return 0; }
In this style of WndProc-Like dialog, we just write our
dialog procedure as if it were a window procedure,
calling DefDlgProc()
to perform default behavior.
And to get this new behavior, we use WLDialogBoxParam
instead of DialogBoxParam
So now I've developed two quite different ways you can write WndProc-Like dialog procedures. You may not like either one of them, so go ahead and write a third way if you prefer. But at least I hope you learned a little more about how Windows works.
0 comments