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, button_wc is initialized with the WNDCLASS information used to register the BUTTON class.  It is here that we extract and store the default window procedure of the parent class (stored in ButtonDefWndProc).  We then change button_wc's hInstance data member to reflect that of our application's instance, the lpfnWndProc to reflect that of our superclass procedure, and the lpClassName data member to reflect the new class name, "TMyButton".  These are the only members that need be initialized with information from our current application.  After registering both new classes, and creating a new TMyForm window, we then use the CreateWindow function to create a new TMyButton windowed control.  Notice that we specify the WS_CHILD and BS_PUSHBUTTON styles in the call to CreateWindow.  The latter is a style of the BUTTON class, which is valid in our case since this is our parent class.  The former is used to indicate that our button is a child of another window, in this case our TMyForm window.  Child windows are typically referred to as “windowed controls” or simply “controls’.   Notice that we specified HWnd (i.e., the handle of our TMyForm window) as the hWndParent member in the call to CreateWindow for our button.

 

Superclassing is performed extensively throughout the VCL, namely, by every TWinControl descendant that extends an existing control class.  For example, the TListBox class is a superclass of the LISTBOX standard control class.  Similarly, the TListView class is a superclass of the WC_LISTIVEW common control class.

 

Now that you're familiar with the specifics of the CreateWindow API function, you can, for most projects, forget about ever using it.  Similarly, you'll rarely need to explicitly call the RegisterWindow function or initialize a WNDCLASS structure.  This applies to the specifics of superclassing as well.  Namely, the TWinControl class encapsulates all of this work.  Indeed, this is a firsthand indication of the level of abstraction presented by the VCL framework.  Such encapsulation makes Rapid Application Development a reality, and continues to shine as one of C++Builder’s strong-points.  Nonetheless, a strong framework and ease of use, is not alone, a sufficient indication of good design.  Flexibility is a crucial when working with such a complicated interface as the Windows API.  As you'll soon see, the VCL will never restrict you from performing low-level API manipulations.

 

 

II.  Window Creation via the TWinControl Class

 

In the previous section, we discussed the basics of the CreateWindow and RegisterClass API functions.  More importantly, we learned that the creation of a windowed control of a unique class requires the initialization of a WNDCLASS structure and the registration of this class of window, prior to the call to the CreateWindow API function.  We also examined the steps necessary to superclass an existing control class.  Now, let's investigate how the TWinControl class encapsulates all of this work via a series of virtual member functions.  Our goal in this section is to examine each of these member functions and discover which of the required steps from the previous section each encapsulates.  Since we are approaching this task with knowledge of the underlying API mechanisms, decoding the TWinControl implementation shouldn't be too difficult.

 

 

IIA.  The CreateParams Function

 

As mentioned nearly to the point of redundancy, the first task in the creation of a custom window is to get that window’s class registered.  Specifically, you need to first initialize a WNDCLASS structure.  This is the task of the TWinControl::CreateParams member function.  This function accepts only one argument, a reference to a TCreateParams structure:

 

struct TCreateParams

{

    char *Caption;

    long Style;

    long ExStyle;

    int X;

    int Y;

    int Width;

    int Height;

    HWND WndParent;

    void *Param;

    WNDCLASSA WindowClass;

    char WinClassName[64];

};

 

Examining this structure, notice that it contains everything you need to create a window.  That is, the TCreateParams structure holds all of the necessary window "Creation Parameters".  Specifically, this structure possesses all the necessary information required for the three steps: (1) Initialize WNDCLASS, (2) RegisterWindow, and (3) CreateWindow.  Also notice that many of the members of the TCreateParams structure directly correspond to the parameters of the CreateWindow API function.  It is the primary goal of the TWinControl::CreateParams member function to initialize this TCreateParams structure.  Listing 2.2 provides a condensed C++ translation of the TWinControl::CreateParams member function.

 

 

 

void __fastcall TWinControl::CreateParams(TCreateParams &Params)

{

    memset(&Params, 0, sizeof(TCreateParams));

 

    //

    //    <snip> initialize: 

    //    Params.Caption (with FText);

    //    Params.Style with WS_* flags (e.g., WS_CHILD)

    //    Params.X with FLeft;

    //    Params.Y with FTop;

    //    Params.Width with FWidth;

    //    Params.Height with FHeight;

    //    Params.WndParent = Parent->Handle;

    //

    

    Params.WindowClass.style = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS;

    Params.WindowClass.lpfnWndProc = DefWindowProc;

    Params.WindowClass.hInstance = HInstance;

    Params.WindowClass.hCursor = LoadCursor(NULL, IDC_ARROW);

    Params.WindowClass.hbrBackground = NULL;

 

    strcpy(Params.WinClassName, AnsiString(ClassName()).c_str());

}

 

Listing 2.2

 

 

You can see from the function definition of Listing 2.2 that the TWinControl::CreateParams function is responsible for initializing much of the TCreateParams structure.  While the details of the implementation will be discussed in future articles on a per-descendant class basis, there are a couple of important issues to note.  First, observe that the lpfnWndProc member of the TCreateParams::WindowClass structure (of type WNDCLASS) is specified as the address of the DefWindowProc API function.  This is a valid specification because the DefWindowProc function can be used as a generic window procedure.  The TWinControl class uses this address as a temporary value, only for the purpose of determining if the class has already been registered.  Also, notice that WindowClass's lpszClassName member is never initialized.  Rather, the class name is stored in the TCreateParams::WinClassName member.  We will return to these issues shortly.  For now, it is sufficient to realize that the TWinControl::CreateParams member function serves the primary role of initializing the TCreateParams structure that is later used for class registration and window creation.

 

 

IIB.  The CreateWnd Function

 

After the TCreateParams structure, and specifically its WindowClass member, is initialized, you're now ready to register the class via the RegisterClass API function.  This task is assigned to the CreateWnd member function, which is, by far, the "do all" function of the TWinControl class's window creation mechanism.  That is, all other window creation member functions are called from within CreateWnd or from within a function that CreateWnd calls.  A condensed C++ translation of the TWinControl::CreateWnd member function is provided in Listing 2.3.

 

 

 

void __fastcall TWinControl::CreateWnd()

{

    TCreateParams Params;

    CreateParams(Params);

    

    if (Params.WndParent == NULL && (Params.Style & WS_CHILD))

    {

        throw EInvalidOperation(SParentRequired, ARRAYOFCONST((Name)));

    }

 

    FDefWndProc = Params.WindowClass.lpfnWndProc;

 

    WNDCLASS dummyClass;   

    bool IsClassRegistered = 

       GetClassInfo(

          Params.WindowClass.hInstance,

          Params.WinClassName,

          &dummyClass

          );

 

    if (!IsClassRegistered ||

        TempClass.lpfnWndProc != reinterpret_cast<WNDPROC>(InitWndProc))

    {

        if (IsClassRegistered)

        {

            UnregisterClass(Params.WinClassName,

                            Params.WindowClass.hInstance);

        }

       

        Params.WindowClass.lpfnWndProc =

            reinterpret_cast<WNDPROC>(InitWndProc);

        Params.WindowClass.lpszClassName = Params.WinClassName;

    }

 

    CreateWindowHandle(Params);

 

    // <snip> font and bounds stuff...

}

 

Listing 2.3

 

 

The CreateWnd member function requires an initialized TCreateParams structure.  As such, the first call in the CreateWnd definition of Listing 2.3 is to the TWinControl::CreateParams member function, passing the address of a locally declared TCreateParams variable.  Next, if the control is a child of another window, a check is performed for a valid WndParent member, raising the common "Control <ControlName> has no Parent" exception upon failure.  The address of the default window procedure is then stored in the FDefWndProc member (accessible via the DefWndProc property).  The GetClassInfo API function is called next, here, with a dummy WNDCLASS variable just for the purpose of determining if the class has been previously registered.  If the class has not already been registered or the address of the registered class's default window procedure is not equal to that of InitWndProc (an "initialization" window procedure – i.e., a placeholder), the new class is registered.  If the former condition is false, but the latter is true, the previously registered class is first unregistered via the UnregisterClass API function.  Next, the TWinControl::CreateWindowHandle member function (which we will examine next) is called with the Params variable as its single argument.

 

Again, don't get bogged down with the specifics here, rather, keep a high-level picture of what's happening.  Namely, it's the primary role of the CreateWnd member function to register the new window class via the RegisterClass API function.  The task of initializing the Params variable is delegated to the CreateParams function, and as we will see next, the task of creating the actual window is left to the CreateWindowHandle function.

 

 

IIC.  The CreateWindowHandle Function

 

As mentioned, it's the job of the CreateWindowHandle member function to create the actual window via the CreateWindow API function.  In fact, CreateWindowHandle actually uses the CreateWindowEx API function.  As its name suggests, this latter function is simply an extension of the CreateWindow function that allows for the specification of certain “Ex”tended window styles.  This fact accounts for the ExStyle data member of the TCreateParams structure. 

 

HWND CreateWindowEx(

    DWORD  dwExStyle,

    LPCTSTR  lpClassName,

    LPCTSTR  lpWindowName,

    DWORD  dwStyle,

    int  x,  

    int  y,  

    int  nWidth,

    int  nHeight,

    HWND  hWndParent,

    HMENU  hMenu,   

    HINSTANCE  hInstance,

    LPVOID  lpParam

);

 

CreateWindowHandle, as translated in Listing 2.4, is one of the more straightforward TWinControl member functions; it wraps only a single call to the CreateWindowEx API function.

 

 

 

void __fastcall TWinControl::CreateWindowHandle(const

    TCreateParams &Params)

{

    FHandle = 

      CreateWindowEx(

         Params.ExStyle,

         Params.WinClassName,

         Params.Caption,

         Params.Style,

         Params.X, Params.Y,

         Params.Width, Params.Height,

         Params.WndParent, NULL,

         Params.WindowClass.hInstance,

         Params.Param

         );

}

 

Listing 2.4

 

 

You can see from the definition of Listing 2.3 that the CreateWindowHandle function has the responsibility of actually creating the window.  It simply makes a call to the CreateWindowEx API function, passing the various members of the initialized Params argument as the parameters to function.  Like the CreateWindow API function, CreateWindowEx returns a handle to the newly created window.  This value is store in the private FHandle member that can be accessed via the TWinControl::Handle property. 

 

The preceding three member functions, CreateParams, CreateWnd, and CreateWindowHandle form the basis of the window creation mechanism of the TWinControl class.  CreateParams initializes a TCreateParams structure, CreateWnd registers the new window class, and CreateWindowHandle creates the actual window.  These are the three basic tasks necessary to creating a window of a new window class.  In fact, these three member functions alone, can sufficiently accomplish the bulk of the example program of Listing 2.0.  As I mentioned earlier, however, many TWinControl descendants are based upon an existing window class.  We already know that the technique of superclassing can be used to accomplish this, so let's now examine how the TWinControl class encapsulates the process of superclassing.

 

 

IID.  Superclassing via the TWinControl Class

 

Recall the basic steps necessary to superclassing an existing control class.  First, you use the GetClassInfo function to initialize a WNDCLASS structure with information from an existing window class.  Next, you store, then swap, that class's default window procedure with your own.  Finally, you appropriately change the hInstance and lpClassName WNDCLASS data members.  As it turns out, these three steps are not performed by any one TWinControl member function alone.

 

The CreateParams member function takes on partial responsibility of superclassing by specifying the hInstance data member of the TCreateParams::WindowClass member.  Like in the implementation of Listing 2.1, the CreateParams member function assigns the application's instance, HInstance, to the hInstance data member.  The CreateWnd member function also contributes to the superclassing task by storing the default window procedure in the FDefWndProc member, specifying the superclass procedure as the lpfnWndProc member, and changing the lpszClassName member to reflect that of the new class.  So what's left to be done?  In fact, no where in this scheme has the GetClassInfo function been called to initialize the TCreateParams::WindowClass member with information from the base class.  This is the job of the TWinControl::CreateSubClass member function.

 

Contrary to its name, the CreateSubClass member function performs the rest of the superclassing procedure that's not covered by the CreateParams or CreateWnd functions.  Namely, it's the role of CreateSubClass to initialize the TCreateParams::WindowClass data member with information from an existing window class.  Listing 2.5 provides a pseudo-C++ translation of the TWinControl::CreateSubClass member function.

 

 

 

void __fastcall TWinControl::CreateSubClass(TCreateParams &Params,

    char *ControlClassName)

{

    if (ControlClassName)

    {

        HINSTANCE SaveInstance = Params.WindowClass.hInstance;

 

        if (!GetClassInfo(HInstance, ...) &&

            !GetClassInfo(NULL, ...) &&

            !GetClassInfo(MainInstance, ...))

        {

            GetClassInfo(Params.WindowClass.hInstance,

                         ControlClassName,

                         &Params.WindowClass);

        }

 

        Params.WindowClass.hInstance = SaveInstance;

    }

}

 

Listing 2.5

 

 

The ControlClassName parameter of the CreateSubClass function specifies the name of the base class.  For example, if you wanted to superclass the Windows BUTTON standard control, you'd pass "BUTTON" as the ControlClassName parameter.  The Params parameter specifies a TCreateParams structure that has been previously initialized by the CreateParams function.  In fact, it’s necessary that the CreateParams function is called before CreateSubClass.  If you were to call the CreateParams function after calling the CreateSubClass function, the CreateParams function would overwrite the data members that were just initialized by the CreateSubClass function.  Typically, CreateSubClass is called from within an augmented CreateParams implementation after calling the ancestor class's CreateParams member function.

 

If you examine the implementation of Listing 2.5, you can see that the CreateSubClass function serves primarily to initialize the TCreateParams::WindowClass member via the GetClassInfo API function.  Moreover, CreateSubClass has the potential of calling GetClassInfo a total of four times, each with a different hInstance parameter.  Recall, the hInstance parameter to the GetClassInfo function defines the instance of the application that registered the class.  The CreateSubClass first attempts to retrieve the base class information from the current application by specifying the global HInstance parameter.  If the first attempt fails, another call is made, this time with a NULL hInstance parameter.  As in our example of Listing 2.1, specifying NULL as this parameter is used to get information from a class that has been registered by Windows itself.  If this second attempt is unsuccessful, the GetClassInfo function is called a third time, here with MainInstance as the hInstance parameter (MainInstance may differ from HInstance if DLLs are used).  Finally, if all three previous attempts fail, a final attempt is made with the WNDCLASS::hInstance data member passed into the GetClassInfo function.  This latter case is needed so that you can explicitly assign a value to the hInstance data member of the WindowClass member to have CreateSubClass pass a handle to a specific application's instance into GetClassInfo.

 

Distributing the subclassing task amongst so many member functions may seem counterintuitive.  In fact, it would seem more logical to have CreateSubClass perform the entire superclassing procedure.  As it turns out, this distribution is necessary because the TWinControl class does not have to superclass an existing control class.  For example, the TCustomGrid class descends from TWinControl, but is not based on an existing control class.  While it is completely possible for the TCustomGrid::CreateParams implementation to not call the CreateSubClass function, the other tasks of superclassing must necessarily be performed.  That is, TWinControl itself is a superclassed version of a generic window.  In fact, TWinControl comes very close to a blank, border-less, child window.  This helps explain why the TWinControl::CreateParams implementation initializes the TCreateParams::Style member with the WS_CHILD style by default.  There's also the issue of mapping the default window procedure to the MainWndProc member function as discussed in the first article ("From Messages to Events")—swapping the DefWindowProc address with that of InitWndProc is crucial to this process.

 

If you're overwhelmed with the details of each of these member functions, relax for a moment and clear you mind.  As with much of this article, the specifics are not as important as the general concepts.  The take home message is that the CreateParams, CreateWnd, and CreateWindowHandle member functions, together, serve to register a new class and create a window of this a new class.  Likewise, CreateSubClass can be thought of as the “superclassing utility” member function, although we know that this is not an entirely accurate description.  To fortify these concepts, let's return to our simple "TMyButton" example of Listing 2.1.  Here, we'll implement the same technique of superclassing the Windows BUTTON standard control using the VCL framework and specifically the TWinControl class.  Listings 2.6a and 2.6b illustrate this process.

 

 

 

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

#ifndef SuperClassMyButtonH

#define SuperClassMyButtonH

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

#include <Classes.hpp>

#include <Controls.hpp>

#include <StdCtrls.hpp>

#include <Forms.hpp>

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

 

class TMyButton : public TWinControl

{

protected:

    virtual void __fastcall CreateParams(TCreateParams &Params);

public:

    __fastcall TMyButton(TComponent *Owner) : TWinControl(Owner) {};

};

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

 

class TForm1 : public TForm

{

__published:  // IDE-managed Components

private:      // User declarations

    TMyButton *MyButton;

Public:              // User declarations

    __fastcall TForm1(TComponent* Owner);

};

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

extern PACKAGE TForm1 *Form1;

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

#endif

 

Listing 2.6a

 

 

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

#include <vcl\vcl.h>

#pragma hdrstop

 

#include "SuperClassMyButton.h"

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

#pragma package(smart_init)

#pragma resource "*.dfm"

TForm1 *Form1;

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

 

__fastcall TForm1::TForm1(TComponent* Owner)

    : TForm(Owner)

{

    MyButton = new TMyButton(this);

    MyButton->Parent = this;

    MyButton->SetBounds(10, 10, 200, 75);

 

    // use SetWindowText to set the caption of the

    // button since we have not published the

    // TControl::Caption property

    SetWindowText(MyButton->Handle, "MyButton1");

}

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

 

void __fastcall TMyButton::CreateParams(TCreateParams &Params)

{

    // have the TWinControl::CreateParams member function

    // perform its default initialization of Params

    TWinControl::CreateParams(Params);

 

    // superclass the Windows BUTTON Standard Control

    CreateSubClass(Params, "BUTTON");

}

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

 

Listing 2.6b

 

 

You should already be familiar with much of the implementation of this (VCL) version of our previous example.  The heart of this example lies in the TMyButton::CreateParams definition.  First, we call the CreateParams member function of the ancestor class, TWinControl.  With our knowledge of the TWinControl::CreateParams member function, we know that this call merely serves to initialize the members of the Params argument.  Finally, we simply call the TWinControl::CreateSubClass member function, passing our initialized Params argument, and the name of the pre-registered control class, "BUTTON".

 

This example serves to prove that although the details of the TWinControl window creation mechanism may be difficult to absorb, actually using the member functions is quite straightforward.  Clearly, the number of predefined control classes is limited, so it's not always necessary to use the CreateSubClass function.  It should also be evident that with the degree of encapsulation presented by the TWinControl class, it's rarely necessary to call the CreateWindow or CreateWindowEx functions directly.  It's quite common, however, to manipulate the parameters that are passed into the CreateWindowEx function.  For this reason, let's now consider some practical situations where you'd need to tap into this area of the TWinControl class.

 

 

III. Customized Window Creation

 

One of the key indicators of a sound class design is its ability to be expanded upon; this is by far, one of the strongest features the TWinControl class.  If you think about the staggering number of window style combinations that can be specified, it won't take long to realize that the TWinControl class is indeed flexible enough to handle all of these.  In other words, although the number of predefined control classes is limited, the number of styles that can be specified within a particular control class is considerable.  For example, the following list indicates the styles that can be specified for the STATIC standard control class.

 

SS_BITMAP, SS_BLACKFRAME, SS_BLACKRECT, SS_CENTER, SS_CENTERIMAGE, SS_ENHMETAFILE, SS_ETCHEDFRAME, SS_ETCHEDHORZ, SS_ETCHEDVERT, SS_GRAYFRAME, SS_GRAYRECT, SS_ICON, SS_LEFT, SS_LEFTNOWORDWRAP, SS_NOPREFIX, SS_NOTIFY, SS_OWNERDRAW, SS_REALSIZEIMAGE, SS_RIGHT, SS_RIGHTJUST, SS_ICON, SS_SIMPLE, SS_SUNKEN, SS_WHITEFRAME, SS_WHITERECT.

 

It's important to realize that each TWinControl descendant usually augments the CreateParams member function to accommodate for the various parameters that are used during the creation of a window of its class.  For example, the TCustomStaticText::CreateParams implementation must deal with many of the static control styles, including those that do not directly deal with textual display.  Listing 2.7 provides a condensed translation of this function.

 

 

 

void __fastcall TCustomStaticText::CreateParams(TCreateParams &Params)

{

    TWinControl::CreateParams(Params);

    CreateSubClass(Params, "STATIC");

 

    Params.Style |= SS_NOTIFY;

    switch (FAlignment)

    {

        case taLeftJustify:

        {

            Params.Style |= SS_LEFT;

            break;

        }

        case taRightJustify:

        {

            Params.Style |= SS_RIGHT;

            break;

        }

        case taCenter:

        {

            Params.Style |= SS_CENTER;

            break;

        }

    }

 

    // <snip> some right to left alignment support...

 

    // BorderStyle...

    switch (BorderStyle)

    {

        case sbsSingle:

        {

            Params.Style |= WS_BORDER;

            break;

        }

        case sbsSunken:

        {

            Params.Style |= WS_BORDER;

            break;

        }

    }   

    Params.WindowClass.style &= ~(CS_HREDRAW | CS_VREDRAW);

}

 

Listing 2.7

 

 

You can see from Listing 2.7 an example of manipulating the TCreateParams::Style and TCreateParams::WindowClass::style data members.  It should be clear that nearly all customization can be done from within the augmented CreateParams member function.  In the sections that follow, we will examine several examples of customizing the window creation process.

 

 

IIIA.  Manipulating the Style and ExStyle Members

 

As I mentioned earlier, it is oftentimes necessary to manipulate the parameters that are passed into the CreateWindowEx function.  Commonly one is interested in altering the dwStyle and/or dwExStyle parameters.  But, since you never directly call the CreateWindowEx function, how can these parameters be adjusted?.  Well, recall that the Style and ExStyle data members of the TCreateParams structure directly correspond to the dwStyle and dwExStyle parameters of the CreateWindowEx function, respectively.  Also, recall that the CreateWindowHandle member function will ultimately call the CreateWindowEx function using the data member of this initialized TCreateParams variable as the parameters to CreateWindowEx.  So, by manipulating the data members of your TCreateParams-type variable, you can effectively manipulate the parameters that will be passed into the CreateWindowEx function.  This task is typically done by augmenting the CreateParams function, and then directly manipulating the Style and/or ExStyle members of the TCreateParams-type parameter. 

 

Let's start with the simple example of creating a tool window, such as those commonly used for floating toolbars.  Although this task can be accomplished by simply changing TForm::BorderStyle property to bsToolWindow, let's implement this by augmenting the TForm::CreateParams member function.  Specifically, a tool window is created by specifying the WS_EX_TOOLWINDOW style as the dwExStyle parameter of CreateWindowEx.  By changing the TCreateParams::ExStyle member in our CreateParams augmentation, we can realize this manipulation.  The code in Listings 2.8a and 2.8b demonstrates this idea.

 

 

 

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

#ifndef ToolWindowH

#define ToolWindowH

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

#include <Classes.hpp>

#include <Controls.hpp>

#include <StdCtrls.hpp>

#include <Forms.hpp>

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

 

class TForm1 : public TForm

{

__published:  // IDE-managed Components

private:      // User declarations

    virtual void __fastcall CreateParams(TCreateParams &Params);

public:              // User declarations

    __fastcall TForm1(TComponent* Owner);

};

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

extern PACKAGE TForm1 *Form1;

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

#endif

 

Listing 2.8a

 

 

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

#include <vcl.h>

#pragma hdrstop

 

#include "ToolWindow.h"

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

#pragma package(smart_init)

#pragma resource "*.dfm"

TForm1 *Form1;

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

 

__fastcall TForm1::TForm1(TComponent* Owner)

    : TForm(Owner)

{

}

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

 

void __fastcall TForm1::CreateParams(TCreateParams &Params)

{

    TForm::CreateParams(Params);

    Params.ExStyle = Params.ExStyle | WS_EX_TOOLWINDOW;

}

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

 

 

Listing 2.8b

 

 

In the TForm1::CreateParams definition of Listing 2.8b, we first call the CreateParams member function of the ancestor class, and then we add the WS_EX_TOOLWINDOW style to the ExStyle member of the initialized Params variable.  In fact, this is exactly the same manipulation that takes place if the BorderStyle property is set to bsToolWindow.  Again, this example isn't too practical since the manipulation is available more easily via the BorderStyle property.  Let's now move on to a slightly more practical example. 

 

Consider a simple Form consisting of three TLabel controls that will serve to report various file attributes, as depicted in Figure 2.0.
 

 


We would like to be able to drag and drop a file directly from Explorer onto our Form.  Recall from the first article ("From Messages to Events") that the
DragAcceptFiles API function can be used to register a window as a valid drop target.  In fact, there's another way to accomplish this registration without the use the DragAcceptFiles function.  The WS_EX_ACCEPTFILES extended window style can be specified to create a window that is capable of accepting drag-drop files.  Like before, we can indirectly pass this style into the CreateWindowEx function by augmenting the CreateParams function and changing the TCreateParams::ExStyle member.  Listings 2.9a and 2.9b illustrate this process.

 

 

 

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

#ifndef DragDropH

#define DragDropH

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

#include <Classes.hpp>

#include <Controls.hpp>

#include <StdCtrls.hpp>

#include <Forms.hpp>

#include <ExtCtrls.hpp>

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

 

class TForm1 : public TForm

{

__published:  // IDE-managed Components

    TLabel *NameLabel;

    TLabel *SizeLabel;

    TLabel *DateLabel;

    TBevel *Bevel1;

protected:

    virtual void __fastcall CreateParams(TCreateParams &Params);

private:      // User declarations

    void __fastcall WMDropFiles(TMessage &Msg);

public:       // User declarations

    __fastcall TForm1(TComponent* Owner);

 

BEGIN_MESSAGE_MAP

    MESSAGE_HANDLER(WM_DROPFILES, TMessage, WMDropFiles)

END_MESSAGE_MAP(TForm)

};

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

extern PACKAGE TForm1 *Form1;

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

#endif

 

Listing 2.9a

 

 

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

#include <vcl.h>

#pragma hdrstop

 

#include <memory>

#include "DragDrop.h"

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

#pragma package(smart_init)

#pragma resource "*.dfm"

TForm1 *Form1;

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

 

__fastcall TForm1::TForm1(TComponent* Owner)

    : TForm(Owner)

{

}

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

 

void __fastcall TForm1::CreateParams(TCreateParams &Params)

{

    TForm::CreateParams(Params);

    Params.ExStyle = Params.ExStyle | WS_EX_ACCEPTFILES;

}

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

 

void __fastcall TForm1::WMDropFiles(TMessage &Msg)

{

    // grab a handle to the drop object

    HDROP HDrop = reinterpret_cast<HDROP>(Msg.WParam);

 

    // find the number of files dropped

    int num_files = DragQueryFile(HDrop, 0xFFFFFFFF, NULL, NULL);

    if (num_files > 1)

    {

        ShowMessage("You have dropped too many files.");

        return;

    }

 

    // extract the name and path of the dropped file

    char filename[MAX_PATH];

    DragQueryFile(HDrop, 0, filename, MAX_PATH);

    NameLabel->Caption = NameLabel->Caption + " " + filename;

 

    std::auto_ptr<TFileStream> fs;

    try

    {

        fs.reset(new TFileStream(filename, fmOpenRead));

 

        // get the file information

        SizeLabel->Caption = SizeLabel->Caption + " " +

            GetFileSize(reinterpret_cast<HANDLE>(fs->Handle), NULL);

        DateLabel->Caption = DateLabel->Caption + " " +

            FileDateToDateTime(FileGetDate(fs->Handle));

    }

    catch (...)

    {

        // close the drop handle

        DragFinish(HDrop);

    }

    // close the drop handle    

    DragFinish(HDrop);

 

    Msg.Result = 0;

}

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

 

Listing 2.9b

 

 

In the TForm1::CreateParams implementation of Listing 2.8b, we first call the CreateParams function of the ancestor class, TForm.  Next, we add the WS_EX_ACCEPTFILES flag to the existing ExStyle member.  Once this member has been altered, the new value will be ultimately used in the call to CreateWindowEx.  In the WMDropFiles message handler, we used the GetFileSize API function to retrieve the size of the dropped file.  We also used the FileGetDate and FileDateToDateTime VCL functions to retrieve the last modification date of the file.

 

Altering the Style and ExStyle members can be useful for child windowed controls as well.  For example, to remove the vertical scrollbar in a TListBox control, you simply augment the CreateParams member function and remove the WS_VSCROLL bit from the Style data member, as demonstrated in Listings 2.10.

 

 

 

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

#ifndef NoScrollListBoxH

#define NoScrollListBoxH

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

#include <StdCtrls.hpp>

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

 

class TMyListBox : public TListBox

{

protected:

    virtual void __fastcall CreateParams(TCreateParams &Params)

    {

        TListBox::CreateParams(Params);

        Params.Style = Params.Style & ~WS_VSCROLL;

    }

 

public:

    __fastcall TMyListBox(TComponent *Owner) : TListBox(Owner) {};

};

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

#endif

 

Listing 2.10

 

 

It's important to realize that the manipulations you make to the TCreateParams argument of the CreateParams member function will be reflected in your window class.  That is, the TWinControl::CreateWnd member function calls the CreateParams function before it registers the class and before it calls CreateWindowHandle to create the actual window.  This is also helps explain why, unlike the CreateWindowHandle function, the argument to the CreateParams function does not have a const qualifier (cf, Listing 2.2, Listing 2.4).

 

While Style and ExStyle are the most commonly manipulated data members of the TCreateParams structure, they're certainly not the only ones that can be altered.  Although it's not as common, as we will discuss next, other data members of the TCreateParams structure can be manipulated as well.

 

 

IIIB.  Manipulating the WindowClass Member

 

Recall that the first step necessary to creating a window of a new control class is to initialize a WNDCLASS variable.  This WNDCLASS variable, whose address is later passed into the RegisterClass API function, is used to define various attributes of the new control class.  Its style member is used to define the class styles.  For example, we initialized the style member of our form_wc variable in Listing 2.0 with a combination of the CS_VREDRAW, CS_HREDRAW, and CS_DBLCLKS class styles.

 

As mentioned previously, the TCreateParams::WindowClass data member corresponds to the WNDCLASS structure that is passed into the RegisterClass function.  Specifically, we know that this member is initialized in the CreateParams member function.  As such, like the Style and ExStyle members, the WindowClass member can be altered in a TCreateParams augmentation to customize the several aspects of the control class.

 

As an example, let's create a Form that has a crosshair as its default cursor.  While this can easily be accomplished by directly setting the Cursor property of our Form to crCross, let's approach this task from the window creation perspective.  That is, we will manipulate WindowClass's hCursor data member, as illustrated in Listing 2.11.

 

 

 

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

#ifndef HelpCursorH

#define HelpCursorH

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

#include <Classes.hpp>

#include <Controls.hpp>

#include <Forms.hpp>

#include <StdCtrls.hpp>

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

class TForm1 : public TForm

{

__published:  // IDE-managed Components

 

protected:

    virtual void __fastcall CreateParams(TCreateParams &Params)

    {

        TForm::CreateParams(Params);

        Params.WindowClass.hCursor = LoadCursor(NULL, IDC_CROSS);

    }

 

public:       // User declarations

    __fastcall TForm1(TComponent* Owner);

};

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

extern PACKAGE TForm1 *Form1;

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

#endif

 

Listing 2.11

 

 

Like before, we first have access to the window creation parameters via the CreateParams member function.  In the TForm1::CreateParams definition of Listing 2.11, we simply use the LoadCursor API function, passing IDC_CROSS as the lpCursorName, to initialize the hCursor data member.

 

Let's now examine another example of manipulating the WindowClass member, by creating a window that has a disabled "Close" title-bar button (i.e., no close option in its system menu), as depicted in Figure 2.1.
 

 


Specifically, we'll manipulate the
WindowClass data member by adding the CS_NOCLOSE class style.  The code for this example is provided in Listing 2.12.

 

 

 

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

#ifndef NoCloseH

#define NoCloseH

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

#include <Classes.hpp>

#include <Controls.hpp>

#include <Forms.hpp>

#include <StdCtrls.hpp>

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

class TForm1 : public TForm

{

__published:  // IDE-managed Components

 

protected:

    virtual void __fastcall CreateParams(TCreateParams &Params)

    {

        TForm::CreateParams(Params);

        Params.WindowClass.style =

            Params.WindowClass.style | CS_NOCLOSE;

    }

 

public:       // User declarations

    __fastcall TForm1(TComponent* Owner);

};

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

extern PACKAGE TForm1 *Form1;

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

#endif

 

Listing 2.12

 

 

In the same way that we changed the hCursor member in our previous example, here we simply manipulate WindowClass's style data member by adding the CS_NOCLOSE class style.  This will serve to disable the "Close" title-bar button and remove the "Close" option from the system menu of our Form.  Unfortunately, our Form can still be closed by right-clicking the taskbar button for our Application, and choosing the "Close" option from the popup menu.  In order to overcome this fact, we'll need to further customize the creation parameters.  We'll return to this issue in the next section.

 

 

IIIC.  Manipulating the WndParent Member

 

One of the most commonly asked questions regarding the TForm class, is how to make each Form within an application show its own taskbar button.  By default, none of the Forms within a VCL application, display a taskbar button.  The one that we typically see actually belongs to the zero dimension  TApplication window.  You can notice the difference between a standard Windows application and a VCL application by examining the popup menu that appears when right-clicking the taskbar button.  Usually, this popup menu is identically the system menu, as depicted for Notepad in Figure 2.2.
 

 

 

For VCL applications, we get a different picture, as the TApplication::CreateHandle member function removes the Maximize, Move, and Size options, as depicted in Figure 2.3.
  

 

 

As I just mentioned, the taskbar button depicted in Figure 2.3, belongs to the Application window.  This helps explain why the TApplication::CreateHandle member function removes the aforementioned menu items.  Namely, one should never be able to maximize, move, or size the Application window. 

 

Recall from our previous discussion that the hWndParent parameter of the CreateWindowEx function is, by default, set to the handle of the Application window; this is the reason we don't see taskbar buttons for individual Forms.  By augmenting the CreateParams member function in the same fashion as of our previous examples, we can change the value of the TCreateParams::WndParent member to that of the desktop window's handle.  We know this scheme will work because the TCreateParams::WndParent data member is specified as the hWndParent parameter in the call to the CreateWindowEx function.  Listing 2.13 illustrates this process.

 

 

 

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

#ifndef AppWindow2H

#define AppWindow2H

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

#include <Classes.hpp>

#include <Controls.hpp>

#include <StdCtrls.hpp>

#include <Forms.hpp>

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

 

class TForm2 : public TForm

{

__published:  // IDE-managed Components

 

private:      // User declarations

    virtual void __fastcall CreateParams(TCreateParams &Params)

    {

        TForm::CreateParams(Params);

        Params.WndParent = GetDesktopWindow();  // or NULL

    }

       

public:       // User declarations

    __fastcall TForm2(TComponent* Owner);

};

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

extern PACKAGE TForm2 *Form2;

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

#endif

 

Listing 2.13

 

 

Here, we use the GetDesktopWindow API function to retrieve a handle to the desktop window, and then we assign this handle to Param's WindowClass data member.  With the above implementation, Form2 will indeed display its own taskbar button, and it will show its system menu when this button is right-clicked.  Moreover, Form2 won't be minimized when the Application itself is minimized.  All of this functionality has been realized by simply changing the WndParent member.

 

In fact, the above scheme is sufficient for all forms except the Application's MainForm (i.e., TApplication::MainForm).  To see why this will not work for the MainForm (Form1 in our example), let's first implement the same augmentation as that of Listing 2.13, here, in our TForm1 class of Listing 2.14.

 

 

 

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

#ifndef AppWindowH

#define AppWindowH

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

#include <Classes.hpp>

#include <Controls.hpp>

#include <StdCtrls.hpp>

#include <Forms.hpp>

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

 

class TForm1 : public TForm

{

__published:  // IDE-managed Components

 

private:      // User declarations

    virtual void __fastcall CreateParams(TCreateParams &Params)

    {

        TForm::CreateParams(Params);

        Params.WndParent = GetDesktopWindow();  // or NULL

    }

       

public:       // User declarations

    __fastcall TForm1(TComponent* Owner);

};

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

extern PACKAGE TForm1 *Form1;

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

#endif

 

Listing 2.14

 

 

When executing this example, you'll immediately notice the first of two problems.  Namely, the taskbar button of the Application window is still visible, along with the new taskbar button for Form1.  Right-clicking the Application's taskbar button and choosing the "Minimize" option, you'll quickly run into the second problem—Form1 will not minimize.  You can verify that the Application window indeed minimized because the "Minimize" option in its taskbar button popup menu is no longer enabled, while the "Restore" option is now enabled.

 

To understand what’s happening here, recall that the WndParent member used to be set to the handle of the Application window.  As such, minimizing the Application would serve to minimize all of its forms.  In our case, because Form1 and Form2 no longer have WndParent set to the handle of the Application window, they cannot be minimized by simply minimizing the Application.  Nonetheless, this is exactly the functionality that we seek for Form2, although we still need to solve the two aforementioned problems for Form1 (our MainForm). 

 

The conflict here is actually due to the private TCustomForm::WMSysCommand message handler.  More specifically, when a window's minimize system command is chosen (either via the system menu or the minimize button), that window is sent the WM_SYSCOMMAND message with the SC_MINIMIZE command flag encoded in the wParam parameter.  The TCustomForm class handles this message via a WMSysCommand message handler.  Within this handler, it simply checks to see if its current instance is identically the Application's MainForm, in which case it passes on the message to the Application window.  The Application window handles the message by simply calling its public TApplication::Minimize member function.  Typically, this would successfully minimize all forms.  Indeed, with our WndParent manipulation, we know this is not case.

 

To overcome the second problem, recall that we can augment the Dispatch member function via the message mapping macros, as discussed in the first article ("From Messages to Events").  In doing so, we can prevent the TCustomForm class from ever receiving the WM_SYSCOMMAND message by trapping the message in our own WMSysCommand message handler.  These modifications to the TForm1 class are provided in Listings 2.15a and 2.15b.

 

   

 

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

#ifndef AppWindowH

#define AppWindowH

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

#include <Classes.hpp>

#include <Controls.hpp>

#include <StdCtrls.hpp>

#include <Forms.hpp>

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

 

class TForm1 : public TForm

{

__published:  // IDE-managed Components

    TButton *Button1;

    void __fastcall FormShow(TObject *Sender);

    void __fastcall Button1Click(TObject *Sender);

private:      // User declarations

    void __fastcall WMSysCommand(TMessage &Msg);

protected:

    virtual void __fastcall CreateParams(TCreateParams &Params);

public:       // User declarations

    __fastcall TForm1(TComponent* Owner);

 

BEGIN_MESSAGE_MAP

    MESSAGE_HANDLER(WM_SYSCOMMAND, TMessage, WMSysCommand)

END_MESSAGE_MAP(TForm)

};

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

extern PACKAGE TForm1 *Form1;

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

#endif

 

Listing 2.15a

 

 

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

#include <vcl.h>

#pragma hdrstop

 

#include "AppWindow.h"

#include "AppWindow2.h"

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

#pragma package(smart_init)

#pragma resource "*.dfm"

TForm1 *Form1;

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

 

__fastcall TForm1::TForm1(TComponent* Owner)

    : TForm(Owner)

{

}

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

 

void __fastcall TForm1::CreateParams(TCreateParams &Params)

{

    TForm::CreateParams(Params);

    Params.WndParent = GetDesktopWindow();

}

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

 

void __fastcall TForm1::WMSysCommand(TMessage &Msg)

{

    // trap the WM_SYSCOMMAND message only if the

    // SC_MINIMIZE flag is specified

    unsigned int op_code = Msg.WParam & 0xFFF0;

    if (op_code == SC_MINIMIZE)

    {

        WindowState = wsMinimized;

        Msg.Result = 0;

        return;

    }

    TForm::Dispatch(&Msg);

}

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

 

void __fastcall TForm1::FormShow(TObject *Sender)

{

    // hide the Application window (and its taskbar button)

    ShowWindow(Application->Handle, SW_HIDE);   

}

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

 

void __fastcall TForm1::Button1Click(TObject *Sender)

{

    // display Form2 (notice its own taskbar button)

    Form2->Show();   

}

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

 

Listing 2.15b

 

 

First, notice the use of the message mapping macros in the header file of Listing 2.15a.  Recall that these macros simply augment the virtual Dispatch member function.  Within our WM_SYSCOMMAND message handler (TForm1::WMSysCommand) of Listing 2.15b, we test the WParam member for the SC_MINIMIZE flag, in which case we manually minimize Form1 via the TCustomForm::WindowState property, and then we trap the message.  All other cases of WM_SYSCOMMAND are passed along the message trail as usual.  This message-handling scheme effectively solves our second (minimization) problem.

 

We have also provided an event handler for the TCustomForm::OnShow event.  It is in the implementation of this handler that we use the ShowWindow API function to hide the Application window, and more specifically its taskbar button.  This solves our initial (double taskbar button) problem.

 

Finally, recall our example of Listing 2.12, in which we used the CS_NOCLOSE style.  With the above WndParent manipulation, the implementation of Listing 2.12 becomes complete.  That is, our taskbar button popup menu now reflects that our MainForm's system menu, and not that of the Application window as before.

 

 

IV.  Window Creation Summary


The preceding example is intended to serve two purposes.  First, it is meant to reinforce the message handling techniques of the first article ("From Messages to Events").  Second, and more importantly, along with the previous examples of this section, it is designed to fortify the idea that the TWinControl class allows straightforward window creation customization via its CreateParams member function.  In fact, the remaining data members of the TCreateParams structure (i.e., the ones that we have not explicitly examined) can easily be changed by following the same technique as in the examples above.  Although, like the WndParent manipulation of this latest example, there may be some unforeseen side effects, which you must be prepared to handle.  Still, with the message handling knowledge of the first article ("From Messages to Events"), and now the window creation fundamentals from this article, these should not present much of a hurdle.

 

The following list summarizes the API techniques needed to register a new control (window) class, and create a window thereof:

 

1.      Declare and initialize a WNDCLASS variable; superclass if necessary.

2.      Register the new control class via the RegisterClass API function, passing the address of the WNDCLASS variable from step 1 as the lpWndClass parameter.

3.      Use the CreateWindow or CreateWindowEx API function to create a new window of this control class.

 

 

The following list summarizes the corresponding TWinControl member functions needed to register a new control (window) class, and create a window thereof:

 

1.      CreateWnd declares a TCreateParams variable and initializes it via the CreateParams member function;  CreateSubClass is called for superclassing, if necessary.

2.      CreateWnd registers the new control class via the RegisterClass API function, passing the address of the TCreateParams::WindowClass member from (1) as the lpWndClass parameter.

3.      CreateWnd then calls the CreateWindowHandle member function, which, in turn, uses the CreateWindowEx API function to create a new window of this control class.

 

 

Table 2.0 lists the various API procedures and their corresponding TWinControl member functions.

 

 

API Technique

TWinControl Member Function

Initialize WNDCLASS

CreateParams

RegisterClass

CreateWnd

CreateWindowEx

CreateWindowHandle

Table 2.0

 

 

Message handling and window creation are, unquestionably, the vital building blocks needed to understanding the rest of the VCL.  It is hoped that with a glimpse into the underlying API methodology, you can gain a true understanding of the mechanisms that drive the various components that we oftentimes take for granted.  Moreover, it is not pragmatic to assume that any of the VCL components will serve the needs of every developer for every imaginable situation.  Still, as we've examined here with the TWinControl class, and as we will see in future articles, much of the VCL was designed on the idea of flexibility and code-reuse. 

 

Copyright © 2004 Damon Chandler; All rights reserved.