Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Modeless Dialog Boxes for Windows


MAY93: MODELESS DIALOG BOXES FOR WINDOWS

Graphical developer interfaces can speed development cycles

Joe received his PhD in the area of compiler optimization from Carnegie Mellon University in 1975. He was involved in some of the earliest interactive language design in the late 1960s, and has been dealing with user interfaces since that time. He is currently a consultant and Windows-application developer.


While almost all Windows programs take advantage of the operating environment's rich graphical user interface, I have found that it's possible to use the environment as a graphical "developer" interface too. For instance, in many cases, information that comes out as debug print statements in a conventional program can be represented more readily in a graphical form. While this doesn't replace the need for the general debug print mechanism (such as the WINIO package described by Schulman, Maxey, and Pietrek in Undocumented Windows), GUIs have considerably shortened my development time on a number of projects.

To expedite the development of these developer interfaces, I wanted to do direct screen layout and not have to develop my own windows. Therefore, I implemented these windows as modeless dialog boxes, allowing me to use the dialog editor for all necessary layout. However, when I attempted to make the modeless dialog boxes do what I wanted, I encountered a number of problems, which I'll discuss here, along with their workarounds.

Debugging a Parser

For one job, I had to develop a report-generator application. The client had a specification of the report language, and I had to implement the compiler for this language, along with display mechanisms. I decided to create a modeless dialog box with controls such as:

  • A static text item that was the input stream as seen by the lexer.
  • A static text item that was the current lexeme being scanned.
  • A static text item that was the lexeme code.
  • A multiline scrollable static text item that was the representation of the parse stack.
  • A collection of buttons and checkboxes.
The buttons and checkboxes allowed me to single-step the lexer and parser, and control the level of detail of the information I was seeing. At each point I could examine the various states. When it came time to debug the generated interpretive tree, I built another modeless dialog box that gave me single-step execution capability on the tree, showing the current operator, current evaluation stack, and so forth. Then I added another box that let me see the underlying database records, and one that let me scroll through the computed internal state vector (based on computations the client had already written to massage the database and derive useful information). It was amazing how fast development went when I could see the information in a reasonable form. Like debug print statements, the output doesn't have to be fancy, and once you've done a couple of these you can clone them forever.

Handling IsDialogMessage

As I added each new modeless dialog box, I discovered that I had to add an IsDialogMessage test to my main message loop; this meant that my main message loop had to know about each of the dialog boxes being used. Somehow this violated a principle of abstraction: You shouldn't have to keep changing the main message loop each time you add a new dialog box. I therefore implemented a dialog-box registry mechanism: Whenever I created a new dialog box, I registered it with the dialog-box handler, which was responsible for ensuring that IsDialogMessage was passed on to each of the dialog boxes in turn; the first one that handled IsDialogMessage would terminate the dialog handling.

The modeless registry (see Listing One, page 82) maintains a linked list of pointers to window handles. It is limited in that the window handle is expected to be in either static memory or in memory that's not freed until after the handle is unregistered, as it determines the identity of window-handle references by equality of pointers. Thus, if the modeless dialog window is ever destroyed, its handle will be set to NULL. However, if it is re-registered without having been unregistered, the old entry will be reused.

The ProcessModeless routine handles all modeless dialog boxes. If any modeless dialog box handles the IsDialog Message call, ProcessModeless returns True; otherwise it returns False. The main message loop (Listing Two, page 82) is quite simple and never needs to know how many modeless dialog boxes exist.

The UnregisterModeless procedure takes a window handle and unregisters it. This is done in Listing One by setting the variable that holds the window handle to NULL, then removing the window-registry item from the list. This is normally not called by the application, but by the dialog message handler I'm about to describe.

Iconizing Dialog Boxes

With all this power and flexibility, my screen was suddenly cluttered with many dialog boxes. When I minimized them, however, I discovered that they used the class icon of the main window, so I couldn't readily tell one from the other.

To address this, I first tried setting the GCW_ICON word of the dialog to hold the icon I wanted to display. This didn't work because this was the class word for dialog boxes, which caused the icon of the most-recently created dialog box to be displayed for all active modeless dialog boxes. I discovered, however, that if I set the GCW_ICON word of the dialog to NULL, it would display the canonical empty-white-square icon.

Next, I needed to set up my own icon and attach it to the dialog box. There are two ways to attach extra information to a window: SetProp and SetWindowLong using the GDL_USER longword offset. Because the GDL_USER longword is very powerful and can be used to attach arbitrary data structures to a dialog window, I did not want to usurp it for the icon or require that the object it references have the icon reference in a known place. Therefore, I used SetProp.

The SetProp function allows you to attach, by name, an arbitrary 16-bit value to any window. I attached the handle of the icon I wanted under the property name "icon"; see Listing Three, page 82. In this procedure, I pass in the name of the icon, which is the name that appears in the resource file with the ICON declaration. I could easily have written one which took an icon handle, but that wasn't necessary.

When the window is closed, you need to remove the property. This is handled in my default dialog-box handler. Normally, a dialog box returns True to indicate that it has handled the message or False to indicate that it has not; some special messages have special values returned. If it returns False, the message was not handled, and the default dialog-box handler, DefDlgProc, will be called by Windows. (It must not be called explicitly in a dialog-box handler.) I added my own default handler, in the style of DefWindowProc; see Listing Four, page 82. If a dialog box doesn't want to process the message, it calls MyDefDialogProc and returns whatever value MyDefDialogProc returns.

My DefDialogProc's main purpose is to handle the dialog-specific icon. The messages it handles are WM_PAINT, WM_NCPAINT, WM_ERASEBKGND, WM_CLOSE, WM_NCDESTROY, and WM_QUERYDRAGICON.

After some experimentation, I found that the class icon for the dialog would be changed each time a new modeless dialog (and perhaps modal dialog) was created; it was set to the class icon of the parent window. Thus, all existing modeless dialog windows displayed their icons using the parent window's icon, no matter what icon they had displayed previously. This wasn't immediately obvious since the icon did not actually change until repainting occurred. I therefore had to keep resetting the class icon of the dialog class to NULL.

The solution is to intercept the WM_NCPAINT message and simply force the GCW_HICON class word to NULL. While this may not be the most elegant solution, it does work consistently and correctly and has done so for over a dozen Windows applications.

The WM_PAINT operation is key; if the dialog window is iconic, it calls PaintIconicDialog; see Listing Five, page 82. This erases the background for the icon and then draws the icon in the window location. The magic constant "2," which is added to the x,y position passed to DrawIcon, is another piece of empiricism; the area erased for the icon is a rectangle 36x36 logical units in size, so these magic constants place the 32x32 icon in the correct place, centered horizontally and vertically, in that area. If the window is not iconic, the WM_PAINT message is not handled here, and will ultimately be handled by the dialog class handler. This probably won't work for CGA displays, but supporting a CGA is not high on my list of priorities. It does, however, suggest a certain fragility in this code if Windows starts supporting 64x64 or scalable icons for high-resolution displays someday.

After I successfully got the correct icon displayed, I discovered that every icon appeared in a white rectangle, no matter what I tried. Using Spy and Codeview, I found that WM_ERASEBKGND was the culprit; a 36x36 hole was left on the screen whenever the window was iconized, so the later DrawIcon operation put the icon on this hole. This is not what you'd expect to see, so I intercept this message. If the window is iconic, I report that I've handled WM_ERASEBKGND (by returning True), while in fact I actually do nothing. Then the icons appear drawn as expected. If the window isn't iconic, I return False and let the operation proceed normally. WM_ICONERASEBKGND, which normally handles this, is not sent if the class icon is NULL.

When the window is finally closed, WM_CLOSE removes the icon property by calling DestroyWindow to close the modeless dialog box; a modeless dialog box must not call EndDialog, which is only for modal dialog boxes. If properties are not removed from Windows before they are destroyed, they may result in unreclaimable resource consumption in the USER heap.

The last message needed to deal with custom icons is WM_QUERYDRAGICON. If you're using this technique, the class icon is NULL, so a drag operation drags a blank rectangle, which is not what you would expect. To get a drag icon that resembles the icon you are using, you must respond to the WM_QUERYDRAGICON with the handle to a cursor or icon that will be used to do the dragging. If you provide an icon, it will be converted to a cursor, so there's no need to keep two identical drawings -- one in icon form and one in cursor form -- unless Windows' default transformation for converting an icon to a drag cursor results in something you find unacceptable. If you want to supply a cursor, you'll have to add a "cursor" property, and have SetWindowIcon attempt to load a cursor of the same name: If it succeeds, add the cursor property; if not, either add the cursor property with the icon handle or don't add it at all.

At this point, it appears that WM_NCDESTROY is the last message ever sent to a window. The MyDefDialogProc handler calls the UnregisterModeless procedure to remove the window from the registry.

Other Useful Techniques

I discovered that if I create a dialog box in the dialog editor without a thick border, or a maximize box, but with a system menu, the system menu contains both a Size and Maximize selection. The Size selection lets me resize the box using the cursor keys and the Maximize selection will maximize it. The only way to handle these was to intercept the WM_INITMENUPOPUP message in those dialog boxes in which I was not prepared to deal with resizing or maximizing and gray out those system-menu options; see Listing Six, page 82.

Summary

Modeless dialog boxes are useful for both user and developer interfaces. However, using them effectively requires awareness of what Windows does when you want to iconize them and have them display unique icons. If you want to use several modeless dialog boxes, it's much simpler if the main loop does not need to know how many of them are active.

The code in this article was derived empirically. While not exactly "undocumented Windows" in the same sense that Schulman, Maxey, and Pietrek mean, the amount of work required to discover these less-than-obvious interactions was considerable. This may not be the most correct or most elegant way to handle the problems, but I have successfully used it for two years, for Windows 3.0 and 3.1, without modifications.



_MODELESS DIALOG BOXES FOR WINDOWS_
by Joseph M. Newcomer


[LISTING ONE]
<a name="0124_000a">

typedef struct modeless {
        LPHANDLE hpWnd;
        struct modeless * next;
                        } modeless;
modeless * modeless_list = NULL;

/********************* RegisterModeless ************************************
* Inputs: LPHANDLE hpWnd: Pointer to modelss dialog handle
* Result: void
* Effect: Registers the handle for a modeless dialog

*       not created for each reference.
***************************************************************************/
void RegisterModeless(LPHANDLE hpWnd)
    {
     modeless * hm;
     modeless * p;
     p = modeless_list;
     while(p != NULL)
        { /* scan list */
         if(p->hpWnd == hpWnd)
            return;   /* already registered */
         p = p->next;
        } /* scan list */
     /* The window handle was not already registered */
     hm = (modeless *)malloc(sizeof(modeless));

     if(hm == NULL)
        return;
     hm->hpWnd = hpWnd;
     hm->next  = modeless_list;
     modeless_list = hm;
    }
/***************************** UnregisterModeless **************************
* Inputs: HWND hWnd: Window handle to unregister
* Result: void
* Effect: Locates the entry which references this modeless dialog box and
*       removes it, after setting the handle to NULL.
***************************************************************************/
void UnregisterModeless(HWND hWnd)
    {
     modeless * * prev_next = &modeless_list;
     modeless * p;
     p = modeless_list;
     while(p != NULL)
        { /* scan list */
         if(*p->hpWnd == hWnd)
            { /* found it */
             *prev_next = p->next;
             *p->hpWnd = NULL;
             free(p);
             return;

            } /* found it */
         prev_next = &p->next;
         p = p->next;
        } /* scan list */
    }
/********************** ProcessModeless **************************************
* Inputs: LPMSG msg: Pointer to a message
* Result: boolean. TRUE if IsDialogMessage returned true. FALSE if returned
*                     false (or no modeless dialog windows were registered)
* Effect: Handles IsDialogMessage for registered modeless dialog windows
* Notes: If a registered modeless window is destroyed, the destroyer must set
*  window handle which has been registered with this routine to NULL. If the
*  window is later re-created, the handle can be set to the new value.
***************************************************************************/
boolean ProcessModeless(LPMSG msg)
    {
     modeless * p;
     p = modeless_list;
     while(p != NULL)
        { /* scan list */
         if(*p->hpWnd != NULL)
            if(IsDialogMessage(*p->hpWnd,msg))
               return TRUE;
         p = p->next;
        } /* scan list */
     return FALSE;
    }





<a name="0124_000b">
<a name="0124_000c">
[LISTING TWO]
<a name="0124_000c">


while(GetMessage(&msg, NULL, 0, 0))
   { /* message loop */
    if(ProcessModeless(&msg))
       continue;
    TranslateMessage(&msg);
    DispatchMessage(&msg);
   } /* message loop */





<a name="0124_000d">
<a name="0124_000e">
[LISTING THREE]
<a name="0124_000e">

/************************** SetWindowIcon ************************************
* Inputs: HWND hWnd: Window into which to set icon. HANDLE hInst: Instance
*    handle for fetching icon char * name: Name of icon
* Result: HICON Icon handle in case it is useful; NULL if there was a failure
*    to load the icon
* Effect: Sets the "icon" property used by PaintIconicDialog
***************************************************************************/
HICON FAR PASCAL SetWindowIcon(HWND hWnd, HANDLE hInst, char * name)
    {
     HANDLE hIcon;
     hIcon = LoadIcon(hInst, name);
     if(hIcon == NULL)

        return NULL;
     SetProp(hWnd,"icon",hIcon);
     SetClassWord(hWnd,GCW_HICON,NULL);
     return hIcon;
    }





<a name="0124_000f">
<a name="0124_0010">
[LISTING FOUR]
<a name="0124_0010">

/******************************* MyDefDialogProc *****************************
* Inputs: HWND hDlg: Dialog window handle unsigned message: Message received
*   WORD wParam: word value from message. LONG lParam: long value from message
* Result: int
* Effect: Handles the most common cases of dialog boxes for this application
***************************************************************************/
int MyDefDialogProc(HWND hDlg, unsigned message, WPARAM wParam, LPARAM lParam)
    {
     switch(message)
        { /* stock responses */
         case WM_PAINT:

                 return PaintIconicDialog(hDlg);
         case WM_ERASEBKGND:
                 /* pretend we've erased the background for an iconic window */
                 if(IsIconic(hDlg))
                    return TRUE;
                 else

                    return FALSE;
         case WM_NCPAINT:
                 SetClassWord(hDlg,GCW_HICON,NULL);
                 return FALSE;
         case WM_CLOSE:
                 RemoveProp(hDlg,"icon");
                 DestroyWindow(hDlg);
                 break;
         case WM_NCDESTROY:
                 UnregisterModeless(hDlg);
                 return TRUE; /* let normal processing proceed */
         case WM_QUERYDRAGICON:
                 {
                  HICON hIcon = GetProp(hDlg,"icon");
                  return hIcon;
                 }
         default:
                 return FALSE;
        } /* stock responses */
     return TRUE;
    }





<a name="0124_0011">
<a name="0124_0012">
[LISTING FIVE]
<a name="0124_0012">

/************************* PaintIconicDialog *****************************
* Inputs: HWND hDlg: Dialog window
* Result: BOOL -- TRUE if painted icon; FALSE if did not paint icon
* Effect: Paints the icon if the window is iconic.  Requires that the
*       icon be installed via the SetProp function
***************************************************************************/
static BOOL PaintIconicDialog(HWND hDlg)
    {
     if(IsIconic(hDlg))
        { /* iconic */
         PAINTSTRUCT ps;
         HDC hDC;
         DWORD xy;
         HICON hIcon = GetProp(hDlg,"icon");
         if(hIcon != NULL)
            { /* found it */
             hDC = BeginPaint(hDlg, &ps);
             xy = GetWindowOrg(hDC);
             SendMessage(hDlg,WM_ICONERASEBKGND,hDC,0L);
             DrawIcon(hDC, LOWORD(xy)+2, HIWORD(xy)+2,hIcon);
             EndPaint(hDlg,&ps);
             return TRUE;
            } /* found it */
        } /* iconic */
     return FALSE;
    }




<a name="0124_0013">
<a name="0124_0014">
[LISTING SIX]
<a name="0124_0014">


int FAR PASCAL WhateverWndProc(HWND hDlg, unsigned message, WPARAM wParam, LPARAM lParam)
    {
     switch(message)
        { /* decode message */
         case WM_INITMENUPOPUP:
                 if(HIWORD(lParam))
                    { /* system menu */
                     HMENU sys;
                     sys = GetSystemMenu(hDlg, false);
                     EnableMenuItem(sys, SC_SIZE, MF_GRAYED);
                     EnableMenuItem(sys, SC_MAXIMIZE, MF_GRAYED);
                    } /* system menu */
                 return MyDefDialogProc(hDlg,message,wParam,lParam);
            ... other messages here












Copyright © 1993, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.