In a Delphi 11 32-bit VCL Application in Windows 10, at run-time, I right-click a control while holding down the SHIFT and CTRL modifier keys, to copy the name of the clicked control to the clipboard:
procedure TformMain.ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean);
begin
case Msg.message of
Winapi.Messages.WM_RBUTTONDOWN:
begin
// Detect the name of the clicked control:
var ThisControl: Vcl.Controls.TWinControl;
ThisControl := Vcl.Controls.FindControl(Msg.hwnd);
if Assigned(ThisControl) then
begin
var keys: TKeyboardState;
GetKeyboardState(keys);
// when right-clicking a control, hold down the SHIFT and CTRL key to escape the possible default click behavior of the control:
if (keys[VK_SHIFT] and $80 <> 0) and (keys[VK_CONTROL] and $80 <> 0) then
begin
Handled := True;
//CodeSite.Send('TformMain.ApplicationEvents1Message: ThisControl.Name', ThisControl.Name);
Vcl.Clipbrd.Clipboard.AsText := ThisControl.Name;
end;
end;
end;
end;
end;
This works with ALMOST all controls, EXCEPT with Timage
and TLabel
(and possibly a few other control types). How can I make this work with Timage
and TLabel
too?
CodePudding user response:
TImage
and TLabel
are derived from TGraphicControl
, not TWinControl
. They do not have an HWND
of their own, which is why Vcl.Controls.FindControl()
does not work for them. You are receiving WM_RBUTTONDOWN
messages belonging to their Parent
's HWND
instead. Internally, when the VCL routes the message, it will account for graphical child controls. But your code is not.
Try Vcl.Controls.FindDragTarget()
instead. It takes screen coordinates as input (which you can get by translating the client coordinates in WM_RBUTTONDOWN
's lParam
using Winapi.ClientToScreen()
or Winapi.MapWindowPoints()
), and then returns the TControl
at those coordinates, so it works with both windowed and graphical controls.
That being said, you don't need to use Winapi.GetKeyboardState()
in this situation, as WM_RBUTTONDOWN
's wParam
tells you whether SHIFT and CTRL keys were held down at the time the message was generated (remember, you are dealing with queued messages, so there is a delay between the time the message is generated and the time you receive it).
procedure TformMain.ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean);
const
WantedFlags = MK_SHIFT or MK_CONTROL;
begin
if Msg.message = WM_RBUTTONDOWN then
begin
// Detect the name of the clicked control:
var Pt: TPoint := SmallPointToPoint(TSmallPoint(Msg.LParam));
Windows.ClientToScreen(Msg.hwnd, Pt);
var ThisControl: TControl := FindDragTarget(Pt, True);
if Assigned(ThisControl) then
begin
// when right-clicking a control, hold down the SHIFT and CTRL key to escape the possible default click behavior of the control:
if (Msg.wParam and WantedFlags) = WantedFlags then
begin
Handled := True;
//CodeSite.Send('TformMain.ApplicationEvents1Message: ThisControl.Name', ThisControl.Name);
Clipboard.AsText := ThisControl.Name;
end;
end;
end;
end;