2008. március 29., szombat

Intercepting Windows messages in non-visual components


Problem/Question/Abstract:

Sometimes we need a non-windowed component (i.e. one that isn't derived from TWinControl) to receive Windows messages - but non-windowed component don't have window handles. For example suppose we are developing a non-visual component that registers our application as a clipboard viewer so the application can respond to changes in the clipboard. To get information about clipboard changes our component needs to receive messages from Windows.

Answer:

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.

The hidden window needs a window procedure. We can use a method of our component class to provide the window procedure. AllocateHWnd takes a reference to the method its parameter - it takes care of the problem of registering the method as a window procedure for us. In the method we handle the messages we are interested in and hand the rest off to Windows using the DefWindowProc API call.

The following code gives the skeleton of how to use AllocateHWnd. First, here's the class declaration from the interface section of code:

type
  // Our class derived from TComponent
  // (or another ancestor class)
  TMyClass = class(TComponent)
  private
    FHWnd: HWND;
    // field to store the window handle
  {...}
  protected
    procedure WndMethod(var Msg: TMessage); virtual;
    // the window proc - called by Windows to handle
    // the given message
  {...}
  public
    constructor Create(AOwner: TComponent); override;
    // create window proc here
    destructor Destroy; override;
    // free window proc here
  {...}
  end;

And here's the implementation details:

TMyClass.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  {... }
  // Create the window
  FHWnd := AllocateHWnd(WndMethod);
  { ...}
end;

TMyClass.Destroy;
begin
  {...}
  // Destroy the window
  DeallocateHWnd(FHWnd);
  {...}
  inherited Destroy;
end;

TMyClass.WndMethod(var Msg: TMessage);
var
  Handled: Boolean;
begin
  // Assume we handle message
  Handled := True;
  case Msg.Msg of
    WM_SOMETHING: DoSomething;
    // Code to handle a message
    WM_SOMETHINGELSE: DoSomethingElse;
    // Code to handle another message
  {...}
  else
    // We didn't handle message
    Handled := False;
  end;
  if Handled then
    // We handled message - record in message result
    Msg.Result := 0
  else
    // We didn't handle message
    // pass to DefWindowProc and record result
    Msg.Result := DefWindowProc(FHWnd, Msg.Msg,
      Msg.WParam, Msg.LParam);
end;

Of course, we could just use the Windows API to create a window the hard way and provide a windows procedure. But it is more difficult to use a method (rather than a simple procedure) as a window procedure if we do it this way. The clever features about AllocateHWnd are 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 -- and a method is more useful since it has access to the class's private data.


Component Download: http://www.delphidabbler.com/download.php?file=pjcbview.zip

Nincsenek megjegyzések:

Megjegyzés küldése