sup { vertical-align:text-top; }

GUI Library

Bring The Simplicity of Windows Forms to Native Apps

John Torjo

This article discusses:

  • The problem with GUI programming
  • Creating window objects
  • Handling events and notifications
  • Forms and controls
This article uses the following technologies:
Win32 API, C++

Contents

It's Native and Portable
No windows.h
Dealing with Each Window
Straightforward Code
Controls Versus Forms
Form Programming
Dealing with Forms
Out with the Old IDs
Events and Notifications
Menus, Shortcuts, and the Like
Tab Controls and Forms
Resizing
Integration with Visual Studio 2005
Implementing Behavior

The problem with GUI programming in C++ is that most libraries are too low level, putting too much of the burden on the programmer. They rely on C-like structs, or their wrapper classes don't hide enough complexity. Also, they don't make event programming simple enough, instead forcing you to have to know about underlying WM_ messages.

In this article, I will show you eGUI++, a C++ library I have written that gives you, the client programmer, a high-level interface for dealing with GUI applications. It hides complexity, making event programming quite simple by completely hiding knowledge of WM_ messages. You won't need to deal with any C-like raw structure; you always deal with classes. All in all, eGUI++ client code is simple to read and simple to write.

eGUI++ is Windows®-only. I don't truly believe in cross-platform GUI applications, unless such an application is either trivial, a simple testing framework, a prototype, or just educational. More importantly, I strongly believe in taking advantage of all the underlying OS is offering. And for Windows XP and Windows Vista® that's quite a bit.

It's Native and Portable

For those looking for CLR code, you already have managed C++. That is a good platform, so there's no need to improve there. For the rest of you craving a good library to generate native Windows code for Windows 2000 and newer operating systems, keep on reading. You'll love the result; the lib is straightforward to use, taking advantage of the OS you're targeting. And you won't need the Microsoft® .NET Framework at all. The code you'll write will feel like C++. Additionally, the code you'll write is not Visual C++ compiler-specific. You can compile it with g++ (GNU C++ Compiler) 4.1 if you wish. Basically, if you wrap the Win32® API, there's nothing preventing you from writing portable code.

That said, for a non-trivial GUI, you'll need a good IDE such as Visual Studio® 2005 or Visual Studio 2008 Express editions. I've tweaked my library to be integrated with Visual Studio 2005 Express and newer and to give you an even better GUI experience. What I really focused on is code completion to make sure that when you create a new GUI class or extend an existing one, the IDE will do its best to help.

I want to enjoy writing GUI applications. Thus, my goals in creating eGUI++ were to make GUI code easy to read and write. For example, I've implemented code completion features wherever possible. GUI programming is now safe (in case there's an error, I catch it at compile time whenever possible; otherwise, a run-time exception is thrown). eGUI++ is resource-editor friendly (it integrates with Visual Studio 2005 and newer versions of resource editor).

No windows.h

The main problem with including windows.h is that it offers too much room for error. What's to stop you from monitoring an event that will never arrive? Imagine you're a button class, but you're waiting for, let's say, a keyboard event; it's not going to happen.

Why should you need windows.h anyway? You should be able to write Windows applications in C++ using regular C++ classes. Thus, you shouldn't need to know the windows.h internals: no WM_LBUTTONDBLCLK, WM_LBUTTONUP, and other lengthy event names. No LPNMITEMACTIVATE, NMHDR, or any other dubious C structure. No more C-style casts either. The primary function of a good C++ GUI library should be to abstract away the Win32 API and allow you to deal with classes.

Have you had enough of dealing with raw C structures like you see in Figure 1? I know I have. So now I write code like this:

wnd<rebar> w = new_(parent);
rebar::item i(rebar::item::color | rebar::item::text);
w->add(i); 

Figure 1 Darn that Old Window Code

// The old way 
hwndRB = CreateWindowEx(WS_EX_TOOLWINDOW,
  REBARCLASSNAME, NULL,
  WS_CHILD|WS_VISIBLE|WS_CLIPSIBLINGS|
  WS_CLIPCHILDREN|RBS_VARHEIGHT|
  CCS_NODIVIDER,
  0,0,0,0, hwndOwner, NULL, g_hinst, NULL);
...
rbi.cbSize = sizeof(REBARINFO);  
rbBand.cbSize = sizeof(REBARBANDINFO);
rbBand.fMask  = RBBIM_COLORS | RBBIM_TEXT |
  RBBIM_BACKGROUND;
rbBand.fStyle = RBBS_CHILDEDGE;

As you can see, eGUI++ hides the complexity: you deal only with C++ classes, You don't need to remember complicated API functions (like CreateWindowEx), constant names (WS_* constants) or complex C structures like REBARPARAMINFO.

eGUI++ targets Windows 2000 and newer. By default, it targets Windows XP SP2. However, you can choose to target a different OS so that less or more features are available. If you want to target a different OS, just #define EGUI_OS to a different OS constant before including any eGUI++ headers (see Figure 2).

Figure 2 Designating the OS

/ code from eGUI++
struct os {
 typedef enum type {
 win_2k,
 win_2k_sp4,
 win_xp,
 win_xp_sp2,
 win_vista
 };
};

#ifndef EGUI_OS
#define EGUI_OS os::win_xp_sp2
#endif

You'll be happy to know that there are very few #ifdefs in the code, so it's much easier to read. What you'll notice instead is that some properties are OS-specific:

property<int,os::win_xp> some_prop;

If you target an earlier OS and try to use the property above, for instance, a compile-time error will be issued.

Dealing with Each Window

Usually it's you, the programmer, who decides how long an object lives. But a big problem in GUI programming is that there is a difference between the visual window and the window object—and it's the user who decides when a window is to be closed. In your code, then, you can have a valid pointer or reference to a window object where the window has been destroyed by the user. As this makes it hard to maintain a one-to-one correspondence where a window on the screen represents an object instance and vice versa. One clear restriction that results from this scenario is that you can't have local window instances (which would be destroyed when the scope exists):

{
form f(...);
f.show();
...
}

Assume your form, f, is shown on screen. What should happen when you exit f's scope? You have two choices: you either destroy the (on-screen) form or you leave the form on screen while its corresponding C++ instance is destroyed. Neither choice is reasonable. In the former case, this could confuse the user, leaving him wondering where the form went. In the latter case, you'll leave the form on the screen, but it won't respond to any events because its corresponding C++ instance is gone. In addition, the user might have already closed the on-screen form while f is in scope. Thus you'd end up working with an object that doesn't exist on screen.

The solution: always access the window through (an indirect) pointer. Then, when the user closes the on-screen window, the corresponding C++ instance is marked as invalid; if you try to access it, an exception is thrown. You can always find out if a window is valid or not and you can destroy the window yourself, like so:

wnd<> w = ...;
// is window valid?
if ( is_valid(w) ) w->do_something();
// is window valid?
if ( w) w->do_something();

// destroy the window
delete_(w);

Because each window on the screen has a corresponding C++ instance, you deal with windows using the wnd<> template class, which represents a shared (ref counted) pointer to a window. The wnd<> class has one optional argument: the window type. This is basically what you'd expect. By default, it's window_base. Or it can be a window class such as text, label, rebar, edit, and so forth. Figure 3 shows a few examples of using window objects and casting between them.

Figure 3 Creating Window Objects and Casting

// when constructing a window, you can specify its type
wnd<> w = new_<form>(parent);
w->bg_color( rgb(0,0,0));

// when constructing a window, if you don't specify its type,
// it will guess it, based on who you assign it to
wnd<button> b = new_(w, rect(10,10,200,20) );
b->events.click += &my_func;

// destroying a window
delete_(b);

// casting - if it fails, it throws
wnd<form> f = wnd_cast(w);

// casting - if it fails, returns null
if ( wnd<edit> e = try_wnd_cast(e) )
  e->text = "not nullio";

Note that you create a window using the new_ function and delete it using the delete_ function (again, it's usually the user who closes a window). Additionally, if you have a window of type X (window_base is the default), and you want to know if it's of type Y as well, you can use casting. Casting is always explicit. There are two types of casts: wnd_cast, which, if it fails, throws an exception, and try_wnd_cast, which, on failure, returns a null window.

Sometimes, when developing an eGUI++ class, besides deriving from its base class you'll want to inherit some extra behavior, such as resizability, skinability, and so on. In this case, you can create multiple re-usable behavior classes and additionally derive from them.

Straightforward Code

It's refreshing to see GUI code that's easy to write and read. I've used every trick in the book to make sure code-completion helps as much as possible. Dealing with a big GUI library, there are always things you tend to forget: property names, event names, flags, and so on. I've dealt with each. I've used doxygen for the documentation; it simply shines. The more I use it, the more I love it.

Browsing through the documentation is very easy. For a property name, just type w-> and, as shown in Figure 4, you'll see the methods and properties (the properties are shown as member variables, so they are easy to differentiate). For event names, it's easy to remember the events that a class can handle; just type class_name::ev::, and after the scope operator, code completion jumps in and shows you the events. But dealing with flags is where eGUI++ really shines. For each property that can be a combination of flags, to find out your available flag options, just add "." to the flag property and code completion will come to the rescue again. As a bonus, I added operator overloading for properties. Thus, this code is valid:

w->text = "hello";
w->text += " world";
w->style |= w->style.tiled;

fig04.gif

Figure 4 Code Completion

And just in case you forget the list of controls you have at your disposal, I've added a namespace just for that called egui::ctrl.

Controls Versus Forms

If you've done Win32 GUI programming in the past, you're familiar with dialogs. You're also familiar with the fact that the API treats dialog creation (::CreateDialog) very differently from window creation (::CreateWindow[Ex]). As a programmer, you shouldn't need to remember two complicated functions with quite different signatures. They're both types of window. eGUI++ has only one way to create a window: the new_ function.

With regard to different types of windows, the name form is more expressive than the name dialog. It describes a window that shows other controls, those controls containing data. I allow both names, but I prefer form. In fact, in code you'll see this:

typedef form dialog;

Conceptually, there are only two types of windows: controls and forms. A control is a window that displays some data that it might permit the user to modify. Every control class derives from "control" class. A form is a window that hosts one or more controls, and some of its own logic (for example, logic that allows the manipulation of some data).

The available functionality of each window type varies based on what the window is supposed to do. For example, note that the form allows you to enumerate its child controls, while the control doesn't; this way, your code will be less prone to errors. Also, you'll very seldom need to create controls; usually they're already on the form because you've put them there using the resource editor.

Forms themselves come in two flavors: modal dialogs and message boxes. To create a modal dialog, just add the form::style::modal when creating the form. To create a message box, use the msg_box<> function, specifying the buttons as template arguments:

if ( msg_box<mb::ok | mb::cancel>("q") == mb::ok)
    std::cout << "ok pressed";

Additionally, the msg_box<> knows at compile-time whether or not a button combination works:

// ok
   msg_box<mb::yes | mb::no>("q");
   // compile-time error
   msg_box<mb::ok | mb::yes>("q");

Form Programming

To reiterate, a form is what the Win32 API calls a dialog. For Windows Forms, form programming has proven to be a winning strategy. On each form you have some controls, and each form solves a single task. Instead of the old and complicated Single Document Interface (SDI) or Multiple Document Interface (MDI), you can use tabs, which can host controls or other forms. Thus, you won't see any CFrameWnd, CMDIChildWnd, or anything similar; there's no need for them. If you need to host multiple forms on a form, simply use tab_form class. It allows you to add child forms, each on its own tab.

Dealing with Forms

As much as I hate wizards, I do believe a few, now and then, make programming tasks easier. In this case, to create a form I have created a New Class Wizard. In the Class View, select Add Class, and at Categories, select eGUI. On the left, select eGUI Form, click Add. Specify the class name, and that's it (Figure 5). The wizard will create a header file called <dlgname>.h, a source file called <dlgname>.cpp, and an extra header file called <dlgname>_form_resource.h, which eGUI++ will maintain internally.

fig05.gif

Figure 5 Add Class (Click the image for a larger view)

That last header file contains all the names of the controls you're using in the form. Thus, you won't need to create extra control variables and use data exchange, like in MFC. You'll use the controls directly. Assume you have a login dialog, which has two edit boxes (User Name and Password) and two buttons (OK and Cancel), as shown in Figure 6.

fig06.gif

Figure 6 Edit Boxes and Buttons

The following files will be generated for you:

// login.h
#pragma once
#include "login_form_resource.h"
struct login : form, 
 private form_resource::login {};

// login.cpp
#include "stdafx.h"
#include "login.h"

Note that the code is quite straightforward; there is no wizard-style code like "enum {IDD = ... }" or message maps. You don't need to provide a custom constructor if you don't want one; the default will do just fine.

The login class derives privately from form_resource::login, which is implemented in login_form_resource.h (this file is maintained by the eGUI++ library); the form_resource::login class contains information about the form's controls (their name and type, and the ability to catch notifications from the controls); you can choose to change the type of accessibility for the derivation, although I'd advise against it. Just as your class' member data is usually private, the same is true of form—its controls should usually be private.

Now, the generated form_resource::login will look similar to this:

// login_form_resource.h
#pragma once
struct form_resource::login {
 // ... (code to allow 
 // handling of notifications)
 wnd<edit> username;
 wnd<edit> passw;
 wnd<button> ok, cancel;
};

This will allow you to easily manipulate your form's controls. Assume you want to make sure the password is "secretword":

void login::on_button_click(ev::button_click &, ok_) {
 if ( passw->text == "secretword")
 { pass_ok = true; visible = false; }
}

As you can see, just like in Visual Basic®, to hide a form you simply set its visible property to false.

Out with the Old IDs

You've probably dealt with the resource editor before. And you've encountered the many types of resource prefixes, such as ID_, IDD_, IDC_, IDR_, IDS_, and so on. Prefixes are good for the resource editor. However, in code, they're just extra information you neither need to remember nor care about. In an eGUI++ application, you'll never need to remember those prefixes because they're completely ignored.

For instance, the previous names (username, passw, ok, cancel) are shortcuts from the resource editor. The eGUI++ library automatically strips their ID* prefixes. The original names could have been IDC_username, IDC_passw, IDOK, and IDCANCEL.

Events and Notifications

As I've mentioned, you don't need to remember a single WM_ message. That said, events are tough to tame. There are quite a few of them out there and you need an easy way to find out which events you can respond to and respond to them easily. You need easy ways to be notified of events happening on your form's controls, to be able to extend controls, and to add your own events.

Every window class (control or form) can generate events. For each, there's an event handler that catches all events. For each event, a function is defined that handles that event; that function is virtual, and its implementation does nothing. Each event handler function has one argument: the event data.

For existing controls, the corresponding event class is called handle_events::control_name. Each existing eGUI++ window class wnd_name already derives from handle_events::wnd_name. If you extend an existing window class, you can always handle its events. (For simplicity, all event handler functions start with "on_".) For instance:

struct my_btn : button {
 void on_char(ev::char& e) {
 cout << "typed " << e.ch;
 }
};

Those of you who have dealt with other GUI libraries know that life isn't as easy as you might think based on what you just saw. Existing controls don't send events but rather notifications. The notifications are in the form of WM_COMMAND/WM_NOTIFY messages, and they are sent to the control's parent, not to the control itself. That might seem reasonable at first: it's the control's parent (the form) that needs to be notified. However, that makes extending control classes quite difficult. What if you want to make a tree to visualize the current file system? You'd need to catch events, such as item expanding (TVN_ITEMEXPANDING), which are sent to the parent of the control. Then you'd need a way to pass the notification down to the control itself.

With eGUI++, notifications are events. Thus, they are always sent to the control and then to the control's parent. When you extend a control class via inheritance, each notification translates into a different event. For example, if you wanted to create a list control that shows a combobox instead of an edit control when the user is editing the first column, the code would look like this:

struct list_with_combo : list {
 ...
 void on_begin_label_edit(
  ev::begin_label_edit & e) {
 e.allow_default = false;
 combo->rect(...);
 combo->visible = true;
 }
 wnd<combo_box> combo;
};

To handle an event, you overload an event handler function, like so:

struct my_btn : button {
 void on_char(ev::char& e);
};

Here you respond to the character pressed event, or, for you Win32 API lovers, the WM_CHAR message.

Remember that for the on_my_event event handler function, the event argument is always of ev::my_event type. All events your class can handle are an ev:: struct. By simply typing ev::, code completion will show you all events your class can handle (see Figure 7). Note that the easiest way to find out the event information is to type e. and let the code completion show all data related to the event (see Figure 8).

fig07.gif

Figure 7 Code Completion Shows Events

fig08.gif

Figure 8 Get Event Information

You can browse through the docs for a control's events: just select the control, then its ev class, and you'll see all its events. The library can send the same event to several event handlers (for instance, notifications are sent to the control, and then to the parent of the control).

All events have the .sender property; this is the control that sent the event (it's useful for notifications, specifically in order to know who sent the notification). All events have the .handled property; it can have two values: handled_partially (default) and handled_fully. By setting this property to handled_fully, you can stop event processing; even if there are more event handlers, they won't be called. For instance, if you were extending the edit class and wanted to prevent the parent from being notified of text changes, you'd write the following:

struct independent_edit : edit {
                      void on_change(ev::change &e) {
                      e.handled = handled_fully;
                      }
                     };

Extending controls is simple, as I've shown above. However, handling notifications on forms should be simple as well. When handling a notification, you need to know who sent it (e.sender). But more to the point, you want to be able to act on a notification from a specific control. Therefore, the event handler function gets an extra argument: the control name, followed by an underscore (_). For instance, to find out what the user is typing in the username edit box, you would run this code:

void login::on_change(
 edit::ev::change &e, username_) {
 cout << "name=" << e.sender->text;
}

As an example, assume you want to convert currency between U.S. dollars and Euros. When you enter a value in the EUR box and type something, it updates the USD box. If you enter a value in the USD box and type something, it updates the EUR box, as you see in Figure 9. Here's the code to make it work:

struct convert : form, form_resource::convert {
 double rate; 
 convert() : rate(1.5) {}
 int mul_str(const string& a, double b) { ... }
 void on_change(edit::ev::change&, eur_) {
 usd->text = mul_str ( eur->text, rate); }
 void on_change(edit::ev::change&, usd_) {
 eur->text = mul_str ( usd->text, 1/rate); }
};

fig09.gif

Figure 9 Currency Converter

The code is self-explanatory; mul_str multiplies a double by a string by converting the string to a double and multiplying it by the conversion rate.

To handle the events as I did above, I had to do quite a bit of work. Assume you have a form with three edit boxes. Each edit box will generate a certain set of events. For each such event (for instance, on_change), I could either generate one overridable function for each control

void on_change(edit::ev::change& e, ctrlname_);

or generate one overridable function:

void on_change(edit::ev::change& e);

I prefer the former solution—the client code is much simpler (and much more analogous to the Visual Basic approach). You can easily see what you're handling (as opposed to the latter solution, where within the implementation of the event you'd have to manually ask what control generated the event via e.sender).

That's why I implemented the first solution. Under the covers, however, there's a lot of work involved. eGUI++ monitors the resource editor. When a new control is added or a control is renamed, it updates all <dlgname>_form_resource.h files. Note that for each <dlgname>_form_resource.h file in the form_resource::<dlgname> class you must override all notifications from existing controls and, for each such overridden notification, find the controls that can send it. The next step is to generate an implementation which will forward to another overridable function for each control. As an example, Figure 10 shows the code for a login form with two edit boxes and two buttons.

Figure 10 Login Form Code

struct form_resource::login {
  wnd<edit> name;
  wnd<edit> passw;
  wnd<button> ok, cancel;

  typedef ... ok_;
  typedef ... cancel_;
  typedef ... name_;
  typedef ... passw_;

  virtual void on_change(edit::ev::change& e, name__) {}
  virtual void on_change(edit::ev::change& e, passw__) {}

  virtual void on_change(edit::ev::change& e) {
    if ( e.sender == name) on_change(e, name__());
    else if ( e.sender == passw) on_change(e, passw__());
  }
  // ... same for other edit notifications

  virtual void on_click(button::ev::click & e, ok__) {}
  virtual void on_click(button::ev::click & e, cancel__) {}
  virtual void on_click(button::ev::click & e) {
    if ( e.sender == ok) on_click(e, ok__());
    else if ( e.sender == cancel) on_click(e, cancel__() );
  }
  // ... same for other button notifications
};

Finally, you can create your own events by deriving from new_event<>. Whether you're sending existing events or your own events, the process is the same: you use the send_event function:

struct hover : new_event<hover> {
 int x,y; // position
 hover(int x,int y) : x(x),y(y) {}
};

w->send_event( hover(x,y) );

The library is thread safe. As a side-note, each window has an m_cs mutex variable (basically, it's a CRITICAL_SECTION), which I use to make sure each method access is thread-safe. When you extend a window class, you can re-use the m_cs variable or create your own—it's your call.

Menus, Shortcuts, and the Like

If you've done GUI programming in the past, you know that both pressing a menu command and pressing a key cause a WM_COMMAND to be sent. As a consequence, when you receive a WM_COMMAND, it's difficult to know whether that event came from a control or from a menu (or keyboard shortcut, for that matter). eGUI++ solves the first part of the problem by placing the menu on the form (dialog) directly. If a command sent to a form is not sent from a button, it's from a menu.

That takes care of menu commands. Now let's look at shortcuts. The problem with shortcuts is that one can be keyed at any time (for instance, when inside an edit box). Keyboard shortcuts (accelerators) are routed first to the immediate window, then to the form hosting the window, then to the form's parent, and up the hierarchy until reaching the top-most window. The first time you find an event handler for that shortcut, processing stops (a given shortcut is not processed by two or more windows).

That leaves toolbars—they go hand-in-hand with menus and shortcuts. When a toolbar button is pressed, the event is translated into a menu command and is routed directly to the form that hosts it—whether the command came from a menu, a shortcut, or a toolbar button is irrelevant.

Assume you're implementing a form to handle a menu command, like so:

void on_menu_command( ev::menu&, 
 menu::some_menu_id) { ... }

To handle two menu commands, new_file and open_file, you'll create these handlers:

void on_menu_command( ev::menu&,
    menu::new_file) { ... }
   void on_menu_command( ev::menu&,
    menu::open_file) { ... }

Tab Controls and Forms

Tabs are a very popular GUI paradigm. I've extended the tab control to allow the tab_type property to have values of normal or one_dialog_per_tab (in this case, the control hosts other forms). In the latter case, you can add new forms like this:

tab->add_form<form_type>( new_([args]) );

To add the login form I've talked about, you'd write:

tab->add_form<login>( new_() );

Once you've added at least one form, you can specify the number of tabs this tab form is to hold:

tab->count = 5;

Here I have five tabs. This will take the last added form and duplicate it as many times as needed. Assuming previously there was only one tab, the form on the first tab will be cloned another four times. That's right. You can clone any existing window!

So far I've shown you intrusive handling of events. In other words, you extend a window class and eventually respond to its events (or, if you're implementing a form, you respond to its notifications). Sometimes, however, you need to implement a behavior that applies to several windows (somewhat unrelated to each other).

Take, for instance, resizeability and skinability. You can implement them in an intrusive manner (creating some classes that implement the behavior, and then deriving your GUI classes from them as well). However, this will complicate the code, and it's not always feasible (take skinability for instance). By implementing behavior unintrusively, the behavior can be reused in other applications, and it can also be turned off easily.

For instance, create a non-intrusive event handler class, create an instance of it, and register it. When a new window is created, your handler instance is notified and you can choose to monitor this instance. If you do, you'll need to manually specify which events you want to monitor, like so:

// monitor button clicks
struct btn_handler : non_intrusive_handler {
 void on_new_window_create(wnd<> w) {
 if ( wnd<button> b = try_cast(w)) {
  b->events.on_click += mem_fn(&on_click,this);
 }
 }
 void on_click(button::ev::click&) { ... }
};

Registering the event handler is easy. Simply perform the following:

btn_handler bh;
window_base::add_non_intrusive_handler(bh);

Resizeability can be implemented in many ways, depending on your application. For instance, you could override the on_size event on each form and update the controls' positions based on the new form size (bad idea, lots of work). Or, on each form, you could create relations between the controls, like "a.x = b.x + b.width + 4;" (this is very flexible, but also requires lots of work).

As an alternative, on each form, you can mark controls either as sizeable or moveable on each axis. If a control is marked as sizeable on a certain axis, when the form's size changes, that control's size will update; if a control is marked as moveable on a certain axis, when the form's size changes, that control will be moved. This should suffice for most applications. I borrowed this idea from WTL's CResizeWindow and implemented it using a nonintrusive handler. Assume you have a dialog like in Figure 11. If, when resized, you'd like it to look like Figure 12, then you'll use this code:

resize(name, axis::x, sizeable);
resize(desc, axis::x | axis::y, sizeable);
resize(ok, axis::x | axis::y, moveable);
resize(cancel, axis::x | axis::y, moveable);

fig11.gif

Figure 11 Dialog

fig12.gif

Figure 12 Dialog Resized

Every GUI operation that fails will trigger an exception. This way, you'll know something went wrong. In debug mode, it will generate a failed assertion and the program will break into debug mode. This is much better than silently ignoring the error, seeing that something is wrong (visually), and then hunting for the bug.

Integration with Visual Studio 2005

Visual Studio is a great IDE, and one of its major advantages is that it can be extended. eGUI++ takes advantage of that; it comes with an Add-in that provides a New Form Class Wizard. It also provides a Visual Basic-like bar that allows you to handle control notifications on forms. To do so, just select a control, then you see a list with the notifications that that control can generate. Note that those that are already handled are shown in bold; click on an event and a handler will be added, if it's not already there—for an example, see Figure 13.

fig13.gif

Figure 13 Handle Control Notifications on Forms (Click the image for a larger view)

Under the hood, eGUI++ monitors the resource editor so that whenever something changes, the _form_resource.h files are updated if needed. And it works with code-completion, as I've already described in detail.

Implementing Behavior

Once you've built your GUI, the next step is to implement behavior and allow for data binding. Many forms are meant only for collecting data. For starters, you can implement a generic form class, which, at construction, gets data to manipulate binds it to the form's controls. Then you can specify a set of rules for validating the data. At destruction, if the validation rules are successful, the original data is updated with the values from the controls; otherwise, the original data remains unchanged. Thus, for each new form, you only need to create the form in the resource editor and then specify a set of rules to use in validating the data (as opposed to creating a new form class and duplicating logic).

Looking ahead, you can bridge the gap between Standard Template Library (STL) arrays and collections, and list controls and tree controls. I'm thinking of having an array of, let's say, employees, and a list control. I can bind the array to the control, like so:

list_ctrl->bind(employees);

As you might expect, this will update the list control. Moreover, any change on a list control's cell will be automatically synchronized with the employees array.

My intention in building eGUI++ was to create a cool library that makes GUI programming fun. If you're a C++ programmer, I certainly hope that you agree. You can download the source and binaries at torjo.com.

John Torjo is a C++ Programmer who is still in love with C++ after 10+ years of programming. John enjoys logging and GUI, and he also enjoys challenges. If you've got one, send him an e-mail. You'll find more information on his site at torjo.com.