Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Catch a mouse click on any VCL component, and determine its .Tag value

Tags:

delphi

I'm using a home-grown translation tool. (Next time I'll use one of the libraries, as described here: delphi translation tool.)

My translators complain that translating a long list of strings is difficult because they're not seeing them in context (on the screen in which they appear.)

One translator made a great suggestion that he should be able to click on a component to change its text. I can implement this if I can find an way to hook program wide, an event so that when a user clicks on a component while holding down the CTRL key, an event handler is called. The event handler would determine if the component has a .Caption property, and if so, get the value of the Tag component (and then allow some user input.)

(Each translatable component has unique integer in its Tag properly, which I use to look up the component's .Caption.)

Any suggestions on how to go about this? It's over my head. I need something like a form's KeyPreview, but for mouse-clicks that would figure out what VCL component was clicked, and determine it's .Tag value.

Tom

EDIT:

Using David H.'s suggestion, the only events I get are when the app gets focus or loses it. What have I done wrong?

    function TForm1.AppHookFunc(var Message : TMessage) : Boolean;
    begin
      Result := FALSE;
      inc(i); outputdebugstring(Pchar(inttostr(i) + ': ' + IntTostr(Message.msg)));
      if Message.Msg = WM_MBUTTONDOWN then
        begin      Beep;
        //...DoSomething...
        //Result := True;
        end;
    end;

    procedure TForm1.FormCreate(  Sender: TObject);
    begin
      Application.HookMainWindow(AppHookFunc);
    end;

    procedure TForm1.FormDestroy(
      Sender: TObject);
    begin
      Application.UnHookMainWindow(AppHookFunc);
    end;

EDIT 2

I'm almost there! But FindDragTarget seldom returns anything but nil. If I make an enormous button covering most of the control, I can sometimes get it to work. The X,Y coordinates in the tagMSG received are relative to the control. I would have though they'd relative to the form. Am I still using a different event hook than I should? Any suggestions:

  procedure TForm1.ApplicationEvents1Message( var Msg: tagMSG;
                                              var Handled: Boolean);
  var
    Target: TControl;
    Point: TPoint;
  begin
    Handled := FALSE;
    if (Msg.Message = WM_LBUTTONDOWN) And isAltDown then
      begin
        Point.X := LongRec(Msg.lParam).Lo;
        Point.Y := LongRec(Msg.lParam).Hi;
        Target := FindDragTarget( Point, {AllowDisabled=}TRUE);
        if Assigned(Target) then
          begin
            if Target Is TButton then
              outputdebugString(Pchar(TButton(Target).Caption));
          end
        else
          outputdebugstring(Pchar(IntToStr(Point.X) + ', ' + IntToStr(Point.Y)));
      end;
  end;

FINAL EDIT:

I changed the code above to use GetCursorPos rather than Msg.lParam. It's working now. Very cool! SO Rocks!

THANK YOU BOTH FOR YOUR HELP!

like image 716
RobertFrank Avatar asked Nov 10 '11 17:11

RobertFrank


1 Answers

I'm assuming this is a VCL app. For FireMonkey this would not work.

  1. Add an Application.OnMessage event handler.
  2. In the event handler look for WM_LBUTTONDOWN or perhaps WM_LBUTTONUP and check that the modifier key state is as you desire, e.g. CTRL is down.
  3. Call FindDragTarget passing the position associated with the mouse event. This will give you the control under the mouse, if indeed there is one (i.e. check for nil).
  4. Do whatever it is you want to that control.
like image 112
David Heffernan Avatar answered Nov 12 '22 04:11

David Heffernan