Communicating with Modal Dialog Boxes in Windows

The problem

Your application uses modal dialog boxes with heaps of data in them. How do you get your data from the client to the dialog box procedure, and get it back?

One solution

Global variables. For those of you who like slop code, and don't mind if your code isn't modular and has to be rewritten for each project, global variables are a great way to create mysterious bugs in your application.

A better solution

Your client code can pass a pointer (or any other datum you like) using the Windows function DialogBoxParam(). This datum is then received by the dialog box procedure as the lParam argument when the procedure receives the WM_INITDIALOG message.

You can use this to pass a pointer to a special object, which is designed for passing data back and forth. Hence the Dialex object. The Dialex object receives initialization data from the client, and the dialog box procedure can apply the contents to the dialog box, refresh the data in the dialog box, and return the data when the user's done with the dialog box. The client can then get the returned data from the Dialex. All this is done with calls to the Dialex's member functions, so it encapsulates neatly.

This works best with modal dialog boxes, which have a "take-it-or-leave-it" approach. By this I mean that the user gets the dialog box, edits the data, and tells the application to either use the edited data or to abandon the edited data. Modeless dialog boxes require a different style of interactivity that isn't discussed here.

dialex.h

The file that defines the Dialex class can look like this:


    #ifndef DIALEX_H
    #define DIALEX_H

    #include <windows.h>

    struct DialexItem {
        struct DialexItem *next;
        short type;
        short dlgBoxItem; // the affected dialog box item
        union {
            int i;        // an integer
        } data;           // the data to be passed
        DialexItem();
        ~DialexItem();
    };

    #define DIT_INT 0     // an integer in data.i

    // for setting up dialog boxes
    class Dialex {
        private:
            int itemCount;
            struct DialexItem *first;
        public:
            Dialex();
            ~Dialex();
            void applyData(HWND);
            void retrieveData(HWND);
            bool setInteger(int,int);
            int getInteger(int);
            // feel free to declare and define more member functions here
    };

    #endif

You should observe that the member functions come in complementary pairs.

The constructor and destructor are most easily called by the client code using the new and delete operators. There's nothing fancy in the construction or destruction.

The other methods are used as follows:

So let's implement them.

dialex.cpp


    // dialex.cpp
    // implement the Dialex class

    #include "dialex.h"

    // constructor
    Dialex::Dialex() {
        itemCount=0;
        first=(DialexItem*)0;
    }

    // This frees up the DialEx's stuff.
    // Use this when the client is done with the dialog box an the Dialex
    // that was created for it.
    Dialex::~Dialex() {
        DialexItem *di,*ni;

        di=first;
        while(di!=(DialexItem*)0) {
            ni=di->next;
            delete di;
            di=ni;
        }
    }

    // This receives the data from a Dialex and uses it to setup the dialog box.
    // Use this when initializing the dialog box and when the user clicks "retry"
    void Dialex::applyData(HWND hwnd) {
        DialexItem *di;

        for(di=first;di!=(DialexItem*)0;di=di->next) {
            switch(di->type) {
                case DIT_INT:
                    SetDlgItemInt(hwnd,di->item,di->data.i,TRUE);
                break;
            }
        }
    }

    // this extracts data from a dialog box and puts it back into the Dialex
    // Use this when the user clicks on "Okay" to close the dialog box.
    void Dialex::retrieveData(HWND hwnd) {
        DialexItem *di;

        for(di=first;di!=(DialexItem*)0;di=di->next) {
            switch(di->type) {
                case DIT_INT:
                    di->data.i=GetDlgItemInt(hwnd,di->item,NULL,TRUE);
                break;
            }
        }
    }

    // The client code uses this method to inform the Dialex that there
    // is an edit field in the dialog box which needs to be initialized
    // at start-up and returned when the procedure ends.
    // It returns false if the entry could not be made in the Dialex.

    bool Dialex::setInteger(int dlgItem,int value) {
        DialexItem *di;
    
        for(di=first;di!=NULL;di=di->next) {
            if(di->item==dlgItem) {
                di->data.i=value;
                return true;
            }
        }
        di=new DialexItem;
        if(di==(DialexItem*)0) return false;
        di->next=first;
        first=di;
        di->item=dlgItem;
        di->data.i=value;
        return true;
    }

    // the client code uses this method to get the returned data from
    // the Dialex after the user has closed the dialog box.

    int Dialex::getInteger(int dlgItem) {
        DialexItem *di;

        for(di=first;di!=NULL;di=di->next) {
            if(di->item==dlgItem) {
                return di->data.i;
            }
        }

        return 0;
    }

    DialexItem::DialexItem() { }

    DialexItem::~DialexItem() { }

client.cpp


    // client.cpp
    // sample client code.

    #include "dialex.h"

    HINSTANCE hInst;  // the module instance
    HWND hwndMain;    // the application's main window
    // EB_COUNT is the identifier of the edit control to receive the integer.
    // IDD_EDIT is the identifier of the dialog box.

    INT_PTR CALLBACK editDlg(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) {
        static Dialex *de;

        switch (msg) {
            case WM_INITDIALOG:
                de=(Dialex*)lParam;
                de->applyData(hwnd);
                return 1;
            case WM_COMMAND:
                switch(LOWORD(wParam)) {
                    case IDOK:
                        de->retrieveData(hwnd);
                    case IDCANCEL:
                        EndDialog(hwnd,LOWORD(wParam));
                    return 1;
                }
            break;
        }
        return 0;
    }

    void editSomething(void) {
        Dialex *de;
        int return_value;

        de=new Dialex;

        de->setInteger(EB_COUNT,2);
        if(DialogBoxParam(hInst,MAKEINTRESOURCE(IDD_EDIT),hwndMain,editDlg,(LPARAM)de)==IDOK) {
            return_value=de->getInteger(EB_COUNT);
            // code here to make use of the returned value
        }
    }

Room for Improvement

You are by no means limited to passing just integer values. To add a new data type:

In addition to set the integer in an edit box, you'll also want to set text in edit boxes, the flags of check boxes, and possibly the text in dialog box labels (and the dialog box title). It may even be possible to set up your dialogs so that they all use the same dialog box procedure. In an application of my own which uses the Dialex, there is a type for integers, floats, strings, a pointer to an array of floats, and a pointer to other object types. I left that out of this article for simplicity's sake.

You can define a DialexItem type which changes the text of dialog box labels (or the title of the dialog box window); since this would be one-way data, you won't need code to get the value back from the dialog box. This allows you to use the same dialog box resource for different requests.

Modeless dialog boxes work differently. The Dialex is good for getting the data to the dialog box when it is created, but data returned from a modeless dialog box needs to go back as each individual change is made. The simplest implementation is to update the Dialex as each change is made (using the set*() member functions) and notify the parent window as well (using a user-defined message, passing the Dialex pointer in lParam and the identifier of the updated DialexItem in wParam).


Got comments?

Send them to John at evilsnack at hotmail dot com

Return to John's Freeloading Home Page

Hosted by www.Geocities.ws

1