Inside the VCL: Window Creation Fundamentals
By Damon Chandler


When encountering the word "window" in a Windows API reference, the first thought that may come to mind is a VCL Form.  Indeed, a Form is a type of window
usually a top-level windowbut it's rarely the only window that you'll use in an application.  As it turns out, all TWinControl descendants are windows of some sort.  For example, notice that the Handle property is first introduced in the TWinControl class.  This was not a random design decision, but rather due to the fact that all windowed controls, including Forms, possess a window handle.  This handle is used throughout the API as an abstract layer between a window's actual memory block and the functions that can be used to manipulate it.  For example, we saw in the first article ("From Messages to Events") the use the of SendMessage API function.  Recall that the first parameter to this function specifies the handle of the window that is to receive the message.  In fact, nearly all API functions that serve to manipulate an aspect of a windowed control will require a window handle.  In many  situations, however, there's no need to use the Handle property; most of us resort to it only when a VCL solution does not exist or is inadequate. 

 

The TWinControl class serves as the "glue" between the Windows API and all windowed VCL controls.  For this reason, in order to understand the TWinControl class, we'll need to first examine the window creation process from an API point of view.  As we did in the first article ("From Messages to Events"), we'll make several fundamental connections between the Windows API and the VCL.  Here we'll focus specifically on the TWinControl class and more specifically on the window creation process.
 
 
I.  Window Creation

 

Most applications that are designed for Windows require some sort of user interaction.  Clearly, the predominant means of communication between the user and the application is accomplished by a visual object known as a window.  In some cases, the window serves as means of user input, while in others, windows are used to inform the user of a particular affair.  While this is old news to anyone who has developed a graphical user interface, many seasoned C++ programmers have strictly developed console or “text-based” applications.  Moreover, as the VCL abstracts much of the window creation process, many proficient C++Builder developers run into to difficulties when the need to customize or troubleshoot arises.  For these reasons, let's now closely examine the process of creating a window directly via the Windows API (i.e., without the use of the VCL).  As you'll see shortly, this process, while cumbersome, is relatively straightforward and can be followed very much like a recipe.

 

 

IA.  The CreateWindow Function

 

Creating any windowed control is accomplished via the CreateWindow API function.  In a raw API implementation, there is no visual developmentall controls are windowed control, and all controls must be created through code.  While this is true in C++Builder as well, the level of abstraction that the VCL introduces oftentimes makes this point a subtlety.  For simple controls such as buttons or edit controls, a single call to CreateWindow is all that's necessary.  This is simply due to the fact that there are pre-registered classes for such controls.  Let's examine the signature of the CreateWindow function to clarify this argument:

 

HWND CreateWindow(

LPCTSTR lpClassName,

LPCTSTR lpWindowName,

DWORD dwStyle,

int x,

int y,

int nWidth,

int nHeight,

HWND hWndParent,

HMENU hMenu,

HANDLE hInstance,

LPVOID lpParam

                 );

 

The function requires seven parameters, and returns a handle to the newly created window.  The first parameter, lpClassName, is a pointer to a string representing the name of the class of window that's to be created.  The stipulation here is that this class must be registered with Windows before the call to the CreateWindow function is made.  Indeed, it would serve little purpose to call the CreateWindow function specifying a class of window that Windows itself doesn't know how to create.  In fact, the Windows API makes available several types of predefined, pre-registered control classes.  Among the most common examples are those that fall under the Standard and Common Control classes.

 

 

IB.  Control Class Registration

 

Again, the Windows API presents several types of predefined classes of controls that can be used without the need for explicit registration.  These controls are pre-registered with Windows and included as part of the API's user-interface service.  Standard Controls such as buttons, edit controls, static controls, combo boxes, list boxes, and the like, are already registered.  For example, to create a button, you'd simply pass “BUTTON” as the lpClassName argument of the CreateWindow function.  Similarly, Common Controls such a list views, tree views, header controls, tab controls, toolbars, etc., are registered by calling the InitCommonControlsEx API function.  What if you want to create a custom control class?  Well, you'll have to first register your class with the system by using the RegisterClass function:. 

 

ATOM RegisterClass(

    const WNDCLASS *lpWndClass

   );

 

The RegisterClass function takes only one parameter, a pointer to a WNDCLASS structure that defines the attributes of the class of window.  Here's what the WNDCLASS structure looks like:

 

typedef struct _WNDCLASS {

    UINT    style;

    WNDPROC lpfnWndProc;

    int     cbClsExtra;

    int     cbWndExtra;

    HANDLE  hInstance;

    HICON   hIcon;

    HCURSOR hCursor;

    HBRUSH  hbrBackground;

    LPCTSTR lpszMenuName;

    LPCTSTR lpszClassName;

} WNDCLASS;

 

Several of the members of the WNDCLASS structure should look familiar.  For example, the lpfnWndProc is a pointer to the window procedure of the window.  The hIcon and hCursor members are the handles to the default icon and cursor of the window, respectively.  The hbrBackground member is the handle to the brush that can be used to paint the background of the window.  Most importantly, the last member, lpszClassName, defines the name of the window's class (e.g., “TForm1”). 

 

Once a WNDCLASS structure is initialized, its address is passed into the RegisterClass function.  Let's look at an example; specifically, let's examine the process of creating a new class of top-level window, “TMyForm”, as demonstrated in Listing 2.0.

 

 

 

//----------------------------------------------------------------------

#include <windows.h>

//----------------------------------------------------------------------

 

// our new window class name

const char NewClassName[] = "TMyForm";

//----------------------------------------------------------------------

 

// main window procedure

LRESULT CALLBACK WndProc(HWND HWnd, unsigned int Msg, WPARAM WParam,

    LPARAM LParam)

{

    // this window will actually do no special processing

    // except terminate the application when destroyed

    switch (Msg)

    {

        case WM_DESTROY:

        {

            // will place WM_QUIT in our message queue

            // (strictly speaking, the QS_QUIT flag)

            PostQuitMessage(0);

            return 0;

        }

    }

    return DefWindowProc(HWnd, Msg, WParam, LParam);

}

//----------------------------------------------------------------------

 

int APIENTRY WinMain(HINSTANCE HInstance, HINSTANCE HPrevInstance,

    LPTSTR lpCmdLine, int nCmdShow)

{

    WNDCLASS wc;

    memset(&wc, 0, sizeof(WNDCLASS));

 

    // initialize the WNDCLASS structure

    wc.style = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS;

    wc.lpfnWndProc = WndProc;

    wc.hInstance = HInstance;

    wc.hCursor = LoadCursor(NULL, IDC_ARROW);

    wc.hbrBackground = GetStockObject(LTGRAY_BRUSH);

    wc.lpszClassName = NewClassName;

 

    // register our new class of window

    if (RegisterClass(&wc))

    {

        // once registered, we are free to create the window

        HWND HWnd = CreateWindow(NewClassName, "MyForm1",

                                 WS_OVERLAPPEDWINDOW |

                                 WS_CAPTION | WS_VISIBLE,

                                 CW_USEDEFAULT, CW_USEDEFAULT,

                                 CW_USEDEFAULT, CW_USEDEFAULT,

                                 NULL, NULL, HInstance, NULL);

 

        if (HWnd)

        {

            // show the new window

            ShowWindow(HWnd, nCmdShow);

            UpdateWindow(HWnd);

 

            // main message pump

            MSG msg;

            while (GetMessage(&msg, NULL, 0, 0))

            {

                TranslateMessage(&msg);

                DispatchMessage(&msg);

            }

        }

    }

    return 0;

}

//----------------------------------------------------------------------

 

 

Listing 2.0

 

 

Note that the WinMain function presented in Listing 2.0 is identical to the WinMain function that is created by the VCL (i.e., what you see by viewing your Project's source code; cf, Listing 1.4 of the article "From Messages to Events").  In addition, notice that we provide a default window procedure since our new class, TMyForm, is not based on an existing window class.  That is, the window procedure that we specify will define the attributes and behavior of our new class of window.  In fact, our simple window procedure (WndProc) definition of Listing 2.0 does little more than call the DefWindowProc API function.  This latter call serves to ensure that our new type of window will behave like any standard top-level window.

 

Focus on the WinMain definition of Listing 2.0; notice that the implementation boils down to three basic steps.  First, we declare and initialize a WNDCLASS structure.  Next, we call the RegisterWindow API function, passing the address of this structure.  Finally, after the class has been successfully registered, we call the CreateWindow function, passing the newly registered class name (NewClassName in this case) as the lpClassName parameter.  These three steps must be followed for every window of a unique class that is to be created.

 

Clearly, without some degree of encapsulation, this process not only becomes extremely cumbersome, but it's also prone to error and difficult to maintain.  Fortunately, all of this is work is encapsulated by the TWinControl class, whose specifics and references to this section will be presented in the next.  For now, let's return to the CreateWindow function and examine its other parameters.

 

HWND CreateWindow(

LPCTSTR lpClassName,

LPCTSTR lpWindowName,

DWORD dwStyle,

int x,

int y,

int nWidth,

int nHeight,

HWND hWndParent,

HMENU hMenu,

HANDLE hInstance,

LPVOID lpParam

                );

 

The second parameter, lpWindowName, is the actual caption of the window.  In C++Builder, the first Form in an application, by default, uses “Form1” as this parameter.  The dwStyle parameter is used to indicate the various attributes of a window.  For example, the WS_OVERLAPPED style is used to create a standard top-level window.  This style is used by the TForm class by default as well.  Similarly, the WS_POPUP style can be used to create a caption-less and border-less window.  You probably already know that the TForm::BorderStyle property can be used to create windows of varying border styles.  You now know exactly what the manipulation of this property does; namely, it changes the various style flags that are passed as the dwStyle parameter.  Likewise, the x, y, nWidth, and nHeight parameters are changed when you change the Top, Left, Width, and Height properties of the form at design-time, respectively.  If the window being created is a child window, The hWndParent parameter is used to indicate the window's parent.  For top-level windows, this parameter is set to the handle of the desktop window, implicitly by specifying NULL, or explicitly by a call to the GetDesktopWindow API function.  The TForm class uses the handle of the zero dimension TApplication window as this parameter.  The hMenu parameter can be used to indicate the handle of the window’s main menu, if present, and the hInstance parameter is specified as the handle to the application’s instance queried via the global HInstance variable.  The last parameter, lpParam, is used to store any additional “user-defined” information.

 

We have just examined how to create a new window class that is not based upon an existing class.  Let's now investigate how to perform a technique called superclassing that's allow you to create a new class that's based on an existing, pre-registered, window class.  For instance, the VCL TButton class actually creates a new window class of type TButton that's based on the BUTTON Standard Control.  In this case, the TButton and TWinControl classes perform the superclassing for us.  Using a tool such as Microsoft's Spy++, you can verify that the class name of a TButton control is indeed "TButton" and not "BUTTON". 

 

 

IC.  Superclassing

 

As I mentioned, superclassing is a technique used to create a new window class from an existing, pre-registered, window class (base class).  Similar to the process of creating a new window class from scratch, superclassing involves declaring and initializing a WNDCLASS structure for the new (super) class.  The difference here, is that the GetClassInfo API function is first called to initialize the WNDCLASS structure with information from the parent class. 

 

bool GetClassInfo(

    HINSTANCE  hInstance,

    LPCTSTR  lpClassName,

    LPWNDCLASS  lpWndClass

   );

 

The address of the default window procedure of the parent class, stored in the lpfnWndProc data member of the WNDCLASS variable whose address is passed into GetClassInfo, is then stored for later use in the window procedure of the superclass.  This step is very similar to what was presented in the example of instance subclassing in the first article ("From Messages to Events"), where the replacement window procedure directly called the original window procedure (cf, Listing 1.18).  While for subclassing, the replacement window procedure was termed the "subclass procedure”, here, the new window procedure is called the "superclass procedure".  After the address of the base class's window procedure is stored, this superclass procedure is specified as the lpfnWndProc member of the WNDCLASS structure for the superclass.  Finally, the handle to the application's instance (hInstance) is specified as the hInstance parameter, and the new class name is specified as the lpClassName parameter. 

 

Let's work through an example of superclassing the BUTTON Standard Control class.  Listing 2.1 illustrates this process, where the name of the superclass is specified as "TMyButton".

  

 

 

//----------------------------------------------------------------------

#include <windows.h>

//-----------------------------------------------------------------------

 

// our new window class name

const char NewFormClassName[] = "TMyForm";

 

// our new button class name

const char NewButtonClassName[] = "TMyButton";

 

// will hold the address of the base (BUTTON)

// class's default window procedure

FARPROC ButtonDefWndProc = NULL;

//----------------------------------------------------------------------

 

// main window procedure

LRESULT CALLBACK FormWndProc(HWND HWnd, unsigned int Msg, WPARAM WParam,

    LPARAM LParam)

{

    // this window will actually do no special processing

    // except terminate the application when destroyed

    switch (Msg)

    {

        case WM_DESTROY:

        {

            // will place WM_QUIT in our message queue

            // (strictly speaking, the QS_QUIT flag)

            PostQuitMessage(0);

            return 0;

        }

    }

    return DefWindowProc(HWnd, Msg, WParam, LParam);

}

//----------------------------------------------------------------------

 

// button window procedure

LRESULT CALLBACK ButtonWndProc(HWND HWnd, unsigned int Msg,

    WPARAM WParam, LPARAM LParam)

{

    //

    // this window will actually do no special processing

    //

 

    // call the original BUTTON window procedure

    return CallWindowProc(ButtonDefWndProc, HWnd, Msg, WParam, LParam);

}

//----------------------------------------------------------------------

 

 

int APIENTRY WinMain(HINSTANCE HInstance, HINSTANCE HPrevInstance,

    LPTSTR lpCmdLine, int nCmdShow)

{

    WNDCLASS form_wc, button_wc;

    memset(&form_wc, 0, sizeof(WNDCLASS));

    memset(&button_wc, 0, sizeof(WNDCLASS));

 

    // initialize the Form's WNDCLASS structure

    form_wc.style = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS;

    form_wc.lpfnWndProc = FormWndProc;

    form_wc.hInstance = HInstance;

    form_wc.hCursor = LoadCursor(NULL, IDC_ARROW);

    form_wc.hbrBackground = GetStockObject(LTGRAY_BRUSH);

    form_wc.lpszClassName = NewFormClassName;

 

    // initialize a WNDCLASS structure for the "BUTTON"

    // base class via the GetClassInfo API function

    if (!GetClassInfo(NULL, "BUTTON", &button_wc)) return false;

 

    // store the address of the original window procedure

    ButtonDefWndProc = reinterpret_cast<FARPROC>(button_wc.lpfnWndProc);

    // specify the address of the subclass procedure

    button_wc.lpfnWndProc = ButtonWndProc;

 

    // initialize the rest of our button's WNDCLASS

    // structure for superclassing

    button_wc.hInstance = HInstance;

    button_wc.lpszClassName = NewButtonClassName;

 

    // register our new class of window and button

    if (RegisterClass(&form_wc) && RegisterClass(&button_wc))

    {

        // once registered, we are free to create the window

        HWND HWnd = CreateWindow(NewFormClassName, "MyForm1",

                                 WS_OVERLAPPEDWINDOW |

                                 WS_CAPTION | WS_VISIBLE,

                                 CW_USEDEFAULT, CW_USEDEFAULT,

                                 CW_USEDEFAULT, CW_USEDEFAULT,

                                 NULL, NULL, HInstance, NULL);

                                

        if (HWnd)

        {

            // create the new button

            CreateWindow(NewButtonClassName, "MyButton1",

                         WS_CHILD | WS_VISIBLE |

                         BS_PUSHBUTTON,

                         10, 10, 200, 75,

                         HWnd, NULL, HInstance, NULL);

 

            // show the new window

            ShowWindow(HWnd, nCmdShow);

            UpdateWindow(HWnd);

 

            // main message pump

            MSG msg;

            while (GetMessage(&msg, NULL, 0, 0))

            {

                TranslateMessage(&msg);

                DispatchMessage(&msg);

            }

        }

    }

    return 0;

}

//----------------------------------------------------------------------

 

Listing 2.1

 

 

Similar to the implementation in Listing 2.0, our WinMain function definition of Listing 2.1 first declares, then initializes a WNDCLASS variable for the TMyForm class.  Here, we also declare a WNDCLASS variable, button_wc, for our new button class.  Instead of explicitly initializing its data members, we first use the GetClassInfo API function, passing NULL as the hInstance parameter, the class name of the parent class, "BUTTON", as the lpClassName parameter, and the address of our WNDCLASS variable as the lpWndClass parameter.  We pass NULL as the hInstance parameter since the BUTTON class is a defined by Windows itself.  After a successful call to GetClassInfo,