A Custom GUI System






2.11/5 (7 votes)
Oct 12, 2005
3 min read

68842

655
If you demand the most possible control of your code, if you like to force Windows looking your way, and if you are lazy enough, this tutorial is exactly for you.
Motivation
If you don't like tools that automatically create loads of useless code you mostly won't understand it because the authors didn't bother to document it. If you demand the most possible control of your code, if you like to force Windows looking your way, and if you are lazy enough, this tutorial is exactly for you.
Well, some months ago these were the reasons that made me think of an ultimate solution of a simple, expandable and easy-to-use GUI system. Actually I liked the ideas behind MFC like: separate class for each window type, handlers for messages instead of huge window procedures, but I wanted them in combination with the "low-level" nature of pure Win32 API.
Description
When you look at the source code, you will notice that the basic framework consists of two classes. (Note: Both classes have more or less detailed documentation in the source files. They were extracted from a larger project, and for the purpose of this tutorial, they were stripped down to substantial elements needed to present the basic principles behind them.)
The first class is winbase
, a class that represents a generic window (like CWnd
in MFC). Its core functionality relies on the standard creation method create()
, pre and post create initialization methods and message handlers. Message handlers are implemented as message specific, one handler for one type of message, except for the generic message handler on_message()
that provides a way of extending the functionality of the system without recompiling the whole API. The generic handler has also the ability to catch unprocessed messages. All functions in this class are virtual
.
The second class is winpool
that is a placeholder for all application windows and also acts as an application manager. This class has only static methods as there is no need for multiple instances of this class per application.
Now comes 'the magic' - the common window procedure passed to CreateWindowEx()
that gets called in create()
:
LRESULT CALLBACK window_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { winbase* wb = 0; //special case: at the moment we don't have //the hwnd associated with our window object if(msg == WM_CREATE) { //find the window we are creating - this window has WDS_INITIAL flag set wb = winpool::get_created_window(); //init windows`s hwnd and data members that need to be obtained //through this hwnd (hdc,...) and set window`s display state to WDS_OPEN wb->post_create_window(hwnd); //bail with calling the user defined (or default) on_create() handler return wb->on_create(wp, lp); } //standard case: we already have the hwnd assigned //to our window object, window has WDS_OPEN flag set //get the window this message belongs to wb = winpool::get_window_from_hwnd(hwnd); //call message specific handlers switch(msg) { case WM_LBUTTONUP: return wb->on_lbuttonup(wp, lp); case WM_RBUTTONUP: return wb->on_rbuttonup(wp, lp); case WM_CLOSE: return wb->on_close(wp, lp); case WM_DESTROY: return wb->on_destroy(wp, lp); case WM_NCDESTROY: return wb->on_ncdestroy(wp, lp); //call generic handlers default: if(wb) //we have the pointer to the window return wb->on_message(msg, wp, lp); else //for the sake of security when we for any reason // don't have the pointer to the window return DefWindowProc(hwnd, msg, wp, lp); } }
Using the system
Well, the system is created, it should do something useful. For the purpose of this tutorial we will make a simple window that will react on mouse clicks and key strokes. So we create a subclass of the generic window, called MyWindow
. This window will center itself on the screen and it will also load and display the cross-hair cursor. Then we override the handlers so that when the user left-clicks the window, a message box pops up asking whether the user likes to close the window. When the user right-clicks the window a message box pops up showing the coordinates of the click, and when the user strokes a key a message box pops up showing the ASCII value of the key being stroked. Making the application run is then a piece of cake:
int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hprev, LPSTR cmdline, int showcmd) { //initialize application manager winpool::open_session(hinst); //register our window class winpool::register_class(hinst, "MyWindow"); //window instance MyWindow wnd; //position and dimensions array (dims={x, y, w, h}) //we don`t need to specify position since the window centers itself int dims[] = {0, 0, 640, 480}; //initialize window data members needed for creating the window //(e.g hinstance, classname and display state to WDS_INITIAL) wnd.pre_create_window(hinst, "MyWindow"); //create the window - we specify only the //owner (NULL=desktop), caption and dimensions wnd.create(NULL, "Tutorial 1 - Use mouse buttons" " and keyboard keys", dims); //finally show the window on the screen wnd.show_window(true); //run message loop until WM_QUIT is encountered while(winpool::dispatch_message()){ /* run */} //clean up winpool::unregister_class(hinst, "MyWindow"); winpool::close_session(); return 1; }
Last Words
As you can see we ended up with a running GUI system that is easy-to-use and can be further expanded. Now you should understand how the "wheels are turning" in the Win32 Pro package (you can find this package here - feel free to download it and experiment with it). See the next tutorial, where I try to explain how I implemented the support for dialogs, custom and subclassed controls.