Win32 GUI Generics Library : Goals
Goals
Win32gui' goals are very bold:
- Make GUI code simple and easy to understand (read and maintain)
- Provide GUI RAII
- Make GUI programming safe
- Make it easy to handle events and manipulate standard controls
- Make it easy to sub-class existing windows and to create your own windows
- Bring dependency on wizards to a minimum
- Increase dialog programming
- Bridge the gap between STL & GUI, allowing for truly generic solutions
- 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).
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.
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.
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.
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!