Using DDX and DDV with WTL


Click here for larger image

Environment: VC6 SP4, Win2K, Win98/ME

Introduction

One of the more tedious parts of UI coding is simply doing the grunt work of passing member data into various dialogs for user interaction, and then pulling it back out and ensuring the data is valid before recommitting it to the core part of your program. MFC realized this, and has long had the concept of DDX (dynamic data exchange) and DDV (dynamic data validation). However, those of us who are focused on download size stick to ATL and SDK style coding, meaning lots of rote code has to be done to enable the user to modify various properties and settings in our UI dialogs. With WTL, however, we now have the opportunity to gain a lot of the ease of MFC coding without the bulk. This article will show you how to make use of WTLs DDX/DDV implementation, show two custom extensions I added to WTLs implementation to extends its reach, and provide code taken from a real-life example using WTLs property sheet implementation (CPropertyPageImpl).

What is DDX/DDV exactly?

As alluded to earlier, the purpose of DDX is to provide a framework for handling the passing (both loading and retrieving) of data between your application and the UI it presents to the user via dialogs, property sheets, etc. The goal of DDV is to enable automatic verification of any changes the user made to ensure they have entered valid data, where valid is defined by your application. The benefit of using DDX is efficient, easy to read and maintain mappings between your applications variables and their presentation/modification in your various dialogs. Additionally, you will see a significant reduction in the amount of time you spend coding the basic infrastructure needed to support interactive UI.

With the overview out of the way, lets get into coding to start explaining the details involved in utilizing DDX in your app. WTL provides DDX/DDV handling via the header atlddx.h, which contains macros for creating a DDX map (same concept as ATLs message map), and the templatized class CWinDataExchange.

Thus the first step to make use of DDX/DDV is

#include <ATLddx.h>

into your StdAfx.h or other primary header file. The one caveat here, is that if you are using WTLs Cstring, you must include AtlMisc.h before AtlDDx.h.

Thus, I always simply add the following to my StdAfx.h
#include <atlmisc.h> //CString support
#include <atlddx.h>
in StdAfx.h, right beneath atlwin.h and forget about it. Because the sample code in this article will also use property sheets to demonstrate ddx, we have to add the include for atldlgs.h, giving us a final StdAfx.h that has this segment of code in it:
...
#include <atlbase.h>
#include <atlapp.h>

extern CAppModule _Module;

#include <atlwin.h>

#include <atlmisc.h>
#include <atlddx.h> 
#include <atldlgs.h>
...

The next step is to ensure the dialog class you are using inherits from CWinDataExchange to get support for DDX like so:

class CCameraBase : public CPropertyPageImpl<CCameraBase>,
                    public CWinDataExchange<CCameraBase>
 

After that, we can now get to the heart of it, which is actually connecting your applications variables to their respective UI components. This is done via the DDX message map, of which a simplistic example is listed below:

BEGIN_DDX_MAP(<your dialog class>)
DDX_TEXT(<dlg resource id>,<string variable>)
DDX_TEXT_LEN(<dlg resource id2>,
             <string variable2>, 
             <max text length>)
END_DDX_MAP()

Within the message map is where you direct WTLs DDX class how to hook up your variables to your dialogs UI elements. There are a number of macros that can be used for various data types, but lets briefly look at the two entries above. The first one DDX_TEXT, simply states that the value in <string variable> should be assigned to <dlg resource id> on load typically a string variable mapping to an edit box.

On the close of the dialog, the mapping is done in reverse the current contents of <dlg resource id> are pulled out and placed back into <string variable>. Very nice and tidy. The second macro shown above, DDX_TEXT_LEN has similar functionality to DDX_TEXT in that it joins the <dlg resource id2> to <string variable2> but you can see there is a third parameter, <max text length>. Specify a value here, and if the users text entry exceeds it, the DDV error handler will kick in. You can override the default handler, or you can use the default handler, which will beep, and set the focus back to the offending control to prompt the user to correct it. (You implement your own handler by overriding the function void OnDataValidateError(UINT id, BOOL bSave,_XData& data), which is demonstrated in the sample app).

There are a number of macros for the message map, usually with a variation of one for pure linkage (ala DDX_TEXT) and one for linkage and validation (ala DDX_TEXT_LEN). See summary at the end of this article.

With that, the final step is to actually tell WTL when to fire the actual data exchange. This is done by calling DoDataExchange(BOOL fParam) with FALSE to load the dialog with your data values, and TRUE to retrieve the data from your dialog. Where to do this is up to you, but OnInitDialog (handler for WM_INITDIALOG) is a good spot for the DoDataExchange(FALSE), or load call. For pulling back the modified data, you could put the DoDataExchange(TRUE) call (retrieve) in OnOK for a dialog, and for property pages, you probably want to handle it in OnKillActivate().

DDX in Action

With an understanding of the fundamentals involved in utilizing DDX/DDV, lets move on to the sample application, where we can see DDX in action, as well as examine some limitations I found and how to work around them.

The sample application is based off of a subset of code from a commercial application that handles the viewing/monitoring of multiple wireless cameras. The relevant part of this app is that it has to allow the user to specify individual settings for up to 16 cameras and do so in a clean UI. The solution I chose was to use WTLs property page implementation to allow a nice tabbed dialog for each camera, and then use DDX/DDV to simplify the transfer back and forth of the individual settings. The sample app simply lets you play with four settings, so that you can see in the debugger how the settings are transferred and validated. Ill give an overview here of some of the more interesting parts, and after that, just tracing the code in the debugger should cement your understanding of DDX/DDV.

Because the camera settings were going to be globally used throughout the app, I made a global struct as follows (from stdafx.h):

struct _cameraprops
{
 CString ssFriendlyName;
 UINT iHouseCode;
 UINT iUnitCode;
 CString ssSaveDir;
 BOOL fIsInSnapshotCycle;
 BOOL fAddTimestamp;
};

extern _cameraprops g_cameraProps[4]; 
and allocated storage in the main cpp file (propsheetddx.cpp):
#include "maindlg.h"

CAppModule _Module;

_cameraprops g_cameraProps[4];

With our central storage structure setup, the next step was to create the header for handling the property sheet and create the base class for the property page handler.

class CCameraBase : public CPropertyPageImpl<CameraBas>,
                    public CWinDataExchange<CameraBas>

I then created a single dialog in VC, which looked like so:


Click here for larger image

Next, I added the DDX message map to specify the linkage between the UI and the global struct g_cameraprops:

BEGIN_DDX_MAP(CCameraBase)
DDX_TEXT_LEN(IDC_edit_CameraTitle,
             g_cameraProps[m_iIdentity].ssFriendlyName, 
             35)
DDX_COMBO_INDEX(IDC_cmbo_HouseCode, 
                g_cameraProps[m_iIdentity].iHouseCode)
DDX_COMBO_INDEX(IDC_cmbo_UnitCode, 
                g_cameraProps[m_iIdentity].iUnitCode)
DDX_TEXT(IDC_edit_FileDirectory,
         g_cameraProps[m_iIdentity].ssSaveDir)
DDX_BOOL_RADIO(IDC_radio_AddTimeStamp, 
               g_cameraProps[m_iIdentity].fAddTimestamp,
               IDC_radio_NoTimeStamp)
END_DDX_MAP() 

enum { IDD = IDD_PROP_PAGE1 };
and of course we had to invoke the DoDataExchange for load...
LRESULT OnInitDialog(...)
{
...
InitComboBoxes(hwndComboHouse, hwndComboUnit, m_iIdentity);
CenterWindow();

DoDataExchange(FALSE);
...
}

and for validating and pulling the modified data back:

BOOL OnKillActive()
{
 DoDataExchange(TRUE);
 return true;  
} 
Since we have to scale up to 16 cameras (four in the sample though), I modified the constructor of the CcameraBase class to take an integer to identify what camera it was handling:
CCameraBase(int _index) 	
{
 m_iIdentity = _index;
 ...
}

With that done, we now have a basic layout for a property page, a framework for transferring the data and pulling it back after the user has modified it, and an index to allow us to reuse the same class for all cameras. Now, to actually implement this 4x in our UI, we turn to our derived CpropertySheetImpl class, CcameraProperties. There isnt too much to it as shown below:

class CCameraProperties : public CPropertySheetImpl<CameraPropertie>
{
public:
 CCameraBase m_page1;
 CCameraBase m_page2;
 CCameraBase m_page3;
 CCameraBase m_page4;

 CCameraProperties():m_page1(1),m_page2(2),m_page3(3),m_page4(4)
 {
  m_psh.dwFlags |= PSH_NOAPPLYNOW;

 AddPage(m_page1);
 AddPage(m_page2);
 AddPage(m_page3);
 AddPage(m_page4);
 SetActivePage(0);

 SetTitle(_T("Camera and Video Input Properties"));
}

Note the use of the member initialization list above in bold to show where we actually id each of the class instances with their respective camera. After that, call AddPage to hook up the classes into the tabbed layout, and specify your first page via SetActivePage. There is a small message map omitted above, but beyond that, you now have the handler for the full property sheet.

Extending DDX in WTL

Of course, things didnt all just fall into place there were two immediate problems I hit that required adding new extensions to to get the DDX framework to do some additional handling. The first item was regarding the combo boxes the generic ddx implementation would be to specify DDX_INT and pass in the combo box id to handle the mapping of the cameras unit code & house code to their respective combo box. However, the gotcha is that DDX_INT calls Get/SetDlgItemInt underneath, which merely places or retrieves the textual representation of an integer. For the cameras, which use X10 wireless protocol, both the unit code and house code represented an index into an array of values, not the values themselves.example: house code A is represented by 0, B is 1, etc. Under the default DDX implementation, if I passed in its intrinsic value 0, I would get a combo box showing 0, which is not what I wanted.

Since I think its very common to have combo boxes representing indexes into arrays or enums rather than trying to represent the literal textual value, I added a new macro and macro handler called DDX_COMBO_INDEX. This will handle the passing and retrieval of an index value, rather than the literal textual translation. The sample has the modified code, but it ended up like this:

#define DDX_COMBO_INDEX(nID, var) \
 if(nCtlID == (UINT)-1 || nCtlID == nID) \
 { \
  if(!DDX_Combo_Index(nID, var, TRUE, bSaveAndValidate)) \
   return FALSE; \
 }

followed by:

template <class Type>
BOOL DDX_Combo_Index(UINT nID, 
                     Type& nVal, 
                     BOOL bSigned, 
                     BOOL bSave, 
                     BOOL bValidate = FALSE, 
                     Type nMin = 0, 
                     Type nMax = 0)
{
 T* pT = static_cast<>(this);
 BOOL bSuccess = TRUE;

 if(bSave)
 {
  nVal = ::SendMessage((HWND) (Type)pT->GetDlgItem(nID),
                       CB_GETCURSEL,
                       (WPARAM) 0,
                       (LPARAM) 0);
  bSuccess = (nVal == CB_ERR ? false : true);	
 }
 else
 {
  ATLASSERT(!bValidate || nVal >= nMin && nVal <= nMax);
  int iRet = 	::SendMessage((HWND) (Type)pT->GetDlgItem(nID),
                            CB_SETCURSEL,
                            (WPARAM) nVal,
                            (LPARAM) 0);

  bSuccess = (iRet == CB_ERR ? false : true);
 }
	
 if(!bSuccess)
 {
  pT->OnDataExchangeError(nID, bSave);
 }
 else if(bSave && bValidate)	// validation
 {
  ATLASSERT(nMin != nMax);
  if(nVal < nMin || nVal > nMax)
  {
   _XData data;
   data.nDataType = ddxDataInt;
   data.intData.nVal = (long)nVal;
   data.intData.nMin = (long)nMin;
   data.intData.nMax = (long)nMax;
   pT->OnDataValidateError(nID, bSave, data);
   bSuccess = FALSE;
  }
 }
 return bSuccess;
}    

With this, I could successfully handle indexes within my combo box UI. The other gotcha was that the UI I wanted to use was to have two radio buttons, representing a true/false choice for the user (see the option #4, do you wish to have timestamps added). Initially I thought DDX_RADIO was what I wanted, but that didnt do it, and neither did DDX_CHECK (didnt handle the toggling of the UI to create an exclusive selection between the two radio buttons). Thus, I added another extension, DDX_BOOL_RADIO, which took two resource ids as follows:

DDX_BOOL_RADIO(<primary radio buttonID>, 
               <OOL variable>, 
               <econdary radio buttonID>)

What this extension does is ensure that in the load, only one of the two buttons is selected, based on the state of the bool variable. If true, the primary ID radio is checked, the secondary radio button is initialized to unchecked, and the reverse if the initial load value is false. Obviously, you could simply use one radio button to represent the true/false state, but I wanted to make it really clear to the user what they were selecting by explicitly calling it out with two buttons and associated text. You can also find the code for DDX_BOOL_BUTTON in the modified file.

With that, the basic code framework for the sample application is complete we now have a UI that can elegantly handle the transfer back and forth of data between UI and internal variables via WTLs DDX/DDV, allow the user to understand what they are selecting via a tabbed property dialog, and finally, we have a codebase that can scale too any number of cameras with minimal change to the code.

Hopefully, a quick run of the sample app under the debugger will clear up any remaining questions, and you will now be able to take advantage of WTLs DDX/DDV framework to ensure you no longer have to keep writing reams of rote GetDlgItem/SetDlgItem style code for your future UI.

If you have recommendations for improving this article, or write your own extensions to atlddx.h, I would appreciate hearing about them. You can email me at less_wright@hotmail.com



Default Data Handlers

Heres the list of default DDX/DDV handlers:

DDX_TEXT(nID, var)
DDX_TEXT_LEN(nID, var, len)

DDX_INT(nID, var)
DDX_INT_RANGE(nID, var, min, max)

DDX_UINT(nID, var)
DDX_UINT_RANGE(nID, var, min, max)

DDX_FLOAT(nID, var)
DDX_FLOAT_RANGE(nID, var, min, max)

// NOTE: you must define _ATL_USE_DDX_FLOAT to 
//       exchange float values.

DDX_CONTROL(nID, obj)
DDX_CHECK(nID, var)
DDX_RADIO(nID, var)	 

Downloads

Download demo project - 35 Kb


Comments

  • Someone used DDX_CONTROL to subclass a control ???

    Posted by Legacy on 12/18/2001 12:00am

    Originally posted by: Marco F

    I don't get it working !
    I tryed to subclass a control thats nested in a Dialog by subclassing a controls Class (e.G. a ListViewCtrl as:

    CMyListViewCtrl : public CWindowImpl< CMyListViewCtrl , CListViewCtrl, WinTraitsOR<WS_CHILD|WS_VISIBLE|LVS_REPORT|LVS_OWNERDATA, WS_EX_CLIENTEDGE> >)

    and

    DECLARE_WND_SUPERCLASS(NULL, CListViewCtrl::GetWndClassName())

    and "Connect" it to the existing ListViewControl with DDX_CONTROL( IDC_MYLIST, CMyListViewCtrl ). The Messages are not mapped to the Message Handling Macros in CMyListViewControl.

    That's the way it's done by class wizard when using MFC.
    What did I wrong ? Any ideas ?

    I don't want to implement it as contained window, because: (1) I have to do the creation "by hand" at runtime,
    (2) I need the class in some more dialogs and don't want to "copy" the message handlers into each dialog ...

    Reply
  • Thank you very much!!

    Posted by Legacy on 09/09/2001 12:00am

    Originally posted by: Jake Jun

    .

    Reply
  • Thanks!

    Posted by Legacy on 08/21/2001 12:00am

    Originally posted by: Alexander

    Your extensions are relevanr and cool

    Reply
  • what is atlddx.h

    Posted by Legacy on 05/09/2001 12:00am

    Originally posted by: Alex

    What is a atlddx.h file and where can I find it.
    In my VisualStudio 6.0 Enterprise edition there is not.
    Furthemore on the msdn.microsoft.com there is not information about this file
    Pls explain .

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: May 6, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT While you likely have very good reasons for remaining on WinXP after end of support -- an estimated 20-30% of worldwide devices still are -- the bottom line is your security risk is now significant. In the absence of security patches, attackers will certainly turn their attention to this new opportunity. Join Lumension Vice President Paul Zimski in this one-hour webcast to discuss risk and, more importantly, 5 pragmatic risk mitigation techniques …

  • The latest release of SugarCRM's flagship product gives users new tools to build extraordinary customer relationships. Read an in-depth analysis of SugarCRM's enhanced ability to help companies execute their customer-facing initiatives from Ovum, a leading technology research firm.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds