Documentation: DataTargets

| Documentation: DataTargets [Remove Frame]

A common application of GUI development is the collection of inputs from the user in the form of booleans, numbers and strings of text. This usually involves building a dialog panel with a collection of controls such as text fields, sliders, and other input controls. The GUI developer then would write callback handler functions so as to be notified when any of these controls changes value. Often, the developer is forced to fill up these controls to reflect the initial suggested values.

This process is common enough that we have found that it makes sense to support it with a much more declarative programming style:- instead of writing many callback routines, and routines to preset the GUI controls with their initial value, we have implemented a more easy to use method in the form of the FOX FXDataTarget class.

The FXDataTarget acts as an intermediary between a program variable, such as an integer or string, and a FOX widget such as a FXTextField or FXSlider. The FXDataTarget works by associating a variable in the application code with one or more control widgets in the GUI. Several controls may be connected to the same data target, although each data target is associated with only one variable at a time.

A FXDataTarget forms a bi-directional channel through which a GUI control can communicate with a variable in an application program. Thus, once the GUI is created and connected via the FXDataTarget, the GUI controls will automatically display the current value of that variable, and when the user starts interacting with a control, the variable will be automatically changed. Moreover, if several GUI controls are connected to one single FXDataTarget, each of these controls will automatically update when the variable has been changed.

Example Usage

Suppose you are writing a program to enter personnel data by means of a dialog. One or the first things you may want to do is to design a data structure which is to hold this information:

    // Employee record
    struct Employee {
      FXString name;
      FXint    number;
      FXdouble salary;
      FXString address;
      };

Lets make a dialog box which obtains this information from the user, and of course we'll use the FXDataTargets to eliminate as much coding as possible; first comes the header file (we'll omit some details in the interest of brevity):

    /* EmployeeEntry.h */

    // Employee Information Entry Dialog
    class EmployeeEntry : public FXDialogBox {
      FXDECLARE(EmployeeEntry)
    public:
      Employee record;   // Record we'll be modifying
    private:
      FXDataTarget nameTarget;
      FXDataTarget numberTarget;
      FXDataTarget salaryTarget;
      FXDataTarget addressTarget;
    public:
      EmployeeEntry(FXWindow* owner);
      };

Well, that's basically it. Now for the implementation file:

    /* EmployeeEntry.cpp */
    #include "EmployeeEntry.h"

    FXIMPLEMENT(EmployeeEntry,FXDialogBox,NULL,0)

    EmployeeEntry::EmployeeEntry(FXWindow* owner):
      FXDialogBox(owner,"Enter Employee Information"),
      nameTarget(record.name),
      numberTarget(record.number),
      salaryTarget(record.salary),
      addressTarget(record.address){

      ...
      new FXTextField(frame,5,&nameTarget,FXDataTarget::ID_VALUE,...);
      new FXSpinner(frame,5,&numberTarget_target,FXDataTarget::ID_VALUE,...);
      new FXTextField(frame,5,&addressTarget,FXDataTarget::ID_VALUE,...);
      new FXSlider(frame,&salaryTarget,FXDataTarget::ID_VALUE,...);
      new FXTextField(frame,5,&salaryTarget,FXDataTarget::ID_VALUE,...);
      ...
      new FXButton(frame,"Cancal",NULL,this,FXDialogBox::ID_CANCEL,...);
      new FXButton(frame,"Accept",NULL,this,FXDialogBox::ID_ACCEPT,...);
      }

We have of course omitted some details here regarding the layout and other visual paraphernalia. Note that we have connected the data target controlling the salary member to both a text field as well as a slider, so we can set the salary either way. So far, it sounds rediculously simple, doesn't it? The secret is, it really is! Next, we're getting ready to use this new panel. Here's how we would do that. Lets say we have selected the employee from a big array of employee records, and we enter the following callback handler to edit one of the entries in this array:

    /* EmployeeDatabase.cpp */
    #include "EmployeeEntry.h"

      ...

    Employee *employeedatabase;     // Database of records
    int       currentemployee;      // Current employee number we're going to edit
    int       numberofemployees;    // Total number

      ...

    long EmployeeDatabase::onCmdChangeInformation(FXObject*,FXSelector,void*){
      EmployeeEntry dialog(mainwindow);
      dialog.record=employeedatabase[currentemployee];    // We will work on the copy
      if(dialog.execute()){
        employeedatabase[currentemployee]=dialog.record;  // We accept the change
        }
      return 1;
      }

In the above code, we copy the employee record from the database, and then pop up the EmployeeEntry dialog by calling its execute() member function. When this dialog shows up, it will initially display the old information from `employeedatabase[currentemployee]`. After editing it with the dialog, if the user hits the Accept button, execute() will return true and we copy the changed record back into the database; if the user hit the Cancel button, execute() we will simply do nothing and return from the callback. The EmployeeEntry dialog's destructors will automatically clean up the mess.

More Advanced Usage

The above shows the most basic usage of FXDataTarget. It uses the ID_VALUE message. When a FXDataTarget receives this message, it will ask the sender of the message for the desired value, and then place that value into the connected program variable.
However, the FXDataTarget also understands the ID_OPTION messages. With these messages, the actual value is encoded in the message ID itself, by adding the value to the message ID.

For example, to set a program variable to the value 10, we could send the FXDataTarget a message ID_OPTION+10. With the ID_OPTION messages we can for example connect a FXDataTarget to a number of FXRadioButtons, and set a program variable by clicking on one of several radio buttons:

      enum Color {Red, Green, Blue};

      FXint color;
      FXDataTarget colorTarget(color);
      color=Red;

      new FXRadioButton(matrix,"Red",option_target,FXDataTarget::ID_OPTION+Red,...);
      new FXRadioButton(matrix,"Green",option_target,FXDataTarget::ID_OPTION+Green,...);
      new FXRadioButton(matrix,"Blue",option_target,FXDataTarget::ID_OPTION+Blue,...);

Here we set the variable color to one of the three values {Red, Green, Blue} by directly connecting the FXDataTarget to three FXRadioButtons.
When the FXDataTarget receives an ID_OPTION message, it changes the program variable to (message-ID_OPTION). Using this method, its easy to input yes/no values, lists of choices, and so on, all without having to write explicit callback handlers.

How It Works

The FXDataTarget idea works because of FOX's built-in GUI Update mechanism. The GUI Update mechanism is responsible for refreshing the state of each widget in your program, based on the state of your application data structures. Basically, each widget periodically inquires about its state by sending its target a SEL_UPDATE message.

When an FXDataTarget receives a message of type SEL_UPDATE, it reads the value of its associated variable and updates the sender of the message by means of another message. Note that the FXDataTarget does not need to know what type of Widget did the sending.

There are two ways the FXDataTarget can receive updates: by an ID_VALUE message or an ID_OPTION message. The former type is usually generated by a valuator control, such as an FXSlider. When a message from a valuator control is received, FXDataTarget responds by sending back an ID_SETINTVALUE, ID_SETREALVALUE, or ID_SETSTRINGVALUE message to the sender of the request.

When a FXDataTarget receives a message of the form (ID_OPTION+i), it resonds by sending back one of two messages: ID_CHECK or ID_UNCHECK, depending on whether the value of the associated variable is equal to i.

In the example above, the "Red" radio button will receive an ID_CHECK message, because the initial value of the variable color is red. The other radio buttons will receive the ID_UNCHECK message.

All of these methods explained here are deployed in the datatarget example program.

Copyright © 1997-2005 Jeroen van der Zijp