We’ve spent quite a bit of time over the past year learning about dialog templates and the dialog manager. Now we’re going to put the pieces together to do something interesting: Building a dialog template on the fly.
What we’re going to write is an extremely lame version of
the MessageBox
function.
Why bother writing a bad version of something that Windows already does?
Because you can use it as a starting point for further enhancements.
For example, once you learn how to generate a template dynamically,
you can dynamically add buttons beyond the boring “OK” button,
or you can add additional controls like a “Repeat this answer for all
future occurrences of this dialog” checkbox or maybe insert
an animation control.
I’m going to start with a highly inefficient dialog template class. This is not production-quality, but it’s good enough for didactic purposes.
#include <vector> class DialogTemplate { public: LPCDLGTEMPLATE Template() { return (LPCDLGTEMPLATE)&v[0]; } void AlignToDword() { if (v.size() % 4) Write(NULL, 4 - (v.size() % 4)); } void Write(LPCVOID pvWrite, DWORD cbWrite) { v.insert(v.end(), cbWrite, 0); if (pvWrite) CopyMemory(&v[v.size() - cbWrite], pvWrite, cbWrite); } template<typename T> void Write(T t) { Write(&t, sizeof(T)); } void WriteString(LPCWSTR psz) { Write(psz, (lstrlenW(psz) + 1) * sizeof(WCHAR)); } private: vector<BYTE> v; };
I didn’t spend much time making this class look pretty because
it’s not the focus of this article. The DialogTemplate
class babysits a vector
of bytes
to which you can Write
data.
There is also a little AlignToDword
method that
pads the buffer to the next DWORD
boundary.
This’ll come in handy, too.
Our message box will need a dialog procedure
which ends the dialog when the IDCANCEL
button is pressed.
If we had made any enhancements to the dialog template, we would handle
them here as well.
INT_PTR CALLBACK DlgProc(HWND hwnd, UINT wm, WPARAM wParam, LPARAM lParam) { switch (wm) { case WM_INITDIALOG: return TRUE; case WM_COMMAND: if (GET_WM_COMMAND_ID(wParam, lParam) == IDCANCEL) EndDialog(hwnd, 0); break; } return FALSE; }
Finally, we build the template. This is not hard, just tedious.
Out of sheer laziness, we make the message box a fixed size.
If this were for a real program, we would have measured the text
(using ncm.lfCaptionFont
and ncm.lfMessageFont
) to determine the
best size for the message box.
BOOL FakeMessageBox(HWND hwnd, LPCWSTR pszMessage, LPCWSTR pszTitle) { BOOL fSuccess = FALSE; HDC hdc = GetDC(NULL); if (hdc) { NONCLIENTMETRICSW ncm = { sizeof(ncm) }; if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, 0, &ncm, 0)) { DialogTemplate tmp; // Write out the extended dialog template header tmp.Write<WORD>(1); // dialog version tmp.Write<WORD>(0xFFFF); // extended dialog template tmp.Write<DWORD>(0); // help ID tmp.Write<DWORD>(0); // extended style tmp.Write<DWORD>(WS_CAPTION | WS_SYSMENU | DS_SETFONT | DS_MODALFRAME); tmp.Write<WORD>(2); // number of controls tmp.Write<WORD>(32); // X tmp.Write<WORD>(32); // Y tmp.Write<WORD>(200); // width tmp.Write<WORD>(80); // height tmp.WriteString(L""); // no menu tmp.WriteString(L""); // default dialog class tmp.WriteString(pszTitle); // title // Next comes the font description. // See text for discussion of fancy formula. if (ncm.lfMessageFont.lfHeight < 0) { ncm.lfMessageFont.lfHeight = -MulDiv(ncm.lfMessageFont.lfHeight, 72, GetDeviceCaps(hdc, LOGPIXELSY)); } tmp.Write<WORD>((WORD)ncm.lfMessageFont.lfHeight); // point tmp.Write<WORD>((WORD)ncm.lfMessageFont.lfWeight); // weight tmp.Write<BYTE>(ncm.lfMessageFont.lfItalic); // Italic tmp.Write<BYTE>(ncm.lfMessageFont.lfCharSet); // CharSet tmp.WriteString(ncm.lfMessageFont.lfFaceName); // Then come the two controls. First is the static text. tmp.AlignToDword(); tmp.Write<DWORD>(0); // help id tmp.Write<DWORD>(0); // window extended style tmp.Write<DWORD>(WS_CHILD | WS_VISIBLE); // style tmp.Write<WORD>(7); // x tmp.Write<WORD>(7); // y tmp.Write<WORD>(200-14); // width tmp.Write<WORD>(80-7-14-7); // height tmp.Write<DWORD>(-1); // control ID tmp.Write<DWORD>(0x0082FFFF); // static tmp.WriteString(pszMessage); // text tmp.Write<WORD>(0); // no extra data // Second control is the OK button. tmp.AlignToDword(); tmp.Write<DWORD>(0); // help id tmp.Write<DWORD>(0); // window extended style tmp.Write<DWORD>(WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP | BS_DEFPUSHBUTTON); // style tmp.Write<WORD>(75); // x tmp.Write<WORD>(80-7-14); // y tmp.Write<WORD>(50); // width tmp.Write<WORD>(14); // height tmp.Write<DWORD>(IDCANCEL); // control ID tmp.Write<DWORD>(0x0080FFFF); // static tmp.WriteString(L"OK"); // text tmp.Write<WORD>(0); // no extra data // Template is ready - go display it. fSuccess = DialogBoxIndirect(g_hinst, tmp.Template(), hwnd, DlgProc) >= 0; } ReleaseDC(NULL, hdc); // fixed 11 May } return fSuccess; }
The fancy formula for determining the font point size is not that fancy after all. The dialog manager converts the font height from point to pixels via the standard formula:
fontHeight = -MulDiv(pointSize, GetDeviceCaps(hdc, LOGPIXELSY), 72);
Therefore, to get the original pixel value back,
we need to solve this formula for pointSize
so that when it is sent through the formula again, we get the
original value back.
The template itself follows the format we discussed earlier, no surprises.
One subtlety is that the control identifier for our OK button
is IDCANCEL
instead of the IDOK
you might
have expected. That’s because this message box has only one button,
so we want to
let the user hit the ESC key to dismiss it.
Now all that’s left to do is take this function for a little spin.
void OnChar(HWND hwnd, TCHAR ch, int cRepeat) { if (ch == TEXT(' ')) { FakeMessageBox(hwnd, L"This is the text of a dynamically-generated dialog template. " L"If Raymond had more time, this dialog would have looked prettier.", L"Title of message box"); } } // add to window procedure HANDLE_MSG(hwnd, WM_CHAR, OnChar);
Fire it up, hit the space bar, and observe the faux message box.
Okay, so it’s not very exciting visually, but that wasn’t the point. The point is that you now know how to build a dialog template at run-time.
0 comments