Keeping It Simple

C/C++ Users Journal September, 2004

Setting goals and sticking to them

By John Torjo

John Torjo is a freelancer and consultant who specializes in C++, generic programming, and streams. He can be reached at john@torjo.com.

In previous columns, I laid the groundwork for using win32gui's advanced window creation/access/iteration, frames, and dialogs. Not thrilling reading, but a must. With this installment, the fun part of GUI programming begins, with the creation of menus, rebars, command manipulation, smart dialogs, splitters, and more.

I've been asked by some of you about the scope and goals of win32gui. Its goals are:

  1. Make GUI code simple and easy to understand (read and maintain).
  2. Provide GUI RAII (previously explained).
  3. Make GUI programming safe. Any failed GUI operation throws exceptions.
  4. Make it easy to handle events and manipulate standard controls.
  5. Make it easy to subclass existing windows, dialogs, and controls, and to create windows.
  6. Bring dependency on wizards to a minimum.
  7. Increase dialog programming.
  8. Bridge the gap between STL and GUIs, allowing for truly generic solutions.
  9. Make C++ a rapid application development (RAD) tool.

The first goal is common sense. Who wants to know the deeply buried Win32 GUI structures you need to carefully fill, and then call those ugly Win32 functions? If you ever tried to set the image on a list control item or manipulate tree control items, you know what I mean. Making GUI programming safe means not having to check for return codes on each call, which makes code much simpler to write and read. Average GUI programming consists mainly of Items 4 and 5, and win32gui does its best to help.

Programmers love and hate wizards. Because of language complexity, wizards and C++ never mixed too well. You sometimes use macros, typedefs, namespaces, small classes, generics, #ifdefs, and so on, which make it difficult for wizards to keep up. As code gains complexity, wizards can lose track of what they're supposed to do, and sometimes even modify the wrong code. Therefore, I've designed win32gui so that implementing window classes is a piece of cake.

Dialogs are just too darn cute. You can set many of their properties at design time, especially their layout. Resizability is easy to add (just extend the resizable_wnd class). Most of your GUI needs can be solved if you put some controls on a dialog and eventually add some code to glue them together.

Win32gui interoperates with STL as much as possible, making it easier for you to learn and use: find_wnd_range<> returns an STL random iterator; all existing standard controls use std::string when dealing with text (the text on controls, on list control items, and so on); thrown exceptions are std::exception derived; and so on. Existing window classes and your future window classes can be easily extended. Even more, you can extend multiple window classes at once, depending on your needs—the common scenario is a resizable dialog, when you extend from both dialog and resizable_wnd classes.

As for the scope, it's a never-ending story. Here I deal with as many GUI issues as possible, including (but not limited to): windows and dialogs, splitters, tabs, property pages, tooltips, resizability, subclassing, event handling, device contexts, resources, menus, dockability, generic containers, OLE, ActiveX, and the like.

Menus, Rebars, Toolbars, and Status Bars

Users love toolbars and with win32gui, you can load toolbars in just one line of code. Of course, toolbar commands are there, such as those that specify check-box-like icons, radio-button-like icons, and the like. Rebars are also available and are similar to toolbars, only better—they can hold toolbars and other controls (combo boxes, edit boxes, and so on). Plus, they allow for cooler separators (grippers). Using them is easy; for instance, Listing 1 yields Figure 1.

While other frameworks offer boilerplate code for this, they fail when you need anything nontrivial—you have to learn the internals of REBARBANDINFO, LVITEM, MENUITEMINFO, and so on. Win32gui offers wrappers over all of the aforementioned. For instance, setting the background of the first rebar band from Figure 1 to a bitmap called "my_bk.bmp" is simple:

rebar->band_info(0, rebar_band_info()      .bg_bitmap ("my_bk.bmp"));

Menus are resources, and I will explain in future columns how win32gui deals with resources in general. For now, remember that any menu can be either menu<owned> (you own this menu, and it's destroyed automatically when your object goes out of scope—because of this, you can't copy it) or menu<shared> (you don't own the menu, and it's not destroyed automatically). Most of the time you'll use shared menus; for instance, if you're handling an event and get a handle to a menu as a parameter, you don't own it; therefore, it is a shared menu. You must use an owned menu when you need to show a context menu (the almighty TrackPopupMenuEx function, for Win32 aficionados).

Windows allows submenus to be accessed by their position (zero-based index) or by their command ID. To make it easier, I've differentiated the two cases—see Listing 2.

Finally, status bars are about what you expect—you can have multiple panes (or indicators, if you wish). Set them (their number and their width) using the panes function, use pane_text(idx) to get a pane's text, and use pane_text(idx,new_text) to set it. In addition, use pane_icon functions to get/set an icon for each pane.

However, there's a twist in the end—status bars are Singleton-like windows. Remember last month? You can easily find a Singleton-like window:

// anywhere in code, if you have a 
// status bar
find_wnd<status_bar>()->pane_text(0,
  "This is cool!");

Menu Command Manipulation

Menu command manipulation is one of the areas where win32gui really shines. If you're handling a menu command, you know it could be coming from anywhere—the main menu, toolbar, rebar, or context menu. While usually you don't care, problems can occur when a command needs extra visual manipulation, such as when:

  1. Enabling/disabling a command.
  2. Checking/unchecking a command.
  3. Radio-button-like command (multiple commands are shown, but only one is selected at a given time).

If you have used MFC/ATL, you know that the code for this is ugly, error prone, and downright boring. What you'd prefer is to have a flag:

That's what win32gui gives you. The flag can be any of a variable or a get/set function pair; get is called before the command is shown (to find its status), and set is called when its status changes (for example, when users check/uncheck a command).

In case the variable or get/set function pair are class members, your class needs to derive from cmd_manipulator. And even if you set the variable flag programmatically, changes are still reflected in the GUI. Everything is automatically synchronized—this works for the main menu, any context menu you might have, and even rebars and toolbars!

Listing 3 shows a radio-like menu command—you can see what it yields at http:// www.torjo.com/win32gui/mnucommand.html.

Splitters at Design Time

Another problem with other frameworks is that, to use splitters, you have to create it manually. The same goes for its children. Thus, you can't use them at dialog design time and, when you create them, you need to compute where each window is to be sited.

However, why not put splitters into dialogs at design time? Then, at runtime a splitter just looks at its siblings and, when needed, resizes the controls on its left/right (for vertical splitters) or on its top/bottom (for horizontal splitters).

To add a splitter at design time, just add a dummy label where you'd like the splitter to be (in Visual C++, add Picture Controls). Then, assign it a special ID, any of ID_splitter1, ID_splitter2, ... ID_splitter10.

Under the hood, automapping comes into play—the splitter class extends the label class and implements auto_mapping. Then, at runtime, when any label gets created, the splitter::matches_hwnd gets called and its implementation is similar to Listing 4. Labels with the ID_splitterXX automatically become splitters. Refer to the simple_splitter program, available at http://www.cuj.com/code/ and http://www .torjo.com/win32gui/.

Granted, this does come at a small price. For Visual C++, you have to manually edit resource.h and insert the IDs from <win32gui/id.hpp>. You just can't convince MSVC to #include another file from resource.h (whenever you add a resource with a new ID, resource.h gets overwritten and you lose your changes). My attempts at tricking Visual C++ have been unsuccessful; if you find a trick that works, please drop me an e-mail.

Smarter Dialogs

In concordance with goal #7, I've enhanced the dialogs. My goal is to let you set as many control/dialog properties at dialog design time. This makes GUI development a lot easier.

In addition to setting splitters at design time, you can also have dialogs on dialogs, again at design time. They can be useful for nontrivial dialogs. The common scenario is when you have a splitter on a dialog, and one or more of the splitter children are also dialogs. When you want to do such a thing (on the parent dialog), add a label and place it where the child dialog should be. Then, set the label's ID to the child dialog ID. That's all—after downloading the code, take a look at the dialog_on_dialog sample.

You typically use dialogs to exchange data. Consequently, the scenario differs, depending on the type of dialog—whether it's modal or modeless. For modals:

  1. You have some structured data (for instance, an Employee's details) that you need to show to users, and eventually let them edit it.
  2. When the dialog is shown, you load the initial data in case users are editing the data (that is, editing the Employee's details), or initialize it to some default values if users are adding data (that is, adding a new Employee).
  3. 3(a). Let users modify the data, eventually giving them cues as to what is right/wrong.
  4. Let users save or cancel. In case users save, validate the data. If it's invalid, show an error message, and eventually let users reedit the data.
  5. Close the dialog.

For modeless dialogs, you may want to add two options:

  1. 3(b). Let users save (this is different from Step 4, which you could think as "Save and Exit").
  2. 3(c). Let users load other data (for instance, another Employee's details).

You use such a dialog for traversing a list (for instance, the records in a table). When users move to a new record, you want to allow saving the old record (in case anything got modified), then load the new record.

The save_dlg class implements all these steps. You use it to bind structured data to controls, and eventually add some validators (Listing 5). You use:

This specifies the data-to-control correspondence and eventually some validation code. In addition to that, you can override dialog behavior—what should happen when a bound control gains focus, loses focus, and so on. This is a truly generic solution—save_dlg clearly separates validation code from UI behavior code so that you can reuse each as you see fit.

For the correspondence and validation code, I recommend you create reusable classes, as in Listing 6. However, you can choose to create correspondences on the spot; see Listing 7.

You can reuse such a correspondence class for multiple dialogs. A common case is when you have multiple views for the same data; i.e., for the Employee case, you can have a Simple dialog where new users register and have few data to insert (the rest are defaulted), and an Edit Details dialog where users can edit all of its details. Going further, you can also have different roles in a company. Different roles edit different Employee details, and all of them can reuse the same correspondence and validation class (as in Listing 6).

As for UI behavior, you can reuse it and give your application a consistent look and feel. Some behaviors you can implement include:

Regarding the last option, I've implemented it in terms of the bolded_save_dlg class.

This is quite a RAD tool. Instead of creating a new UI class for each dialog—a time-consuming, tedious, and error-prone process—just create a simple correspondence class and use it throughout the program. At the same time, you can reuse different UI behaviors.

More To Do

In future installments, I'll examine advanced message boxes, manipulating controls, command forwarding, advanced subclassing, and more. Finally, I'm interested in what features you'd like added to win32gui, so don't be shy and drop me an e-mail.