TTreeView
│
English (en) │
español (es) │
suomi (fi) │
français (fr) │
magyar (hu) │
русский (ru) │
A TTreeView is a graphical control element that presents a hierarchical view of information. Each item can have a number of subitems.
An item can be expanded to reveal subitems, if any exist, and collapsed to hide subitems.
A Short example of using TTreeview
Here is a simple example:
- Create a new application.
- Add an empty treeview to the form, a button1 with caption "Add Child" and a button2 with caption "Delete"
- Assign the following code to the buttons' OnClick events. Compile and run.
procedure TForm1.Button1Click(Sender: TObject);
var
n: integer;
s: string;
begin
// If there are no nodes, create a root node with a parent of nil
if TreeView1.Items.Count = 0 then
begin
Treeview1.Items.Add(nil,'Root Node');
exit;
end;
// Set up a simple text for each new node - "Node1", "Node2" etc
n := Treeview1.Items.Count;
s := 'Node ' + IntToStr(n);
// Add a new node to the currently selected node
if TreeView1.Selected <> nil then
Treeview1.Items.AddChild(Treeview1.Selected, s);
end;
procedure TForm1.Button2Click(Sender: TObject);
// A procedure to recursively delete nodes
procedure DeleteNode(ANode: TTreeNode);
begin
while ANode.HasChildren do
DeleteNode(ANode.GetLastChild);
TreeView1.Items.Delete(ANode);
end;
begin
if TreeView1.Selected = nil then
exit;
// If the selected node has child nodes, first ask for confirmation
if Treeview1.Selected.HasChildren then
if MessageDlg('Delete node and all children?', mtConfirmation, [mbYes,mbNo], 0 ) <> mrYes then
exit;
DeleteNode(TreeView1.Selected);
end;
When running, the treeview is empty. If you click "Add Child", a root node is created. After that a child will be added to any selected node by clicking "Add Child" again.
The "Delete" button will delete the currently selected node. If it doesn't have children, it will delete it immediately, but if it has children, it will first ask.
Adding a new item in code
To add a sibling node to pre-existing node, use TTreeView.ITems.Add.
TreeView1.Items.Add(ATreeNode, 'Added node');
To add the first node, it becomes:
TreeView1.Items.Add(nil, 'First node');
Use TTreeView.Items.AddChild or AddChildObject to add child node.
TreeView1.Items.AddChild(ATreeNode, 'Child node');
Creating a TreeView which loads items only when expanding
To add the expansion symbol to a node without subitems use:
MyNode.HasChildren := True;
And then set an event handler for the OnExpanding event. In this even you should return if the expansion can actually be made or not and when yes, you should add subitems to the node. If the expansion cannot be done, the expansion symbol will be automatically removed even if you have previously set HasChildren to true.
How to move a node in TTreeview
Assuming you have UP and Down buttons to move the selected node in the TTreeview, these procedures will achieve moving the selected node.
procedure TForm1.UpBtnClick(Sender: TObject);
begin
// Ensure a node is selected
if(Treeview1.Selected <> nil) then
// Ensure there is a previous sibling node
if Treeview1.Selected.GetPrevSibling <> nil then
// If we have made it this far, move it UP
Treeview1.Selected.MoveTo(Treeview1.Selected.GetPrevSibling, naInsert);
end;
procedure TForm1.DownBtnClick(Sender: TObject);
begin
// Ensure a node is selected
if(Treeview1.Selected <> nil) then
// Ensure there is a next sibling node
if Treeview1.Selected.GetNextSibling <> nil then
// If we have made it this far, move it DOWN
Treeview1.Selected.MoveTo(Treeview1.Selected.GetNextSibling, naInsertBehind);
end;
Note: These procedures will only move nodes which are at the same level in the TTreeview.
Table of attachment mode of Nodes | |
---|---|
Mode | Effect |
naAdd | add as last sibling of target node |
naAddFirst | add as first sibling of target node |
naAddChild | add as last child of target node |
naAddChildFirst | add as first child of target node |
naInsert | insert in front of target node |
naInsertBehind | insert behind target node |
Using multiple node selection
To allow multiple selection, you need to set the tvoAllowMultiSelect
flag of the Options
property. This will allow you to select additional nodes by Ctrl+Mouse click.
To get a list of selected nodes from code, you can use the TreeView1.Selections
property:
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
begin
Memo1.Lines.Clear;
for i := 0 to TreeView1.SelectionCount - 1 do
Memo1.Lines.Add(TreeView1.Selections[i].GetTextPath);
end;
Freeing TreeNode Data
Use the TreeView's OnDeletion event to free your object.
procedure TForm1.TreeView1Deletion(Sender: TObject; Node: TTreeNode);
begin
TMyObject(Node.Data).Free;
Node.Data := nil;
end;
Using Drag and Drop in a TreeView
If you want to allow a drag and drop function in your treeview, you need to :
- Set to DmAutomatic, the property "Drag Mode" of your treeview
- Create events for
onDragDrop
andOnDragOver
:
...
Var
Node: TTreeNode;
...
procedure TForm1.TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer);
begin
Treeview1 := TTreeView(Sender); { Sender is TreeView where the data is being dropped }
Node := Treeview1.GetNodeAt(x,y); { x,y are drop coordinates (relative to the Sender) }
{ since Sender is TreeView we can evaluate }
{ a tree at the X,Y coordinates }
if Source = Sender then { drop is happening within a TreeView }
begin
if Assigned(Treeview1.Selected) and { check if any node has been selected }
(Node <> Treeview1.Selected) then { and we're dropping to another node }
begin
if Node <> nil then
Treeview1.Selected.MoveTo(Node, naAddChild) { complete drop, by moving selected node }
else
Treeview1.Selected.MoveTo(Node, naAdd); { complete drop, by moving selected node in root }
end;
end;
end;
procedure TForm1.TreeView1DragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean);
begin
Accept := true;
end;
Comments on Events
Event handlers are convenient places for developing LCL applications. There are some peculiar events for TTreeview.
When selected node is changed
procedure TForm1.TreeView1Change(Sender: TObject; Node: TTreeNode);
The OnChange
event is fired when another node is selected. It can be used in a program to update some data.
procedure TForm1.TreeView1Changing(Sender: TObject; Node: TTreeNode; var AllowChange: Boolean);
The OnChanging
event is called before OnChange
, which allows you to prevent node selection under certain conditions. To do this, just set AllowChange
to false
. It is not necessary to set to true
as this is the default value.
Please note that in Delphi the Node
variable points to the node that leaves the selection. In Lazarus, the Node
variable points to the node receiving the selection, but you can use the Selected
property to find out the "previous" node.
In both events, the Node
and Selected
can be equal to nil
(if there was no selection or it is being removed).
When a node is inserted
When a new node is inserted, related events occur in the following order.
1) OnNodeChanged 2) (OnChanging): This is called if there was a previously selected node. 3) OnSelectionChanged 4) OnChange
When deleting a node
When the selected node is deleted, the following events occur.
1) OnDelete 2) OnChanging 3) SelectionChanged 4) OnChange : This is called because when you delete a node, another node is selected.
If an unselected node is deleted, e.g. programmatically, then only the OnDelete event handler is called.
When Form is Closed
OnChanging event occurs, as the selected node is "de"selected. But OnChanging eventhander is executed AFTER the form is closed. This means that you cannot access the form's other data (other controls, other variables, etc.) within the OnChanging event handler procedure.
When Drag&Drop is done
When you drag a node to another node and drop, the following events occur.
1) OnSelectionChanged 2) OnNodeChanged 3) OnChange
Painting of the TreeView
The treeview can draw itself in three ways:
- Themed painting (default) is done by dedicated, theme-specific drawing routines of the operating system/widgetset. You have litte control over the appearance of the tree, but it is guaranteed that it looks consistently across applications, in particular concerning selection and hot-tracking.
- Non-themed painting is activated by removing the element tvoThemedDraw from the treeview's Options property. It gives you more control over the drawing process. For example, you can set the color of the selection background (SelectionColor) and text (SelectionFontColor), or the color of the hot-tracked node text (HotTrackColor).
- Custom painting gives you full control over the drawing process by providing handlers for specific events. Depending on the return value of the event parameter DefaultDraw, you can determine whether you want to draw everything yourself (DefaultDraw = false) or whether you still want to use the built-in routines, but use different parameters that you specify in the event handler (DefaultDraw = true).
Basically the drawing process of the treeview consists of two phases:
- drawing of the background: You can custom-draw the background by providing a handler for the OnCustomDraw and/or OnAdvancedCustomDraw events.
- drawing of the individual tree nodes over this background: You can custom-draw the nodes by providing a handler for the OnCustomDrawItem and/or OnAdvancedCustomDrawItem events.
The "advanced" events are fired several times during the painting process signaling the currently active phase in the Stage parameter which is an enumeration of the following elements:
- cdPrePaint and cdPostPaint are set by the OnAdvancedCustomDraw and OnAdvancedCustomDrawItem events before any drawing action begins (cdPrePaint) / after drawing has been completed (cdPostPaint), respectively.
- cdPreErase and cdPostErase have not been used for a long time (also in Delphi). But they were implemented in Lazarus v4.99 to get more control over the custom drawing process of the nodes in the OnAdvancedCustomDrawItem event:
- cdPreErase is set in OnAdvancedCustomDrawItem before the normal background of a tree node is painted.
- cdPostErase is set inOnAdvancedCustomDrawItem before the background and the text of a selected or hot-tracked node is painted.
- They still are not used by the OnAdvancedCustomDraw event.
- In contrast to cdPrePaint, setting DefaultDraw to false here only inhibits painting of the background of the currently drawn node.
The OnCustomDraw* events are equivalent to the "advanced" events in the cdPrePaint stage.
Example: Custom-painting a treeview with a background gradient
The gradient must be painted at the very beginning of the drawing cyle, i.e. we must provide a handler for the OnAdvancedCustomDraw event at the cdPrePaint stage:
procedure TForm1.BackgroundGradient_AdvancedCustomDraw(Sender: TCustomTreeView;
const ARect: TRect; Stage: TCustomDrawStage; var DefaultDraw: Boolean);
begin
if Stage = cdPrePaint then
begin
// Draw the gradient
Sender.Canvas.GradientFill(ARect, clSkyBlue, clWhite, gdVertical);
// Avoid painting the normal background below the last node
Sender.Canvas.Brush.Color := clNone;
end;
// We must not set DefaultDraw to false here because the CustomDraw
// PrePaint stage would force us to paint everything by ourselves.
end;
However, that's not all because the default painting routine of the node background is executed which overwrites the gradient by the node rectangle over the entire tree width. Therefore we also must inhibit drawing of the normal background in OnAdvancedCustomDrawItem using the cdPreErase stage:
procedure TMainForm.BackgroundGradient_AdvancedCustomDrawItem(Sender: TCustomTreeView;
Node: TTreeNode; State: TCustomDrawState; Stage: TCustomDrawStage;
var PaintImages, DefaultDraw: Boolean);
begin
if Stage = cdPreErase then
// Avoid overwriting the gradient with the node background color
DefaultDraw := false;
end;
This draws a themed selection rectangle over the gradient background. If you want to custom-draw also the selected and hot-tracked nodes you must also set the corresponding canvas properties in the cdPostErase stage:
procedure TMainForm.BackgroundGradient_AdvancedCustomDrawItem(Sender: TCustomTreeView;
Node: TTreeNode; State: TCustomDrawState; Stage: TCustomDrawStage;
var PaintImages, DefaultDraw: Boolean);
begin
case Stage of
cdPreErase:
// Avoid overwriting the gradient with the node background color
DefaultDraw := false;
cdPostErase:
// Set selected and hot-track properties as usual
if ([cdsFocused, cdsSelected] * State <> []) then // selected node
begin
Sender.Canvas.Brush.Color := clNavy;
Sender.Canvas.Font.Color := clYellow;
Sender.Canvas.Font.Style := [fsBold];
end else
if (cdsHot in State) then // hot-tracked node
begin
Sender.Canvas.Brush.Color := clGray;
Sender.Canvas.Font.Color := clHighlightText;
Sender.Canvas.Font.Style := [];
end else // normal node
// Setting Brush.Color to clNone inhibits drawing the rectangle for the normal nodes.
// Don't use Sender.Canvas.BrushStyle = bsClear because it will be reset by changing the brush color.
Sender.Canvas.Brush.Color := clNone;
end;
end;
All this looks rather complicated... Yes, it is. Therefore, you can find more worked-out examples in the demo project in the examples/treeview/customdrawing' folder of the Lazarus installation.
See also