Inside the VCL: From Messages to Events Part I
By Damon Chandler

 

 

Most introductory C++ courses fortify the ideas of syntax, object-oriented design, coding styles, and a plethora of other topics that serve to form a solid foundation for the language itself.  Indeed, such a fortification is neccessary to mastering any progamming language.  However, being an apt C++ programmer is far from being a strong Windows developer.  It isn’t until one embarks upon the journey of implementing an event-driven program that a true appreciation of the idea of a window-based environment can be gained.  Indeed, Windows itself is based upon a message or “signal” oriented framework that lends to a better understanding of the system as a whole.  These signals form the foundation of Windows’ multi-tasking capabilities.

 

The fact that C++Builder encapsulates most the API leads many to believe that there is some type magic going on behind the scene in the VCL.  In fact, although it is sometimes a subtle point, a typical VCL application contains all the necessary underlying API work.  The level of abstraction exhibited by the VCL presents us with an intuitive, easily maintanable framework, of which the Rapid Application Development (RAD) theme is based.  Nonetheless, a true understanding of the VCL cannot be appreciated without some knowledge of the API itself.  The purpose of this article is to introduce the basic constructs of a VCL application from an API point of view.  Mastery of the Windows API is surely not a prerequisite here.  As such, many of the presentations of this first article will be preceeded by a high-level introduction of the underlying API methods. The intent here is introduce the inner workings of the TApplication and TWinControl classes from an API point of view; a look “under the hood” if you will.   This, in turn, will allow a common foundation of terminology and notation from which subsequent presentations can be based.

 

 

I. An Event-Driven Implementation

 

Recall, the basic “Hello World!” program which simply outputs this, almost ubiquitous, single line of text.  For completeness, this is provided in Listing 1.0.

 

 

 

int main(int argc, char *argv[])

{

    std::cout << "Hello World!" << std::endl;

    return 0;

}

 

Listing 1.0

 

 

Common knowledge tells us that this is clearly not an event-driven program.  That is, a certain event does not trigger a certain action at any given time.  Imagine now, a primitive attempt to extend this simple program toward an event-driven implementation as in Listing 1.1.

 

 

 

int main(int argc, char* argv[])

{

    while (getch() != 'Q')

    {

        std::cout << "Hello World!" << std::endl;

    }

    return 0;

}

 

Listing 1.1

 

 

Here, we see that the program will simply output the phrase, “Hello World!”, for every keystroke, until the user enters the letter “Q”.  While this is far from a de facto event-driven program, it lends to the idea of an “action / reaction” implementation.  That is, for every user action, there is some reaction.  Sometimes this reaction is simply the feedback of textual information as in our simple program above.  Sometimes the reaction will be the output of a printed page of text.  Sometimes the reaction will be as complicated as a change in the direction of an animated figure in a graphically intense 3-D game.  Perhaps even the output will be nothing at all.  The idea here is that the user implicitly defines the reactions or “path” of the program through his or her own actions. 

 

As mentioned, Windows is an event-driven, message based environment.  Clicking the Start Button, for example, will cause the Start Menu to appear.  Like before, this is an example of an “action / reaction” implementation, which happens to be the standard behavior of most Windows programs.  As we will shortly see, the actual main processing in any Windows program resembles the form of our simple “Hello World!” extension. 

 

To begin to understand the fundamental connection between a text-based C++ application and an event-driven Windows application, let us first examine a typical application message loop, commonly referred to as the main message “pump” as provided in Listing 1.2.

  

 

 

WINAPI APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

    LPTSTR lpCmdLine, int nCmdShow)

{

    //

    // <window creation code> ...

    //

 

    MSG Msg;

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

    {

        TranslateMessage(&Msg);

        DispatchMessage(&Msg);

    }

    return 0;

}

 

Listing 1.2

 

 

The first thing to notice from Listing 1.2 is that the familiar main function of Listing 1.1 is replaced by its Windows equivalent, WinMain.  From within the implementation of this  WinMain function, we can begin to see how a Windows application can be based upon events.  Just as the getch function of Listing 1.1 served to retrieve a character from the keyboard, the GetMessage API function is designed to retrieve a message.  These messages originate from the system – the keyboard driver, mouse driver, and other device drivers. 

 

In fact, the implementation of our WinMain function consists of nothing more than what may at first seem like an infinite while loop.  This latter statement would certainly be accurate if the GetMessage function were to always return true.  In fact, the GetMessage function returns true for all messages except WM_QUIT.  The purpose of this function is to simply retrieve a message from the application’s message queue (filled by Windows).  Again, this is working in much the same way as the getch function from our “Hello World!” example. 

 

The TranslateMessage API function serves to interpret keyboard input messages.  Specifically, this function converts virtual-key code messages (WM_KEYDOWN, WM_KEYUP) into the more useful (WM_CHAR) character value message.  It the then the job of the DispatchMessage API function to dispatch or "send" the message to the correct window.  Specifically, this information is sent to a special callback function known as a window procedure.  The DispatchMessage function serves a role analogous to that of the cout statement of Listing1.1, except that DispatchMessage directs a message to a window procedure instead of directing text to an iostream.  The message retrieval/transmission  cycle continuously repeats until the WM_QUIT message is received (i.e., the GetMessage function returns false, breaking the while loop, or “terminating the pump”). 

 

The information that the GetMessage function retrieves is placed in a MSG structure.  The six data members of this structure serve to inform an application of a particular event.

 

typedef struct tagMSG {

    HWND   hwnd;    

    UINT   message;

    WPARAM wParam;

    LPARAM lParam;

    DWORD  time;

    POINT  pt;

} MSG;

 

The hwnd data member contains the handle of the window that the message is intended for.  Again, this is used by the DispatchMessage API function to determine to which window's window procedure to send the mesasge.  The message data member contains the actual message constant (e.g., WM_QUIT).  The wParam and lParam members are 32-bit message parameters that may contain useful information about the specifics of the message.  For example, the wParam member of the WM_QUIT message contains an integer value identifying the “exit code”.  The time data member identifies the value of the system clock when the message was posted, while the pt member contains the location of the mouse cursor at this time.

 

 

II.  Communication via Messages

 

As mentioned earlier, the window procedure of a window receives any messages that are sent to it.  Most of these messages will be sent by Windows itself, either directly to the window procedure, or indirectly through our main message pump.  Messages that pass through the message pump are called queued messages, and are commonly referred to as “posted” messages.  These typically include messages resulting from user input via the mouse or keyboard.  Painting messages, such as WM_PAINT, are also queued, as are system timer (WM_TIMER) and termination (WM_QUIT) messages.  Most other messages are sent directly to a window’s window procedure.  These latter messages are called non-queued messages.

 

To get a better understanding of window messages, let us consider a simple VCL application consisting of a single form with a TButton control and a TEdit control parented to it, as depicted in Figure 1.1.  The code for this example is provided in Listing 1.3.  

 

   

 


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

#include <vcl.h>

#pragma hdrstop

 

#include "Unit1.h"

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

#pragma package(smart_init)

#pragma resource "*.dfm"

TForm1 *Form1;

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

 

__fastcall TForm1::TForm1(TComponent* Owner)

    : TForm(Owner)

{

}

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

 

void __fastcall TForm1::Button1Click(TObject* Sender)

{

    Edit1->Text = "Hello World!";   

}

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

 

Listing 1.3

 

 

In fact, our entire implementation consists of nothing more than a single event handler for the TButton::OnClick event, in which we simply maniuplate the TEdit::Text property.  We see here that when the button is clicked, the edit control’s text is changed to the value “Hello World!”.  This is another example of an “action / reaction” implementation.  The action being the user clicking the button control, while the reaction is a change in the edit control’s text. 

 

While this simple program is far from being a sophisticated application, we get first hand evidence here of the level of abstraction presented by the VCL.  Namely, unlike in a raw API program, we did not have to send any window messages to the edit control itself, nor did we have to tap into the button control’s window procedure and respond to the WM_LBUTTONUP message.  Further, we did not have to code the standard windows message pump.  How then can this program operate in an event driven fashion?  To see how this is accomplished, we need to examine our project’s source code as provided in Listing 1.4.

 

 


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

#include <vcl.h>

#pragma hdrstop

 

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

USEFORM("Unit1.cpp", Form1);

USERES("Project1.res");

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

 

WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)

{

    try

    {

         Application->Initialize();

         Application->CreateForm(__classid(TForm1), &Form1);

         Application->Run();

    }

    catch (Exception &exception)

    {

         Application->ShowException(&exception);

    }

    return 0;

}

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

 

Listing 1.4

 

 

Notice that our project’s source code contains the aforementioned WinMain function.  Specifically, this is the exact same WinMain function as in Listing 1.2.  The main difference here is that the entire message loop, window creation, and exception handling mechanisms are encapsulated by the TApplication and TWinControl classes.  In fact, the standard Windows message loop is entered via the TApplication::Run member function.  We can begin to see here that a VCL application is indeed a standard Windows application consisting of a WinMain function and a message loop.

 

Now that we have convinced ourselves that the standard message pump is indeed present, let us return to our TForm1 class implementation and examine how the TWinControl class relieves the hassle of dealing with messages, including those dispatched from the Application object. 

 

In Listing 1.3 our button’s OnClick event handler consists of a single line of code, which simply changes the edit control’s text via the TWinControl::Text property.  Yet, in an API implementation, to actually change the text of any windowed control, the WM_SETTEXT message is used, as demonstrated in Listing 1.5.

 

 

 

void __fastcall TForm1::Button1Click(TObject* Sender)

{

    // Edit1->Text = "Hello World!";

    SendMessage(Edit1->Handle, WM_SETTEXT, 0,

                reinterpret_cast<LPARAM>("Hello World!"));

}

 

Listing 1.5

 

 

Here we use the SendMessage API function to directly send the edit control’s window procedure the WM_SETTEXT message.  Not only is this code hard to maintain, it is less concise and not as type-safe as the TWinControl::Text Property.  The latter simply encapsulates the SendMessage call via the TWinControl::SetText member function.  In fact, the TWinControl class allows manipulation of many window attributes through the use of its member functions.  There are very few occassions where we will need to explicitly use the SendMessage API function. 

 

We now know that changing a TWinControl’s Text property will indirectly send the WM_SETTEXT message to the control itself.  However, we have yet to examine the messages associated with our button’s OnClick event.  Specifically, let us explore the messages that are sent to the button when it is clicked, and examine how these messages relate to our Button1Click event handler. 

 

As previously stated, messages that are directed to a window are sent to a special function called a window procedure.  For example, when we used the SendMessage API function, in Listing 1.5, we sent our edit control’s window procedure the WM_SETTEXT message.  In turn, the edit control handled this message by changing its text.  In much the same way, when our button is clicked we indirectly handled the WM_LBUTTONUP message through our button’s OnClick event handler.  The difference here is that Windows itself, indirectly sent our Button the WM_LBUTTONUP message.  The TWinControl class handled this message by testing the location of the mouse cursor, then appropriately calling our Button1Click member function. 

 

In general, when the left mouse button is depressed while the mouse cursor is within the bounds of any enabled windowed control within our application, Windows sends the WM_LBUTTONDOWN message to our message pump.  The DispatchMessage function within our message loop then directs this message to the control’s window procedure.  Similarly, if the left mouse button is then released, the control is indirectly sent the WM_LBUTTONUP message.  These two messages are examples of queued messages, and are commonly used to define a mouse “click”.  Typically, these messages are explicitly handled in the control’s window procedure as demonstrated in Listing 1.6.

 

 

 

LRESULT CALLBACK ButtonWndProc(

    HWND hwnd,        // handle of window

    UINT uMsg,        // message identifier

    WPARAM wParam,    // "word" message parameter

    LPARAM lParam)    // "long" message parameter

{  

 

    switch (uMsg)

    {

        case WM_LBUTTONDOWN:

        {

            SetCapture(hwnd);

            break;

        }

 

        case WM_LBUTTONUP:

        {

            ReleaseCapture();

            SendMessage(hwndEdit, WM_SETTEXT, 0,

                        reinterpret_cast<LPARAM>("Hello World!"));

            break;

        }

        default:

            return DefWindowProc(hwnd, uMsg, wParam, lParam);

    }

    return 0;

}

 

 

Listing 1.6

 

 

The OnClick event, however, saves us the hassle of implementing a window procedure as that of Listing 1.6.  This is especially useful when a control need only respond to a single user event.  We will examine the specifics of the TButton and TEdit classes in subsequent articles.

 

 

III.  Window Procedures and VCL Events

 

In the previous section, we discussed queued and non-queued messages, and how specific messages can be used to invoke certain changes.  For example, we used to the WM_SETTEXT message to change the text entry in our edit control.  Similarly, we could have used the WM_SETFONT message to change the font of this text.  We also implemented an OnClick event handler for a TButton control, and examined how this event related to the underlying window messages WM_LBUTTONDOWN and WM_LBUTTONUP.  Let us now investigate, in-depth, how these events are triggered and how we can manipulate the fashion in which the messages that trigger these events are handled.  In this section and the next (Message Handling), we will discover how user-defined event handlers are triggered in response to window messages.

 

Recall, our simple program of Listing 1.3 consisting of a single form with a TEdit control and a TButton control parented to it.  Focussing on the TButton control, let us inspect the various events published in the Object Inspector, as depicted in Figure 1.2.

 

 

Since we already know that the WM_LBUTTONUP message fires the OnClick event, let us examine the OnEnter and OnExit events.  When a windowed control receives keyboard or “input” focus, its window procedure is sent the WM_SETFOCUS message.  For example, notice that our TButton oftentimes displays a selection rectangle that surrounds its client area, as depicted in Figure 1.1.  This rectangle serves to indicate that the button has keyboard focus, and is rendered in response to the WM_SETFOCUS message.  When the button loses keyboard focus (i.e., another windowed control receives keyboard focus), the button is redrawn without the selection rectangle.  This latter task is performed in response to the WM_KILLFOCUS message.  For TWinControl descendants, both the OnExit and OnEnter events are triggered by the WM_SETFOCUS message.  While this may seem counterintuitive, we will see in later articles how this functionality is accomplished by TCustomForm::SetFocusedControl member function.  For now, it is sufficient to understand that we can provide a handler for the button’s OnEnter event to perform special processing when the button receives keyboard focus.  Similarly, we can use the OnExit event to perform processing when the button loses keyboard focus. 

 

The OnKeyDown, OnKeyPress, and OnKeyUp events work in much the same way as the OnEnter and OnExit events.  Namely, they are fired in response to a certain window message.  In this case, the messages are WM_KEYDOWN, WM_CHAR, and WM_KEYUP, respectively.  The WM_KEYDOWN message is sent to the window procedure of a window that has input focus when a non-system key is pressed.   For example, pressing the letter ‘A’ when our button has keyboard focus will cause Windows to send our message pump the WM_KEYDOWN message (remember, this is a keyboard message, and is thus a queued message).  The DispatchMessage function within our message loop will then send the WM_KEYDOWN message to our button’s window procedure.  The OnKeyDown event is fired in response to this message.  In the same fashion, the WM_CHAR and WM_KEYUP messages are responsible for the OnKeyPress and OnKeyUp events, respectively.

 

While the OnKeyDown, OnKeyPress, and OnKeyUp events correspond to keyboard input, the OnMouseDown, OnMouseMove, and OnMouseUp events are fired in response to mouse input.  OnMouseDown is fired in response to the WM_LBUTTONDOWN, WM_MBUTTONDOWN, or WM_RBUTTONDOWN messages.  OnMouseMove is fired in response to the WM_MOUSEMOVE message.  OnMouseUp is fired in response to the WM_LBUTTONUP, WM_MBUTTONUP, or WM_RBUTTONUP messages.  Similarly, the OnDblClick event is fired in response to the WM_LBUTTONDBLCLK message.

 

While we have discussed which of our button’s events correspond to which window messages, we still have yet to investigate how exactly these events are triggered.  Since the messages are sent to the window procedure of our button, we first need to examine the TWinControl::WndProc member function.  This latter function is simply a member function that is called from the TWinControl::MainWndProc member function.  As mentioned, all messages that are sent to a window are ultimately sent to its window procedure, which is simply a non-member callback function that defines how the window responds to certain messages.  We saw in Listing 1.6 an example of a window procedure that defines how a control responds to the WM_LBUTTONDOWN and WM_LBUTTONUP messages.

 

 

IIIB.  Inside the Window Procedure

 

As mentioned, a window procedure is a non-member callback function that receives all messages sent to a windowed control.  That is, it cannot be a (non-static) member function of a class.  On the contrary, the MainWndProc and WndProc functions are member functions of the TWinControl class.  The VCL performs this slight-of-hand via the MakeObjectInstance function, defined in the forms.pas unit.  This latter function uses the VirtualAlloc API function to map a non-member window procedure to the TWinControl::MainWndProc member function.  In turn, this member function calls the WndProc member function, protected around an Object Pascal try/except block, passing all exceptions to the TApplication::HandleException member function. 

 

What we see here amongst a maze of functions is the VCL’s attempt to encapsualte the C-style Windows messaging system in an object-oriented framework.  In the next series of articles, we will explore how the TWinControl class encapsulates the process of window creation.  For now, let us focus on the MainWndProc and WndProc member functions.  A condensed C++ translation of the Object Pascal TWinControl::MainWndProc member function is provided in Listing 1.7

 

 

 void __fastcall TWinControl::MainWndProc(TMessage& Message)

{

    try

    {

        try

        {

            WndProc(Message);

        }

        catch (...)

        {

            //

            // some clean-up routines...

            //

        }

        //

        // some clean-up routines...

        //

    }

    catch (...)

    {

        Application->HandleException(this);

    }

}

 

Listing 1.7

 

 

We see here that the MainWndProc member function uses the VCL TMessage structure.  This is similar to the API MSG structure, except the hwnd, time, and pt data members are excluded.

 

#pragma pack(push, 1)

struct TMessage

{

    unsigned long Msg;

    union

    {

       struct

       {

           WORD WParamLo;

           WORD WParamHi;

           WORD LParamLo;

           WORD LParamHi;

           WORD ResultLo;

           WORD ResultHi;               

       };

       struct

       {

           long WParam;

           long LParam;

           long Result;    

       };

    };

};

#pragma pack(pop)

 

Take note of the #pragma pack directives that surround the TMessage structure definition.  These simply serve to instruct the compiler to use single-byte data alignment such that the various TMessage data members will directly correspond to their associated members of the MSG structure.

 

 

Sidebar – A window procedure for non-windowed controls?


Isn't it a bit strange that the WndProc function is a member of the TControl class, and not first introduced by the TWinControl class?  Indeed, only true windowed controls can be sent window messages.  Remember, both the DispatchMessage and SendMessage functions require a handle to the target window, while only TWinControl descendants exhibit a handle.  However, like the SendMessage API function, the TControl::Perform member function can be used to send messages to all TControl descendants.  In this way, non-windowed controls can respond to mouse events as well.  The Perform function uses the TControl::WindowProc property to directly call WndProc function of the corresponding control. 

 

 

 

The TWinControl::WndProc member function passes most messages on to the default handler for the anscestor class.  For example, the TButton control requires very little intervention from the VCL, as most of the default functionality is built into the underlying BUTTON standard control.  A pseudo-C++ overview of the TWinControl::WndProc member function is provided in Listing 1.8.

 

 

void __fastcall TWinControl::WndProc(TMessage& Message)

{

    switch (Message.Msg)

    {     

        // ... 

        case // most mouse messages...       

        {

            TWMMouse WMMouse;

            WMMouse.Pos = PointToSmallPoint(

                            Point(Message.WParamLo, Message.WParamHi));

            WMMouse.XPos = Message.WParamLo;

            WMMouse.YPos = Message.WParamHi;

           

            if (IsControlMouseMsg(WMMouse))

            {

                if (WMMouse.Result == 0)

                    DefWndProc(Handle, Message.Msg,

                               Message.WParam, Message.LParam);

                return;

            }

            break;

        }

        case // most keyboard messages...

        {

            if (Dragging) return;

                   break;

        }

        // ...

    }

    TControl::WndProc(Message);

}

 

Listing 1.8

 

 

While the implementation of the TWinControl::WndProc member function may seem arbitrary at first, the thing to note is that the VCL handles only the messages necessary to maintain functionality with other VCL classes;  particularly, the mouse messages, WM_MOUSEMOVE through WM_MBUTTONDBLCLK.  It is the handler for these messages that allows mouse messages to be relayed to non-windowed controls. 

 

 

Sidebar – How non-windowed controls respond to mouse messages.

 

Notice that the TWinControl::IsControlMouseMsg function is called in response to all mouse messages.  This function simply performs internal hit-testing so that only the mouse events directed to the windowed control itself are processed by the window’s default window procedure.  Again, this is necessary only to accomodate for the ability of TGraphicControl descendants to publish mouse-input-related events.  For example, consider a TPanel with a TImage parented to it.  Here, the TPanel is a TWinControl descendant (i.e., a true windowed control with a window handle), while the TImage is a TGraphicControl descendant (no window handle).  Since Windows has no idea of the notion of graphical controls, the TImage control cannot receive any window messages.  (Recall, the DispatchMessage function within our main message loop uses the handle of the window specified in the MSG structure to determine which window procedure to direct the message to).   When the mouse cursor is moved over the Image, and thus over the Panel, the Panel’s WndProc member function, not the Image’s, will receive the WM_MOUSEMOVE message.  Yet, the TImage class publishes the OnMouseMove event.  Here is where the hit-testing and thus the IsControlMouseMsg function comes into play.  As in Listing 1.8, when the Panel’s WndProc member function receives the WM_MOUSEMOVE message, it calls the IsControlMouseMsg function.  It is the job of this latter function to determine if a child controls exists at the location of the cursor, and if so, pass the WM_MOUSEMOVE message to the child control, in this case the Image.  In turn, the TControl::WndProc member function will receive the WM_MOUSEMOVE message and ultimately fire the Image’s OnMouseMove event handler, if one is assigned.  The same sequence applies to other mouse messages as well.  It is in this way that TGraphicControl descendants can respond to mouse events.

 

 

 

Notice the last function call in the TWinControl::WndProc definition of Listing 1.8.  Here, the TControl::WndProc member function is called, allowing all unhandled messages to be passed on to this latter function.  Listing 1.9 contains an overview of the TControl::WndProc member function.

 

 

 

void __fastcall TControl::WndProc(TMessage& Message)

{

 

   //

   // (1) handle design-time mouse messages...

   //

 

   //

   // (2) handle keyboard messages...

   //

 

   //

   // (3) handle run-time mouse messages...

   // 

   //   (a) handle double clicks...

   //   (b) send hint messages for Application hints

   //   (c) support dragging and ControlStyle

   // 

 

    Dispatch(&Message);

}

 

Listing 1.9

 

 

Among other things, it is the job of the TControl::WndProc member function query the messages to maintain its ControlState property, provide information back to the TApplication object for hint window functionality, and filter out messages according to the TControl::ControlStyle property.  The function then calls the Dispatch member function, which passes the message on to either a specific message handler (if one has been specified) or the DefaultHandler member function.  For TWinControl descendants, the TWinControl::DefaultHandler member function uses the CallWindowProc API function to call the default window procedure of the underlying window class.  This ends the chain of functions that a window message can traverse. 

 

We have successfully traced the path of a typical window message through the various VCL member functions.  For queued messages, this path starts with the TApplication::HandleMessage member function, whereas for non-queued messagse, this path starts with the TWinControl::MainWndProc member function.  Both types of messages will ultimately end with the DefaultHandler member function.  In fact, not all messages need be passed on to the default window procedure.  Oftentimes a certain message will be trapped (i.e., handled internally), effectively bypassing the default window procedure.  In the proceeding sections, we will examine three techniques used to explicitly handle messages and later expand upon the idea of message trapping.

 

 

IV.  Message Handling

 

In the last section we discussed how a window procedure handles certain messages and specifically how the TWinControl class encapsulates this window procedure in the MainWndProc and WndProc member functions.  When a certain message need be handled, the logical approach is to simply tap into the WndProc member function.  As it turns out, there are several different approaches to handling specific messages.  Let us now examine each of these techniques – this will give us an insight into how messages are handled.  Later we will return to our VCL “Hello World!” example, and use our message handling knowledge to gain a true understanding of how VCL events are fired in response to certain messages.

 

 

IVA.  Augmenting the WndProc Member Function

 

Recall that the TControl::WndProc member function is a virtual member function.  It then follows that if we need to handle any specific (or all) messages sent to a particular control, all we need to do is augment (or override) that control's WndProc member function.  Listings 1.10a and 1.10b provide an example that illustrates this idea.

 

 

 

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

#ifndef Unit1H

#define Unit1H

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

#include <Classes.hpp>

#include <Controls.hpp>

#include <Forms.hpp>

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

 

class TForm1 : public TForm

{

private:      // User declarations

    void __fastcall DoMouseUp(TMessage& Msg);

protected:

    virtual void __fastcall WndProc(TMessage& Msg);

public:       // User declarations

    __fastcall TForm1(TComponent* Owner);

};

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

extern PACKAGE TForm1 *Form1;

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

#endif

 

Listing 1.10a

 

 

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

#include <vcl.h>

#pragma hdrstop

 

#include "Unit1.h"

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

#pragma resource "*.dfm"

TForm1 *Form1;

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

 

__fastcall TForm1::TForm1(TComponent* Owner)

    : TForm(Owner)

{

}

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

 

void __fastcall TForm1::WndProc(TMessage& Msg)

{

    switch (Msg.Msg)

    {

        case WM_LBUTTONUP:

        {

            DoMouseUp(Msg);

            break;

        }

    }

    TForm::WndProc(Msg);

}

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

 

void __fastcall TForm1::DoMouseUp(TMessage& Msg)

{

    int X = Msg.LParamLo;

    int Y = Msg.LParamHi;

    ShowMessage("mouse up at: " +

                IntToStr(X) + ", " + IntToStr(Y));

}

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

 

 

Listing 1.10b

 

 

In Listing 1.10a and 1.10b, we augment the WndProc member function in our TForm1 class.  In this way, we can manipulate or customize the way all instances of our form will respond to messages.  The augmented WndProc member function definition of Listing 1.10b contains a single swtich block, and in this case, a test for only one message, WM_LBUTTONUP.  It is in response to this message that we call our DoMouseUp member function, in which, we extract the mouse cursor coordinates from the LParamLo and LParamHi data members.  These members correspond to the low-order and high-order words of the 32-bit LParam data member, respectively. 

 

   

IVB.  Augmenting the Dispatch Member Function

 

In fact, augmenting the WndProc member function is not the most common way of handling a specific message.  Recall, the Dispatch member function is the last function called from within the TControl::WndProc definition of Listing 1.9.  This means is that after a message is processed by the TControl::WndProc member function, and previously by the TWinControl::WndProc member function for TWinControl descendants, it is passed on to the Dispatch member function.  This latter function, a virtual member of the TObject base class, serves the role of either calling a specific message handler or passing on the message to the DefaultHandler member function.  It then follows, that if we want to handle a specific message, we can alternatively augment the Dispatch member function.  Listings 1.10c and 1.10d illustrate this idea.

 

 

 

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

#ifndef Unit1H

#define Unit1H

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

#include <Classes.hpp>

#include <Controls.hpp>

#include <Forms.hpp>

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

 

class TForm1 : public TForm

{

private:      // User declarations

    void __fastcall DoMouseUp(TMessage &Msg);

public:       // User declarations

    __fastcall TForm1(TComponent* Owner);

    virtual void __fastcall Dispatch(void* Message);

};

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

extern PACKAGE TForm1 *Form1;

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

#endif

 

Listing 1.10c

 

 

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

#include <vcl.h>

#pragma hdrstop

 

#include "Unit1.h"

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

#pragma resource "*.dfm"

TForm1 *Form1;

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

 

__fastcall TForm1::TForm1(TComponent* Owner)

    : TForm(Owner)

{

}

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

 

void __fastcall TForm1::Dispatch(void* Message)

{

    TMessage* PMsg = static_cast<TMessage*>(Message);

    switch (PMsg->Msg)

    {

        case WM_LBUTTONUP:

        {

            DoMouseUp(*PMsg);

            break;

        }

    }

    TForm::Dispatch(Message);

}

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

 

void __fastcall TForm1::DoMouseUp(TMessage& Msg)

{

    int X = Msg.LParamLo;

    int Y = Msg.LParamHi;

    ShowMessage("mouse up at: " +

                IntToStr(X) + ", " + IntToStr(Y));

}

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

 

 

Listing 1.10d

 

 

In fact, the implementation of our augmented Dispatch function in Listing 1.10d is virtually identical to that of the augmented WndProc function of Listing 1.10b.  Further, both implementations are rather simple since we handle only a single message.  However, as the number of messages that need be handled increases, the implementation of our switch block can become enourmous and increasingly harder to read and maintain. 

 

 

IVC.  Using the Message Mapping Macros

 

Recall, Windows itself is based on a messaging system, and as an application provides more an more functionality that relies on the Windows API, handling on the order of hundreds of messages is not uncommon.  As such, the VCL introduces the the BEGIN_MESSAGE_MAP, VCL_MESSAGE_HANDLER, and END_MESSAGE_MAP macros defined in the sysdefs.h header file and provided in Listing 1.11.

 

 

 

#define BEGIN_MESSAGE_MAP                           \

    virtual void __fastcall Dispatch(void *Message) \

    {                                               \

            switch  (((PMessage)Message)->Msg)      \

            {

#define VCL_MESSAGE_HANDLER(msg,type,meth)          \

                case    msg:                        \

                    meth(*((type *)Message));       \

                    break;

#define END_MESSAGE_MAP(base)                       \

                default:                            \

                    base::Dispatch(Message);        \

                    break;                          \

            }                                       \

    }

 

Listing 1.11

 

 

We can see here that these macros simply perform the same process of augmenting the Dispatch member function as in Listing 1.10.  Using the above macros, we can rewrite the example in Listing 1.10 as that of Listing 1.12a and Listing 1.12b.

 

 

 

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

#ifndef Unit1H

#define Unit1H

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

#include <Classes.hpp>

#include <Controls.hpp>

#include <Forms.hpp>

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

 

class TForm1 : public TForm

{

__published:  // IDE-managed Components

private:      // User declarations

    void __fastcall DoMouseUp(TMessage& Msg);

public:       // User declarations

    __fastcall TForm1(TComponent* Owner);

 

BEGIN_MESSAGE_MAP

    VCL_MESSAGE_HANDLER(WM_LBUTTONUP, TMessage, DoMouseUp)

END_MESSAGE_MAP(TForm)

};

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

extern PACKAGE TForm1 *Form1;

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

#endif

 

Listing 1.12a

 

 

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

#include <vcl.h>

#pragma hdrstop

 

#include "Unit1.h"

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

#pragma resource "*.dfm"

TForm1 *Form1;

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

 

__fastcall TForm1::TForm1(TComponent* Owner)

    : TForm(Owner)

{

}

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

 

void __fastcall TForm1::DoMouseUp(TMessage &Msg)

{

    // extract the mouse coordinates from the

    // lParam of the underlying MSG structure

    int X = Msg.LParamLo;

    int Y = Msg.LParamHi;

 

    ShowMessage("mouse up at: " +

                IntToStr(X) + ", " + IntToStr(Y));

}

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

 

Listing 1.12b

 

 

The implementations provided in Listing 1.10 and Listing 1.12 are virtually identical.  The only difference is that the latter redefines the Dispatch member function inline, implicitly though the BEGIN_MESSAGE_MAP, VCL_MESSAGE_HANDLER, and END_MESSAGE_MAP macros.  The advantage presented by these macros not only lies in implementation ease, but also contributes greatly to the readability of the code and maintainability of the application.

 

 

VIII.  Summary and Remarks

 

In this first article alone, we have covered many of the concepts that are vital in creating an event-driven application.  The best advice here is not to get bogged down with the details of each implementation.  A high-level picture is good enough!  Let's recap the main concepts.

 

Beginning with a simple "Hello World!" extension, we discussed the basis of how an event-driven program is realized.  Particularly, we saw how the main message pump in a typical Windows application is little more than a fancy while loop.  This is the first step to understanding the event-driven nature of most Windows applications.  We also briefly discussed how the VCL's TApplication class encapsultes this main message loop, freeing us from the neccessity of such an implemention for each new application that we create.  Next, we covered the idea of Window messages; particularly, we discussed the difference between queued and non-queued messages, and how the former passes through our main message pump, while the latter is sent directly to a window procedure.  We also discussed how the VCL provides an object-oriented approach to that standard C-style window procedure, via the MakeObjectInstance VCL function and the  TWinControl::WndProc member function. 

 

To make the link between VCL events and window messages, we examined, how the TWinControl class handles specifics messages and fires their corresponding event handlers.  We also discussed three different ways of explcitly handling messages: augmenting the Dispatch member function (directly and via the message mapping macros), augmenting the WndProc member function, and instance subclassing.  We ended the discussion with an examination of the differences between each of these three approaches, and briefly discussed how to determine which technique to implement.  In the next article of this series, we'll discuss a technique called subclassing, which allows you to tap into the message stream of a component without creating a new class.

 

  

  Copyright © 2001 Damon Chandler; All rights reserved; e-mail: dmc27@cornell.edu

   
Please Rate "From Messages to Events Part I"
1 (worst) 2 3 4 5 (best)



Results


Click here to add a Rating tool like this to your site!