Extending DDX/DDV for a Wizard Sheet

One of cool things that MFC provides is a DDX/DDV mechanism. Variables are associated with controls transparently. It probably can be thought as the easiest way to use the dialog box. But things were changed a little bit after the Property Sheet was introduced. I'd like to explain what has been changed, and what I did to adapt it. This article focuses on the "Wizard Sheet," but as you know, the wizard sheet is a special kind of property sheet, and the same technique will fit a normal property sheet too. I list the class sketch here; for the complete declarations, please refer to the source code.

class CWizardPage : public CPropertyPageEx
  virtual BOOL OnSetActive();
  virtual LRESULT OnWizardNext();
  virtual LRESULT OnWizardBack();
      virtual BOOL OnWizardFinish();
  virtual BOOL OnNotify(WPARAM wParam, LPARAM lParam,
                                       LRESULT* pResult);
  CWizardSheet *m_pps;
  virtual void DoDataExchange(CDataExchange* pDX);
          // DDX/DDV support
  friend class CWizardSheet;


class CWizardSheet : public CPropertySheetEx

  void AddPage( CWizardPage *pPage );

First of all, the question is, "Can DDX/DDV be used on a Wizard Sheet?" The answer is "Yes!" A Wizard Sheet just is a set of dialog boxes. (Check the CPropertyPage class; it directly inherits from CDialog, so there is nothing to prevent us from using the DDX/DDV.) But the problem is that each variable should be added into the CPropertyPage separately as a member variable. This means that we cannot use the wizard pages array but need to declare each individual class for each dialog box, even though the only difference is the associated member variables. Another drawback is that we must also collect all of the information from many classes.

  CWizardSheet theSheet(...);
  CWizardPage1 page1(...);
  CWizardPage2 page2(...);
  CWizardPage3 page3(...);


  {//Get all values...
   func1(page1.v1, page3.v2, page2.v1...) ;

It gets worse when there are many pages. Honestly, when I'm using a wizard sheet, I just think of it as a single object to collect some information from users, and I don't want to separate it over five or six pages. Instead of using a dialog box, I use a wizard sheet; it's just because I'd like people to think me a polite and friendly guy. CWizardSheet is the right place to get all of this information and not all of those individual pages. So, the first thing I did is here:

  void CWizardPage::DoDataExchange(CDataExchange* pDX)

Aha, the page does nothing; all data exchange jobs are delegated to the Sheet. But how does the sheet know which variable is associated with which control? To hard-code the relationship isn't a good solution. Because we don't know how many pages and how many controls will be adopted by this sheet, I decided to design a generic mechanism to bind the control and a variable pair at run time.

  struct DDX_MAP_ENTRY
    CWnd* pDlgWnd;
    int nIDCtrl;
    CTRLTYPE nTypeCtrl;
    void *pVariable;

No doubt, we need nIDCtrl to represent the control and pVariable for the variable. nTypeCtrl was used to indicate the control and variable type. But why do we need pDlgWnd? We know each control's resource ID in a dialog box is unique, but this is not true when it's used across a couple of dialog boxes, so "pDlgWnd" is necessary. For the convenience, I defined some macros here:

  #define DECLARE_DDX_MAP() \
  public: \
    const DDX_MAP_ENTRY *_pddxEntries; \
    virtual void DoDataExchange(CDataExchange* pDX); \

  #define BEGIN_DDX_MAP(theMap) \
    const DDX_MAP_ENTRY theMap##_ddxEntries[] = \
    { \

  #define END_DDX_MAP() \
      {NULL, 0, None, NULL} \
    }; \

  #define DDX_ENTRY(winDlg, idCtrl, typeCtrl, varObj) \
      {&winDlg, idCtrl, typeCtrl, &varObj}, \

  #define ASSOCIATE_DDX_MAP(theClassInst,theMap) \
    theClassInst._pddxEntries = theMap##_ddxEntries;

The usage will be like this:

        DDX_ENTRY(MakeCertPages[0], IDC_LABEL, Edit, strLabel)
        DDX_ENTRY(MakeCertPages[0], 1000, CheckBox, email)
        DDX_ENTRY(MakeCertPages[0], 1001, CheckBox, file)
        DDX_ENTRY(MakeCertPages[2], IDC_DTPICKER_END,
                                    DateTimePicker, TimeEnd)

Now it's the time to finish the CWizardSheet::DoDataExchange(CDataExchange* pDX).

void CWizardSheet::DoDataExchange(CDataExchange* pDX)
    const DDX_MAP_ENTRY *_pddxEntries = this->_pddxEntries;
if( !_pddxEntries ) return;

while( _pddxEntries->nIDCtrl )
  if( pDX->m_pDlgWnd == _pddxEntries->pDlgWnd)
    switch (_pddxEntries->nTypeCtrl)
      case Edit:
        DDX_Text(pDX, _pddxEntries->nIDCtrl,
      case CheckBox:
          DDX_Check(pDX, _pddxEntries->nIDCtrl,
      case RadioBox:
          DDX_Radio(pDX, _pddxEntries->nIDCtrl,
      case ListBoxIdx:
          DDX_LBIndex(pDX, _pddxEntries->nIDCtrl,
      case ComboBoxIdx:
          DDX_CBIndex(pDX, _pddxEntries->nIDCtrl,
      case ListBoxTxt:
          DDX_LBString(pDX, _pddxEntries->nIDCtrl,
      case ComboBoxTxt:
          DDX_CBString(pDX, _pddxEntries->nIDCtrl,
      case ScrollBar:
          DDX_Scroll(pDX, _pddxEntries->nIDCtrl,
      case Slider:
          DDX_Slider(pDX, _pddxEntries->nIDCtrl,
      case MonthCalCtrl:
          DDX_MonthCalCtrl(pDX, _pddxEntries->nIDCtrl,
      case DateTimeCtrl:
          DDX_DateTimeCtrl(pDX, _pddxEntries->nIDCtrl,

Things seem done for the most part, but wait a minute; there's a special "DDX_" function in MFC. It's called "DDX_Control." MFC uses it to transfer the data between a subclassed control and a CWnd instance. If we are using an activeX control, we will need it. For example, if I want to use the "MS Date and Time Picker," I need to associate it with a CDTPicker (inherits from CWnd) instance. So, is what I need do just like this?

      case DateTimePicker:
        DDX_Control(pDX, _pddxEntries->nIDCtrl,
                    *( CDTPicker*)_pddxEntries->pVariable);

Not at all! What "DDX_Control" does is just to subclass a control; this means you can operate the control only when the control is alive. Once the sheet was closed, you cannot get any data from the CDTPicker! I like the "MS DT Picker" because it looks cool, but all I need is that it gives me a CTime just like the normal DateTimeCtrl does. So, I declared:

  struct CDTPickerEx : public CDTPicker, public CTime

and added these into CWizardSheet::DoDataExchange:

      case DateTimePicker:
        CDTPicker& picker=static_cast<CDTPicker&>
        CTime& timeVal= static_cast<CTime&>(
        DDX_Control(pDX, _pddxEntries->nIDCtrl, picker);

            timeVal = CTime(picker.GetYear().intVal,
           //We did not support it yet!


It's the end of my article, but not the end of the story. There are many things we can improve in the DDX/DDV; the Mediator pattern could be a good hint. But, after all, we should remember it's the lazy guys (not the smart guys) who are improving the world.


Download demo project - 77 Kb


  • What if we have just one page in sheet?

    Posted by Legacy on 12/24/2002 12:00am

    Originally posted by: Ivan Nevraev

    BOOL CWizardPage::OnSetActive()

    int act = m_pps->GetActiveIndex();
    int count= m_pps->GetPageCount();

    if( act == 0 && count == 1 )
    else if(act==0)
    else if(act+1==count)
    m_pps->SetWizardButtons(PSWIZB_BACK | PSWIZB_FINISH);
    m_pps->SetWizardButtons(PSWIZB_BACK | PSWIZB_NEXT);

    return CPropertyPageEx::OnSetActive();

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

Top White Papers and Webcasts

  • The impact of a data loss event can be significant. Real-time data is essential to remaining competitive. Many companies can no longer afford to rely on a truck arriving each day to take backup tapes offsite. For most companies, a cloud backup and recovery solution will eliminate, or significantly reduce, IT resources related to the mundane task of backup and allow your resources to be redeployed to more strategic projects. The cloud - can now be comfortable for you – with 100% recovery from anywhere all …

  • You probably have several goals for your patient portal of choice. Is "community" one of them? With a bevy of vendors offering portal solutions, it can be challenging for a hospital to know where to start. Fortunately, YourCareCommunity helps ease the decision-making process. Read this white paper to learn more. "3 Ways Clinicians can Leverage a Patient Portal to Craft a Healthcare Community" is a published document owned by www.medhost.com

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds