简体   繁体   中英

MFC: child dialog behavior

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.

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, ie 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)

As for the handle being invalid after the DoModal call, probably the GetSpeed method is attempting to access a control variable. It's not an invalid handle on the closed dialog itself, but a child control that contains the speed value.

If you copy the Speed value to a non-control member in OnOK, your code can access its value after the modal dialog is closed.

You should abandon your idea of creating the dialog on startup and hiding/showing it. This will only work properly with a modeless dialog. You can create it modelessly, then emulate modal behaviour, but this is tedious work.

I'm assuming you want to show/hide your dialog because that way it keeps the settings across your application, and you want to use the dialog to store the settings in. You should separate the dialog with the settings from the settings themselves. Create a class that contains only the settings and is not derived from the CDialog. Then, make your dialog fill its fields from that class. Store your settings object in your CWinApp derived class and create a now dialog (with .DoModal) every time you want to show it.

(reading the other answers again this is probably what peterchen is also saying)

From the sound of it you need a modeless dialog which you can create by deriving from dialog.

You can declare it in your app as you did above and create/destroy at your leisure. If you call it as a modal dialog you will be blocking the rest of application although when you describe your requirements it sounds like a modal behavior is not what you want eg

and show/hide according to whether the user select a particular menu option

I have ended up deciding to create a struct containing the settings to be configured in the child dialog in the parent dialog class, passing in the pointer to the struct when calling a constructor, and have the child dialog's OK button handler modify the struct's contents as it is a pointer. I think this is as clean as I can make the implementation for now.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM