View Issue Details

IDProjectCategoryView StatusLast Update
0037145LazarusLCLpublic2020-08-28 07:13
ReporterJoeny Ang Assigned ToJuha Manninen  
PrioritynormalSeverityminorReproducibilityalways
Status closedResolutionreopened 
Product Version2.1 (SVN) 
Summary0037145: [Patch] TTreeView mouse selection
DescriptionThe following are addressed by this patch:

1. Multiselect with Shift, no msSiblingOnly; select a first child then select the parent (eg. on the attached example: select Item4, then Item0), this will select everything from Item4 to the last item
2. Setting MultiSelect to False does not clear selection
3. GetNodeAt(X,Y) should honor RowSelect state. When RowSelect=False, it will only return a node if X/Y falls inside the rect of the node and its icon.
4. Added tvoEmptySpaceUnselect option; clicking on white space will clear selection; when RowSelect=True, selection will only be cleared when the space after the last item is clicked.
TagsNo tags attached.
Fixed in Revisionr63232, r63235
LazTarget-
Widgetset
Attached Files

Activities

Joeny Ang

2020-05-26 08:34

reporter  

ttreeview-mouse-selection-fix.patch (5,333 bytes)   
--- lcl/comctrls.pp
+++ lcl/comctrls.pp
@@ -3287,7 +3287,8 @@
     tvoShowSeparators,
     tvoToolTips,
     tvoNoDoubleClickExpand,
-    tvoThemedDraw
+    tvoThemedDraw,
+    tvoEmptySpaceUnselect
     );
   TTreeViewOptions = set of TTreeViewOption;
 
@@ -3425,7 +3426,8 @@
     function IsStoredBackgroundColor: Boolean;
     procedure HintMouseLeave(Sender: TObject);
     procedure ImageListChange(Sender: TObject);
-    function MouseDownNode(X, Y: Integer): TTreeNode;
+    function MouseDownNode(X, Y: Integer; const fIncludeExpandSign: Boolean =
+      False): TTreeNode;
     procedure OnChangeTimer(Sender: TObject);
     procedure SetAutoExpand(Value: Boolean);
     procedure SetBackgroundColor(Value: TColor);
--- lcl/include/treeview.inc
+++ lcl/include/treeview.inc
@@ -2797,7 +2797,7 @@
     end;
 
     //select again
-    bGoNext := (FirstNode.Index <= Node.Index);
+    bGoNext := (FirstNode.AbsoluteIndex <= Node.AbsoluteIndex);
     I := FirstNode;
     I.MultiSelected:=True;
     while (I<>Node) do
@@ -3861,6 +3861,7 @@
 begin
   if MultiSelect <> AValue then
   begin
+    ClearSelection;
     if AValue then
       Include(FOptions,tvoAllowMultiselect)
     else
@@ -3974,9 +3975,13 @@
 
 function TCustomTreeView.GetNodeAt(X, Y: Integer): TTreeNode;
 begin
-  if (X >= BorderWidth) and (X < ClientWidth - BorderWidth) then
-    Result := GetNodeAtY(Y)
-  else
+  Result := GetNodeAtY(Y);
+  if not Assigned(Result) then
+    Exit;
+  if (not (tvoRowSelect in Options) and
+      ((X < Result.DisplayStateIconLeft) or (X >= Result.DisplayTextRight))) or
+     ((tvoRowSelect in Options) and     // row select
+      ((X < BorderWidth) or (X >= ClientWidth - BorderWidth))) then
     Result := nil;
 end;
 
@@ -5574,9 +5579,22 @@
   Result := FIndent >= 0;
 end;
 
-function TCustomTreeView.MouseDownNode(X, Y: Integer): TTreeNode;
-begin
-  Result := GetNodeAt(X, Y);
+function TCustomTreeView.MouseDownNode(X, Y: Integer; const fIncludeExpandSign:
+  Boolean): TTreeNode;
+begin
+  if fIncludeExpandSign then
+  begin
+    Result := GetNodeAtY(Y);
+    if Assigned(Result) then
+      if (not (tvoRowSelect in Options) and   // need to include DisplayExpandSignLeft
+          ((X < Result.DisplayExpandSignLeft) or (X >= Result.DisplayTextRight))) or
+         ((tvoRowSelect in Options) and     // row select
+          ((X < BorderWidth) or (X >= ClientWidth - BorderWidth))) then
+        Result := nil;
+  end
+  else
+    Result := GetNodeAt(X, Y);
+
   // Update the NodeSelected flag.
   FMouseDownNodeSelected := Assigned(Result) and
     (Result.Selected or ((tvoAllowMultiselect in Options) and Result.MultiSelected));
@@ -5601,16 +5619,25 @@
   if (Button = mbRight) and RightClickSelect and//right click
      (([ssDouble, ssTriple, ssQuad] * Shift) = []) and//single or first of a multi click
      not AllowMultiSelectWithCtrl(Shift) and//only when CTRL is not pressed
-     (CursorNode <> nil) and
-     (LogicalX >= CursorNode.DisplayStateIconLeft)//only after expand sign
+     (CursorNode <> nil)
   then
   begin
+    if not (tvoRowSelect in Options) and
+       (tvoEmptySpaceUnselect in Options) and
+       (LogicalX >= CursorNode.DisplayStateIconLeft) and
+       (LogicalX > CursorNode.DisplayTextRight) then
+      ClearSelection
+    else
     if not (tvoAllowMultiselect in Options) then
       Selected := CursorNode
     else
     if not FMouseDownNodeSelected then
       Items.SelectOnlyThis(CursorNode);
-  end;
+  end
+  else // empty space below last node
+  if (Button = mbRight) and RightClickSelect and (CursorNode = nil) and
+     (tvoEmptySpaceUnselect in Options) then
+    ClearSelection;
 
   if not Focused and CanFocus then
     SetFocus;
@@ -5618,7 +5645,7 @@
   inherited MouseDown(Button, Shift, X, Y);
 
   //CursorNode must be reassigned again - e.g. in OnMouseDown the node can be deleted or moved.
-  CursorNode := MouseDownNode(X, Y);
+  CursorNode := MouseDownNode(LogicalX, Y, True);
 
   //Flag is used for DblClick/TripleClick/QuadClick, so set it before testing ShiftState
   FMouseDownOnFoldingSign :=
@@ -5634,7 +5661,8 @@
     if FMouseDownOnFoldingSign then
       // mousedown occured on expand sign -> expand/collapse
       CursorNode.Expanded := not CursorNode.Expanded
-    else if LogicalX >= CursorNode.DisplayStateIconLeft then
+    else if (LogicalX >= CursorNode.DisplayStateIconLeft) or
+            (tvoRowSelect in Options) then
     begin
       // mousedown occured in text or icon -> select node and begin drag operation
       {$IFDEF VerboseDrag}
@@ -5671,12 +5699,18 @@
             Include(FStates, tvsSingleSelectOnMouseUp);
         end;
       end;
-    end;
+    end
+    else if tvoEmptySpaceUnselect in Options then
+      ClearSelection;
   end
   else// multi click
   if not (tvoNoDoubleClickExpand in Options) and (ssDouble in Shift)
   and (Button = mbLeft) and (CursorNode<>nil) then
-    CursorNode.Expanded := not CursorNode.Expanded;
+    CursorNode.Expanded := not CursorNode.Expanded
+  else  // empty space below last node
+  if (Button = mbLeft) and (CursorNode = nil) and (tvoEmptySpaceUnselect in Options) and
+     not AllowMultiSelectWithShift(Shift) and not AllowMultiSelectWithCtrl(Shift) then
+    ClearSelection;
 end;
 
 procedure TCustomTreeView.MouseMove(Shift: TShiftState; X, Y: Integer);

ttreeview-test-01.zip (115,014 bytes)

Juha Manninen

2020-05-27 01:27

developer   ~0123088

Looks good. I applied the patch after some refactoring and simplification.
You had duplicated the GetNodeAt() code in MouseDownNode(). I removed the latter completely.
Please test everybody.

Joeny Ang

2020-05-27 04:19

reporter   ~0123092

Hi, thanks for applying the patch.

Need to re-open this. When RowSelect=False, clicking on the expand icon does not work. The duplicated code was supposed to fix this hehe :)

Juha Manninen

2020-05-27 09:38

developer   ~0123095

Last edited: 2020-05-27 09:39

View 2 revisions

Hmmm...
I thought I did pure refactoring but apparently not. I must study the code some more.

Juha Manninen

2020-05-27 14:12

developer   ~0123100

Yes, there was DisplayStateIconLeft versus DisplayExpandSignLeft. Somehow I missed that. Sorry. :)
I fixed it and refactored a little. I hope it is OK.
Please test.

Joeny Ang

2020-05-28 07:39

reporter   ~0123105

Nice. Everything's good. Thanks :)

Issue History

Date Modified Username Field Change
2020-05-26 08:34 Joeny Ang New Issue
2020-05-26 08:34 Joeny Ang File Added: ttreeview-mouse-selection-fix.patch
2020-05-26 08:34 Joeny Ang File Added: ttreeview-test-01.zip
2020-05-27 01:23 Juha Manninen Assigned To => Juha Manninen
2020-05-27 01:23 Juha Manninen Status new => assigned
2020-05-27 01:27 Juha Manninen Status assigned => resolved
2020-05-27 01:27 Juha Manninen Resolution open => fixed
2020-05-27 01:27 Juha Manninen Fixed in Revision => r63232
2020-05-27 01:27 Juha Manninen LazTarget => -
2020-05-27 01:27 Juha Manninen Note Added: 0123088
2020-05-27 04:19 Joeny Ang Status resolved => assigned
2020-05-27 04:19 Joeny Ang Resolution fixed => reopened
2020-05-27 04:19 Joeny Ang Note Added: 0123092
2020-05-27 09:38 Juha Manninen Note Added: 0123095
2020-05-27 09:39 Juha Manninen Note Edited: 0123095 View Revisions
2020-05-27 14:12 Juha Manninen Status assigned => resolved
2020-05-27 14:12 Juha Manninen Fixed in Revision r63232 => r63232, r63235
2020-05-27 14:12 Juha Manninen Note Added: 0123100
2020-05-28 07:39 Joeny Ang Note Added: 0123105
2020-08-28 07:13 Joeny Ang Status resolved => closed