LCL: Improve TTreeView mouse selection. Issue #37145

git-svn-id: branches/fixes_2_0@63475 -
This commit is contained in:
mattias 2020-06-28 17:01:03 +00:00
parent 2d62ff2977
commit a4206958b2
2 changed files with 65 additions and 28 deletions

View File

@ -3279,7 +3279,8 @@ type
tvoShowSeparators, tvoShowSeparators,
tvoToolTips, tvoToolTips,
tvoNoDoubleClickExpand, tvoNoDoubleClickExpand,
tvoThemedDraw tvoThemedDraw,
tvoEmptySpaceUnselect
); );
TTreeViewOptions = set of TTreeViewOption; TTreeViewOptions = set of TTreeViewOption;
@ -3332,7 +3333,6 @@ type
FMaxLvl: integer; // maximum level of all nodes FMaxLvl: integer; // maximum level of all nodes
FMaxRight: integer; // maximum text width of all nodes (needed for horizontal scrolling) FMaxRight: integer; // maximum text width of all nodes (needed for horizontal scrolling)
FMouseDownPos: TPoint; FMouseDownPos: TPoint;
FMouseDownNodeSelected: Boolean;
FMouseDownOnFoldingSign: Boolean; FMouseDownOnFoldingSign: Boolean;
FMultiSelectStyle: TMultiSelectStyle; FMultiSelectStyle: TMultiSelectStyle;
FHotTrackColor: TColor; FHotTrackColor: TColor;
@ -3416,7 +3416,7 @@ type
function IsStoredBackgroundColor: Boolean; function IsStoredBackgroundColor: Boolean;
procedure HintMouseLeave(Sender: TObject); procedure HintMouseLeave(Sender: TObject);
procedure ImageListChange(Sender: TObject); procedure ImageListChange(Sender: TObject);
function MouseDownNode(X, Y: Integer): TTreeNode; function NodeIsSelected(aNode: TTreeNode): Boolean;
procedure OnChangeTimer(Sender: TObject); procedure OnChangeTimer(Sender: TObject);
procedure SetAutoExpand(Value: Boolean); procedure SetAutoExpand(Value: Boolean);
procedure SetBackgroundColor(Value: TColor); procedure SetBackgroundColor(Value: TColor);
@ -3624,6 +3624,7 @@ type
procedure EraseBackground(DC: HDC); override; procedure EraseBackground(DC: HDC); override;
function GetHitTestInfoAt(X, Y: Integer): THitTests; function GetHitTestInfoAt(X, Y: Integer): THitTests;
function GetNodeAt(X, Y: Integer): TTreeNode; function GetNodeAt(X, Y: Integer): TTreeNode;
function GetNodeWithExpandSignAt(X, Y: Integer): TTreeNode;
procedure GetInsertMarkAt(X, Y: Integer; out AnInsertMarkNode: TTreeNode; procedure GetInsertMarkAt(X, Y: Integer; out AnInsertMarkNode: TTreeNode;
out AnInsertMarkType: TTreeViewInsertMarkType); out AnInsertMarkType: TTreeViewInsertMarkType);
procedure SetInsertMark(AnInsertMarkNode: TTreeNode; procedure SetInsertMark(AnInsertMarkNode: TTreeNode;

View File

@ -2793,7 +2793,7 @@ begin
end; end;
//select again //select again
bGoNext := (FirstNode.Index <= Node.Index); bGoNext := (FirstNode.AbsoluteIndex <= Node.AbsoluteIndex);
I := FirstNode; I := FirstNode;
I.MultiSelected:=True; I.MultiSelected:=True;
while (I<>Node) do while (I<>Node) do
@ -3856,6 +3856,7 @@ procedure TCustomTreeView.SetMultiSelect(const AValue: Boolean);
begin begin
if MultiSelect <> AValue then if MultiSelect <> AValue then
begin begin
ClearSelection;
if AValue then if AValue then
Include(FOptions,tvoAllowMultiselect) Include(FOptions,tvoAllowMultiselect)
else else
@ -3968,16 +3969,35 @@ begin
end; end;
function TCustomTreeView.GetNodeAt(X, Y: Integer): TTreeNode; function TCustomTreeView.GetNodeAt(X, Y: Integer): TTreeNode;
var
b: Boolean;
begin begin
if (X >= BorderWidth) and (X < ClientWidth - BorderWidth) then Result := GetNodeAtY(Y);
Result := GetNodeAtY(Y) if Result = nil then Exit;
if tvoRowSelect in Options then // row select
b := (X < BorderWidth) or (X >= ClientWidth - BorderWidth)
else else
b := (X < Result.DisplayStateIconLeft) or (X >= Result.DisplayTextRight);
if b then
Result := nil;
end;
function TCustomTreeView.GetNodeWithExpandSignAt(X, Y: Integer): TTreeNode;
var
b: Boolean;
begin
Result := GetNodeAtY(Y);
if Result = nil then Exit;
if tvoRowSelect in Options then // row select
b := (X < BorderWidth) or (X >= ClientWidth - BorderWidth)
else // need to include DisplayExpandSignLeft
b := (X < Result.DisplayExpandSignLeft) or (X >= Result.DisplayTextRight);
if b then
Result := nil; Result := nil;
end; end;
procedure TCustomTreeView.GetInsertMarkAt(X, Y: Integer; procedure TCustomTreeView.GetInsertMarkAt(X, Y: Integer;
out AnInsertMarkNode: TTreeNode; out AnInsertMarkType: TTreeViewInsertMarkType out AnInsertMarkNode: TTreeNode; out AnInsertMarkType: TTreeViewInsertMarkType);
);
var var
ANode: TTreeNode; ANode: TTreeNode;
NodeRect: TRect; NodeRect: TRect;
@ -5558,18 +5578,17 @@ begin
Result := FIndent >= 0; Result := FIndent >= 0;
end; end;
function TCustomTreeView.MouseDownNode(X, Y: Integer): TTreeNode; function TCustomTreeView.NodeIsSelected(aNode: TTreeNode): Boolean;
begin begin
Result := GetNodeAt(X, Y); Result := Assigned(aNode) and
// Update the NodeSelected flag. (aNode.Selected or ((tvoAllowMultiselect in Options) and aNode.MultiSelected));
FMouseDownNodeSelected := Assigned(Result) and
(Result.Selected or ((tvoAllowMultiselect in Options) and Result.MultiSelected));
end; end;
procedure TCustomTreeView.MouseDown(Button: TMouseButton; Shift: TShiftState; procedure TCustomTreeView.MouseDown(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer); X, Y: Integer);
var var
CursorNode: TTreeNode; CursorNode: TTreeNode;
CursorNdSelected: Boolean;
LogicalX: Integer; LogicalX: Integer;
begin begin
{$IFDEF VerboseDrag} {$IFDEF VerboseDrag}
@ -5578,23 +5597,33 @@ begin
FMouseDownPos := Point(X,Y); FMouseDownPos := Point(X,Y);
FStates:=FStates-[tvsEditOnMouseUp,tvsSingleSelectOnMouseUp]; FStates:=FStates-[tvsEditOnMouseUp,tvsSingleSelectOnMouseUp];
CursorNode := MouseDownNode(X, Y); CursorNode := GetNodeAt(X, Y);
CursorNdSelected := NodeIsSelected(CursorNode);
LogicalX:=X; LogicalX:=X;
//change selection on right click //change selection on right click
if (Button = mbRight) and RightClickSelect and//right click if (Button = mbRight) and RightClickSelect and //right click
(([ssDouble, ssTriple, ssQuad] * Shift) = []) and//single or first of a multi click (([ssDouble, ssTriple, ssQuad] * Shift) = []) and //single or first of a multi click
not AllowMultiSelectWithCtrl(Shift) and//only when CTRL is not pressed not AllowMultiSelectWithCtrl(Shift) and //only when CTRL is not pressed
(CursorNode <> nil) and (CursorNode <> nil)
(LogicalX >= CursorNode.DisplayStateIconLeft)//only after expand sign
then then
begin 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 if not (tvoAllowMultiselect in Options) then
Selected := CursorNode Selected := CursorNode
else else
if not FMouseDownNodeSelected then if not CursorNdSelected then
Items.SelectOnlyThis(CursorNode); 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 if not Focused and CanFocus then
SetFocus; SetFocus;
@ -5602,7 +5631,8 @@ begin
inherited MouseDown(Button, Shift, X, Y); inherited MouseDown(Button, Shift, X, Y);
//CursorNode must be reassigned again - e.g. in OnMouseDown the node can be deleted or moved. //CursorNode must be reassigned again - e.g. in OnMouseDown the node can be deleted or moved.
CursorNode := MouseDownNode(X, Y); CursorNode := GetNodeWithExpandSignAt(LogicalX, Y);
CursorNdSelected := NodeIsSelected(CursorNode);
//Flag is used for DblClick/TripleClick/QuadClick, so set it before testing ShiftState //Flag is used for DblClick/TripleClick/QuadClick, so set it before testing ShiftState
FMouseDownOnFoldingSign := FMouseDownOnFoldingSign :=
@ -5611,14 +5641,14 @@ begin
(LogicalX < CursorNode.DisplayExpandSignRight); (LogicalX < CursorNode.DisplayExpandSignRight);
//change selection on left click //change selection on left click
if (Button = mbLeft) and//left click if (Button = mbLeft) and //left click
(([ssDouble, ssTriple, ssQuad] * Shift) = []) and//single or first of a multi click (([ssDouble, ssTriple, ssQuad] * Shift) = []) and //single or first of a multi click
(CursorNode <> nil) then (CursorNode <> nil) then
begin begin
if FMouseDownOnFoldingSign then if FMouseDownOnFoldingSign then
// mousedown occured on expand sign -> expand/collapse // mousedown occured on expand sign -> expand/collapse
CursorNode.Expanded := not CursorNode.Expanded CursorNode.Expanded := not CursorNode.Expanded
else if LogicalX >= CursorNode.DisplayStateIconLeft then else if (LogicalX >= CursorNode.DisplayStateIconLeft) or (tvoRowSelect in Options) then
begin begin
// mousedown occured in text or icon -> select node and begin drag operation // mousedown occured in text or icon -> select node and begin drag operation
{$IFDEF VerboseDrag} {$IFDEF VerboseDrag}
@ -5649,18 +5679,24 @@ begin
end end
else else
begin begin
if not FMouseDownNodeSelected then if not CursorNdSelected then
Items.SelectOnlyThis(CursorNode) Items.SelectOnlyThis(CursorNode)
else else
Include(FStates, tvsSingleSelectOnMouseUp); Include(FStates, tvsSingleSelectOnMouseUp);
end; end;
end; end;
end; end
else if tvoEmptySpaceUnselect in Options then
ClearSelection;
end end
else// multi click else// multi click
if not (tvoNoDoubleClickExpand in Options) and (ssDouble in Shift) if not (tvoNoDoubleClickExpand in Options) and (ssDouble in Shift)
and (Button = mbLeft) and (CursorNode<>nil) then 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; end;
procedure TCustomTreeView.MouseMove(Shift: TShiftState; X, Y: Integer); procedure TCustomTreeView.MouseMove(Shift: TShiftState; X, Y: Integer);