Messages and Events of Windows Controls |
|
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. |
The controls of a typical application send messages to the operating system. As the operating system is constantly asked to perform these assignments, because there can be so many requests presented unpredictably, it (the operating system) leaves it up to the controls to specify what they want, when they want it, and what behavior or result they expect. A message, like a letter you send to somebody, must provide a few pieces of information in order to be processed. For a Win32 application, these pieces of information are stored in a structure called MSG. It is defined as follows: typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSG, *PMSG; |
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 ‘;’.
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.
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 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(); } //---------------------------------------------------------------------------
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||
Previous | Copyright © 2010-2016, FunctionX | Next |
|