![]() |
Inside the VCL: Window
Creation Fundamentals |
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
window—but 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 development—all 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.