Home > other >  How to make TImage and TLabel receive WM_RBUTTONDOWN messages?
How to make TImage and TLabel receive WM_RBUTTONDOWN messages?

Time:11-12

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;
  • Related