Data exchange in user interface

In C/C++ programming, we hold data in a variable and they are processed in the program. The output of the variable to the user is generally done through printf() which basically converts data type to a string and then displays it in the text console. The input from the user is taken from the console using scanf() function. These two functions primarily act as the data exchange routine in console-based C applications.

Graphical user interface-based programmings like Win32 programming using C and MFC programming with C++, cannot use the console for user interaction. So we use a graphical dialog box or form window for the interactions and exchanges. Programming variables types like a string, integer, boolean etc are exchanged to the graphical user interface for display purposes. Again when we require to take input from the user, we display dialogs, and the input is given to the user interfaces and then exchanged to the variables. This mechanism is called dialog data exchange in Win32/MFC programming practices.

Win32 Dialog Data Exchange

A dialog window is a top-level window and dialog UI controls like button, editbox, checkbox, combobox, radio buttons, list box are child windows. These child controls are identified by an control identification number or control ID.

Win32 Dialog Data Exchange

There are Win32 API to retrieve values from the UI controls. These APIs have a common format of arguments and the dialog window handle is the first argument and then the control ID. Each UI controls holds different types of information in it thus the exchange of data should be in different types of variables. Users can retrieve strings, integers, BOOL etc using these calls.

/* Get the text from EDIT control and save to string variable */
UINT GetDlgItemText(
  HWND   hDlg,
  int    nIDDlgItem,
  LPTSTR lpString,
  int    nMaxCount
);

/* Get the integer from EDIT control and save to int variable */
UINT GetDlgItemInt(
  HWND hDlg,
  int  nIDDlgItem,
  BOOL *lpTranslated,
  BOOL bSigned
);

/* Get the state from CHECK box control and save to int variable */
UINT IsDlgButtonChecked(
  HWND hDlg,
  int  nIDButton
);

The below Win32 APIs are used to apply text or set the state of the button from a variable. Here are few examples of APIs for UI controls like editbox, checkbox, radio buttons however there is a collection of APIs available for other UI controls.

/* Set the text of EDIT control using a string variable */
void SetDlgItemText(
  HWND hDlg,
  int nID,
  LPCTSTR lpszString
);

/* Set the text of EDIT control using a int variable */
void SetDlgItemInt(
  HWND hDlg,
  int nID,
  UINT nValue,
  BOOL bSigned
);

/* Set the CHECK box control using a int variable */
void CheckDlgButton(
  HWND hDlg,
  int nIDButton,
  UINT nCheck
);

/* Set the RADIO box control using a int variable */
void CheckRadioButton(
  HWND hDlg,
  int nIDFirstButton,
  int nIDLastButton,
  int nIDCheckButton
);

MFC Dialog Data Exchange

MFC applications can use these same APIs wrapped in CWnd class. These APIs often require additional parameters to exchange a single value. Calling Win32 C API or using the same APIs through CWnd class members in C++ does not make sense. MFC framework however has a managed way to exchange data and that is known as Dialog Data Exchange (DDX) in MFC framework. MFC data exchange is not an independent way however above Win32 APIs are used internally to construct these MFC data exchange macros. Let us see how to exchange data in dialog boxes and how DDX functions works in DoDataExchange.

Here is one login dialog example in a generic user-based application. This dialog has three UI elements to collect username, password, and a field to remember in future options. We have two textboxes IDC_EDITUSER and IDC_EDITPASSWORD and one checkbox IDC_CHKREMEMBER. We have taken two CString type variables named as m_strUsername and m_strPassword for handling these text fields. There is also m_bRemember member which is a BOOL type variable to exchange data between checkboxes. We implement dialog DoDataExchange() and place data exchange macros (DDX_Text, DDX_Check) inside this function.

class CStudentDlg : public CDialog
{
public :
    CStudentDlg(void);
    ~CStudentDlg(void);
   virtual void DoDataExchange(CDataExchange* pDX);
private :
    CString m_strUsername;
    CString m_strPassword;
    BOOL    m_bRemember;
};

void CStudentDlg::DoDataExchange(CDataExchange* pDX)
{
   /* Call parent function */
   CDialog::DoDataExchange(pDX);
   
   /* Exchange User name field */
   DDX_Text(pDX, IDC_EDITUSER, m_strUsername);
   
   /* Exchange Password field */
   DDX_Text(pDX, IDC_EDITPASSWORD, m_strPassword);
   
   /* Exchange remnember field */
   DDX_Check(pDX, IDC_CHKREMEMBER, m_bRemember);

};

UpdateData

We use UpdateData() function to invoke data exchange in the dialog. UpdateData() function calls DoDataExchange() abstract function and the parameter tells the direction of the data i.e. whether to update member data from UI (with TRUE) or update UI with member data(with FALSE). We need to call UpdateData(FALSE) when we want to update UI fields and call UpdateData(TRUE) when we want to update member variables from UI.

UpdateData data flow direction

mfc dodataexchange updatedata true false

CStudentDlg::CStudentDlg()
{
  m_strUsername = "user1";
  m_strPassword = "";
  m_bRemember = FALSE;
}
void OnInitDialog()
{
  /* Update UI controls with data */
  UpdateData(FALSE);
}
void OnSubmitDialog()
{
  /* Update data member from UI data */
  UpdateData(TRUE);
  
  /* Process user name, password, remeber field */
  /* check username and password from database /file */
}

DoDataExchange function

Now we know how to write a data exchange routine with DDX macros placed inside and how to call updatedata function. Let us understand how these functions work and how these functions are implemented. These will help us to understand the mechanism and this will give more insight into the framework.

UpdateData() function calls DoDataExchange() abstract function and the parameter tells the direction of the data exchange. To carry out the exchange, UpdateData sets up a CDataExchange object. It constructs the object with dialog window and the bSaveAndValidate variable from UpdateData() function. It then calls dialog class's override of CDialog's DoDataExchange member function passing the reference of the CDataExchange object as the argument. The CDataExchange object passed to UpdateData represents the context of the exchange, defining such information as the direction of the exchange. The purpose of UpdateData() function is to update member variables with the values from UI controls. This is done with an argument value of TRUE. However, a FALSE parameter to this function inverses the flow, and UI controls are updated with dialog member variables. DoDataExchange should have data exchange macro or functions and they do the actual exchange.

void CStudentDlg::UpdateData(Bool bSaveAndValidate)
{
  CDataExchange DX(this, bSaveAndValidate);
  
  DoDataExchange(&DX);
   
}
void CStudentDlg::DoDataExchange(CDataExchange* pDX)
{
  if (pDX->m_bSaveAndValidate == TRUE)
  {
    /* UI data saved to member data */
  }
  else
  {
    /* Member data transferred to UI */
  }
}
void CStudentDlg::DoDataExchange(CDataExchange* pDX)
{
   /* Call parent function */
   CDialog::DoDataExchange(pDX);
   
   /* Exchange User name field */
   DDX_Text(pDX, IDC_EDITUSER, m_strUsername);
   
}

DDX Functions

Dialog Data Exchange(DDX) function or macros are used in DoDataExchange() routine to carry out the exchange. These functions start with a DDX_ prefix and a suffix of XXX which is the type of control it uses for the exchange. They take an object reference of CDataExchange as the first parameter and the control ID as the second parameter. The suffix part and the datatype of the next parameter depend on the type of UI control it deals with. It can be a string for text control and boolean for check box and so on.

The name of these functions are self-explanatory and they are DDX_Text, DDX_Check, DDX_Radio, DDX_CBIndex, DDX_CBString, DDX_CBStringExact, DDX_Control, DDX_DateTimeCtrl, DDX_IPAddress, DDX_LBIndex, DDX_LBString, DDX_LBStringExact, DDX_ManagedControl, DDX_MonthCalCtrl, DDX_Scroll, DDX_Slider.

void DDX_MyText(CDataExchange* pDX, int nIDC, CString& value)
{
  if (pDX->m_bSaveAndValidate == TRUE)
  {
    /* UI data saved to member data */
  }
  else
  {
    /* Member data transferred to UI */
  }
}

Implement DDX_Text using Win32 API

DDX_XXX functions are the key routines to do the data exchange. Here we have written our DDX routine and placed it in DoDataExchange(). This is to show how DDX routines can be written with Win32 APIs and this also gives an idea of how MFC framework implements these DDX functions. Our DDX_MyText() function is written with the help of Win32 APIs like GetWindowText(), GetWindowTextLength(), SetWindowText(). The actual implementation of DDX_Text() in MFC framework might be different but similar logic and APIs are used.

void DDX_MyText(CDataExchange* pDX, int nIDC, CString& value)
{
  HWND hWndCtrl = pDX->PrepareCtrl(nIDC);
  /* Window to variable exchange */
  if (pDX->m_bSaveAndValidate)
  {
    int length;
    LPTSTR buffer;
    length = GetWindowTextLength(hWndCtrl);
    buffer = value.GetBufferSetLength(length + 1);
    GetWindowText(hWndCtrl, buffer, length+1);
  }
  /* Variable to Window/UI exchange */
  else
  {
    SetWindowText(hWndCtrl, (LPCTSTR)value);
  }
}
void CStudentDlg::DoDataExchange(CDataExchange* pDX)
{
   CDialog::DoDataExchange(pDX);
   DDX_MyText(pDX, IDC_EDITUSER, m_strUsername);
   DDX_MyText(pDX, IDC_EDITPASSWORD, m_strPassword);
   DDX_Check(pDX, IDC_CHKREMEMBER, m_bRemember);

}

DDX class wizard

MFC dialog class wizard can help users to add data exchange entries in DoDataExchange() function. This wizard can be opened by right click on the dialog resource.

MFC Dialog class wizard

"Add Class" button helps to add the dialog class associated with the dialog. Dialog class is generally derived from CDialog. "Add Variable" button helps to open the data exchange variable wizard.

MFC Dialog class wizard add variable

Users can specify member variable names graphically for each control in the dialog. Class wizard automatically adds DDX functions for each member-control pair in DoDataExchange().

MFC Dialog class wizard add text member variable

Here is what the class wizard dialog looks like after we fill all the variables for individual controls. The left side shows the control IDs of the dialog and the right side shows the member variables. This in short shows a summary of control IDs vs member variables side by side.

MFC Dialog class wizard control ids vs member variables

About our authors: Team EQA

Further readings

Where is WinMain() function in MFC application ?

MFC hides WinMain in its framework and includes source file on WinMain(). This explains how framework calls global CWinApp::Initinstance() from entry WinMain.

What is the utility of CWinApp class?

This is constructed during global C++ objects are constructed and is already available when Windows calls the WinMain function, which is supplied by the ...

Basic steps in Win32 GUI Application with source code.

Define a custom Window class structure, Register the class name, CreateWindow, Show windows and write message get and dispatch loop statements. Define the Window CallBack procedure and write the handlers.

What is a Window CallBack procedure and what is its utility?

DispatchMessage() is a API which indirectly triggers the Window CallBack procedure. Message structure members from this function are passed to the CallBack procedure. CallBack procedure should implement event handlers depending on the need of the application.

What are LPARAM and WPARAM in window proc function?

LPARAM and WPARAM are the two parameters in Window CallBack procedure. They signifies parameters of various events. They are used in handing individual events.

What are the basic steps of a typical MFC based application?

We need to write WinMain and need to follow all these in a Win32 application. However we need not to write much if we are writing an application with MFC ...

#