modula-2 home

  Win32 API  


Dialogs, GUI coders best friend

Example: dlg_one


There's hardly a windows program out there that doesn't use dialog boxes. Just go File -> Open in any text editor or any other kind of editor for that matter and voila, you are presented with a dialog box, one that probably allows you to select a file to be opened.

Dialogs aren't limited to the standard open file ones, they can look like and do whatever you choose. The attractive point of dialogs is that they provide a quick way to arrange and create a GUI (Graphic User Interface) and even some default processing, cutting down on the amount of code you must write.

One thing to remember is that dialogs are just windows. The difference between a dialog and a "normal" window is that the system does some additional default processing for dialogs, such as creating and initialising controls, and handling tab order. Nearly all APIs that are applicable to "normal" windows will work just as well on dialogs, and vice versa!

The first step is to create the dialog resource. The resource file we will use in this example is included in the download.

So! We now need to write a Dialog Procedure to process message for this box. Don't worry this is nothing new, it's practicly the same as our main Window Procedure (but not exactly).

PROCEDURE AboutDlgProc(hwnd : HWND; msg : UINT;
                       wParam : WPARAM; lParam : LPARAM) : BOOL [EXPORT, OSCall];
    CASE msg OF
    | WM_COMMAND    :
      CASE LOWORD(wParam) OF
      | IDOK     : FUNC EndDialog(hwnd, IDOK);
      | IDCANCEL : FUNC EndDialog(hwnd, IDCANCEL);
END AboutDlgProc;

There are a few important differences between a dialog procedure and window procedure. One is that you DO NOT call DefWindowProc() for message you don't handle. With dialogs this is done automatically for you (and will really screw things up if you do it).

Secondly, in general you return FALSE for messages you don't process, and TRUE for messages you do process, UNLESS the message specifies you return something else. Note that this is what we do above, the default is to do nothing and return FALSE, while messages we do handle break the switch() and return TRUE.

Thirdy, You do not call DestroyWindow() to close a dialog, you call EndDialog(). The second paramter is the value that is returned to whatever code called DialogBox().

Finally, instead of handling WM_CREATE, you handle WM_INITDIALOG to do any processing that needs to be done before the dialog appears, and then return TRUE to have the keyboard focus set to the default control. (You can actually handle WM_CREATE as well, but it is sent BEFORE any of the controls have been created, so you can't access them. In WM_INITDIALOG the controls have already been created).

Enough chit-chat, lets create it....

    | WM_COMMAND :
      CASE LOWORD(wParam) OF
      | ID_HELP_ABOUT : ret := DialogBoxId(Instance,
                                     IDD_ABOUT, hwnd, AboutDlgProc);
                        IF ret = IDOK THEN
                          MessageBox(hwnd, "Dialog exited with IDOK.", "Notice",
                                     MB_OK BOR MB_ICONINFORMATION);
                        ELSIF ret = IDCANCEL THEN
                          MessageBox(hwnd, "Dialog exited with IDCANCEL.", "Notice",
                                     MB_OK BOR MB_ICONINFORMATION);
                        ELSIF ret = -1 THEN
                          MessageBox(hwnd, "Dialog failed!", "Error",
                                     MB_OK BOR MB_ICONINFORMATION);
      (* other menu commands *)

This is the code I used to create my about box, you can probably guess that this is to be merged into your WM_COMMAND handler, if you aren't clear on this aspect, you might want to review the section on menus. ID_HELP_ABOUT is the identifier of my Help -> About menu item.

Since we want the menu on our main window to create the dialog, we obviously want to put this code in the WndProc() of our main window, not the dialog proc.

Now I stored the return value from the call to DialogBox(), this is just so you can observe the effects of pressing the two buttons, hitting Esc, Enter etc... from inside the dialog. It also illustrates how to use the return value from a dialog box to check for success, failure, a users choice, or whatever other information you choose to send back to the caller from the Dialog Procedure.

    ret := DialogBoxId(Instance, IDD_ABOUT, hwnd, AboutDlgProc);

This is the only important part, and you can choose to put it wherever in your code that you want the dialog to come up. IDD_ABOUT is the id of the dialog resource. hwnd is the handle to the parent window of the dialog. AboutDlgProc() is of course the dialog procedure to use to control the dialog.

That's it!

A perticularly astute reader might eventually wonder, if DialogBox() doesn't return untill the dialog closes we can't process messages while it's up, so how does it work? Well the nifty thing about DialogBox() is that it has it's own message loop, so while the dialog is displayed, our message loop is out of the picture and the default loop is handled by windows. This loop also takes care of fun things like moving the keyboard focus from control to control when you press Tab.

Another effect of using DialogBox is that your main window is disabled untill the dialog is dismissed. Sometimes this is what we want, and sometimes it isn't, such as when we want to use a dialog as a floating toolbar. In this case we want to be able to interact with both out dialog and our main window, and this will be the focus of the next section.


Copyright © 1998-2011, Brook Miles. All rights reserved. Adapted for Modula-2 by Frank Schoonjans, with permission.