|  | Click Here!

Search the site:
EXPERT SEARCH

| Programming Languages
| Databases
| Security
| Web Services
| Network Services
| Middleware
| Components
| Operating Systems
| User Interfaces
| Groupware & Collaboration
| Content Management
| Productivity Applications
| Hardware
| Fun & Games

EarthWeb sites
Crossnodes
Datamation
Developer.com
DICE
EarthWeb.com
EarthWeb Direct
ERP Hub
Gamelan
GoCertify.com
HTMLGoodies
Intranet Journal
IT Knowledge
IT Library
JavaGoodies
JARS
JavaScripts.com
open source IT
RoadCoders
Y2K Info

| ## Charlie Calvert's C++ Builder Unleashed ## - 4 - ## Events In this chapter you take a look at the basic facts about the BCB delegation model. Events are fundamental to the use of this programming environment, and you will never be able to do serious work in BCB without understanding them. Subjects covered in this chapter include Using the mouse and the keyboard Responding directly to Windows messages such as `WM_KEYDOWN` or `WM_MOUSEMOVE` Custom event structures such as `TWMMouse` or `TMessage` Menus and menu IDs Setting up a `WndProc` in a VCL application Responding to `WM_COMMAND` messages This chapter is not designed to be difficult, but rather to hit the highlights of each of these subjects so you can understand how to use events in your own programs. By the time you are through with this chapter, you should have a thorough understanding of the BCB delegation model and will know how to use events in your own program. Additional material on events is included in the chapters that cover creating components. ### Events: The BCB Delegation Model The two central ideas behind the type of RAD programming of which BCB partakes are components and delegation. Components are treated at length in Part IV of this book, called "Creating Components." This is where I will talk about delegation. Delegation is an alternative to inheritance. It's a trick to allow you to receive the same benefits as inheritance but with less work. Delegation is not entirely original with RAD programming; however, it is taken much further in this paradigm than elsewhere. A part of classic windows programming that supports delegation is the way you handle standard `WM_COMMAND` messages. Think for a moment about standard Windows programming as it appeared before RAD rewrote the book on Windows programming. I'm talking the Windows programming of Petzold, or of my Teach Yourself Windows 95 Programming in 21 Days (Sams Publishing) book. Suppose that in a standard Windows program you have a button that responds to a particular event, say a message click. You usually handled that click inside the `WM_COMMAND` section of the window procedure (`WndProc`). Windows was delegating the event from the button to your `WM_COMMAND` handler. In other words, Windows did not force you to subclass the `button` class in order to respond to clicks on a button. This is the central idea behind the delegation model in BCB. In C++Builder, if you drop a button on a form, you can set up an `OnClick` event handler to handle clicks on the button. The great advantage this system has over the standard Windows model is ease of use. BCB will create the message handler for you, asking that you write only the code that responds to the event. Specifically, it fills in the class definition with a declaration for your event in the header:
    class TForm1 : public TForm

    {

    __published:

      TButton *Button1;

      void __fastcall Button1Click(TObject *Sender); // DECLARATION HERE!

    private:

    public:

      virtual __fastcall TForm1(TComponent* Owner);

    };
And it creates the even handler itself in your CPP file:
    void __fastcall TForm1::Button1Click(TObject *Sender)

    {

    }
This is what is meant by the delegation model in BCB. What is radical about the VCL's use of the delegation model is that it appears everywhere. All sorts of components support the model and enable you to use it to do all sorts of things that required subclassing in standard Windows programming and in OWL or MFC. One of the primary goals of the delegation model is to allow the main form in your application to handle code that you would have had to use inheritance to handle in OWL or MFC. In the inheritance model you had to override a constructor to change the way an object was initialized. In BCB, you just respond to an `OnCreate` event. If you want to modify the things a user can type into an edit control, you don't have to subclass the control; instead, you just respond to `OnKeyDown` events. Delegation is easier than inheritance. It enables neophytes who don't understand inheritance a way to get up to speed quickly. More importantly, it enables programmers to get their work done quickly without a lot of repetitive typing. Of course, if you want to use inheritance, it is available. In fact, inheritance is used all the time in BCB. The point is not to eliminate a powerful technique like inheritance, but to give you an alternative to use in the vast majority of cases where inheritance is overkill. #### The Delegation Model and Contract-Free Programming The delegation model supports something called contract-free programming. In some semiliterate circles this has also been known as "contractless" programming. The idea behind contract-free programming is that there is no contract between the developer of a component and the user of a component as to what can and can't be done inside an event handler. A classic example of a contract-bound program is found in Windows when you handle `WM_KILLFOCUS` message. There are some things you can and cannot do in response to this message. For example, if you change the focus during your response to this message, you can crash Windows. There is a contract between you and Windows not to change the focus while responding to this message. Learning all the things you can and cannot do in response to a message is a long, error-prone, and frustrating process. BCB supports contract-free programming, which means you can do whatever you want while responding to an event. BCB is religious in pursuit of this goal. In my code, I strive to achieve this same goal, though I admit that I have been known to cheat. When I do so, however, I severely limit the usability of my components. ### The Basics of the Delegation Model Event-oriented code is one of the central tenets of Windows programming. Some rapid- application development environments attempt to hide users from this feature altogether, as if it were something so complicated that most programmers couldn't understand it. The truth is that event-oriented programming is not, in itself, particularly complex. However, some features of the way it is implemented in Windows can be confusing under certain circumstances. BCB gives you full access to the event-oriented substructure that provides Windows with a high degree of power and flexibility. At the same time, it simplifies and clarifies the way a programmer handles those events. The end result is a system that gives you complete access to the power of Windows while simultaneously protecting you from unnecessary complexity. These next few sections cover the following topics: Event-oriented programming basics Responding to mouse events or key events Accessing the information passed in events The basics of sets, which are used frequently in BCB event handlers Circumventing BCB message handling tools and directly capturing messages Creating `WM_COMMAND` handlers and finding the IDs of the components used in a program To be really good at programming Windows, you need to be able to look at these subjects from both the perspective of a RAD programmer and of a Windows API programmer. That is, you need to know the VCL inside out, and you need to know the Windows API inside out. This chapter concentrates on the VCL side of that equation, whereas the other side is featured in books such as Teach Yourself Windows 95 Programming in 21 Days (Sams Publishing) and Programming Windows (Microsoft Press). Put both perspectives together and you can go on to create your own VCL components or you can design elegant, well-structured Windows programs. Before starting, let me reiterate that BCB hides much of the complexity of Windows programming. However, the developers did not want to prevent programmers from accessing any portion of the Windows API. By the time you finish reading this section of the chapter, you should be able to see that BCB gives you access to the full range of power provided by an event- oriented system. #### BCB Events BCB makes it easy to handle keyboard and mouse events. Suppose, for instance, you wanted to capture left mouse clicks in the main form of your program. Here's how to get started. Create a new project and name it `EVENTS1.MAK`. In the Object Inspector for the main form, choose the Events Page and double-click the area to the right of the `OnClick` property. Create the following function:
    void __fastcall TForm1::FormClick(TObject *Sender)

    {

      MessageDlg("The delegation model says hello.", mtInformation,

                 TMsgDlgButtons() << mbOK, 0);

    }
This code tells Windows that a dialog box should appear every time the user clicks the left mouse button in the form. The dialog box is shown in Figure 4.1. FIGURE 4.1. The dialog box displayed by the EVENTS1 program when you click the left mouse button inside the main form. The previous code presents one of the simplest possible cases of responding to an event in a BCB program. It is so simple, in fact, that many programmers write this kind of code without ever understanding that they are writing event-oriented code. In this case, BCB programmers get the event secondhand, because VCL massages the event before passing it on to the main form. Nonetheless, this is real event-oriented programming, albeit in a very simplified manifestation. As you saw back in the section on the Windows API, the operating environment notifies you not only of the event, but of several related bits of information. For instance, when a mouse- down event is generated, a program is informed about where the event occurred and which button generated the event. If you want to get access to this kind of relatively detailed information, you should turn to the Events Page for the form and create an `OnMouseDown` handler:
    void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button,

      TShiftState Shift, int X, int Y)

    {

      if (Shift.Contains(ssRight))

      {

        Canvas->Brush->Style = bsClear;

        Canvas->TextOut(X, Y, "* Button");

      }

    }
This method writes text to the form every time the user clicks the right mouse button. It sets the brush to `bsClear` style to make the background of text transparent. To test this method, run the program and click the right mouse button in several different locations in the form. You'll see that each spot you click is marked, as shown in Figure 4.2. FIGURE 4.2. When you click the right mouse button in the form of the EVENTS1 program, the location of the event is recorded. The `Canvas->TextOut` function prints text to the screen at the location specified by the variables `X` and `Y`. Both of these variables are supplied to you by BCB, which received them in turn from the operating system. The `X` variable tells you the column in which the mouse was clicked, and the `Y` variable tells you the row. As you can see, BCB makes it simple for you to respond to events. Furthermore, not only mouse events are easy to handle. You can respond to keypresses in a similar manner. For instance, if you create a method for the `OnKeyDown` property on the Events Page for the form, you can show the user which key was pressed on the keyboard whenever the EVENTS1 program has the focus:
    void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key,

      TShiftState Shift)

    {

      MessageDlg(Key, mtInformation, TMsgDlgButtons() << mbOK, 0);

    }
In the preceding code, the `MessageDlg` function will pop up the ASCII value associated with a keystroke. In other words, BCB and the operating system both pass you not an actual letter like `A`, `B`, or `C`, but the number associated with the key you pressed. On PCs, the letter `A` is associated with the number 65. It wouldn't be appropriate for BCB to perform this translation for you automatically, because some keys, such as the F1 or Enter key, have no letter associated with them. Later in this chapter you learn how to use `OnKeyDown` events to respond sensibly to keypresses on special keys such as F1, Shift, or Caps Lock. Besides `OnKeyDown` events, BCB also lets you respond to keyboard activity through the `OnKeyPress` event:
    void __fastcall TForm1::FormKeyPress(TObject *Sender, char &Key)

    {

      AnsiString S("OnKeyPress: " + AnsiString(Key));

      MessageDlg(S, mtInformation, TMsgDlgButtons() << mbOK, 0);

    }
You can see that this event is similar to an `OnKeyDown` event. The difference is that the `Key` variable passed to `OnKeyPress` events is already translated into a `char`. However, `OnKeyPress` events work only for the alphanumeric keys and are not called when special keys are pressed. In short, the `OnKeyPress` event is the same as a `WM_CHAR` event. The code for the EVENTS1 program is shown in Listing 4.1. Get the program up and running and take whatever time is necessary to be sure it all makes sense to you. There is no point in trying to be a Windows programmer if you don't understand events. Listing 4.1. The main form for the EVENTS1 program.
    ///////////////////////////////////////

    // Copyright (c) 1997 by Charlie Calvert

    //

    #include <vcl.h>

    #pragma hdrstop

    #include "Main.h"

    #pragma resource "*.dfm"

    TForm1 *Form1;

    __fastcall TForm1::TForm1(TComponent* Owner)

      : TForm(Owner)

    {

    }

    void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button,

      TShiftState Shift, int X, int Y)

    {

      if (Shift.Contains(ssRight))

      {

        Canvas->Brush->Style = bsClear;

        Canvas->TextOut(X, Y, "* Button");

      }

    }

    void __fastcall TForm1::DelegateMe(TObject *Sender)

    {

      MessageDlg("The menu says hello.", mtInformation,

                 TMsgDlgButtons() << mbOK, 0);

    }

    void __fastcall TForm1::FormClick(TObject *Sender)

    {

      MessageDlg("The delegation model says hello.", mtInformation,

                 TMsgDlgButtons() << mbOK, 0);

    }

    void __fastcall TForm1::FormKeyPress(TObject *Sender, char &Key)

    {

      AnsiString S("OnKeyPress: " + AnsiString(Key));

      MessageDlg(S, mtInformation, TMsgDlgButtons() << mbOK, 0);

    }

    void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key,

      TShiftState Shift)

    {

      MessageDlg(Key, mtInformation, TMsgDlgButtons() << mbOK, 0);

    }
After this introduction to event-oriented programming, it's time to step back and see some of the theory behind the code. After explaining something of how the system works, this chapter goes on to give examples of how to take full advantage of Windows event-oriented code base. #### Understanding Events Event-oriented programming isn't unique to Windows, nor is it a chore that can be handled only by an operating system. For instance, any DOS program could be based around a simple loop that keeps running the entire time the program is in memory. Here is a hypothetical example of how such code might look:
    do

    {

      CheckForMouseEvent(Events);

      CheckForKeyPress(Events)

      HandleEvents(Events);

    } while (!Events.Done);
This code represents a typical event-oriented loop. A simple `do..while` statement checks for keyboard and mouse events and then calls `HandleEvents` to give the program a chance to respond to the events that are generated by the user or the operating system. The variable called `Events` might be a record with a fairly simple structure:
    struct TEvent {

      int X, Y;

      TButton MouseButton;

      int Key;

      bool Done;

    };
`X` and `Y` give the current location of the cursor, and `Key` contains the value of the top event in the key buffer. The `TButton` type might have a declaration that looks like this:
    enum TButton {ButtonLeft, ButtonRight};
These structures permit you to track where the mouse is, what state its buttons are in, and what keys the user has pressed. Admittedly, this is a simple type of event structure, but the principles involved mirror what is going on inside Windows or inside other event-oriented systems such as Turbo Vision. If the program being written was an editor, pseudo-code for the `HandleEvent` for the program might look like this:
    void HandleEvent(TEvent: Events)

    {

      switch(Events.Key)

      {

        case "A..z":

          WriteXY(Events.X, Events.Y, Events.Key);

          break;

        case EnterKey:

          Write(CarriageReturn);

          break;

        case EscapeKey:

          Events.Done = TRUE;

          break;

       }

    }
Given the preceding code, the program would go to location `X,Y` and write the letter most recently pressed by the user. If the Enter key was pressed, a carriage return would be written to the screen. A press on the Esc key would cause the program to terminate. All other keypresses would be ignored. Code like this can be very powerful, particularly if you're writing a program that requires animation. For instance, if you need to move a series of bitmaps across the screen, you want to move the bitmap a few pixels and then check to see whether the user has pressed a button or hit a keystroke. If an event has occurred, you want to handle it. If nothing occurred, you want to continue moving the bitmap. I hope the short code samples shown here give you some feeling for the way event-oriented systems work. The only piece that's missing is an understanding of why Windows is event- oriented. Microsoft made Windows event-oriented in part because multiple programs run under the environment at the same time. In multitasking systems, the operating system needs to know whether the user has clicked in a program or whether the click was in the desktop window. If the mouse click occurred in a window that was partially hidden behind another window, it is up to the operating system to recognize the event and bring that window to the foreground. Clearly, it wouldn't be appropriate for the window itself to have to be in charge of that task. To ask that much would place an impossible burden on the programmer who created the window. As a result, it's best for the operating system to handle all the keystrokes and mouse clicks, and to then pass them on to the various programs in the form of events. Any other system would force every programmer to handle all the events that occurred when his or her program had the focus, and to manipulate the entire operating system in response to certain mouse events or keystrokes, such as Alt+Tab. In short, Windows programmers almost never directly monitor the hardware or hardware interrupts. Instead, the operating system handles that task. It passes all external events on to individual programs in the form of messages. In a typical event-oriented system, the operating system continually polls the hardware in a loop and then sends each event off to its programs in the form of some kind of event structure or event variables. This is the same kind of activity you saw in the brief code snippets shown earlier. You have seen that Windows handles mouse and keyboard events, and passes them on to the appropriate window. The message that is generated in these cases gets sent to the default window function, which, as you know, is called `DefWindowProc`. `DefWindowProc` is analogous to the `HandleEvent` function shown earlier. The important point to understand is that Windows messages contain the information that drives the entire operating environment. Almost everything that happens inside Windows is a message; if you really want to tap into the power of BCB, you need to understand how these messages work. One of the tricky parts of Windows event-oriented programming is extracting information from the `WPARAM` and `LPARAM` variables passed to the window function. In most cases, BCB frees you from the necessity of performing this task. For instance, if you create an event for the `OnMouseDown` property, BCB directly tells you the `X` value and `Y` value where the event occurred. As a programmer, you don't have to struggle to get the event and its associated values. As you will see in the next section, everything about the event is shown to you in a simple and straightforward manner. NOTE: `WPARAM` and `LPARAM` are the types of the parameters passed to a standard `WndProc` as is declared in `WinUser.h`:

    typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);

> These variables are usually given names such as `wParam` and `lParam` or `WParam` and `LParam`:

>     WndProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam);

> You will find that I sometimes refer to these parameters by type in all caps, and sometimes as variable identifiers with mixed caps and small letters. In all cases, I am referring to the same variables passed to the user from the system. Even in the discussion of the `TMessage` type, which occurs later in the chapter, I am still referring to these same parameters, only in a slightly different context. Specifically, `TMessage` is a VCL type that contains exact copies of these parameters. The `X` and `Y` parameters passed to an `OnMouseDown` event are not exact copies of `WPARAM` and `LPARAM`, but variables designed to display information originally contained in `WPARAM` or `LPARAM` after they have been parsed by the VCL.

### Using Sets to Track Messages

Rather than ask you to parse the `LPARAM` and `WPARAM` parameters, BCB performs this chore for you and then passes the information on in the form of parameters:

    void __fastcall TForm1::FormMouseDown(

      TObject *Sender,

      TMouseButton Button,

      TShiftState Shift,

      int X,

      int Y)

This is by far the most convenient way for you to handle events. BCB can also give direct access to the values sent to you by the operating system. That is, you can handle `WPARAM` and `LPARAM` directly if you want. After you have studied the EVENTS2 program and learned more about sets, I'll show you exactly how to get at that raw data.

Take a moment to consider the `Shift` parameter shown in the `FormMouseDown` header. `Shift` is declared to be of type `TShiftState`:

    enum Classes_1 { ssShift, ssAlt, ssCtrl, ssLeft, ssRight, ssMiddle, ssDouble };

    typedef Set<Classes_1, ssShift, ssDouble>  TShiftState;

`TShiftState` is a set, that is, it's an instance of the `Set` template class from `SYSDEFS.H`. To find out whether a particular element is a member of the set passed to you by BCB, you can perform simple tests using the `Contains` method:

    ShiftKey->Checked = Shift.Contains(ssShift);

    ControlKey->Checked = Shift.Contains(ssCtrl);

    LeftButton->Checked = Shift.Contains(ssLeft);

    RightButton->Checked = Shift.Contains(ssRight);

This code asks whether the element `ssRight` is in the set passed to you via the `Shift` variable. If it is, the code sets the state of a `TCheckBox` component.

Here is how you can declare a set at runtime:

    TShiftState LeftShift;

    LeftShift << ssLeft << ssShift;

Given this set, the `Contains` operator returns `True` if you ask about `ssLeft` or `ssShift`.

Besides the important `Contains` method, there are three key operators you can use with the `Set` class:

    +     Union

    -     Difference

    *     Intersection

All three of these operators return a set, whereas `Contains` returns a Boolean value. The SETSEXP program, shown in Listing 4.2, shows how to work with these key elements of the `Set` template class.
Listing 4.2. The SETEXP program shows how to use operators to track the members of sets such as TShiftState.

    ///////////////////////////////////////

    // Copyright (c) 1997 by Charlie Calvert

    //

    #include <vcl.h>

    #pragma hdrstop

    #include "Main.h"

    #pragma resource "*.dfm"

    TForm1 *Form1;

    __fastcall TForm1::TForm1(TComponent* Owner)

      : TForm(Owner)

    {

    }

    void TForm1::CheckState(TShiftState Shift)

    {

      ShiftKey->Checked = Shift.Contains(ssShift);

      ControlKey->Checked = Shift.Contains(ssCtrl);

      LeftButton->Checked = Shift.Contains(ssLeft);

      RightButton->Checked = Shift.Contains(ssRight);

    }

    void __fastcall TForm1::UnionClick(TObject *Sender)

    {

      AnsiString Operators[3] = {"+", "*", "-"};

      TShiftState FinalSet;

      TShiftState LeftShift;

      TShiftState LeftCtrl;

      LeftShift << ssLeft << ssShift;

      LeftCtrl << ssLeft << ssCtrl;

      switch (TOptType(dynamic_cast <TBitBtn*>(Sender)->Tag))

      {

        case otUnion:

          FinalSet = LeftShift + LeftCtrl;

          break;

        case otIntersection:

          FinalSet = LeftShift * LeftCtrl;

          break;

        case otDifference:

          FinalSet = LeftShift - LeftCtrl;

          break;

      }

      CheckState(FinalSet);

      Label2->Caption = Operators[dynamic_cast<TBitBtn *>(Sender)->Tag];

    }

    void __fastcall TForm1::Label4MouseDown(TObject *Sender, TMouseButton Button,

      TShiftState Shift, int X, int Y)

    {

      CheckState(Shift);

    }

The SETEXP program shows how you can read and manipulate sets. In particular, you work with sets of type `TShiftState`. When you are finished studying the program, you will have all the knowledge you need to work with BCB sets.

The main form for the SETEXP program consists of four checkboxes, two panels, four labels, and three bitbtns, as shown in Figure 4.3. The four checkboxes are placed on top of the first panel, and the fourth label is placed on the top of the second panel.
FIGURE 4.3. The SETEXP program's main form enables you to manipulate variables of type `TShiftState`.

SETEXP tells you whether the Shift or Ctrl keys are pressed when the mouse is clicked, and it tells you whether the user pressed the right or left mouse button. The code also shows how to use the `Intersection`, `Union`, and `Difference` operators.

The key method in the SETEXP program looks at a variable of type `TShiftState` and displays its contents to the user through the program's radio buttons:

    void TForm1::CheckState(TShiftState Shift)

    {

      ShiftKey->Checked = Shift.Contains(ssShift);

      ControlKey->Checked = Shift.Contains(ssCtrl);

      LeftButton->Checked = Shift.Contains(ssLeft);

      RightButton->Checked = Shift.Contains(ssRight);

    }

This code takes advantage of the fact that `Contains` returns a Boolean variable, and the `Checked` property of a radio button is also declared to be of type Boolean. As a result, you can test to see whether a particular element is part of the `Shift` set. If it is, you can easily set the `Checked` state of a radio button to record the result. For example, in the preceding code, if `ssShift` is part of the current set, the Shift Key radio button is checked.

Two different routines pass variables of type `TShiftState` to the `CheckState` method. The first routine is called whenever the user clicks in the panel at the bottom of the program or the label that rests on the panel:

    void __fastcall TForm1::Label4MouseDown(TObject *Sender, TMouseButton Button,

      TShiftState Shift, int X, int Y)

    {

      CheckState(Shift);

    }

This code passes the `Shift` variable on to `CheckState`, which displays the contents of the variable to the user. For instance, if the Shift key is being held down and the right mouse button is pressed, `Label4MouseDown` is called. `Label4MouseDown` then passes the `Shift` variable to `CheckState`, and `CheckState` causes the `ShiftKey` and `RightButton` controls to be checked. The other two radio buttons are left unchecked.

There are three bitbtns on the right side of the main form. They are labeled Union, Intersection, and Difference. Clicking any of these buttons demonstrates one of the non-Boolean set operators. All three buttons have their `OnClick` event set to the following function:

    void __fastcall TForm1::UnionClick(TObject *Sender)

    {

      AnsiString Operators[3] = {"+", "*", "-"};

      TShiftState FinalSet;

      TShiftState LeftShift;

      TShiftState LeftCtrl;

      LeftShift << ssLeft << ssShift;

      LeftCtrl << ssLeft << ssCtrl;

      switch (TOptType(dynamic_cast <TBitBtn*>(Sender)->Tag))

      {

        case otUnion:

          FinalSet = LeftShift + LeftCtrl;

          break;

        case otIntersection:

          FinalSet = LeftShift * LeftCtrl;

          break;

        case otDifference:

          FinalSet = LeftShift - LeftCtrl;

          break;

      }

      CheckState(FinalSet);

      Label2->Caption = Operators[dynamic_cast<TBitBtn *>(Sender)->Tag];

    }

The `UnionClick` method declares three variables of type `TShiftState`. Two of these variables are used to create sets that are used by the rest of the `SetBtnClick` method:

    LeftShift << ssLeft << ssShift;

    LeftCtrl << ssLeft << ssCtrl;

The first line assigns the `LeftShift` variable to a set that contains the values `ssLeft` and `ssShift`. The next line assigns the `LeftCtrl` variable to a set that contains `ssLeft` and `ssCtrl`. The rest of this method enables the user to see the union, intersection, and difference of these two sets.

The `switch` statement in the middle of the `SetBtnClick` method detects which of the three bitbtns the user clicked. This is the old, time-honored technique featuring the use of an enumerated type and the assignment of zero-based ordinal values to the `Tag` field of each button.

If the user clicks the Union button, the `FinalSet` variable is set to the union of the `LeftShift` and `LeftCtrl` variables:

    FinalSet = LeftShift + LeftCtrl;

A click on the Intersection button executes the following code:

    FinalSet = LeftShift * LeftCtrl;

The difference of the sets is calculated if the user clicks the Difference button:

    FinalSet = LeftShift - LeftCtrl;

After the `switch` statement ensures the selection of the proper operator, the `FinalSet` value is passed to `CheckState` and its contents are displayed to the user. For instance, if the user clicks the Union button, the LeftButton, ShiftKey, and ControlKey radio buttons are all checked. The Intersection button causes the LeftKey to be checked, and the Difference button causes the ShiftKey to be checked. Here is another way of looking at the work accomplished by these operators:

    [ssLeft, ssShift] + [ssLeft, ssCtrl] = [ssLeft, ssShift, ssCtrl];

    [ssLeft, ssShift] * [ssLeft, ssCtrl] = [ssLeft]

    [ssLeft, ssShift] - [ssLeft, ssCtrl] = [ssShift]

To help the user understand exactly what is happening, the current set operation is displayed at the top of the form. For instance, if the user clicks the Union button, the following expression is shown to the user:

    LeftShift + LeftCtrl

Throughout a run of the program, the words `LeftShift` and `LeftCtrl` are displayed to the user in a pair of `TLabels`. A third label displays `+`, `-`, or `*`, depending on the current state of the program:

    Label2->Caption = Operators[dynamic_cast<TBitBtn *>(Sender)->Tag];

In this code, `Operators` is an array of three strings that contains the operators that return a set:

    AnsiString Operators[3] = {"+", "*", "-"};

The SETEXP program gives you enough information that you should be able to work with the sets that are passed to BCB event handlers. The code shown here defines the way sets are usually handled in all BCB programs. However, you can actually directly manipulate the raw data that represents a BCB set. Techniques for performing these manipulations are shown in the `GetShift` method, which is part of the program examined in the next section of this chapter. You can also review the information on sets in Chapter 3.
Tracking the Mouse and Keyboard

You now know enough to begin an in-depth study of the main event handlers used by BCB forms and controls. The EVENTS2 program, shown in Listings 4.3 through 4.5, enables you to trace the occurrence of all the keyboard or mouse interrupts generated during the run of a program.
Listing 4.3. The EVENTS2 header and main form provide a detailed look at how to track events.

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

    #ifndef MainH

    #define MainH

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

    #include <Classes.hpp>

    #include <Controls.hpp>

    #include <StdCtrls.hpp>

    #include <Forms.hpp>

    #include <ExtCtrls.hpp>

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

    class TForm1 : public TForm

    {

    __published:

      TPanel *Panel1;

      TLabel *Label1;

      TLabel *LMouseMove;

      TLabel *Label3;

      TLabel *LMouseDown;

      TLabel *Label5;

      TLabel *LKeyDown;

      TLabel *Label7;

      TLabel *LKeyUp;

      TLabel *Label9;

      TLabel *LMouseUp;

      TLabel *Label11;

      TLabel *LWidth;

      TLabel *Label13;

      TLabel *LHeight;

      TLabel *LSpecialMouse;

      TLabel *Label16;

      void __fastcall FormResize(TObject *Sender);

      void __fastcall FormPaint(TObject *Sender);

      void __fastcall FormMouseUp(TObject *Sender, TMouseButton Button,

      	TShiftState Shift, int X, int Y);

      void __fastcall FormKeyUp(TObject *Sender, WORD &Key, TShiftState Shift);

      void __fastcall FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift);

      void __fastcall FormMouseMove(TObject *Sender, TShiftState Shift, int X,

      int Y);

      void __fastcall FormMouseDown(TObject *Sender, TMouseButton Button,

      	TShiftState Shift, int X, int Y);

    private:

    MESSAGE void MyMouseMove(TWMMouse &Message);

    public:

      virtual __fastcall TForm1(TComponent* Owner);

    BEGIN_MESSAGE_MAP

      MESSAGE_HANDLER(WM_MOUSEMOVE, TWMMouse, MyMouseMove);

    END_MESSAGE_MAP(TForm);

    };

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

    extern TForm1 *Form1;

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

    #endif

Listing 4.4. The EVENTS2 main form provides a detailed look at how to track events.

    ///////////////////////////////////////

    // Copyright (c) 1997 by Charlie Calvert

    //

    #include <vcl.h>

    #pragma hdrstop

    #include "Main.h"

    #include "VKeys1.h"

    #include "Binary.h"

    #pragma resource "*.dfm"

    TForm1 *Form1;

    __fastcall TForm1::TForm1(TComponent* Owner)

      : TForm(Owner)

    {

    }

    void TForm1::MyMouseMove(TWMMouse &Message)

    {

      TForm::Dispatch(&Message);

      LSpecialMouse->Caption = "X " + IntToStr(Message.XPos) +

                               " Y " + IntToStr(Message.YPos);

    }

    void __fastcall TForm1::FormResize(TObject *Sender)

    {

      LHeight->Caption = IntToStr(Width);

      LWidth->Caption = IntToStr(Height);

    }

    void __fastcall TForm1::FormPaint(TObject *Sender)

    {

      Canvas->Font->Name = "New Times Roman";

      Canvas->Font->Size = 48;

      Canvas->TextOut(1, Panel1->Height, "Mouse Zone");

    }

    void __fastcall TForm1::FormMouseUp(TObject *Sender, TMouseButton Button,

      	TShiftState Shift, int X, int Y)

    {

      LMouseUp->Caption = "X " + IntToStr(X) + " Y " +

                            IntToStr(Y) + " " + GetShift(Shift);

    }

    void __fastcall TForm1::FormKeyUp(TObject *Sender, WORD &Key, TShiftState Shift)

    {

      LKeyUp->Caption = GetKey(Key) + " " + GetShift(Shift);

    }

    void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key,

      TShiftState Shift)

    {

      LKeyDown->Caption = GetKey(Key) + " " + GetShift(Shift);

    }

    void __fastcall TForm1::FormMouseMove(TObject *Sender, TShiftState Shift, int X,

      	int Y)

    {

      LMouseMove->Caption = "X " + IntToStr(X) + " Y " +

                            IntToStr(Y) + " " + GetShift(Shift);

    }

    void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button,

      	TShiftState Shift, int X, int Y)

    {

      LMouseDown->Caption = GetShift(Shift);

    }

Listing 4.5. The VKeys unit is used by the EVENTS2 program.

    ///////////////////////////////////////

    // Vkeys.cpp

    // Project Name: Events2

    // Copyright (c) 1997 by Charles Calvert

    //

    #include <vcl.h>

    #pragma hdrstop

    #include "VKeys1.h"

    AnsiString MessageArray[5] =

      {"WM_CHAR", "WM_KEY", "WM_MOUSEMOVE", "WM_MOUSEDOWN", "WM_MOUSEUP"};

    AnsiString ButtonArray[3] =

      {"mbLeft", "mbRight", "mbCenter"};

    AnsiString ShiftArray[8] = {"ssShift", "ssAlt", "ssCtrl", "ssLeft",

       	"ssRight", "ssMiddle", "ssDouble", "ssUnknown"};

    AnsiString GetShift(TShiftState State)

    {

      int B = 0, i;

      AnsiString S;

      for (i = 0; i <= 7; i++)

      {

        if (State.Contains(i))

          S = S + " " +  ShiftArray[i];

      }

      return S;

    }

    AnsiString GetKey(WORD K)

    {

      AnsiString S;

      switch (K)

      {

        case VK_LBUTTON: S = "VK_LButton"; break;

        case VK_RBUTTON   : S = "VK_RBUTTON"; break;

        case VK_CANCEL    : S = "VK_CANCEL"; break;

        case VK_MBUTTON   : S = "VK_MBUTTON"; break;

        case VK_BACK      : S = "VK_BACK"; break;

        case VK_TAB       : S = "VK_TAB"; break;

        case VK_CLEAR     : S = "VK_CLEAR"; break;

        case VK_RETURN    : S = "VK_RETURN"; break;

        case VK_SHIFT     : S = "VK_SHIFT"; break;

        case VK_CONTROL   : S = "VK_CONTROL"; break;

        case VK_MENU      : S = "VK_MENU"; break;

        case VK_PAUSE     : S = "VK_PAUSE"; break;

        case VK_CAPITAL   : S = "VK_CAPITAL"; break;

        case VK_ESCAPE    : S = "VK_ESCAPE"; break;

        case VK_SPACE     : S = "VK_SPACE"; break;

        case VK_PRIOR     : S = "VK_PRIOR"; break;

        case VK_NEXT      : S = "VK_NEXT"; break;

        case VK_END       : S = "VK_END"; break;

        case VK_HOME      : S = "VK_HOME"; break;

        case VK_LEFT      : S = "VK_LEFT"; break;

        case VK_UP        : S = "VK_UP"; break;

        case VK_RIGHT     : S = "VK_RIGHT"; break;

        case VK_DOWN      : S = "VK_DOWN"; break;

        case VK_SELECT    : S = "VK_SELECT"; break;

        case VK_PRINT     : S = "VK_PRINT"; break;

        case VK_EXECUTE   : S = "VK_EXECUTE"; break;

        case VK_SNAPSHOT  : S = "VK_SNAPSHOT"; break;

        case VK_INSERT    : S = "VK_INSERT"; break;

        case VK_DELETE    : S = "VK_DELETE"; break;

        case VK_HELP      : S = "VK_HELP"; break;

        case VK_NUMPAD0   : S = "VK_NUMPAD0"; break;

        case VK_NUMPAD1   : S = "VK_NUMPAD1"; break;

        case VK_NUMPAD2   : S = "VK_NUMPAD2"; break;

        case VK_NUMPAD3   : S = "VK_NUMPAD3"; break;

        case VK_NUMPAD4   : S = "VK_NUMPAD4"; break;

        case VK_NUMPAD5   : S = "VK_NUMPAD5"; break;

        case VK_NUMPAD6   : S = "VK_NUMPAD6"; break;

        case VK_NUMPAD7   : S = "VK_NUMPAD7"; break;

        case VK_NUMPAD8   : S = "VK_NUMPAD8"; break;

        case VK_NUMPAD9   : S = "VK_NUMPAD9"; break;

        case VK_MULTIPLY  : S = "VK_MULTIPLY"; break;

        case VK_ADD       : S = "VK_ADD"; break;

        case VK_SEPARATOR : S = "VK_SEPARATOR"; break;

        case VK_SUBTRACT  : S = "VK_SUBTRACT"; break;

        case VK_DECIMAL   : S = "VK_DECIMAL"; break;

        case VK_DIVIDE    : S = "VK_DIVIDE"; break;

        case VK_F1        : S = "VK_F1"; break;

        case VK_F2        : S = "VK_F2"; break;

        case VK_F3        : S = "VK_F3"; break;

        case VK_F4        : S = "VK_F4"; break;

        case VK_F5        : S = "VK_F5"; break;

        case VK_F6        : S = "VK_F6"; break;

        case VK_F7        : S = "VK_F7"; break;

        case VK_F8        : S = "VK_F8"; break;

        case VK_F9        : S = "VK_F9"; break;

        case VK_F10       : S = "VK_F10"; break;

        case VK_F11       : S = "VK_F11"; break;

        case VK_F12       : S = "VK_F12"; break;

        case VK_F13       : S = "VK_F13"; break;

        case VK_F14       : S = "VK_F14"; break;

        case VK_F15       : S = "VK_F15"; break;

        case VK_F16       : S = "VK_F16"; break;

        case VK_F17       : S = "VK_F17"; break;

        case VK_F18       : S = "VK_F18"; break;

        case VK_F19       : S = "VK_F19"; break;

        case VK_F20       : S = "VK_F20"; break;

        case VK_F21       : S = "VK_F21"; break;

        case VK_F22       : S = "VK_F22"; break;

        case VK_F23       : S = "VK_F23"; break;

        case VK_F24       : S = "VK_F24"; break;

        case VK_NUMLOCK   : S = "VK_NUMLOCK"; break;

        case VK_SCROLL    : S = "VK_SCROLL"; break;

      default:

        S = K;

      }

      return S;

    }

EVENTS2 shows how to extract the full content of a message sent to you by BCB. The main form for the program (shown in Figure 4.4) provides information on a wide range of mouse and keyboard-generated events.
FIGURE 4.4. The EVENTS2 program tracks key Windows events as they occur.

To use the program, simply compile and run it. Click the mouse in random locations and strike any of the keys on the keyboard. Just rattle away; you won't do any harm unless you press Ctrl+Alt+Del. Every time you move the mouse, click the mouse or strike a key; the exact nature of the event that occurred is shown in the main form of the program. For instance, if you move the mouse, its current location is shown on the form. If you press the F1 key while the Ctrl key is pressed, those keys' values are displayed on the form.

If you don't have the `MyMouseMove` function defined, the `FormMouseMove` event handler in the `EVENTS2` window tracks the current location of the mouse and the state of its buttons. It does this by responding to `OnMouseMove` events:

    void __fastcall TForm1::FormMouseMove(TObject *Sender, TShiftState Shift, int X,

      int Y)

    {

      LMouseMove->Caption = "X " + IntToStr(X) + " Y " +

                            IntToStr(Y) + " " + GetShift(Shift);

    }

The method for tracking the `X` and `Y` values is fairly intuitive. `X` stands for the current column, and `Y` stands for the current row, with columns and rows measured in pixels. Before these values can be shown to the user, they need to be translated into strings by the `IntToStr` function. Nothing could be simpler than the techniques used to record the current location of the mouse.

The technique for recording the current shift state, however, is a bit more complex. As you saw earlier, the elements of this set track all the possible states of the Shift, Alt, and Ctrl keys, as well as the mouse buttons.

The `Set` class makes it a simple matter to create a function that will find all the currently selected elements of a variable of type `TShiftState`:

    AnsiString GetShift(TShiftState State)

    {

      int B = 0, i;

      AnsiString S;

      for (i = 0; i <= 7; i++)

      {

        if (State.Contains(i))

          S = S + " " +  ShiftArray[i];

      }

      return S;

    }

The preceding code takes advantage of the following constant array:

    AnsiString ShiftArray[8] = {"ssShift", "ssAlt", "ssCtrl", "ssLeft",

       	"ssRight", "ssMiddle", "ssDouble", "ssUnknown"};

More specifically, the code checks to see whether the first possible element of the set is active, and if it is, `"ssShift"` is added to the string returned by the function. If the next element in the enumerated type underlying the set is present, the string `"ssAlt"` is added to the string returned by the function, and so on. As you move through the elements in the underlying enumerated type, you get a picture of the current state of the mouse and keyboard. For instance, if the Shift and Ctrl keys are pressed, as well as the right mouse button, the string returned by the function looks like this:

    ssShift ssCtrl ssRight

### Trapping Virtual Keys

When keys are pressed in a Windows program, two different messages can be sent to your program. One message is called `WM_KEYDOWN`, and it is sent whenever any key on the keyboard is pressed. The second message is called `WM_CHAR`, and it is sent when one of the alphanumeric keys is pressed. In other words, if you press the A key, you get both a `WM`_`KEYDOW` and a `WM_CHAR` message. If you press the F1 key, only the `WM_KEYDOWN` message is sent.

`OnKeyPress` event handlers correspond to `WM_CHAR` messages, and `OnKeyDown` events correspond to `WM_KEYDOWN` events. That's why `OnKeyPress` handlers are passed a `Key` variable that is of type `char`, and `OnKeyDown` handlers are passed a `Key` variable that is of type `WORD`.

When you get a `WM_KEYDOWN` message, you need to have some way of translating that message into a meaningful value. To help with this chore, Windows declares a set of virtual key constants that all start with `vk`. For example, if you press the F1 key, the `Key` variable passed to an `OnKeyDown` event is set to `VK_F1`, in which the letters `vk` stand for virtual key. The virtual key codes are found in the `WINDOWS` unit and also in the online help under Virtual Key Codes.

You can test to see which virtual key has been pressed by writing code that looks like this:

    if (Key == VK_CANCEL)

      DoSomething();

This code simply tests to see whether a particular key has been pressed. If it has, the code calls the `DoSomething` function.

To help you understand virtual keys, the `GetKey` method from the `VKEYS` unit returns a string stating exactly what key has been pressed:

    AnsiString GetKey(WORD K)

    {

      AnsiString S;

      switch (K)

      {

        case VK_LBUTTON: S = "VK_LButton"; break;

        case VK_RBUTTON   : S = "VK_RBUTTON"; break;

        case VK_CANCEL    : S = "VK_CANCEL"; break;

        case VK_MBUTTON   : S = "VK_MBUTTON"; break;

        case VK_BACK      : S = "VK_BACK"; break;

        case VK_TAB       : S = "VK_TAB"; break;

        case VK_CLEAR     : S = "VK_CLEAR"; break;

        case VK_RETURN    : S = "VK_RETURN"; break;

        case VK_SHIFT     : S = "VK_SHIFT"; break;

        case VK_CONTROL   : S = "VK_CONTROL"; break;

        case VK_MENU      : S = "VK_MENU"; break;

        case VK_PAUSE     : S = "VK_PAUSE"; break;

        case VK_CAPITAL   : S = "VK_CAPITAL"; break;

        case VK_ESCAPE    : S = "VK_ESCAPE"; break;

        case VK_SPACE     : S = "VK_SPACE"; break;

        case VK_PRIOR     : S = "VK_PRIOR"; break;

        case VK_NEXT      : S = "VK_NEXT"; break;

        case VK_END       : S = "VK_END"; break;

        case VK_HOME      : S = "VK_HOME"; break;

        case VK_LEFT      : S = "VK_LEFT"; break;

        case VK_UP        : S = "VK_UP"; break;

        case VK_RIGHT     : S = "VK_RIGHT"; break;

        case VK_DOWN      : S = "VK_DOWN"; break;

        case VK_SELECT    : S = "VK_SELECT"; break;

        case VK_PRINT     : S = "VK_PRINT"; break;

        case VK_EXECUTE   : S = "VK_EXECUTE"; break;

        case VK_SNAPSHOT  : S = "VK_SNAPSHOT"; break;

        case VK_INSERT    : S = "VK_INSERT"; break;

        case VK_DELETE    : S = "VK_DELETE"; break;

        case VK_HELP      : S = "VK_HELP"; break;

        case VK_NUMPAD0   : S = "VK_NUMPAD0"; break;

        case VK_NUMPAD1   : S = "VK_NUMPAD1"; break;

        case VK_NUMPAD2   : S = "VK_NUMPAD2"; break;

        case VK_NUMPAD3   : S = "VK_NUMPAD3"; break;

        case VK_NUMPAD4   : S = "VK_NUMPAD4"; break;

        case VK_NUMPAD5   : S = "VK_NUMPAD5"; break;

        case VK_NUMPAD6   : S = "VK_NUMPAD6"; break;

        case VK_NUMPAD7   : S = "VK_NUMPAD7"; break;

        case VK_NUMPAD8   : S = "VK_NUMPAD8"; break;

        case VK_NUMPAD9   : S = "VK_NUMPAD9"; break;

        case VK_MULTIPLY  : S = "VK_MULTIPLY"; break;

        case VK_ADD       : S = "VK_vkADD"; break;

        case VK_SEPARATOR : S = "VK_SEPARATOR"; break;

        case VK_SUBTRACT  : S = "VK_SUBTRACT"; break;

        case VK_DECIMAL   : S = "VK_DECIMAL"; break;

        case VK_DIVIDE    : S = "VK_DIVIDE"; break;

        case VK_F1        : S = "VK_F1"; break;

        case VK_F2        : S = "VK_F2"; break;

        case VK_F3        : S = "VK_F3"; break;

        case VK_F4        : S = "VK_F4"; break;

        case VK_F5        : S = "VK_F5"; break;

        case VK_F6        : S = "VK_F6"; break;

        case VK_F7        : S = "VK_F7"; break;

        case VK_F8        : S = "VK_F8"; break;

        case VK_F9        : S = "VK_F9"; break;

        case VK_F10       : S = "VK_F10"; break;

        case VK_F11       : S = "VK_F11"; break;

        case VK_F12       : S = "VK_F12"; break;

        case VK_F13       : S = "VK_F13"; break;

        case VK_F14       : S = "VK_F14"; break;

        case VK_F15       : S = "VK_F15"; break;

        case VK_F16       : S = "VK_F16"; break;

        case VK_F17       : S = "VK_F17"; break;

        case VK_F18       : S = "VK_F18"; break;

        case VK_F19       : S = "VK_F19"; break;

        case VK_F20       : S = "VK_F20"; break;

        case VK_F21       : S = "VK_F21"; break;

        case VK_F22       : S = "VK_F22"; break;

        case VK_F23       : S = "VK_F23"; break;

        case VK_F24       : S = "VK_F24"; break;

        case VK_NUMLOCK   : S = "VK_NUMLOCK"; break;

        case VK_SCROLL    : S = "VK_SCROLL"; break;

      default:

        S = char(K);

      }

      return S;

    }

This function is really just a giant `case` statement that checks to see whether the `Key` variable is equal to any of the virtual keys. If it is not, the code assumes that it must be one of the standard keys between `A` and `Z`. (See the `else` clause in the code to see how these standard keys are handled.)

As explained in the last paragraph, the virtual key codes do not cover normal letters such as `A`, `B`, and `C`. In other words, there is no value `VK_A` or `VK_B`. To test for these letters, just use the standard ASCII values. In other words, test whether `Key` is equal to `65`, or whether `char(key) = `A'`. The point here is that these letters already have key codes. That is, the key codes for these letters are the literal values `A`, `B`, `C`, and so on. Because these are perfectly serviceable values, there is no need to create virtual key codes for the standard letters of the alphabet, or for numbers.

To see the value as a number, make the default section of the code look like this:

    S = K;

Then if you press the A key, you get back `65`. If you want to see the letter ``A'` instead, write the following:

    S = char(K);

You probably won't have much use for the `GetKey` routine in a standard BCB program. However, it is useful when you are trying to understand virtual keys and the `OnKeyDown` event. As a result, I have included it in this program.

#### Handling Events Directly

If you look at the bottom of the `EVENTS2` form, you see that there is a special event that tracks the position of the mouse. The EVENTS2 program tracks the mouse movements in two different ways because I wanted to show you that you can get information about the mouse either by responding to `OnMouseMove` events or by directly tracking `WM_MOUSEMOVE` messages.

Here is how you declare a function that is going to directly capture a message:

    class TForm1 : public TForm

    {

    __published:

      ... // Declarations omitted

    private:

    MESSAGE void MyMouseMove(TWMMouse &Message);

    public:

      virtual __fastcall TForm1(TComponent* Owner);

    BEGIN_MESSAGE_MAP

      MESSAGE_HANDLER(WM_MOUSEMOVE, TWMMouse, MyMouseMove);

    END_MESSAGE_MAP(TForm);

    };

The declaration shown here tells BCB that you want to respond directly when the operating system informs your program that the mouse has moved. In other words, you don't want the BCB VCL to trap the message first and then pass it on to you in an `OnMouseMove` event. Instead, you just want the message sent straight to you by the operating system, as if you were working with one of the Windows API programs shown earlier in the book. In short, you're telling the VCL: "Yes, I know you can make this task very simple and can automate nearly the entire process by using visual tools. That's nice of you, but right now I want to get the real event itself. I have some reason of my own for wanting to get very close to the metal. As a result, I'm going to grab the message before you ever get a chance to look at it!"

Here's the code for the `MyMouseMove` function:

    void TForm1::MyMouseMove(TWMMouse &Message)

    {

      TForm::Dispatch(&Message);

      LSpecialMouse->Caption = "X " + IntToStr(Message.XPos) +

                               " Y " + IntToStr(Message.YPos);

    }

You can see that the code begins by calling the `Dispatch` method inherited from `TObject`. If you didn't make this call, the program would still run, but the `OnMouseMove` event would never be sent to the `FormMouseMove` function. It isn't an error if you don't pass the message back to BCB. You can either keep the message for yourself or pass it on, as you prefer.

If you omit the call to `Dispatch` from the `MySpecialMouse` function, the `FormMouseMove` method in the EVENTS2 program is no longer called. In other words, you are directly trapping `WM_MOUSEMOVE` messages and not passing them on to the VCL. As a result, the VCL does not know that the event occurred, and `FormMouseMove` is not called.

The explanation in the last paragraph might not be easy to grasp unless you actually experiment with the EVENTS2 program. You should run the program once with the default version of the `MyMouseMove` method, and once with the call to `Inherited` commented out:

    void TForm1::MyMouseMove(TWMMouse &Message)

    {

      // TForm::Dispatch(&Message);

      LSpecialMouse->Caption = "X " + IntToStr(Message.XPos) +

                               " Y " + IntToStr(Message.YPos);

    }

Notice that when you run the program this way, the `OnMouseMove` message at the top of the form is left blank.

If you look at the header for the `MyMouseMove` function, you can see that it is passed a parameter of type `TWMMouse`. As you recall, the `TWMMouse` record, found in `MESSAGES.PAS`, looks like this:

    struct TWMMouse

    {

      unsigned int Msg;

      long Keys;

      union

      {

        struct

        {

          Windows::TSmallPoint Pos;

          long Result;

        };

        struct

        {

          short XPos;

          short YPos;

        };

      };

    };

If you break out both of the options shown in this variant record, you can further simplify this record by writing

    struct TWMMouse

    {

      unsigned int Msg;

      long Keys;

      Windows::TSmallPoint Pos;

      long Result;

    };

or

    struct TWMMouse

    {

      unsigned int Msg;

      long Keys;

      short XPos;

      short YPos;

    };

For most users, one of these two views will be the most useful way to picture the record.

The same information is present in a `TWMMouse` record that you would find if you responded to an `OnMouseMove` or `OnMouseDown` event. If appropriate, you can find out the row and column where the mouse is located, what key is pressed, and what state the Shift, Alt, and Ctrl keys are in. To pursue this matter further, you should look up `WM_MOUSEMOVE` and `WM_MOUSEDOWN` messages in the online help.

`TWMMouse` plays the same role in a BCB program that message crackers from `WINDOWSX.H` play in a C++ program. In other words, they automatically break out the values passed in `lParam` or `wParam` parameters of the `WndProc`. However, if you want, you can pass a variable of type `TMessage` as the parameter sent to the `WM_MOUSEMOVE` message handler.

Because `TMessage` and `TWMMouse` are both the same size, BCB doesn't care which one you use when trapping `WM_MOUSEMOVE` events. It's up to you to decide how you want to crack the `wParam` and `lParam` parameters passed to the `WndProc`.

In this section, you have learned something about directly handling Windows messages. When you write code that captures messages directly, you are in a sense reverting back to the more complicated model of programming found in Borland C++ 5.x. However, there are times when it is helpful to get close to the machine; BCB lets you get there if that is what you need to do.

#### Menu IDs, Handling WM_COMMAND, Finding TForms WndProc

In standard Windows programming, as it was conducted before the appearance of visual tools, one of the most important messages was `WM_COMMAND`. This message was sent to a program every time the user selected a menu item or a button, or clicked almost any other control that is part of the current program. Furthermore, each of the buttons, menu items, and other controls in a program had a special ID, which was assigned by the programmer. This ID was passed to `WM_COMMAND` handlers in the `wParam` variable.

BCB handles `WM_COMMAND` messages in such a way that you almost never have to think about them. For instance, you can get clicks on a button or menu by using the delegation model. Standard BCB controls still have IDs, but BCB assigns these numbers automatically, and there is no obvious way for you to learn the value of these IDs.

Despite BCB's capability to simplify this aspect of Windows programming, there are still times when you want to get down to the bare bones and start handling `WM_COMMAND` messages yourself. In particular, you will want to find a way to discover the ID associated with a particular command, and you will want to trap that ID inside a `WM_COMMAND` handler.

> WARNING: If you are new to good RAD programming tools like BCB, you are likely to think you need to get hold of menu IDs a lot more often than you really need to get hold of them. When I first came to this paradigm, I was used to using menu IDs and I had a lot of tricks that were associated with them. As a result, I was determined to use them in my first RAD programs. That was a mistake, and I did nothing but waste time trying to walk down that road.
> If the developers of the VCL thought you had any need at all for these ids, they would have made them readily available. The fact that they are a bit difficult to get at should tell you that it is very rare for anyone to ever need to access these items. It wouldn't have been hard to make these IDs available; there was simply no need to do it, and some good reasons to hint broadly that RAD had some better ways to do this type of programming. The obscurity of menu IDs is not a weakness of RAD; they are just a broad hint that you don't need to worry about that side of Windows programming any more.
> The people who designed the VCL are very bright folks who know the Windows API in intimate, excruciating detail. Their goal was to create an easy-to-use programming environment that still gives users full access to all the features of Windows. They would never deliberately cut you off from something you needed.
> Don't forget that both Delphi and BCB are built in the VCL by the same people who created the VCL in the first place. One of the reasons the VCL is so good is that it was honed by its creators during the process of creating the Delphi and BCB IDEs. If there were something wrong with the way the VCL was designed, it became apparent to the developers while they were creating the IDE, and it was fixed.
> This was not a tool created cynically in a back room by a bunch of guys who looked down on their own users as second-rate programmers. They themselves were the programmers who first used their tools, so they did everything they could to build them correctly.

The `MENUDEF` program gives a general overview of how to handle `WM_COMMAND` messages. The program enables you to discover the ID used by a series of menu items and then enables you to trap these IDs when they are sent to a `WM_COMMAND` handler in the form of a `TMessage.wParam` variable.

As a bonus, the program also shows how to use the `TForm` `WndProc` method, which lets you set up your own window function that receives all the messages sent to your form. It is extremely rare that you would ever need to override this virtual method in your own programs, but I show you how to do it just so you will understand a little more about how the VCL operates. You should be warned that this is a dangerous method to override, because it might come into existence before your controls are initialized and ready for use and might disappear after the controls have been disposed. For instance, I check for `WM_CLOSE` messages, and once I get one, I don't try to show you any more messages coming into the `WndProc`. Needless to say, you would crash your program immediately if you overrode this method and did not call its ancestor.

Figure 4.5 shows the form for the MENUDEF program. Here are the menu items that you can't see in Figure 4.5:

    Caption = `File'

      Caption = `Open'

      Caption = `Close'

      Caption = `Exit'

    Caption = `Edit'

      Caption = `Cut'

      Caption = `Copy'

      Caption = `Paste'

FIGURE 4.5. The MENUDEF program uses a `TMemo`, a `TButton`, and a `TMainMenu` control.

The code for the MENUDEF program is in Listings 4.6 and 4.7. You can see that it features two standard BCB event handlers, as well as a `WM_COMMAND` handler and the overridden `WndProc` virtual method.
Listing 4.6. The MENUDEF program shows how to retrieve the ID of a BCB menu item.

    #ifndef MainH

    #define MainH

    #include <Classes.hpp>

    #include <Controls.hpp>

    #include <StdCtrls.hpp>

    #include <Forms.hpp>

    #include <Menus.hpp>

    class TForm1 : public TForm

    {

    __published:

      TMemo *Memo1;

      TButton *MenuID;

      TMainMenu *MainMenu1;

      TMenuItem *File1;

      TMenuItem *Open1;

      TMenuItem *CLose1;

      TMenuItem *Exit1;

      TMenuItem *Exit2;

      TMenuItem *Cut1;

      TMenuItem *Copy1;

      TMenuItem *Paste1;

      TListBox *ListBox1;

      TButton *MiniWinSight;

      void __fastcall MenuIDClick(

      TObject *Sender);

      void __fastcall MiniWinSightClick(

      TObject *Sender);

    private:

     MESSAGE void WMCommand(TMessage &Message);

     int TotalMenuItems;

     int MenuItemArray[100];

    protected:

      virtual void __fastcall WndProc(Messages::TMessage &Message);

    public:

      virtual __fastcall TForm1(TComponent* Owner);

    BEGIN_MESSAGE_MAP

      MESSAGE_HANDLER(WM_COMMAND, TMessage, WMCommand);

    END_MESSAGE_MAP(TForm);

    };

    extern TForm1 *Form1;

    #endif

Listing 4.7. The MENUDEF program uses a TMemo, a TButton, and a TMainMenu control.

    ///////////////////////////////////////

    // Copyright (c) 1997 by Charlie Calvert

    //

    #include <vcl.h>

    #pragma hdrstop

    #include "Main.h"

    #pragma resource "*.dfm"

    TForm1 *Form1;

    static bool ShowMessages = FALSE;

    __fastcall TForm1::TForm1(TComponent* Owner)

      : TForm(Owner)

    {

      int i;

      TotalMenuItems = 0;

      for (i = 0; i < ComponentCount; i++)

        if (dynamic_cast<TMenuItem *>(Components[i]))

        {

          MenuItemArray[TotalMenuItems] =

            dynamic_cast<TMenuItem*>(Components[i])->Command;

          TotalMenuItems++;

        }

    }

    void TForm1::WMCommand(TMessage &Message)

    {

      int i,j;

      AnsiString S1(IntToStr(Message.WParam)), S3;

      for (i = 0; i < TotalMenuItems; i++)

        if (Message.WParam == MenuItemArray[i])

        {

          for (j = 0; j < ComponentCount; j++)

          {

            if (dynamic_cast<TMenuItem*>(Components[j]))

              if (dynamic_cast<TMenuItem*>(Components[j])->Command ==

                  MenuItemArray[i])

                S3 = dynamic_cast<TMenuItem*>(Components[j])->Caption;

          }

          S1 = "ID: " + S1 + "\rName: " +  S3;

          MessageBox(Handle, S1.c_str(), "Menu Item Info", MB_OK);

        }

      TForm::Dispatch(&Message);

    }

    void __fastcall TForm1::MenuIDClick(TObject *Sender)

    {

      int Command, i;

      AnsiString Name;

      Memo1->Lines->Clear();

      for (i = 0; i < ComponentCount; i++)

      {

        if (dynamic_cast<TMenuItem*>(Components[i]))

        {

          Command = dynamic_cast<TMenuItem*>(Components[i])->Command;

          Name = dynamic_cast<TMenuItem*>(Components[i])->Caption;

          Memo1->Lines->Add(Name + " = " + IntToStr(Command));

        }

      }

    }

    void __fastcall TForm1::MiniWinSightClick(

      TObject *Sender)

    {

      ShowMessages = True;

    }

    void ShowMsg(AnsiString &S)

    {

      if (Form1->ListBox1)

        Form1->ListBox1->Items->Add(S);

      if (Form1->ListBox1->Items->Count > 6)

        Form1->ListBox1->TopIndex = Form1->ListBox1->Items->Count - 5;

    }

    void HandleMessages(TMessage &Msg)

    {

      AnsiString S;

      switch(Msg.Msg)

      {

        case WM_PAINT:

          S = "wm_Paint";

          ShowMsg(S);

          break;

        case WM_MOUSEMOVE:

          S = IntToStr(Msg.LParamLo) + " " + IntToStr(Msg.LParamHi);

          S = "WM_MOUSEMOVE " + S;

          ShowMsg(S);

          break;

        case WM_LBUTTONDOWN:

          S = IntToStr(Msg.LParamLo) + " " + IntToStr(Msg.LParamHi);

          S = "WM_LBUTTONDOWN" + S;

          ShowMsg(S);

          break;

        case WM_RBUTTONDOWN:

          S = IntToStr(Msg.LParamLo) + " " + IntToStr(Msg.LParamHi);

          S = "WM_RBUTTONDOWN" + S;

          ShowMsg(S);

          break;

    /*    case WM_NCHITTEST: // Uncomment WM_NCHITEST to see a flurry of messages.

          S = "WM_NCHITTEST";

          ShowMsg(S);

          break; */

        }

    }

    void __fastcall TForm1::WndProc(Messages::TMessage &Message)

    {

      if (Message.Msg == WM_CLOSE)

        ShowMessages = FALSE;

      if (ShowMessages)

        HandleMessages(Message);

      TForm::WndProc(Message);

    }

The MENUDEF program has two features:

- If you click the button at the bottom of the main form, the program's `memo` control displays a list of all the items in the menu, along with their IDs.

- If you click any of the menu items, a message box appears stating the name of the menu item and its ID.

Here is the code that grabs the ID of all the menu items and displays the ID along with the menu item's caption in a `TMemo`:

    void __fastcall TForm1::MenuIDClick(TObject *Sender)

    {

      int Command, i;

      AnsiString Name;

      Memo1->Lines->Clear();

      for (i = 0; i < ComponentCount; i++)

      {

        if (dynamic_cast<TMenuItem*>(Components[i]))

        {

          Command = dynamic_cast<TMenuItem*>(Components[i])->Command;

          Name = dynamic_cast<TMenuItem*>(Components[i])->Caption;

          Memo1->Lines->Add(Name + " = " + IntToStr(Command));

        }

      }

    }

The code begins by clearing the current contents of the `memo` control. It then iterates through all the components on the form and finds any of them that are of type `TMenuItem`. The next step is to get the ID and the caption of the menu items. To get the ID, you need only reference the `Command` property of the `TMenuItem` component. The caption can be retrieved the same way, and then you can add this information to the list box.

The use of a `dynamic_cast` in the previous code demonstrates BCB's capability to work with Run Time Type Information (RTTI). RTTI enables you to test the type of a particular variable and respond accordingly. For instance, in this case the program simply asks whether a particular component is of type `TMenuItem`; if this Boolean question returns `True`, the program examines the component in more depth.

The remaining code in the program captures the IDs of the menu items in an array and then responds to `WM_COMMAND` messages generated by clicks in one of the program's menu items. As explained earlier, the code displays a message box stating that the menus are not yet functional. In the caption of the menu box, you can read the ID of the control that was clicked.

The code that captures the menu items in an array occurs in a `Forms` constructor:

    __fastcall TForm1::TForm1(TComponent* Owner)

      : TForm(Owner)

    {

      int i;

      TotalMenuItems = 0;

      for (i = 0; i < ComponentCount; i++)

        if (dynamic_cast<TMenuItem *>(Components[i]))

        {

          MenuItemArray[TotalMenuItems] =

            dynamic_cast<TMenuItem*>(Components[i])->Command;

          TotalMenuItems++;

        }

    }

This code is almost identical to the `MenuIDClick` method, except that the IDs of the `TMenuItem`s are stored in an array rather than being shown in a `TMemo`. The declaration for the array looks like this:

    int MenuItemArray[100];

The declaration for the `WM_COMMAND` handler should be familiar to you by this time:

    MESSAGE void WMCommand(TMessage &Message);

    BEGIN_MESSAGE_MAP

      MESSAGE_HANDLER(WM_COMMAND, TMessage, WMCommand);

    END_MESSAGE_MAP(TForm);

Here, the message directive tells the compiler that this is a dynamic method and the offset in the dynamic method table is established by the `WM_COMMAND` constant.

The `WMCommand` method compares the values sent in `Message.wParam` to the values in the `MenuItemArray`. If it finds a match, it displays the message box previously described.

    void TForm1::WMCommand(TMessage &Message)

    {

      int i,j;

      AnsiString S1(IntToStr(Message.WParam)), S3;

      for (i = 0; i < TotalMenuItems; i++)

        if (Message.WParam == MenuItemArray[i])

        {

          for (j = 0; j < ComponentCount; j++)

          {

            if (dynamic_cast<TMenuItem*>(Components[j]))

              if (dynamic_cast<TMenuItem*>(Components[j])->Command ==

                MenuItemArray[i])

                S3 = dynamic_cast<TMenuItem*>(Components[j])->Caption;

          }

          S1 = "ID: " + S1 + "\rName: " +  S3;

          MessageBox(Handle, S1.c_str(), "Menu Item Info", MB_OK);

        }

      TForm::Dispatch(&Message);

    }

This code calls the Windows API `MessageBox` function rather than `MessageDlg`.

Here is the code that overrides the window function:

    void __fastcall TForm1::WndProc(Messages::TMessage &Message)

    {

      if (Message.Msg == WM_CLOSE)

        ShowMessages = FALSE;

      if (ShowMessages)

        HandleMessages(Message);

      TForm::WndProc(Message);

    }

The `TMessage` `struct` looks like this:

    struct TMessage

    {

      unsigned int Msg;

      union

      {

        struct

        {

          unsigned short WParamLo;

          unsigned short WParamHi;

          unsigned short LParamLo;

          unsigned short LParamHi;

          unsigned short ResultLo;

          unsigned short ResultHi;

        };

        struct

        {

          long WParam;

          long LParam;

          long Result;

        };

      };

    };

This structure has all the information in it that you would get if you were inside a standard windows function. For all intents and purposes, you are inside a standard window function. (See `TApplication.Create` in the Pascal source, as well as the `TForm` object, for more information.)

At this stage I could begin a 400- or 500-page analysis of window functions, messages, and the structure of the Windows operating system. (That was pretty much what I did in Teach Yourself Windows 95 Programming in 21 Days (Sams Publishing), so there is a source for this kind of information if you want it.) However, this book is not about that kind of material, so I will leave this method hanging and let you explore it to the degree to which the subject matter calls you. Remember, however, that this method can be dangerous to handle, as it is active even when the controls on your form have not been created, and it is still active after they have been destroyed.

One final point about working with the IDs of a control: If you build the interface of your program and then don't change its menus or any other aspect of its interface, the IDs that BCB associates with each control will (at least theoretically) remain the same throughout the development of your application. This might give some people the idea of using the `MenuIDClick` method, shown earlier, as a temporary response to a click on one of your controls and the storing of the output to a file. Thereafter, you would hope to know the ID associated with each of your controls and could handle them in a `WM_COMMAND` routine. This technique is theoretically possible, but I wouldn't recommend it as part of the framework for any serious application. In short, if you have to know the ID of a particular control, I would determine it in the `FormCreate` method, thereby ensuring that the ID is correct for that build of your program.

### Summary

In this chapter you learned how to handle events using the standard BCB delegation model. You saw that when the need arises, you can circumvent this system and handle events directly by employing message maps. You can also pass messages back to the system by calling the inherited `Dispatch` method. Events represent one of the most important subjects in BCB programming, and you probably shouldn't move on until you have a good idea of what's going on in the EVENTS2 program.

This chapter also explained that you can handle `wParam` and `lParam` variables directly. Furthermore, BCB gives you a way to parse the information passed with events, so that you can place a custom record as the parameter to a message handler. For instance, if Windows conceals a set of `X` and `Y` coordinates inside the high or low words of `wParam`, BCB enables you to define a custom record that automatically breaks up `wParam` into two separate variables called `X` and `Y`. This is the same functionality that is found in the message crackers provided in `WINDOWSX.H`. The `TWMMouse` record discussed earlier is one of these records; `TMessage` is another. If you want, you can create your own records that parse the information associated with standard BCB events or with events that you create yourself. You are not limited to custom BCB handlers such as `TMessage` or `TWMMouse`.

©Copyright, Macmillan Computer Publishing. All rights reserved.

| Use of this site is subject certain Terms & Conditions. Copyright (c) 1996-1999 EarthWeb, Inc.. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Please read our privacy policy for details.