Bob is a professional C++ programmer with a PhD in mathematics and experience in business and manufacturing applications. He can be reached at 20 E. Scott, Chicago, IL 60610.
Recently, I had to write a Windows front end for a collection of database tables, with records consisting of varying numbers of strings of varying lengths. A dialog box was to be used to edit selected records of these tables. Because the record specification was not constant, and in fact could only be determined while the program was running, the dialog box had to be dynamic; that is, it had to create itself at run time, rather than be created from a resource file.
My solution starts with a published example of programming a dynamic dialog box in C, and consists of two C++ classes: DialogTemplate, a class for a Windows dialog-box template, and StringsBox, a class for a dialog box used to edit strings, as required by my application. The result demonstrates how even a complicated Windows object, once incorporated into a C++ class, can be easily used by an application program. It also demonstrates a new technique for associating an instance of a predefined Windows class--in this case a dialog box--with a C++ object.
The Dialog Template Class
To create a dialog box, it is first necessary to allocate a block of memory called a "dialog template," described in the Microsoft Windows Programmer's Reference (Microsoft Press, 1990) on pages 7-31 through 7-35, and to fill it with information defining the dialog box and its controls. Once the dialog template is prepared, a handle to it is passed to DialogBoxIndirect() or DialogBoxIndirectParam(), which realizes the information in the dialog template as a modal dialog box. In most applications, the two steps of preparing the dialog template and realizing the dialog box occur together in a single call to DialogBox(), which creates a dialog box specified as a resource. To create a dialog box dynamically, the application must follow these two steps explicitly; Jeffrey Richter shows how to do this in a C program in his Windows 3: A Developer's Guide (M&T Books, 1991) on pages 159-170.
The DialogTemplate class in Listing One(page 122) is a straightforward adaptation of Richter's technique, although the added power of C++ makes the process somewhat simpler. For example, Richter temporarily stores the size of the dialog template inside the template itself; with C++, it is more natural to define a field nbytes inside the DialogTemplate class instead.
The dialog template is built in steps, with one call to the class constructor, one call to SpecifyDialogBox(), and, optionally, one call to SpecifyFont(); omitting this last step causes the dialog box to use the system font. AddItem() is then called once for each control to be added. Finally, the dialog box is realized with a call to DialogTemplate:: DialogBoxIndirect() or DialogTemplate:: DialogBoxIndirectParam().
A pointer to a DialogTemplate object is included in the class definition of StringsBox, discussed next.
The StringsBox Class
Listing Two(page 122) contains StringsBox, a class used by the example program in Listing Three (page 124) to create the dialog box. To create the dialog box, an application first fills in an array of StringSpec structures to define the title and maximum length of each string. It then passes this array and the number of its elements to SetUp(), which creates a dialog template for the dialog box. Calling GetStrings() displays the dialog box.
StringsBox stores the strings for its edit windows contiguously in a block of memory; the public field hStrings is a handle to this block, which the application uses to initialize or access the strings. To display the dialog box with edit windows initialized, the application locks hStrings, fills the block, unlocks hStrings, and calls GetStrings() with parameter InitializedFlag set to True; to display the box with edit windows initially blank, the application calls GetStrings() with InitializedFlag set to False. GetStrings() returns True if the user selects OK and False if the user selects "Cancel;" on the return of True, the application can access the user's string values by locking hStrings, reading the block, and unlocking hStrings again. (Note that the hStrings block is allocated by StringsBox rather than by the application, and that its contents are only changed when the user selects OK or when altered by the application; successive calls to GetStrings() with InitializedFlag set to True will continue to display the dialog box with edit windows as last set by the user.)
Two Friend Functions and a Linked List
A universal problem in writing Windows applications in C++ is that, since Windows is written in C, a Windows procedure can be at best a friend or static member of a C++ class, rather than a true member. This means that, during run time, an instance of a Windows procedure does not automatically possess a this pointer to its corresponding C++ object. In the case of StringsBox, the dialog procedure StringsBox_DlgProc needs a pointer to the proper StringsBox object in order to access the strings in its hStrings block.
For Windows classes defined from scratch, the standard solution is to allocate enough extra bytes in the Window class to hold the this pointer. However, this technique will not work for predefined Windows classes, such as a dialog box.
StringsBox uses a different technique of a linked list of pointers to StringsBox objects with its first element embedded in the class as a static member; being static means that there will be one copy of this element, shared by all instances of the StringsBox class. Each object contains two additional, nonstatic pointers to the objects preceding and succeeding it in the list. The class constructor and destructor manage the list by resetting these pointers appropriately.
The lookup field for the linked list, the handle hDlg, can only be determined once the dialog box is displayed. To do this, DialogTemplate::DialogBox-IndirectParam() is sent the StringsBox object's this pointer as its last parameter, which allows the dialog-box procedure StringsBox_DlgProc() to recover it (but only during WM_INITDIALOG). StringsBox_DlgProc() then sets the hDlg field of the object referenced by this pointer. The lookup function GetStringsBox() is provided to let StringsBox_DlgProc() find the this pointer on subsequent calls; GetStringsBox() is also a friend rather than a member function, since it is called from a nonmember function.
Example Program
Listing Three contains dboxdemo.cpp, a simple program that demonstrates the StringsBox class. This program declares a global StringsBox object EditBox, which it displays when the user double-clicks on the main window. StringsBox::GetStrings() is called with the InitializedFlag set to True, so the contents of the dialog box's edit controls are preserved between calls. These strings are also displayed in the main window by the WM_PAINT command to demonstrate how the application accesses the contents of the StringsBox's hStrings block.
Conclusion
The two classes and example presented here demonstrate how C++ can make complex objects easily accessible from a Windows application; and the technique of embedding a linked list in the StringsBox class shows how to make the predefined Windows class for a dialog box available to a C++ object.
All of the code can be made more object oriented. The dboxdemo program in particular looks more like a C program than a C++ program; this is mainly to show how a library of C++ classes can add power even to a traditional-style Windows program.
It would be natural to expand the StringsBox class into a collection of many customized or dynamic dialogbox classes. In this case, it would be appropriate to embed the linked-list members in a base class, and make StringsBox and the other dialog-box classes into derived classes of this base class. However, it is probably not a good idea to include the dialog template in the base class. Leaving DialogTemplate as a separate class and having StringsBox contain a pointer to a DialogTemplate object allows a constructor of the form StringsBox(const StringsBox &x), which would let several StringsBox objects use the same dialog template.
DialogTemplate and StringsBox were written with the future possibility of a DLL in mind, so all pointers passed to public member functions are far. The second parameter of StringsBox::Setup() is a far pointer to a structure containing a near pointer as one of its fields; the near pointer is converted to a far pointer by the MakeFarPointer macro, defined at the top ofListing Two.
_DYNAMIC DIALOG BOXES IN C++_ by Robert Sardis[LISTING ONE]
<a name="025d_000a"> /***** DialogTemplate class ******/ #include <windows.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <mem.h> int ErrorMessage(LPSTR); class DialogTemplate { public: DialogTemplate(void); ~DialogTemplate(void); void SpecifyDlgBox(long Style, int x, int y, int width, int height, LPSTR MenuName, LPSTR ClassName, LPSTR CaptionText); void SpecifyFont(short int PointSize, LPSTR TypeFace); void AddItem(int x, int y, int width, int height, int ID, long Style, LPSTR Class, LPSTR Text, BYTE DataBytes, LPBYTE Data); int DialogBoxIndirect(HANDLE hInstance, HWND hWndParent, FARPROC lpDialogProc); int DialogBoxIndirectParam(HANDLE hInstance, HWND hWndParent, FARPROC lpDialogProc, DWORD dwInitParam); private: HANDLE hMem; int nBytes; int nItems; }; typedef struct { long Style; BYTE nItems; int x; int y; int width; int height; // char MenuName[]; // char ClassName[]; // char CaptionText[]; } DLGTEMPLATE, FAR *LPDLGTEMPLATE; typedef struct { short int PointSize; // char TypeFace[]; } FONTINFO, FAR *LPFONTINFO; typedef struct { int x; int y; int width; int height; int ID; long Style; // char Class[]; // char Text[]; // BYTE Info; // PTR Data; } DLGITEMTEMPLATE, FAR *LPDLGITEMTEMPLATE; DialogTemplate::DialogTemplate(void) { nBytes = 0; nItems = 0; hMem = 0; } DialogTemplate::~DialogTemplate(void) { GlobalFree(hMem); } void DialogTemplate::SpecifyDlgBox(long Style, int x, int y, int width, int height, LPSTR MenuName, LPSTR ClassName, LPSTR CaptionText) { LPDLGTEMPLATE lpDlg; LPSTR lpText; int MenuNameBytes = lstrlen(MenuName) + 1; // sizes of strings, int ClassNameBytes = lstrlen(ClassName) + 1; // including null int CaptionTextBytes = lstrlen(CaptionText) + 1; // terminator nBytes = sizeof(DLGTEMPLATE) + MenuNameBytes + ClassNameBytes + CaptionTextBytes; nItems = 0; GlobalFree(hMem); hMem = GlobalAlloc(GHND, nBytes); if (hMem == NULL) { ErrorMessage("Memory allocation error creating dialog template"); return; } lpDlg = (LPDLGTEMPLATE) GlobalLock(hMem); // add the "fixed size" lpDlg->Style = Style; // fields of the template lpDlg->nItems = 0; lpDlg->x = x; lpDlg->y = y; lpDlg->width = width; lpDlg->height = height; lpText = ((LPSTR) lpDlg) + sizeof (DLGTEMPLATE); // append the three _fmemcpy(lpText, MenuName, MenuNameBytes); // null-terminated text lpText += MenuNameBytes; // strings _fmemcpy(lpText, ClassName, ClassNameBytes); lpText += ClassNameBytes; _fmemcpy(lpText, CaptionText, CaptionTextBytes); GlobalUnlock(hMem); } void DialogTemplate::SpecifyFont(short int PointSize, LPSTR TypeFace) { LPDLGTEMPLATE lpDlg; LPFONTINFO lpFont; LPSTR lpText; int OldnBytes = nBytes; int TypeFaceBytes = lstrlen(TypeFace) + 1; nBytes += sizeof(FONTINFO) + TypeFaceBytes; hMem = GlobalReAlloc(hMem, nBytes, GHND); if (hMem == NULL) { ErrorMessage("Memory allocation error adding dialog font"); return; } // add DS_SETFONT to style to indicate font template is being added lpDlg = (LPDLGTEMPLATE) GlobalLock(hMem); lpDlg->Style |= DS_SETFONT; // append font template to dialog template lpFont = (LPFONTINFO) (((LPSTR) lpDlg) + OldnBytes); lpFont->PointSize = PointSize; // append fixed-size field of font info lpText = ((LPSTR) lpFont) + sizeof(LPFONTINFO); // append null-termi- _fmemcpy(lpText, TypeFace, TypeFaceBytes); // nated text string GlobalUnlock(hMem); } void DialogTemplate::AddItem(int x, int y, int width, int height, int ID, long Style, LPSTR Class, LPSTR Text, BYTE DataBytes, LPBYTE Data) { LPDLGTEMPLATE lpDlg; LPDLGITEMTEMPLATE lpItem; LPSTR lpText; int OldnBytes = nBytes; int ClassBytes = lstrlen(Class) + 1; int TextBytes = lstrlen(Text) + 1; nBytes += sizeof(DLGITEMTEMPLATE) + ClassBytes + TextBytes + sizeof(BYTE) + DataBytes; hMem = GlobalReAlloc(hMem, nBytes, GHND); if (hMem == NULL) { ErrorMessage("Memory allocation error adding dialog item"); return; } nItems++; lpDlg = (LPDLGTEMPLATE) GlobalLock(hMem); lpDlg->nItems = nItems; // update # items // append item template to template block lpItem = (LPDLGITEMTEMPLATE) (((LPSTR) lpDlg) + OldnBytes); lpItem->x = x; // append fixed-size lpItem->y = y; // fields lpItem->width = width; lpItem->height = height; lpItem->ID = ID; lpItem->Style = Style | WS_CHILD | WS_VISIBLE; lpText = ((LPSTR) lpItem )+ sizeof(DLGITEMTEMPLATE); // append variable _fmemcpy(lpText, Class, ClassBytes); // length portion: lpText += ClassBytes; // two strings, _fmemcpy(lpText, Text, TextBytes); // one byte, and a lpText += TextBytes; // data block. *lpText = DataBytes; lpText += sizeof(BYTE); _fmemcpy(lpText, Data, DataBytes); GlobalUnlock(hMem); } int DialogTemplate::DialogBoxIndirect(HANDLE hInstance, HWND hWndParent, FARPROC lpDialogProc) { return ::DialogBoxIndirect(hInstance, hMem, hWndParent, lpDialogProc); } int DialogTemplate::DialogBoxIndirectParam(HANDLE hInstance, HWND hWndParent, FARPROC lpDialogProc, DWORD dwInitParam) { return ::DialogBoxIndirectParam(hInstance, hMem, hWndParent, (FARPROC) lpDialogProc, dwInitParam); } <a name="025d_000b"> <a name="025d_000c">[LISTING TWO]
<a name="025d_000c"> /****** StringsBox class ******/ #include <windows.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <mem.h> #include <math.h> int ErrorMessage(LPSTR); #define max(A,B) ((A) > (B) ? A : B) #define MakeFarPointer(N,F) \ (sizeof(PSTR)==sizeof(LPSTR) ? (LONG)N : MAKELONG((WORD)N,HIWORD((LONG)F))) // macro to convert near pointer "N" to far -- "F" is a // reference far pointer, in the same segment as N. Used for DLLs struct StringSpec { char *Title; int MaxLength; }; class StringsBox { public: StringsBox(void); ~StringsBox(void); void SetUp(LPSTR Caption, struct StringSpec far *Items, int NumItems); int GetStrings(HANDLE hInstance, HWND hwnd, BOOL InitializedFlag); friend BOOL FAR PASCAL _export StringsBox_DlgProc(HWND hDlg, WORD msg, WORD wParam, LONG lParam); HANDLE hStrings; private: DialogTemplate *DT; int nItems; int *ItemLengths; static StringsBox *FirstBox; // linked list parameters so the StringsBox *PrevBox; // function DlgProc() can StringsBox *NextBox; // determine the StringsBox HWND hDlg; // corresponding to the handle hDlg friend StringsBox *GetStringsBox(HWND hDlg); BOOL InitFlag; }; StringsBox *StringsBox::FirstBox = NULL; // initialize linked list StringsBox::StringsBox(void) { DT = new DialogTemplate; ItemLengths = NULL; nItems = 0; hStrings = 0; hDlg = 0; if (FirstBox == NULL) // insert new object in the linked list-- { // either at the beginning, if the list FirstBox = this; // is empty ... PrevBox = NULL; NextBox = NULL; } else // or else at the end, after the first { // StringsBox whose NextBox pointer is StringsBox *pBox; // NULL for (pBox = FirstBox; pBox->NextBox != NULL; pBox = pBox->NextBox); PrevBox = pBox; pBox->NextBox = this; NextBox = NULL; } } StringsBox::~StringsBox(void) { delete DT; GlobalDiscard(hStrings); delete ItemLengths; if (this == FirstBox) // take object out of linked list FirstBox = NextBox; if (PrevBox != NULL) PrevBox->NextBox = NextBox; if (NextBox != NULL) NextBox->PrevBox = PrevBox; } #define LINE_HEIGHT 2*cy #define LINE_TEXT_HEIGHT 1.5*cy #define HSPACE 2*cx #define IDD_ITEM(A) A+101 void StringsBox::SetUp(LPSTR Caption,struct StringSpec far *Items,int NumItems) { int i, y; LPSTR lpStringsBlock; int MaxTitleWidth = 0; int MaxEditWidth = 0; int hStringSize = 0; int cx = 4; // character average width and height, int cy = 8; // in dialog box units // get max dimensions of titles and edit windows -- hdc needed for call to // GetTextExtent(), tm needed to convert return value of GetTextExtent() // from logical units to dialog box units HDC hdc = CreateDC("DISPLAY", NULL, NULL, NULL); TEXTMETRIC tm; HFONT hFont = GetStockObject(SYSTEM_FONT); SelectObject(hdc, hFont); GetTextMetrics(hdc, &tm); for (i = 0; i < NumItems; i++) { MaxTitleWidth = max(MaxTitleWidth, LOWORD(GetTextExtent(hdc, (LPSTR)MakeFarPointer(Items[i].Title, Items), lstrlen((LPSTR)MakeFarPointer(Items[i].Title, Items))))); MaxEditWidth = max(MaxEditWidth, cx*(Items[i].MaxLength+1)); } // convert MaxTitleWidth from logical units to dialog box units: // multiply by ratio of (ave char width in dialog box units) to // (ave char width in logical units), and round up to next integer MaxTitleWidth = ceil(((double)MaxTitleWidth * (double)cx) / (double)tm.tmAveCharWidth); DeleteDC(hdc); // calculate locations of controls int ItemTitleX = HSPACE; int ItemEditX = ItemTitleX + MaxTitleWidth + HSPACE; int FirstItemY = LINE_HEIGHT; int ButtonWidth = 10*cx; int ButtonY = FirstItemY + NumItems*LINE_HEIGHT + LINE_HEIGHT; int BoxX = 1; int BoxY = 1; int BoxWidth = max(MaxTitleWidth + MaxEditWidth + HSPACE + 2*HSPACE, 2*ButtonWidth + 4*HSPACE); int BoxHeight = ButtonY + LINE_HEIGHT; int CenterX = BoxWidth / 2; // set up dialog template DT->SpecifyDlgBox(WS_CAPTION | WS_SYSMENU | WS_VISIBLE | WS_POPUP, BoxX, BoxY, BoxWidth, BoxHeight, "", "", Caption); // DT->SpecifyFont() not called -- use default font for (i = 0; i < NumItems; i++) { y = FirstItemY + i*LINE_HEIGHT; // Item title DT->AddItem(ItemTitleX, y, MaxTitleWidth, LINE_TEXT_HEIGHT, -1, SS_LEFT | WS_GROUP, "STATIC", (LPSTR) MakeFarPointer(Items[i].Title, Items), 0, NULL); // Item edit DT->AddItem(ItemEditX, y, cx*(Items[i].MaxLength+1), LINE_TEXT_HEIGHT, IDD_ITEM(i), ES_LEFT | ES_AUTOHSCROLL | WS_BORDER | WS_TABSTOP, "EDIT", "", 0, NULL); } // 'OK' button DT->AddItem(CenterX - ButtonWidth - HSPACE, ButtonY, ButtonWidth, LINE_TEXT_HEIGHT, IDOK, BS_DEFPUSHBUTTON | WS_TABSTOP | WS_GROUP, "BUTTON", "OK", 0, NULL); // 'CANCEL' button DT->AddItem(CenterX + HSPACE, ButtonY, ButtonWidth, LINE_TEXT_HEIGHT, IDCANCEL, BS_PUSHBUTTON | WS_TABSTOP | WS_GROUP, "BUTTON", "Cancel", 0, NULL); // set class parameters nItems = NumItems; if (ItemLengths != NULL) delete ItemLengths; ItemLengths = new int[nItems]; for (i = 0; i < nItems; i++) hStringSize += (ItemLengths[i] = Items[i].MaxLength); // allocate hStrings block and initialize it to nulls if (hStrings == 0) hStrings = GlobalAlloc(GHND, hStringSize); else hStrings = GlobalReAlloc(hStrings, hStringSize, GHND); lpStringsBlock = GlobalLock(hStrings); _fmemset(lpStringsBlock, '\0', hStringSize); GlobalUnlock(hStrings); } int StringsBox::GetStrings(HANDLE hInstance, HWND hwnd, BOOL InitializedFlag) { FARPROC lpDialogProc; int RetVal; InitFlag = InitializedFlag; lpDialogProc = MakeProcInstance((FARPROC) StringsBox_DlgProc, hInstance); // pass "this" pointer so dialog box procedure // can recover it during WM_INITDIALOG RetVal = DT->DialogBoxIndirectParam(hInstance, hwnd, (FARPROC)lpDialogProc, (DWORD) this); FreeProcInstance(lpDialogProc); return RetVal; } BOOL FAR PASCAL _export StringsBox_DlgProc(HWND hDlg, WORD msg, WORD wParam, LONG lParam) { int i; LPSTR lpText; // find "this" pointer of corresponding StringsBox object -- // on WM_INITDIALOG, it is passed as lParam; on subsequent // calls, use GetStringsBox() to look it up in the linked list StringsBox *pBox = (msg == WM_INITDIALOG ? (StringsBox *) lParam : GetStringsBox(hDlg)); switch(msg) { case WM_INITDIALOG: pBox->hDlg = hDlg; // insert "this" pointer in linked list -- set // Box->hDlg so GetStringsBox() can find it on // subsequent calls from this dialog procedure if (pBox->InitFlag) { lpText = GlobalLock(pBox->hStrings); for (i = 0; i < pBox->nItems; i++) { SetDlgItemText(hDlg, IDD_ITEM(i), lpText); lpText += pBox->ItemLengths[i]; } GlobalUnlock(pBox->hStrings); } return TRUE; case WM_COMMAND: switch(wParam) { case IDOK: lpText = GlobalLock(pBox->hStrings); for (i = 0; i < pBox->nItems; i++) { GetDlgItemText(hDlg, IDD_ITEM(i), lpText, pBox->ItemLengths[i]); lpText += pBox->ItemLengths[i]; } GlobalUnlock(pBox->hStrings); pBox->hDlg = 0; // set pBox->hDlg back to // an invalid value EndDialog(hDlg, TRUE); return TRUE; case IDCANCEL: pBox->hDlg = 0; EndDialog(hDlg, FALSE); return TRUE; } break; } return FALSE; } StringsBox * GetStringsBox(HWND hDlg) { StringsBox *pBox; for (pBox = StringsBox::FirstBox; pBox != NULL; pBox = pBox->NextBox) { if (pBox->hDlg == hDlg) return pBox; } return NULL; } <a name="025d_000d"> <a name="025d_000e">[LISTING THREE]
<a name="025d_000e"> /***** dboxdemo.cpp -- C++ dynamic dialog box example *****/ #include <windows.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include "wbdialog.hpp" // header file containing DialogTemplate and // StringsBox class definitions StringsBox EditBox; struct StringSpec StringFieldSpec[] = // field spec for EditBox { "Name", 20, "Address", 30, "Telephone", 15, }; int NumStrings = sizeof(StringFieldSpec) / sizeof(struct StringSpec); struct { HANDLE hInstance; } InsGlobs; struct { HWND hwnd; short cxChar; short cyChar; } WndGlobs; long FAR PASCAL _export WndProc(HWND, WORD, WORD, LONG); int ErrorMessage(char *msg); char szAppName[] = "dboxdemo"; int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { HWND hwnd; MSG msg; WNDCLASS wndclass; if(!hPrevInstance) { wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground = GetStockObject(WHITE_BRUSH); wndclass.lpszMenuName = szAppName; wndclass.lpszClassName = szAppName; RegisterClass(&wndclass); } hwnd = CreateWindow(szAppName, "C++ Dynamic Dialog Box Demo", WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); WndGlobs.hwnd = hwnd; InsGlobs.hInstance = hInstance; ShowWindow(WndGlobs.hwnd, nCmdShow); UpdateWindow(WndGlobs.hwnd); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } long FAR PASCAL _export WndProc(HWND hwnd, WORD message, WORD wParam, LONG lParam) { HDC hdc; TEXTMETRIC tm; PAINTSTRUCT ps; LPSTR lpString; int i; switch (message) { case WM_CREATE: hdc = GetDC(hwnd); GetTextMetrics(hdc, &tm); WndGlobs.cxChar = tm.tmAveCharWidth; WndGlobs.cyChar = tm.tmHeight + tm.tmExternalLeading; ReleaseDC(hwnd, hdc); EditBox.SetUp("Edit Fields", StringFieldSpec, NumStrings); return 0; case WM_LBUTTONDBLCLK: // display dialog box on double-click // StringsBox::GetStrings() returns TRUE if // user selects OK button; in this case, // cause window to be repainted to display // latest contents of EditBox.hStrings block if ((EditBox.GetStrings(InsGlobs.hInstance, WndGlobs.hwnd, TRUE) == TRUE)) InvalidateRect(WndGlobs.hwnd, NULL, TRUE); return 0; case WM_PAINT: hdc = BeginPaint(WndGlobs.hwnd, &ps); if (EditBox.hStrings != 0) { lpString = GlobalLock(EditBox.hStrings); for (i = 0; i < NumStrings; i++) { TextOut(hdc, 0, i*WndGlobs.cyChar, lpString, _fstrlen(lpString)); lpString += StringFieldSpec[i].MaxLength; } GlobalUnlock(EditBox.hStrings); } EndPaint(WndGlobs.hwnd, &ps); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); } int ErrorMessage(LPSTR msg) { MessageBox(WndGlobs.hwnd, msg, szAppName, MB_ICONINFORMATION|MB_OK); return 0; }
Copyright © 1992, Dr. Dobb's Journal