bootstack.Tree#
- class bootstack.Tree(*, nodes=None, data_source=None, parent_field='parent_id', root_value=None, label_field='name', icon_field=None, node_builder=None, order=None, selection_mode='single', show_selection_controls=False, select_on_click=True, indent=16, striped=False, show_scrollbar=True, scrollbar_variant='thin', height=None, density='default', accent=None, accent_selection=True, parent=None, **kwargs)#
Bases:
PublicWidgetBaseA hierarchical tree for navigation and selection.
Tree displays nested data as expandable rows. It is a hierarchy-first widget — reach for it when the structure is the point (file trees, outlines, settings navigation, grouped pickers). For flat records with columns, sorting, filtering, and paging, use
DataTableinstead.Nodes are object handles:
add()returns aTreeNodethat you hold and pass back toexpand(),select(),remove(), and so on — there are no string ids. Each node carries alabeland an optionalicon(with optional open/closed variants), plus an open-endeddatabag for your own attributes.- Parameters:
nodes (list[str | dict] | None) – Declarative initial tree — a list of node specs, where each spec is a label string or a dict like
{"label": "src", "icon": "folder", "children": [...]}. Anything not a recognized display key becomes that node’sdata. Mutually exclusive withdata_source.data_source (DataSourceProtocol | None) – A flat data source to project as a hierarchy. Each record carries a
parent_fieldpointing at its parent’s id (an adjacency list); the tree loads one branch at a time, querying a node’s children only when it is expanded — so a huge hierarchy shows instantly. Any data-source-protocol source works. Mutually exclusive withnodes.parent_field (str) – With
data_source, the record field that holds each row’s parent id. Defaults to'parent_id'.root_value (Any) – With
data_source, theparent_fieldvalue that marks a root node. Defaults toNone(a NULL/absent parent).label_field (str) – With
data_source, the record field used as the node label. Defaults to'name'. Ignored whennode_builderis given.icon_field (str | None) – With
data_source, an optional record field whose value is used as the node icon.node_builder (Callable[[dict], dict] | None) – With
data_source, an optional callable taking a record and returning a node spec dict ({'label': ..., 'icon': ...}) for full control over how a record renders. Overrideslabel_field/icon_field.order (str | Column | SortKey | Sequence[str | Column | SortKey] | None) – With
data_source, the ordering applied to each sibling group — a sort key or sequence of keys ('name','-created', acol, or aSortKey). Defaults to the source’s natural order.selection_mode (SelectionMode) – Node selection behavior —
'single'keeps one highlighted node,'multi'is click-to-toggle a set,'none'does no highlight selection (navigation/display only). Default'single'.show_selection_controls (bool) – If
True, show a per-node selection control (a checkbox inmultimode, a radio insinglemode) as the visible affordance for selection — mirroring ListView and DataTable. A selected row shows both the control and the highlight wash.select_on_click (bool) – If
True(default), clicking a row selects it. SetFalsewithshow_selection_controls=Trueso that only the control selects and a row click just focuses (e.g. to driveon_activatefor opening, VS Code style).indent (int) – Horizontal indent per depth level, in pixels. Defaults to
16.striped (bool) – If
True, alternate the row background color.show_scrollbar (bool) – If
True(default), show the vertical scrollbar. Mousewheel scrolling works regardless.scrollbar_variant (ScrollbarVariant) – Scrollbar style —
'thin'(default) for a slim bar, or'default'for the standard rounded bar.height (int | None) – Fixed height in pixels. When set, the tree maintains this height regardless of its content (so it can scroll without the parent layout providing a vertical constraint).
density (WidgetDensity) – Row height. Default
'default'.accent (AccentToken | str | None) – Color intent token for the selection highlight. Defaults to the theme’s default color.
parent (Any) – Override the context-stack parent.
**kwargs (Any) – Layout placement options applied by the parent container —
fill,expand,anchor,margin,row,column,sticky. See Layout & Spacing.
- property data_source: DataSourceProtocol | None#
The backing data source, or
Nonefor a declarative tree.
- property is_attached: bool#
Whether the widget is currently placed in its layout.
Truewhile the widget occupies space in its parent;Falseafterdetach(or before it has ever been placed). A detached widget keeps its state and can be returned to the layout withattach.
- property roots: list[TreeNode]#
The top-level nodes, in order.
Useful for reaching handles after declarative construction (
Tree(nodes=...)), which otherwise returns noTreeNodehandles.
- property schedule: Schedule#
Scheduler tied to this widget’s lifetime.
All jobs are automatically cancelled when the widget is destroyed. First access creates the
Scheduleinstance; subsequent accesses return the same instance.Usage:
self.schedule.delay(500, callback) self.schedule.every(1000, tick) job = self.schedule.idle(refresh) job.cancel()
- property selection: TreeNode | list[TreeNode] | None#
The selected node(s) — node handles, in tree order.
In
'single'mode, the selectedTreeNode(orNonewhen nothing is selected). In'multi'mode, alistof nodes (empty when nothing is selected). Each node’s data bag is atnode.data. Read-only.
- add(label='', *, parent=None, icon=None, open_icon=None, closed_icon=None, expanded=False, children=None, loader=None, data=None, **extra)#
Add a node and return its
TreeNodehandle.- Parameters:
label (str) – The node’s display label.
parent (TreeNode | None) – Parent node, or
Nonefor a root node.icon (str | None) – Bootstrap icon name shown before the label.
open_icon (str | None) – Icon used when the node is expanded (overrides
icon).closed_icon (str | None) – Icon used when the node is collapsed (overrides
icon).expanded (bool) – Whether the node starts expanded.
children (list | None) – Optional child specs (same format as
nodes=).loader (Callable[[TreeNode], Any] | None) – Callable invoked on first expand to fetch children lazily. Receives the node; returns an iterable of child specs.
data (dict | None) – Initial data bag for the node.
**extra (Any) – Extra keywords folded into the node’s
databag.
- attach(**kwargs)#
Return a detached widget to its layout, optionally moving it.
With no arguments, restores the widget to exactly where
detachtook it from. Any layout kwargs accepted by the original placement (e.g.fill,expand,anchor,sticky,margin) override the stored options. For stacked widgets,index=sets the position among the currently attached siblings (or pass an explicitbefore=/after=sibling); without one, the snapshotted position is used.Calling
attachon a widget that is already attached moves it (the kwargs are re-applied). Fireson_attach.- Parameters:
**kwargs (Any) – Layout placement options to override for this placement.
- Raises:
ParentResolutionError – If the widget was never placed in a layout.
- clear()#
Remove all nodes.
- clear_selection()#
Clear the selection.
- collapse(node)#
Collapse a node to hide its children.
- collapse_all()#
Collapse every node.
- deselect(node)#
Remove a node from the selection.
- destroy()#
Detach the context menu and destroy the widget (cancels pending relayout/type-ahead callbacks on the internal view).
- detach()#
Remove the widget from its layout without destroying it.
The widget stops occupying space but keeps its state, children, and event bindings, ready to be returned with
attach. The current position is snapshotted so a plainattach()restores it exactly — for stacked siblings this is the index among the currently attached siblings, so detaching other siblings first shifts that index.Calling
detachon a widget that is already detached, or one that was never placed in a layout, does nothing. Fireson_detach.
- emit(event, *, data=None)#
Fire a named event on this widget, as if it produced the event itself.
This is how a composite widget surfaces high-level activity to its listeners, and the generic counterpart to the
on_*()shorthands for firing events that have no dedicated method.- Parameters:
event (str) – The event name, unprefixed — the same name you pass to
on()or anon_<event>()shorthand (e.g.'change','select').data (Any) – The payload delivered to handlers. For a data-carrying event, pass the matching payload dataclass from
bootstack.events— the same object anon_<event>()handler receives. Leave as None for native events (click, hover, focus, …), which carry no payload.
Example
widget.emit("change", data=bs.events.ChangeEvent(value=new_value))
- expand(node)#
Expand a node to reveal its children.
- expand_all()#
Expand every node (loading lazy children as needed).
- find(matcher)#
Return the first node matching
matcher, orNone.matcheris either a predicate(TreeNode) -> boolor acol(...)condition (see Data Sources):A predicate is evaluated over the materialized nodes. For a data-source-backed tree that means the currently loaded branches only — a Python callable cannot be pushed down to the source.
A condition is matched against each node’s
data. On a declarative tree this scans every node; on a data-source-backed tree it is pushed down to the source, so it reaches nodes in unexpanded branches, and the path to the match is loaded (without expanding it) so a realTreeNodehandle is returned. Callreveal()on the result to scroll it into view.
- find_all(matcher)#
Return every node matching
matcher.Accepts the same predicate-or-condition
matcherasfind(), with the same reach (a predicate sees only materialized nodes; a condition is pushed down on a data-source-backed tree). Predicate and declarative matches come back in tree order; pushed-down condition matches come back in the source’s order.
- get_clipboard()#
Return the current text contents of the system clipboard.
- Returns:
The clipboard text, or an empty string when the clipboard is empty or holds non-text data.
- Return type:
- insert(index, label='', *, parent=None, **kwargs)#
Add a node at a specific position among its siblings.
- move(node, parent=None, index='end')#
Move a node to a new parent and/or position.
- on(event, handler=None)#
Bind
handlertoevent, or return a composableStream.With a handler — binds immediately and returns a
Subscription:sub = widget.on("change", handler) sub.cancel()
Without a handler — returns a
Streamfor operator chaining. The Tk binding is created lazily when.listen()is called:sub = widget.on("change").debounce(300).listen(handler) sub.cancel()
- Parameters:
event (str) – Event name (e.g.
"change","click").handler (Callable[[Any], Any] | None) – Optional callback. If omitted, a
Streamis returned.
- Returns:
Subscriptionwhen a handler is provided;Streamotherwise.- Return type:
- on_activate() Stream#
- on_activate(handler: Callable[[TreeNode], Any]) Subscription
Fired when a node is activated (double-click or Enter).
- on_attach(handler=None)#
Register a callback fired when the widget enters the layout.
Fires each time the widget becomes visible in its parent — on initial placement and on every
attach. Pair it withon_detachto keep per-visibility resources (timers, observers) tied to the widget’s presence on screen. The handler receives a curatedEvent.- Parameters:
handler (Callable[[Event], Any] | None) – Called when the widget is attached. Omit to get a composable
Stream.- Returns:
A cancellable
Subscriptionwhen a handler is given, otherwise aStream.- Return type:
- on_collapse() Stream#
- on_collapse(handler: Callable[[TreeNode], Any]) Subscription
Fired when a node is collapsed.
- on_destroy(handler=None)#
Register a callback fired when the widget is destroyed.
Fires once, as the widget is torn down — the place to release resources the widget owns that aren’t cleaned up automatically (file handles, observers, external subscriptions). The handler receives a curated
Event.- Parameters:
handler (Callable[[Event], Any] | None) – Called as the widget is destroyed. Omit to get a composable
Stream.- Returns:
A cancellable
Subscriptionwhen a handler is given, otherwise aStream.- Return type:
- on_detach(handler=None)#
Register a callback fired when the widget leaves the layout.
Fires each time the widget stops occupying space in its parent — on
detachand when an ancestor hides it. Pair it withon_attachto release per-visibility resources. The handler receives a curatedEvent.- Parameters:
handler (Callable[[Event], Any] | None) – Called when the widget is detached. Omit to get a composable
Stream.- Returns:
A cancellable
Subscriptionwhen a handler is given, otherwise aStream.- Return type:
- on_expand() Stream#
- on_expand(handler: Callable[[TreeNode], Any]) Subscription
Fired when a node is expanded.
- on_right_click() Stream#
- on_right_click(handler: Callable[[dict[str, Any]], Any]) Subscription
Fired on a right-click.
- on_select() Stream#
- on_select(handler: Callable[[TreeSelectionEvent], Any]) Subscription
Fired when the set of selected nodes changes.
- Parameters:
handler (Callable[[TreeSelectionEvent], Any] | None) – Called with a
TreeSelectionEventcarryingnodes(the full selection, in tree order). Omit to get a composableStreaminstead.- Returns:
A cancellable
Subscriptionwhen a handler is given, otherwise aStream.- Return type:
- refresh()#
Reload a data-source-backed tree from its source.
Re-queries the roots and discards loaded branches, so the tree reflects records that were inserted, updated, or deleted in the source. Selection and expansion state are reset. A no-op for a declarative tree.
- reload_children(node)#
Refresh a lazy node’s children — drop them and re-fetch via its
loader. Ifnodeis expanded the children reload immediately; otherwise they reload on the next expand. A no-op for non-lazy nodes.- Parameters:
node (TreeNode) – The lazy node (created with
loader=) to refresh.
- remove(node)#
Remove a node (and its descendants).
- Parameters:
node (TreeNode) – The node to remove.
- reveal(node)#
Expand ancestors and scroll so
nodeis visible.
- select(node)#
Select a node (replaces in single mode, adds in multi).
- select_all()#
Select every node. Only effective when
selection_mode='multi'.
- set_clipboard(text)#
Replace the system clipboard contents with
text.- Parameters:
text (str) – The text to place on the clipboard.
Attach a per-node context menu.
On right-click,
builder(node, menu)is called with the right-clickedTreeNodeand a fresh (emptied)ContextMenu; populate it withmenu.add_item(...)/add_check_item(...)/add_separator()etc. (capturenodein your callbacks). The menu is then shown at the cursor. Because it is rebuilt on every right-click, the items can depend on the node. If the builder adds no items, nothing is shown. PassNoneto remove the menu.Example:
def build(node, menu): menu.add_item("Rename", on_click=lambda: rename(node)) menu.add_item("Delete", icon="trash", on_click=lambda: tree.remove(node)) tree.set_context_menu(build)
- toggle(node)#
Expand the node if collapsed, or collapse it if expanded.
- walk()#
Yield every node depth-first, including collapsed ones.