There are several situations where you need to create an unvisible window
for communicating or notifying purposes. If you e.g. want to handle display
mode changes in it's own unit, you need to create a window that then can
handle the WM_DEVICECHANGE messages. Or if you're writing a multi threaded
application, the threads need to communicate, which is often solved by using
unvisible windows.
The usual solution is to use the undocumented function "AllocateHWnd",
which is implemented in "Forms.pas" and used in several VCL components. But
this function has some disadvantages: You have to enter a method, so you
can't use a normal procedure as the window procedure. Another (even more
important) disadvantage is that you have to allocate one window for each
purpose. In winNt that's no big problem (apart from that window enumerations
get long). But in win9x window handles are limited to 16k. So you're
consuming precious system resources.
The "MsgHandler" stuff described here is similar to "AllocateHWnd" regarding
the fact that it is also based on unvisible windows internally, but there are
a lot of differences, especially in the usage area. E.g. when using
"AllocateHWnd" you need to enter a full self written method, which handles
all messages. What this method does exactly is up to you. That means you have
the full power, but that also means that you have to do a lot, even if you
just need one message to be handled.
In contrast to that, all you need to do when using the "MsgHandler" stuff is
adding and deleting message handlers as you like. A message handler is a
procedure or a method (both is possible) which gets called when our message
handler window receives a specified message. Here are the message handler
definitions:
 |
type
TMsgHandler = procedure (window, msg: cardinal; wParam, lParam: integer; var result: integer);
TMsgHandlerOO = procedure (window, msg: cardinal; wParam, lParam: integer; var result: integer) of object;
|
|
When adding a message handler, you can choose whether you want to use a
specific message value, or whether you just want to use a free one. If you
want to have the power, you can force e.g. the message "WM_USER + 777" to be
used. If you don't need it that strict, you can simply enter a "0" as the
desired message. In this case a free message is used and returned.
Deleting message handlers is as easy. Simply enter the message handler
you want to delete together with the message you chose (or that was
returned). If you don't enter which message was used, then all message
handlers, that were registered with the specified "handler" procedure/method,
are deleted.
 |
function AddMsgHandler (handler: TMsgHandler; msg: cardinal = 0; threadID: cardinal = 0) : cardinal; overload;
function AddMsgHandler (handler: TMsgHandlerOO; msg: cardinal = 0; threadID: cardinal = 0) : cardinal; overload;
function DelMsgHandler (handler: TMsgHandler; msg: cardinal = 0; threadID: cardinal = 0) : boolean; overload;
function DelMsgHandler (handler: TMsgHandlerOO; msg: cardinal = 0; threadID: cardinal = 0) : boolean; overload;
|
|
Perhaps you're wondering what happended with the message handler window(s).
I've mentioned them, but I've not described them yet. The reason is that you
normally don't need to care about them. In the moment when you add the first
message handler, a message handler window for the current thread is created.
There is always only one message handler window per thread. That saves
resources and that furthermore makes it possible to ask for the one message
handler window of another thread. When you delete all message handlers in one
thread, the message handler window of that thread is freed automatically.
The function "MsgHandlerWindow" returns the message handler window of the
specified thread (you can enter a "0" for the current thread). If no such
window exists yet and if you're asking for the current thread's window, the
message handler window is created. If you're asking for another thread's
message handler window which does not exist yet, the result is "0".
 |
function MsgHandlerWindow (threadID: cardinal = 0) : cardinal;
|
|
Now let me write a little demo program, which does nothing but wait for the
next display mode change and then stops. First I'll write this program with
using "AllocateHWnd":
 |
program WaitUntilDisplayModeChanged;
uses Forms, Windows, Messages;
procedure MessageLoop; [...]
var window : dword;
type
TDummyClass = class
procedure WndMethod (var Message: TMessage);
end;
procedure TDummyClass.WndMethod(var Message: TMessage);
begin
if Message.Msg = WM_DISPLAYCHANGE then
ExitProcess(0);
Message.result := DefWindowProc(window, Message.Msg, Message.wParam, Message.lParam);
end;
begin
window := AllocateHWnd(TDummyClass.Create.WndMethod);
MessageLoop;
end.
|
|
And here comes the version of the same program with the "MsgHandler" stuff.
It's much nicer and shorter - and it doesn't need "Forms.pas", which results
in a *much* smaller executable:
 |
program WaitUntilDisplayModeChanged;
uses Windows, Messages, madTools;
procedure MessageLoop; [...]
procedure DisplayModeChanged(window, msg: cardinal; lParam, wParam: integer; var result: integer);
begin
ExitProcess(0);
end;
begin
AddMsgHandler(DisplayModeChanged, WM_DISPLAYCHANGE);
MessageLoop;
end.
|
|