How do I get the Control that is under the cursor in Delphi?

10,475

Solution 1

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.

Solution 2

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;

Solution 3

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.

Share:
10,475
lkessler
Author by

lkessler

I'm a Programmer and a Genealogist. I am the developer of the Genealogy Software: Behold Also the DNA Analysis Software: Double Match Triangulator I operate the GenSoftReviews site My blog | My Tweets | Brute Force If you're into Genealogy, I recommend you try the Genealogy and Family History Stack Exchange site at: http://genealogy.stackexchange.com/ See My Family Research and Unsolved Mysteries

Updated on June 08, 2022

Comments

  • lkessler
    lkessler almost 2 years

    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.

  • lkessler
    lkessler almost 13 years
    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
    lkessler almost 13 years
    @David: So are you supporting Johan's answer, or your own FindVCLWindow answer?
  • lkessler
    lkessler almost 13 years
    Is it as easy as that? I looked up FindVCLWindow on SO and found: stackoverflow.com/questions/1078472/… and in that they are also adjusting the CursorPosition and one of the comments in the answer mentioned a problem with FindVCLWindow.
  • David Heffernan
    David Heffernan almost 13 years
    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
    David Heffernan almost 13 years
    What exactly is the problem with FindVCLWindow?
  • Marjan Venema
    Marjan Venema almost 13 years
    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 ( stackoverflow.com/questions/1078472/… ) indeed simply uses FindVCLWindow.
  • Sertac Akyuz
    Sertac Akyuz almost 13 years
    You can use GetParentForm in forms.pas that walks the parent chain for you.
  • lkessler
    lkessler over 9 years
    Thank you for adding your answer even this late. I never got it working perfectly myself, and I'm going to try this and see if it's better.
  • Andrei Galatyn
    Andrei Galatyn over 9 years
    @lkessler I updated the code, now it is compatible with MDI-forms too.
  • Server Overflow
    Server Overflow over 3 years
    This should be the accepted answer. Good job Andrei!