How a non-windowed component can receive messages from Windows
Why do it?
Sometimes we need a non-windowed component (i.e. one that isn't derived from TWinControl) to receive Windows messages. To receive messages the component needs a window handle, but a non-windowed component hasn't got one!
This article is about how to enable such a component to use a hidden window to receive messages.
How it's done
The Delphi library function AllocateHWnd is used to create a hidden window for us and the related DeallocateHWnd disposes of the window when we've finished with it.
A hidden window requires a window procedure. AllocateHWnd enables us to use a method as a window procedure where Windows normally requires a stdcall function. We pass a reference to the required method to AllocateHWnd and it takes care of the problem of registering the method as a window procedure for us. Inside the registered method we handle the messages we are interested in and hand the rest off to Windows using the DefWindowProc API call.
Listing 2 below provides a skeleton of how to use AllocateHWnd. First though, Listing 1 shows an outline definition for our component class:
1type
2
3
4 TMyClass = class(TComponent)
5 private
6 fHWnd: HWND;
7
8 ...
9 protected
10 procedure WndMethod(var Msg: TMessage); virtual;
11
12
13 ...
14 public
15 constructor Create(AOwner: TComponent); override;
16
17 destructor Destroy; override;
18
19 ...
20 end;
Listing 1
And here are the implementation details:
1constructor TMyClass.Create(AOwner: TComponent);
2begin
3 inherited Create(AOwner);
4 ...
5
6 fHWnd := AllocateHWnd(WndMethod);
7 ...
8end;
9
10destructor TMyClass.Destroy;
11begin
12 ...
13
14 DeallocateHWnd(fHWnd);
15 ...
16 inherited Destroy;
17end;
18
19procedure TMyClass.WndMethod(var Msg : TMessage);
20var
21 Handled: Boolean;
22begin
23
24 Handled := True;
25 case Msg.Msg of
26 WM_SOMETHING: DoSomething;
27
28 WM_SOMETHINGELSE: DoSomethingElse;
29
30
31 else
32
33 Handled := False;
34 end;
35 if Handled then
36
37 Msg.Result := 0
38 else
39
40
41 Msg.Result := DefWindowProc(fHWnd, Msg.Msg,
42 Msg.WParam, Msg.LParam);
43end;
Listing 2
Of course, we could just use the Windows API to create a window the hard way and provide a windows procedure. But it is much more difficult to use a method as a window procedure if we do it this way. The cleverness of AllocateHWnd is that (a) it creates the hidden window for us and (b) it allows us to use a method, rather than a simple procedure, as the window procedure. Obviously, a method is more useful since it has access to the class' private data.
A Real World Example
While there is no demo code to accompany this article, my Clipboard Viewer Component is a non-visual component that uses the hidden window techniques described here. The window receives Windows messages that provide information about changes to the clipboard.
Feel free to check it out.
Feedback
I hope you found this article useful.
If you have any observations, comments, or have found any errors, then you can report them using this website's Issues page. Make sure you mention that the issue relates to "Article #1".