Reliable Software Logo
Home >C++ Resources > Windows API Tutorial  >   Dialog-based App

Program with a Dialog Box as the Main Window

The main window of a program doesn't have to be a resizable general purpose window. Many small applications work better in a dialog box format. The obvious advantage of such an approach is that you can use a resource editor to arrange all your controls on the surface of the box. This is in fact how the UI of the Frequency Analyzer was implemented. Since this is a useful technique, I will describe it in some detail. You can also download source codedownload the source code for a simple application that demonstrates the techniques described here (courtesy Laszlo Radanyi).

First of all, we have to design the dialog box using the resource editor. We assign identifiers to all the controls and to the dialog itself. Here, the dialog resource has the identifier DLG_MAIN. In the WinMain procedure we don't have to register any window class, because Windows has a pre-defined class for dialog boxes. Instead of creating a window, we call CreateDialog, passing it a pointer to our own dialog procedure (explained later).

The message loop is non-standard in that it calls IsDialogMessage for every message. This API not only checks whether a given message is directed at the dialog box, but more importantly, it dispatches it to the dialog procedure. If the message was not for the dialog, we do the standard translation and dispatching.

For convenience, we keep the value of HINSTANCE in a global variable. This is actually a precursor of a more general object--the Application. Here, however, we decided it was too trivial to deserve a class of its own.

HINSTANCE TheInstance = 0;

int WINAPI WinMain
   (HINSTANCE hInst, HINSTANCE hPrevInst, char * cmdParam, int cmdShow)
{
    TheInstance = hInst;
    _set_new_handler (& NewHandler);

    HWND hDialog = 0;

    hDialog = CreateDialog (hInst, 
                            MAKEINTRESOURCE (DLG_MAIN), 
                            0, 
                            DialogProc);

    if (!hDialog)
    {
        char buf [100];
        wsprintf (buf, "Error x%x", GetLastError ());
        MessageBox (0, buf, "CreateDialog", MB_ICONEXCLAMATION | MB_OK);
        return 1;
    }

    MSG  msg;
    int status;
    while ((status = GetMessage (& msg, 0, 0, 0)) != 0)
    {
        if (status == -1)
            return -1;
        if (!IsDialogMessage (hDialog, & msg))
        {
            TranslateMessage ( & msg );
            DispatchMessage ( & msg );
        }
    }

    return msg.wParam;
}

The dialog procedure is just like Windows procedure, except that it returns TRUE when it processes a message and FALSE when it doesn't. There is no need to call the default procedure, because Windows does it for us whenever the dialog procedure returns FALSE (makes you think, "Why wasn't the same design used in the window procedure...?"). The first message the dialog gets is WM_INITDIALOG and the last one is WM_CLOSE. During the processing of these messages we create and destroy the Controller. Other than that, the dialog expects messages from its controls--these are passed as WM_COMMAND. One control that requires special processing is a (horizontal) scrollbar--it sends the WM_HSCROLL message. There are scrollbar controls in the Frequency Analyzer and that's how they are dealt with.

BOOL CALLBACK DialogProc (HWND hwnd, 
                          UINT message, 
                          WPARAM wParam, 
                          LPARAM lParam)
{
    static Controller* control = 0;
    switch (message)
    {
    case WM_INITDIALOG:
        try
        {
            control = new Controller (hwnd);
        }
        catch (WinException e)
        {
            MessageBox (0, e.GetMessage (), "Exception",
                        MB_ICONEXCLAMATION | MB_OK);
            PostQuitMessage(1);
        }
        catch (...)
        {
            MessageBox (0, "Unknown", "Exception",
                        MB_ICONEXCLAMATION | MB_OK);
            PostQuitMessage(2);
        }
        return TRUE;
    case WM_COMMAND:
        control->Command(hwnd, LOWORD (wParam), HIWORD (wParam));
        return TRUE;
    case WM_HSCROLL:
        control->Scroll (hwnd, LOWORD (wParam), HIWORD (wParam));
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return TRUE;
    case WM_CLOSE:
        delete control;
        DestroyWindow (hwnd);
        return TRUE;
    }
    return FALSE;
}

Let's have a look at the Controller. Notice how every control on the face of the dialog box has a corresponding (embedded) control object inside the Controller. There are edits, combos, radio buttons and scrollbars. There is a special metafile control that draws the frequency scale and two view objects corresponding to two static panes upon which we draw the graphs. Finally we have the Painter object that is responsible for the asynchronous repainting of the two panes.

class Controller
{
public:
    Controller(HWND hwnd);
    ~Controller ();
    void Command (HWND hwnd, int id, int code);
    void Scroll (HWND hwnd, int cmd, int pos);
    void Paint (HWND hwnd);
    void ReInit (HWND hwnd);
    void Stop (HWND hwnd);

private:
    void InitScrollPositions ();
    void PaintScale ();

    BOOL                _isStopped;

    int                 _bitsPerSample;
    int                 _samplesPerSecond;
    int                 _fftPoints;
    int                 _samplesPerBuf;

    EditReadOnly        _edit;
    Combo               _comboFreq;
    Combo               _comboPoints;
    RadioButton         _radio8;
    RadioButton         _radio16;
    ScrollBarMap        _scroll;

    StaticEnhMetafileControl    _scaleFreq;

    ViewWave            _viewWave;
    ViewFreq            _viewFreq;

    Painter             _display;
};

The constructor of the Controller takes care of the initialization of all the controls by passing them the handle to the dialog box window and the appropriate identifiers. As a matter of cosmetics, we attach our own icon to the dialog--otherwise the system would use the standard Windows icon.

Controller::Controller (HWND hwnd)
   :_isStopped (TRUE),
    _bitsPerSample (16),
    _samplesPerSecond (SAMPLES_SEC),
    _fftPoints (FFT_POINTS * 4),
    _samplesPerBuf (FFT_POINTS * 2),
    _radio8 (hwnd, IDC_8_BITS),
    _radio16 (hwnd, IDC_16_BITS),
    _scroll (hwnd, IDC_SCROLLBAR),
    _edit (hwnd, IDC_EDIT),
    _comboFreq (hwnd, IDC_SAMPLING),
    _comboPoints (hwnd, IDC_POINTS),
    _viewWave (hwnd, IDS_WAVE_PANE, FFT_POINTS * 8),
    _viewFreq (hwnd, IDS_FREQ_PANE),
    _scaleFreq (hwnd, IDC_FREQ_SCALE),
    _display (hwnd,
            _viewWave,
            _viewFreq,
            _samplesPerBuf,
            _samplesPerSecond,
            _fftPoints)
{
    // Attach icon to main dialog
    HICON hIcon = LoadIcon (TheInstance, MAKEINTRESOURCE (ICO_FFT));
    SendMessage (hwnd, WM_SETICON, WPARAM (ICON_SMALL), LPARAM (hIcon));
    // Other initializations...
}

Using a dialog box as the main window is a very handy and simple technique, especially for applications that display a panel-like interface. By the way, the Battleship app (see the home page) uses the same trick. You may now continue to the next tutorial on the use of dialog boxes in Windows applications.