How do I get another application's window handle passed to Delphi via a mouse click

20,626

Solution 1

if you know what text is in the title of the window, this code will do the trick for you:

var
  WindowList: TList;

function GetHandle (windowtitle: string): HWND;
var
  h, TopWindow: HWND;
  Dest: array[0..80] of char;
  i: integer;
  s: string;

  function getWindows(Handle: HWND; Info: Pointer): BOOL; stdcall;
    begin
      Result:= True;
      WindowList.Add(Pointer(Handle));
    end;

begin
  result:= 0;

  try
    WindowList:= TList.Create;
    TopWindow:= Application.Handle;
    EnumWindows(@getWindows, Longint(@TopWindow));
    i:= 0;
    while (i < WindowList.Count) and (result = 0) do
      begin
        GetWindowText(HWND(WindowList[i]), Dest, sizeof(Dest) - 1);
        s:= dest;
        if length(s) > 0 then
          begin
            if (Pos(UpperCase(Windowtitle), UpperCase(s)) >= 1) then
              begin
                h:= HWND(WindowList[i]);
                if IsWindow(h) then
                  result:= h
             end
           end;
        inc(i)
      end
    finally
      WindowList.Free;
    end;
end;

Usage in your example (notepad puts the name of the opened file in the window caption):

h:= getHandle('text.txt');
if (h = 0)
  // Oops not found
else 
  begin
    // you got the handle!
  end;

I used this code to check if my application was already up and running. But it can be used on any launched application.

Solution 2

The approach that user STATUS_ACCESS_DENIED outlined in the comment is likely the simplest way to go here. I'd recommend using mouse capture over hooking, as it's somewhat simpler to implement.

Here's a slightly more detailed outline of what's involved:

The first thing to change the way that the selection process works. Instead of having the user click a button on your app to start the process, and then click the target window, and finally click again to confirm; it's a lot easier to implement if you have the user click a specific area on your app, then drag to the target window, and then let go of the mouse button while over the target. This is because windows considers a click on another app to belong to that app, and you have to do extra work to intercept it. But there's a simple way - called mouse capture - to get information about a drag/release if it starts off as a click on your own app.

This is also the approach that the Windows SDK Spy++ tool uses; so by doing it this way, you're also being consistent with a well-known tool. (Pic of Spy++ here - note the crosshair Finder Tool in the dialog - that's what you click and drag to the target. Would highly recommend downloading the Windows SDK and playing with this tool if you haven't done so before; it's also a very useful way of seeing how other applications are constructed so great as a Windows API learning tool.)

Steps involved:

  • Have some control in your app that response to mouse-down events (WM_LBUTTONDOWN in Win32/C, OnMouseDown in delphi). You might want to draw a crosshairs icon or similar here so the user knows where to click.
  • When you get a mouse down, use SetCapture to 'capture' the mouse. This means that the control will receive all the mouse messages while the mouse is moving - until the user releases the button - even if it moves outside the control.
  • Set the icon to look like a crosshairs so that the user knows they are in dragging mode
  • As the user moves the mouse, you'll get WM_MOUSEMOVE message (OnMouseMove in Delphi) that has the pointer coordinates. You'll need to use ClientToScreen to convert these to screen coordinates, then WindowFromPoint to find the window at that point. (Note that this finds the innermost window at that point, you could use ChildWindowFromPoint starting from the desktop window to just get the top-level window if you want that.) It's up to you to decide whether you want to update your UI at every mouse move throughout the drag, or just when the user releases the mouse button.
  • When the user releases the mouse button, you'll get a WM_LBUTTONUP/OnMouseUp; at that stage, wrap things up by calling ReleaseCapture and putting the cursor back to normal shape.

Note that you'll get mouse move events both during the drag, and also if the user just happens to move the mouse pointer across your control, perhaps on the way to some other control. The simplest way to tell these two cases apart is to use a flag in your control that you set when you get the mouse down, and clear when you get the mouse up, and only process mouse move events if that flag is set.

The above describes the process in terms of plain Win32 APIs that you'd call from C/C++; but it looks like Delphi provides direct support for most or all of them.

edit: Possible Delphi implementation:

type
  TForm1 = class(TForm)
    Label1: TLabel;
    procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
    procedure FormPaint(Sender: TObject);
    procedure FormMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
  private
    FCacheWnd: HWND;
    FCaptured: Boolean;
  public
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

const  // the first item, the place where the crosshair is
  ClickRect: TRect = (Left: 10; Top: 10; Right: 44; Bottom: 44);

procedure TForm1.FormPaint(Sender: TObject);
begin
  // draw the control and the crosshair if no capturing
  if GetCapture <> Handle then begin  
    DrawFrameControl(Canvas.Handle, ClickRect, 0, DFCS_BUTTONPUSH);
    DrawIcon(Canvas.Handle, ClickRect.Left, ClickRect.Top,
              Screen.Cursors[crCross]);
  end;
end;

procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if (Button = mbLeft) and (Shift = [ssLeft])
      and PtInRect(ClickRect, Point(X, Y)) then begin
    // the second item, draw the control pressed,
    // set the flag and the capture. FCacheWnd is used not to get
    // window information for every mouse move - if the window under the
    // mouse is not changed.
    DrawFrameControl(Canvas.Handle, ClickRect, 0, DFCS_PUSHED);
    FCacheWnd := 0;
    FCaptured := True;
    SetCapture(Handle);
    Screen.Cursor := crCross; // the third item, set the cursor to crosshair.
  end;
end;

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

function GetWndInfo(Wnd: HWND): string;
var
  ClassName: array [0..256] of Char;
begin
  Result := '';
  if IsWindow(Wnd) then begin
    GetClassName(Wnd, ClassName, 256);
    Result := Format('Window: %x [%s]', [Wnd, ClassName]);
    if (GetWindowLong(Wnd, GWL_STYLE) and WS_CHILD) = WS_CHILD then begin
      Wnd := GetAncestor(Wnd, GA_ROOT);
      GetClassName(Wnd, ClassName, 256);
      Result := Format(Result + sLineBreak + 'Top level: %x [%s]', [Wnd, ClassName]);
    end;
  end;
end;

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  Wnd: HWND;
begin
  if FCaptured then begin
    // fourth item, convert coordinates and find the window under the cursor
    Wnd := GetWndFromClientPoint(Handle, Point(X, Y));
    if Wnd <> FCacheWnd then 
      Label1.Caption := GetWndInfo(Wnd);
    FCacheWnd := Wnd;
  end;
end;

procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if FCaptured then begin
    // fifth item
    FCaptured := False;
    ReleaseCapture;
    InvalidateRect(Handle, @ClickRect, False); // invalidate pressed look
    Screen.Cursor := crDefault;
  end;
end;

Solution 3

Edit: It's gone, but you used to be able to download Delphi Window Spy by Eddie Shipman, from delphipages.com, which has turned into a festering heap of useless linkbait.

Share:
20,626
Letholdrus
Author by

Letholdrus

Updated on August 08, 2020

Comments

  • Letholdrus
    Letholdrus over 3 years

    How can I get the handle of a window to be passed to Delphi by the user selecting the window (could be any other aplication's window) by clicking with the mouse on it. In my Delphi app I could have a button the user clicks that starts this detection process as well as a label displaying the clicked on window's title in the Delphi app. When the user is satisfied he selected the correct window he could click the button in my Delphi app (which will be modal) to stop the selection process and let my app start doing to the other window what it needs to do...