One of the keys to traditional Windows programming is handling the messages sent by Windows to applications. Delphi handles most of the common ones for you. It is possible, however, that you will need to handle messages that Delphi does not already handle or that you will create your own messages.
There are three aspects to working with messages:
All Delphi classes have a built-in mechanism for handling messages, called message-handling methods or message handlers. The basic idea of message handlers is that the class receives messages of some sort and dispatches them, calling one of a set of specified methods depending on the message received. If no specific method exists for a particular message, there is a default handler.
The following diagram shows the message-dispatch system:
The Visual Component Library defines a message-dispatching system that translates all Windows messages (including user-defined messages) directed to a particular class into method calls. You should never need to alter this message-dispatch mechanism. All you will need to do is create message-handling methods. See the section "Declaring a new message-handling method" for more on this subject.
A Windows message is a data record that contains several fields. The most important of these is an integer-size value that identifies the message. Windows defines many messages, and the Messages unit declares identifiers for all of them. Other useful information in a message comes in two parameter fields and a result field.
One parameter contains 16 bits, the other 32 bits. You often see Windows code that refers to those values as wParam and lParam, for "word parameter" and "long parameter." Often, each parameter will contain more than one piece of information, and you see references to names such as lParamHi, which refers to the high-order word in the long parameter.
Originally, Windows programmers had to remember or look up in the Windows API what each parameter contained. More recently, Microsoft has named the parameters. This so-called "message cracking" makes it much simpler to understand what information accompanies each message. For example, the parameters to the WM_KEYDOWN message are now called nVirtKey and lKeyData, which gives much more specific information than wParam and lParam.
For each type of message, Delphi defines a record type that gives a mnemonic name to each parameter. For example, mouse messages pass the x- and y-coordinates of the mouse event in the long parameter, one in the high-order word, and the other in the low-order word. Using the mouse-message structure, you do not have to worry about which word is which, because you refer to the parameters by the names XPos and YPos instead of lParamLo and lParamHi.
When an application creates a window, it registers a window procedure with the Windows kernel. The window procedure is the routine that handles messages for the window. Traditionally, the window procedure contains a huge case statement with entries for each message the window has to handle. Keep in mind that "window" in this sense means just about anything on the screen: each window, each control, and so on. Every time you create a new type of window, you have to create a complete window procedure.
Delphi simplifies message dispatching in several ways:
The greatest benefit of this message dispatch system is that you can safely send any message to any component at any time. If the component does not have a handler defined for the message, the default handling takes care of it, usually by ignoring the message.
Delphi registers a method called MainWndProc as the window procedure for each type of component in an application. MainWndProc contains an exception-handling block, passing the message structure from Windows to a virtual method called WndProc and handling any exceptions by calling the application class's HandleException method.
MainWndProc is a nonvirtual method that contains no special handling for any particular messages. Customizations take place in WndProc, since each component type can override the method to suit its particular needs.
WndProc methods check for any special conditions that affect their processing so they can "trap" unwanted messages. For example, while being dragged, components ignore keyboard events, so the WndProc method of TWinControl passes along keyboard events only if the component is not being dragged. Ultimately, WndProc calls Dispatch, a nonvirtual method inherited from TObject, which determines which method to call to handle the message.
Dispatch uses the Msg field of the message structure to determine how to dispatch a particular message. If the component defines a handler for that particular message, Dispatch calls the method. If the component does not define a handler for that message, Dispatch calls DefaultHandler.
Before changing the message handling of your components, make sure that is what you really want to do. Delphi translates most Windows messages into events that both the component writer and the component user can handle. Rather than changing the message-handling behavior, you should probably change the event-handling behavior.
To change message handling, you override the message-handling method. You can also prevent a component from handling a message under certain circumstances by trapping the message.
To change the way a component handles a particular message, you override the message-handling method for that message. If the component does not already handle the particular message, you need to declare a new message-handling method.
To override a message-handling method, you declare a new method in your component with the same message index as the method it overrides. Do not use the override directive; you must use the message directive and a matching message index.
Note that the name of the method and the type of the single var parameter do not have to match the overridden method. Only the message index is significant. For clarity, however, it is best to follow the convention of naming message-handling methods after the messages they handle.
For example, to override a component's handling of the WM_PAINT message, you redeclare the WMPaint method:
type TMyComponent = class(...) ... procedure WMPaint(var Message: TWMPaint); message WM_PAINT; end;
Once inside a message-handling method, your component has access to all the parameters of the message structure. Because the parameter passed to the message handler is a var parameter, the handler can change the values of the parameters if necessary. The only parameter that changes frequently is the Result field for the message: the value returned by the SendMessage call that sends the message.
Because the type of the Message parameter in the message-handling method varies with the message being handled, you should refer to the documentation on Windows messages for the names and meanings of individual parameters. If for some reason you need to refer to the message parameters by their old-style names (WParam, LParam, and so on), you can typecast Message to the generic type TMessage, which uses those parameter names.
Under some circumstances, you might want your components to ignore messages. That is, you want to keep the component from dispatching the message to its handler. To trap a message, you override the virtual method WndProc.
The WndProc method screens messages before passing them to the Dispatch method, which in turn determines which method gets to handle the message. By overriding WndProc, your component gets a chance to filter out messages before dispatching them. An override of WndProc for a control derived from TWinControl looks like this:
procedure TMyControl.WndProc(var Message: TMessage);
begin
{ tests to determine whether to continue processing }
inherited WndProc(Message);
end;
The TControl component defines entire ranges of mouse messages that it filters when a user is dragging and dropping controls. Overriding WndProc helps this in two ways:
Here is part of the WndProc method for TControl, for example:
procedure TControl.WndProc(var Message: TMessage);
begin
...
if (Message.Msg >= WM_MOUSEFIRST) and (Message.Msg <= WM_MOUSELAST) then
if Dragging then { handle dragging specially }
DragMouseMsg(TWMMouse(Message))
else
... { handle others normally }
end;
... { otherwise process normally }
end;
Because Delphi provides handlers for most common Windows messages, the time you will most likely need to create new message handlers is when you define your own messages. Working with user-defined messages has two aspects:
A number of the standard components define messages for internal use. The most common reasons for defining messages are broadcasting information not covered by standard Windows messages and notification of state changes.
Defining a message is a two-step process. The steps are
A message identifier is an integer-sized constant. Windows reserves the messages below 1,024 for its own use, so when you declare your own messages you should start above that level.
The constant WM_APP represents the starting number for user-defined messages. When defining message identifiers, you should base them on WM_APP.
Be aware that some standard Windows controls use messages in the user-defined range. These include list boxes, combo boxes, edit boxes, and command buttons. If you derive a component from one of these and want to define a new message for it, be sure to check the Messages unit to see which messages Windows already defines for that control.
The following code shows two user-defined messages.
const WM_MYFIRSTMESSAGE = WM_APP + 400; WM_MYSECONDMESSAGE = WM_APP + 401;
If you want to give useful names to the parameters of your message, you need to declare a message-record type for that message. The message-record is the type of the parameter passed to the message-handling method. If you do not use the message's parameters, or if you want to use the old-style parameter notation (wParam, lParam, and so on), you can use the default message-record, TMessage.
To declare a message-record type, follow these conventions:
Define the next four bytes to correspond to the Longint parameter.
For example, here is the message record for all mouse messages, TWMMouse, which uses a variant record to define two sets of names for the same parameters.
type
TWMMouse = record
Msg: TMsgParam; ( first is the message ID )
Keys: Word; ( this is the wParam )
case Integer of ( two ways to look at the lParam )
0: {
XPos: Integer; ( either as x- and y-coordinates...)
YPos: Integer);
1: {
Pos: TPoint; ( ... or as a single point )
Result: Longint); ( and finally, the result field )
end;
There are two sets of circumstances that require you to declare new message-handling methods:
To declare a message-handling method, do the following:
Here is the declaration, for example, of a message handler for a user-defined message called CM_CHANGECOLOR.
const CM_CHANGECOLOR = WM_APP + 400; type TMyComponent = class(TControl) ... protected procedure CMChangeColor(var Message: TMessage); message CM_CHANGECOLOR; end; procedure TMyComponent.CMChangeColor(var Message: TMessage); begin Color := Message.lParam; inherited; end;
pubsweb@inprise.com
Copyright © 1999, Inprise Corporation. All rights reserved.