3

How can I detect if the user enter into a component by tab key or by mouse click?

UPDATE 1

In fact is about a TVirtualStringTree which depending by the way is getting focused, it open an custom editor focused on one or another column.

UPDATE 2

Check-out the code below.

procedure TForm1.Tree1Click(Sender: TObject);
var
  Tree: TVirtualStringTree;
  Click: THitInfo;
  HitNode: PVirtualNode;
  HitColumn: TColumnIndex;
  col: Integer;
begin
  Tree:= Sender as TVirtualStringTree;
  Tree.GetHitTestInfoAt(Mouse.CursorPos.X-Tree.ClientOrigin.X, Mouse.CursorPos.Y-Tree.ClientOrigin.Y, True, Click);

  HitNode:= Click.HitNode;
  if not Assigned(Click.HitNode) and Assigned(Tree.FocusedNode) then
    HitNode:= Tree.FocusedNode;

  HitColumn:= Click.HitColumn;

  //get first visible and editable column
  if (HitColumn <= NoColumn) or
     ((HitColumn > NoColumn) and
      (not (coVisible in Tree.Header.Columns.Items[HitColumn].Options) or
       not (coEditable in Tree.Header.Columns.Items[HitColumn].Options))) then
    if Tree.Header.Columns.Count > 0 then
      for col := 0 to Tree.Header.Columns.Count - 1 do
        if (coVisible in Tree.Header.Columns.Items[col].Options) and
           (coEditable in Tree.Header.Columns.Items[col].Options) then
          begin
            HitColumn:= col;
            Break;
          end;

  if Assigned(HitNode) and (HitColumn > NoColumn) then
    {if (Tree.IsEditing and (HitNode <> Tree.FocusedNode)) or
       ((not Tree.IsEditing) and (HitNode = Tree.FocusedNode)) then}
      Tree.EditNode(HitNode,HitColumn);
end;

procedure TForm1.Tree1Enter(Sender: TObject);
var
  Tree: TVirtualStringTree;
  Click: THitInfo;
  HitNode: PVirtualNode;
  HitColumn: TColumnIndex;
  col: Integer;
begin
  Tree:= Sender as TVirtualStringTree;

  HitNode:= Tree.FocusedNode;

  if not Assigned(Tree.FocusedNode) then
    HitNode:= Tree.GetFirstVisible;

  HitColumn:= NoColumn;

  //get first visible and editable column
  if Tree.Header.Columns.Count > 0 then
    for col := 0 to Tree.Header.Columns.Count - 1 do
      if (coVisible in Tree.Header.Columns.Items[col].Options) and
         (coEditable in Tree.Header.Columns.Items[col].Options) then
        begin
          HitColumn:= col;
          Break;
        end;

  if Assigned(HitNode) and (HitColumn > NoColumn) then
    Tree.EditNode(HitNode,HitColumn);
end;

What I would like to do is:

  • to edit the first column of the focused node if the user enter by Tab key or
  • to edit the corresponding hited column if enter by mouse click

If I click on component, OnEnter is triggered firstly and after that OnClick, so the problem is that edit node it gets trigged 2 times.

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
REALSOFO
  • 852
  • 9
  • 37
  • 1
    You are expected not to care. After all there are many other ways of doing this. – David Heffernan Aug 24 '16 at 08:43
  • intercept all the mouse clicks and if the last click before gaining focus was to this component then it probably was it. However you really should not care. What if I just restore a minimized window - that is yet another way to gain focus. What if I minimize window of another app - the 4th way to get focus. What if I unlock the Windows session or connect to terminal service session running? and so on, and so forth. – Arioch 'The Aug 24 '16 at 08:46
  • see my update... I was afraid I'm too general. – REALSOFO Aug 24 '16 at 09:01
  • This feels all wrong to me. I fear you are making a mistake. Do you have full appreciation of all the different ways that users can interact with your program. Pressing the tab key, and using a mouse are not the only ways to provide input. – David Heffernan Aug 24 '16 at 09:20
  • see update 2. in fact the editor works but i don't like that the edit node command is triggered 2 times. – REALSOFO Aug 24 '16 at 12:02
  • Now i realized that there is another problem. When the editor get closed the OnEnter of the Tree is triggered again... – REALSOFO Aug 24 '16 at 13:12

2 Answers2

3

In order to know whether the focus was gained by using tab or by mouse-click you'll need to do some detective work.

  1. Here's the code that ensures focus when pressing tab.
procedure TBaseVirtualTree.WMKeyUp(var Message: TWMKeyUp);

begin
  inherited;

  case Message.CharCode of
    VK_SPACE:
     .... [snip] ....
     VK_TAB:
       //This method causes a flurry of event handlers to be called.
       EnsureNodeFocused(); 
  end;
end;
  1. VTV has two events for focus changes: OnFocusChanged and OnFocusChanging in addition to the standard OnEnter and OnExit events.
    I'm have no idea which of these best fits your needs, you'll have to experiment.

Let's assume the focus is always gained using the mouse, unless we can prove it was gained using tab. This is a bit of a shaky assumption but never mind.

First we use an interposer to override the WMKeyUp message handler.
WMKeyUp is private, but luckily message handlers can always be overriden, even if they are private.

The interposer
You can use a trick called an interposer to redefine a VST to suit our needs.
Because of scoping rules the 'improved' TVST takes the place of the default VST. This does not interfere with streaming or form creation or anything. It just works.

type
  TVirtualStringTree = class(VirtualTrees.TVirtualStringTree)
  private
    FTabPressed: boolean;
  protected
    procedure WMKeyUp(var Message: TWMKeyUp); message WM_KEYUP;
    property TabPressed: boolean read FTabPressed;
  end;

  TForm56 = class(TForm)
    VirtualStringTree1: TVirtualStringTree;
    ....


procedure TVirtualStringTree.WMKeyUp(var Message: TWMKeyUp);
begin
  case Message.CharCode of
    VK_TAB: FTabPressed:= true;
    else FTabPressed:= false;
  end; {case}
  inherited;
  FTabPressed:= false;
end;

The focus event handlers
Use one of these two three event handlers.

//use the standard onEnter....
procedure TForm56.VirtualStringTree1Enter(Sender: TObject);
begin
    if VirtualStringTree1.TabPressed then .....
  else ....
end;

//... or use this event, or...
procedure TForm56.VirtualStringTree1FocusChanged(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Column: TColumnIndex);
begin
  if VirtualStringTree1.TabPressed then .....
  else ....
end;

//.... this event if you want to mess with the focus.
procedure TForm56.VirtualStringTree1FocusChanging(Sender: TBaseVirtualTree;
  OldNode, NewNode: PVirtualNode; OldColumn, NewColumn: TColumnIndex;
  var Allowed: Boolean);
begin
  Allowed:= VirtualStringTree1.TabPressed; //just a silly example.
end;
Community
  • 1
  • 1
Johan
  • 74,508
  • 24
  • 191
  • 319
2

Because focus achieved by clicking would always generate a mousedown event before an onEnter event you can set an event in the mousedown saying 'gMousedown' := true then in the OnEnter event you can check if mousedown. Do not forget to reset the gMousedown to false in the onMouseUp event.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490