C++ – MFC: child dialog behavior

cdialogmfcvisual c++

I'm trying to make my child dialog box to be created as a member of the main application class as follows:

class ParentWindow : public CWinApp
{
public:
    // Other MFC and user-implemented classes before this line
    MiscSettings    activeMiscSettings;

public:
    ParentWindow();
    ~ParentWindow();

// Overrides
    virtual BOOL InitInstance();

// Implementation
    afx_msg void OnAppAbout();
    afx_msg void OnMiscSettingsPrompt();
    DECLARE_MESSAGE_MAP()
};

I would like to have the dialog box described by MiscSettings to be instantiated when the program starts up, destructed when the program exits, and show/hide according to whether the user select a particular menu option vs. the user clicking a "OK" or "Cancel" button of the dialog box. However, when I implemented the OnMiscSettingsPrompt() handler function as follows:

void ParentWindow::OnMiscSettingsPrompt()
{
    float temp;
    INT_PTR status = activeMiscSettings.DoModal();
    switch(status)
    {
    case IDOK:
        temp = activeMiscSettings.GetSpeed();
        break;
    case IDCANCEL:
    default:
        break;
    }
}

I cannot access activeMiscSettings.GetSpeed() method b/c the handle is invalid after the DoModal() call. I used this method similar to other examples on showing child dialog boxes. However, the contents of activeMiscSettings were not accessible by ParentWindow class. I know I can put handlers in MiscSettings class to transfer the contents properly in the OK button handler of the edit control and other user control settings to the appropriate class contents of the rest of the application. At this point, I'm not sure what would be the cleanest way of transferring the settings on the child popup dialog to the rest of the application.

Another specification that I am trying to achieve is to have the misc. settings pop-up dialog to show pre-configured settings when it first appears when the user selected the menu option for the first time. After changing some settings and pressing ok, if the user opens the settings window again, I would like to have the current settings show up in the user controls rather than showing the preconfigured settings previously seen in the very first instance. Is this an easily achievable goal?

Thanks in advance for the comments.

Best Answer

You can achieve what you want with a modeless dialog, though it's a bit strange: - call the dialogs Create in ParentWindow.OnCreate. Pass ParentWindow as parent - the dialog needs to be created invisibly, IIRC you need to override CMyDialog::PreCreateWindow for that - to open the dialog, use dlg.ShowWindow(SW_SHOW) and parent.EnableWindow(false) - to close, use dlg.ShowWindow(SW_HIDE) and parent.EnableWindow(true)

However, I'd advice against that.

  • It doesn't even attempt to separate view from controller, but that might be forgivable.
  • It binds the dialog to one parent, i.e. it can't be shown from another window.
  • It doesn't allow to implement "Cancel" correctly
  • Last not least, it feels very strange - which might be a code smell or a matter of taste.

Here's what I'd consider "normal":

All my settings dialogs are associated with a Settings class, and end up following roughly the following interface:

class CSettings
{
   double speed;
   EDirection direction;
   bool hasSpeedStripes;

   bool IsValid(CString & diagnostics);
};

class CSettingsDialog
{
   CSettings m_currentSettings;
public:
   // that's the method to call!
   bool Edit(CWnd * parent, CSettings & settings)
   {
      m_currentSettings = settings; // create copy for modification
      if (DoModal(parent) != IDOK)
        return false;
      settings = m_currentSettings;
      return true;
   }

   OnInitDialog()
   {
     // copy m_cuirrentSettings to user controls
   }

   OnOK()
   {
     // copy user controls to m_currentSettings
     CString diagnostics;
     if (!m_currentSettings.IsValid(diagnostics))
     {
       MessageBox(diagnostics); // or rather, a non-modal error display
       return;
     }
     EndDialog(IDOK);
   }
};

The copy is necessary for the validate. I use the settings class for the "currentSettings" again, since I am not much in favor of MFC's DDX/UpdateData() mechanism, and often do the transfer manually.

However, if you follow MFC's ideas, you would

  • use class wizard to map the controls to data members, where you can do basic range validation
  • In OnInitDialog, copy the settings to the data members and call UpdateData(false)
  • In OnOK, call UpdateData(true), and "return" the data members.

You could even manually edit the DoDataExchange to map the controls directly to m_currentSettings members, but that doesn't work always.


A interdependency validation should be done on the copy since the user might change the values, see that the new values aren't ok, and then press cancel, expecting the original values to be preserved. Example:

if (speed < 17 && hasSpeedStripes)
{
   diagnsotics = "You are to slow to wear speed stripes!";
   return false;
}

the validation should be separate from the dialog class (though one could argue that generating the diagnostics don't belong into the settings class either, in that case you'd need a third "controller" entity indeed. Though I usually get by without)

Related Topic