C/C++ Users Journal July, 2004
GUI programming and C++ have never mixed too well. In fact, many programmers don't do GUI in C++, preferring to avoid it altogether, or use a rapid application-development language such as Visual Basic. But that's a thing of the past. Starting with this article, I present win32gui, a full-blown templated library that will make you switch from Visual Basic back to C++ just for the sake of GUI programming. Moreover, I'll throw in real GUI RAII (Resource Acquisition Is Initialization)no more message maps, no template code bloat, rookie friendly, easy handling of events and event ranges, very loose coupling, fast compilation times (even when adding/removing events), no main loop, simple manipulating of existing standard controls (buttons, edit boxes, and the like), thread safety, and more. And that's just to start...
Even if this library sits on top of your existing GUI framework (MFC anyone?), you can still use it only where you like it; thus, you can gradually port your code to this library if you see fit.
I chose to implement it only for Windows. Making the library independent of the underlying operating system would make the time spent developing grow in size by an order of magnitude and end up not taking full advantage of the graphics abilities found on each OS. Therefore, let's stick to Windows. The library is portable, and runs on Visual C 7.1, GCC 3.3.1+, and Comeau 4.3+ compilers.
The problem with existing frameworks is that they often hide too little of the GUI complexity and don't take into account generic programming. MFC has no idea of generics, and you end up with a plethora of classes. If you wish to add a feature that could be used for a set of classes (for example, making it resizable), you have to reimplement for each class. ATL makes use of templates too often, causing a lot of code bloat. For the sake of efficiency, the message maps are in your header files; when you modify the message map from a header file, a lot of files depending on it will need to be recompiled, making maintenance painful. Not to mention the great set of macros and message maps used by both MFC/ATL. And most of all, without wizards, ATL/MFC are almost useless. Besides, you don't want to know what happens when the wizards get confused. Event processing is not separated from the rest of the member functions. What's stopping some client code from calling a member function that should only answer to user events (OnActivate, for instance), and even more, calling it with the wrong parameters? By the way, are they portable?
Updating toolbars, menus, tooltips, status bars, docking, splitters, tabs, list/tree views, and so much more is hard to handle using these frameworks. Thread safety is difficult, and of course, you can throw in a couple of your own problems.
How easy is it with this library to create the Hello World program? Listing 1 and Figure 1 answer that question. Pretty neat, with just a few lines of code and no .rc resources whatsoever.
As you might have noticed, I'm working with pointers. In fact, all window classes are smart pointers (under the hood, I use the boost::smart_ptr<> pointer). This is to be expected. When you write window a = b;, you certainly don't want one more window on the screen; you just want one more pointer to it. So one way or another, you still end up using pointers. You might not want to expose this and just consider it an implementation detail. This is not a good choice; take a look at Listing 2 to see why.
One great feature of the win32gui library is that you have real RAII. When you create a window (on the screen), its corresponding C++ object gets created only after the window creation. That means that in your window class's constructor, you know that your window exists on the screen. No more monitoring for WM_CREATE/WM_INITDIALOG events! When the window is destroyed (from the screen), its corresponding C++ object is destroyed automatically.
You never create a window directly. Instead, you say create_wnd<my_window>(parent, [info,] args) or parend.add_child<my_window>(args [,info]) (the info is information you pass at window creation, such as rectangle, style, and so on).
Trying to create a window directly, as in my_window wnd(args), should fail as soon as possible (you're not allowed to do it [1]). In our case, an assertion failure occurs. To figure out how this happens, you need to dig up the deeply buried virtual base rulethe virtual base class constructor is called only once by the most-derived type. Combine this with the provision of two constructors for the virtual base class and you're nearly there (see Listing 3).
Given a parent, you can simply get one of its children by using parent.child [<child_type>](child_id), as in Listing 4. Actually, accessing windows/children can get much better than this. Just to give you a taste: You can do advanced iteration and can, for instance, disable all push buttons from your application in just one line of code.
Finally, adding children is simplejust look at the Hello World program.
First of all, forget about those high compilation times and maintenance nightmares. Event processing is totally separated from your window class logic. In fact, your class's clients don't know anything about the event processing that takes place. I do this by having two classes: one that is visible to clients (the window class) and provides the accessible interface, and one that handles event processing and is not visible at all to clients. In fact, the latter resides only in a source file. Listing 5 shows an example of such a window class, which handles the dialog in Figure 2. Since the event processing is handled only in the source file, if you need to add/update/ remove events, recompiling is blissjust one file. But most important, you've clearly separated event processing from your window class logic. That increases both maintainability and readability of your and your peers' code.
The communication between the window class and its event handler is bidirectional:
The latter is very important: The window does not directly call the event handler, but rather indirectly, by sending it a message. Thus, it's very loosely coupled and brings one extra advantage: If a window has multiple event handlers (some of them inherited from base window classes), the library transparently makes sure each handler gets called.
There's one more subtle advantage of the window/handler separation, which you'll notice only if you've done hardcore GUI combined with multithreading. In multithreaded applications, if your window class contains a lot of state (internal data), which needs to be thread-safe, then event handling would need to be thread-safe as well. But when using this library, only your window class needs to be thread-safe. The event handler doesn't need to be because it's always called on the same threadthe operating system takes care of that.
Extending (or deriving, if you wish) is a powerful feature of the win32gui library. You extend a window in order to inherit its behavior and/or provide additional functionality. Examples include: extending a combobox to show a bitmap on the left of each item, extending an edit box to show path names and provide word completion (such as the "File Name" edit box in the File Open dialog), extending a list view so that you can show icons on multiple columns (not only on the first one), and so on.
As an aside, most window extending today (in C++) happens by extending dialogs [2]. That said, with the win32gui library, it is easy to extend a window:
At step 1, you declare your base class. At step 2, you decide if you need to have custom event handling. (If you're satisfied with how your base class handles events, you don't need to take this step.) If you take both steps, it's recommended that you make the handler a friend. Usually, in your event handler, to answer some events you'll need to call back your window class. The callbacks will most likely be private member functions. (You don't want to expose them to the world, do you?)
You can even extend multiple windows (window behaviors). The most common scenario is when you're implementing a dialog class: extend both the dialog and the resizable_wnd classes. The resizable_wnd automatically resizes children as the window is resized (much like WTL's CDialogResize<> class). Check out Listing 6.
After executing step 1, behind the scenes a link is created from base_wnd_class to your_class. Thus, at runtime, a class hierarchy gets created. When an instance of your_class is created, the library goes from your class up the hierarchy to the top window class. For each existing relation (for instance, from cool_wnd to dialog), it checks whether there's an event handler that handles the relation (such as from cool_wnd to dialog, the optional step 2). If so, it creates an event handler for that (multiple event handlers might get created for a certain window). If you're familiar with ATL, this is event chaining without the ugly macros. No need to specify anything at runtime, just let it run.
As a bonus, you can specify a handle type, which can be events_after (handles events after the base class event handler), which is the default, or events_before (handles events before the base class event handler). In the rare case you want both, add this to your goodie bag: You can have two event_handlers for a relation; see Listing 7.
Here's more good newsthere are no more message maps. In your event-handler class, when you need to handle an event, create a new function (Listing 8). Here are the events you can handle:
If you want to handle ranges of events, just use event_range, command_range, and notify_ range, instead. However, the real beauty comes from the fact that you can choose what parameters you're interested in and have them in any order you wish. For example, for WM_SIZE, you're probably interested in the new size (the low and high word from lParam); for a right-click notification, you're interested in the lParam cast to NMHDR; for a WM_TIMER, you're interested in wParam; and for a WM_HSCROLL, you're interested in the low and high word from wParam (Listing 9). As for parameter types, you can choose from:
To see how I implemented the aforementioned, take a look at Listing 10.
Most frameworks now implement one main loop with PeekMessage, GetMessage, TranslateMessage, and DispatchMessage. This is quite inflexibleyou can't stop it. You also can't interfere with it. Imagine you have an application that has multiple tabs (for instance, most likely, your favorite IDE). You want to allow renaming the tab: When right clicking the tab, an edit box will show up, the user enters the new name, and presses Enter. Listing 11 presents an easy way to do this.
Instead of the old main loop, after some window gets created, you can choose when the loop terminates: when a signal happens or when a timeout occurs (for instance, you can choose to terminate the loop in five seconds). You can wait for a signal or multiple signals (just separate each signal with "||").
Think of waiting for a signal the same as waiting for an event to happen. For instance, signal::cmd(IDOK,BN_CLICKED) waits for the OK button to be pressed. Here are the different flavors:
There are some predefined signals you can use: wait_for::destroy, wait_for::quit, wait_for::ok_pushed, wait_for::cancel_pushed, wait_for::any_button_pushed, wait_for::kill_ focus, wait_for::key_pressed(some_vk_key), wait_for::button_pushed(some_button_id).
You can use win32gui standalone or on top of existing frameworks. For ATL, just #include <win32gui/on_top_of_atl.hpp>, and on CMainFrame::OnCreate() call on_top_of_atl().
For MFC, just #include <win32gui/on_top_ of_mfc.hpp>, and on CMainFrame::OnCreate() call on_top_of_mfc(). If you're using another framework, look through the existing on_top_of* implementation and do the same, or just drop me an e-mail.
Extending a window does not produce code bloat. (The wnd_extend<> class is very lightweightwhen you download code, take a look.) And of course, it's great to finally have fast compilation times: Adding/updating/ removing of events will cause only the source file you're modifying to get recompiled, as it should.
There's plenty ahead: advanced windows access, advanced thread-safe issues, window iterating, advanced subclassing, complex dialogs, and more.