Initializing complex dialog boxes with transfer buffers

Back Up Next

In last month's issue of Borland C++ Developer's Journal, we showed how you can use a transfer buffer to pass data to and from a list box control (see "Using Transfer Buffers With OWL 2.0 List Boxes"). However, in the example, we created a simple dialog box that contained only a list box and two buttons.

To manage a more complex dialog box with a transfer buffer, you must be very careful about specifying the transfer buffer elements in the correct manner. In this article, we'll show how you can use transfer buffers to set up and respond to complex dialog boxes­­like the one shown in Figure A­­in an ObjectWindows Library (OWL) application.


Figure A - You can use transfer buffers to simplify managing complex dialog boxes.

Buffers and dialog boxes

In a dialog box that contains only one control, the transfer buffer you'll create will be correspondingly simple. For example, in the TRANSBUF.EXE program we created last month, the transfer buffer class MyDialogBuffer consisted of only a TListBoxData struct.

As you add more controls to your dialog boxes, though, you'll have to add corresponding elements to your transfer buffer. In fact, if you don't include a transfer element for each control in the dialog box that transfers data, it's unlikely that the OWL code will be able to properly initialize any of the controls.

Control order

In addition, for you to include an element for each control, the transfer buffer must contain those elements in a very specific order. The order that you'll use for the transfer elements will be the same order you use for the controls when you build the dialog box.

When you add OWL control objects to a TDialog-derived object, OWL builds a list of child objects it will manage. This happens due to cooperation between the TDialog-derived object and the constructors of the OWL control objects.

For example, if you're building a simple dialog box that contains an edit field, a list box, and a check box, you can write

TDialog* d = new TDialog(GetMainWindow(),DLG_ID);
TEdit* ed = new TEdit(d, ED_ID, 20);
TListBox lb = new TListBox(d, LB_ID);
TCheckBox cb = new TCheckBox(d, CB_ID);


These lines create a new TDialog object d, then add the edit field ed, the list box lb, and the check box cb to the TDialog object.

You'll notice that the first parameter of each control's constructor is the pointer to the TDialog object d. In the control constructors, each control object notifies the TDialog object that the control object is the TDialog object's child.

When the TDialog object is ready to set or retrieve the dialog box data, it uses this same list of controls to identify the data in its transfer buffer. Inside the SetupWindow() member function of the TWindow class, the OWL code indirectly calls the Transfer() member function, which in turn calls the Transfer() member function with each of the TDialog object's child objects.

As the TWindow::Transfer() function iterates the list of child objects, it uses the return values from these calls to increment a pointer that points to the current transfer element in the transfer buffer. (By default, each control object's Transfer() member function returns the size of its corresponding transfer buffer element.)

To illustrate, consider what happens if we define a new transfer buffer to use with the TDialog object d. If we write

struct DlgBuff
{  char         edText[20];
   TListBoxData lbData;
   WORD         cbData;
}; 


and then create an instance of the DlgBuff struct buf, the TWindow::Transfer() function will advance the pointer through the transfer buffer, as shown in Figure B.


Figure B - The TWindow::Transfer() function initializes each child control from a corresponding location in the transfer buffer.

Last month, we focused our attention on transferring data to and from a list box. Briefly, let's examine how you transfer data to and from the other OWL controls.

Combo box controls

If you look at the class declaration for the TComboBox class, you'll see that it derives most of its behavior from the TListBox class we used before. However, to manage a TComboBox object, you'll use a TComboBoxData object as its transfer buffer element.

Unfortunately, the section on combo box transfer buffers in Chapter 10 of the OWL Programming Guide might lead you to believe that the member functions for the TComboBoxData class are significantly different from those for the TListBoxData class. In reality, there are only a few small differences.

For the most part, you'll use both classes the same way. The primary difference is that a list box can contain more than one selected item. In contrast, you can select only one item in a combo box.

To support multiple selections, the class TListBoxData provides a member function GetSelIndices() that returns a reference to an internal array of integers. In this array, each integer contains the index of one of the selected items in the list box.

Because it doesn't need to support multiple selections, the TComboBoxData class provides the GetSelIndex() member function instead. This function returns the integer value of the selected item's index.

By the way, the Programming Guide mistakenly lists the Selection data member of the TComboBoxData class as a char* that contains the selected item's text. Selection is actually a string object for the selected item, but you can retrieve the internal char* pointer by calling GetSelection().c_str().

Other controls

The transfer buffer elements for the remaining controls are all basic C++ types. To transfer data to and from a TEdit object, you use an array of characters that's the same size as the textLen parameter of the TEdit object's constructor. For TRadioButton or TCheckBox objects, you use a WORD (the Windows typedef for an unsigned int).

By default, TStatic, TButton, and TGroupBox objects don't take any action in response to the Transfer() function call. This is because each of their constructors calls the member function DisableTransfer(). To transfer data to one of these controls (for example, to alter the text in a TStatic control), you'll need to call the EnableTransfer() member function of the appropriate object.

Transferring complex dialog box data

Listing A: trnsbuf2.cpp

#include "owlpch.h"
#include <owl\checkbox.h>
#include <owl\combobox.h>
#include <owl\checkbox.h>
#include <owl\edit.h>
#include <stdio.h>

class TBufferApp : public TApplication{
  public:
    TBufferApp() {}
    ~TBufferApp() {}

    void InitMainWindow()
     { 
       TFrameWindow* frame = new TFrameWindow( 0, "TRNSBUF2.EXE");
       frame->AssignMenu(1);
       SetMainWindow( frame ); 
     }

    void Dialog();

    DECLARE_RESPONSE_TABLE(TBufferApp);
};

DEFINE_RESPONSE_TABLE1(TBufferApp,TApplication)
  EV_COMMAND(101,Dialog),
END_RESPONSE_TABLE;

struct MyDialogBuffer{
  char          edText[20];
  TComboBoxData lbData;
  WORD          cbData;
  char          stText[20];
};

class MyDialog : public TDialog{
 public:
  MyDialog(TWindow* parent, int resID) :
    TDialog(parent, resID), TWindow(parent)
  {
    new TEdit(this, 1100, 20);
    new TComboBox(this, 1200, 20);
    new TCheckBox(this, 1300);
    TStatic* st = new TStatic(this, 1400, 20);
    st->EnableTransfer();
  }
};
void TBufferApp::Dialog()
{
  MyDialogBuffer buf;
  strcpy(buf.edText, "Initial Text");
  buf.lbData.AddStringItem("Item 1", 800);
  buf.lbData.AddStringItem("Item 2", 433);
  buf.lbData.AddStringItem("Item 3", 223);
  buf.cbData = BF_GRAYED;
  strcpy(buf.stText, "Static Text");
  MyDialog* d =
    new MyDialog(GetMainWindow(), 1000);
  d->SetTransferBuffer(&buf);
  while(d->Execute() == IDOK)
  {
    char temp[128];
    int idx = buf.lbData.GetSelIndex();
    int itemID = (int)buf.lbData.GetItemDatas()[idx];
    const char* itemText =
      buf.lbData.GetSelection().c_str();
    sprintf(temp,
            "Text = %s \n"
            "Combo Box = %s (ID)%d item #%d \n"
            "Check Box = %d \n"
            "Static Text = %s",
            buf.edText,
            itemText,
            itemID,
            idx,
            buf.cbData,
            buf.stText);
    GetMainWindow()->MessageBox(temp, 
                                "Transfer Data");
    strcpy(buf.stText, buf.edText);
  }
  d->Destroy();
}


int
OwlMain(int, char**)
{
  return TBufferApp().Run();
}

Listing B: TRNSBUF2.RC

1000 DIALOG 19, 15, 154, 91
STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog Box"
{
 DEFPUSHBUTTON "OK", IDOK, 19, 65, 50, 14
 PUSHBUTTON "Cancel", IDCANCEL, 84, 65, 50, 14
 COMBOBOX 1200, 9, 21, 72, 38, CBS_SIMPLE | WS_TABSTOP
 EDITTEXT 1100, 9, 6, 72, 11
 CHECKBOX "Checkbox", 1300, 92, 32, 52, 12, BS_AUTOCHECKBOX | WS_TABSTOP
 LTEXT "Text", 1400, 93, 8, 54, 12
}

1 MENU
{
 POPUP "Menu"
 {
  MENUITEM "Dialog", 101
 }
}

When you finish entering the code for the resource file, choose Save from the File menu and enter TRNSBUF2.RC in the Save File As dialog box. Click OK to save the file. To let the compiler use its default module definition file, right-click on the name TRNSBUF2 [.DEF] in the project window and choose Delete Node from the pop-up menu. Click Yes when the IDE asks if you want to delete this node.

When you're ready to build and run the application, choose Run from the Debug menu. When the TRNSBUF2.EXE window appears, choose Dialog from the menu. When the dialog box appears, it should resemble the one shown in Figure A.

Now, enter NEW TEXT in the entry field, select Item 2 in the combo box, and click the Checkbox check box. When you click OK, you'll see a message box that displays a summary of the data from the transfer buffer, as shown in Figure C.


Figure C - The message box displays a summary of the transfer buffer information for each control.

When you click OK in the message box, the dialog box will reappear with the same information you entered previously, as shown in Figure D. However, you'll notice that the static text field now contains the text you entered in the entry field. This demonstrates how you can use the function EnableTransfer() to explicitly transfer text to this control.


Figure D - Each control in the dialog box contains the data from the corresponding transfer buffer elements.

Click Cancel to dismiss the dialog box. Then, double-click on the System menu icon to exit the TRNSBUF2.EXE application.

If you want to use one of the other combo box styles, you won't need to make any changes to the TRNSBUF2.CPP file, because the TComboBoxData class will work correctly with all three styles of combo boxes. To confirm this, change the combo box style in the TRNSBUF2.RC file from CBS_SIMPLE to either CBS_DROPDOWNLIST or CBS_DROPDOWN and relink the application.

Deriving from TDialog

It's not uncommon to see OWL programs derive new dialog box classes from the TDialog class, as we've shown in this example. However, unless you need to define some specific action that must occur while the user is viewing the dialog box, you should avoid overriding member functions of the TDialog class.

Particularly if you're just beginning to use the OWL TDialog and TWindow classes, you should derive new classes from TDialog merely to customize the creation of the dialog box object's child controls in the derived class constructor. If you find yourself defining member functions for a TDialog-derived class to set or retrieve control states when the dialog box is not in view, you're probably not using transfer buffers properly.

You'll notice that in the example we've shown here, we create a MyDialog object and then assign its address to a TDialog pointer. Using a TDialog pointer prevents us from calling anything other than the public member functions of the TDialog class. This is a good habit because it makes it impossible to manipulate any of the dialog box's control objects directly from the pointer.

Conclusion

Initializing dialog box controls in a traditional C application can be tedious. By using transfer buffers to manage complex dialog boxes in OWL applications, you can achieve the same results with fewer problems.