Over the years, I don’t think I can count the number of times that I’ve wanted to send window messages to some other application. It can be useful for automating the target application, or even as a primitive form of Inter-process communication. Each time, I have to decide how I’m going to get the target window handle in order to send messages to it, and on many occasions the best approach is to provide the application user with a reticle for locating the target window.

If you’ve ever used Microsoft’s Spy++ tool (which comes with professional editions of visual studio), you’ll know precisely what I mean when I describe this reticle. Spy++ has a “Find Window” dialog which contains this icon:

When you click on the icon and hold down your mouse pointer, the little targeting reticle becomes detached from the icon and tracks your mouse. Move the reticle over some other window, and Spy++ reads out the window handle, class name and caption, along with a small bunch of other information.

As I want this functionality in my own application, I decided to build a custom control to reproduce this behavior.

If you just want to get to the solution, go ahead and skip down to the “completed source” heading and grab the source-code. Otherwise, stick around and I’ll walk you through how to build this component…

Reticle usage example

Completed Source

You can download the completed source code for this project here:


Or, if you’d rather simply copy and paste it into a package of your own, here it is in long form..

unit comp.reticle;

interface
uses
  Winapi.Windows,
  System.SysUtils, System.Classes, Vcl.Controls;

type
  TWindowReticle = class(TCustomControl)
  private
    fDragging: boolean;
    fWindowHandle: HWnd;
    fWindowCaption: string;
    fWindowClass: string;
    fAncestorHandle: Hwnd;
    fAncestorClass: string;
    fAncestorCaption: string;
    fOnWindowChange: TNotifyEvent;
  private
    procedure ClearWindowProperties;
    function GetWndFromClientPoint(Pt: TPoint): HWND;
  protected
    function CanResize(var NewWidth, NewHeight: Integer): Boolean; override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
  public
    procedure Paint; override;
    constructor Create( aOwner: TComponent ); override;
  published
    property OnWindowChange: TNotifyEvent read fOnWindowChange write fOnWindowChange;
    property WindowHandle: Hwnd read fWindowHandle;
    property WindowClass: string read fWindowClass;
    property WindowCaption: string read fWindowCaption;
    property AncestorClass: string read fAncestorClass;
    property AncestorCaption: string read fAncestorCaption;
    property AncestorHandle: Hwnd read fAncestorHandle;
  end;

procedure Register;

implementation
uses
  vcl.forms,
  vcl.graphics;

const
  cFixedSize = 32;
  cHalfSize = 16;
  cQuaterSize = 8;

procedure Register;
begin
  RegisterComponents('Samples', [TWindowReticle]);
end;

{ TWindowReticle }

function TWindowReticle.CanResize(var NewWidth, NewHeight: Integer): Boolean;
begin
  Result := False;
end;

constructor TWindowReticle.Create(aOwner: TComponent);
begin
  inherited Create(aOwner);
  Width := cFixedSize;
  Height := cFixedSize;
  fOnWindowChange := nil;
  fDragging := False;
  ClearWindowProperties;
end;

procedure TWindowReticle.ClearWindowProperties;
begin
  fWindowHandle := 0;
  fWindowCaption := '';
  fWindowClass := '';
  fAncestorClass := '';
  fAncestorHandle := 0;
  fAncestorCaption := '';
end;

procedure TWindowReticle.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if (Button = mbLeft) then begin
    ClearWindowProperties;
    fDragging := True;
    SetCapture(Handle);
    Screen.Cursor := crCross;
    Self.Repaint;
  end;
end;

function TWindowReticle.GetWndFromClientPoint(Pt: TPoint): HWND;
begin
  MapWindowPoints(Handle, GetDesktopWindow, Pt, 1);
  Result := WindowFromPoint(Pt);
end;

procedure TWindowReticle.MouseMove(Shift: TShiftState; X, Y: Integer);
var
  wnd: HWnd;
  TextBuffer: array [0..255] of Char;
begin
  if not fDragging then begin
    exit;
  end;
  wnd := GetWndFromClientPoint(Point(X,Y));
  if wnd=fWindowHandle then begin
    exit;
  end;
  ClearWindowProperties;
  fWindowHandle := wnd;
  //- Get the window class name
  FillChar(TextBuffer,length(TextBuffer),0);
  GetClassName(fWindowHandle, TextBuffer, pred(length(TextBuffer)));
  fWindowClass := TextBuffer;
  //- Get window caption
  FillChar(TextBuffer,length(TextBuffer),0);
  GetWindowText(fWindowHandle,TextBuffer, pred(length(TextBuffer)));
  fWindowCaption := TextBuffer;
  if (GetWindowLong(fWindowHandle, GWL_STYLE) and WS_CHILD) = WS_CHILD then begin
      //- Get Ancestor window handle
      fAncestorHandle := GetAncestor(fWindowHandle, GA_ROOT);
      //- Get ancestor class name
      FillChar(TextBuffer,length(TextBuffer),0);
      GetClassName(fAncestorHandle, TextBuffer, pred(length(TextBuffer)));
      fAncestorClass := TextBuffer;
      //- Get ancestor caption
      FillChar(TextBuffer,length(TextBuffer),0);
      GetWindowText(fAncestorHandle,TextBuffer,pred(length(TextBuffer)));
      fAncestorCaption := TextBuffer;
  end;
  //- Call UI event handler
  if assigned(fOnWindowChange) then begin
    fOnWindowChange(Self);
  end;
end;

procedure TWindowReticle.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if not fDragging then begin
    exit;
  end;
  fDragging := False;
  ReleaseCapture;
  Screen.Cursor := crDefault;
  Self.Repaint;
end;

procedure TWindowReticle.Paint;
begin
  // Set brush and pen properties.
  Canvas.Brush.Style := bsClear;
  Canvas.Pen.Style := psSolid;
  Canvas.Pen.Color := clRed;
  Canvas.Pen.Width := 2;
  // Draw circle
  Canvas.Ellipse( cQuaterSize, cQuaterSize, Width - cQuaterSize, Height - cQuaterSize );
  if not fDragging then begin
    Canvas.Pen.Color := clWhite;
    // Draw vertical line
    Canvas.MoveTo( cHalfSize, 0 );
    Canvas.LineTo( cHalfSize, Height );
    // Draw Horizontal line
    Canvas.MoveTo( 0, cHalfSize );
    Canvas.LineTo( Width, cHalfSize );
  end;
end;

end.

I hope you find this code useful!
Until next time, thanks for reading.