Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / MFC

A Custom GUI System - Part 2: Dialogs and Controls

2.77/5 (10 votes)
31 May 20064 min read 1   585  
This article explains how the support for dialogs and various controls were implemented in the custom GUI system.

Foreword

In my previous tutorial I've tried to explain the layout of the basic framework for a custom GUI system and I assume you are familiar with solutions I've introduced. If you don't know what I'm talking about, or if you feel like you need to refresh your mind, just follow the link: Custom GUI System - Part 1: Basic Framework. (Note: As in the first part of this tutorial, all 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.)

Dialogs

Let's begin with the easier part of today's subject: the class windialog. It is inherited from the class winbase and it is a plain translation of this class to the language of dialogs. The main difference is that the standard creation method create() is overridden so that it does nothing. It simply returns false. The reason for this is that the class windialog has its own creation methods create_dialog() and create_modal(). The first of these creates a modeless dialog and the second one creates a modal dialog. Both methods take three parameters: the application instance handle, the parent window handle and the resource ID of the dialog. All initialization takes place within these methods so simply calling them creates a dialog object and opens the dialog window using a template designed in the resource editor (from my point of view, the visual resource editors, even the one in VC++, are very useful and relatively clean code generation tools). What follows are the differences between dialog_proc() and window_proc() shown in code (code that is identical for both procedures was left out - reference the updated window_proc() in the section Controls below):

//different procedure type
BOOL CALLBACK dialog_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
   winbase* wb = 0;

   //we process WM_INITDIALOG instead of WM_CREATE
   if(msg == WM_INITDIALOG)
   {
      //exactly the same code as in window_proc()
   }

   //get the window this message belongs to
   wb = winpool::get_window_from_hwnd(hwnd);

   //call message specific handlers
   switch(msg)
   {
   //exactly the same code as in window_proc()
   // .
   // .
   // .
   //up to call to generic handler
   default:
      if(wb)
      //we have the pointer to the window
         return wb->on_message(msg, wp, lp);
   }
   //and for unprocessed messages
   return false;
}

Controls

This section can be started with taking a short look at the updated window_proc(). You may remember that this was the procedure registered for our frame window in the first part of this tutorial. It is also used by custom controls. As you will see there are new handler calls in the procedure. These handlers (defined in class winbase) allow us to process the respective messages separately (not via the on_message() handler):

LRESULT CALLBACK window_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
   winbase* wb = 0;

   if(msg == WM_CREATE)
   {
      wb = winpool::get_created_window();
      wb->post_create_window(hwnd);
      return wb->on_create(wp, lp);
   }

   wb = winpool::get_window_from_hwnd(hwnd);

   switch(msg)
   {

   //////////////////////////////////////////////
   //NEW - added new handlers to support custom controls
   case WM_COMMAND:
      return wb->on_command(wp, lp);
   case WM_PAINT:
      return wb->on_paint(wp, lp);
   case WM_NCPAINT:
      return wb->on_ncpaint(wp, lp);
   case WM_MOUSEMOVE:
      return wb->on_mousemove(wp, lp);
   case WM_LBUTTONDOWN:
      return wb->on_lbuttondown(wp, lp);
   case WM_KILLFOCUS:
      return wb->on_killfocus(wp, lp);
   case WM_SETFOCUS:
      return wb->on_setfocus(wp, lp);
   //////////////////////////////////////////////

   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);
   default:
      if(wb)
         return wb->on_message(msg, wp, lp);
      else
         return DefWindowProc(hwnd, msg, wp, lp);
   }
}

As the next step, let us declare the class winctrl, a base class for the custom controls. This class inherits from the class winbase and implements access to custom control states, support for manipulating the outlook of controls, support for message routing as well as the most important part (I think), the method, attach(), that allows attaching of class objects to any dialog controls created in the resource editor. Finally a base class for subclassed controls, the class wintruectrl, can be introduced. This class inherits from the class winctrl so it supports all aspects of custom controls. However subclassed controls have a single but considerable difference: instead of window_proc() they have their own procedure subcls_proc(), that doesn't call on_create() since these controls get subclassed after WM_CREATE is processed. The subclass procedure subcls_proc() calls the on_message() handler for all unprocessed messages. The default implementation of this handler calls the original window procedure of the subclassed control. Also note that the default implementation of all handlers in the class wintruectrl must call the original window procedure of the subclassed control. In order to make this happen, the class winbase contains new methods for subclassing/unsubclassing a window and a method for calling the original window procedure of the subclassed window.

Using the system

For the purpose of this tutorial, we will create:

  1. a test dialog template with a single selection listbox (will be subclassed) and two buttons (will be custom).
  2. a class MyButton inherited from the class winctrl, that will create a clickable, flat button.
  3. a class MyListbox inherited from the class wintruectrl, that will subclass the generic listbox class and will allow us append strings at the end of the list.
  4. a class MyDialog, inherited from windialog, that will encapsulate our test dialog template.

We will also use a subclass of the generic window, called MyWindow, from the first part of the tutorial. This window will center itself on the screen and it will also load and display a cross-hair cursor. We will re-implement the on_rbuttonup() handler so that it opens our test dialog. The last thing we do is that we add two lines of code for registering/unregistering our custom MyButton window class:

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");

   ///////////////////////////////////////////////////
   //NEW - registering MyButton class 
   //could be put into winpool::open_session()
   winpool::register_class(hinst, "MyButton");
   ///////////////////////////////////////////////////

   //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 */}

   ///////////////////////////////////////////////////
   //NEW - unregistering MyButton class,
   //could be put into winpool::close_session()
   winpool::unregister_class(hinst, "MyButton");
   ///////////////////////////////////////////////////

   //clean up
   winpool::unregister_class(hinst, "MyWindow");
   winpool::close_session();

   return 1;
}

Last Words

Congratulations, you've made it to the end! We have extended our little GUI system to something like this:

winbase           -> MyWindow
|- windialog      -> MyDialog
'- winctrl        -> MyButton
   '- wintruectrl -> MyListbox

Now you should be able to develop your own window types, controls, etc. For inspiration, you can have a look at the Win32 Pro package (you can find this package here - feel free to download it and experiment with it). If in doubt, don't hesitate to drop me a mail:

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here