-1

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?

user1580348
  • 5,721
  • 4
  • 43
  • 105
  • 4
    `TLabel` does not have a handle. I don't believe that `TImage` does either. This means that you can't identify them by `Msg.hwnd`. – Ken White Nov 11 '21 at 18:43
  • 3
    This is because `TImage` and `TLabel` are graphic controls (they derive from `TGraphicControl`) and not windowed controls (`TWinControl`). So a `TImage` (or a `TLabel`) isn't a window in the Win32 sense -- it has no HWND. Instead, it merely paints itself on top of its parent, which *is* a windowed control. – Andreas Rejbrand Nov 11 '21 at 18:43
  • Related/dupe: [Catch a mouse click on any VCL component, and determine its .Tag value](https://stackoverflow.com/questions/8083445/) – Remy Lebeau Nov 11 '21 at 19:34

1 Answers1

3

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;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • I am not sure whether the expression `WantedFlags = WantedFlags` is equivalent to `12 = 12`? Maybe this expression `((Msg.wParam and WantedFlags) = WantedFlags)` adds clarity? – user1580348 Nov 11 '21 at 20:54
  • 1
    @user1580348: `and` [has higher precedence](https://docwiki.embarcadero.com/RADStudio/Sydney/en/Expressions_(Delphi)#Operator_Precedence) than `=`, so `Msg.wParam and WantedFlags = WantedFlags` is interpreted by the compiler as `(Msg.wParam and WantedFlags) = WantedFlags`: that is, the comparison checks that all the bits in `WantedFlags` are set in `Msg.wParam`. No need for parentheses, just like you don't need them in elementary school's `2 + 3×4`. So the expression `WantedFlags = WantedFlags` isn't part of Remy's code. – Andreas Rejbrand Nov 11 '21 at 20:59
  • @user1580348 `and` has a higher precedence than `=`, so `Msg.wParam and WantedFlags = WantedFlags` would be interpreted as `(Msg.wParam and WantedFlags) = WantedFlags`, but OK, I've updated my example to make that more explicit. – Remy Lebeau Nov 11 '21 at 21:00
  • 3
    In fact, the OP already knows this, since the OP's code contains `keys[VK_SHIFT] and $80 <> 0`. – Andreas Rejbrand Nov 11 '21 at 21:03
  • @RemyLebeau I love the clarity of EXPLICIT expressions! Implicit things sometimes lead to misunderstandings. I could tell you some real-life examples, but... – user1580348 Nov 11 '21 at 21:15
  • 1
    @user1580348 ...but then you would have used parentheses in your own code, right? – AmigoJack Nov 12 '21 at 01:06
  • @AmigoJack How does your RHETORIC comment contribute to answering the current question? (Which is already answered). – user1580348 Nov 13 '21 at 13:32
  • @user1580348 It wasn't [rhetoric](https://en.wiktionary.org/wiki/rhetoric) at all: why is your code the opposite of "clarity of explicit expressions" when you "love" it? And how does **your comment** contribute (other than underline your own inconsistency)? – AmigoJack Nov 13 '21 at 16:15