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
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:
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.
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.
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. ///////////////////////////////////////
// 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:
+
- 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. ///////////////////////////////////////
// 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. 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,
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
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. 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. //--------------------------------------------------------------------------
#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(
{
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. 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+
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(
{
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. 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'
#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:
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 TMenuItems 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.
VMS Desenvolvimentos
Diversas Dicas, Apostilas, Arquivos Fontes,
Tutoriais, Vídeo Aula, Download de Arquivos Relacionado a Programação em C++ Builder.
|