Introduction
In an earlier article, I showed how interacting controls in a dialog box can be encapsulated in a custom control that handles all the interactions between the controls. An even better way to handle interacting controls is to put them into a child dialog. This article presents a simple class that helps you to put a child dialog into a dialog box.
Background
I was developing a MFC application to play MIDI files, and one of the dialog boxes seemed to end up being much too complicated. It was handling the interactions between an array of volume controls, one per track, and a parallel array of buttons for muting the tracks. I decided it would be better to have some sort of object containing the volume control slider and the mute button for a single track, any interactions between them being handled within the object, and the dialog box could then have an array of such objects.
In C terms, instead of having a structure containing two parallel arrays of objects, I wanted an array of structures, each structure containing two related objects.
A web search for advice on how to do this with controls, yielded information on custom controls, so my first attempt to handle the interacting controls used a custom control and is described here. Then I sorted out a better way of doing it, which is to put the interacting controls into a child dialog box, and this article describes how to do that.
Example program
The example program accompanying this article takes part of the standard Color selection dialog box, and handles the interaction between the Hue, Saturation, Luminosity, Red, Green and Blue controls by putting them in a child dialog. The HSL values affect the RGB values, and vice versa, but the parent dialog box doesn't have to know anything about this, it just interacts with the child dialog in terms of RGB values. The child dialog also shows the current color.
Creating a Child Dialog
The steps required to create a child dialog that will appear in a parent dialog box are as follows:
- Use Dialog Editor and ClassWizard in the normal way to create the child dialog box and its associated class (called
ChildDialog
(say), and based on CDialog
), also to lay out the controls in the child dialog box and create associated member variables in the class. But note the following:
- Change the properties of the child dialog box (by right-clicking on its border, then choosing Properties) as follows:
Style
: Child
Border
: None
More styles
: Visible
and Control
must be ticked
- Delete the OK and Cancel buttons from the child dialog box (otherwise the child dialog will disappear from its parent if you click them).
- Drag the guidelines to the edge of the box and place the controls right up to the edge (otherwise the child dialog will have a gap all round it).
- Add any code and data to
ChildDialog
to make the child dialog box work as required, including any interactions required between the controls (that's the whole point of putting the controls into a child dialog).
- In ChildDialog.h,
#include "GenericChildDialog.h"
and change the derivation of ChildDialog
from CDialog
to CRHGenericChildDialog
. Replace the standard constructor for ChildDialog
with one without parameters. (It's not essential to do this, but I found it makes other things much easier.) Add a declaration of CRHGetDialogID()
. #include "GenericChildDialog.h"
...
class ChildDialog : public CRHGenericChildDialog
{
public:
ChildDialog();
virtual int CRHGetDialogID();
...
- In ChildDialog.c, replace the standard constructor for
ChildDialog
with the new constructor (and move the AFX_DATA_INIT
lines to the new constructor, so that ClassWizard puts any initialization it wants in the right place) and add the CRHGetDialogID()
routine:
ChildDialog::ChildDialog()
{
... whatever ClassWizard might have put in ...
}
int ChildDialog::CRHGetDialogID()
{
return(IDD);
}
Putting a Child Dialog into a parent dialog box
To put the child dialog into a parent dialog box:
- Use Dialog Editor and ClassWizard in the normal way to create the parent dialog box and its associated class (called
ParentDialog
(say), and based on CDialog
).
- Use Dialog Editor in the normal way to place a Group Box control in the parent dialog box at the place where you want the child dialog to appear. Only the position of the top left corner of this control is used. Its size is not critical, because it will be resized by
CRHGenericChildDialog
to match the size of the child dialog. Give this control the ID IDC_CHILDPLACEMARKER
(say).
- Use ClassWizard in the normal way to add a
ChildDialog
data member (called vChildDialog
, say) to ParentDialog
.
- If necessary, create
ParentDialog::OnInitDialog()
(in ClassView, right-click on ParentDialog
, choose "Add Windows Message Handler ...", select WM_INITDIALOG
, click "Add and Edit").
- In
ParentDialog::OnInitDialog()
, call vChildDialog.CRHCreateGenericChildDialog(this, IDC_CHILDPLACEMARKER,
Id, &BorderRectangle);
Id
is a WPARAM
value that will appear in wparam
in any message sent by the child dialog up to the parent dialog box, thereby allowing the parent dialog box to identify where the message came from (this is useful if you have more than one child dialog of the same sort in the parent dialog box). BorderRectangle
is a CRect
you can use to specify the size of the gap between the child dialog and the Group Box control in the parent dialog box. CRect(4,15,4,6)
looks quite good, and is available as CRHGenericChildDialog::CRHRectForGroupBox
. You can use different borders, or use NULL
if you want the child dialog to appear in place of the Group Box control without any gap round it.
Trying out the Child Dialog
You just need to create the parent dialog box, and the child dialog should now appear in it. If you haven't yet written some code to create the parent dialog box, just add a NewParent
item to your main menu, and use ClassWizard in the normal way (using the "Message Maps" tab) to add a COMMAND
handler for it. Then create the parent dialog box as a modal dialog box:
void CCc9App::OnNewparent()
{
ParentDialog vParentDialog;
vParentDialog.DoModal();
}
Communication between a child dialog and its parent dialog box
To finish off the child dialog, we have to make it communicate with its parent dialog box. The child dialog must tell its parent dialog box when something has happened to the child dialog that the parent needs to know about. And the parent dialog box must be able to tell the child dialog to do various things (exactly what depends on just what functionality the child dialog supports).
This is very similar to how a Windows common control communicates with its parent dialog box:
- A control informs its parent of any changes by sending a message, which is handled in the class for the parent dialog box by a message handler routine (e.g.
OnHScroll()
).
- The parent tells the control what to do by calling a member function (e.g.
CSliderCtrl::SetPos()
).
So our child dialog and its parent dialog box communicate similarly, as follows:
- The child dialog informs its parent of any changes by sending a message (by calling
CRHGenericChildDialog::CRHPostMessageToParent()
), which is handled in the class for the parent dialog box by a message handler routine (e.g. OnCHANGED_RGB()
).
- The parent tells the child dialog what to do by calling a member function (e.g.
ChildDialog::SetRGB()
).
Using the Child Dialog
Now that we've created a child dialog that handles the interactions between its controls internally, and that communicates with its parent dialog box, we can put one into a dialog box using a placemarker control
void CRHGenericChildDialog::CRHCreateGenericChildDialog(CWnd *pParent,
int PlaceMarkerCtrlID,
int Id,
CRect *ABorderRect)
or we can put an array of them into a dialog box using a CRect
.
void CRHGenericChildDialog::CRHCreateGenericChildDialog(CWnd *pParent,
CRect *pPlaceMarkerRect,
int Id,
CRect *ABorderRect)
The interactions between the HSL controls, the RGB controls and the colored squares are handled entirely within the child dialogs. In this last example, the main dialog box knows only about an array of 5 objects whose interface is just in terms of RGB. This approach is much simpler than having the main dialog box handle the interactions between 65 separate controls.
Finally
If anyone's interested in seeing the original MidiPlay application that prompted this article, you can find it here (click on the "MidiPlay.exe v1.06" link). For a version of Midiplay using custom controls as described in my earlier article, click on the "MidiPlay.exe v1.07" link. For a version of Midiplay using child dialogs as described in this article, click on the "MidiPlay.exe v1.08" link.
History
- 19th October 2003 - first submitted.
- 1st November 2003 - major bug fixed (was leaking GDI resources). See Joseph M. Newcomer's article to see the
SelectObject()
mistake that I made. I originally saw the article on Joe's excellent site (thanks very much Joe!).