3

I need the opposite information that the question "How to get cursor position on a control?" asks.

Given the current cursor position, how can I find the form (in my application) and the control that the cursor is currently over? I need the handle to it so that I can use Windows.SetFocus(Handle).

For reference, I'm using Delphi 2009.

Community
  • 1
  • 1
lkessler
  • 19,819
  • 36
  • 132
  • 203

4 Answers4

5

I experienced some problems with suggested solutions (Delphi XE6/Windows 8.1/x64):

  • FindVCLWindow doesn't search disabled controls (Enabled=False).
  • TWinControl.ControlAtPos doesn't search controls if they are disabled indirectly (for example if Button.Enabled=True, but Button.Parent.Enabled=False).

In my case it was a problem, because i need to find any visible control under the mouse cursor, so i have to use my own implementation of function FindControlAtPos:

function FindSubcontrolAtPos(AControl: TControl; AScreenPos, AClientPos: TPoint): TControl;
var
  i: Integer;
  C: TControl;
begin
  Result := nil;
  C := AControl;
  if (C=nil) or not C.Visible or not TRect.Create(C.Left, C.Top, C.Left+C.Width, C.Top+C.Height).Contains(AClientPos) then
    Exit;
  Result := AControl;
  if AControl is TWinControl then
    for i := 0 to TWinControl(AControl).ControlCount-1 do
    begin
      C := FindSubcontrolAtPos(TWinControl(AControl).Controls[i], AScreenPos, AControl.ScreenToClient(AScreenPos));
      if C<>nil then
        Result := C;
    end;
end;

function FindControlAtPos(AScreenPos: TPoint): TControl;
var
  i: Integer;
  f,m: TForm;
  p: TPoint;
  r: TRect;
begin
  Result := nil;
  for i := Screen.FormCount-1 downto 0 do
    begin
      f := Screen.Forms[i];
      if f.Visible and (f.Parent=nil) and (f.FormStyle<>fsMDIChild) and 
        TRect.Create(f.Left, f.Top, f.Left+f.Width, f.Top+f.Height).Contains(AScreenPos) 
      then
        Result := f; 
    end;
  Result := FindSubcontrolAtPos(Result, AScreenPos, AScreenPos);
  if (Result is TForm) and (TForm(Result).ClientHandle<>0) then
  begin
    WinAPI.Windows.GetWindowRect(TForm(Result).ClientHandle, r);
    p := TPoint.Create(AScreenPos.X-r.Left, AScreenPos.Y-r.Top);
    m := nil;
    for i := TForm(Result).MDIChildCount-1 downto 0 do
    begin
      f := TForm(Result).MDIChildren[i];
      if TRect.Create(f.Left, f.Top, f.Left+f.Width, f.Top+f.Height).Contains(p) then
        m := f; 
    end;
    if m<>nil then
      Result := FindSubcontrolAtPos(m, AScreenPos, p);
  end;
end;
Gabriel
  • 20,797
  • 27
  • 159
  • 293
Andrei Galatyn
  • 3,322
  • 2
  • 24
  • 38
3

I think FindVCLWindow will meet your needs. Once you have the windowed control under the cursor you can walk the parent chain to find the form on which the window lives.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Is it as easy as that? I looked up FindVCLWindow on SO and found: http://stackoverflow.com/questions/1078472/using-findvclwindow-to-call-winhelp32-winxp-pro-sp3-32bit-in-delphi and in that they are also adjusting the CursorPosition and one of the comments in the answer mentioned a problem with FindVCLWindow. – lkessler Jul 16 '11 at 19:30
  • What exactly is the problem with `FindVCLWindow`? – David Heffernan Jul 16 '11 at 19:34
  • 2
    It is as simple. Gamecat provided an alternative, but the OP came back with his own answer explaining he had done something wrong in his event handlers. His own answer ( http://stackoverflow.com/questions/1078472/using-findvclwindow-to-call-winhelp32-winxp-pro-sp3-32bit-in-delphi/1078808#1078808 ) indeed simply uses FindVCLWindow. – Marjan Venema Jul 16 '11 at 19:35
  • 2
    You can use `GetParentForm` in forms.pas that walks the parent chain for you. – Sertac Akyuz Jul 17 '11 at 02:50
2

If you want to know the control inside a form that is at a certain x,y coordinate

Use

function TWinControl.ControlAtPos(const Pos: TPoint; AllowDisabled: Boolean;
        AllowWinControls: Boolean = False; AllLevels: Boolean = False): TControl;

Given the fact that you seem only interested in forms inside your application, you can just query all forms.

Once you get a non-nil result, you can query the control for its Handle, with code like the following

Pseudo code

function HandleOfControlAtCursor: THandle;
const
  AllowDisabled = true;
  AllowWinControls = true;
  AllLevels = true;
var
  CursorPos: TPoint
  FormPos: TPoint;
  TestForm: TForm;
  ControlAtCursor: TControl;
begin
  Result:= THandle(0);
  GetCursorPos(CursorPos);
  for each form in my application do begin
    TestForm:= Form_to_test;
    FormPos:= TestForm.ScreenToClient(CursorPos);
    ControlAtCursor:= TestForm.ControlAtPos(FormPos,  AllowDisabled,
                                            AllowWinControls, AllLevels);
    if Assigned(ControlAtCursor) then break;
  end; {for each}
  //Break re-enters here
  if Assigned(ControlAtCursor) then begin
    while not(ControlAtCursor is TWinControl) do 
      ControlAtCursor:= ControlAtCursor.Parent;
    Result:= ControlAtCursor.Handle;
  end; {if}
end;

This also allows you to exclude certain forms from consideration should you so desire. If you're looking for simplicity I'd go with David and use FindVCLWindow.

P.S. Personally I'd use a goto rather than a break, because with a goto it's instantly clear where the break re-enters, but in this case it's not a big issue because there are no statements in between the break and the re-entry point.

Johan
  • 74,508
  • 24
  • 191
  • 319
  • I've never done that (querying all forms) before. My program may have a half dozen open at once. How do I go about doing that, I mean iterating through all of them rather than hardcoding them? – lkessler Jul 16 '11 at 19:21
  • @David: So are you supporting Johan's answer, or your own FindVCLWindow answer? – lkessler Jul 16 '11 at 19:27
  • I was merely answering your question about iterating over forms. If you don't have a form then I think FindVclWindow may be cleaner. It's hard to say for sure. Only you can know. – David Heffernan Jul 16 '11 at 19:30
0

I worked on a large project that had lots of frames and lots of dynamically created controls. When the software was running, it was difficult to figure out which control is which and were it is created. So, I wrote this tiny piece of code that tells you which control is under the mouse. I was showing the Digger form only if the program was compiled in Debug mode, so it was not available to the customer but only to the developers.

The code is very very simple. It all resumes to a single recursive function called ShowParentTree. We start calling the ShowParentTree from Digg which is called when the application goes idle:

procedure TfrmDigger.ApplicationEventsIdle(Sender: TObject; var Done: Boolean);
begin
   Digg;
end;

The Digg function looks like this. The magic is done by FindVCLWindow:

procedure TfrmDigger.Digg;
VAR Ctrl : TWinControl;
begin
 Ctrl := FindVCLWindow(Mouse.CursorPos); { It will not “see” disabled controls }

 if Ctrl <> NIL then
 begin
  VAR s:= ctrl.Name+ ‘ (‘+ ctrl.ClassName + ‘)’;
  Memo.Text:= s+ #13#10+ ShowParentTree(ctrl, 1);

 Caption := s;
 if ctrl is TLabeledEdit then
 Caption := Caption + ‘ Text: ‘+TLabeledEdit(ctrl).Text;
end;
end;

Once we got the control under the mouse, ShowParentTree digs down the parent of that control, and the parent’s parent and so on, with a recursive call to itself:

function ShowParentTree(Control: TControl; Depth: Integer): string; { Recursive }
VAR Ctrl: TControl;
begin
  Ctrl:= Control.Parent;
  if Ctrl = NIL
  then Result:= ”
  else
  begin
     Result:= System.StringOfChar(‘ ‘, Depth);
     Inc(Depth);
     Result:= Result+ ‘ ‘+ Ctrl.Name + ‘ (‘+ Ctrl.ClassName+ ‘)’+ #13#10+ ShowParentTree(Ctrl, Depth); { Recursive }
  end;
end;

We leave the recursive call once we reached deep down to the form.

_

A warning: Disabled controls cannot be found/investigated but FindDragTarget will solve that problem.

Gabriel
  • 20,797
  • 27
  • 159
  • 293