modula-2 home

  Home  
  Tutorial  
  Win32 API  
  Reference  
  Projects  
 

 

Modeless Dialogs

Example: dlg_two

[images/dlg_two.gif]

Now we take a look at CreateDialog(), DialogBox()'s sister function. The difference is that while DialogBox() implements it's own message loop and does not return untill the dialog is closed, CreateDialog() acts more like a window created with CreateWindowEx() in that it returns immediately and depends on your message loop to pump the messages as it does for your main window. This is termed Modeless, whereas DialogBox() creates Modal dialogs.

We have create the dialog resource using the Stony Brook Resource Editor and it is included in the download.

We want to create the dialog when our program runs, and we want the dialog visible right away so we do this in WM_CREATE. We also want to declare a global variable to hold the window handle returned from CreateDialog() so that we can use it later. DialogBox() didn't return a handle to us since when DialogBox() returns the window has been destroyed.

VAR
    g_hToolbar : HWND;

This is some new code in our WinProc procedure:

VAR ptr : LPTSTR;
BEGIN
    CASE msg OF
    | WM_CREATE  : ptr := MAKEADR(IDD_TOOLBAR);
                   g_hToolbar := CreateDialog(Instance, ptr^, hwnd, ToolDlgProc);
                   IF g_hToolbar # NIL THEN
                     FUNC ShowWindow(g_hToolbar, SW_SHOW);
                   ELSE
                     FUNC MessageBox(hwnd, "CreateDialog returned NIL", "Warning!",
                                     MB_OK BOR MB_ICONINFORMATION);
                   END;
    (* ... *)

We check the return value, which is ALWAYS a good idea, and if it's valid (not NULL) we show the window with ShowWindow(), with DialogBox() this isn't necessary since the system calls ShowWindow() for us.

Now we need a dialog procedure for our toolbar.

PROCEDURE ToolDlgProc(hwnd : HWND; msg : UINT;
                       wParam : WPARAM; lParam : LPARAM) : BOOL [EXPORT, OSCall];
BEGIN
    CASE msg OF
    | WM_COMMAND    :
      CASE LOWORD(wParam) OF
      | IDC_PRESS : FUNC MessageBox(hwnd, "Hi!", "This is a message",
                                    MB_OK BOR MB_ICONEXCLAMATION);
      | IDC_OTHER : FUNC MessageBox(hwnd, "Hi!", "This is also a message",
                                    MB_OK BOR MB_ICONEXCLAMATION);
      ELSE
      END;
    ELSE RETURN FALSE;
    END;
    RETURN TRUE;
END ToolDlgProc;

Most of the same message handling rules apply to dialogs created with CreateDialog() as with DialogBox(), don't call DefWindowProc(), return FALSE for messages you don't handle and TRUE for those you do.

One change is that we don't call EndDialog() for modeless dialogs, we can use DestroyWindow() just like for regular windows. In this case I destroy the dialog when the main window is destroyed. In the main window's WndProc()...

    | WM_DESTROY : DestroyWindow(g_hToolbar);
                   PostQuitMessage(0);

Last but not least, we want to be able to display and hide our toolbar whenever we choose so I've added two commands to my menu to do this, and handled them so:

    | WM_COMMAND :
      CASE LOWORD(wParam) OF
      | ID_FILE_EXIT   : FUNC PostMessage(hwnd, WM_CLOSE, 0, 0);
      | ID_DIALOG_SHOW : FUNC ShowWindow(g_hToolbar, SW_SHOW);
      | ID_DIALOG_HIDE : FUNC ShowWindow(g_hToolbar, SW_HIDE);
      ELSE RETURN DefWindowProc(hwnd, msg, wParam, lParam);
      END;

You should be able to create your own menu using the resource editor, but if not (as always) take a look at the example project dlg_two provided with the tutorial.

Now when you run the program, you should be able to access both the dialog window, and main window at the same time.

If you've run the program at this point and tried tabbing between the two buttons, you have probably noticed it doesn't work, neither does hitting Alt-P or Alt-O to activate the buttons. Why not? Whereas DialogBox() implements it's own message loop and handles these events by default, CreateDialog() does not. We can do it ourselves though, by calling IsDialogMessage() in our message loop which will do the default processing for us.

    WHILE GetMessage( Msg, NIL, 0, 0) DO
       IF ~IsDialogMessage(g_hToolbar, Msg) THEN
          FUNC TranslateMessage(Msg);
          FUNC DispatchMessage(Msg);
       END;
    END;

Here we first pass the message to IsDialogMessage(), if the message is destined for our toolbar (indicated by the window handle we pass in) the system will perform the default processing and return TRUE. Is this case the message has already been handled so we don't want to call TranslateMessage() or DispatchMessage(). If the message is for another window we process as usual.

It's also worth noting that IsDialogMessage() can also be used with windows that aren't dialogs in order to to give them dialog-like behaviour. Remember, a dialog is a window, and most (if not all) dialog APIs will work on any window.

And that is pretty much all there is to modeless dialogs! One issue that may arise is if you have more than one toolbar... what do you do? Well one possible solution is to have a list (either an array, an STL std::list, or similar) and loop through it in your message loop passing each handle to IsDialogMessage() until the right one is found, and if none, do the regular processing. This is a generic programming problem, not one that is Win32 related, and is left as an excersize to the reader.

 


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