John Torjo - C++ Expert

Win32 GUI Generics Library : Goals

Goals

Win32gui' goals are very bold:

  1. Make GUI code simple and easy to understand (read and maintain)
  2. Provide GUI RAII
  3. Make GUI programming safe
  4. Make it easy to handle events and manipulate standard controls
  5. Make it easy to sub-class existing windows and to create your own windows
  6. Bring dependency on wizards to a minimum
  7. Increase dialog programming
  8. Bridge the gap between STL & GUI, allowing for truly generic solutions
  9. Make C++ a RAD (Rapid Application Development)

 

Make GUI code simple and easy to understand

Who wants to know the deeply buried Win32 structures, like, LVITEM, TVITEMEX, LVFINDINFO, HDITEM, REBARBANDINFO, etc.? What code do you prefer? This:

// using win32gui
wnd<rebar> rb = create_wnd<rebar>(top);
rb->add_band( rebar_band_info()
    .style(RBBS_BREAK | RBBS_CHILDEDGE | RBBS_GRIPPERALWAYS)
    .width(170)
    .text("MyCombo ")
    .child( create_wnd<combo_box>(rb)) );
rb->add_band(
    rebar_band_info()
    .style(RBBS_GRIPPERALWAYS)
    .width(200)
    .text("MyTool ")
    .child( create_wnd<toolbar>(top)) );

or this:

REBARINFO     rbi;
REBARBANDINFO rbBand;
RECT          rc;
HWND   hwndCB, hwndTB, hwndRB;
DWORD  dwBtnSize;
INITCOMMONCONTROLSEX icex;

icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC   = ICC_COOL_CLASSES|ICC_BAR_CLASSES;
InitCommonControlsEx(&icex);
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);
if(!hwndRB) return NULL;
// Initialize the REBARINFO structure.
rbi.cbSize = sizeof(REBARINFO);  
rbi.fMask  = 0;
rbi.himl   = (HIMAGELIST)NULL;
if(!SendMessage(hwndRB, RB_SETBARINFO, 0, (LPARAM)&rbi)) return NULL;
// Initialize structure members that both bands will share.
rbBand.cbSize = sizeof(REBARBANDINFO);  // Required
rbBand.fMask  = RBBIM_COLORS | RBBIM_TEXT | RBBIM_BACKGROUND | RBBIM_STYLE | RBBIM_CHILD | RBBIM_CHILDSIZE | 
                RBBIM_SIZE;
rbBand.fStyle = RBBS_CHILDEDGE;
// Create the combo box control to be added.
hwndCB = CreateComboBox(hwndRB);
// Set values unique to the band with the combo box.
GetWindowRect(hwndCB, &rc);
rbBand.lpText     = "My Combo";
rbBand.hwndChild  = hwndCB;
rbBand.cxMinChild = 0;
rbBand.cyMinChild = rc.bottom - rc.top;
rbBand.cx         = 170;

// Add the band that has the combo box.
SendMessage(hwndRB, RB_INSERTBAND, (WPARAM)-1, (LPARAM)&rbBand);

// Create the toolbar control to be added.
hwndTB = CreateToolbar(hwndOwner, dwStyle);

// Get the height of the toolbar.
dwBtnSize = SendMessage(hwndTB, TB_GETBUTTONSIZE, 0,0);

// Set values unique to the band with the toolbar.
rbBand.lpText     = "MyTool ";
rbBand.hwndChild  = hwndTB;
rbBand.cxMinChild = 0;
rbBand.cyMinChild = HIWORD(dwBtnSize);
rbBand.cx         = 200;

// Add the band that has the toolbar.
SendMessage(hwndRB, RB_INSERTBAND, (WPARAM)-1, (LPARAM)&rbBand);

Of course, MFC/WTL offer you some wrappers for window classes such as re-bars, but they fall short when you want something more than trivial (like, set the ideal width of a re-bar band).

 

Provide GUI RAII

Win32gui has created a correspondence from C++ class to on-the-screen window class. But more important:

  • As soon as a window is created on the screen, its corresponding C++ object is created
  • As soon as a window is destroyed from the screen, its corresponding C++ object is destroyed

This releases this burden from you - the developers. You no longer need to monitor for events like WM_CREATE/ WM_INITDIALOG/ WM_(NC)DESTROY (you can still monitor for them, if you wish). In the class' constructor you know the on-screen window is created, and on the class' destructor you know the on-screen window is about to be destroyed.

Another advantage is that you can finally keep context data in the windows classes you create - and not worry that your C++ object will be destroyed too soon or too late.

 

Make GUI programming safe

For too long, you had to always check if a GUI operation was successful or not. This is tedious and error-prone, and makes code hard to read, understand and maintain.
Now, every GUI operation that fails will throw - thus, making it easier to handle exceptions. This makes the code easier to read and write. Finally, you can say what you want in less lines of code:

		 // move the "User Name" control 10 pixels to the right
rectangle r = child(IDC_user_name)->window_rect(rel_to_parent) + wnd_size(10,0);
child(IDC_user_name)->move(r);

 

Make it easy to handle events and manipulate standard controls

Handling events is a major feature of win32gui - they are just too easy and straighforward:

			        // Taken from "DirectX/video_in_player" sample

// sample_dlg.h
#ifndef SAMPLE_DLG_H
#define SAMPLE_DLG_H

struct sample_dlg : wnd_extend<dialog, sample_dlg> {
    static int dialog_id();
private:
    void fill_video_in_combo();
    void select_source(const std::string & in_source_name);
    void handle_video_events();
    void stop_play();
    // ...
};

#endif


// sample_dlg.cpp
#include "resource.h"
#include "sample_dlg.h"

using namespace win32::gui;
namespace {
    const int WM_VIDEO_IN = WM_APP + 1; // sent when there's any events coming from "Video In" signal
}

// handle events fro sample_dlg class
struct sample_dlg_handler : event_handler<...> {

    // when "IDC_sources" combo-box is clicked in order to be expanded
    handle_event on_dropdown() {
        window()->fill_video_in_combo();
        return command<IDC_sources,CBN_DROPDOWN>().HANDLED_BY(&me::on_dropdown);
    }
    // when user has selected something from "IDC_sources" combo-box 
    handle_event on_select_source() {
        window()->select_source( child(IDC_sources)->text() );
        return command<IDC_sources, CBN_CLOSEUP>().HANDLED_BY(&me::on_select_source);
    }
    // when user pressed "Stop" button
    handle_event on_stop() {
        window()->stop_play();
        return command<IDC_stop,BN_CLICKED>().HANDLED_BY(&me::on_stop);
    }
    // when there is Video In signal    
    handle_event on_video_in_msg() {
        window()->handle_video_events();
        return event<WM_VIDEO_IN>().HANDLED_BY(&me::on_video_in_msg);
    }
};
				

Notice the fact that the event handling takes places only in the source file. Thus, in case you need to add/delete/change events, only one source file gets recompiled!

As for easy manipulating of standard controls, it's a very reasonable request, and win32gui provides it:

// insert columns into List Control
child<list_ctrl>(IDC_files)->add_col( lv_col().text("File Name").width(300) );
child<list_ctrl>(IDC_files)->add_col( lv_col().text("Type").width(100) );
child<list_ctrl>(IDC_files)->add_col( lv_col().text("Size").width(100) );

// fill the list control
int idx = 0;
for ( file_infos::iterator begin = new_files.begin(), end = new_files.end(); begin != end; ++begin) {
    files->add_item( lv_item() );
    files->item( idx, 0, lv_item().text( begin->second.name).image(1) );
    bool is_dir = (begin->second.attrib & _A_SUBDIR) != 0;
    files->item( idx, 1, lv_item().text( is_dir ? "Directory" : "File" ));
    std::ostringstream size; 
    // ...
    files->item( idx, 2, lv_item().text( size.str()));
    ++idx;
}

 

Make it easy to sub-class existing windows, and to create your own windows

On a non-trivial GUI application, you usually need to:

  • sub-class dialogs (often)
  • sub-class controls (not so often)
  • create your own window classes (rarely)
  • manually create windows (rarely, usually you use dialogs)

All of the above are very easy to do using win32gui.
Sub-classing dialogs:

				// explorer_dlg.h
struct explorer_dlg :   wnd_extend<dialog,explorer_dlg> {
    static int dialog_id();
    // ...
};


// explorer_dlg.cpp
#include "explorer_dlg.h"
#include "resource.h"
using namespace win32::gui;
struct explorer_handler : event_handler<explorer_handler, dialog,explorer_dlg> {
    // handle events
};

int explorer_dlg::dialog_id() { return IDD_do_explore; }

// ... other explorer_dlg functions

			

Sub-classing controls:

// Note: when extending a control, you usually implement auto-mapping
// Auto-mapping is explained in CUJ/ August 04 issue

// controls.hpp
struct check_box : wnd_extend<button, check_box>, auto_mapping<check_box> {
    static create_info def_create_info();

    static bool matches_hwnd(HWND h);
    bool is_checked() const;
    // ... extra functions
};

// controls.cpp
bool check_box::matches_hwnd(HWND h) {
    bool matches = ...;
    return matches;        
}

bool check_box::is_checked() const {
    return chk_state() == checked;
}

// ... extra functions

Create your own window classes:

					// custom_wnd.h
struct custom_wnd : wnd_extend<window_base,custom_wnd> {
    static win32::gui::create_info def_create_info();
    // ... other members
};


// custom_wnd.cpp
struct custom_wnd_handler : event_handler<custom_wnd_handler, window_base, custom_wnd> {
    // ... handle events, the usual way
};

create_info custom_wnd::def_create_info() {
    ::RegisterClassEx( &window_class_info().wnd_class_name("My Custom Window").raw_info() );
    return create_info().class_name( "My Custom Window")
            .style( WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_TABSTOP);
}

// ... other members
				

Manually create windows:

				wnd<> parent = ...;
// default edit box
create_wnd<edit>(parent);
// edit box with specific rectangle and with extra style (align text to the right)
create_wnd<edit>(parent, create_info().rect(10,10,200,40).add_style(WS_EX_RIGHT) );
// label with specific rectangle and centered text 
create_wnd<label>(parent, "My label", create_info().rect(10,100,200,40).style(SS_CENTER) );
			

Bring dependency on wizards to a minimum

Wizards – we both love and hate them. Because of the language complexity, Wizards and C++ never mixed too well. In your code, you sometimes use macros, typedefs, namespaces, small classes, generics, #ifdefs, implementation classes, and so on, which make it just so difficult for Wizards to keep up. As code gains complexity, Wizards become a time bomb waiting to happen – they will soon lose track of what they’re supposed to do, and worse, sometimes they end up modifying the wrong code.

That’s why I recommend: use mostly creation Wizards – those that create something (like, a C++ class), and then get out of the way. A wizard that will create a win32gui class is extremely easy to make – it’s almost the same as a C++ class creator Wizard. After that, win32gui makes handling events a piece of cake – thus, no need for wizards.

Even so, because of how event handler classes are defined, it’s easier to conceive Wizards to add/modify/delete events. Each event class derives from ‘event_handler<>’, and each event starts with ‘handle_event’ – thus, making it extremely easy for wizards to find/modify events/ event classes.

 

Increase dialog programming

Dialogs – they are just too cute... You can set so many of their properties at design time – and especially their layout. Resizability is easy to add (just extend the resizable_wnd class). There – most of your GUI needs can be solved just by putting some controls on a dialog and eventually add some code to glue them together.

You can have a dialog on a dialog, splitters on a dialog, and all of this can be set at dialog design time!

The September 04 issue of C/C++ Users Journal has a big section dedicated to this.

Bridge the gap between STL & GUI, allowing for truly generic solutions

Unfortunately, most GUI libraries have started development quite a while ago, and they did not take advantage of templates, STL or STL algorithms. Thus, they ended up using proprietary String classes, proprietary algorithms, duplicating existing code, and worse, making it very hard for STL and GUI to inter-operate.

However, this is not the case for win32gui. I’ve already taken the first steps – find_wnd_range<> returns an STL random iterator you can use in any STL algorithm; all existing standard controls use std::string when dealing with text (the text on an edit control, on a label, on list control items, etc.); thrown exceptions are std::exception derived; and so on. Existing window classes and your future window classes can be easily extended from. Even more, you can extend multiple window classes at once, depending on your programming needs. The common scenario is a resizable dialog, when you extend from both dialog and resizable_wnd classes.

 

Make C++ a RAD

As I've already said, I will gradually show you that C++ is the best language to do GUI - generics playing a major role. This implies that C++ will become a RAD - it's just a matter of time ;).

The trip has already started:

  • easy event handling
  • easy manipulation of standard controls/bars
  • STL-friendly
  • enhanced dialog programming
  • menu command manipulation

But the thrill has just began - there's so much more to come!