C/C++ Users Journal August, 2004
In the first installment of this series on win32gui, a C++ templated library for GUI development, I examined GUI RAII, event handling, loose coupling, message maps, and main loops. I continue this month by addressing advanced window creation, advanced window access, window iteration, manipulation of frames and dialogs, and other topics.
The Windows operating system uses class names to assign a WindowProc when a certain window is created. For every event this window receives, its corresponding WindowProc is called. A class name is to its windows what a C++ class is to its instances. In particular, once a window has acquired a class name, it cannot change it. Thus, you can create mappings from Windows' class names to C++ classesor, putting it another way, once a window is created on the screen, you can decide an instance of which C++ class to create.
Win32gui implements GUI RAII: Once a window is created on the screen, its C++ object is automatically created. (The same goes for destructiononce the on-the-screen window is destroyed, its corresponding C++ object is destroyed.) When a window gets created on the screen, win32gui decides which corresponding C++ object to create:
Multiple mappings can exist for the same class name. Examples include the button/ check_box/radio_button classes; Win32 GUI aficionados know that button/checkbox and radio buttons have the same class name ("BUTTON"), but differ in their window style. Based on the window style they were created with, win32gui decides at runtime which C++ class to create. This eliminates another source of errors. Until now, manipulating buttons/ checkboxes/radio buttons was done through the same interface (MFC or WTL's CButton class) and you could, for instance, mistakenly set a push-button as checked (non-sense).
Multiple mappings can exist for certain class names. If classes A and B are mappings for the same class name, and class A extends class B, A is asked if it match_hwnd before B. In the button/check_box/radio_button example, check_box and radio_button extend the class button. If they weren't asked if they match_hwnd before button, button would always return true, and check_box and radio_button instances would never get created.
Still, creating your own mappings is easy. In your window class, you simply:
A C++ class that implements (automatic) mapping (that is, one that implements the static member function matches_hwnd) must have a default constructor; otherwise, how could the win32gui create a C++ object of its type? Listing 1 is an example of such a class. Also, when you download the code, take a look at controls.hpp; all standard controls provide this behavior.
Finally, when you're implementing a window class and want to manually create child windows, you can use the create_ child<>(...) shortcut, which is exactly the same as create_wnd<>(this,...).
Remember that with window access, you can always cast to the real C++ type of a window object or any of its base classes. Any function that allows access to a window (child(id), for example) has a templated corresponding function, which allows casting to another type (child<some_other_type>(id)). The nontemplated function always returns a wnd<window_base> value (window_base being the top-most window class), while the templated complement returns a wnd<some_other_type> value or throws an exception if the cast is invalid (cannot be done).
You can also specifically cast a window to another window type (using cast<>) or test if it's possible (using try_cast<>); see Listing 2.
Window access is one of win32gui's major features. It gives you many ways to access windows and iterate through them (à la STL).
The simplest window access is when you have a window object and you want to retrieve windows that are relative to it:
You might want to cast these windows to another desired type. Every such function has a templated counterpart, which is equivalent to calling cast<some_type>(your_func()). For instance, next_wnd() has a corresponding next_wnd<some_type>().
Each of these functions returns null_wnd if the window is not found (instead of throwing an exception). If I had thrown an error when a window was not found, the client code would get more complicated than it needs to be. For instance, if you wanted to retrieve the parent of a window, you would have to surround that in a try/catch body to handle the case when the window does not have a parent. Of course, the templated functions might throw an exception in case the cast fails.
The following functions are global:
The most powerful access function is find_wnd_range([window [,search_type]]) and its corresponding find_wnd_range<some_wnd_type>([ window [,search_type]]). The find_wnd_range returns an STL-like random iterator to all windows that:
The search type is any of:
Listing 3 shows some samples to get you started.
If, for your search, you know the result consists of only one window (or you're interested only in one window), use the find_wnd counterpart. Think of find_wnd(...) as executing *find_wnd_range(...).begin(). (Note that find_wnd might cache results to be faster). find_wnd is useful for Singleton-like windows. In Explorer-like applications, you can use it to find the Folders View, Search View, History View, and the like. Make sure there's a window matching your criteria when calling find_wnd(), otherwise an exception will be thrown.
For top_wnd and find_by_hwnd, I've provided try_top_wnd/try_find_ by_hwnd functions. Again, top_wnd/find_by_hwnd might throw exceptions, just in case the window is not found or if the cast fails. There are cases when you want to see if a window of your desired type exists, but don't want to use a try/catch block just for that. Use the try_ counterpart. The try_ function returns the window if it exists and if it is of the desired type; otherwise, it returns null_wnd. try_cast works the same, also allowing for the assignment-as-condition idiom; see Listing 4.
Finally, to be consistent, I've added try_find_wnd, the complement of find_wnd.
The simplest thing you can draw on the screen is a dialog. Recall that you create a window using create_wnd<>(...). This works fineexcept for dialogs. Windows treats dialogs different from the rest of the windows on the screen because a dialog can have children that are created by default when the dialog is created [2]. A dialog needs a unique ID to be created. This ID must identify a dialog resource from your .rc file. To make things more complicated, there are two types of dialogsmodal and modeless.
When you create a modal dialog, the function used to create it returns only after the dialog is closed. In contrast, when you create a modeless dialog, the function returns exactly after the window was created on the screen (as you would expect).
After you create a dialog, you can still wait for signals, just like you would for windows. But because of the way modal dialogs are created/destroyed, you need to specify what signals to wait for at creation, not afterwards (as you would expect); see Listing 5.
When creating modeless dialogs, use create_dlg just as you would use create_wnd. When creating modal dialogs, use create_modal_dlg and pass the parameters in the same way you would for create_wnd. Since create_modal_dlg returns only after the dialog has been closed (and destroyed from the screen), create_modal_dlg needs to return two parametersa pointer to the C++ object corresponding to the dialog and the signal that was triggered. Thus, it returns an instance of modal_dlg<your_dialog_class>, and you can query the .dlg (dialog) and the .result (triggered signal). You'll need the dialog instance (.dlg) in case the dialog holds any state (data) that you might find meaningful after the dialog has been destroyed. The common scenario is to handle the OK-button-pressed event and save the dialog state into variables, which can be accessed after the dialog has been destroyed; see Listing 6.
To create your own dialog, just extend the dialog class and override the static int dialog_id() function (which returns the ID of the dialog you're handling). I recommend that you define the dialog_id() function in your source file. This way, you'll only need to include resource.h in your source file, so if you're modifying any resource (and indirectly the resource.h file), only the source file is recompiled. This is another step to faster compilation; see Listing 7.
The cornerstone for most applications is the frame window(s). An application's top window is the Frame, and other windows sit on it. You can choose to sit the menu, toolbar(s), and status bar on the frame.
The frame dictates the visual appearance (look-and-feel) of your application. Moreover, the frame orchestrates how commands and notifications are handled. Typically, some commands/notifications are best handled by the frame itself. Such commands include creating a new view, opening an existing view, help, and so on. Also, if the frame does not handle a command or notification, it forwards it to its active child.
Other than that, the frame should get out of the way. MFC/ ATL/WTL have pretty much failed at this issue. Using an MFC/ ATL/WTL wizard to create an application automatically creates a lot of classes, and you end up not knowing where to handle eventsis it the CChildFrame, CMainFrame, CMyView, or CMyApp?
Finally, you'll want easy access to your frame children, usually by some unique name. Indeed, this is what most applications' Window menu is foridentifying a window (a child of the frame) by some unique name. That's why I added a novel feature to win32guiyou can easily find your children by their names. How? When creating a child of your frame, give it a name and you'll find it by it later.
The view_frame class provides all of this. Here's how:
Your own application frame can extend view_frame, but usually you'll prefer to use:
Listing 8 shows you the simplest frame-based application in which children are dialogs. It lets you create new children dialogsand it compiles and works. If you've used other frameworks, you probably know the difference.
The multiple_frames example (http://www.cuj.com/code/) presents the frame-based GUI application that allows multiple frames, answers to menu commands, and lets you change the look-and-feel at runtimeall this in around 200 lines of code (see Figure 1). The simple_explorer example presents a simple file browser written in less than 150 lines.
In upcoming installments, I'll examine what's involved in forwarding commands and notifications, and jump into status bars and toolbars, enabling/disabling of commands, advanced subclassing, smarter dialogs, manipulating controls, advanced event handling, and more. Until then, there are quite a few samples provided with win32gui. Check them out, and if you have suggestions for other samples, let me know.