mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-28 07:03:46 +02:00

Prettified TStrings property editor. Memo now has automatic scrollbars (not fully working), WordWrap and Scrollbars property Removed saving of old combo text (it broke things and is not needed). Cleanups. git-svn-id: trunk@3283 -
4442 lines
122 KiB
PHP
4442 lines
122 KiB
PHP
// included by comctrls.pp
|
|
|
|
{******************************************************************************
|
|
TTreeView
|
|
******************************************************************************
|
|
|
|
Author: Mattias Gaertner
|
|
|
|
*****************************************************************************
|
|
* *
|
|
* This file is part of the Lazarus Component Library (LCL) *
|
|
* *
|
|
* See the file COPYING.LCL, included in this distribution, *
|
|
* for details about the copyright. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
|
|
* *
|
|
*****************************************************************************
|
|
|
|
Abstract:
|
|
TTreeView for LCL
|
|
|
|
ToDo:
|
|
- Multiselection
|
|
- custom draw
|
|
- Drag&Drop
|
|
- Editing
|
|
- Columns
|
|
}
|
|
|
|
|
|
{ $DEFINE TREEVIEW_DEBUG}
|
|
|
|
const
|
|
TTreeNodeStreamVersion : word = 1;
|
|
|
|
// maximum scroll range
|
|
MAX_SCROLL = 32767;
|
|
|
|
|
|
|
|
procedure TreeViewError(const Msg: string);
|
|
begin
|
|
raise ETreeViewError.Create(Msg);
|
|
end;
|
|
|
|
{procedure TreeViewErrorFmt(const Msg: string; Format: array of const);
|
|
begin
|
|
raise ETreeViewError.CreateFmt(Msg, Format);
|
|
end;}
|
|
|
|
procedure TreeNodeError(const Msg: string);
|
|
begin
|
|
raise ETreeNodeError.Create(Msg);
|
|
end;
|
|
|
|
{procedure TreeNodeErrorFmt(const Msg: string; Format: array of const);
|
|
begin
|
|
raise ETreeNodeError.CreateFmt(Msg, Format);
|
|
end;}
|
|
|
|
function IndexOfNodeAtTop(NodeArray: TTreeNodeArray; Count, y: integer): integer;
|
|
// NodeArray must be sorted via Top
|
|
// returns index of Node with Node.Top <= y < Node[+1].Top
|
|
var l, m, r: integer;
|
|
begin
|
|
if (Count=0) or (NodeArray=nil) then exit(-1);
|
|
l:=0;
|
|
r:=Count-1;
|
|
while (l<=r) do begin
|
|
m:=(l+r) shr 1;
|
|
//writeln(':0 [IndexOfNodeAtTop] m=',m,' y=',y,' ',NodeArray[m].Text,' NodeArray[m].Top=',NodeArray[m].Top,' NodeArray[m].BottomExpanded=',NodeArray[m].BottomExpanded);
|
|
if NodeArray[m].Top>y then
|
|
r:=m-1
|
|
else if NodeArray[m].BottomExpanded<=y then
|
|
l:=m+1
|
|
else
|
|
exit(m);
|
|
end;
|
|
Result:=-1;
|
|
end;
|
|
|
|
|
|
{ TTreeNode }
|
|
|
|
function TTreeNode.DefaultTreeViewSort(Node1, Node2: TTreeNode): Integer;
|
|
begin
|
|
if (Node1.TreeView<>nil) and Assigned(Node1.TreeView.OnCompare) then
|
|
Node1.TreeView.OnCompare(Node1.TreeView,Node1, Node2, Result)
|
|
else
|
|
Result := AnsiCompareStr(Node1.Text,Node2.Text);
|
|
end;
|
|
|
|
constructor TTreeNode.Create(AnOwner: TTreeNodes);
|
|
begin
|
|
inherited Create;
|
|
FOverlayIndex := -1;
|
|
FStateIndex := -1;
|
|
FStates := [];
|
|
FOwner := AnOwner;
|
|
FSubTreeCount:=1;
|
|
if Owner<>nil then inc(Owner.FCount);
|
|
end;
|
|
|
|
destructor TTreeNode.Destroy;
|
|
//var
|
|
// Node: TTreeNode;
|
|
// CheckValue: Integer;
|
|
begin
|
|
{$IFDEF TREEVIEW_DEBUG}
|
|
writeln('[TTreeNode.Destroy] Self=',HexStr(Cardinal(Self),8),' Self.Text=',Text);
|
|
{$ENDIF}
|
|
FDeleting := True;
|
|
HasChildren := false;
|
|
Unbind;
|
|
if Owner<>nil then dec(Owner.FCount);
|
|
{if Owner.Owner.FLastDropTarget = Self then
|
|
Owner.Owner.FLastDropTarget := nil;
|
|
Node := Parent;
|
|
if (Node <> nil) and (not Node.Deleting) then begin
|
|
if Node.IndexOf(Self) <> -1 then
|
|
CheckValue := 1
|
|
else
|
|
CheckValue := 0;
|
|
if Node.CompareCount(CheckValue) then begin
|
|
Expanded := False;
|
|
Node.HasChildren := False; // delete all childs
|
|
end;
|
|
end;
|
|
if ItemId <> nil then TreeView_DeleteItem(Handle, ItemId);}
|
|
Data := nil;
|
|
if FItems<>nil then begin
|
|
FreeMem(FItems);
|
|
FItems:=nil;
|
|
end;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TTreeNode.GetHandle: THandle;
|
|
begin
|
|
if TreeView<>nil then
|
|
Result := TreeView.Handle
|
|
else
|
|
Result := 0;
|
|
end;
|
|
|
|
function TTreeNode.GetTreeNodes: TTreeNodes;
|
|
begin
|
|
if (Owner<>nil) and (Owner is TTreeNodes) then
|
|
Result:=TTreeNodes(Owner)
|
|
else
|
|
Result:=nil;
|
|
end;
|
|
|
|
function TTreeNode.GetTreeView: TCustomTreeView;
|
|
begin
|
|
if Owner<>nil then
|
|
Result := Owner.Owner
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
function TTreeNode.GetTop: integer;
|
|
begin
|
|
if TreeView<>nil then
|
|
TreeView.UpdateAllTops;
|
|
Result:=FTop;
|
|
end;
|
|
|
|
function TTreeNode.HasAsParent(AValue: TTreeNode): Boolean;
|
|
begin
|
|
if AValue<>nil then begin
|
|
if Parent=nil then Result := False
|
|
else if Parent=AValue then Result := True
|
|
else Result := Parent.HasAsParent(AValue);
|
|
end
|
|
else Result := True;
|
|
end;
|
|
|
|
procedure TTreeNode.SetText(const S: string);
|
|
//var Item: TTVItem;
|
|
begin
|
|
if S=FText then exit;
|
|
FText := S;
|
|
if TreeView=nil then exit;
|
|
// ToDo:
|
|
{
|
|
with Item do begin
|
|
mask := TVIF_TEXT;
|
|
hItem := ItemId;
|
|
pszText := LPSTR_TEXTCALLBACK;
|
|
end;
|
|
TreeView_SetItem(Handle, Item);
|
|
}
|
|
Include(TreeView.FStates,tvsMaxRightNeedsUpdate);
|
|
if (TreeView.SortType in [stText, stBoth]) and FInTree then begin
|
|
if (Parent <> nil) then Parent.AlphaSort
|
|
else TreeView.AlphaSort;
|
|
end;
|
|
Update;
|
|
end;
|
|
|
|
procedure TTreeNode.SetData(AValue: Pointer);
|
|
begin
|
|
if FData=AValue then exit;
|
|
FData := AValue;
|
|
if (TreeView<>nil)
|
|
and (TreeView.SortType in [stData, stBoth]) and Assigned(TreeView.OnCompare)
|
|
and (not Deleting) and FInTree then
|
|
begin
|
|
if Parent <> nil then Parent.AlphaSort
|
|
else TreeView.AlphaSort;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNode.GetState(NodeState: TNodeState): Boolean;
|
|
//var Item: TTVItem;
|
|
begin
|
|
// ToDo:
|
|
Result:=NodeState in FStates;
|
|
{
|
|
Result := False;
|
|
with Item do begin
|
|
mask := TVIF_STATE;
|
|
hItem := ItemId;
|
|
if TreeView_GetItem(Handle, Item) then
|
|
case NodeState of
|
|
nsCut: Result := (state and TVIS_CUT) <> 0;
|
|
nsFocused: Result := (state and TVIS_FOCUSED) <> 0;
|
|
nsSelected: Result := (state and TVIS_SELECTED) <> 0;
|
|
nsExpanded: Result := (state and TVIS_EXPANDED) <> 0;
|
|
nsDropHilited: Result := (state and TVIS_DROPHILITED) <> 0;
|
|
end;
|
|
end;
|
|
}
|
|
end;
|
|
|
|
procedure TTreeNode.SetHeight(AValue: integer);
|
|
begin
|
|
if AValue<0 then AValue:=0;
|
|
if AValue=FHeight then exit;
|
|
FHeight:=AValue;
|
|
if TreeView<>nil then
|
|
TreeView.FStates:=TreeView.FStates+[tvsScrollbarChanged,tvsTopsNeedsUpdate];
|
|
Update;
|
|
end;
|
|
|
|
procedure TTreeNode.SetImageIndex(AValue: integer);
|
|
//var Item: TTVItem;
|
|
begin
|
|
if FImageIndex=AValue then exit;
|
|
FImageIndex := AValue;
|
|
Update;
|
|
// ToDo
|
|
{
|
|
with Item do
|
|
begin
|
|
mask := TVIF_IMAGE or TVIF_HANDLE;
|
|
hItem := ItemId;
|
|
if Assigned(TCustomTreeView(Owner.Owner).OnGetImageIndex) then
|
|
iImage := I_IMAGECALLBACK
|
|
else
|
|
iImage := FImageIndex;
|
|
end;
|
|
TreeView_SetItem(Handle, Item);
|
|
}
|
|
end;
|
|
|
|
procedure TTreeNode.SetSelectedIndex(AValue: Integer);
|
|
//var Item: TTVItem;
|
|
begin
|
|
if FSelectedIndex = AValue then exit;
|
|
FSelectedIndex := AValue;
|
|
Update;
|
|
{ ToDo:
|
|
with Item do begin
|
|
mask := TVIF_SELECTEDIMAGE or TVIF_HANDLE;
|
|
hItem := ItemId;
|
|
if Assigned(TCustomTreeView(Owner.Owner).OnGetSelectedIndex) then
|
|
iSelectedImage := I_IMAGECALLBACK
|
|
else
|
|
iSelectedImage := FSelectedIndex;
|
|
end;
|
|
TreeView_SetItem(Handle, Item);
|
|
}
|
|
end;
|
|
|
|
procedure TTreeNode.SetOverlayIndex(AValue: Integer);
|
|
//var Item: TTVItem;
|
|
begin
|
|
if FOverlayIndex = AValue then exit;
|
|
FOverlayIndex := AValue;
|
|
Update;
|
|
{ ToDo:
|
|
with Item do begin
|
|
mask := TVIF_STATE or TVIF_HANDLE;
|
|
stateMask := TVIS_OVERLAYMASK;
|
|
hItem := ItemId;
|
|
state := IndexToOverlayMask(FOverlayIndex + 1);
|
|
end;
|
|
TreeView_SetItem(Handle, Item);
|
|
}
|
|
end;
|
|
|
|
procedure TTreeNode.SetStateIndex(AValue: Integer);
|
|
//var Item: TTVItem;
|
|
begin
|
|
if FStateIndex = AValue then exit;
|
|
FStateIndex := AValue;
|
|
Update;
|
|
{ ToDo:
|
|
if Value >= 0 then Dec(Value);
|
|
with Item do
|
|
begin
|
|
mask := TVIF_STATE or TVIF_HANDLE;
|
|
stateMask := TVIS_STATEIMAGEMASK;
|
|
hItem := ItemId;
|
|
state := IndexToStateImageMask(Value + 1);
|
|
end;
|
|
TreeView_SetItem(Handle, Item);
|
|
}
|
|
end;
|
|
|
|
function TTreeNode.AreParentsExpanded: Boolean;
|
|
var ANode: TTreeNode;
|
|
begin
|
|
Result:=false;
|
|
ANode:=Parent;
|
|
while ANode<>nil do begin
|
|
if not ANode.Expanded then exit;
|
|
ANode:=ANode.Parent;
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
function TTreeNode.CompareCount(CompareMe: Integer): Boolean;
|
|
{var
|
|
ACount: integer;
|
|
Node: TTreeNode;}
|
|
Begin
|
|
Result:=(CompareMe=Count);
|
|
{
|
|
ACount := 0;
|
|
Result := False;
|
|
Node := GetFirstChild;
|
|
while Node <> nil do begin
|
|
Inc(ACount);
|
|
Node := Node.GetNextChild(Node);
|
|
if ACount > CompareMe then Exit;
|
|
end;
|
|
if ACount = CompareMe then Result := True;}
|
|
end;
|
|
|
|
function TTreeNode.DoCanExpand(ExpandIt: Boolean): Boolean;
|
|
begin
|
|
Result := False;
|
|
if (TreeView<>nil) and HasChildren then begin
|
|
if ExpandIt then Result := TreeView.CanExpand(Self)
|
|
else Result := TreeView.CanCollapse(Self);
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNode.DoExpand(ExpandIt: Boolean);
|
|
begin
|
|
//writeln('[TTreeNode.DoExpand] Self=',HexStr(Cardinal(Self),8),' Text=',Text,
|
|
//' HasChildren=',HasChildren,' ExpandIt=',ExpandIt,' Expanded=',Expanded);
|
|
if HasChildren and (Expanded<>ExpandIt) then begin
|
|
if (TreeView<>nil) then begin
|
|
if ExpandIt then
|
|
TreeView.Expand(Self)
|
|
else
|
|
TreeView.Collapse(Self);
|
|
end;
|
|
if ExpandIt then
|
|
Include(FStates,nsExpanded)
|
|
else begin
|
|
Exclude(FStates,nsExpanded);
|
|
if (not Owner.KeepCollapsedNodes) then begin
|
|
while GetLastChild<>nil do
|
|
GetLastChild.Free;
|
|
end;
|
|
end;
|
|
if TreeView<>nil then begin
|
|
TreeView.FStates:=(TreeView.FStates+[tvsTopsNeedsUpdate,
|
|
tvsTopItemNeedsUpdate,tvsBottomItemNeedsUpdate,
|
|
tvsScrollbarChanged,tvsMaxRightNeedsUpdate]);
|
|
TreeView.Invalidate;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNode.ExpandItem(ExpandIt: Boolean; Recurse: Boolean);
|
|
var
|
|
//Flag: Integer;
|
|
ANode: TTreeNode;
|
|
begin
|
|
if Recurse then begin
|
|
ExpandItem(ExpandIt, False);
|
|
ANode := GetFirstChild;
|
|
while ANode<>nil do begin
|
|
ANode.ExpandItem(ExpandIt, true);
|
|
ANode := ANode.FNextBrother;
|
|
end;
|
|
end
|
|
else begin
|
|
if TreeView<>nil then
|
|
Include(TreeView.FStates,tvsManualNotify);
|
|
try
|
|
if DoCanExpand(ExpandIt) then
|
|
DoExpand(ExpandIt);
|
|
//if Flag <> 0 then TreeView_Expand(Handle, ItemId, Flag);
|
|
finally
|
|
if TreeView<>nil then
|
|
Exclude(TreeView.FStates,tvsManualNotify);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNode.Expand(Recurse: Boolean);
|
|
begin
|
|
ExpandItem(True, Recurse);
|
|
end;
|
|
|
|
procedure TTreeNode.ExpandParents;
|
|
var ANode: TTreeNode;
|
|
begin
|
|
ANode:=Parent;
|
|
while ANode<>nil do begin
|
|
ANode.Expanded:=true;
|
|
ANode:=ANode.Parent;
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNode.Collapse(Recurse: Boolean);
|
|
begin
|
|
ExpandItem(False, Recurse);
|
|
end;
|
|
|
|
function TTreeNode.GetExpanded: Boolean;
|
|
begin
|
|
Result := GetState(nsExpanded);
|
|
end;
|
|
|
|
procedure TTreeNode.SetExpanded(AValue: Boolean);
|
|
begin
|
|
if AValue=Expanded then exit;
|
|
if AValue then
|
|
Expand(False)
|
|
else
|
|
Collapse(False);
|
|
end;
|
|
|
|
function TTreeNode.GetSelected: Boolean;
|
|
begin
|
|
Result := GetState(nsSelected);
|
|
end;
|
|
|
|
procedure TTreeNode.SetSelected(AValue: Boolean);
|
|
begin
|
|
if AValue=GetSelected then exit;
|
|
if AValue then
|
|
Include(FStates,nsSelected)
|
|
else begin
|
|
Exclude(FStates,nsSelected);
|
|
if (TreeView<>nil) and (TreeView.Selected=Self) then
|
|
TreeView.Selected:=nil;
|
|
end;
|
|
Update;
|
|
{ ToDo:
|
|
if Value then
|
|
TreeView_SelectItem(Handle, ItemId)
|
|
else if Selected then
|
|
TreeView_SelectItem(Handle, nil);
|
|
}
|
|
end;
|
|
|
|
function TTreeNode.GetCut: Boolean;
|
|
begin
|
|
Result := GetState(nsCut);
|
|
end;
|
|
|
|
procedure TTreeNode.SetCut(AValue: Boolean);
|
|
{var
|
|
Item: TTVItem;
|
|
Template: DWORD;}
|
|
begin
|
|
if AValue=Cut then exit;
|
|
// ToDo
|
|
if AValue then
|
|
Include(FStates,nsCut)
|
|
else
|
|
Exclude(FStates,nsCut);
|
|
{ if Value then
|
|
Template := DWORD(-1)
|
|
else
|
|
Template := 0;
|
|
with Item do begin
|
|
mask := TVIF_STATE;
|
|
hItem := ItemId;
|
|
stateMask := TVIS_CUT;
|
|
state := stateMask and Template;
|
|
end;
|
|
TreeView_SetItem(Handle, Item);}
|
|
end;
|
|
|
|
{function TTreeNode.GetDropTarget: Boolean;
|
|
begin
|
|
Result := GetState(nsDropHilited);
|
|
end;}
|
|
|
|
{procedure TTreeNode.SetDropTarget(Value: Boolean);
|
|
begin
|
|
if Value then TreeView_SelectDropTarget(Handle, ItemId)
|
|
else if DropTarget then TreeView_SelectDropTarget(Handle, nil);
|
|
end;}
|
|
|
|
function TTreeNode.GetHasChildren: Boolean;
|
|
begin
|
|
Result := GetState(nsHasChildren);
|
|
end;
|
|
|
|
procedure TTreeNode.SetFocused(AValue: Boolean);
|
|
{var
|
|
Item: TTVItem;
|
|
Template: DWORD;}
|
|
begin
|
|
if AValue=GetFocused then exit;
|
|
// ToDo
|
|
if AValue then
|
|
Include(FStates,nsFocused)
|
|
else
|
|
Exclude(FStates,nsFocused);
|
|
{if Value then Template := DWORD(-1)
|
|
else Template := 0;
|
|
with Item do
|
|
begin
|
|
mask := TVIF_STATE;
|
|
hItem := ItemId;
|
|
stateMask := TVIS_FOCUSED;
|
|
state := stateMask and Template;
|
|
end;
|
|
TreeView_SetItem(Handle, Item);}
|
|
Update;
|
|
end;
|
|
|
|
function TTreeNode.Bottom: integer;
|
|
begin
|
|
Result:=Top+Height;
|
|
end;
|
|
|
|
function TTreeNode.BottomExpanded: integer;
|
|
begin
|
|
if GetNextSibling<>nil then
|
|
Result:=GetNextSibling.Top
|
|
else if GetLastChild<>nil then
|
|
Result:=GetLastChild.BottomExpanded
|
|
else
|
|
Result:=Bottom;
|
|
end;
|
|
|
|
function TTreeNode.GetFocused: Boolean;
|
|
begin
|
|
Result := GetState(nsFocused);
|
|
end;
|
|
|
|
procedure TTreeNode.SetHasChildren(AValue: Boolean);
|
|
//var Item: TTVItem;
|
|
begin
|
|
if AValue=HasChildren then exit;
|
|
//writeln('[TTreeNode.SetHasChildren] Self=',HexStr(Cardinal(Self),8),
|
|
//' Self.Text=',Text,' AValue=',AValue);
|
|
if AValue then
|
|
Include(FStates,nsHasChildren)
|
|
else begin
|
|
while GetLastChild<>nil do
|
|
GetLastChild.Free;
|
|
Exclude(FStates,nsHasChildren)
|
|
end;
|
|
{ Delphi:
|
|
with Item do
|
|
begin
|
|
mask := TVIF_CHILDREN;
|
|
hItem := ItemId;
|
|
cChildren := Ord(Value);
|
|
end;
|
|
TreeView_SetItem(Handle, Item);
|
|
}
|
|
Update;
|
|
end;
|
|
|
|
function TTreeNode.GetNextSibling: TTreeNode;
|
|
begin
|
|
Result:=FNextBrother;
|
|
end;
|
|
|
|
function TTreeNode.GetPrevSibling: TTreeNode;
|
|
begin
|
|
Result:=FPrevBrother;
|
|
end;
|
|
|
|
function TTreeNode.GetNextVisible: TTreeNode;
|
|
begin
|
|
if Expanded and (GetFirstChild<>nil) then
|
|
Result:=GetFirstChild
|
|
else begin
|
|
Result:=Self;
|
|
while (Result<>nil) and (Result.GetNextSibling=nil) do
|
|
Result:=Result.Parent;
|
|
if Result<>nil then Result:=Result.GetNextSibling;
|
|
end;
|
|
if (Result<>nil) and (not Result.IsVisible) then
|
|
Result:=nil;
|
|
end;
|
|
|
|
function TTreeNode.GetPrevVisible: TTreeNode;
|
|
begin
|
|
Result:=GetPrev;
|
|
if (Result<>nil) and (TreeView<>nil) and (not TreeView.IsNodeVisible(Result))
|
|
then
|
|
Result:=nil;
|
|
end;
|
|
|
|
function TTreeNode.GetNextChild(AValue: TTreeNode): TTreeNode;
|
|
begin
|
|
if AValue <> nil then
|
|
Result := AValue.GetNextSibling
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
function TTreeNode.GetPrevChild(AValue: TTreeNode): TTreeNode;
|
|
begin
|
|
if AValue <> nil then
|
|
Result := AValue.GetPrevSibling
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
function TTreeNode.GetFirstChild: TTreeNode;
|
|
begin
|
|
if Count>0 then
|
|
Result:=FItems[0]
|
|
else
|
|
Result:=nil;
|
|
end;
|
|
|
|
function TTreeNode.GetLastSibling: TTreeNode;
|
|
begin
|
|
if Parent<>nil then
|
|
Result:=Parent.GetLastChild
|
|
else begin
|
|
Result:=Self;
|
|
while Result.FNextBrother<>nil do
|
|
Result:=Result.FNextBrother;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNode.GetLastChild: TTreeNode;
|
|
begin
|
|
if Count>0 then
|
|
Result:=FItems[Count-1]
|
|
else
|
|
Result:=nil;
|
|
end;
|
|
|
|
function TTreeNode.GetLastSubChild: TTreeNode;
|
|
var Node: TTreeNode;
|
|
begin
|
|
Result:=GetLastChild;
|
|
if Result<>nil then begin
|
|
Node:=Result.GetLastSubChild;
|
|
if Node<>nil then
|
|
Result:=Node;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNode.GetNext: TTreeNode;
|
|
{var
|
|
NodeID, ParentID: HTreeItem;
|
|
Handle: HWND;}
|
|
begin
|
|
Result:=GetFirstChild;
|
|
if Result=nil then begin
|
|
// no childs, search next
|
|
Result:=Self;
|
|
while (Result<>nil) and (Result.FNextBrother=nil) do
|
|
Result:=Result.Parent;
|
|
if Result<>nil then Result:=Result.FNextBrother;
|
|
end;
|
|
{Handle := FOwner.Handle;
|
|
NodeID := TreeView_GetChild(Handle, ItemId);
|
|
if NodeID = nil then NodeID := TreeView_GetNextSibling(Handle, ItemId);
|
|
ParentID := ItemId;
|
|
while (NodeID = nil) and (ParentID <> nil) do
|
|
begin
|
|
ParentID := TreeView_GetParent(Handle, ParentID);
|
|
NodeID := TreeView_GetNextSibling(Handle, ParentID);
|
|
end;
|
|
Result := FOwner.GetNode(NodeID);}
|
|
end;
|
|
|
|
function TTreeNode.GetPrev: TTreeNode;
|
|
var
|
|
ANode: TTreeNode;
|
|
begin
|
|
Result := GetPrevSibling;
|
|
if Result <> nil then begin
|
|
ANode := Result;
|
|
repeat
|
|
Result := ANode;
|
|
ANode := Result.GetLastChild;
|
|
until ANode = nil;
|
|
end else
|
|
Result := Parent;
|
|
end;
|
|
|
|
function TTreeNode.GetAbsoluteIndex: Integer;
|
|
// - first node has index 0
|
|
// - the first child of a node has an index one bigger than its parent
|
|
// - a node without childs has an index one bigger than its previous brother
|
|
var
|
|
ANode: TTreeNode;
|
|
begin
|
|
Result:=-1;
|
|
ANode:=Self;
|
|
repeat
|
|
inc(Result);
|
|
while ANode.FPrevBrother<>nil do begin
|
|
ANode:=ANode.FPrevBrother;
|
|
inc(Result,ANode.FSubTreeCount);
|
|
end;
|
|
ANode:=ANode.Parent;
|
|
until ANode=nil;
|
|
end;
|
|
|
|
function TTreeNode.GetHeight: integer;
|
|
begin
|
|
if FHeight<=0 then begin
|
|
if TreeView<>nil then
|
|
Result:=TreeView.FDefItemHeight
|
|
else
|
|
Result:=20;
|
|
end else
|
|
Result:=FHeight;
|
|
end;
|
|
|
|
function TTreeNode.GetIndex: Integer;
|
|
// returns number of previous siblings (nodes on same lvl with same parent)
|
|
var
|
|
ANode: TTreeNode;
|
|
begin
|
|
// many algorithms uses the last sibling, so we check that first for speed
|
|
if (Parent<>nil) and (Parent[Parent.Count-1]=Self) then begin
|
|
Result:=Parent.Count-1;
|
|
exit;
|
|
end;
|
|
// count previous siblings
|
|
Result := -1;
|
|
ANode := Self;
|
|
while ANode <> nil do begin
|
|
Inc(Result);
|
|
ANode := ANode.GetPrevSibling;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNode.GetItems(AnIndex: Integer): TTreeNode;
|
|
begin
|
|
if (AnIndex<0) or (AnIndex>=Count) then
|
|
TreeNodeError('TTreeNode.GetItems: Index '+IntToStr(AnIndex)
|
|
+' out of bounds '+IntToStr(Count));
|
|
Result:=FItems[AnIndex];
|
|
{Result := GetFirstChild;
|
|
while (Result <> nil) and (Index > 0) do
|
|
begin
|
|
Result := GetNextChild(Result);
|
|
Dec(Index);
|
|
end;
|
|
if Result = nil then TreeViewError(SListIndexError);}
|
|
end;
|
|
|
|
procedure TTreeNode.SetItems(AnIndex: Integer; AValue: TTreeNode);
|
|
begin
|
|
if (AnIndex<0) or (AnIndex>=Count) then
|
|
TreeNodeError('TTreeNode.SetItems: Index '+IntToStr(AnIndex)
|
|
+' out of bounds '+IntToStr(Count));
|
|
Items[AnIndex].Assign(AValue);
|
|
end;
|
|
|
|
function TTreeNode.IndexOf(AValue: TTreeNode): Integer;
|
|
begin
|
|
if AValue=nil then begin
|
|
Result:=-1;
|
|
exit;
|
|
end;
|
|
Result:=Count-1;
|
|
while Result>=0 do begin
|
|
if FItems[Result]=AValue then exit;
|
|
dec(Result);
|
|
end;
|
|
end;
|
|
|
|
function TTreeNode.GetCount: Integer;
|
|
//var Node: TTreeNode;
|
|
begin
|
|
Result:=FCount;
|
|
{
|
|
Result := 0;
|
|
Node := GetFirstChild;
|
|
while Node <> nil do
|
|
begin
|
|
Inc(Result);
|
|
Node := Node.GetNextChild(Node);
|
|
end;}
|
|
end;
|
|
|
|
procedure TTreeNode.EndEdit(Cancel: Boolean);
|
|
begin
|
|
// ToDo:
|
|
//TreeView_EndEditLabelNow(Handle, Cancel);
|
|
if Cancel then begin
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNode.Unbind;
|
|
// unbind from parent and neighbor siblings
|
|
var OldIndex, i: integer;
|
|
HigherNode: TTreeNode;
|
|
begin
|
|
{$IFDEF TREEVIEW_DEBUG}
|
|
writeln('[TTreeNode.Unbind] Self=',HexStr(Cardinal(Self),8),' Self.Text=',Text);
|
|
{$ENDIF}
|
|
Selected:=false;
|
|
if Owner<>nil then begin
|
|
Owner.ClearCache;
|
|
if FParent=nil then
|
|
Owner.MoveTopLvlNode(Owner.IndexOfTopLvlItem(Self),-1,Self);
|
|
if Owner.Owner<>nil then begin
|
|
Owner.Owner.FStates:=Owner.Owner.FStates+[tvsMaxRightNeedsUpdate,
|
|
tvsTopsNeedsUpdate,tvsTopItemNeedsUpdate,tvsBottomItemNeedsUpdate];
|
|
end;
|
|
end;
|
|
if FPrevBrother<>nil then FPrevBrother.FNextBrother:=FNextBrother;
|
|
if FNextBrother<>nil then FNextBrother.FPrevBrother:=FPrevBrother;
|
|
FPrevBrother:=nil;
|
|
FNextBrother:=nil;
|
|
if FParent<>nil then begin
|
|
HigherNode:=FParent;
|
|
while HigherNode<>nil do begin
|
|
dec(HigherNode.FSubTreeCount,FSubTreeCount);
|
|
HigherNode:=HigherNode.Parent;
|
|
end;
|
|
//if TreeNodes<>nil then Dec(TreeNodes.FCount,FSubTreeCount);
|
|
OldIndex:=Index;
|
|
for i:=OldIndex to Count-1 do
|
|
FParent.FItems[i]:=FParent.FItems[i+1];
|
|
dec(FParent.FCount);
|
|
if (FParent.FCapacity>15) and (FParent.FCount<(FParent.FCapacity shr 2))
|
|
then begin
|
|
// shrink FParent.FItems
|
|
FParent.FCapacity:=FParent.FCapacity shr 1;
|
|
ReAllocMem(FParent.FItems,SizeOf(Pointer)*FParent.FCapacity);
|
|
end;
|
|
if FParent.Count=0 then begin
|
|
FParent.Expanded:=false;
|
|
FParent.HasChildren:=false;
|
|
end;
|
|
FParent:=nil;
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNode.InternalMove(ANode: TTreeNode;
|
|
AddMode: TAddMode);
|
|
{
|
|
TAddMode = (taAddFirst, taAdd, taInsert);
|
|
|
|
taAdd: add Self as last child of ANode
|
|
taAddFirst: add Self as first child of ANode
|
|
taInsert: add Self in front of ANode
|
|
}
|
|
var HigherNode: TTreeNode;
|
|
NewIndex, NewParentItemSize, i: integer;
|
|
begin
|
|
{$IFDEF TREEVIEW_DEBUG}
|
|
write('[TTreeNode.InternalMove] Self=',HexStr(Cardinal(Self),8),' Self.Text=',Text
|
|
,' ANode=',ANode<>nil,' AddMode=',AddModeNames[AddMode]);
|
|
if ANode<>nil then write(' ANode.Text=',ANode.Text);
|
|
writeln('');
|
|
{$ENDIF}
|
|
Unbind;
|
|
// set parent
|
|
if AddMode in [taAdd, taAddFirst] then
|
|
FParent:=ANode
|
|
else begin // taInsert
|
|
if (ANode=nil) then
|
|
TreeNodeError('TTreeNode.InternalMove AddMode=taInsert but ANode=nil');
|
|
FParent:=ANode.Parent;
|
|
FPrevBrother:=ANode.FPrevBrother;
|
|
FNextBrother:=ANode;
|
|
end;
|
|
if FParent<>nil then begin
|
|
FParent.HasChildren:=true;
|
|
if (FParent.FCount=FParent.FCapacity) then begin
|
|
// grow FParent.FItems
|
|
if FParent.FCapacity=0 then
|
|
FParent.FCapacity:=5
|
|
else
|
|
FParent.FCapacity:=FParent.FCapacity shl 1;
|
|
NewParentItemSize:=SizeOf(Pointer)*FParent.FCapacity;
|
|
if FParent.FItems=nil then
|
|
GetMem(FParent.FItems,NewParentItemSize)
|
|
else
|
|
ReAllocMem(FParent.FItems,NewParentItemSize);
|
|
end;
|
|
inc(FParent.FCount);
|
|
// calculate new Index
|
|
case AddMode of
|
|
taAdd: NewIndex:=FParent.Count-1;
|
|
taAddFirst: NewIndex:=0;
|
|
else
|
|
// taInsert
|
|
NewIndex:=ANode.Index;
|
|
end;
|
|
// move next siblings
|
|
for i:=FParent.FCount-1 downto NewIndex+1 do
|
|
FParent.FItems[i]:=FParent.FItems[i-1];
|
|
// insert this node to parent's items
|
|
FParent.FItems[NewIndex]:=Self;
|
|
// set Next and Prev sibling
|
|
if NewIndex>0 then
|
|
FPrevBrother:=FParent.FItems[NewIndex-1]
|
|
else
|
|
FPrevBrother:=nil;
|
|
if NewIndex<FParent.Count-1 then
|
|
FNextBrother:=FParent.FItems[NewIndex+1]
|
|
else
|
|
FNextBrother:=nil;
|
|
// update total node count of all parents
|
|
HigherNode:=FParent;
|
|
while HigherNode<>nil do begin
|
|
inc(HigherNode.FSubTreeCount,FSubTreeCount);
|
|
HigherNode:=HigherNode.Parent;
|
|
end;
|
|
//if TreeNodes<>nil then inc(TreeNodes.FCount,FSubTreeCount);
|
|
end else begin
|
|
// add as top level node
|
|
case AddMode of
|
|
taAdd:
|
|
begin
|
|
// add as last top level node
|
|
if Owner<>nil then begin
|
|
FPrevBrother:=Owner.GetLastNode;
|
|
Owner.MoveTopLvlNode(-1,Owner.FTopLvlCount,Self);
|
|
end;
|
|
end;
|
|
taAddFirst:
|
|
begin
|
|
// add as first top level node = root node
|
|
if Owner<>nil then begin
|
|
FNextBrother:=Owner.GetFirstNode;
|
|
Owner.MoveTopLvlNode(-1,0,Self);
|
|
end;
|
|
end;
|
|
taInsert:
|
|
begin
|
|
// insert node in front of ANode
|
|
//writeln('[TTreeNode.InternalMove] ANode.Index=',ANode.Index,' ANode=',HexStr(Cardinal(ANode),8));
|
|
FNextBrother:=ANode;
|
|
FPrevBrother:=ANode.GetPrevSibling;
|
|
if Owner<>nil then begin
|
|
Owner.MoveTopLvlNode(-1,ANode.Index,Self);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
// connect Next and Prev sibling
|
|
if FPrevBrother<>nil then FPrevBrother.FNextBrother:=Self;
|
|
if FNextBrother<>nil then FNextBrother.FPrevBrother:=Self;
|
|
if Owner.Owner<>nil then
|
|
Owner.Owner.FStates:=Owner.Owner.FStates+[tvsMaxRightNeedsUpdate,
|
|
tvsTopsNeedsUpdate,tvsTopItemNeedsUpdate,tvsBottomItemNeedsUpdate];
|
|
|
|
{$IFDEF TREEVIEW_DEBUG}
|
|
write('[TTreeNode.InternalMove] END Self=',HexStr(Cardinal(Self),8),' Self.Text=',Text
|
|
,' ANode=',ANode<>nil,' AddMode=',AddModeNames[AddMode]);
|
|
if ANode<>nil then write(' ANode.Text=',ANode.Text);
|
|
writeln('');
|
|
{$ENDIF}
|
|
|
|
{var
|
|
I: Integer;
|
|
NodeId: HTreeItem;
|
|
TreeViewItem: TTVItem;
|
|
Children: Boolean;
|
|
IsSelected: Boolean;
|
|
begin
|
|
Owner.ClearCache;
|
|
if (AddMode = taInsert) and (Node <> nil) then
|
|
NodeId := Node.ItemId else
|
|
NodeId := nil;
|
|
Children := HasChildren;
|
|
IsSelected := Selected;
|
|
if (Parent <> nil) and (Parent.CompareCount(1)) then
|
|
begin
|
|
Parent.Expanded := False;
|
|
Parent.HasChildren := False;
|
|
end;
|
|
with TreeViewItem do
|
|
begin
|
|
mask := TVIF_PARAM;
|
|
hItem := ItemId;
|
|
lParam := 0;
|
|
end;
|
|
TreeView_SetItem(Handle, TreeViewItem);
|
|
with Owner do
|
|
HItem := AddItem(HItem, NodeId, CreateItem(Self), AddMode);
|
|
if HItem = nil then
|
|
raise EOutOfResources.Create(sInsertError);
|
|
for I := Count - 1 downto 0 do
|
|
Item[I].InternalMove(Self, nil, HItem, taAddFirst);
|
|
TreeView_DeleteItem(Handle, ItemId);
|
|
FItemId := HItem;
|
|
Assign(Self);
|
|
HasChildren := Children;
|
|
Selected := IsSelected;}
|
|
end;
|
|
|
|
procedure TTreeNode.MoveTo(Destination: TTreeNode; Mode: TNodeAttachMode);
|
|
{
|
|
TNodeAttachMode = (naAdd, naAddFirst, naAddChild, naAddChildFirst, naInsert);
|
|
|
|
naAdd: add as last sibling of Destination
|
|
naAddFirst: add as first sibling of Destnation
|
|
naAddChild: add as last child of Destination
|
|
naAddChildFirst: add as first child of Destination
|
|
naInsert: insert in front of Destination
|
|
}
|
|
var
|
|
AddMode: TAddMode;
|
|
//ANode: TTreeNode;
|
|
//HItem: HTreeItem;
|
|
OldOnChanging: TTVChangingEvent;
|
|
OldOnChange: TTVChangedEvent;
|
|
begin
|
|
if (Destination=nil) and (Mode in [naAddChild,naAddChildFirst,naInsert]) then
|
|
TreeNodeError('TTreeNode.MoveTo Destination=nil');
|
|
if (Destination = nil) or not Destination.HasAsParent(Self) then begin
|
|
OldOnChanging := TreeView.OnChanging;
|
|
OldOnChange := TreeView.OnChange;
|
|
TreeView.OnChanging := nil;
|
|
TreeView.OnChange := nil;
|
|
try
|
|
if (Destination <> nil) and (Mode in [naAdd, naAddFirst]) then
|
|
Destination := Destination.Parent;
|
|
case Mode of
|
|
naAdd,
|
|
naAddChild: AddMode := taAdd;
|
|
naAddFirst,
|
|
naAddChildFirst: AddMode := taAddFirst;
|
|
naInsert: AddMode := taInsert;
|
|
else
|
|
AddMode:=taAdd;
|
|
end;
|
|
{if ANode <> nil then
|
|
HItem := ANode.ItemId else
|
|
HItem := nil;}
|
|
if (Destination <> Self) then
|
|
InternalMove(Destination, AddMode);
|
|
if Parent <> nil then
|
|
Parent.Expanded := True;
|
|
finally
|
|
TreeView.OnChanging := OldOnChanging;
|
|
TreeView.OnChange := OldOnChange;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNode.MakeVisible;
|
|
begin
|
|
if TreeView<>nil then
|
|
TreeView.EnsureNodeIsVisible(Self)
|
|
else
|
|
ExpandParents;
|
|
end;
|
|
|
|
function TTreeNode.GetLevel: Integer;
|
|
// root is on level 0
|
|
var
|
|
ANode: TTreeNode;
|
|
begin
|
|
Result := 0;
|
|
ANode := Parent;
|
|
while ANode <> nil do begin
|
|
Inc(Result);
|
|
ANode := ANode.Parent;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNode.IsNodeVisible: Boolean;
|
|
//var Rect: TRect;
|
|
begin
|
|
if TreeView<>nil then
|
|
Result:=TreeView.IsNodeVisible(Self)
|
|
else
|
|
Result:=AreParentsExpanded;
|
|
//Result := TreeView_GetItemRect(Handle, ItemId, Rect, True);
|
|
end;
|
|
|
|
procedure TTreeNode.Update;
|
|
begin
|
|
if (TreeView<>nil) and (not (csLoading in TreeView.ComponentState)) then
|
|
TreeView.Invalidate;
|
|
end;
|
|
|
|
function TTreeNode.EditText: Boolean;
|
|
begin
|
|
// ToDo:
|
|
Result:=false;
|
|
//Result := TreeView_EditLabel(Handle, ItemId) <> 0;
|
|
end;
|
|
|
|
function TTreeNode.DisplayRect(TextOnly: Boolean): TRect;
|
|
begin
|
|
FillChar(Result, SizeOf(Result), 0);
|
|
if TreeView<>nil then begin
|
|
Result.Left:=TreeView.BorderWidth;
|
|
Result.Top:=Top-TreeView.ScrolledTop+TreeView.BorderWidth;
|
|
Result.Right:=TreeView.ClientWidth-TreeView.BorderWidth;
|
|
Result.Bottom:=Result.Top+Height;
|
|
if TextOnly then begin
|
|
Result.Left:=DisplayTextLeft;
|
|
if Result.Left>Result.Right then
|
|
Result.Left:=Result.Right;
|
|
Result.Right:=DisplayTextRight;
|
|
end;
|
|
//TreeView_GetItemRect(Handle, ItemId, Result, TextOnly);
|
|
end;
|
|
end;
|
|
|
|
function TTreeNode.DisplayExpandSignLeft: integer;
|
|
begin
|
|
Result:=0;
|
|
if TreeView<>nil then begin
|
|
inc(Result,TreeView.Indent*Level+TreeView.BorderWidth);
|
|
end;
|
|
end;
|
|
|
|
function TTreeNode.DisplayExpandSignRect: TRect;
|
|
begin
|
|
FillChar(Result, SizeOf(Result), 0);
|
|
if TreeView<>nil then begin
|
|
Result.Left:=DisplayExpandSignLeft;
|
|
Result.Top:=Top;
|
|
Result.Right:=Result.Left+TreeView.Indent;
|
|
Result.Bottom:=Top+Height;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNode.DisplayExpandSignRight: integer;
|
|
begin
|
|
Result:=DisplayExpandSignLeft;
|
|
if TreeView<>nil then begin
|
|
inc(Result,TreeView.Indent);
|
|
end;
|
|
end;
|
|
|
|
function TTreeNode.DisplayIconLeft: integer;
|
|
begin
|
|
Result:=DisplayExpandSignLeft;
|
|
if (TreeView<>nil) then
|
|
inc(Result,TreeView.Indent);
|
|
end;
|
|
|
|
function TTreeNode.DisplayStateIconLeft: integer;
|
|
begin
|
|
Result:=DisplayIconLeft;
|
|
if (TreeView<>nil) and (TreeView.Images<>nil) then
|
|
inc(Result,TreeView.Images.Width);
|
|
end;
|
|
|
|
function TTreeNode.DisplayTextLeft: integer;
|
|
begin
|
|
Result:=DisplayStateIconLeft;
|
|
if (TreeView<>nil) and (TreeView.StateImages<>nil) then
|
|
inc(Result,TreeView.StateImages.Width);
|
|
end;
|
|
|
|
function TTreeNode.DisplayTextRight: integer;
|
|
begin
|
|
Result:=DisplayTextLeft;
|
|
if TreeView<>nil then
|
|
Inc(Result,TreeView.Canvas.TextWidth(Text));
|
|
end;
|
|
|
|
function TTreeNode.AlphaSort: Boolean;
|
|
begin
|
|
Result := CustomSort(nil);
|
|
end;
|
|
|
|
function TTreeNode.CustomSort(SortProc: TTreeNodeCompare): Boolean;
|
|
//var SortCB: TTVSortCB;
|
|
|
|
procedure Merge(Src,Buffer: TTreeNodeArray; Pos1, Pos2, Pos3: integer);
|
|
// merge two sorted arrays (result is in Src)
|
|
// the first array ranges Pos1..Pos2-1, the second ranges Pos2..Pos3
|
|
var Src1Pos,Src2Pos,DestPos,cmp,a:integer;
|
|
begin
|
|
if (Pos1>=Pos2) or (Pos2>Pos3) then exit;
|
|
Src1Pos:=Pos2-1;
|
|
Src2Pos:=Pos3;
|
|
DestPos:=Pos3;
|
|
while (Src2Pos>=Pos2) and (Src1Pos>=Pos1) do begin
|
|
cmp:=SortProc(Src[Src1Pos],Src[Src2Pos]);
|
|
if cmp>0 then begin
|
|
Buffer[DestPos]:=Src[Src1Pos];
|
|
dec(Src1Pos);
|
|
end else begin
|
|
Buffer[DestPos]:=Src[Src2Pos];
|
|
dec(Src2Pos);
|
|
end;
|
|
dec(DestPos);
|
|
end;
|
|
while Src2Pos>=Pos2 do begin
|
|
Buffer[DestPos]:=Src[Src2Pos];
|
|
dec(Src2Pos);
|
|
dec(DestPos);
|
|
end;
|
|
for a:=DestPos+1 to Pos3 do
|
|
Src[a]:=Buffer[a];
|
|
end;
|
|
|
|
procedure MergeSort(Src,Buffer: TTreeNodeArray; StartPos, EndPos: integer);
|
|
// sort Src from Position StartPos to EndPos (both included)
|
|
var cmp,mid:integer;
|
|
begin
|
|
if StartPos>=EndPos then begin
|
|
// sort one element -> very easy :)
|
|
end else if StartPos+1=EndPos then begin
|
|
// sort two elements -> quite easy :)
|
|
cmp:=SortProc(Src[StartPos],Src[EndPos]);
|
|
if cmp>0 then begin
|
|
Buffer[StartPos]:=Src[StartPos];
|
|
Src[StartPos]:=Src[EndPos];
|
|
Src[EndPos]:=Buffer[StartPos];
|
|
end;
|
|
end else begin
|
|
// sort more than two elements -> Mergesort
|
|
mid:=(StartPos+EndPos) shr 1;
|
|
MergeSort(Src,Buffer,StartPos,mid);
|
|
MergeSort(Src,Buffer,mid+1,EndPos);
|
|
Merge(Src,Buffer,StartPos,mid+1,EndPos);
|
|
end;
|
|
end;
|
|
|
|
var FMergedItems: TTreeNodeArray;
|
|
begin
|
|
if FCount>0 then begin
|
|
if Owner<>nil then Owner.ClearCache;
|
|
if SortProc=nil then SortProc:=@DefaultTreeViewSort;
|
|
GetMem(FMergedItems,SizeOf(Pointer)*FCount);
|
|
MergeSort(FItems,FMergedItems,0,FCount-1);
|
|
{
|
|
with SortCB do begin
|
|
if not Assigned(SortProc) then lpfnCompare := @DefaultTreeViewSort
|
|
else lpfnCompare := SortProc;
|
|
hParent := ItemId;
|
|
lParam := Data;
|
|
end;
|
|
Result := TreeView_SortChildrenCB(Handle, SortCB, 0);
|
|
}
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
procedure TTreeNode.Delete;
|
|
begin
|
|
if not Deleting then Free;
|
|
end;
|
|
|
|
procedure TTreeNode.DeleteChildren;
|
|
begin
|
|
if Owner<>nil then Owner.ClearCache;
|
|
Collapse(true);
|
|
HasChildren := False;
|
|
end;
|
|
|
|
procedure TTreeNode.Assign(Source: TPersistent);
|
|
var
|
|
ANode: TTreeNode;
|
|
begin
|
|
if Owner<>nil then Owner.ClearCache;
|
|
if Source is TTreeNode then
|
|
begin
|
|
ANode := TTreeNode(Source);
|
|
Text := ANode.Text;
|
|
Data := ANode.Data;
|
|
ImageIndex := ANode.ImageIndex;
|
|
SelectedIndex := ANode.SelectedIndex;
|
|
StateIndex := ANode.StateIndex;
|
|
OverlayIndex := ANode.OverlayIndex;
|
|
Height := ANode.Height;
|
|
Focused := ANode.Focused;
|
|
//DropTarget := ANode.DropTarget;
|
|
Cut := ANode.Cut;
|
|
HasChildren := ANode.HasChildren;
|
|
end
|
|
else inherited Assign(Source);
|
|
end;
|
|
|
|
function TTreeNode.IsEqual(Node: TTreeNode): Boolean;
|
|
begin
|
|
Result := (Text = Node.Text) and (Data = Node.Data);
|
|
end;
|
|
|
|
procedure TTreeNode.ReadData(Stream: TStream; StreamVersion: integer;
|
|
Info: PTreeNodeInfo);
|
|
var
|
|
I, ItemCount: Integer;
|
|
NewExpanded: boolean;
|
|
begin
|
|
if Owner<>nil then Owner.ClearCache;
|
|
Stream.ReadBuffer(Info^, SizeOf(TTreeNodeInfo));
|
|
ImageIndex := Info^.ImageIndex;
|
|
SelectedIndex := Info^.SelectedIndex;
|
|
StateIndex := Info^.StateIndex;
|
|
OverlayIndex := Info^.OverlayIndex;
|
|
Data := Info^.Data;
|
|
Height := Info^.Height;
|
|
NewExpanded := Info^.Expanded;
|
|
SetLength(FText,Info^.TextLen);
|
|
if FText<>'' then
|
|
Stream.Read(FText[1],length(FText));
|
|
if Owner<>nil then begin
|
|
ItemCount := Info^.Count;
|
|
for I := 0 to ItemCount - 1 do
|
|
Owner.AddChild(Self, '').ReadData(Stream, StreamVersion, Info);
|
|
end;
|
|
Expanded := NewExpanded;
|
|
end;
|
|
|
|
procedure TTreeNode.ReadDelphiData(Stream: TStream; Info: PDelphiNodeInfo);
|
|
var
|
|
I, Size, ItemCount: Integer;
|
|
begin
|
|
if Owner<>nil then Owner.ClearCache;
|
|
Stream.ReadBuffer(Size, SizeOf(Size));
|
|
Stream.ReadBuffer(Info^, Size);
|
|
Text := Info^.Text;
|
|
ImageIndex := Info^.ImageIndex;
|
|
SelectedIndex := Info^.SelectedIndex;
|
|
StateIndex := Info^.StateIndex;
|
|
OverlayIndex := Info^.OverlayIndex;
|
|
Data := Info^.Data;
|
|
if Owner<>nil then begin
|
|
ItemCount := Info^.Count;
|
|
for I := 0 to ItemCount - 1 do
|
|
Owner.AddChild(Self, '').ReadDelphiData(Stream, Info);
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNode.WriteData(Stream: TStream; Info: PTreeNodeInfo);
|
|
var i: integer;
|
|
begin
|
|
Info^.ImageIndex := ImageIndex;
|
|
Info^.SelectedIndex := SelectedIndex;
|
|
Info^.OverlayIndex := OverlayIndex;
|
|
Info^.StateIndex := StateIndex;
|
|
Info^.Data := Data;
|
|
Info^.Height := FHeight;
|
|
Info^.Count := Count;
|
|
Info^.Expanded := Expanded;
|
|
Info^.TextLen := Length(Text);
|
|
Stream.WriteBuffer(Info^, SizeOf(TTreeNodeInfo));
|
|
if Text<>'' then
|
|
Stream.Write(FText[1],length(Text));
|
|
for i := 0 to Count - 1 do
|
|
Items[i].WriteData(Stream, Info);
|
|
end;
|
|
|
|
procedure TTreeNode.WriteDelphiData(Stream: TStream; Info: PDelphiNodeInfo);
|
|
var
|
|
I, Size, L, ItemCount: Integer;
|
|
begin
|
|
L := Length(Text);
|
|
if L > 255 then L := 255;
|
|
Size := SizeOf(TDelphiNodeInfo) + L - 255;
|
|
Info^.Text := Text;
|
|
Info^.ImageIndex := ImageIndex;
|
|
Info^.SelectedIndex := SelectedIndex;
|
|
Info^.OverlayIndex := OverlayIndex;
|
|
Info^.StateIndex := StateIndex;
|
|
Info^.Data := Data;
|
|
ItemCount := Count;
|
|
Info^.Count := ItemCount;
|
|
Stream.WriteBuffer(Size, SizeOf(Size));
|
|
Stream.WriteBuffer(Info^, Size);
|
|
for I := 0 to ItemCount - 1 do
|
|
Items[I].WriteDelphiData(Stream, Info);
|
|
end;
|
|
|
|
function TTreeNode.ConsistencyCheck: integer;
|
|
var RealSubTreeCount: integer;
|
|
i: integer;
|
|
Node1: TTreeNode;
|
|
begin
|
|
if FOwner<>nil then begin
|
|
end;
|
|
if FCapacity<0 then exit(-1);
|
|
if FCapacity<FCount then exit(-2);
|
|
if FCount<0 then exit(-3);
|
|
if FHeight<0 then exit(-4);
|
|
if (FItems<>nil) and (FCapacity<=0) then exit(-5);
|
|
if (FCapacity>0) and (FItems=nil) then exit(-6);
|
|
if (FNextBrother<>nil) and (FNextBrother.FPrevBrother<>Self) then exit(-7);
|
|
if (FPrevBrother<>nil) and (FPrevBrother.FNextBrother<>Self) then exit(-8);
|
|
// check childs
|
|
RealSubTreeCount:=1;
|
|
for i:=0 to FCount-1 do begin
|
|
if (Items[i]=nil) then exit(-9);
|
|
if (i=0) and (Items[i].FPrevBrother<>nil) then exit(-10);
|
|
if (i>0) and (Items[i].FPrevBrother<>Items[i-1]) then exit(-11);
|
|
if (i<FCount-1) and (Items[i].FNextBrother<>Items[i+1]) then exit(-12);
|
|
if (i=FCount-1) and (Items[i].FNextBrother<>nil) then exit(-13);
|
|
if Items[i].FParent<>Self then exit(-14);
|
|
Result:=Items[i].ConsistencyCheck;
|
|
if Result<>0 then exit;
|
|
inc(RealSubTreeCount,Items[i].SubTreeCount);
|
|
end;
|
|
if FParent<>nil then begin
|
|
if FParent.IndexOf(Self)<0 then exit(-15);
|
|
end;
|
|
if RealSubTreeCount<>SubTreeCount then exit(-16);
|
|
if FTop<0 then exit(-17);
|
|
// check for circles
|
|
if FNextBrother=Self then exit(-18);
|
|
if FPrevBrother=Self then exit(-19);
|
|
if FParent=Self then exit(-20);
|
|
Node1:=FParent;
|
|
while Node1<>nil do begin
|
|
if (Node1=Self) then exit(-21);
|
|
Node1:=Node1.FParent;
|
|
end;
|
|
Result:=0;
|
|
end;
|
|
|
|
procedure TTreeNode.WriteDebugReport(const Prefix: string; Recurse: boolean);
|
|
var i: integer;
|
|
begin
|
|
write(Prefix);
|
|
write('TTreeNode.WriteDebugReport Self=',HexStr(Cardinal(Self),8));
|
|
write(' Consistency=',ConsistencyCheck);
|
|
write(' Text=',Text);
|
|
writeln('');
|
|
if Recurse then begin
|
|
for i:=0 to FCount-1 do
|
|
Items[i].WriteDebugReport(Prefix+' ',true);
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TTreeNodes }
|
|
|
|
constructor TTreeNodes.Create(AnOwner: TCustomTreeView);
|
|
begin
|
|
inherited Create;
|
|
FOwner := AnOwner;
|
|
end;
|
|
|
|
destructor TTreeNodes.Destroy;
|
|
begin
|
|
Clear;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TTreeNodes.GetCount: Integer;
|
|
begin
|
|
Result:=FCount;
|
|
//if Owner.HandleAllocated then Result := TreeView_GetCount(Handle)
|
|
//else Result := 0;
|
|
end;
|
|
|
|
function TTreeNodes.GetHandle: THandle;
|
|
begin
|
|
if Owner<>nil then
|
|
Result:=Owner.Handle
|
|
else
|
|
Result:=0;
|
|
end;
|
|
|
|
procedure TTreeNodes.Delete(Node: TTreeNode);
|
|
begin
|
|
if Owner<>nil then
|
|
//if (Node.ItemId = nil) then
|
|
Owner.Delete(Node);
|
|
Node.Delete;
|
|
end;
|
|
|
|
procedure TTreeNodes.Clear;
|
|
begin
|
|
ClearCache;
|
|
while GetLastNode<>nil do
|
|
GetLastNode.Delete;
|
|
end;
|
|
|
|
function TTreeNodes.AddChildFirst(ParentNode: TTreeNode; const S: string): TTreeNode;
|
|
begin
|
|
Result := AddChildObjectFirst(ParentNode, S, nil);
|
|
end;
|
|
|
|
function TTreeNodes.AddChildObjectFirst(ParentNode: TTreeNode; const S: string;
|
|
Data: Pointer): TTreeNode;
|
|
begin
|
|
Result := InternalAddObject(ParentNode, S, Data, taAddFirst);
|
|
end;
|
|
|
|
function TTreeNodes.AddChild(ParentNode: TTreeNode; const S: string): TTreeNode;
|
|
begin
|
|
Result := AddChildObject(ParentNode, S, nil);
|
|
end;
|
|
|
|
function TTreeNodes.AddChildObject(ParentNode: TTreeNode; const S: string;
|
|
Data: Pointer): TTreeNode;
|
|
begin
|
|
Result := InternalAddObject(ParentNode, S, Data, taAdd);
|
|
end;
|
|
|
|
function TTreeNodes.AddFirst(SiblingNode: TTreeNode; const S: string): TTreeNode;
|
|
begin
|
|
Result := AddObjectFirst(SiblingNode, S, nil);
|
|
end;
|
|
|
|
function TTreeNodes.AddObjectFirst(SiblingNode: TTreeNode; const S: string;
|
|
Data: Pointer): TTreeNode;
|
|
var ParentNode: TTreeNode;
|
|
begin
|
|
if SiblingNode <> nil then
|
|
ParentNode := SiblingNode.Parent
|
|
else
|
|
ParentNode := nil;
|
|
Result := InternalAddObject(ParentNode, S, Data, taAddFirst);
|
|
end;
|
|
|
|
function TTreeNodes.Add(SiblingNode: TTreeNode; const S: string): TTreeNode;
|
|
begin
|
|
Result := AddObject(SiblingNode, S, nil);
|
|
end;
|
|
|
|
procedure TTreeNodes.Repaint(ANode: TTreeNode);
|
|
var
|
|
R: TRect;
|
|
begin
|
|
if (FUpdateCount < 1) and (Owner<>nil) then begin
|
|
while (ANode <> nil) and not ANode.IsVisible do ANode := ANode.Parent;
|
|
if ANode <> nil then begin
|
|
R := ANode.DisplayRect(False);
|
|
InvalidateRect(Owner.Handle, @R, True);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNodes.AddObject(SiblingNode: TTreeNode; const S: string;
|
|
Data: Pointer): TTreeNode;
|
|
var ParentNode: TTreeNode;
|
|
begin
|
|
if SiblingNode <> nil then
|
|
ParentNode := SiblingNode.Parent
|
|
else
|
|
ParentNode := nil;
|
|
Result := InternalAddObject(ParentNode, S, Data, taAdd);
|
|
end;
|
|
|
|
procedure TTreeNodes.AddedNode(AValue: TTreeNode);
|
|
begin
|
|
if AValue <> nil then begin
|
|
AValue.HasChildren := True;
|
|
Repaint(AValue);
|
|
end;
|
|
end;
|
|
|
|
function TTreeNodes.Insert(NextNode: TTreeNode; const S: string): TTreeNode;
|
|
begin
|
|
Result := InsertObject(NextNode, S, nil);
|
|
end;
|
|
|
|
function TTreeNodes.InsertObject(NextNode: TTreeNode; const S: string;
|
|
Data: Pointer): TTreeNode;
|
|
// create a new node with Text=S and Data=Data and insert in front of
|
|
// NextNode (as sibling with same parent).
|
|
begin
|
|
Result:=InternalAddObject(NextNode,S,Data,taInsert);
|
|
end;
|
|
|
|
function TTreeNodes.InsertBehind(PrevNode: TTreeNode; const S: string
|
|
): TTreeNode;
|
|
begin
|
|
Result := InsertObjectBehind(PrevNode, S, nil);
|
|
end;
|
|
|
|
function TTreeNodes.InsertObjectBehind(PrevNode: TTreeNode; const S: string;
|
|
Data: Pointer): TTreeNode;
|
|
// create a new node with Text=S and Data=Data and insert in front of
|
|
// NextNode (as sibling with same parent).
|
|
begin
|
|
if (PrevNode<>nil) and (PrevNode.GetNextSibling<>nil) then
|
|
Result:=InternalAddObject(PrevNode.GetNextSibling,S,Data,taInsert)
|
|
else
|
|
Result:=AddObject(PrevNode,S,Data);
|
|
end;
|
|
|
|
function TTreeNodes.InternalAddObject(Node: TTreeNode; const S: string;
|
|
Data: Pointer; AddMode: TAddMode): TTreeNode;
|
|
{
|
|
TAddMode = (taAddFirst, taAdd, taInsert);
|
|
|
|
taAdd: add Result as last child of Node
|
|
taAddFirst: add Result as first child of Node
|
|
taInsert: add Result in front of Node
|
|
}
|
|
//var Item: HTreeItem;
|
|
var ok: boolean;
|
|
begin
|
|
if Owner=nil then
|
|
TreeNodeError('TTreeNodes.InternalAddObject Owner=nil');
|
|
{$IFDEF TREEVIEW_DEBUG}
|
|
write('[TTreeNodes.InternalAddObject] Node=',Node<>nil,' S=',S,
|
|
' AddMode=',AddModeNames[AddMode]);
|
|
if Node<>nil then write(' Node.Text=',Node.Text);
|
|
writeln('');
|
|
{$ENDIF}
|
|
Result := Owner.CreateNode;
|
|
ok:=false;
|
|
try
|
|
Result.Data := Data;
|
|
Result.Text := S;
|
|
// move node in tree (tree of TTreeNode)
|
|
Result.InternalMove(Node,AddMode);
|
|
if (Owner<>nil) and Owner.AutoExpand and (Node<>nil) and (Node.Parent<>nil)
|
|
then
|
|
Node.Parent.Expanded:=true;
|
|
if (FUpdateCount=0) and (Owner<>nil) then
|
|
Owner.Invalidate;
|
|
ok:=true;
|
|
finally
|
|
// this construction creates nicer exception output
|
|
if not ok then
|
|
Result.Free;
|
|
end;
|
|
end;
|
|
|
|
{function TTreeNodes.CreateItem(Node: TTreeNode): TTVItem;
|
|
begin
|
|
Node.FInTree := True;
|
|
with Result do
|
|
begin
|
|
mask := TVIF_TEXT or TVIF_PARAM or TVIF_IMAGE or TVIF_SELECTEDIMAGE;
|
|
lParam := Longint(Node);
|
|
pszText := LPSTR_TEXTCALLBACK;
|
|
iImage := I_IMAGECALLBACK;
|
|
iSelectedImage := I_IMAGECALLBACK;
|
|
end;
|
|
end;}
|
|
|
|
{function TTreeNodes.AddItem(Parent, Target: HTreeItem;
|
|
const Item: TTVItem; AddMode: TAddMode): HTreeItem;
|
|
var
|
|
InsertStruct: TTVInsertStruct;
|
|
begin
|
|
ClearCache;
|
|
with InsertStruct do begin
|
|
hParent := Parent;
|
|
case AddMode of
|
|
taAddFirst:
|
|
hInsertAfter := TVI_FIRST;
|
|
taAdd:
|
|
hInsertAfter := TVI_LAST;
|
|
taInsert:
|
|
hInsertAfter := Target;
|
|
end;
|
|
end;
|
|
InsertStruct.item := Item;
|
|
FOwner.FChangeTimer.Enabled := False;
|
|
Result := TreeView_InsertItem(Handle, InsertStruct);
|
|
end;}
|
|
|
|
function TTreeNodes.GetFirstNode: TTreeNode;
|
|
begin
|
|
if FTopLvlItems<>nil then
|
|
Result := FTopLvlItems[0]
|
|
else
|
|
Result := nil;
|
|
//Result := GetNode(TreeView_GetRoot(Handle));
|
|
end;
|
|
|
|
function TTreeNodes.GetLastNode: TTreeNode;
|
|
begin
|
|
if FTopLvlItems<>nil then
|
|
Result := FTopLvlItems[FTopLvlCount-1]
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
function TTreeNodes.GetLastSubNode: TTreeNode;
|
|
// absolute last node
|
|
var Node: TTreeNode;
|
|
begin
|
|
Result:=GetLastNode;
|
|
if Result<>nil then begin
|
|
Node:=Result.GetLastSubChild;
|
|
if Node<>nil then Result:=Node;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNodes.GetLastExpandedSubNode: TTreeNode;
|
|
// absolute last expanded node
|
|
var Node: TTreeNode;
|
|
begin
|
|
Result:=GetLastNode;
|
|
while (Result<>nil) and (Result.Expanded) do begin
|
|
Node:=Result.GetLastChild;
|
|
if Node<>nil then Result:=Node;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNodes.GetNodeFromIndex(Index: Integer): TTreeNode;
|
|
// find node with absolute index in ALL nodes (even collapsed)
|
|
var
|
|
I, J: Integer;
|
|
begin
|
|
if (Index < 0) or (Index >= FCount) then
|
|
TreeNodeError('TTreeNodes.GetNodeFromIndex Index '+IntToStr(Index)
|
|
+' out of bounds (Count='+IntToStr(FCount)+')');
|
|
if (FNodeCache.CacheNode <> nil) and (Abs(FNodeCache.CacheIndex - Index) <= 1)
|
|
then begin
|
|
with FNodeCache do
|
|
begin
|
|
if Index = CacheIndex then Result := CacheNode
|
|
else if Index < CacheIndex then Result := CacheNode.GetPrev
|
|
else Result := CacheNode.GetNext;
|
|
end;
|
|
end
|
|
else begin
|
|
Result := GetFirstNode;
|
|
I:=0;
|
|
while (Result<>nil) and (Index>I) do begin
|
|
Repeat
|
|
// calculate the absolute index of the next sibling
|
|
J:=I+Result.FSubTreeCount;
|
|
if J=I then
|
|
TreeNodeError(
|
|
'TTreeNodes.GetNodeFromIndex: Consistency error - SubTreeCount=0');
|
|
if J<=Index then begin
|
|
// Index > absolute index of next sibling -> search in next sibling
|
|
Result:=Result.GetNext;
|
|
I:=J;
|
|
end else
|
|
break;
|
|
until false;
|
|
if (Result<>nil) and (Index>I) then begin
|
|
// Index is somewhere in subtree of Result
|
|
Result:=Result.GetFirstChild;
|
|
if Result=nil then
|
|
TreeNodeError(
|
|
'TTreeNodes.GetNodeFromIndex: Consistency error'
|
|
+' - invalid SubTreeCount');
|
|
inc(I);
|
|
end;
|
|
end;
|
|
end;
|
|
if Result = nil then
|
|
TreeNodeError(
|
|
'TTreeNodes.GetNodeFromIndex: Consistency Error - Count too big');
|
|
FNodeCache.CacheNode := Result;
|
|
FNodeCache.CacheIndex := Index;
|
|
end;
|
|
|
|
{function TTreeNodes.GetNode(ItemId: HTreeItem): TTreeNode;
|
|
var
|
|
Item: TTVItem;
|
|
begin
|
|
with Item do
|
|
begin
|
|
hItem := ItemId;
|
|
mask := TVIF_PARAM;
|
|
end;
|
|
if TreeView_GetItem(Handle, Item) then Result := TTreeNode(Item.lParam)
|
|
else Result := nil;
|
|
end;}
|
|
|
|
procedure TTreeNodes.SetItem(Index: Integer; AValue: TTreeNode);
|
|
begin
|
|
GetNodeFromIndex(Index).Assign(AValue);
|
|
end;
|
|
|
|
procedure TTreeNodes.SetTopLvlItems(Index: integer; AValue: TTreeNode);
|
|
begin
|
|
GetTopLvlItems(Index).Assign(AValue);
|
|
end;
|
|
|
|
procedure TTreeNodes.BeginUpdate;
|
|
begin
|
|
if FUpdateCount = 0 then SetUpdateState(True);
|
|
Inc(FUpdateCount);
|
|
end;
|
|
|
|
procedure TTreeNodes.SetUpdateState(Updating: Boolean);
|
|
begin
|
|
//SendMessage(Handle, WM_SETREDRAW, Ord(not Updating), 0);
|
|
if Updating then
|
|
Include(Owner.FStates,tvsUpdating)
|
|
else
|
|
Exclude(Owner.FStates,tvsUpdating);
|
|
if not Updating then Owner.Refresh;
|
|
end;
|
|
|
|
procedure TTreeNodes.EndUpdate;
|
|
begin
|
|
Dec(FUpdateCount);
|
|
if FUpdateCount = 0 then SetUpdateState(False);
|
|
end;
|
|
|
|
procedure TTreeNodes.GrowTopLvlItems;
|
|
begin
|
|
if FTopLvlItems<>nil then begin
|
|
FTopLvlCapacity:=FTopLvlCapacity shl 1;
|
|
ReAllocMem(FTopLvlItems,SizeOf(Pointer)*FTopLvlCapacity);
|
|
end else begin
|
|
FTopLvlCapacity:=10;
|
|
GetMem(FTopLvlItems,SizeOf(Pointer)*FTopLvlCapacity);
|
|
end;
|
|
end;
|
|
|
|
function TTreeNodes.GetTopLvlItems(Index: integer): TTreeNode;
|
|
begin
|
|
Result:=FTopLvlItems[Index];
|
|
end;
|
|
|
|
procedure TTreeNodes.ShrinkTopLvlItems;
|
|
begin
|
|
if FTopLvlCount>0 then begin
|
|
FTopLvlCapacity:=FTopLvlCapacity shr 1;
|
|
if FTopLvlCapacity<FTopLvlCount then FTopLvlCapacity:=FTopLvlCount;
|
|
ReAllocMem(FTopLvlItems,SizeOf(Pointer)*FTopLvlCapacity);
|
|
end else begin
|
|
FTopLvlCapacity:=0;
|
|
FreeMem(FTopLvlItems);
|
|
FTopLvlItems:=nil;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNodes.IndexOfTopLvlItem(Node: TTreeNode): integer;
|
|
begin
|
|
if (FTopLvlCount>0) and (FTopLvlItems[0]=Node) then exit(0);
|
|
Result:=FTopLvlCount-1;
|
|
while (Result>=0) and (FTopLvlItems[Result]<>Node) do dec(Result);
|
|
end;
|
|
|
|
procedure TTreeNodes.MoveTopLvlNode(TopLvlFromIndex, TopLvlToIndex: integer;
|
|
Node: TTreeNode);
|
|
var i: integer;
|
|
begin
|
|
{$IFDEF TREEVIEW_DEBUG}
|
|
writeln('[TTreeNodes.MoveTopLvlNode] TopLvlFromIndex=',TopLvlFromIndex,
|
|
' TopLvlToIndex=',TopLvlToIndex,' Node.Text=',Node.Text);
|
|
{$ENDIF}
|
|
if (TopLvlFromIndex>=FTopLvlCount) then
|
|
TreeNodeError('TTreeNodes.MoveTopLvlNode TopLvlFromIndex>FTopLvlCount');
|
|
if (TopLvlToIndex>FTopLvlCount) then
|
|
TreeNodeError('TTreeNodes.MoveTopLvlNode TopLvlFromIndex>FTopLvlCount');
|
|
if (TopLvlFromIndex>=0) then begin
|
|
Node:=FTopLvlItems[TopLvlFromIndex];
|
|
if (TopLvlToIndex>=0) then begin
|
|
// move node
|
|
if TopLvlToIndex=TopLvlFromIndex then exit;
|
|
if TopLvlFromIndex<TopLvlToIndex then begin
|
|
// move forward
|
|
for i:=TopLvlFromIndex to TopLvlToIndex-1 do
|
|
FTopLvlItems[i]:=FTopLvlItems[i+1];
|
|
end else begin
|
|
// move backward
|
|
for i:=TopLvlToIndex downto TopLvlFromIndex+1 do
|
|
FTopLvlItems[i]:=FTopLvlItems[i-1];
|
|
end;
|
|
FTopLvlItems[TopLvlToIndex]:=Node;
|
|
end else begin
|
|
// remove node
|
|
for i:=TopLvlFromIndex to FTopLvlCount-2 do
|
|
FTopLvlItems[i]:=FTopLvlItems[i+1];
|
|
Dec(FTopLvlCount);
|
|
if FTopLvlCount<(FTopLvlCapacity shr 2) then ShrinkTopLvlItems;
|
|
end;
|
|
end else begin
|
|
if (TopLvlToIndex>=0) then begin
|
|
if Node=nil then
|
|
TreeNodeError('TTreeNodes.MoveTopLvlNode inserting nil');
|
|
// insert node
|
|
if FTopLvlCount=FTopLvlCapacity then GrowTopLvlItems;
|
|
inc(FTopLvlCount);
|
|
for i:=FTopLvlCount-1 downto TopLvlToIndex+1 do
|
|
FTopLvlItems[i]:=FTopLvlItems[i-1];
|
|
FTopLvlItems[TopLvlToIndex]:=Node;
|
|
end else begin
|
|
// nothing to do
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNodes.Assign(Source: TPersistent);
|
|
var
|
|
TreeNodes: TTreeNodes;
|
|
MemStream: TMemoryStream;
|
|
begin
|
|
ClearCache;
|
|
if Source is TTreeNodes then begin
|
|
TreeNodes := TTreeNodes(Source);
|
|
Clear;
|
|
MemStream := TMemoryStream.Create;
|
|
try
|
|
TreeNodes.WriteData(MemStream);
|
|
MemStream.Position := 0;
|
|
ReadData(MemStream);
|
|
finally
|
|
MemStream.Free;
|
|
end;
|
|
end
|
|
else
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
procedure TTreeNodes.DefineProperties(Filer: TFiler);
|
|
|
|
function WriteNodes: Boolean;
|
|
var
|
|
I: Integer;
|
|
Nodes: TTreeNodes;
|
|
begin
|
|
Nodes := TTreeNodes(Filer.Ancestor);
|
|
if Nodes = nil then
|
|
Result := Count > 0
|
|
else if Nodes.Count <> Count then
|
|
Result := True
|
|
else
|
|
begin
|
|
Result := False;
|
|
for I := 0 to Count - 1 do
|
|
begin
|
|
Result := not Items[I].IsEqual(Nodes[I]);
|
|
if Result then Break;
|
|
end
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
inherited DefineProperties(Filer);
|
|
Filer.DefineBinaryProperty('Data', @ReadData, @WriteData, WriteNodes);
|
|
end;
|
|
|
|
procedure TTreeNodes.ReadData(Stream: TStream);
|
|
var
|
|
I, NewCount, MagicNumber: Integer;
|
|
NodeInfo: TDelphiNodeInfo;
|
|
StreamVersion: word;
|
|
begin
|
|
Clear;
|
|
// -7 for lcl stream
|
|
Stream.ReadBuffer(MagicNumber,SizeOf(Integer));
|
|
if MagicNumber=LCLStreamID then begin
|
|
// read stream version
|
|
Stream.ReadBuffer(StreamVersion,SizeOf(StreamVersion));
|
|
// read top level node count
|
|
Stream.ReadBuffer(NewCount, SizeOf(NewCount));
|
|
for I := 0 to NewCount - 1 do
|
|
Add(nil, '').ReadData(Stream, StreamVersion, @NodeInfo);
|
|
end else begin
|
|
// delphi stream
|
|
NewCount:=MagicNumber;
|
|
for I := 0 to NewCount - 1 do
|
|
Add(nil, '').ReadDelphiData(Stream, @NodeInfo);
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNodes.WriteData(Stream: TStream);
|
|
var
|
|
ANode: TTreeNode;
|
|
NodeInfo: TDelphiNodeInfo;
|
|
MagicNumber: integer;
|
|
begin
|
|
// -7 for lcl stream
|
|
MagicNumber:=LCLStreamID;
|
|
Stream.WriteBuffer(MagicNumber,SizeOf(MagicNumber));
|
|
// write stream version
|
|
Stream.WriteBuffer(TTreeNodeStreamVersion,SizeOf(Word));
|
|
// write top level node count
|
|
Stream.WriteBuffer(FTopLvlCount, SizeOf(Integer));
|
|
// write all nodes recursively
|
|
ANode := GetFirstNode;
|
|
while ANode <> nil do begin
|
|
ANode.WriteData(Stream, @NodeInfo);
|
|
ANode := ANode.GetNextSibling;
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNodes.ReadExpandedState(Stream: TStream);
|
|
var
|
|
ItemCount,
|
|
Index: Integer;
|
|
Node: TTreeNode;
|
|
NodeExpanded: Boolean;
|
|
begin
|
|
// ToDo: read different stream formats
|
|
if Stream.Position < Stream.Size then
|
|
Stream.ReadBuffer(ItemCount, SizeOf(ItemCount))
|
|
else Exit;
|
|
Index := 0;
|
|
Node := GetFirstNode;
|
|
while (Index < ItemCount) and (Node <> nil) do begin
|
|
Stream.ReadBuffer(NodeExpanded, SizeOf(NodeExpanded));
|
|
Node.Expanded := NodeExpanded;
|
|
Inc(Index);
|
|
Node := Node.GetNext;
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNodes.WriteExpandedState(Stream: TStream);
|
|
var
|
|
Size: Integer;
|
|
ANode: TTreeNode;
|
|
NodeExpanded: Boolean;
|
|
begin
|
|
// ToDo: read different stream formats
|
|
Size := SizeOf(Boolean) * Count;
|
|
Stream.WriteBuffer(Size, SizeOf(Size));
|
|
ANode := GetFirstNode;
|
|
while (ANode <> nil) do begin
|
|
NodeExpanded := ANode.Expanded;
|
|
Stream.WriteBuffer(NodeExpanded, SizeOf(Boolean));
|
|
ANode := ANode.GetNext;
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNodes.ClearCache;
|
|
begin
|
|
FNodeCache.CacheNode := nil;
|
|
end;
|
|
|
|
function TTreeNodes.ConsistencyCheck: integer;
|
|
var Node: TTreeNode;
|
|
RealCount, i: integer;
|
|
OldCache: TNodeCache;
|
|
begin
|
|
if FUpdateCount<0 then exit(-1);
|
|
RealCount:=0;
|
|
Node:=GetFirstNode;
|
|
while Node<>nil do begin
|
|
Result:=Node.ConsistencyCheck;
|
|
if Result<>0 then begin
|
|
dec(Result,100);
|
|
exit;
|
|
end;
|
|
inc(RealCount,Node.SubTreeCount);
|
|
//writeln(' ConsistencyCheck: B ',RealCount,',',Node.SubTreeCount);
|
|
Node:=Node.FNextBrother;
|
|
end;
|
|
//writeln(' ConsistencyCheck: B ',RealCount,',',FCount);
|
|
if RealCount<>FCount then exit(-3);
|
|
if (FTopLvlCapacity<=0) and (FTopLvlItems<>nil) then exit(-4);
|
|
if (FTopLvlCapacity>0) and (FTopLvlItems=nil) then exit(-5);
|
|
if FTopLvlCapacity<FTopLvlCount then exit(-6);
|
|
if (FTopLvlCount<0) then exit(-7);
|
|
for i:=0 to FTopLvlCount-1 do begin
|
|
if (i=0) and (FTopLvlItems[i].FPrevBrother<>nil) then exit(-8);
|
|
if (i>0) and (FTopLvlItems[i].FPrevBrother<>FTopLvlItems[i-1]) then
|
|
exit(-9);
|
|
if (i<FTopLvlCount-1) and (FTopLvlItems[i].FNextBrother<>FTopLvlItems[i+1])
|
|
then begin
|
|
writeln(' CONSISTENCY i=',i,' FTopLvlCount=',FTopLvlCount,' FTopLvlItems[i]=',HexStr(Cardinal(FTopLvlItems[i]),8),' FTopLvlItems[i].FNextBrother=',HexStr(Cardinal(FTopLvlItems[i].FNextBrother),8),' FTopLvlItems[i+1]=',HexStr(Cardinal(FTopLvlItems[i+1]),8));
|
|
exit(-10);
|
|
end;
|
|
if (i=FTopLvlCount-1) and (FTopLvlItems[i].FNextBrother<>nil) then
|
|
exit(-11);
|
|
end;
|
|
if FNodeCache.CacheNode<>nil then begin
|
|
OldCache:=FNodeCache;
|
|
ClearCache;
|
|
if GetNodeFromIndex(OldCache.CacheIndex)<>OldCache.CacheNode then exit(-12);
|
|
end;
|
|
Result:=0;
|
|
end;
|
|
|
|
procedure TTreeNodes.WriteDebugReport(const Prefix: string; AllNodes: boolean);
|
|
var Node: TTreeNode;
|
|
begin
|
|
write(Prefix);
|
|
write('TTreeNodes.WriteDebugReport Self=',HexStr(Cardinal(Self),8));
|
|
write(' Consistency=',ConsistencyCheck);
|
|
writeln('');
|
|
if AllNodes then begin
|
|
Node:=GetFirstNode;
|
|
while Node<>nil do begin
|
|
Node.WriteDebugReport(Prefix+' ',true);
|
|
Node:=Node.GetNextSibling;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
type
|
|
TTreeStrings = class(TStrings)
|
|
private
|
|
FOwner: TTreeNodes;
|
|
protected
|
|
function Get(Index: Integer): string; override;
|
|
function GetBufStart(Buffer: PChar; var Level: Integer): PChar;
|
|
function GetCount: Integer; override;
|
|
function GetObject(Index: Integer): TObject; override;
|
|
procedure PutObject(Index: Integer; AObject: TObject); override;
|
|
procedure SetUpdateState(Updating: Boolean); override;
|
|
public
|
|
constructor Create(AnOwner: TTreeNodes);
|
|
function Add(const S: string): Integer; override;
|
|
procedure Clear; override;
|
|
procedure Delete(Index: Integer); override;
|
|
procedure Insert(Index: Integer; const S: string); override;
|
|
procedure LoadTreeFromStream(Stream: TStream);
|
|
procedure SaveTreeToStream(Stream: TStream);
|
|
function ConsistencyCheck: integer;
|
|
procedure WriteDebugReport(const Prefix: string);
|
|
property Owner: TTreeNodes read FOwner;
|
|
end;
|
|
|
|
constructor TTreeStrings.Create(AnOwner: TTreeNodes);
|
|
begin
|
|
inherited Create;
|
|
FOwner := AnOwner;
|
|
end;
|
|
|
|
function TTreeStrings.Get(Index: Integer): string;
|
|
const
|
|
TabChar = #9;
|
|
var
|
|
Level, I: Integer;
|
|
Node: TTreeNode;
|
|
begin
|
|
Result := '';
|
|
Node := Owner.GetNodeFromIndex(Index);
|
|
Level := Node.Level;
|
|
for I := 0 to Level - 1 do Result := Result + TabChar;
|
|
Result := Result + Node.Text;
|
|
end;
|
|
|
|
function TTreeStrings.GetBufStart(Buffer: PChar; var Level: Integer): PChar;
|
|
begin
|
|
Level := 0;
|
|
while Buffer^ in [' ', #9] do
|
|
begin
|
|
Inc(Buffer);
|
|
Inc(Level);
|
|
end;
|
|
Result := Buffer;
|
|
end;
|
|
|
|
function TTreeStrings.GetObject(Index: Integer): TObject;
|
|
begin
|
|
Result := TObject(Owner.GetNodeFromIndex(Index).Data);
|
|
end;
|
|
|
|
procedure TTreeStrings.PutObject(Index: Integer; AObject: TObject);
|
|
begin
|
|
Owner.GetNodeFromIndex(Index).Data := AObject;
|
|
end;
|
|
|
|
function TTreeStrings.GetCount: Integer;
|
|
begin
|
|
Result := Owner.Count;
|
|
end;
|
|
|
|
procedure TTreeStrings.Clear;
|
|
begin
|
|
Owner.Clear;
|
|
end;
|
|
|
|
procedure TTreeStrings.Delete(Index: Integer);
|
|
begin
|
|
Owner.GetNodeFromIndex(Index).Delete;
|
|
end;
|
|
|
|
procedure TTreeStrings.SetUpdateState(Updating: Boolean);
|
|
begin
|
|
//SendMessage(Owner.Handle, WM_SETREDRAW, Ord(not Updating), 0);
|
|
if not Updating then Owner.Owner.Refresh;
|
|
end;
|
|
|
|
function TTreeStrings.Add(const S: string): Integer;
|
|
var
|
|
Level, OldLevel, I: Integer;
|
|
NewStr: string;
|
|
Node: TTreeNode;
|
|
begin
|
|
Result := GetCount;
|
|
if (Length(S) = 1) and (S[1] = Chr($1A)) then Exit;
|
|
Node := nil;
|
|
OldLevel := 0;
|
|
NewStr := GetBufStart(PChar(S), Level);
|
|
if Result > 0 then
|
|
begin
|
|
Node := Owner.GetNodeFromIndex(Result - 1);
|
|
OldLevel := Node.Level;
|
|
end;
|
|
if (Level > OldLevel) or (Node = nil) then
|
|
begin
|
|
if Level - OldLevel > 1 then
|
|
TreeViewError('TTreeStrings.Add: Invalid level, Level='+IntToStr(Level)
|
|
+' OldLevel='+IntToStr(OldLevel));
|
|
end
|
|
else begin
|
|
for I := OldLevel downto Level do
|
|
begin
|
|
Node := Node.Parent;
|
|
if (Node = nil) and (I - Level > 0) then
|
|
TreeViewError('TTreeStrings.Add: Invalid level, Node=nil I='+IntToStr(I)
|
|
+' Level='+IntToStr(Level));
|
|
end;
|
|
end;
|
|
Owner.AddChild(Node, NewStr);
|
|
end;
|
|
|
|
procedure TTreeStrings.Insert(Index: Integer; const S: string);
|
|
begin
|
|
with Owner do
|
|
Insert(GetNodeFromIndex(Index), S);
|
|
end;
|
|
|
|
procedure TTreeStrings.LoadTreeFromStream(Stream: TStream);
|
|
var
|
|
List: TStringList;
|
|
ANode, NextNode: TTreeNode;
|
|
ALevel, i: Integer;
|
|
CurrStr: string;
|
|
ok: boolean;
|
|
begin
|
|
List := TStringList.Create;
|
|
Owner.BeginUpdate;
|
|
ok:=false;
|
|
try
|
|
Clear;
|
|
List.LoadFromStream(Stream);
|
|
ANode := nil;
|
|
for i := 0 to List.Count - 1 do
|
|
begin
|
|
CurrStr := GetBufStart(PChar(List[i]), ALevel);
|
|
if ANode = nil then
|
|
ANode := Owner.AddChild(nil, CurrStr)
|
|
else if ANode.Level = ALevel then
|
|
ANode := Owner.AddChild(ANode.Parent, CurrStr)
|
|
else if ANode.Level = (ALevel - 1) then
|
|
ANode := Owner.AddChild(ANode, CurrStr)
|
|
else if ANode.Level > ALevel then
|
|
begin
|
|
NextNode := ANode.Parent;
|
|
while NextNode.Level > ALevel do
|
|
NextNode := NextNode.Parent;
|
|
ANode := Owner.AddChild(NextNode.Parent, CurrStr);
|
|
end
|
|
else TreeViewError('TTreeStrings.LoadTreeFromStream: Level='
|
|
+IntToStr(ALevel)+' CuurStr="'+CurrStr+'"');
|
|
end;
|
|
ok:=true;
|
|
finally
|
|
Owner.EndUpdate;
|
|
List.Free;
|
|
if not ok then
|
|
Owner.Owner.Invalidate; // force repaint on exception
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeStrings.SaveTreeToStream(Stream: TStream);
|
|
const
|
|
TabChar = #9;
|
|
EndOfLine = #13#10;
|
|
var
|
|
i: Integer;
|
|
ANode: TTreeNode;
|
|
NodeStr: string;
|
|
begin
|
|
if Count > 0 then
|
|
begin
|
|
ANode := Owner[0];
|
|
while ANode <> nil do
|
|
begin
|
|
NodeStr := '';
|
|
for i := 0 to ANode.Level - 1 do NodeStr := NodeStr + TabChar;
|
|
NodeStr := NodeStr + ANode.Text + EndOfLine;
|
|
Stream.Write(Pointer(NodeStr)^, Length(NodeStr));
|
|
ANode := ANode.GetNext;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TTreeStrings.ConsistencyCheck: integer;
|
|
begin
|
|
Result:=0;
|
|
end;
|
|
|
|
procedure TTreeStrings.WriteDebugReport(const Prefix: string);
|
|
begin
|
|
write(Prefix);
|
|
write('TTreeStrings.WriteDebugReport Self=',HexStr(Cardinal(Self),8));
|
|
write(' Consistency=',ConsistencyCheck);
|
|
writeln('');
|
|
end;
|
|
|
|
|
|
{ TCustomTreeView }
|
|
|
|
constructor TCustomTreeView.Create(AnOwner: TComponent);
|
|
begin
|
|
inherited Create(AnOwner);
|
|
ControlStyle := ControlStyle - [csCaptureMouse]
|
|
+ [csDisplayDragImage, csReflector];
|
|
Width := 121;
|
|
Height := 97;
|
|
TabStop := True;
|
|
ParentColor := False;
|
|
FBackgroundColor := clWhite;
|
|
FCanvas := TControlCanvas.Create;
|
|
TControlCanvas(FCanvas).Control := Self;
|
|
FDefItemHeight:=20;
|
|
FExpandSignType:=tvestPlusMinus;
|
|
FExpandSignSize:=9;
|
|
FTreeNodes := TTreeNodes.Create(Self);
|
|
FBorderStyle := bsSingle;
|
|
BorderWidth := 2;
|
|
FOptions := [tvoShowRoot, tvoShowLines, tvoShowButtons, tvoHideSelection,
|
|
tvoToolTips, tvoKeepCollapsedNodes];
|
|
Items.KeepCollapsedNodes:=KeepCollapsedNodes;
|
|
FScrollBars:=ssBoth;
|
|
FDragImage := TDragImageList.CreateSize(32, 32);
|
|
FIndent:=15;
|
|
FChangeTimer := TTimer.Create(Self);
|
|
FChangeTimer.Enabled := False;
|
|
FChangeTimer.Interval := 0;
|
|
FChangeTimer.OnTimer := @OnChangeTimer;
|
|
//FEditInstance := MakeObjectInstance(EditWndProc);
|
|
FImageChangeLink := TChangeLink.Create;
|
|
FImageChangeLink.OnChange := @ImageListChange;
|
|
FSelectedColor:=clHighlight;
|
|
fSeparatorColor:=clGray;
|
|
FStateChangeLink := TChangeLink.Create;
|
|
FStateChangeLink.OnChange := @ImageListChange;
|
|
FStates:=[tvsMaxLvlNeedsUpdate,tvsMaxRightNeedsUpdate,tvsScrollbarChanged];
|
|
FTreeLineColor := clWindowFrame;
|
|
end;
|
|
|
|
destructor TCustomTreeView.Destroy;
|
|
begin
|
|
FTreeNodes.Free;
|
|
FTreeNodes:=nil;
|
|
FChangeTimer.Free;
|
|
FSaveItems.Free;
|
|
FDragImage.Free;
|
|
//FMemStream.Free;
|
|
//FreeObjectInstance(FEditInstance);
|
|
FImageChangeLink.Free;
|
|
FStateChangeLink.Free;
|
|
FCanvas.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TCustomTreeView.CreateParams(var Params: TCreateParams);
|
|
{const
|
|
BorderStyles: array[TBorderStyle] of DWORD = (0, WS_BORDER);
|
|
LineStyles: array[Boolean] of DWORD = (0, TVS_HASLINES);
|
|
RootStyles: array[Boolean] of DWORD = (0, TVS_LINESATROOT);
|
|
ButtonStyles: array[Boolean] of DWORD = (0, TVS_HASBUTTONS);
|
|
EditStyles: array[Boolean] of DWORD = (TVS_EDITLABELS, 0);
|
|
HideSelections: array[Boolean] of DWORD = (TVS_SHOWSELALWAYS, 0);
|
|
DragStyles: array[TDragMode] of DWORD = (TVS_DISABLEDRAGDROP, 0);
|
|
RTLStyles: array[Boolean] of DWORD = (0, TVS_RTLREADING);
|
|
ToolTipStyles: array[Boolean] of DWORD = (TVS_NOTOOLTIPS, 0);
|
|
AutoExpandStyles: array[Boolean] of DWORD = (0, TVS_SINGLEEXPAND);
|
|
HotTrackStyles: array[Boolean] of DWORD = (0, TVS_TRACKSELECT);
|
|
RowSelectStyles: array[Boolean] of DWORD = (0, TVS_FULLROWSELECT);}
|
|
const
|
|
ScrollBar: array[TScrollStyle] of DWORD = (0, WS_HSCROLL, WS_VSCROLL,
|
|
WS_HSCROLL or WS_VSCROLL, WS_HSCROLL, WS_VSCROLL, WS_HSCROLL or WS_VSCROLL);
|
|
BorderStyles: array[TBorderStyle] of DWORD = (0, WS_BORDER);
|
|
ClassStylesOff = CS_VREDRAW or CS_HREDRAW;
|
|
begin
|
|
//InitCommonControl(ICC_TREEVIEW_CLASSES);
|
|
inherited CreateParams(Params);
|
|
//CreateSubClass(Params, WC_TREEVIEW);
|
|
with Params do begin
|
|
{$IFOPT R+}{$DEFINE RangeCheckOn}{$R-}{$ENDIF}
|
|
WindowClass.Style := WindowClass.Style and not Cardinal(ClassStylesOff);
|
|
Style := Style or ScrollBar[FScrollBars] or BorderStyles[fBorderStyle]
|
|
or WS_CLIPCHILDREN;
|
|
{$IFDEF RangeCheckOn}{$R+}{$ENDIF}
|
|
if NewStyleControls and Ctl3D and (fBorderStyle = bsSingle) then begin
|
|
Style := Style and not Cardinal(WS_BORDER);
|
|
ExStyle := ExStyle or WS_EX_CLIENTEDGE;
|
|
end;
|
|
end;
|
|
{with Params do begin
|
|
Style := Style or LineStyles[FShowLines] or BorderStyles[FBorderStyle] or
|
|
RootStyles[FShowRoot] or ButtonStyles[FShowButtons] or
|
|
EditStyles[FReadOnly] or HideSelections[FHideSelection] or
|
|
DragStyles[DragMode] or RTLStyles[UseRightToLeftReading] or
|
|
ToolTipStyles[FToolTips] or AutoExpandStyles[FAutoExpand] or
|
|
HotTrackStyles[FHotTrack] or RowSelectStyles[FRowSelect];
|
|
if Ctl3D and NewStyleControls and (FBorderStyle = bsSingle) then begin
|
|
Style := Style and not WS_BORDER;
|
|
ExStyle := Params.ExStyle or WS_EX_CLIENTEDGE;
|
|
end;
|
|
WindowClass.style := WindowClass.style and not (CS_HREDRAW or CS_VREDRAW);
|
|
end;}
|
|
end;
|
|
|
|
procedure TCustomTreeView.CreateWnd;
|
|
begin
|
|
Exclude(FStates,tvsStateChanging);
|
|
inherited CreateWnd;
|
|
//TreeView_SetBkColor(Handle, ColorToRGB(Color));
|
|
//TreeView_SetTextColor(Handle, ColorToRGB(Font.Color));
|
|
{if FMemStream <> nil then begin
|
|
Items.ReadData(FMemStream);
|
|
Items.ReadExpandedState(FMemStream);
|
|
FMemStream.Destroy;
|
|
FMemStream := nil;
|
|
SetTopItem(Items.GetNodeFromIndex(FSaveTopIndex));
|
|
FSaveTopIndex := 0;
|
|
SetSelection(Items.GetNodeFromIndex(FSaveIndex));
|
|
FSaveIndex := 0;
|
|
end;}
|
|
//if (Images <> nil) and Images.HandleAllocated then
|
|
// SetImageList(Images.Handle, TVSIL_NORMAL);
|
|
//if (StateImages <> nil) and StateImages.HandleAllocated then
|
|
// SetImageList(StateImages.Handle, TVSIL_STATE);
|
|
end;
|
|
|
|
procedure TCustomTreeView.DestroyWnd;
|
|
//var Node: TTreeNode;
|
|
begin
|
|
Include(FStates,tvsStateChanging);
|
|
{if (Items<>nil) and (Items.Count > 0) then begin
|
|
FMemStream := TMemoryStream.Create;
|
|
Items.WriteData(FMemStream);
|
|
Items.WriteExpandedState(FMemStream);
|
|
FMemStream.Position := 0;
|
|
Node := GetTopItem;
|
|
if Node <> nil then FSaveTopIndex := Node.AbsoluteIndex;
|
|
Node := Selected;
|
|
if Node <> nil then FSaveIndex := Node.AbsoluteIndex;
|
|
end;}
|
|
inherited DestroyWnd;
|
|
end;
|
|
|
|
procedure TCustomTreeView.EditWndProc(var Message: TLMessage);
|
|
var ok: boolean;
|
|
begin
|
|
try
|
|
ok:=false;
|
|
with Message do
|
|
begin
|
|
case Msg of
|
|
LM_KEYDOWN,
|
|
LM_SYSKEYDOWN: if DoKeyDown(TLMKey(Message)) then Exit;
|
|
LM_CHAR: if DoKeyPress(TLMKey(Message)) then Exit;
|
|
LM_KEYUP,
|
|
LM_SYSKEYUP: if DoKeyUp(TLMKey(Message)) then Exit;
|
|
CN_KEYDOWN,
|
|
CN_CHAR, CN_SYSKEYDOWN,
|
|
CN_SYSCHAR:
|
|
begin
|
|
WndProc(Message);
|
|
Exit;
|
|
end;
|
|
end;
|
|
Result := CallWindowProc(FDefEditProc, FEditHandle, Msg, WParam, LParam);
|
|
end;
|
|
ok:=true;
|
|
finally
|
|
if not ok then
|
|
Application.HandleException(Self);
|
|
end;
|
|
end;
|
|
|
|
{procedure TCustomTreeView.CMColorChanged(var Message: TLMessage);
|
|
begin
|
|
inherited;
|
|
RecreateWnd;
|
|
end;}
|
|
|
|
{procedure TCustomTreeView.CMCtl3DChanged(var Message: TLMessage);
|
|
begin
|
|
inherited;
|
|
if FBorderStyle = bsSingle then RecreateWnd;
|
|
end;}
|
|
|
|
{procedure TCustomTreeView.CMFontChanged(var Message: TLMessage);
|
|
begin
|
|
inherited;
|
|
TreeView_SetTextColor(Handle, ColorToRGB(Font.Color));
|
|
end;}
|
|
|
|
{procedure TCustomTreeView.CMSysColorChange(var Message: TLMessage);
|
|
begin
|
|
inherited;
|
|
if not (csLoading in ComponentState) then
|
|
begin
|
|
Message.Msg := WM_SYSCOLORCHANGE;
|
|
DefaultHandler(Message);
|
|
end;
|
|
end;}
|
|
|
|
procedure TCustomTreeView.BeginUpdate;
|
|
begin
|
|
inc(FUpdateCount);
|
|
end;
|
|
|
|
procedure TCustomTreeView.EndUpdate;
|
|
begin
|
|
// if FUpdateCount<=0 then
|
|
// writeln('TCustomTreeView.EndUpdate UpdateCount=',FUpdateCount);
|
|
if FUpdateCount<=0 then exit;
|
|
dec(FUpdateCount);
|
|
if FUpdateCount=0 then begin
|
|
// ToDo: only refresh if something changed
|
|
UpdateScrollBars;
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
function TCustomTreeView.AlphaSort: Boolean;
|
|
var
|
|
Node: TTreeNode;
|
|
begin
|
|
if HandleAllocated then begin
|
|
BeginUpdate;
|
|
Result := CustomSort(nil);
|
|
Node := FTreeNodes.GetFirstNode;
|
|
while Node <> nil do begin
|
|
if Node.HasChildren then Node.AlphaSort;
|
|
Node := Node.GetNext;
|
|
end;
|
|
EndUpdate;
|
|
end
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function TCustomTreeView.CustomSort(SortProc: TTreeNodeCompare): Boolean;
|
|
//var SortCB: TTVSortCB;
|
|
var Node: TTreeNode;
|
|
begin
|
|
Result := False;
|
|
if HandleAllocated then begin
|
|
// ToDo: sort root nodes
|
|
|
|
{with SortCB do begin
|
|
if not Assigned(SortProc) then lpfnCompare := @DefaultTreeViewSort
|
|
else lpfnCompare := SortProc;
|
|
hParent := TVI_ROOT;
|
|
lParam := Data;
|
|
Result := TreeView_SortChildrenCB(Handle, SortCB, 0);
|
|
end;}
|
|
Node := FTreeNodes.GetFirstNode;
|
|
while Node <> nil do begin
|
|
if Node.HasChildren then Node.CustomSort(SortProc);
|
|
Node := Node.GetNext;
|
|
end;
|
|
Items.ClearCache;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetAutoExpand(Value: Boolean);
|
|
begin
|
|
if AutoExpand <> Value then begin
|
|
if Value then
|
|
Include(FOptions,tvoAutoExpand)
|
|
else
|
|
Exclude(FOptions,tvoAutoExpand);
|
|
//SetComCtlStyle(Self, TVS_SINGLEEXPAND, Value);
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetHotTrack(Value: Boolean);
|
|
begin
|
|
if HotTrack <> Value then begin
|
|
if Value then
|
|
Include(FOptions,tvoHotTrack)
|
|
else
|
|
Exclude(FOptions,tvoHotTrack);
|
|
//SetComCtlStyle(Self, TVS_TRACKSELECT, Value);
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetRowSelect(Value: Boolean);
|
|
begin
|
|
if RowSelect <> Value then begin
|
|
if Value then
|
|
Include(FOptions,tvoRowSelect)
|
|
else
|
|
Exclude(FOptions,tvoRowSelect);
|
|
if FSelectedNode<>nil then
|
|
Invalidate;
|
|
//SetComCtlStyle(Self, TVS_FULLROWSELECT, Value);
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetScrollBars(const Value: TScrollStyle);
|
|
begin
|
|
if (FScrollBars <> Value) then begin
|
|
FScrollBars := Value;
|
|
RecreateWnd;
|
|
Include(FStates,tvsScrollbarChanged);
|
|
UpdateScrollBars;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetScrolledLeft(AValue: integer);
|
|
begin
|
|
//writeln('@@@@@ ',FScrolledTop,',',AValue);
|
|
if AValue<0 then AValue:=0;
|
|
if AValue=FScrolledLeft then exit;
|
|
if AValue>GetMaxScrollLeft then AValue:=GetMaxScrollLeft;
|
|
if AValue=FScrolledLeft then exit;
|
|
FScrolledLeft:=AValue;
|
|
Include(FStates,tvsScrollbarChanged);
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetScrolledTop(AValue: integer);
|
|
begin
|
|
//writeln('$$$$$ ',FScrolledTop,',',AValue);
|
|
if FScrolledTop=AValue then exit;
|
|
if AValue<0 then AValue:=0;
|
|
if AValue>GetMaxScrollTop then AValue:=GetMaxScrollTop;
|
|
if AValue=FScrolledTop then exit;
|
|
FScrolledTop:=AValue;
|
|
FStates:=FStates+[tvsTopItemNeedsUpdate,tvsBottomItemNeedsUpdate,
|
|
tvsScrollbarChanged];
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetToolTips(Value: Boolean);
|
|
begin
|
|
if ToolTips <> Value then begin
|
|
if Value then
|
|
Include(FOptions,tvoToolTips)
|
|
else
|
|
Exclude(FOptions,tvoToolTips);
|
|
//SetComCtlStyle(Self, TVS_NOTOOLTIPS, not Value);
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetSortType(Value: TSortType);
|
|
begin
|
|
if SortType <> Value then begin
|
|
FSortType := Value;
|
|
if ((SortType in [stData, stBoth]) and Assigned(OnCompare)) or
|
|
(SortType in [stText, stBoth]) then
|
|
AlphaSort;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetBackgroundColor(Value: TColor);
|
|
begin
|
|
if FBackgroundColor<>Value then begin
|
|
FBackgroundColor:=Value;
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetSelectedColor(Value: TColor);
|
|
begin
|
|
if FSelectedColor<>Value then begin
|
|
FSelectedColor:=Value;
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetBorderStyle(Value: TBorderStyle);
|
|
begin
|
|
if BorderStyle <> Value then begin
|
|
FBorderStyle := Value;
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.Paint;
|
|
begin
|
|
DoPaint;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetDragMode(Value: TDragMode);
|
|
begin
|
|
// ToDo: implement Drag&Drop
|
|
//if Value <> DragMode then
|
|
// SetComCtlStyle(Self, TVS_DISABLEDRAGDROP, Value = dmManual);
|
|
inherited;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetOptions(NewOptions: TTreeViewOptions);
|
|
var ChangedOptions: TTreeViewOptions;
|
|
begin
|
|
if FOptions=NewOptions then exit;
|
|
ChangedOptions:=(FOptions-NewOptions)+(NewOptions-FOptions);
|
|
FOptions:=NewOptions;
|
|
if (tvoKeepCollapsedNodes) in ChangedOptions then
|
|
Items.KeepCollapsedNodes:=(tvoKeepCollapsedNodes in FOptions);
|
|
if (tvoReadOnly in ChangedOptions) and (not (tvoReadOnly in FOptions)) then
|
|
EndEditing;
|
|
if ([tvoHideSelection,tvoReadOnly,tvoShowButtons,tvoShowRoot,tvoShowLines]
|
|
* ChangedOptions)<>[] then
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomTreeView.UpdateAllTops;
|
|
|
|
procedure CalculateTops(FirstSibling: TTreeNode; var CurTop: integer;
|
|
Expanded: boolean);
|
|
begin
|
|
while FirstSibling<>nil do begin
|
|
FirstSibling.fTop:=CurTop;
|
|
if Expanded then
|
|
inc(CurTop,FirstSibling.Height);
|
|
CalculateTops(FirstSibling.GetFirstChild,CurTop,
|
|
Expanded and FirstSibling.Expanded);
|
|
FirstSibling:=FirstSibling.GetNextSibling;
|
|
end;
|
|
end;
|
|
|
|
var i: integer;
|
|
begin
|
|
if not (tvsTopsNeedsUpdate in FStates) then exit;
|
|
i:=0;
|
|
CalculateTops(Items.GetFirstNode,i,true);
|
|
Exclude(FStates,tvsTopsNeedsUpdate);
|
|
Include(FStates,tvsScrollbarChanged);
|
|
end;
|
|
|
|
procedure TCustomTreeView.UpdateMaxLvl;
|
|
|
|
procedure LookInChildsAndBrothers(Node: TTreeNode; CurLvl: integer);
|
|
begin
|
|
if Node=nil then exit;
|
|
if CurLvl>FMaxLvl then FMaxLvl:=CurLvl;
|
|
LookInChildsAndBrothers(Node.GetFirstChild,CurLvl+1);
|
|
LookInChildsAndBrothers(Node.GetNextSibling,CurLvl);
|
|
end;
|
|
|
|
begin
|
|
if not (tvsMaxLvlNeedsUpdate in FStates) then exit;
|
|
FMaxLvl:=0;
|
|
LookInChildsAndBrothers(Items.GetFirstNode,1);
|
|
Exclude(FStates,tvsMaxRightNeedsUpdate);
|
|
end;
|
|
|
|
procedure TCustomTreeView.UpdateMaxRight;
|
|
var Node: TTreeNode;
|
|
i: integer;
|
|
begin
|
|
if not (tvsMaxRightNeedsUpdate in FStates) then exit;
|
|
FMaxRight:=0;
|
|
Node:=Items.GetFirstNode;
|
|
while Node<>nil do begin
|
|
i:=Node.DisplayTextRight;
|
|
if FMaxRight<i then FMaxRight:=i;
|
|
Node:=Node.GetNext;
|
|
end;
|
|
Exclude(FStates,tvsMaxRightNeedsUpdate);
|
|
Include(FStates,tvsScrollbarChanged);
|
|
end;
|
|
|
|
procedure TCustomTreeView.UpdateTopItem;
|
|
begin
|
|
//writeln('TCustomTreeView.UpdateTopItem tvsTopItemNeedsUpdate in FStates=',tvsTopItemNeedsUpdate in FStates);
|
|
if (FStates*[tvsTopItemNeedsUpdate,tvsTopsNeedsUpdate]=[]) then exit;
|
|
FTopItem:=GetNodeAtY(BorderWidth);
|
|
Exclude(FStates,tvsTopItemNeedsUpdate);
|
|
end;
|
|
|
|
procedure TCustomTreeView.UpdateBottomItem;
|
|
begin
|
|
if (FStates*[tvsTopItemNeedsUpdate,tvsTopsNeedsUpdate,
|
|
tvsBottomItemNeedsUpdate]=[])
|
|
then exit;
|
|
if not (tvsBottomItemNeedsUpdate in FStates) then exit;
|
|
FBottomItem:=TopItem;
|
|
while (FBottomItem<>nil) and (FBottomItem.GetNextVisible<>nil) do
|
|
FBottomItem:=FBottomItem.GetNextVisible;
|
|
Exclude(FStates,tvsBottomItemNeedsUpdate);
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetBottomItem(Value: TTreeNode);
|
|
begin
|
|
if HandleAllocated and (Value <> nil) then begin
|
|
Value.MakeVisible;
|
|
ScrolledTop:=Value.Top+Value.Height-(ClientHeight-ScrollBarWidth);
|
|
//TreeView_SelectSetFirstVisible(Handle, Value.ItemId);
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetShowButton(Value: Boolean);
|
|
begin
|
|
if ShowButtons <> Value then begin
|
|
if Value then
|
|
Include(FOptions,tvoShowButtons)
|
|
else
|
|
Exclude(FOptions,tvoShowButtons);
|
|
Invalidate;
|
|
//SetComCtlStyle(Self, TVS_HASBUTTONS, Value);
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetShowLines(Value: Boolean);
|
|
begin
|
|
if ShowLines <> Value then begin
|
|
if Value then
|
|
Include(FOptions,tvoShowLines)
|
|
else
|
|
Exclude(FOptions,tvoShowLines);
|
|
Invalidate;
|
|
//SetComCtlStyle(Self, TVS_HASLINES, Value);
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetShowRoot(Value: Boolean);
|
|
begin
|
|
if ShowRoot <> Value then begin
|
|
if Value then
|
|
Include(FOptions,tvoShowRoot)
|
|
else
|
|
Exclude(FOptions,tvoShowRoot);
|
|
Invalidate;
|
|
//SetComCtlStyle(Self, TVS_LINESATROOT, Value);
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetShowSeparators(Value: Boolean);
|
|
begin
|
|
if ShowSeparators <> Value then begin
|
|
if Value then
|
|
Include(FOptions,tvoShowSeparators)
|
|
else
|
|
Exclude(FOptions,tvoShowSeparators);
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetKeepCollapsedNodes(Value: Boolean);
|
|
begin
|
|
if KeepCollapsedNodes=Value then exit;
|
|
if Value then
|
|
Include(FOptions,tvoKeepCollapsedNodes)
|
|
else
|
|
Exclude(FOptions,tvoKeepCollapsedNodes);
|
|
Items.KeepCollapsedNodes:=Value;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetReadOnly(Value: Boolean);
|
|
begin
|
|
if ReadOnly <> Value then begin
|
|
if Value then
|
|
Include(FOptions,tvoRightClickSelect)
|
|
else
|
|
Exclude(FOptions,tvoRightClickSelect);
|
|
if not Value then EndEditing;
|
|
//SetComCtlStyle(Self, TVS_EDITLABELS, not Value);
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetRightClickSelect(Value: Boolean);
|
|
begin
|
|
if Value then
|
|
Include(FOptions,tvoRightClickSelect)
|
|
else
|
|
Exclude(FOptions,tvoRightClickSelect);
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetHideSelection(Value: Boolean);
|
|
begin
|
|
if HideSelection <> Value then begin
|
|
if Value then
|
|
Include(FOptions,tvoHideSelection)
|
|
else
|
|
Exclude(FOptions,tvoHideSelection);
|
|
//SetComCtlStyle(Self, TVS_SHOWSELALWAYS, not Value);
|
|
if FSelectedNode<>nil then Invalidate;
|
|
end;
|
|
end;
|
|
|
|
function TCustomTreeView.GetMaxLvl: integer;
|
|
begin
|
|
UpdateMaxRight;
|
|
Result:=FMaxRight;
|
|
end;
|
|
|
|
function TCustomTreeView.GetMaxScrollLeft: integer;
|
|
begin
|
|
UpdateMaxRight;
|
|
Result:=FMaxRight-(ClientWidth-ScrollBarWidth-2*BorderWidth);
|
|
if Result<0 then Result:=0;
|
|
end;
|
|
|
|
function TCustomTreeView.GetMaxScrollTop: integer;
|
|
var LastVisibleNode: TTreeNode;
|
|
begin
|
|
LastVisibleNode:=Items.GetLastExpandedSubNode;
|
|
if LastVisibleNode=nil then
|
|
Result:=0
|
|
else begin
|
|
Result:=LastVisibleNode.Top+LastVisibleNode.Height
|
|
-(ClientHeight-ScrollBarWidth)+2*BorderWidth;
|
|
//writeln('>>> ',LastVisibleNode.Text,' ',Result);
|
|
if Result<0 then Result:=0;
|
|
end;
|
|
end;
|
|
|
|
function TCustomTreeView.GetNodeAtInternalY(Y: Integer): TTreeNode;
|
|
// search in all expanded nodes for the node at the absolute coordinate Y
|
|
var i: integer;
|
|
begin
|
|
i:=IndexOfNodeAtTop(Items.FTopLvlItems,Items.FTopLvlCount,Y);
|
|
if i>=0 then begin
|
|
Result:=Items.FTopLvlItems[i];
|
|
while Result.Expanded do begin
|
|
i:=IndexOfNodeAtTop(Result.FItems,Result.FCount,Y);
|
|
if i>=0 then
|
|
Result:=Result.Items[i]
|
|
else
|
|
break;
|
|
end;
|
|
end else
|
|
Result:=nil;
|
|
end;
|
|
|
|
function TCustomTreeView.GetNodeAtY(Y: Integer): TTreeNode;
|
|
// search in all expanded nodes for the node at the screen coordinate Y
|
|
begin
|
|
Result:=nil;
|
|
if (Y>=BorderWidth) and (Y<(ClientHeight-ScrollBarWidth)-BorderWidth) then
|
|
begin
|
|
inc(Y,FScrolledTop-BorderWidth);
|
|
Result:=GetNodeAtInternalY(Y);
|
|
end;
|
|
end;
|
|
|
|
function TCustomTreeView.GetNodeDrawAreaWidth: integer;
|
|
begin
|
|
Result:=ClientWidth-ScrollBarWidth-BorderWidth*2;
|
|
end;
|
|
|
|
function TCustomTreeView.GetNodeDrawAreaHeight: integer;
|
|
begin
|
|
Result:=ClientHeight-ScrollBarWidth-BorderWidth*2;
|
|
end;
|
|
|
|
function TCustomTreeView.GetNodeAt(X, Y: Integer): TTreeNode;
|
|
//var HitTest: TTVHitTestInfo;
|
|
begin
|
|
Result:=nil;
|
|
if (X>=BorderWidth) and (X<ClientWidth-BorderWidth) then begin
|
|
Result:=GetNodeAtY(Y);
|
|
if Result<>nil then begin
|
|
inc(X,FScrolledLeft-BorderWidth);
|
|
if (X<Result.DisplayExpandSignLeft) then
|
|
Result:=nil;
|
|
end;
|
|
end;
|
|
{with HitTest do begin
|
|
pt.X := X;
|
|
pt.Y := Y;
|
|
if TreeView_HitTest(Handle, HitTest) <> nil then
|
|
Result := Items.GetNode(HitTest.hItem)
|
|
else
|
|
Result := nil;
|
|
end;}
|
|
end;
|
|
|
|
function TCustomTreeView.GetHitTestInfoAt(X, Y: Integer): THitTests;
|
|
//var HitTest: TTVHitTestInfo;
|
|
var Node: TTreeNode;
|
|
begin
|
|
// ToDo
|
|
Result := [];
|
|
if (X>=0) and (X<ClientWidth) and (Y>=0) and (Y<(ClientHeight-ScrollBarWidth))
|
|
then begin
|
|
inc(Y,FScrolledTop);
|
|
Node:=GetNodeAtY(Y);
|
|
if Node<>nil then begin
|
|
inc(X,FScrolledLeft);
|
|
if X<Node.DisplayExpandSignLeft then
|
|
Include(Result,htOnIndent)
|
|
else if X<Node.DisplayIconLeft then
|
|
Include(Result,htOnButton)
|
|
else if X<Node.DisplayStateIconLeft then
|
|
Include(Result,htOnItem)
|
|
else if X<Node.DisplayTextLeft then
|
|
Include(Result,htOnStateIcon)
|
|
else if X<Node.DisplayTextRight then
|
|
Include(Result,htOnLabel);
|
|
end else
|
|
Include(Result,htNowhere);
|
|
end;
|
|
{with HitTest do begin
|
|
pt.X := X;
|
|
pt.Y := Y;
|
|
TreeView_HitTest(Handle, HitTest);
|
|
if (flags and TVHT_ABOVE) <> 0 then Include(Result, htAbove);
|
|
if (flags and TVHT_BELOW) <> 0 then Include(Result, htBelow);
|
|
if (flags and TVHT_NOWHERE) <> 0 then Include(Result, htNowhere);
|
|
if (flags and TVHT_ONITEM) = TVHT_ONITEM then
|
|
Include(Result, htOnItem)
|
|
else
|
|
begin
|
|
if (flags and TVHT_ONITEM) <> 0 then Include(Result, htOnItem);
|
|
if (flags and TVHT_ONITEMICON) <> 0 then Include(Result, htOnIcon);
|
|
if (flags and TVHT_ONITEMLABEL) <> 0 then Include(Result, htOnLabel);
|
|
if (flags and TVHT_ONITEMSTATEICON) <> 0 then Include(Result, htOnStateIcon);
|
|
end;
|
|
if (flags and TVHT_ONITEMBUTTON) <> 0 then Include(Result, htOnButton);
|
|
if (flags and TVHT_ONITEMINDENT) <> 0 then Include(Result, htOnIndent);
|
|
if (flags and TVHT_ONITEMRIGHT) <> 0 then Include(Result, htOnRight);
|
|
if (flags and TVHT_TOLEFT) <> 0 then Include(Result, htToLeft);
|
|
if (flags and TVHT_TORIGHT) <> 0 then Include(Result, htToRight);
|
|
end;}
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetTreeLineColor(Value: TColor);
|
|
begin
|
|
if FTreeLineColor<>Value then begin
|
|
FTreeLineColor:=Value;
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetTreeNodes(Value: TTreeNodes);
|
|
begin
|
|
Items.Assign(Value);
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetIndent(Value: Integer);
|
|
begin
|
|
if Value <> Indent then begin
|
|
FIndent := Value;
|
|
Invalidate;
|
|
//TreeView_SetIndent(Handle, Value);
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.FullExpand;
|
|
var
|
|
Node: TTreeNode;
|
|
begin
|
|
Node := Items.GetFirstNode;
|
|
while Node <> nil do begin
|
|
Node.Expand(True);
|
|
Node := Node.GetNextSibling;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.FullCollapse;
|
|
var
|
|
Node: TTreeNode;
|
|
begin
|
|
Node := Items.GetFirstNode;
|
|
while Node <> nil do begin
|
|
Node.Collapse(True);
|
|
Node := Node.GetNextSibling;
|
|
end;
|
|
end;
|
|
|
|
function TCustomTreeView.IsNodeVisible(ANode: TTreeNode): Boolean;
|
|
begin
|
|
Result:=(ANode<>nil) and (ANode.AreParentsExpanded);
|
|
//writeln('[TCustomTreeView.IsNodeVisible] A Node=',HexStr(Cardinal(ANode),8),
|
|
//' ANode.AreParentsExpanded=',ANode.AreParentsExpanded);
|
|
if Result then begin
|
|
//writeln('[TCustomTreeView.IsNodeVisible] B Node=',HexStr(Cardinal(ANode),8),
|
|
//' ',FScrolledTop,'>=',ANode.Top,'+',ANode.Height,' or ',FScrolledTop,'+',ClientHeight,'<',ANode.Top);
|
|
if (FScrolledTop>=ANode.Top+ANode.Height)
|
|
or (FScrolledTop+(ClientHeight-ScrollBarWidth)-2*BorderWidth<ANode.Top)
|
|
then
|
|
Result:=false;
|
|
end;
|
|
//writeln('[TCustomTreeView.IsNodeVisible] END Node=',HexStr(Cardinal(ANode),8),
|
|
//' Node.Text=',ANode.Text,' Visible=',Result);
|
|
end;
|
|
|
|
procedure TCustomTreeView.Loaded;
|
|
begin
|
|
inherited Loaded;
|
|
if csDesigning in ComponentState then FullExpand;
|
|
end;
|
|
|
|
function TCustomTreeView.GetTopItem: TTreeNode;
|
|
begin
|
|
if HandleAllocated then begin
|
|
UpdateTopItem;
|
|
Result := FTopItem;
|
|
end else
|
|
Result := nil;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetTopItem(Value: TTreeNode);
|
|
begin
|
|
if HandleAllocated and (Value <> nil) then begin
|
|
Value.MakeVisible;
|
|
ScrolledTop:=Value.Top;
|
|
//TreeView_SelectSetFirstVisible(Handle, Value.ItemId);
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.OnChangeTimer(Sender: TObject);
|
|
begin
|
|
FChangeTimer.Enabled := False;
|
|
Change(TTreeNode(FChangeTimer.Tag));
|
|
end;
|
|
|
|
procedure TCustomTreeView.UpdateScrollbars;
|
|
var
|
|
ScrollInfo: TScrollInfo;
|
|
begin
|
|
if not (tvsScrollbarChanged in FStates) then exit;
|
|
if not HandleAllocated or (FUpdateCount>0) then begin
|
|
//Include(FStates,tvsScrollbarChanged);
|
|
end else begin
|
|
if ScrolledLeft>GetMaxScrollLeft then ScrolledLeft:=GetMaxScrollLeft;
|
|
if ScrolledTop>GetMaxScrollTop then ScrolledTop:=GetMaxScrollTop;
|
|
Exclude(FStates,tvsScrollbarChanged);
|
|
if fScrollBars <> ssNone then begin
|
|
ScrollInfo.cbSize := SizeOf(ScrollInfo);
|
|
ScrollInfo.fMask := SIF_ALL or SIF_DISABLENOSCROLL;
|
|
ScrollInfo.nTrackPos := 0;
|
|
if fScrollBars in [ssBoth, ssHorizontal] then begin
|
|
// horizontal scrollbar
|
|
ScrollInfo.nMin := 0;
|
|
ScrollInfo.nPage := (ClientWidth-ScrollBarWidth)-2*BorderWidth;
|
|
if ScrollInfo.nPage<1 then ScrollInfo.nPage:=1;
|
|
ScrollInfo.nMax := GetMaxScrollLeft+ScrollInfo.nPage;
|
|
if ScrollInfo.nMax<1 then ScrollInfo.nMax:=1;
|
|
ScrollInfo.nPos := FScrolledLeft;
|
|
if not CompareMem(@ScrollInfo,@FLastHorzScrollInfo,SizeOf(TScrollInfo))
|
|
then begin
|
|
FLastHorzScrollInfo:=ScrollInfo;
|
|
SetScrollInfo(Handle, SB_HORZ, ScrollInfo, True);
|
|
ShowScrollBar(Handle,SB_HORZ,True);
|
|
end;
|
|
//writeln('>>>>>>>>>> [TCustomTreeView.UpdateScrollbars] nMin=',ScrollInfo.nMin,
|
|
//' nMax=',ScrollInfo.nMax,' nPage=',ScrollInfo.nPage,
|
|
//' nPos=',ScrollInfo.nPos,' GetMaxScrollLeft=',GetMaxScrollLeft,
|
|
//' ClientW=',ClientWidth,
|
|
//' MaxRight=',FMaxRight
|
|
//);
|
|
end else begin
|
|
|
|
// ToDo: tell interface to remove horizontal scrollbar
|
|
|
|
end;
|
|
if fScrollBars in [ssBoth, ssVertical] then begin
|
|
// vertical scrollbar
|
|
ScrollInfo.nMin := 0;
|
|
ScrollInfo.nPage := (ClientHeight-ScrollBarWidth)-FDefItemHeight;
|
|
if ScrollInfo.nPage<1 then ScrollInfo.nPage:=1;
|
|
ScrollInfo.nMax := GetMaxScrollTop+ScrollInfo.nPage;
|
|
if ScrollInfo.nMax<1 then ScrollInfo.nMax:=1;
|
|
ScrollInfo.nTrackPos := 0;
|
|
ScrollInfo.nPos := FScrolledTop;
|
|
if not CompareMem(@ScrollInfo,@FLastVertScrollInfo,SizeOf(TScrollInfo))
|
|
then begin
|
|
FLastVertScrollInfo:=ScrollInfo;
|
|
SetScrollInfo(Handle, SB_VERT, ScrollInfo, True);
|
|
ShowScrollBar(Handle,SB_VERT,True);
|
|
end;
|
|
//writeln('>>>>>>>>>> [TCustomTreeView.UpdateScrollbars] nMin=',ScrollInfo.nMin,
|
|
//' nMax=',ScrollInfo.nMax,' nPage=',ScrollInfo.nPage,
|
|
//' nPos=',ScrollInfo.nPos,' GetMaxScrollTop=',GetMaxScrollTop);
|
|
end else begin
|
|
|
|
// ToDo: tell interface to remove vertical scrollbar
|
|
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TCustomTreeView.GetSelection: TTreeNode;
|
|
begin
|
|
if HandleAllocated then
|
|
begin
|
|
if RightClickSelect and Assigned(FRClickNode) then
|
|
Result := FRClickNode
|
|
else
|
|
Result := FSelectedNode;
|
|
end
|
|
else Result := nil;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetSelection(Value: TTreeNode);
|
|
begin
|
|
if FSelectedNode=Value then exit;
|
|
FSelectedNode:=Value;
|
|
if Value <> nil then begin
|
|
Value.Selected := True;
|
|
Value.MakeVisible;
|
|
end;
|
|
Invalidate;
|
|
end;
|
|
|
|
function TCustomTreeView.GetShowButtons: boolean;
|
|
begin
|
|
Result:=(tvoShowButtons in FOptions);
|
|
end;
|
|
|
|
function TCustomTreeView.GetShowLines: boolean;
|
|
begin
|
|
Result:=(tvoShowLines in FOptions);
|
|
end;
|
|
|
|
function TCustomTreeView.GetShowRoot: boolean;
|
|
begin
|
|
Result:=(tvoShowRoot in FOptions);
|
|
end;
|
|
|
|
function TCustomTreeView.GetShowSeparators: boolean;
|
|
begin
|
|
Result:=(tvoShowSeparators in FOptions);
|
|
end;
|
|
|
|
function TCustomTreeView.GetToolTips: boolean;
|
|
begin
|
|
Result:=(tvoToolTips in FOptions);
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetExpandSignType(Value: TTreeViewExpandSignType);
|
|
begin
|
|
if Value=FExpandSignType then exit;
|
|
FExpandSignType:=Value;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetChangeDelay(Value: Integer);
|
|
begin
|
|
FChangeTimer.Interval := Value;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetDefaultItemHeight(Value: integer);
|
|
begin
|
|
if Value<=0 then Value:=20;
|
|
if Value=FDefItemHeight then exit;
|
|
FDefItemHeight:=Value;
|
|
Include(FStates,tvsTopsNeedsUpdate);
|
|
Invalidate;
|
|
end;
|
|
|
|
function TCustomTreeView.GetAutoExpand: boolean;
|
|
begin
|
|
Result:=(tvoAutoExpand in FOptions);
|
|
end;
|
|
|
|
function TCustomTreeView.GetBottomItem: TTreeNode;
|
|
begin
|
|
if HandleAllocated then begin
|
|
UpdateBottomItem;
|
|
Result := FBottomItem;
|
|
end else
|
|
Result := nil;
|
|
end;
|
|
|
|
function TCustomTreeView.GetChangeDelay: Integer;
|
|
begin
|
|
Result := FChangeTimer.Interval;
|
|
end;
|
|
|
|
{function TCustomTreeView.GetDropTarget: TTreeNode;
|
|
begin
|
|
if HandleAllocated then
|
|
begin
|
|
Result := Items.GetNode(TreeView_GetDropHilite(Handle));
|
|
if Result = nil then Result := FLastDropTarget;
|
|
end
|
|
else Result := nil;
|
|
end;}
|
|
|
|
function TCustomTreeView.GetHideSelection: boolean;
|
|
begin
|
|
Result:=(tvoHideSelection in FOptions);
|
|
end;
|
|
|
|
function TCustomTreeView.GetHotTrack: boolean;
|
|
begin
|
|
Result:=(tvoHotTrack in FOptions);
|
|
end;
|
|
|
|
function TCustomTreeView.GetKeepCollapsedNodes: boolean;
|
|
begin
|
|
Result:=(tvoKeepCollapsedNodes in FOptions);
|
|
end;
|
|
|
|
function TCustomTreeView.GetReadOnly: boolean;
|
|
begin
|
|
Result:=(tvoReadOnly in FOptions);
|
|
end;
|
|
|
|
function TCustomTreeView.GetRightClickSelect: boolean;
|
|
begin
|
|
Result:=(tvoRightClickSelect in FOptions);
|
|
end;
|
|
|
|
function TCustomTreeView.GetRowSelect: boolean;
|
|
begin
|
|
Result:=(tvoRowSelect in FOptions);
|
|
end;
|
|
|
|
{procedure TCustomTreeView.SetDropTarget(Value: TTreeNode);
|
|
begin
|
|
if HandleAllocated then
|
|
if Value <> nil then Value.DropTarget := True
|
|
else TreeView_SelectDropTarget(Handle, nil);
|
|
end;}
|
|
|
|
{function TCustomTreeView.GetNodeFromItem(const Item: TTVItem): TTreeNode;
|
|
begin
|
|
Result := nil;
|
|
if Items <> nil then
|
|
with Item do
|
|
if (state and TVIF_PARAM) <> 0 then Result := Pointer(lParam)
|
|
else Result := Items.GetNode(hItem);
|
|
end;
|
|
}
|
|
function TCustomTreeView.IsEditing: Boolean;
|
|
//var ControlHand: HWnd;
|
|
begin
|
|
Result:=tvsIsEditing in FStates;
|
|
//ControlHand := TreeView_GetEditControl(Handle);
|
|
//Result := (ControlHand <> 0) and IsWindowVisible(ControlHand);
|
|
end;
|
|
|
|
{procedure TCustomTreeView.CNNotify(var Message: TWMNotify);
|
|
var
|
|
Node: TTreeNode;
|
|
MousePos: TPoint;
|
|
R: TRect;
|
|
DefaultDraw, PaintImages: Boolean;
|
|
TmpItem: TTVItem;
|
|
LogFont: TLogFont;
|
|
begin
|
|
with Message do
|
|
case NMHdr^.code of
|
|
NM_CUSTOMDRAW:
|
|
with PNMCustomDraw(NMHdr)^ do
|
|
begin
|
|
FCanvas.Lock;
|
|
try
|
|
Result := CDRF_DODEFAULT;
|
|
if (dwDrawStage and CDDS_ITEM) = 0 then
|
|
begin
|
|
R := ClientRect;
|
|
case dwDrawStage of
|
|
CDDS_PREPAINT:
|
|
begin
|
|
if IsCustomDrawn(dtControl, cdPrePaint) then
|
|
begin
|
|
try
|
|
FCanvas.Handle := hdc;
|
|
FCanvas.Font := Font;
|
|
FCanvas.Brush := Brush;
|
|
DefaultDraw := CustomDraw(R, cdPrePaint);
|
|
finally
|
|
FCanvas.Handle := 0;
|
|
end;
|
|
if not DefaultDraw then
|
|
begin
|
|
Result := CDRF_SKIPDEFAULT;
|
|
Exit;
|
|
end;
|
|
end;
|
|
if IsCustomDrawn(dtItem, cdPrePaint) or IsCustomDrawn(dtItem, cdPreErase) then
|
|
Result := Result or CDRF_NOTIFYITEMDRAW;
|
|
if IsCustomDrawn(dtItem, cdPostPaint) then
|
|
Result := Result or CDRF_NOTIFYPOSTPAINT;
|
|
if IsCustomDrawn(dtItem, cdPostErase) then
|
|
Result := Result or CDRF_NOTIFYPOSTERASE;
|
|
end;
|
|
CDDS_POSTPAINT:
|
|
if IsCustomDrawn(dtControl, cdPostPaint) then
|
|
CustomDraw(R, cdPostPaint);
|
|
CDDS_PREERASE:
|
|
if IsCustomDrawn(dtControl, cdPreErase) then
|
|
CustomDraw(R, cdPreErase);
|
|
CDDS_POSTERASE:
|
|
if IsCustomDrawn(dtControl, cdPostErase) then
|
|
CustomDraw(R, cdPostErase);
|
|
end;
|
|
end else
|
|
begin
|
|
FillChar(TmpItem, SizeOf(TmpItem), 0);
|
|
TmpItem.hItem := HTREEITEM(dwItemSpec);
|
|
Node := GetNodeFromItem(TmpItem);
|
|
if Node = nil then Exit;
|
|
case dwDrawStage of
|
|
CDDS_ITEMPREPAINT:
|
|
try
|
|
FCanvas.Handle := hdc;
|
|
FCanvas.Font := Font;
|
|
FCanvas.Brush := Brush;
|
|
// Unlike the list view, the tree view doesn't override the text
|
|
// foreground and background colors of selected items.
|
|
if uItemState and CDIS_SELECTED <> 0 then
|
|
begin
|
|
FCanvas.Font.Color := clHighlightText;
|
|
FCanvas.Brush.Color := clHighlight;
|
|
end;
|
|
FCanvas.Font.OnChange := CanvasChanged;
|
|
FCanvas.Brush.OnChange := CanvasChanged;
|
|
FCanvasChanged := False;
|
|
DefaultDraw := CustomDrawItem(Node,
|
|
TCustomDrawState(Word(uItemState)), cdPrePaint, PaintImages);
|
|
if not PaintImages then
|
|
Result := Result or TVCDRF_NOIMAGES;
|
|
if not DefaultDraw then
|
|
Result := Result or CDRF_SKIPDEFAULT
|
|
else if FCanvasChanged then
|
|
begin
|
|
FCanvasChanged := False;
|
|
FCanvas.Font.OnChange := nil;
|
|
FCanvas.Brush.OnChange := nil;
|
|
with PNMTVCustomDraw(NMHdr)^ do
|
|
begin
|
|
clrText := ColorToRGB(FCanvas.Font.Color);
|
|
clrTextBk := ColorToRGB(FCanvas.Brush.Color);
|
|
if GetObject(FCanvas.Font.Handle, SizeOf(LogFont), @LogFont) <> 0 then
|
|
begin
|
|
FCanvas.Handle := 0; // disconnect from hdc
|
|
// don't delete the stock font
|
|
SelectObject(hdc, CreateFontIndirect(LogFont));
|
|
Result := Result or CDRF_NEWFONT;
|
|
end;
|
|
end;
|
|
end;
|
|
if IsCustomDrawn(dtItem, cdPostPaint) then
|
|
Result := Result or CDRF_NOTIFYPOSTPAINT;
|
|
finally
|
|
FCanvas.Handle := 0;
|
|
end;
|
|
CDDS_ITEMPOSTPAINT:
|
|
if IsCustomDrawn(dtItem, cdPostPaint) then
|
|
CustomDrawItem(Node, TCustomDrawState(Word(uItemState)), cdPostPaint, PaintImages);
|
|
CDDS_ITEMPREERASE:
|
|
if IsCustomDrawn(dtItem, cdPreErase) then
|
|
CustomDrawItem(Node, TCustomDrawState(Word(uItemState)), cdPreErase, PaintImages);
|
|
CDDS_ITEMPOSTERASE:
|
|
if IsCustomDrawn(dtItem, cdPostErase) then
|
|
CustomDrawItem(Node, TCustomDrawState(Word(uItemState)), cdPostErase, PaintImages);
|
|
end;
|
|
end;
|
|
finally
|
|
FCanvas.Unlock;
|
|
end;
|
|
end;
|
|
TVN_BEGINDRAG:
|
|
begin
|
|
FDragged := True;
|
|
with PNMTreeView(NMHdr)^ do
|
|
FDragNode := GetNodeFromItem(ItemNew);
|
|
end;
|
|
TVN_BEGINLABELEDIT:
|
|
begin
|
|
with PTVDispInfo(NMHdr)^ do
|
|
if Dragging or not CanEdit(GetNodeFromItem(item)) then
|
|
Result := 1;
|
|
if Result = 0 then
|
|
begin
|
|
FEditHandle := TreeView_GetEditControl(Handle);
|
|
FDefEditProc := Pointer(GetWindowLong(FEditHandle, GWL_WNDPROC));
|
|
SetWindowLong(FEditHandle, GWL_WNDPROC, LongInt(FEditInstance));
|
|
end;
|
|
end;
|
|
TVN_ENDLABELEDIT: Edit(PTVDispInfo(NMHdr)^.item);
|
|
TVN_ITEMEXPANDING:
|
|
if not FManualNotify then
|
|
begin
|
|
with PNMTreeView(NMHdr)^ do
|
|
begin
|
|
Node := GetNodeFromItem(ItemNew);
|
|
if (action = TVE_EXPAND) and not CanExpand(Node) then
|
|
Result := 1
|
|
else if (action = TVE_COLLAPSE) and
|
|
not CanCollapse(Node) then Result := 1;
|
|
end;
|
|
end;
|
|
TVN_ITEMEXPANDED:
|
|
if not FManualNotify then
|
|
begin
|
|
with PNMTreeView(NMHdr)^ do
|
|
begin
|
|
Node := GetNodeFromItem(itemNew);
|
|
if (action = TVE_EXPAND) then Expand(Node)
|
|
else if (action = TVE_COLLAPSE) then Collapse(Node);
|
|
end;
|
|
end;
|
|
TVN_SELCHANGINGA, TVN_SELCHANGINGW:
|
|
if not CanChange(GetNodeFromItem(PNMTreeView(NMHdr)^.itemNew)) then
|
|
Result := 1;
|
|
TVN_SELCHANGEDA, TVN_SELCHANGEDW:
|
|
with PNMTreeView(NMHdr)^ do
|
|
if FChangeTimer.Interval > 0 then
|
|
with FChangeTimer do
|
|
begin
|
|
Enabled := False;
|
|
Tag := Integer(GetNodeFromItem(itemNew));
|
|
Enabled := True;
|
|
end
|
|
else
|
|
Change(GetNodeFromItem(itemNew));
|
|
TVN_DELETEITEM:
|
|
begin
|
|
Node := GetNodeFromItem(PNMTreeView(NMHdr)^.itemOld);
|
|
if Node <> nil then
|
|
begin
|
|
Node.FItemId := nil;
|
|
FChangeTimer.Enabled := False;
|
|
if FStateChanging then Node.Delete
|
|
else Items.Delete(Node);
|
|
end;
|
|
end;
|
|
TVN_SETDISPINFO:
|
|
with PTVDispInfo(NMHdr)^ do
|
|
begin
|
|
Node := GetNodeFromItem(item);
|
|
if (Node <> nil) and ((item.mask and TVIF_TEXT) <> 0) then
|
|
Node.Text := item.pszText;
|
|
end;
|
|
TVN_GETDISPINFO:
|
|
with PTVDispInfo(NMHdr)^ do
|
|
begin
|
|
Node := GetNodeFromItem(item);
|
|
if Node <> nil then
|
|
begin
|
|
if (item.mask and TVIF_TEXT) <> 0 then
|
|
StrLCopy(item.pszText, PChar(Node.Text), item.cchTextMax);
|
|
if (item.mask and TVIF_IMAGE) <> 0 then
|
|
begin
|
|
GetImageIndex(Node);
|
|
item.iImage := Node.ImageIndex;
|
|
end;
|
|
if (item.mask and TVIF_SELECTEDIMAGE) <> 0 then
|
|
begin
|
|
GetSelectedIndex(Node);
|
|
item.iSelectedImage := Node.SelectedIndex;
|
|
end;
|
|
end;
|
|
end;
|
|
NM_RCLICK:
|
|
begin
|
|
FRClickNode := nil;
|
|
GetCursorPos(MousePos);
|
|
if RightClickSelect then
|
|
with PointToSmallPoint(ScreenToClient(MousePos)) do
|
|
begin
|
|
FRClickNode := GetNodeAt(X, Y);
|
|
Perform(WM_CONTEXTMENU, Handle, Integer(PointToSmallPoint(MousePos)));
|
|
FRClickNode := nil;
|
|
end
|
|
else
|
|
// Win95/98 eat WM_CONTEXTMENU when posted to the message queue
|
|
PostMessage(Handle, CN_BASE+WM_CONTEXTMENU, Handle, Integer(PointToSmallPoint(MousePos)));
|
|
Message.Result := 1; // tell treeview not to perform default response
|
|
end;
|
|
end;
|
|
end;}
|
|
|
|
{function TCustomTreeView.GetDragImages: TDragImageList;
|
|
begin
|
|
if FDragImage.Count > 0 then
|
|
Result := FDragImage
|
|
else
|
|
Result := nil;
|
|
end;
|
|
}
|
|
procedure TCustomTreeView.WndProc(var Message: TLMessage);
|
|
begin
|
|
if not (csDesigning in ComponentState) and ((Message.Msg = LM_LBUTTONDOWN) or
|
|
(Message.Msg = LM_LBUTTONDBLCLK)) and not Dragging and
|
|
(DragMode = dmAutomatic) and (DragKind = dkDrag) then
|
|
begin
|
|
if not IsControlMouseMsg(TLMMouse(Message)) then begin
|
|
ControlState := ControlState + [csLButtonDown];
|
|
Dispatch(Message);
|
|
end;
|
|
end
|
|
{else if Message.Msg = CN_BASE+LM_CONTEXTMENU then
|
|
Message.Result := Perform(LM_CONTEXTMENU, Message.WParam, Message.LParam)
|
|
}
|
|
else inherited WndProc(Message);
|
|
end;
|
|
|
|
procedure TCustomTreeView.DoStartDrag(var DragObject: TDragObject);
|
|
{var
|
|
ImageHandle: HImageList;
|
|
DragNode: TTreeNode;
|
|
P: TPoint;}
|
|
begin
|
|
inherited DoStartDrag(DragObject);
|
|
{DragNode := FDragNode;
|
|
FLastDropTarget := nil;
|
|
FDragNode := nil;
|
|
if DragNode = nil then begin
|
|
GetCursorPos(P);
|
|
with ScreenToClient(P) do DragNode := GetNodeAt(X, Y);
|
|
end;
|
|
if DragNode <> nil then begin
|
|
// ToDo: implement Drag&Drop
|
|
ImageHandle := 0; TreeView_CreateDragImage(Handle, DragNode.ItemId);
|
|
if ImageHandle <> 0 then
|
|
with FDragImage do
|
|
begin
|
|
Handle := ImageHandle;
|
|
SetDragImage(0, 2, 2);
|
|
end;
|
|
end;}
|
|
end;
|
|
|
|
procedure TCustomTreeView.DoEndDrag(Target: TObject; X, Y: Integer);
|
|
begin
|
|
inherited DoEndDrag(Target, X, Y);
|
|
FLastDropTarget := nil;
|
|
end;
|
|
|
|
{procedure TCustomTreeView.CMDrag(var Message: TCMDrag);
|
|
begin
|
|
inherited;
|
|
with Message, DragRec^ do
|
|
case DragMessage of
|
|
dmDragMove:
|
|
with ScreenToClient(Pos) do
|
|
DoDragOver(Source, X, Y, Message.Result <> 0);
|
|
dmDragLeave:
|
|
begin
|
|
TDragObject(Source).HideDragImage;
|
|
FLastDropTarget := DropTarget;
|
|
DropTarget := nil;
|
|
TDragObject(Source).ShowDragImage;
|
|
end;
|
|
dmDragDrop: FLastDropTarget := nil;
|
|
end;
|
|
end;}
|
|
|
|
{procedure TCustomTreeView.DoDragOver(Source: TDragObject; X, Y: Integer;
|
|
CanDrop: Boolean);
|
|
var
|
|
Node: TTreeNode;
|
|
begin
|
|
Node := GetNodeAt(X, Y);
|
|
if (Node <> nil) and
|
|
((Node <> DropTarget) or (Node = FLastDropTarget)) then
|
|
begin
|
|
FLastDropTarget := nil;
|
|
TDragObject(Source).HideDragImage;
|
|
Node.DropTarget := True;
|
|
TDragObject(Source).ShowDragImage;
|
|
end;
|
|
end;}
|
|
|
|
procedure TCustomTreeView.DoPaint;
|
|
var a,HalfBorderWidth:integer;
|
|
SpaceRect:TRect;
|
|
Node: TTreeNode;
|
|
begin
|
|
if tvsUpdating in FStates then exit;
|
|
UpdateScrollbars;
|
|
with Canvas do begin
|
|
// draw nodes
|
|
Node:=TopItem;
|
|
//write('[TCustomTreeView.DoPaint] A Node=',HexStr(Cardinal(Node),8));
|
|
//if Node<>nil then writeln(' Node.Text=',Node.Text) else writeln('');
|
|
while Node<>nil do begin
|
|
DoPaintNode(Node);
|
|
Node:=Node.GetNextVisible;
|
|
//write('[TCustomTreeView.DoPaint] B Node=',HexStr(Cardinal(Node),8));
|
|
//if Node<>nil then writeln(' Node.Text=',Node.Text) else writeln('');
|
|
end;
|
|
// draw unused space below nodes
|
|
SpaceRect:=Rect(BorderWidth,BorderWidth,
|
|
(ClientWidth-ScrollBarWidth)-BorderWidth,
|
|
(ClientHeight-ScrollBarWidth)-BorderWidth);
|
|
Node:=BottomItem;
|
|
if Node<>nil then
|
|
SpaceRect.Top:=Node.Top+Node.Height-FScrolledTop+BorderWidth;
|
|
//if Node<>nil then writeln('BottomItem=',BottomItem.text) else writeln('NO BOTTOMITEM!!!!!!!!!');
|
|
// TWinControl(Parent).InvalidateRect(Self,SpaceRect,true);
|
|
if (FBackgroundColor<>clNone) and (SpaceRect.Top<SpaceRect.Bottom) then
|
|
begin
|
|
//writeln(' SpaceRect=',SpaceRect.Left,',',SpaceRect.Top,',',SpaceRect.Right,',',SpaceRect.Bottom);
|
|
Brush.Color:=FBackgroundColor;
|
|
FillRect(SpaceRect);
|
|
end;
|
|
// draw border
|
|
HalfBorderWidth:=BorderWidth shr 1;
|
|
Pen.Color:=clGray;
|
|
for a:=0 to BorderWidth-1 do begin
|
|
if a=HalfBorderWidth then
|
|
Pen.Color:=clBlack;
|
|
MoveTo(a,(ClientHeight-ScrollBarWidth)-1-a);
|
|
LineTo(a,a);
|
|
LineTo((ClientWidth-ScrollBarWidth)-1-a,a);
|
|
end;
|
|
Pen.Color:=clWhite;
|
|
for a:=0 to BorderWidth-1 do begin
|
|
if a=HalfBorderWidth then
|
|
Pen.Color:=clLtGray;
|
|
MoveTo((ClientWidth-ScrollBarWidth)-1-a,a);
|
|
LineTo((ClientWidth-ScrollBarWidth)-1-a,(ClientHeight-ScrollBarWidth)-1-a);
|
|
LineTo(a,(ClientHeight-ScrollBarWidth)-1-a);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.DoPaintNode(Node: TTreeNode);
|
|
var NodeRect: TRect;
|
|
VertMid: integer;
|
|
|
|
function InvertColor(AColor: TColor): TColor;
|
|
var Red, Green, Blue: integer;
|
|
begin
|
|
Result:=clWhite;
|
|
Red:=(AColor shr 16) and $ff;
|
|
Green:=(AColor shr 8) and $ff;
|
|
Blue:=AColor and $ff;
|
|
if Red+Green+Blue>$180 then
|
|
Result:=clBlack;
|
|
//writeln('[TCustomTreeView.DoPaintNode.InvertColor] Result=',Result,' ',Red,',',Green,',',Blue);
|
|
end;
|
|
|
|
function DrawTreeLines(CurNode: TTreeNode): integer;
|
|
// paints tree lines, returns indent
|
|
var CurMid: integer;
|
|
begin
|
|
if CurNode<>nil then begin
|
|
Result:=DrawTreeLines(CurNode.Parent);
|
|
if ShowLines then begin
|
|
CurMid:=Result+(Indent shr 1);
|
|
if CurNode=Node then begin
|
|
// draw horizontal line
|
|
Canvas.MoveTo(CurMid,VertMid);
|
|
Canvas.LineTo(Result+Indent,VertMid);
|
|
end;
|
|
if CurNode.GetNextSibling<>nil then begin
|
|
// draw vertical line to next brother
|
|
Canvas.MoveTo(CurMid,NodeRect.Top);
|
|
Canvas.LineTo(CurMid,NodeRect.Bottom);
|
|
end else if CurNode=Node then begin
|
|
// draw vertical line from top to horizontal line
|
|
Canvas.MoveTo(CurMid,NodeRect.Top);
|
|
Canvas.LineTo(CurMid,VertMid);
|
|
end;
|
|
end;
|
|
inc(Result,Indent);
|
|
end else begin
|
|
Result:=BorderWidth-FScrolledLeft;
|
|
end;
|
|
end;
|
|
|
|
procedure DrawExpandSign(MidX,MidY: integer; CollapseSign: boolean);
|
|
var HalfSize, ALeft, ATop, ARight, ABottom: integer;
|
|
Points: PPoint;
|
|
begin
|
|
if not ShowButtons then exit;
|
|
with Canvas do begin
|
|
Brush.Color:=BackgroundColor;
|
|
Pen.Color:=TreeLineColor;
|
|
Pen.Style:=psSolid;
|
|
HalfSize:=fExpandSignSize shr 1;
|
|
if ((FExpandSignSize and 1)=0) then dec(HalfSize);
|
|
ALeft:=MidX-HalfSize;
|
|
ATop:=MidY-HalfSize;
|
|
ARight:=ALeft+(HalfSize shl 1);
|
|
ABottom:=ATop+(HalfSize shl 1);
|
|
case ExpandSignType of
|
|
tvestPlusMinus:
|
|
begin
|
|
// draw a plus or a minus sign
|
|
Rectangle(ALeft, ATop, ARight, ABottom);
|
|
MoveTo(ALeft+2,MidY);
|
|
LineTo(ARight-2+1,MidY);
|
|
if not CollapseSign then begin
|
|
MoveTo(MidX,ATop+2);
|
|
LineTo(MidX,ABottom-2+1);
|
|
end;
|
|
end;
|
|
tvestArrow:
|
|
begin
|
|
// draw an arrow. down for collapse and right for expand
|
|
GetMem(Points,SizeOf(TPoint)*3);
|
|
if CollapseSign then begin
|
|
// draw an arrow down
|
|
Points[0]:=Point(ALeft,MidY);
|
|
Points[1]:=Point(ARight,MidY);
|
|
Points[2]:=Point(MidX,ABottom);
|
|
end else begin
|
|
// draw an arrow right
|
|
Points[0]:=Point(MidX-1,ATop);
|
|
Points[1]:=Point(ARight-1,MidY);
|
|
Points[2]:=Point(MidX-1,ABottom);
|
|
end;
|
|
PolyGon(Points,3,false);
|
|
FreeMem(Points);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
var x, ImgIndex: integer;
|
|
CurBackgroundColor, OldFontColor: TColor;
|
|
CurTextRect: TRect;
|
|
begin
|
|
NodeRect:=Node.DisplayRect(false);
|
|
if (NodeRect.Bottom<0) or (NodeRect.Top>=(ClientHeight-ScrollBarWidth)) then
|
|
exit;
|
|
VertMid:=(NodeRect.Top+NodeRect.Bottom) shr 1;
|
|
//writeln('[TCustomTreeView.DoPaintNode] Node=',HexStr(Cardinal(Node),8),' Node.Text=',Node.Text,' NodeRect=',NodeRect.Left,',',NodeRect.Top,',',NodeRect.Right,',',NodeRect.Bottom,' VertMid=',VertMid);
|
|
with Canvas do begin
|
|
// draw background
|
|
if (FSelectedNode<>Node) or (not (tvoRowSelect in FOptions)) then
|
|
CurBackgroundColor:=FBackgroundColor
|
|
else
|
|
CurBackgroundColor:=FSelectedColor;
|
|
if CurBackgroundColor<>clNone then begin
|
|
Brush.Color:=CurBackgroundColor;
|
|
FillRect(NodeRect);
|
|
end;
|
|
// draw tree lines
|
|
Pen.Color:=TreeLineColor;
|
|
Pen.Style:=psDot;
|
|
x:=DrawTreeLines(Node);
|
|
Pen.Style:=psSolid;
|
|
//writeln('TCustomTreeView.DoPaintNode x=',x);
|
|
// draw expand sign
|
|
if Node.HasChildren then begin
|
|
DrawExpandSign(x-Indent+(Indent shr 1),VertMid,Node.Expanded);
|
|
end;
|
|
// draw icon
|
|
if Images<>nil then begin
|
|
if FSelectedNode<>Node then
|
|
ImgIndex:=Node.ImageIndex
|
|
else
|
|
ImgIndex:=Node.SelectedIndex;
|
|
if (ImgIndex>=0) and (ImgIndex<Images.Count) then
|
|
Images.Draw(Canvas,x,NodeRect.Top,ImgIndex,true);
|
|
inc(x,Images.Width);
|
|
end;
|
|
// draw state icon
|
|
if StateImages<>nil then begin
|
|
if (Node.StateIndex>=0) and (Node.StateIndex<StateImages.Count) then
|
|
StateImages.Draw(Canvas,x,NodeRect.Top,Node.StateIndex,true);
|
|
inc(x,StateImages.Width);
|
|
end;
|
|
// draw text
|
|
if (FSelectedNode=Node) and (FSelectedColor<>clNone) then begin
|
|
Brush.Color:=FSelectedColor;
|
|
CurTextRect:=NodeRect;
|
|
CurTextRect.Left:=x;
|
|
CurTextRect.Right:=x+TextWidth(Node.Text);
|
|
OldFontColor:=Font.Color;
|
|
Font.Color:=InvertColor(Brush.Color);
|
|
FillRect(CurTextRect);
|
|
TextOut(x,NodeRect.Top+(TextHeight(Node.Text) shr 1),Node.Text);
|
|
Font.Color:=OldFontColor;
|
|
end else begin
|
|
TextOut(x,NodeRect.Top+(TextHeight(Node.Text) shr 1),Node.Text);
|
|
end;
|
|
// draw separator
|
|
if (tvoShowSeparators in FOptions) then begin
|
|
Pen.Color:=SeparatorColor;
|
|
MoveTo(NodeRect.Left,NodeRect.Bottom-1);
|
|
LineTo(NodeRect.Right,NodeRect.Bottom-1);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.GetImageIndex(Node: TTreeNode);
|
|
begin
|
|
if Assigned(FOnGetImageIndex) then FOnGetImageIndex(Self, Node);
|
|
end;
|
|
|
|
procedure TCustomTreeView.GetSelectedIndex(Node: TTreeNode);
|
|
begin
|
|
if Assigned(FOnGetSelectedIndex) then FOnGetSelectedIndex(Self, Node);
|
|
end;
|
|
|
|
function TCustomTreeView.CanChange(Node: TTreeNode): Boolean;
|
|
begin
|
|
Result := True;
|
|
if Assigned(FOnChanging) then FOnChanging(Self, Node, Result);
|
|
end;
|
|
|
|
procedure TCustomTreeView.Change(Node: TTreeNode);
|
|
begin
|
|
if Assigned(FOnChange) then FOnChange(Self, Node);
|
|
end;
|
|
|
|
procedure TCustomTreeView.Delete(Node: TTreeNode);
|
|
begin
|
|
if Assigned(FOnDeletion) then FOnDeletion(Self, Node);
|
|
end;
|
|
|
|
procedure TCustomTreeView.Expand(Node: TTreeNode);
|
|
begin
|
|
if Assigned(FOnExpanded) then FOnExpanded(Self, Node);
|
|
end;
|
|
|
|
function TCustomTreeView.CanExpand(Node: TTreeNode): Boolean;
|
|
begin
|
|
Result := True;
|
|
if Assigned(FOnExpanding) then FOnExpanding(Self, Node, Result);
|
|
end;
|
|
|
|
procedure TCustomTreeView.Collapse(Node: TTreeNode);
|
|
begin
|
|
if Assigned(FOnCollapsed) then FOnCollapsed(Self, Node);
|
|
end;
|
|
|
|
function TCustomTreeView.CanCollapse(Node: TTreeNode): Boolean;
|
|
begin
|
|
Result := True;
|
|
if Assigned(FOnCollapsing) then FOnCollapsing(Self, Node, Result);
|
|
end;
|
|
|
|
function TCustomTreeView.CanEdit(Node: TTreeNode): Boolean;
|
|
begin
|
|
Result := True;
|
|
if Assigned(FOnEditing) then FOnEditing(Self, Node, Result);
|
|
end;
|
|
|
|
{procedure TCustomTreeView.Edit(const Item: TTVItem);
|
|
var
|
|
S: string;
|
|
Node: TTreeNode;
|
|
begin
|
|
with Item do
|
|
if pszText <> nil then begin
|
|
S := pszText;
|
|
Node := GetNodeFromItem(Item);
|
|
if Assigned(FOnEdited) then FOnEdited(Self, Node, S);
|
|
if Node <> nil then Node.Text := S;
|
|
end;
|
|
end;}
|
|
|
|
procedure TCustomTreeView.EndEditing;
|
|
begin
|
|
if not (tvsIsEditing in FStates) then exit;
|
|
// ToDo:
|
|
// restore value
|
|
|
|
// end editing
|
|
Exclude(FStates,tvsIsEditing);
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomTreeView.EnsureNodeIsVisible(ANode: TTreeNode);
|
|
var b: integer;
|
|
begin
|
|
if ANode=nil then exit;
|
|
ANode.ExpandParents;
|
|
if ANode.Top<ScrolledTop then
|
|
ScrolledTop:=ANode.Top
|
|
else begin
|
|
b:=ANode.Top+ANode.Height-GetNodeDrawAreaHeight;
|
|
if ScrolledTop<b then ScrolledTop:=b;
|
|
end;
|
|
end;
|
|
|
|
function TCustomTreeView.CreateNode: TTreeNode;
|
|
begin
|
|
Result := TTreeNode.Create(Items);
|
|
end;
|
|
|
|
{procedure TCustomTreeView.SetImageList(Value: HImageList; Flags: Integer);
|
|
begin
|
|
if HandleAllocated then TreeView_SetImageList(Handle, Value, Flags);
|
|
end;}
|
|
|
|
procedure TCustomTreeView.ImageListChange(Sender: TObject);
|
|
//var ImageHandle: HImageList;
|
|
begin
|
|
// ToDo
|
|
Invalidate;
|
|
{Delphi:
|
|
if HandleAllocated then
|
|
begin
|
|
if TCustomImageList(Sender).HandleAllocated then
|
|
ImageHandle := TCustomImageList(Sender).Handle
|
|
else
|
|
ImageHandle := 0;
|
|
if Sender = Images then
|
|
SetImageList(ImageHandle, TVSIL_NORMAL)
|
|
else if Sender = StateImages then
|
|
SetImageList(ImageHandle, TVSIL_STATE);
|
|
end;}
|
|
end;
|
|
|
|
procedure TCustomTreeView.MouseDown(Button: TMouseButton; Shift: TShiftState;
|
|
X, Y: Integer);
|
|
var
|
|
CursorNode: TTreeNode;
|
|
bStartDrag: boolean;
|
|
begin
|
|
if (X>=(ClientWidth-ScrollBarWidth)) or (Y>=(ClientHeight-ScrollBarWidth))
|
|
then begin
|
|
// workaround vs scrollbar clientrect bug in lcl
|
|
inherited MouseDown(Button, Shift, X, Y);
|
|
exit;
|
|
end;
|
|
if Button = mbLeft then begin
|
|
if ssDouble in Shift then Exit;
|
|
end;
|
|
fMouseDownX := X;
|
|
fMouseDownY := Y;
|
|
if Button=mbMiddle then begin
|
|
if ssDouble in Shift then Exit;
|
|
if tvsIsEditing in FStates then begin
|
|
// ToDo: insert clipboard text into node text
|
|
// :=PrimarySelection.AsText;
|
|
end;
|
|
end;
|
|
inherited MouseDown(Button, Shift, X, Y);
|
|
CursorNode:=GetNodeAt(X,Y);
|
|
bStartDrag := false;
|
|
if (Button = mbLeft) and (CursorNode<>nil) then begin
|
|
Exclude(fStates,tvsWaitForDragging);
|
|
if CursorNode.HasChildren
|
|
and (x>=CursorNode.DisplayExpandSignLeft)
|
|
and (x<CursorNode.DisplayExpandSignRight) then begin
|
|
// mousedown occured on expand sign -> expand/collapse
|
|
CursorNode.Expanded:=not CursorNode.Expanded;
|
|
end else if x>=CursorNode.DisplayTextLeft then begin
|
|
// mousedown occured in text -> select node and begin drag operation
|
|
Include(FStates,tvsMouseCapture);
|
|
Selected:=CursorNode;
|
|
bStartDrag := true;
|
|
end;
|
|
end;
|
|
if (Button = mbLeft) and bStartDrag then
|
|
Include(fStates, tvsWaitForDragging)
|
|
else begin
|
|
if not (tvsDblClicked in fStates) then begin
|
|
if Button=mbMiddle then begin
|
|
// insert primary selection text
|
|
|
|
end;
|
|
end;
|
|
end;
|
|
//LCLLinux.SetFocus(Handle);
|
|
end;
|
|
|
|
procedure TCustomTreeView.MouseMove(Shift: TShiftState; X, Y: Integer);
|
|
begin
|
|
inherited MouseMove(Shift, x, y);
|
|
if (X>=ClientWidth-ScrollBarWidth) or (Y>=(ClientHeight-ScrollBarWidth)) then
|
|
begin
|
|
// workaround vs scrollbar clientrect bug in lcl
|
|
exit;
|
|
end;
|
|
if MouseCapture and (tvsWaitForDragging in fStates) then begin
|
|
if (Abs(fMouseDownX - X) >= GetSystemMetrics(SM_CXDRAG))
|
|
or (Abs(fMouseDownY - Y) >= GetSystemMetrics(SM_CYDRAG))
|
|
then begin
|
|
Exclude(fStates, tvsWaitForDragging);
|
|
//BeginDrag(false);
|
|
end;
|
|
end else if (ssLeft in Shift) and MouseCapture then begin
|
|
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.MouseUp(Button: TMouseButton; Shift: TShiftState;
|
|
X, Y: Integer);
|
|
begin
|
|
inherited MouseUp(Button, Shift, X, Y);
|
|
if (X>=ClientWidth-ScrollBarWidth) or (Y>=(ClientHeight-ScrollBarWidth)) then
|
|
begin
|
|
exit;
|
|
end;
|
|
if (Button = mbRight) and (Shift = [ssRight]) and Assigned(PopupMenu) then
|
|
exit;
|
|
MouseCapture := False;
|
|
if fStates * [tvsDblClicked, tvsWaitForDragging] = [tvsWaitForDragging] then
|
|
begin
|
|
Exclude(fStates, tvsWaitForDragging);
|
|
end;
|
|
if (Button=mbLeft)
|
|
and (fStates * [tvsDblClicked, tvsWaitForDragging] = []) then begin
|
|
//AquirePrimarySelection;
|
|
end;
|
|
Exclude(fStates, tvsDblClicked);
|
|
end;
|
|
|
|
procedure TCustomTreeView.Notification(AComponent: TComponent;
|
|
Operation: TOperation);
|
|
begin
|
|
inherited Notification(AComponent, Operation);
|
|
if Operation = opRemove then begin
|
|
if AComponent = Images then Images := nil;
|
|
if AComponent = StateImages then StateImages := nil;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetImages(Value: TCustomImageList);
|
|
begin
|
|
if Images = Value then exit;
|
|
if Images <> nil then
|
|
Images.UnRegisterChanges(FImageChangeLink);
|
|
FImages := Value;
|
|
if Images <> nil then begin
|
|
Images.RegisterChanges(FImageChangeLink);
|
|
Images.FreeNotification(Self);
|
|
//SetImageList(Images.Handle, TVSIL_NORMAL)
|
|
end;
|
|
//else SetImageList(0, TVSIL_NORMAL);
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetStateImages(Value: TCustomImageList);
|
|
begin
|
|
if FStateImages=Value then exit;
|
|
if StateImages <> nil then
|
|
StateImages.UnRegisterChanges(FStateChangeLink);
|
|
FStateImages := Value;
|
|
if StateImages <> nil then begin
|
|
StateImages.RegisterChanges(FStateChangeLink);
|
|
StateImages.FreeNotification(Self);
|
|
//SetImageList(StateImages.Handle, TVSIL_STATE)
|
|
end;
|
|
//else SetImageList(0, TVSIL_STATE);
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomTreeView.LoadFromFile(const FileName: string);
|
|
var
|
|
Stream: TStream;
|
|
begin
|
|
Stream := TFileStream.Create(FileName, fmOpenRead);
|
|
try
|
|
LoadFromStream(Stream);
|
|
finally
|
|
Stream.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.LoadFromStream(Stream: TStream);
|
|
begin
|
|
with TTreeStrings.Create(Items) do
|
|
try
|
|
LoadTreeFromStream(Stream);
|
|
finally
|
|
Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SaveToFile(const FileName: string);
|
|
var
|
|
Stream: TStream;
|
|
begin
|
|
Stream := TFileStream.Create(FileName, fmCreate);
|
|
try
|
|
SaveToStream(Stream);
|
|
finally
|
|
Stream.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SaveToStream(Stream: TStream);
|
|
begin
|
|
with TTreeStrings.Create(Items) do
|
|
try
|
|
SaveTreeToStream(Stream);
|
|
finally
|
|
Free;
|
|
end;
|
|
end;
|
|
|
|
{procedure TCustomTreeView.WMContextMenu(var Message: TWMContextMenu);
|
|
var
|
|
R: TRect;
|
|
begin
|
|
if (Message.XPos < 0) and (Selected <> nil) then begin
|
|
R := Selected.DisplayRect(True);
|
|
Message.Pos := PointToSmallPoint(ClientToScreen(Point(R.Left, R.Bottom)));
|
|
end;
|
|
inherited;
|
|
end;}
|
|
|
|
procedure TCustomTreeView.WMVScroll(var Msg: TLMScroll);
|
|
begin
|
|
case Msg.ScrollCode of
|
|
// Scrolls to start / end of the text
|
|
SB_TOP: ScrolledTop := 0;
|
|
SB_BOTTOM: ScrolledTop := GetMaxScrollTop;
|
|
// Scrolls one line up / down
|
|
SB_LINEDOWN: ScrolledTop := ScrolledTop + FDefItemHeight div 2;
|
|
SB_LINEUP: ScrolledTop := ScrolledTop - FDefItemHeight div 2;
|
|
// Scrolls one page of lines up / down
|
|
SB_PAGEDOWN: ScrolledTop := ScrolledTop + (ClientHeight-ScrollBarWidth)
|
|
- FDefItemHeight;
|
|
SB_PAGEUP: ScrolledTop := ScrolledTop - (ClientHeight-ScrollBarWidth)
|
|
+ FDefItemHeight;
|
|
// Scrolls to the current scroll bar position
|
|
SB_THUMBPOSITION,
|
|
SB_THUMBTRACK: ScrolledTop := Msg.Pos;
|
|
// Ends scrolling
|
|
SB_ENDSCROLL: ;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.WMHScroll(var Msg: TLMScroll);
|
|
begin
|
|
case Msg.ScrollCode of
|
|
// Scrolls to start / end of the text
|
|
SB_LEFT: ScrolledLeft := 0;
|
|
SB_RIGHT: ScrolledLeft := GetMaxScrollLeft;
|
|
// Scrolls one line left / right
|
|
SB_LINERIGHT: ScrolledLeft := ScrolledLeft + FDefItemHeight div 2;
|
|
SB_LINELEFT: ScrolledLeft := ScrolledLeft - FDefItemHeight div 2;
|
|
// Scrolls one page of lines left / right
|
|
SB_PAGERIGHT: ScrolledLeft := ScrolledLeft + (ClientHeight-ScrollBarWidth)
|
|
- FDefItemHeight;
|
|
SB_PAGELEFT: ScrolledLeft := ScrolledLeft - (ClientHeight-ScrollBarWidth)
|
|
+ FDefItemHeight;
|
|
// Scrolls to the current scroll bar position
|
|
SB_THUMBPOSITION,
|
|
SB_THUMBTRACK: ScrolledLeft := Msg.Pos;
|
|
// Ends scrolling
|
|
SB_ENDSCROLL: ;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.WMLButtonDown(var Message: TLMLButtonDown);
|
|
var
|
|
Node: TTreeNode;
|
|
MousePos: TPoint;
|
|
begin
|
|
Exclude(FStates,tvsDragged);
|
|
FDragNode := nil;
|
|
try
|
|
inherited;
|
|
if (DragMode = dmAutomatic) and (DragKind = dkDrag) then
|
|
begin
|
|
SetFocus;
|
|
if not (tvsDragged in FStates) then begin
|
|
GetCursorPos(MousePos);
|
|
with PointToSmallPoint(ScreenToClient(MousePos)) do
|
|
Perform(LM_LBUTTONUP, 0, MakeLong(X, Y));
|
|
end
|
|
else begin
|
|
Node := GetNodeAt(Message.XPos, Message.YPos);
|
|
if Node <> nil then
|
|
begin
|
|
Node.Focused := True;
|
|
Node.Selected := True;
|
|
BeginDrag(False);
|
|
end;
|
|
end;
|
|
end;
|
|
finally
|
|
FDragNode := nil;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.WMNotify(var Message: TLMNotify);
|
|
{var
|
|
Node: TTreeNode;
|
|
MaxTextLen: Integer;
|
|
Pt: TPoint;}
|
|
begin
|
|
{with Message do
|
|
if NMHdr^.code = TTN_NEEDTEXTW then
|
|
begin
|
|
// Work around NT COMCTL32 problem with tool tips >= 80 characters
|
|
GetCursorPos(Pt);
|
|
Pt := ScreenToClient(Pt);
|
|
Node := GetNodeAt(Pt.X, Pt.Y);
|
|
if (Node = nil) or (Node.Text = '') or
|
|
(PToolTipTextW(NMHdr)^.uFlags and TTF_IDISHWND = 0) then Exit;
|
|
if (GetComCtlVersion >= ComCtlVersionIE4) and (Length(Node.Text) < 80) then
|
|
begin
|
|
inherited;
|
|
Exit;
|
|
end;
|
|
FWideText := Node.Text;
|
|
MaxTextLen := SizeOf(PToolTipTextW(NMHdr)^.szText) div SizeOf(WideChar);
|
|
if Length(FWideText) >= MaxTextLen then
|
|
SetLength(FWideText, MaxTextLen - 1);
|
|
PToolTipTextW(NMHdr)^.lpszText := PWideChar(FWideText);
|
|
FillChar(PToolTipTextW(NMHdr)^.szText, MaxTextLen, 0);
|
|
Move(Pointer(FWideText)^, PToolTipTextW(NMHdr)^.szText, Length(FWideText) * SizeOf(WideChar));
|
|
PToolTipTextW(NMHdr)^.hInst := 0;
|
|
SetWindowPos(NMHdr^.hwndFrom, HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE or
|
|
SWP_NOSIZE or SWP_NOMOVE or SWP_NOOWNERZORDER);
|
|
Result := 1;
|
|
end
|
|
else}
|
|
inherited;
|
|
end;
|
|
|
|
procedure TCustomTreeView.WMSize(var Msg: TLMSize);
|
|
begin
|
|
FStates:=FStates+[tvsScrollbarChanged,
|
|
tvsBottomItemNeedsUpdate];
|
|
inherited;
|
|
end;
|
|
|
|
{ CustomDraw support }
|
|
|
|
procedure TCustomTreeView.CanvasChanged(Sender: TObject);
|
|
begin
|
|
Include(FStates,tvsCanvasChanged);
|
|
end;
|
|
|
|
function TCustomTreeView.IsCustomDrawn(Target: TCustomDrawTarget;
|
|
Stage: TCustomDrawStage): Boolean;
|
|
begin
|
|
{ Tree view doesn't support erase notifications }
|
|
if Stage = cdPrePaint then begin
|
|
if Target = dtItem then
|
|
Result := Assigned(FOnCustomDrawItem)
|
|
or Assigned(FOnAdvancedCustomDrawItem)
|
|
else if Target = dtControl then
|
|
Result := Assigned(FOnCustomDraw) or Assigned(FOnAdvancedCustomDraw) or
|
|
Assigned(FOnCustomDrawItem) or Assigned(FOnAdvancedCustomDrawItem)
|
|
else
|
|
Result := False;
|
|
end else begin
|
|
if Target = dtItem then
|
|
Result := Assigned(FOnAdvancedCustomDrawItem)
|
|
else if Target = dtControl then
|
|
Result := Assigned(FOnAdvancedCustomDraw)
|
|
or Assigned(FOnAdvancedCustomDrawItem)
|
|
else
|
|
Result := False;
|
|
end;
|
|
end;
|
|
|
|
function TCustomTreeView.CustomDraw(const ARect: TRect;
|
|
Stage: TCustomDrawStage): Boolean;
|
|
begin
|
|
Result := True;
|
|
if (Stage = cdPrePaint) and Assigned(FOnCustomDraw) then
|
|
FOnCustomDraw(Self, ARect, Result);
|
|
if Assigned(FOnAdvancedCustomDraw) then
|
|
FOnAdvancedCustomDraw(Self, ARect, Stage, Result);
|
|
end;
|
|
|
|
function TCustomTreeView.CustomDrawItem(Node: TTreeNode;
|
|
State: TCustomDrawState;
|
|
Stage: TCustomDrawStage; var PaintImages: Boolean): Boolean;
|
|
begin
|
|
Result := True;
|
|
PaintImages := True;
|
|
if (Stage = cdPrePaint) and Assigned(FOnCustomDrawItem) then
|
|
FOnCustomDrawItem(Self, Node, State, Result);
|
|
if Assigned(FOnAdvancedCustomDrawItem) then
|
|
FOnAdvancedCustomDrawItem(Self, Node, State, Stage, PaintImages, Result);
|
|
end;
|
|
|
|
function TCustomTreeView.ConsistencyCheck: integer;
|
|
var OldMaxRight, OldLastTop, OldMaxLvl: integer;
|
|
OldTopItem, OldBottomItem: TTreeNode;
|
|
begin
|
|
if FCanvas=nil then exit(-1);
|
|
if (fExpandSignSize<0) then exit(-2);
|
|
if FDefItemHeight<0 then exit(-3);
|
|
if FIndent<0 then exit(-4);
|
|
if FMaxRight<0 then exit(-5);
|
|
if FTreeNodes=nil then exit(-6);
|
|
Result:=FTreeNodes.ConsistencyCheck;
|
|
if Result<>0 then begin
|
|
dec(Result,1000);
|
|
exit;
|
|
end;
|
|
if FUpdateCount<0 then exit(-7);
|
|
if (not (tvsTopsNeedsUpdate in FStates)) then begin
|
|
if Items.GetLastSubNode<>nil then begin
|
|
OldLastTop:=Items.GetLastSubNode.Top;
|
|
Include(FStates,tvsTopsNeedsUpdate);
|
|
UpdateAllTops;
|
|
if OldLastTop<>Items.GetLastSubNode.Top then exit(-8);
|
|
end;
|
|
end;
|
|
if not (tvsMaxRightNeedsUpdate in FStates) then begin
|
|
OldMaxRight:=FMaxRight;
|
|
Include(FStates,tvsMaxRightNeedsUpdate);
|
|
UpdateMaxRight;
|
|
if OldMaxRight<>FMaxRight then exit(-9);
|
|
end;
|
|
if not (tvsMaxLvlNeedsUpdate in FStates) then begin
|
|
OldMaxLvl:=FMaxLvl;
|
|
Include(FStates,tvsMaxLvlNeedsUpdate);
|
|
UpdateMaxLvl;
|
|
if OldMaxLvl<>FMaxLvl then exit(-10);
|
|
end;
|
|
if (tvsIsEditing in FStates) and (FSelectedNode=nil) then exit(-11);
|
|
if (FSelectedNode<>nil) then begin
|
|
if not FSelectedNode.IsVisible then exit(-12);
|
|
end;
|
|
if not (tvsTopItemNeedsUpdate in FStates) then begin
|
|
OldTopItem:=FTopItem;
|
|
UpdateTopItem;
|
|
if FTopItem<>OldTopItem then exit(-13);
|
|
end;
|
|
if not (tvsBottomItemNeedsUpdate in FStates) then begin
|
|
OldBottomItem:=FBottomItem;
|
|
UpdateBottomItem;
|
|
if FBottomItem<>OldBottomItem then exit(-14);
|
|
end;
|
|
Result:=0;
|
|
end;
|
|
|
|
procedure TCustomTreeView.WriteDebugReport(const Prefix: string;
|
|
AllNodes: boolean);
|
|
begin
|
|
write(Prefix);
|
|
write('TCustomTreeView.WriteDebugReport Self=',HexStr(Cardinal(Self),8));
|
|
write(' Consistency=',ConsistencyCheck);
|
|
writeln('');
|
|
if AllNodes then begin
|
|
Items.WriteDebugReport(Prefix+' ',true);
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetSeparatorColor(const AValue: TColor);
|
|
begin
|
|
if fSeparatorColor=AValue then exit;
|
|
fSeparatorColor:=AValue;
|
|
if tvoShowSeparators in Options then
|
|
Invalidate;
|
|
end;
|
|
|
|
// back to comctrls.pp
|
|
|