The hit test for the expand/collapse button is "htOnButton". But that itself would be too easy...
One problem is that the public method GetNodeAt ignores clicks left of the node text. Fortunately there is another "GetNode*" method, GetNodeAtY, which does not do this; however, it is protected... To access it nevertheless, write a class helper:
type
TShellTreeViewHelper = class helper for TShellTreeView
public
function MyGetNodeAtY(Y: Integer): TTreeNode;
end;
function TShellTreeViewHelper.MyGetNodeAtY(Y: Integer): TTreeNode;
begin
Result := GetNodeAtY(Y);
end;
The other problem is that the OnClick event removes HitTest results again, so that the "htOnButton" cannot be detected here. Therefore, put the code into the OnMouseDown or OnMouseUp events. It turned out that OnMouseDown is too early because the regular click is still handled later and this causes some confusion. I got the best results when I put your code into the OnMouseUp event:
procedure TForm1.ShellTreeView1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
HT : THitTests;
begin
HT:=ShellTreeView1.GetHitTestInfoAt(X, Y);
//Caption := SetToString(PTypeInfo(TypeInfo(HT)), Integer(HT), True); // Activate this line to see which hit tests were detected. Requires "TypInfo" in "uses".
if (HT * [htOnButton, htOnIcon, htOnLabel] <> []) then // React on clicks on expand/collapse, icon or label
ShellTreeView1.TopItem:=ShellTreeView1.MyGetNodeAtY(Y); // this accesses the protected GetNoteAtY via the class helper
end;
Side-note: I personally consider automatic scrolling the clicked node to the top a very annoying user interface because I always have to move the mouse for quite some distance when I want to open child nodes. And when the text click even moves the focused node to the top I cannot see the nodes above the focused node any more.