Recent

Author Topic: [Solved] Focus Tracker (component tracking focus changes and cause)  (Read 14429 times)

balazsszekely

  • Guest
This is a more simplistic approach(not necessary a better one).  I prefer to use what is already implemented in lcl(see attachment).
Code: Pascal  [Select][+][-]
  1. unit uDrawGridEx;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Controls, Forms, Grids, LMessages, Dialogs;
  9.  
  10. type
  11.  
  12.   { TDrawGridEx }
  13.  
  14.   TDrawGridEx = class(TDrawGrid)
  15.   private
  16.     FForm: TForm;
  17.     FPrevActiveControl: TControl;
  18.     FCurrActiveControl: TControl;
  19.     FMsg: Cardinal;
  20.     FShiftState: TShiftState;
  21.     FKey: Word;
  22.     procedure DoActiveControlChange(Sender: TObject);
  23.     procedure DoUserInput(Sender: TObject; Msg: Cardinal);
  24.     procedure DoKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
  25.     procedure DoOnEnter(Sender: TObject);
  26.   public
  27.     constructor Create(TheOwner: TComponent); override;
  28.     destructor Destroy; override;
  29.   end;
  30.  
  31. implementation
  32.  
  33. { TDrawGridEx }
  34.  
  35. constructor TDrawGridEx.Create(TheOwner: TComponent);
  36. begin
  37.   inherited Create(TheOwner);
  38.   Screen.OnActiveControlChange := @DoActiveControlChange;
  39.   Application.OnUserInput := @DoUserInput;
  40.   if (TheOwner is TForm) then
  41.   begin
  42.     FForm := TForm(TheOwner);
  43.     FForm.KeyPreview := True;
  44.     FForm.OnKeyUp := @DoKeyUp;
  45.   end;
  46.   Self.OnEnter := @DoOnEnter;
  47. end;
  48.  
  49. destructor TDrawGridEx.Destroy;
  50. begin
  51.   inherited Destroy;
  52. end;
  53.  
  54. procedure TDrawGridEx.DoActiveControlChange(Sender: TObject);
  55. begin
  56.   FPrevActiveControl := FCurrActiveControl;
  57.   FCurrActiveControl :=  FForm.ActiveControl;
  58. end;
  59.  
  60. procedure TDrawGridEx.DoUserInput(Sender: TObject; Msg: Cardinal);
  61. begin
  62.   //you can log here the control that was clicked
  63.   if (Msg >= LM_MOUSEFIRST) and (Msg <= LM_MOUSELAST) then
  64.     FMsg := Msg;  
  65. end;
  66.  
  67. procedure TDrawGridEx.DoKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
  68. begin
  69.   FShiftState := Shift;
  70.   FKey := Key;
  71. end;
  72.  
  73. procedure TDrawGridEx.DoOnEnter(Sender: TObject);
  74. begin
  75.   //process input
  76. end;
  77.  
  78. end.
  79.  

dot.not

  • New Member
  • *
  • Posts: 22
  • The answer is 42

@GetMem:
Thank you. I see and I agree, the simplest approach is almost always the best.

I can see only one problem arising from using this control:
Say you create it on a form (just like you did on FormCreate in your example zip) but sometime in the future, having forgotten the specifics that it has soft-"reserved" some notifications, specifically Screen.OnActiveControlChange, Application.OnUserInput, Form.OnKeyUp and even it's own OnEnter.
Say you also want to use the form's KeyPreview on that form, so you set it at design-time to true and also write an OnKeyUp handler, and then you run that app...
Aaaand... you are left scratching your head on why either the form's handler or the grid's functionality is broken.
Now, you, being the dev of both, may remember (or otherwise quickly doscover) why it's broken, but will others who may use that control?

That's what I'm usually referring to as "Tag-Wars" since to me it happened when I wrote a control that based some internal functionality by manipulating the value of its own Tag. But Tag is exposed and another dev, who used my control also used all the form's control's Tags to base some other funcionality on. The result was hilarious My control was working just fine on any other form, his form was working just fine without my control on it. Sad story, sad times.

As long as you're in control of the whole code it's fine I guess.


P.S.:
If you dont mind, would you please explain the reasons you would use the OnEnter notification instead of overriding the ancestor's DoEnter
(tWinControl virtual method, which btw does trigger the onEnter notification)
like so:

(class declaration)
Code: Pascal  [Select][+][-]
  1.  
  2. type
  3.   TDrawGridEx = class(TDrawGrid)
  4.   private
  5.     FForm: TForm;
  6.     FPrevActiveControl: TControl;
  7.     FCurrActiveControl: TControl;
  8.     FMsg: Cardinal;
  9.     FShiftState: TShiftState;
  10.     FKey: Word;
  11.     procedure DoActiveControlChange(Sender: TObject);
  12.     procedure DoUserInput(Sender: TObject; Msg: Cardinal);
  13.     procedure DoKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
  14.  
  15.     //
  16.     // procedure DoOnEnter(Sender: TObject);                  // this is replaced by DoEnter
  17.     //
  18.   protected
  19.     procedure DoEnter; override;
  20.  
  21.   public
  22.     constructor Create(TheOwner: TComponent); override;
  23.     destructor Destroy; override;                             // note: dont realy need this
  24.   end;
  25.  

(class constructor)
Code: Pascal  [Select][+][-]
  1.  
  2. constructor TDrawGridEx.Create(TheOwner: TComponent);
  3. begin
  4.   inherited Create(TheOwner);
  5.   Screen.OnActiveControlChange := @DoActiveControlChange;     // potential to trigger "Tag-War" .
  6.   Application.OnUserInput := @DoUserInput;                    // potential to trigger "Tag-War" .
  7.   if (TheOwner is TForm) then
  8.   begin
  9.     FForm := TForm(TheOwner);
  10.     FForm.KeyPreview := True;
  11.     FForm.OnKeyUp := @DoKeyUp;                                // potential to trigger "Tag-War" .
  12.   end;
  13.  
  14.   // Self.OnEnter := @DoOnEnter;        // no need to soft-reserve this, job is done in DoEnter
  15.  
  16. end;
  17.  

(replaced class focus reception handler)
Code: Pascal  [Select][+][-]
  1.  
  2. // procedure TDrawGridEx.DoOnEnter(Sender: TObject);             // this is replaced by DoEnter
  3. procedure TDrawGridEx.DoEnter;
  4. begin
  5.   inherited DoEnter;                              // allow my ancestor to do what it needs done
  6.   //
  7.   // now do whatever it is TDrawGridEx needs doing on focus reception
  8.   //
  9.   .
  10.   .
  11.   .
  12. end;
  13.  


P.P.S. (and now im nitpicking, my bad):
I think you dont need to implicitly free FDrawGridEx on FormDestroy, since you already gave it an owner on create it should be freed as part of its owner's destroy sequence, right?

Best regards
There's 10 kinds of people

balazsszekely

  • Guest
@dot.not

First of all, from a strictly OOP point of view, this whole idea that a grid should know about external clicks, keyboard events, etc... is wrong, I mean fundamentally wrong, the grid should only do grid related stuff. Secondly you did not give more details about why you need such a mechanism, perhaps there is a better way to achieve the same thing?. Thirdly, I implemented this alternative method in a few minutes, without thinking too much about the side effects or paying too much attention to details. With that said, you're absolutely right about overriding the DoEnter method and freeing the grid(since it's already has an owner).

Quote
....
Say you also want to use the form's KeyPreview on that form, so you set it at design-time to true and also write an OnKeyUp handler, and then you run that app...
Aaaand... you are left scratching your head on why either the form's handler or the grid's functionality is broken.
In order for more event(from the same type) to "join the party" you can do something like this:
Code: Pascal  [Select][+][-]
  1.   Application.AddOnUserInputHandler(@DoUserInput, True);
  2.   Screen.AddHandlerActiveControlChanged(@DoActiveControlChanged, True);
  3.   form1.AddHandlerOnKeyDown(....);
  4.  

As a conclusion you should write a separate class just to monitor the event you're interested in, then the grid just subscribe to one of the instance of this class. On windows I would do a low level keyboard/mouse hook, but that's not cross platform.
« Last Edit: August 26, 2017, 08:06:19 am by GetMem »

dot.not

  • New Member
  • *
  • Posts: 22
  • The answer is 42

First of all, from a strictly OOP point of view, this whole idea that a grid should know about external clicks, keyboard events, etc... is wrong, I mean fundamentally wrong, the grid should only do grid related stuff
and
Quote
As a conclusion you should write a separate class just to monitor the event you're interested in, then the grid just subscribe to one of the instance of this class.

Absolutely true, that's why my 2nd post on this thread
I dont realy need the the grid to be able to mine the knowledge itself, it just needs to ne made aware of, or served with, the prev focus info.
Anyhow, here is as far as I got:
I wrote a FocusTracker component that maintains an internal list of objects, one for every tWinControl on the form.
Each of those objects hooks into the WindowProc queue of said tWincontrol and so can monitor CM_ENTER messages and report them back to the FocusTracker, who exposes the last activated control (the one that was active bofore the current ActiveControl) and fires a notification event

Also the reason the grid has to be aware is to position/select a diferent cell based
on TAB (=activate 1st visible cell)
or SHIFT+TAB (=activate last visible cell)
and CLICK (=let the active cell be as it got activated by the click).

Quote
In order for more event(from the same type) to "join the party" you can do something like this:

      Application.AddOnUserInputHandler(@DoUserInput, True);
      Screen.AddHandlerActiveControlChanged(@DoActiveControlChanged, True);
      form1.AddHandlerOnKeyDown(....);

I wont be the only user of that control. If any other forgets to use xxxx.AddHandlerYYYY and just goes and assigns OnYYYY := @ZZZZZ then there's a mess like the tag-war I described. I would like to avoid such pitfalls.

Quote
On windows I would do a low level keyboard/mouse hook, but that's not cross platform.

Yes please, it would be much appreciated if you could share that, or if you know how to complete the WindowProc approach I am attempting (and keep failing at) posted on page 1 reply #11 :

Code: Pascal  [Select][+][-]
  1.  
  2. procedure TndFocusTrackedItem.TrackedWindowProc(var p_Message:TLMessage);
  3. begin
  4.   if p_Message.msg=CM_ENTER then ;                    // report to my focusTracker master: a control just received focus
  5.   //
  6.   if p_Message.msg=LM_LBUTTONDOWN then ;              // report to my focusTracker master: a control got clicked on
  7.   //
  8.   if p_Message.msg=LM_KEYDOWN then ;                  // report to my focusTracker master: a Key just got pressed
  9.   //
  10.   origiinalWindowProc(p_Message);                     // this was the original WindowProc of the TWinControl I'm tracking .
  11.                                                       // before I highjacked it and set it to point to TrackedWindowProc
  12.                                                       // so make sure I call the original
  13. end;
  14.  


Thank you

There's 10 kinds of people

balazsszekely

  • Guest
Quote
Yes please, it would be much appreciated if you could share that

1. Keyboard: http://forum.lazarus.freepascal.org/index.php/topic,37049.msg247722.html#msg247722
2. Mouse: http://forum.lazarus.freepascal.org/index.php/topic,28963.msg182081.html#msg182081

Please note:
1. You have to adapt the above examples to suit your needs.
2. Because they are low level hooks, you don't have to inject them in every running application via a dll +
they work even outside your application
3. It's windows only
« Last Edit: August 24, 2017, 08:31:36 pm by GetMem »

dot.not

  • New Member
  • *
  • Posts: 22
  • The answer is 42

Yes I understand, I will try to achieve the requested functionality while trying to stay O.S. independent. If that doesn't work out I will try the win API hook.
Thank you
There's 10 kinds of people

balazsszekely

  • Guest
Quote
Yes please, it would be much appreciated if you could share that, or if you know how to complete the WindowProc approach I am attempting (and keep failing at) posted on page 1 reply #11 :
This is cross platform, tested on carbon, gtk2, win32/win64, qt:
Code: Pascal  [Select][+][-]
  1. uses LCLIntf, LCLType, LMessages;
  2.  
  3. procedure TndFocusTrackedItem.TrackedWindowProc(var p_Message: TLMessage);
  4. var
  5.   ShiftState: TShiftState;
  6. begin
  7.   case p_Message.Msg of
  8.     LM_KEYFIRST..LM_KEYLAST:
  9.     begin
  10.       ShiftState := MsgKeyDataToShiftState(p_Message.lParam);
  11.       //to detect Shift + Tab for example
  12.       if (ssShift in ShiftState) and (p_Message.wParam = VK_TAB) then
  13.     end;
  14.     LM_SETFOCUS:
  15.     begin
  16.       if (GetKeyState(VK_LBUTTON) and $8000 <> 0) then
  17.         //focus received via left mouse
  18.     end;
  19.     LM_KILLFOCUS:
  20.     begin
  21.       //
  22.     end;
  23.     LM_MOUSEFIRST..LM_MOUSELAST:
  24.     begin
  25.       //
  26.     end;
  27.   end;
  28.   origiinalWindowProc(p_Message);
  29. end;
« Last Edit: August 25, 2017, 10:25:06 am by GetMem »

dot.not

  • New Member
  • *
  • Posts: 22
  • The answer is 42
 :o

This looks awesome, trying it out right away.

BRB


Small update:

Wow this is a superset (as opposed to subset) solution to what I was asking for.
MsgKeyDataToShiftState? awesome!!!  GetKeyState? wicked!!!
It seems LM_KEYFIRST..LM_KEYLAST and LM_SETFOCUS are just about enough to cover my request and MsgKeyDataToShiftState and GetKeyState are just what I was missing.

Thank you so very much GetMem
« Last Edit: August 26, 2017, 12:24:47 am by dot.not »
There's 10 kinds of people

molly

  • Hero Member
  • *****
  • Posts: 2330
@getmem:
Very nicely done !

Quote
Ah, you're a qubit :D
Of course I'm all the intermediate values and pretty much far and beyond everything... maybe one day I'll find out who I am... I'll send you a postcard from paradise... just in case you wanna make a little vacation...  :D
I recently read somewhere that vacation is for wimps  ;)

I'm not sure that the location where i spent my last vacation had any support for a mail-delivery-service. We could at least give it a try  :)

dot.not

  • New Member
  • *
  • Posts: 22
  • The answer is 42
Greetings everyone

I have completed the focus tracker. I am posting the completed code here for anyone interested

Code: Pascal  [Select][+][-]
  1.  
  2. unit udnFocusTracker;
  3.  
  4. interface
  5. uses Classes,Controls,LMessages,LCLType,Forms;
  6.  
  7. type
  8.   TdnFocusTracker=class(TComponent)
  9.   private
  10.     fNotifyOnFocusChanged:TNotifyEvent;
  11.     current_ActiveControl:TWinControl;
  12.     tracked_ActiveControl:TWinControl;
  13.     tracked_KeyPressed:WPARAM;
  14.     tracked_ShiftState:TShiftState;
  15.     trackList:TList;
  16.     function countTrackedControls:integer;
  17.     procedure clearTrackedControls;
  18.     function setLastActiveControl(p_Control:TWinControl):boolean;
  19.     procedure doNotifyFocusChanged;
  20.   public
  21.     property NotifyOnFocusChanged:TNotifyEvent read fNotifyOnFocusChanged write fNotifyOnFocusChanged;
  22.     property TrackedControlCount:integer read countTrackedControls;
  23.     property LastActiveControl:TWinControl read tracked_ActiveControl;
  24.     property LastKeyPressed:WPARAM read tracked_KeyPressed;
  25.     property LastShiftState:TShiftState read tracked_ShiftState;
  26.     class function FindTrackerOnForm(p_Form:TCustomForm):TdnFocusTracker;
  27.     constructor Create(p_OwnerForm:TCustomForm);reintroduce;
  28.     destructor Destroy;override;
  29.     procedure ComposeTrackingList;
  30.   end;
  31.  
  32. implementation
  33. uses sysutils,LCLIntf;
  34.  
  35. type
  36.   TdnFocusTrackedItem=class
  37.   public
  38.     constructor Create(p_FocusTracker:TdnFocusTracker;p_TrackedControl:TWinControl);
  39.     destructor Destroy;override;
  40.   private
  41.     focus_tracker:TdnFocusTracker;
  42.     tracked_control:TWinControl;
  43.     originalWindowProc:TWndMethod;
  44.     procedure trackingWindowProc(var p_Message:TLMessage);
  45.   end;
  46.  
  47. constructor TdnFocusTrackedItem.Create(p_FocusTracker:TdnFocusTracker;p_TrackedControl:TWinControl);
  48. begin
  49.   tracked_control:=p_TrackedControl;
  50.   originalWindowProc:=tracked_control.WindowProc;
  51.   tracked_control.WindowProc:=@trackingWindowProc;
  52.   focus_tracker:=p_FocusTracker;
  53. end;
  54.  
  55. destructor TdnFocusTrackedItem.Destroy;
  56. begin
  57.   if assigned(tracked_control) then tracked_control.WindowProc:=originalWindowProc;
  58.   inherited;
  59. end;
  60.  
  61. procedure TdnFocusTrackedItem.trackingWindowProc(var p_Message:TLMessage);
  62. var f:boolean=false;
  63. begin
  64.   case p_Message.Msg of
  65.     LM_SETFOCUS:begin
  66.       if GetKeyState(VK_LBUTTON)<0 then focus_tracker.tracked_KeyPressed:=VK_LBUTTON;
  67.       f:=focus_tracker.setLastActiveControl(tracked_control);
  68.     end;
  69.     LM_KEYFIRST..LM_KEYLAST:begin
  70.       focus_tracker.tracked_ShiftState:=MsgKeyDataToShiftState(p_Message.lParam);
  71.       focus_tracker.tracked_KeyPressed:=p_Message.wParam;
  72.     end;
  73.   end;
  74.   originalWindowProc(p_Message);
  75.   if f then focus_tracker.doNotifyFocusChanged;
  76. end;
  77.  
  78. class function TdnFocusTracker.FindTrackerOnForm(p_Form:TCustomForm):TdnFocusTracker;
  79. var i:integer;
  80. begin
  81.   result:=nil;
  82.   if assigned(p_Form) then for i:=0 to p_Form.ComponentCount-1 do
  83.     if ClassType=p_Form.Components[i].ClassType then begin
  84.       result:=TdnFocusTracker(p_Form.Components[i]);
  85.       break;
  86.     end;
  87. end;
  88.  
  89. constructor TdnFocusTracker.Create(p_OwnerForm:TCustomForm);
  90. begin
  91.   if not assigned(p_OwnerForm)
  92.     then raise exception.create(ClassName+' needs an existing owner of type TCustomForm');
  93.   if assigned(FindTrackerOnForm(p_OwnerForm))
  94.     then raise exception.create('Only 1 '+ClassName+' per form required and allowed');
  95.   inherited create(p_OwnerForm);
  96.   current_ActiveControl:=nil;
  97.   tracked_ActiveControl:=nil;
  98.   tracked_KeyPressed:=0;
  99.   tracked_ShiftState:=[];
  100.   trackList:=TList.Create;
  101. end;
  102.  
  103. procedure TdnFocusTracker.clearTrackedControls;
  104. var i:integer;
  105. begin
  106.   if assigned(trackList) then begin
  107.     for i:=0 to trackList.Count-1 do TdnFocusTrackedItem(trackList[i]).Free;
  108.     trackList.Clear;
  109.   end;
  110. end;
  111.  
  112. destructor TdnFocusTracker.Destroy;
  113. begin
  114.   if assigned(trackList) then begin
  115.     clearTrackedControls;
  116.     FreeAndNil(trackList);
  117.   end;
  118.   inherited;
  119. end;
  120.  
  121. function TdnFocusTracker.countTrackedControls:integer;
  122. begin
  123.   result:=trackList.Count;
  124. end;
  125.  
  126. procedure TdnFocusTracker.ComposeTrackingList;
  127.   //
  128.   procedure internalTrackChildControls(p_Control:TWinControl);
  129.   var i:integer; w:TWinControl;
  130.   begin
  131.     for i:=0 to p_Control.ControlCount-1 do
  132.       if p_Control.Controls[i] is TWinControl then begin
  133.         w:=TWinControl(p_Control.Controls[i]);
  134.         trackList.Add(TdnFocusTrackedItem.Create(self,w));
  135.         internalTrackChildControls(w);
  136.       end;
  137.   end;
  138.   //
  139. begin
  140.   clearTrackedControls;
  141.   internalTrackChildControls(TWinControl(Owner));
  142. end;
  143.  
  144. function TdnFocusTracker.setLastActiveControl(p_Control:TWinControl):boolean;
  145. begin
  146.   result:=assigned(p_Control) and (current_ActiveControl<>p_Control);
  147.   if result then begin
  148.     tracked_ActiveControl:=current_ActiveControl;
  149.     current_ActiveControl:=p_Control;
  150.   end;
  151. end;
  152.  
  153. procedure TdnFocusTracker.doNotifyFocusChanged;
  154. var n:TNotifyEvent;
  155. begin
  156.   if assigned(fNotifyOnFocusChanged) then try
  157.     n:=fNotifyOnFocusChanged;
  158.     fNotifyOnFocusChanged:=nil;
  159.     n(self);
  160.   finally
  161.     fNotifyOnFocusChanged:=n;
  162.   end;
  163. end;
  164.  
  165. end.
  166.  


I will shortly follow up with a post with basic instructions on how to use this component.

Best regards


P.S.:
Is there a way for me to mark the thread as [Solved] or will a mod do that?
There's 10 kinds of people

balazsszekely

  • Guest
@dot.not
Quote
I have completed the focus tracker. I am posting the completed code here for anyone interested
Thanks for sharing.

Quote
Is there a way for me to mark the thread as [Solved] or will a mod do that?
Just paste "[Solved]" in front of the thread title.

dot.not

  • New Member
  • *
  • Posts: 22
  • The answer is 42
Re: [Solved] Focus Tracker (component tracking last focus change and cause)
« Reply #26 on: August 27, 2017, 10:08:32 pm »
The TdnFocusTracker is a component that keeps track of the the focus changes on a form and exposes what control had last focus before the focus shifted to the current ActiveControl. It also exposes what keyboard key was last pressed and what the keyboard state was. Additionaly it offers a TNotifyEvent which gets triggered everytime a focus change occurs.

Following is an example how to use it:

Add the unit to the {interface} uses clause:
Code: Pascal  [Select][+][-]
  1.  
  2. uses
  3.   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs,
  4.   udnFocusTracker;
  5.  

Add a field of type TdnFocusTracker in your form's private section:
Code: Pascal  [Select][+][-]
  1.  
  2.   TForm1 = class(TForm)
  3.   private
  4.     { private declarations }
  5.     myFocusTracker : TdnFocusTracker;
  6.     .
  7.     .
  8.     .
  9.  

Create the component in the Form's OnCreate handler:
Code: Pascal  [Select][+][-]
  1.  
  2. procedure TForm1.FormCreate(Sender:TObject);
  3. begin
  4.   myFocusTracker := TdnFocusTracker.Create(self);
  5.   .
  6.   .
  7.   .
  8.  

If your code is chaotic, or you place the TdnFocusTracker creation call in a reoccuring event handler, you can always check if the form has already a focus tracker component and obtain it, before trying to create it, like this:
Code: Pascal  [Select][+][-]
  1.  
  2.   myFocusTracker := TdnFocusTracker.FindTrackerOnForm(self);
  3.   if not assigned(myFocusTracker)
  4.     then myFocusTracker := TdnFocusTracker.Create(self);
  5.  

The FindTrackerOnForm is a class method, it accepts a TCustomForm parameter and returns nil if no tracker exists on the form or a pointer to the tracker.

The tracker offers a Notification event you can hook into to get notified whenever a focus change occurs. To set this up you have to have a TNotifyEvent method and assign it like bellow:
Code: Pascal  [Select][+][-]
  1.  
  2.   myFocusTracker.NotifyOnFocusChanged := @CentralFocusChangeHandler;
  3.  

This is completely optional, the focus changes and last key and shift states will be tracked regardless of if you use this notification.


Once you have created all controls (if you create any by code) you should invoke the method to start tracking:
Code: Pascal  [Select][+][-]
  1.  
  2.   myFocusTracker.ComposeTrackingList;
  3.  

This will cause the tracker to create an internal list of objects, each of those pointing to a control on the form and placing code to intercept the commands that are send to each control.

At any time after tracking has started you can obtain the values for which control was last active before the focus shifted to the current ActiveControl, what key was last pressed (vk code that reflects LCLType.VK_XXX values) and the shiftstate. Those are Exposed by the properties LastActiveControl, LastKeyPressed and LastShiftState respecively. Key and ShiftState values are same as those passed to the standard OnKeyDown, OnKeyPress and OnKeyUp event handlers, except here they are read-only.

There is no need to destroy/free the focus tracker, it will be freed by the form when the form is destroyed.


NOTES and LIMITATIONS:
i.
The TdnFocusTracker needs an existing TCustomForm as its owner passed as parameter to its constructor. An invalid or unassigned (nil) form parameter will fail the component construction raising an exception with following message:
TdnFocusTracker needs an existing owner of type TCustomForm

ii.
Only 1 TdnFocusTracker per Form allowed, subsequent creation attempts will fail the component construction raising an exception with following message:
Only 1 TdnFocusTracker per form required and allowed

iii.
Focus tracking will start once you call ComposeTrackingList. Subsequent calls to this method will clear and reconstruct the internal tracking list and restart tracking.

iv.
When using the focus change notification event make sure to avoid focus changes from within it. It will not fall into an endless loop but you will not get subsequent focus change notification triggered while in this handler.
Also make sure to catch and handle any exceptions your code may produce.

v.
If a mouse click resulted in a focus change then the LastKeyPressed will be equal to LCLType.VK_LBUTTON.

vi.
If only 1 control has ever been active on the form then LastActiveControl will equal nil.

vii.
The property TrackedControlCount returns the number of currently tracked controls. If this number is less than 1 then either your form has no controls on it or you have not called ComposeTrackingList yet.

- - - - - - - - -
EDIT.:

viii.
Another important limitation: Controls created after ComposeTrackingList will not be tracked.
If for example you have a tStringGrid with editing enabled, the cell's implace-editor is created and destroyed as needed. That editor would not have existed at the time of the tracking list creation and so will not produce tracking results.
- - - - - - -


Best regards
« Last Edit: September 01, 2017, 09:05:01 pm by dot.not »
There's 10 kinds of people

RAW

  • Hero Member
  • *****
  • Posts: 871
Re: [Solved] Focus Tracker (component tracking last focus change and cause)
« Reply #27 on: August 27, 2017, 10:34:54 pm »
@dot.not: Thanks for sharing...  :)

Quote
...had any support for a mail-delivery-service. ...
Just do a little flyby...

Quote
..location where i spent my last vacation....
You never told me this ...

dot.not

  • New Member
  • *
  • Posts: 22
  • The answer is 42
Re: [Solved] Focus Tracker (component tracking focus changes and cause)
« Reply #28 on: September 01, 2017, 09:12:02 pm »
So I was thinking:
i.    The new limitation I added.  ( no. 8 )
ii.   There's a main message loop for each application.
iii.  Would it be possible to intercept messages at a [deeper/more centralised] level instead of hooking into each control?

And btw, where is the application main message loop to be found?
Is that widget domain?


P.S.  @RAW:  My pleasure

There's 10 kinds of people

balazsszekely

  • Guest
Re: [Solved] Focus Tracker (component tracking focus changes and cause)
« Reply #29 on: September 01, 2017, 09:50:22 pm »
@dot.not
I believe that you're more than skilled enough to figure it out yourself. Just put a break point somewhere in your "TrackedWindowProc" method. Then use the "Call stack" window(Ctrl + Alt + S). First it will look like you opened pandora's box, but then you will be fine.

 

TinyPortal © 2005-2018