Messages Fundamentals
|
Introduction
|
An application is made of various objects or controls.
During the lifetime of the application, its controls regularly send
messages to the operating system to do something on their behalf. These
messages must be processed appropriately. Also, most of the time (in fact
all the time), more than one application is (many processes are) running
on the computer.
|
For a Win32 application, each message is processed by
a function called a window procedure. A window procedure is a pointer to
function, therefore declared as CALLBACK, and it returns a positive
32-bit number. Therefore, a MSG variable must be passed to the
window procedure for processing.
In a Win32 application, a window procedure that
processes a message requires 3 pieces of information with the last piece
divided in two (which produces 4 pieces of information):
Here is an example:
//---------------------------------------------------------------------------
LRESULT CALLBACK WndProc(HWND hWnd,
UINT Msg,
WPARAM wParam,
LPARAM lParam)
{
switch(Msg)
{
case WM_DESTROY:
PostQuitMessage(WM_QUIT);
break;
default:
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
return 0;
}
//---------------------------------------------------------------------------
Once again, to make programming fast, the VCL provides
its own message structure called TMessage and defined as follows:
struct TMessage { Cardinal Msg; union { struct { Word WParamLo; Word WParamHi; Word LParamLo; Word LParamHi; Word ResultLo; Word ResultHi; }; struct { int WParam; int LParam; int Result; }; }; };
A TMessage message must provide two pieces of
information, as the TMessage structure shows: the name of the
message and one group of information. Unlike the Win32 MSG
structure, in a VCL application, the object that sends the message is
already known (because it is the one that sent the message). The
accompanying items of the message are coded into either the top or the
bottom structures of the anonymous union. The two anonymous structures
inside the union indicate that you use either the top or the bottom
structure but not both.
Once a control has composed a message, it must send it
to the right target, which could be the operating system. In order to send
a message, a control must create an event. The control is also said to
fire an event. To make a distinction between the two, a message's name
will usually be written in uppercase. An example would be WM_MOVE,
which stands for "Window Message Move". The name of an event usually
starts with On, which indicates an action.
Remember, the message is what needs to be sent. The
event is the action of sending the message.
As mentioned already, the messages of a Win32
application are processed by a window procedure. For a VCL application,
you can use the same approach. Alternatively, you can use a technique
referred to as creating a map of messages.
For the compiler to manage messages, they should be
listed in a public section of the class definition. The messages are
included in a list referred to as a message map. The list of messages,
that is, the message map, starts with the BEGIN_MESSAGE_MAP and
ends with the END_MESSAGE_MAP macros. The END_MESSAGE_MAP
macro takes an argument, which is the original class that holds the
primary implementation of the messages. Here is an example:
#ifndef StaticTextH #define StaticTextH #include <Controls.hpp> //--------------------------------------------------------------------------- class TStaticText : TGraphicControl { __published: private: public: BEGIN_MESSAGE_MAP END_MESSAGE_MAP(TGraphicControl) }; //--------------------------------------------------------------------------- #endif
Between the BEGIN_MESSAGE_MAP and the
END_MESSAGE_MAP macros, each message is declared using either the
MESSAGE_HANDLER or the VCL_MESSAGE_HANDLER macros. Their
syntaxes are:
MESSAGE_HANDLER(VCLMessageName, TMessage, EventName); VCL_MESSAGE_HANDLER(WindowsMsgName, VCLMessageName, EventName);
There are various categories of messages the operating
system receives. Some of them come from the keyboard, some from the mouse,
and some others from various other sources. For example, some messages are
sent by the application itself while some other messages are controlled by
the operating system.
The most commonly sent messages have already been
created in the objects of the VCL controls so much that you will hardly
need to define new messages, at least not in the beginning of your
C++Builder programming adventure. Most of what you will do consists of
implementing the desired behavior when a particular message is sent.
To start, you should know what messages are available,
when, and how they work. As mentioned already, each control sends its own
messages when necessary. Based on this, some messages are unique to some
controls according to their roles. Some other messages are common to
various controls, as they tend to provide similar actions. To manage such
various configurations, the VCL considers the messages in two broad
categories. Those that take an argument and those that do not. The VCL
defines events as function pointers (or pointers to function).
As it happens, some messages do not require much
information to be performed. For example, suppose your heart sends a
message to the arm and states, “Raise your hand”. In this case, suppose
everything is alright, the arm does not ask, "how do I raise my hand?". It
simply does. This type of message would be sent without any accompanying
information. Consider another message where the arm carries some water and
says to the mouth, "Swallow the following water". The mouth would need the
water that needs to be swallowed. Therefore, the message must be
accompanied by additional information, which is considered an argument.
Consider one more message where the heart says to the tongue, “Taste the
following food but do not swallow it.” In order to process this message,
the tongue would need the food and something to indicate that the food
must not be swallowed. In this case, the message must be accompanied by
two pieces of information.
To process messages that do not require any additional
argument, the VCL creates such an event with the TNotifyEvent type.
Such an event is declared as a pointer to function in the classes.hpp
library as follows:
typedef void __fastcall (__closure *TNotifyEvent)(System::TObject* Sender);
The Sender argument is the control that is sending the
messages.
Besides TNotifyEvent, the other events carry
different and appropriate names.
Although there are different ways you can implement an
event, there are two main ways you can initiate its coding. If the control
has a default event and if you double-click it, the compiler would
initiate the default event. Another technique you can use is to click the
Events tab of the Object Inspector. This would display a list of the
events associated with the selected control:
The list is divided in two columns. The name of each
event is displayed on the left side. You can click the name of an event to
reveal a combo box. If a similar event (similar events are those that
share a behavior) has already been written, you can click the arrow of the
combo box and select it from the list:
Otherwise, to initiate an event double-click the field
on the right column of the name of the desired event.
When an event has been initiated, you would be
transported to the Code Editor and the caret would be positioned in the
body of the event, ready to receive your instructions. To customize an
event, the compiler divides its structure in three sections:
//--------------------------------------------------------------------------- void __fastcall TForm1::FormMouseMove(TObject *Sender, TShiftState Shift, int X, int Y) { // Write code associated with the event here } //---------------------------------------------------------------------------
The coding of an event starts with its return value.
All events in C++Builder return void. All events use the
__fastcall convention.
The return type is followed by the name of the parent
class from where the event would be fired. This is mainly the class that
controls the form.
After the name of the class, the compiler rightly uses
the class member access operator (::) to call the event, following a C++
rule.
The name of an event is made of a combination of the
control that “owns” or fired the event and the name of the event.
Each event has at least one argument, the TObject
*Sender. Some events use additional arguments that we will review when
coding such events.
The user typically presses a key, which sends
a signal to a program. The signal is analyzed to find its meaning.
If the program or control that has focus is equipped to deal with
the signal, it may produce the expected result. If the program or
control cannot figure out what to do, it ignores the action.
Each key has a code that the operating system can
recognize. This code is known as the virtual key code and they are as
follows:
The following keys apply to the numeric keypad
There are actually more keys than that but the above
are the most frequently used.
The VCL implements keyboard events using two function
pointers, TKeyEvent and TKeyPress that depend on the
message.
When a keyboard key is pressed, a message called
WMKeyDown is sent. WMKeyDown is a TKeyEvent message that
produces the OnKeyDown event. Its syntax is:
void __fastcall OnKeyDown(System::TObject* Sender, Word &Key, Classes::TShiftState Shift);
The Key argument specifies the key that was
pressed. The OnKeyDown() event can be sent by any key. Alphabetic
keys are represented by their character. For example, if the user presses
p, the Key argument would be represented as ‘p’. If the user presses a
key, the Key argument would have the value of ‘;’.
The Shift argument specifies whether the Shift, the Ctrl, or the Alt keys were pressed along with the Key key. It is a Pascal Set whose enumerator has the following members:
If the user presses two keys as an uppercase letter,
such R, the Key argument would have a value of ‘r’. The Shift argument
would be used to indicate that the Shift key was down when the letter r
was pressed.
As opposed to the key down message that is sent when a
key is down, the WMKeyUp message is sent when the user releases the
key. Like WMKeyDown, WMKeyUp is a TKeyEvent message.
It produces the OnKeyUp event whose syntax is:
void __fastcall OnKeyUp(System::TObject* Sender, Word &Key, Classes::TShiftState Shift);
The Key argument specifies the key that was
pressed. Like the OnKeyDown event, the OnKeyUp event
processes any key. Alphabetic keys are represented by their character.
The Shift argument indicates whether the Shift, the Ctrl, or the Alt key participates in a key combination such as Shift + $
When the user presses a key, the WMKeyPress
message is sent. Unlike the other two keyboard messages, the key pressed
for this event should (must) be a character key. WMKeyPress
produces the OnKeyPress event whose syntax is:
void __fastcall OnLKeyPress(System::TObject* Sender, char &Key);
The Key argument must be a letter or a
recognizable symbol. Lowercase alphabetic characters, digits, and the
lower base characters such as ; , ‘ [ ] - = / are recognized as they are.
For an uppercase letter or an upper base symbols, the user must press
Shift + the key. The character would be identified as one entity. This
means that the symbol % typed with Shift + 5 is considered as one
character.
|
The mouse is another object that is attached to the
computer allowing the user to interact with the machine. The mouse and the
keyboard can each accomplish some tasks that are not normally available on
the other and both can accomplish some tasks the same way.
The mouse is equipped with two, three, or more buttons.
When a mouse has two buttons, one is usually located on the left and the
other is located on the right. When a mouse has three buttons, one is in the
middle of the other two. The mouse is used to select a point or position on
the screen. Once the user has located an item, which could also be an empty
space, a letter or a word, he or she would position the mouse pointer on it.
To actually use the mouse, the user would press the left, the middle (if
any), or the right button. If the user presses the left button once, this
action is called Click. If the user presses the right mouse button, the
action is referred to as Right-Click. If the user presses the left button
twice and very fast, the action is called Double-Click.
Mouse events are implemented as TMouseEvent and
TMouseMoveEvent function pointers.
Imagine the user has located a position or an item on a
document and presses one of the mouse buttons. While the button is pressed
and is down, a button-down message is sent. The syntax of this event is as
follows:
void __fastcall OnMouseDown(System::TObject* Sender, TMouseButton Button, Classes::TShiftState Shift, int X, int Y);
The Button argument specifies what button was
clicked. The buttons of the mouse are identified by the TMouseButton
enumerator whose members are:
The Shift argument indicates whether mouse button
and/or a keyboard key was/were pressed and held down when the Button was
clicked. It can have one of the following values:
The X and the Y argument represent the TPoint(X,
Y) point where the mouse was clicked.
After pressing a mouse button, the user usually releases
it. While the button is being released, a button-up message is sent and it
depends on the button, left or right, that was down. The event produced is
OnMouseUp.
The OnMouseUp event uses the same syntax as the
OnMouseDown event and processes the same arguments.
Whenever the mouse is positioned and being moved on top
of a control, a mouse event is sent. This event is implemented as a
TMouseMoveEvent function pointer and its syntax is:
void __fastcall OnMouseMove(System::TObject* Sender, Classes::TShiftState Shift, int X, int Y);
The Shift argument is the same as the other mouse
messages. The X and Y values identify the location of the mouse at the time
this event fires.
The list of methods and messages available for the
objects used in your applications is very impressive because the VCL tries
to help process as many messages as possible. Unfortunately, in some areas,
the VCL tends to address only the most commonly used messages, which is
still very long, as we will see throughout our lessons.
The Win32 library provides more messages than the VCL
implements. Normally, you should first check if a message you want to
process is already available for your control. If it is not, you can create
your own message and its event. Once again you have various options. If the
message belongs to, or must be processed by, a form, you can create it in
the header file of the form. Otherwise, you can create a new control by
simply deriving a class from an existing control.
Another way you will manipulate controls consists of
calling a Win32 API function. There are two main categories of functions you
will call: those that must identify the control that is calling the function
and those that do not. If the function requires the control that called it,
you can specify the control using its handle. If the function does not need
this piece of information, then you can omit it. We will see various types
of both categories of functions.
Most of the messages we will use in our applications are
implemented in various classes of the VCL. Some others are not. Some of the
messages are available in the Win32 library and you can use them in your
application. This is made possible by calling the SendMessage()
function. Its syntax is:
LRESULT SendMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
The hWnd argument is the object or control that
is sending the message.
The Msg argument is the message to be sent. The wParam and the lParam values depend on the message that is being sent.
The advantage of using the SendMessage() function
is that, when sending this message, it would target the procedure that can
perform the task and this function would return only after its message has
been processed. Because this function can sometimes universally be used,
that is by almost any control or object, the application cannot predict the
type of message that SendMessage() is carrying. Therefore, (the
probable disadvantage is that) you must know the (name or identity of the)
message you are sending and you must provide accurate accompanying items.
Here is an example that changes the caption of a form
using the WM_SETTEXT message:
//--------------------------------------------------------------------------- void __fastcall TForm1::FormCreate(TObject *Sender) { const char *Msg = "This message was sent"; SendMessage(Handle, WM_SETTEXT, 0, (LPARAM)(LPCTSTR)Msg); } //---------------------------------------------------------------------------
Besides the SendMessage() function, the
TControl class provides the Perform() method that allows you to
send a message any time. Its syntax is:
int __fastcall Perform(unsigned Msg, int WParam, int LParam);
The TControl::Perform() method functions like the
SendMessage() function except that, since it is a VCL function, it
omits the handle to the control because the control that calls it would be
specified already. The first argument, Msg, is the identifier of the
message that needs to be processed. It is exactly like the second argument
of the SendMessage() function. Like the SendMessage()
function, the values of WParam and LParam depend on the
message. Here is an example used to close a form:
//--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { Perform(WM_CLOSE, 0, 0); } //---------------------------------------------------------------------------
To customize an existing message of a control or to
create a new message that is not available for your control, the TControl
class provides the WndProc() method. Its syntax is:
virtual void __fastcall WndProc(Messages::TMessage &Message);
In order to use this method, you must override it in
your own class. Here is an example:
//--------------------------------------------------------------------------- class TForm1 : public TForm { __published: // IDE-managed Components private: // User declarations public: // User declarations __fastcall TForm1(TComponent* Owner); virtual void __fastcall WndProc(TMessage &Msg); }; //---------------------------------------------------------------------------
The Message argument is any message you want to process.
As done for a Win32 application, the Message value is typically passed to a
switch condition and each desired message is treated in a case statement.
After processing the messages and before closing the member function, you
should (must) call the parent class to process the messages that were not
treated. Here is an example that treats the WM_MOVE message and
prevents the user from moving the form (by preventing the mouse from
capturing the title bar):
//--------------------------------------------------------------------------- void __fastcall TForm1::WndProc(TMessage &Message) { switch(Message.Msg) { case WM_MOVE: ReleaseCapture(); break; } TForm::WndProc(Message); } //---------------------------------------------------------------------------
After creating the message, if it is intended for the
form to treat, the message is ready and its event would fire appropriately.
If the event is for a particular control, you must let the compiler know
that you have a window procedure that would process a particular message or
the messages of a certain control. To do this, you can assign WndProc
to the control’s WindowProc member variable. This could be done in
the constructor or the OnCreate() event of the form as follows:
//--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { this->WindowProc = WndProc; } //---------------------------------------------------------------------------
As we mentioned earlier, instead of using a window
procedure, you can create a message map that lists the event(s) that you
want to fire. Make sure you also declare the function that will carry the
event:
//--------------------------------------------------------------------------- #ifndef Unit1H #define Unit1H //--------------------------------------------------------------------------- #include <Classes.hpp> #include <Controls.hpp> #include <StdCtrls.hpp> #include <Forms.hpp> //--------------------------------------------------------------------------- class TForm1 : public TForm { __published: // IDE-managed Components private: // User declarations void __fastcall TForm1::DontMoveMe(TObject *Sender); public: // User declarations __fastcall TForm1(TComponent* Owner); BEGIN_MESSAGE_MAP VCL_MESSAGE_HANDLER(WM_MOVE, TObject *, DontMoveMe) END_MESSAGE_MAP(TForm) }; //--------------------------------------------------------------------------- extern PACKAGE TForm1 *Form1; //--------------------------------------------------------------------------- #endif
After doing this in the header file, in the source file,
implement the method as you see fit. Here is an example:
//--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "Unit1.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm1::DontMoveMe(TObject * Sender) { ReleaseCapture(); } //---------------------------------------------------------------------------
The events of Windows controls are designed to be
executed at specific periods in response to some occurrence. For example,
only when the user presses a mouse button can a message related to this
action would occur. Yet, one of the most influential ways of creating an
effective program is to anticipate users actions as much as possible. This
allows you to correctly respond. To support this idea, the VCL provides a
system of combining controls messages and their associated events.
When the user initiates an action to send a message, the
control that owns the action sends the message and an accompanying method.
This method, although belonging to the control, allows other controls or
other parts of the program to access the event as if they controlled when
such an event would occur. For example, if the user clicks a control, the
control composes and sends a click message. Because the control is also
equipped with a Click() method, another control of the application
can call its Click() event and perform the same action. Here is an
example:
//--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { Perform(WM_CLOSE, 0, 0); } //--------------------------------------------------------------------------- void __fastcall TForm1::Panel1MouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { Button1->Click(); } //---------------------------------------------------------------------------
|
No comments:
Post a Comment