devxlogo

Create Adaptable Dialog Boxes in MFC

Create Adaptable Dialog Boxes in MFC

hen working with the Microsoft Foundation Class Library (MFC), I occasionally need dialog boxes that look and act similarly but have small differences?dialog boxes that do different things with the same data. While writing a database application, for instance, I needed one dialog screen for users to add records and another for them to edit records. Both screens would have the same fields and they basically would look the same, but the add screen would have something like “Add Record” in the title bar, while the “edit” screen would have “Modify Record” in the title bar. The add screen would always come up blank, whereas the edit screen would always contain record data.

As a Visual C++ developer, the alternatives available to me were not very appealing:

  1. Create separate dialog boxes with separate CDialog-type classes for each.
  2. Create one dialog box with one CDialog class, but set up switches in the code to turn on different modes so the dialog can change its behavior and appearance accordingly.

Figure 1. The Main Dialog: The user gets to the custom screens from here.

If you use only the tools that Visual C++ 6 gives you, you’ll feel forced to take one of these approaches. With number 1, you duplicate code across the different dialog boxes, since each one is similar to the others. For number 2, you have to add many conditional statements to your event handlers, so you can switch between different modes. Both of these approaches make code maintenance difficult.

I have developed a better design scheme for these situations, which I will present in this article. I have included the code for a dialog-based application that shows this design at its bare minimum. Some of the interface functions are included, but are stubbed out to demonstrate the design structure. Some of it is not needed by the sample app, however, in normal circumstances these functions would be used.

Get Started

Figure 2. The Common Dialog Resource: This is the dialog that will be customized to appear as 3 different dialog screens. The controls will be made visible/invisible or enabled/disabled as appropriate for the function the user selects.

The main dialog in my application, which the user sees first, gives three options: Enter Text, Edit Text, and View Text (Figure 1). Each one activates a customized dialog. I created these dialog screens using what I call a common dialog box. The common dialog box for this app, which I’ve defined as one dialog box resource, contains an edit control (CEdit) and three buttons (Figure 2). I use C++ polymorphism to encapsulate the code that modifies the common dialog for each custom screen.

The View Text customized screen displays the same Print button that is in the common dialog box, and it is the only screen that does. It allows the user to “print” what they are viewing (it actually just displays a message box for the sake of demonstration). I set the Print button’s “Visible” property to false in the resource editor, since it only shows up on the View Text screen.

Figure 3. The Class Hierarchy: I used Class Wizard to generate CTextDlg from the common dialog resource, and then converted it to an abstract base class.

The program itself merely saves whatever is typed, allows the user to edit the text, and displays the text. Depending on which function the user selects from the main dialog, the custom screens display the common dialog with some modifications to its appearance and functionality.

How Does It Work?
In general, the design I present holds to a basic object-oriented design precept: put the common functionality in the base class, and put any code that specifically applies to a custom screen in the corresponding derived class. Figure 3 shows the class diagram for the program.

The Base Class
The keys to the design are a few interface methods I added to the base class (along with a factory method I will discuss in an upcoming section). The interface methods are Setup(), DoOK(), and DoCancel() (see Listing 1). They are called for certain events, like so:

BOOL CTextDlg::OnInitDialog(){      Setup();   } void CTextDlg::OnOK() {   // allow derived class to carry out “OK” action   DoOK();   } void CTextDlg::OnCancel() {   // allow derived class to carry out “Cancel” action   DoCancel();   }

The derived classes override Setup(), DoOK(), and DoCancel(), so the events effectively get passed down. You can expand upon this idea in your own code, adding or subtracting virtual methods to control the flow of the program. I kept the message handlers for the above events in the base class for this design concept, since I often find I want to add common action code to them.

A Breakdown of the Elements

Figure 4. Starting Out: Entering some new text. That’s all the user can do here. No printing is allowed, so no Print button. If the user re-enters this screen, the text buffer will be a clean slate again.

The CTextDlg class cannot be instantiated by itself, which makes CreateDlg() a key element. CreateDlg() is a factory method that takes an enumerated value (from dlgType), instantiates one of the CTextDlg-derived classes based on that value, and returns the object as a base pointer (CTextDlg *) (see Listing 2). The main dialog code (which I will get to later) uses this method to create the custom screens.

The application-specific code for this sample is embodied in several elements, declared in CTextDlg.h: SetText(), GetText(), m_Text, m_EditCtrl, and m_PrintBtnCtrl. I use SetText() and GetText() in the main dialog code to get/set the text displayed in the edit control of the common dialog. Note that I declared SetText() as pure virtual, because the derived classes need to control when and where the text buffer (CTextDlg::m_Text) gets initialized. To clarify, m_Text is bound to the edit control, which enables me to set the control’s text by assigning a string to it.

The variables m_EditCtrl and m_PrintBtnCtrl represent the edit control and the Print button, respectively. The subclasses use them both to modify the user’s access to them. Class Wizard places such variables in the dialog class that corresponds to the resource where they are located, CTextDlg. I figure where they are located doesn’t make much of a difference, and moving them to the subclass level seems more work than necessary since code in DoDataExchange() would need to be moved down as well.

Figure 5. Changing Things A Little: The original message has been modified. Again, no printing allowed from here, so no Print button.

The Screens
Each of the screens needs to deal only with what makes it unique. As you will see, the screens don’t require very much code.

The Text
The New Text screen is the simplest. It always starts out with an empty text buffer and then saves whatever the user types. Notice that although SetText() exists in the code, it doesn’t do anything (see Listings 3 and 4). This is one of the prices you pay for using an interface: it must be fully implemented even if not totally practical. The best thing to do is either leave the impractical portions of the implementation empty or make them illegal (raise an error). It’s a judgment call.

The New Text screen is simply the common dialog box without the Print button showing (see Figure 4).

Edit Text
The Edit Text screen looks just like the New Text screen (see Figure 5). The implementation is also similar. In this case, however, SetText() does something useful. The main dialog code calls it so the user can see what was entered previously. CEditTextDlg allows the user to edit the text and then saves it (see Listings 5 and 6).

View Text
The last of the custom screens, View Text, functions similarly to Edit Text–with a couple of differences. It displays any previously entered text, but the user is not allowed to edit it. The Print button is now visible and functional (see Figure 6). The class declaration and implementation of CViewTextDlg contain the messaging code for the Print button (see Listings 7 and 8). Again, the dialog creator sets the text in the dialog by calling SetText(), which is implemented in CViewTextDlg. If the user hits Print, it displays the message box shown in Figure 7.

Figure 6. Adding Options and Restrictions: From here, you can view the text, but the edit control does not allow text editing. You can print now, so the Print button is visible. Figure 7. The “Print” Dialog: The message box located in the print button handler in CViewTextDlg is activated when the user hits the Print button.

Bring It All Together
The main screen, shown in Figure 1, ties it all together. AppWizard created most of the code for it, so I am not going to show that portion. I’ll show just the code I added (see Listing 9). CPolymorphicDialogBoxExampleDlg is the name of the class I used for the main screen. The header file contains the following variable declaration to create a holding place for what the user typed/edited in the screens:

class CPolymorphicDialogBoxExampleDlg : public CDialog{protected:    CString DlgText;};

The three functions you see in Listing 9 for the “New,” “Edit,” and “View” buttons are the event handlers I set up using Class Wizard. Notice how they use CreateDlg() to create the custom screens.

This program is just one example of what you can do with MFC customization. You can customize MFC in all sorts of ways to get the look and functionality that you want-and you don’t have to sacrifice object-orientation to do it.

devx-admin

Share the Post: