Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Building MFC Dialogs at Runtime


Sharing classes leads to less clutter

Adrian is a founder of InPhase Technologies, a Lucent Technologies/Bell Labs spinoff that is developing holographic data storage products. You can contact him at adrianhill inphase-tech.com, with subject "DDJ."


Dialogs are generally defined for GUI-based applications using a visual tool to design the appearance of the dialog, and an automatic code generator to generate most of the source code associated with the dialog. Apple's Macintosh and Windows-based PCs both use this approach.

For MFC-based Windows applications, you can use Visual Studio's resource editor to position the dialog elements, then use ClassWizard to generate the corresponding C++ class code. The resource editor saves the layout information for all the application's dialogs in a single resource (.rc) file, and saves the corresponding resource ID symbol definitions in the resource.h file. ClassWizard generally writes the C++ class code for each dialog to a separate .cpp and .h file.

While this process is fairly easy to learn and use, the resulting code has several limitations, including:

  • The class encapsulating the dialog cannot be reused in other applications by simply including the dialog's .cpp and .h files in the new application's build. The dialog resources (in the application's .rc and resource.h files) must also be copied, either manually or using the resource editor, from one application to the other. This process is tedious, particularly if the class is to be reused in several applications. If the dialog is subsequently changed and the changes are required in all applications, the modifications have to be made to each application in turn. Overall, this procedure is more like the bad old days of C programming than an object-oriented method.
  • The process of copying and pasting resources from one application to another can lead to an insidious bug. Since the resource IDs are effectively at global scope (they are all #defined in resource.h), clashes are possible if resource IDs copied from one application already exist in the destination application. The application with the resource ID conflict may crash, with no obvious reason. (This has only bitten me once, but it hurt.)
  • Only one developer can edit an application's resource (.rc) file at a time. This limitation exists because the resource editor updates several #defines in resource.h (for example, _APS_NEXT_RESOURCE_VALUE and _APS_NEXT_CONTROL_VALUE) each time new resources are added. If two developers try to check in updated .rc and resource.h files to a source-code version-control system (such as CVS), the process fails because of conflicts in the values of these symbols. By contrast, multiple developers can work on different parts of a .cpp file concurrently, without clashing.
  • MFC's ClassWizard will generate a new class for each dialog, no matter how simple the dialog is, or how similar the dialog is to other dialogs in the application. This produces "class clutter" that, though not serious, serves to muddy the class view in a large project and adds to code bloat.

In this article I provide a class for defining MFC-based dialogs, which overcomes these limitations by not using resources in the application's .rc and resource.h files. (The complete source code and related files for the class are available electronically; see "Resource Center," page 5.) Instead, dialog box templates are built in memory at runtime. The details are encapsulated in class Dynamic_dialog, together with its helper class, Dialog_item.

You define a dialog in C++ source code, either using an instance of Dynamic_dialog directly or using a class derived from it when the dialog requires additional message map functions. To leverage as much existing MFC functionality as possible, Dynamic_dialog is derived from MFC's CDialog class. In particular, it was simple to make all the familiar data exchange and data validation (DDX/DDV) functionality available with very little additional code.

Example 1 is the code for a simple dialog with two edit controls, and the OK and Cancel buttons (Figure 1). Even without seeing the class declaration, the intent of the code is clear.

Inside the Dynamic_dialog Class

In general, an MFC-based dialog is constructed by creating a dialog box template that describes the dialog and its controls, such as edit boxes and pushbuttons. The dialog box template is then used to construct an instance of a CDialog-derived class (MFC provides class CDialog to encapsulate the basic functionality of a dialog box).

When you use the resource editor to define a dialog, the dialog box template is compiled from the contents of the .rc file as part of the build process. In the Dynamic_dialog class, rather than building the templates from the resource file at compile time, I generate the dialog box template in memory at runtime.

This approach is mentioned, but not described, in Jeff Prosise's Programming Windows with MFC, Second Edition (Microsoft Press, 1999) and Michael Blaszczak's Professional MFC with Visual C++ 6, Fourth Edition (Wrox Press, 1999). Blaszczak notes that this method is "fraught with pointer arithmetic and alignment tomfoolery." However, by encapsulating the template details in the Dynamic_dialog and Dialog_item classes, the apparent complexity is addressed once and presents no further disadvantages.

Dialog Templates in Memory

A dialog box template in memory consists of a template header followed by control definitions for each control in the dialog. A standard template header consists of an instance of the Windows SDK DLGTEMPLATE struct, typically followed by a Unicode string for the dialog box title, the font size, and another Unicode string for the dialog box font name. Each control definition typically consists of an instance of the DLGITEMTEMPLATE struct followed by a Unicode string specifying the control's initial text (see "Templates in Memory," Platform SDK: Windows User Interface, MSDN Library CD, July 2001).

The dialog box template has to be constructed with particular word alignment in order to work correctly, which is probably why this approach is not widely used. There is an example of building dialog templates in memory in "DLGTEMPL: Creating Dialog Templates Dynamically" (MSDN Library CD, July 2001), but it is incomplete and somewhat unconvincing because it implements most of its dialogs using standard (.rc) resources.

The Dynamic_dialog class has a CArray data member that contains one Dialog_item instance for each control in the dialog. The final dialog template is assembled by the Dynamic_dialog::Build_template_buffer and Dialog_item::Write_to_template_buffer functions (in Dyn_dialog.cpp and Dialog_item.cpp).

Using the Dynamic_dialog Class

The Dynamic_dialog class supports the five most common dialog controls: buttons, combo boxes, list boxes, edit controls, and static text. The DDlgTest application in the code accompanying this article has examples illustrating the use of all these controls. Look for the Dynamic_dialog_test namespace functions in DynDlgTest.cpp.

Listing One (available electronically) shows the interface to Dynamic_dialog. You build a dialog by constructing an instance of Dynamic_dialog, then adding controls to it. The tab order of controls is determined by the order in which controls are added to the dialog. Call DoModal to display the dialog, just as you would with a class generated by ClassWizard.

The overall dialog appearance can be set using the functions with names having the "Set_dialog_" prefix.

Functions to add controls to the dialog have names starting with "Add_." Most of these functions have a pointer argument value to associate a variable with the control. All the "Add_" functions have parameters specifying the size and position of the controls.

Edit controls, list boxes, and combo boxes often have associated static strings to tell the user what the control's contents are for. For convenience, the "Add_" functions for these controls take a pointer to a string to be displayed to the left of the control, and a second pointer to another string to be displayed to the right of the control (see Figure 1 and Example 1). If a pointer is 0, then no string is displayed in the corresponding position.

Static text strings can also be added to a dialog using the Add_static_text function, but supplying these strings in the same call as their edit control (or list box or combo box) is convenient and makes the intent of the code line self documenting.

For numerical values, the Add_edit_control function is implemented as a template function. If you require range checking (data validation) on the variable, then supply the value limits as the parameters min and max. If not, set these two parameters to the same value.

Rather obviously, checkboxes are added with the Add_checkbox function. The state of the checkbox is reflected in the value of the associated bool value passed to this function. As far as MFC is concerned, checkboxes are simply another form of button.

You add a group of radio buttons with a call to Add_first_radio_button for the first button in the group, followed by a call to Add_radio_button for each additional button in the group. Call Add_group_box to complete the set of radio buttons. The variable associated with a group of radio buttons is passed as a parameter to Add_first_radio_button.

Buttons and Message Maps

MFC uses a message map to associate a call to a dialog member function with a dialog event, such as clicking on a button or updating an edit box. For example, for a dialog produced with the resource editor, ClassWizard would generate a message map like this (with comments removed):

BEGIN_MESSAGE_MAP(File_dlg, CDialog)
ON_BN_CLICKED(IDC_INPUT_BROWSE, OnInputBrowse)
END_MESSAGE_MAP()

to associate function OnInputBrowse with a button click on the button with resource ID IDC_INPUT_BROWSE. The first line of the message map indicates that this message map is part of class File_dlg, which is derived from CDialog. BEGIN_MESSAGE_MAP, ON_BN_CLICKED, and END_MESSAGE_MAP are all MFC-defined macros.

There is no need to generate message map entries for the nearly ubiquitous OK and Cancel buttons because the CDialog base class already provides the necessary mapping. These buttons can be added to a Dynamic_dialog instance with the functions Add_OK_button and Add_Cancel_button. Typically, I make an Add_OK_button call immediately after constructing the dialog (see Example 1) so that the OK button has focus when the dialog is displayed. If users immediately press Enter, the dialog acts as if the OK button had been clicked.

To add other buttons to a dynamic dialog, you derive a class from Dynamic_dialog and add the button with a call to Dynamic_dialog's Add_pushbutton function. You must also provide a message map for your derived class, with an entry for each button. The resource ID specified in the message map must match the value passed as a parameter to Add_pushbutton. As with resource-based dialogs, you provide the function associated with the button in your derived class.

An example of adding a pushbutton in a Dynamic_dialog-derived class is in the Pushbutton_dialog class (Push_dlg.cpp and Push_dlg.h) in the DDlgTest application.

Browse buttons that let users select a file or directory by popping up a second, standard dialog for file or directory selection are common in many applications' dialogs. When users click OK in the second dialog, the file or directory name is copied back to an edit control in the primary dialog. To avoid the need to derive a class from Dynamic_dialog and add a message map every time I wanted this functionality, I decided to implement it directly in the Dynamic_dialog class. The message map and associated functions in Dynamic_dialog are shown in Example 2. I implemented four message map entries; the number can easily be extended if you anticipate needing more than four Browse buttons in a single dialog.

To add a Browse button to a dialog and associate the button with the filename contained in an edit control, call function Add_Browse_button immediately after the Add_edit_control call controlling the filename. The first parameter to Add_Browse_button determines whether the secondary dialog is a standard CFileDialog, or a dialog to allow a directory to be selected. An example of a dialog with three CString edit controls with associated Browse buttons is in function Dynamic_dialog_test::Browse_test in the DDlgTest application.

Message map notifications are not limited to pushbuttons. The sample application also contains an example of a dialog with two combo boxes where the contents of the right-hand combo box (in this case, a choice of cities) depends on the selection made by the user in the left combo box (in this example, a choice of states). The dialog code is in class Derived_dialog (Derived.cpp); the dialog is in Figure 2. The message map for this class is:

BEGIN_MESSAGE_MAP
(Derived_dialog, Dynamic_dialog)
ON_CBN_SELCHANGE
(Derived_dialog::e_box_id,
OnSelchangeState)
END_MESSAGE_MAP()

Conclusion

My primary aim in writing the Dynamic_dialog class was to let classes that use dialogs be shared among various projects, without the need to cut-and-paste resources from project to project.

To implement simple dialogs, create an instance of Dynamic_dialog and add the required controls with calls to the member functions. Call DoModal, as you would for a dialog generated with the resource editor and ClassWizard, to display the dialog. If users click OK (or presses Enter) to close the dialog, then the variables associated with the dialog controls are updated.

When a message map is needed for a dialog, derive a class from Dynamic_dialog and add the controls for the dialog in the constructor of the derived class. As before, call DoModal to display the dialog.

I chose the order of parameters passed to several of the Dynamic_dialog functions to allow common default values, and also to be close to the order of the corresponding values in the definition of dialog controls in a resource file. This makes it fairly simple to write a converter to generate C++ code from .rc dialog resources that already exist in a project.

Another advantage of building dialogs at runtime is that the current state of the program can be used to decide what controls to display. This can result in dialogs that are less cluttered than resource-based dialogs by avoiding the need to gray out or hide dialog items, for example, which are not relevant to the current state of the application.

An obvious extension of the Dynamic_dialog approach is a class to automatically size a dialog and position the controls in it, given a list of controls to be displayed. This, in turn, offers the prospect of dialogs that resize themselves depending on the differences in lengths of strings in different languages such as German and Spanish.

Acknowledgements

Thanks to Charles Stanhope and Martin Pane, both at InPhase Technologies, for their insightful comments.

Download the source code for this article here


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.