How do I get the Control that is under the cursor in Delphi?
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.
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, 2022Comments
-
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 almost 13 yearsI'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 almost 13 years@David: So are you supporting Johan's answer, or your own FindVCLWindow answer?
-
lkessler almost 13 yearsIs 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 almost 13 yearsI 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 almost 13 yearsWhat exactly is the problem with
FindVCLWindow
? -
Marjan Venema almost 13 yearsIt 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 almost 13 yearsYou can use
GetParentForm
in forms.pas that walks the parent chain for you. -
lkessler over 9 yearsThank 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 over 9 years@lkessler I updated the code, now it is compatible with MDI-forms too.
-
Server Overflow over 3 yearsThis should be the accepted answer. Good job Andrei!