Using and Creating Messages


The Windows operating system uses messages to notify the application that an event occurred. The operating system generates a message every time something happens — for example, when the user clicks the Start button, types something, or moves a window — and sends that message to the appropriate window.

Messages have several details, the most important of which are the window handle, the message identifier, and two message parameters. When a message occurs, its details are written to a TMsg record and then passed to the appropriate application. The TMsg record is declared in the Windows unit, shown in Listing 20-11.

Listing 20-11: Windows message details

image from book
tagMSG = packed record   hwnd: HWND;   message: UINT;   wParam: WPARAM;   lParam: LPARAM;   time: DWORD;   pt: TPoint; end; TMsg = tagMSG;
image from book

The hwnd field is the handle of the window to which the message is sent. In the Windows operating system, window handles are 32-bit integer values used to uniquely identify a window. Also, in the Windows operating system, all controls, like buttons, edit boxes, and list boxes, are windows and have a handle value that uniquely identifies them.

The message field is an integer value that identifies the message. For each Windows message there is a more descriptive constant in the Messages unit. All Windows message constants start with the WM_ prefix.

Listing 20-12: Several standard Windows messages

image from book
WM_MOVE            = $0003; { window has been moved } WM_ENABLE          = $000A; { change window state } WM_PAINT           = $000F; { repaint the window client area } WM_CLOSE           = $0010; { close the window } WM_KEYDOWN         = $0100; { a key is pressed } WM_LBUTTONDOWN     = $0201; { the left mouse button is pressed }
image from book

The wParam and lParam fields are closely related to the message passed in the message field. Both values are used to further define the message. The time and pt values are rarely used. The time field specifies the time when the event occurred, and the pt field contains the X and Y coordinates of the mouse cursor at the time the event occurred.

The VCL encapsulates the majority of standard messages into events like OnClick and OnMouseDown, but it also gives you the ability to handle other messages. To handle a message that isn't encapsulated in a VCL event, you have to write a message method.

To create a message method, you have to create a method that accepts a single var TMessage (or a similar message-related record) parameter and you have to mark the method with the message directive followed by the constant that identifies the handled message. In VCL Forms applications, the TMessage record, rather than the TMsg record, is used when working with messages.

Handling Messages

Now let's try to handle the WM_MOVE message that is sent to the window after it has been moved. The message method that handles the WM_MOVE message should look like this:

procedure MyMoveHandler(var Message: TMessage); message WM_MOVE;

Listing 20-13 contains the entire message method that simply displays the top and left coordinates of the main form when the form is moved (see Figure 20-5).

image from book
Figure 20-5: Handling the WM_MOVE message

Listing 20-13: The WM_MOVE message method

image from book
type   TForm1 = class(TForm)     Label1: TLabel;   private     { Private declarations }   public     { Public declarations }     procedure WMMove(var Message: TMessage); message WM_MOVE;   end; var   Form1: TForm1; implementation {$R *.dfm} procedure TForm1.WMMove(var Message: TMessage); begin   Caption := Format('Top: %d - Left: %d', [Top, Left]); end;
image from book

The difference between VCL events and direct message handling is that we have to pass the message to the ancestor's message handler and, for some messages, notify the operating system that we've processed the message. The documentation for every message specifies what to return when we handle it.

procedure TForm1.WMMove(var Message: TMessage); begin   Caption := Format('Top: %d - Left: %d', [Top, Left]);   inherited; { pass message to the ancestor's message handler } end;

As said earlier, the lParam and wParam fields provide us with more information about the message. In the WM_MOVE message, lParam contains both X and Y coordinates of the window. The low-order word of the lParam value specifies the X coordinate and the high-order word specifies the Y coordinate.

Listing 20-14: Reading coordinates from the lParam value

image from book
procedure TForm1.WMMove(var Message: TMessage); begin   Caption := Format('X: %d - Y: %d', [Message.LParamLo, Message.LParamHi]);   inherited; end;
image from book

An even better way to handle a message is to use a record more related to the message than the generic TMessage record. The Messages unit contains a large collection of such records. Here's the record related to the WM_MOVE message:

TWMMove = packed record   Msg: Cardinal;   Unused: Integer;   case Integer of     0: (       XPos: Smallint;       YPos: Smallint);     1: (       Pos: TSmallPoint;       Result: Longint); end; 

You should always use these message-related records, because if nothing else, they result in cleaner code, as shown in Listing 20-15.

Listing 20-15: Using a message-related record

image from book
type   TForm1 = class(TForm)   private   public     procedure WMMove(var Message: TWMMove); message WM_MOVE;   end; var   Form1: TForm1; implementation {$R *.dfm} procedure TForm1.WMMove(var Message: TWMMove); begin   Caption := Format('X: %d - Y: %d', [Message.XPos, Message.YPos]);   inherited; end;
image from book

Now that we know how to use messages, we can do something a bit more fun. For instance, we can create elastic windows that behave like the Main window and Playlist windows in Winamp and other media players. First, add another form to the project and then attach it to the main form in the WM_MOVE message handler, as shown in Listing 20-16.

Listing 20-16: Automatically moving another form in the WM_MOVE message handler

image from book
type   TForm1 = class(TForm)     procedure FormShow(Sender: TObject);   private   public     procedure WMMove(var Message: TWMMove); message WM_MOVE;   end; var   Form1: TForm1; implementation uses Unit2; {$R *.dfm} procedure TForm1.WMMove(var Message: TWMMove); begin   { we have to test if Form2 exists because the WM_MOVE message     for the main form gets generated before the Form2 is created }   if Assigned(Form2) then   begin     Form2.Top := Top + Height;     Form2.Left := Left;   end;   inherited; end; procedure TForm1.FormShow(Sender: TObject); begin   Form2.Show; end;
image from book

There is a section in Chapter 12 that describes how you can move a form that has no title bar. The implementation involved three different event handlers and a Boolean variable that was used to determine when the form should be moved. Although the code itself is not complicated, there is actually too much of it. A better way to implement this functionality is to catch and respond to the WM_NCHITTEST message.

The WM_NCHITTEST is sent to a window when a mouse button is pressed or released or when the mouse cursor is moved. The return value of this message indicates the position of the mouse cursor — whether the mouse is over the client area, the title bar, the menu, or any other window portion. The message result also determines what the operating system does with the window. So, the only thing that we have to do is trick Windows into believing that the mouse is always over the title bar by returning the HTCAPTION constant in the message result.

Listing 20-17: Enabling the user to move the form by clicking on its client area

image from book
type   TForm1 = class(TForm)   private   public     procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHITTEST;   end; var   Form1: TForm1; implementation uses Unit2; {$R *.dfm} procedure TForm1.WMNCHitTest(var Message: TWMNCHitTest); begin   inherited;   if Message.Result = HTCLIENT then     Message.Result := HTCAPTION; end;
image from book

The result of this code is displayed in Figure 20-6.

image from book
Figure 20-6: Moving both forms by clicking in the client area of the main form

Handling Messages in C++

The C++ language doesn't have a message directive that can be used to identify a method as a message handler. To handle a message in C++, you have to create a message method, which will be called in response to a message, and a message map in which you list your message handling methods.

For instance, to handle the WM_MOVE message in a C++Builder VCL Forms application, first create the message method:

class TMainForm : public TForm { __published:    // IDE-managed components private:        // User declarations public:         // User declarations    void __fastcall WMMove(TWMMove &Message); }; void __fastcall TMainForm::WMMove(TWMMove &Message) {    Caption = Format("X: %d - Y: %d",       ARRAYOFCONST((Message.XPos, Message.YPos))); }

When you're done with the method, you need to create the message map and add your method to the message map to notify the compiler that your method is a message handler.

To create a message map, you use two macros: BEGIN_MESSAGE_MAP and END_MESSAGE_MAP(BaseClass). The BaseClass is responsible for handling all other messages that aren't handled inside the message map.

To define your method as a message handler, you have to use the VCL_MESSAGE_HANDLER macro, which requires you to pass the message you are handling, the record (structure) that holds the message data, and the name of the message handler.

Here's how a message map looks:

BEGIN_MESSAGE_MAP    VCL_MESSAGE_HANDLER(Message, Structure, Method) END_MESSAGE_MAP(BaseClass)

So, to qualify the WMMove method as a message handler for the WM_MOVE message, you have to create the following message map (in the public section of the class):

class TMainForm : public TForm { __published:    // IDE-managed components private:        // User declarations public:         // User declarations    __fastcall TMainForm(TComponent* Owner);    void __fastcall WMMove(TWMMove &Message);    BEGIN_MESSAGE_MAP       VCL_MESSAGE_HANDLER(WM_MOVE, TWMMove, WMMove);    END_MESSAGE_MAP(TForm) };

If you have to pass the message to the base class, which is necessary in some cases, you cannot use the inherited reserved word. You have to call the Dispatch method of the base class:

BaseClass::Dispatch(&Message);

For instance, in order to work properly, the WM_NCHITTEST message handler needs to pass the message to the base class:

class TMainForm : public TForm { __published:    // IDE-managed components private:        // User declarations public:         // User declarations    __fastcall TMainForm(TComponent* Owner);    void __fastcall WMMove(TWMMove &Message);    void __fastcall WMNCHitTest(TWMNCHitTest &Message);    BEGIN_MESSAGE_MAP       VCL_MESSAGE_HANDLER(WM_MOVE, TWMMove, WMMove);       VCL_MESSAGE_HANDLER(WM_NCHITTEST,         TWMNCHitTest, WMNCHitTest);    END_MESSAGE_MAP(TForm) }; void __fastcall TMainForm::WMNCHitTest(TWMNCHitTest &Message) {    // first call the base class handler    TForm::Dispatch(&Message);    if(Message.Result == HTCLIENT)       Message.Result = HTCAPTION; }

Creating Custom Messages

To create a custom message for use in an application, you only have to define an integer constant in the range of WM_USER to WM_USER + $7FFF (see Listing 20-18).

Listing 20-18: A custom message

image from book
uses   Windows, Messages, SysUtils, Variants, Classes,   Graphics, Controls, Forms, Dialogs; const   MM_CHANGECOLOR = WM_USER + 1; type   TForm1 = class(TForm)   private     { Private declarations }   public     { Public declarations }   end;
image from book

After you've created a custom message, write a message method that will handle the message. Since this is a custom message, you can do whatever you want with it. You can, for instance, treat the wParam message field as a color value and use the MM_CHANGECOLOR message to change the color of the form, as shown in Listing 20-19.

Listing 20-19: Handling the custom message

image from book
type   TForm1 = class(TForm)   private   public     procedure MMChangeColor(var Message: TMessage);       message MM_CHANGECOLOR;   end; procedure TForm1.MMChangeColor(var Message: TMessage); begin   Color := Message.wParam; end;
image from book

The last thing to do is send the message to the form. In Delphi, there are three possible ways of sending a message. Besides the VCL Perform method, you can also use the SendMessage and PostMessage Windows API functions.

The SendMessage function is used when the message needs to be handled as soon as possible. When you use the SendMessage function, it doesn't return until the message is processed.

The PostMessage function is used to send the message when its processing isn't time-critical. A message sent using the PostMessage function is placed in the window's message queue and is handled later.

The most straightforward way to send a message in a VCL Forms application is to call the Perform method. You should use the Perform method when you know exactly which control should process the message.

Here's how you can send the MM_CHANGECOLOR message to the main form and instruct it to change its color to white (see Figure 20-7):

image from book
Figure 20-7: Using the MM_CHANGECOLOR custom message

procedure TForm1.Button1Click(Sender: TObject); begin   Perform(MM_CHANGECOLOR, clWhite, 0); end;

If you want to use the SendMessage and PostMessage API functions to send messages, you'll have to pass the handle of the destination window as the first parameter. The window handle of the main form is specified in its Handle property.

SendMessage(Handle, MM_CHANGECOLOR, clWhite, 0);

The messaging system is very flexible and allows us to do whatever we want with our custom messages. For instance, we can even use messages to add text from a text box to a list box. This can be done by typecasting the components to integers and then sending them as the wParam and lParam fields in the message.

Listing 20-20: Playing with messages

image from book
const   MM_ADDTOLIST = WM_USER + 2; type   TForm1 = class(TForm)   public     procedure MMAddToList(var Message: TMessage);       message MM_ADDTOLIST;   end; var   Form1: TForm1; implementation {$R *.dfm} procedure TForm1.MMAddToList(var Message: TMessage); var   edit: TEdit; begin   edit := TEdit(Message.LParam);   TListBox(Message.WParam).Items.Add(edit.Text); end; procedure TForm1.Button1Click(Sender: TObject); begin   SendMessage(Handle, MM_ADDTOLIST,     Integer(ListBox1), Integer(Edit1)); end;
image from book



Inside Delphi 2006
Inside Delphi 2006 (Wordware Delphi Developers Library)
ISBN: 1598220039
EAN: 2147483647
Year: 2004
Pages: 212
Authors: Ivan Hladni

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net