Gum Tool Tree View Reference

SkillSearch

Gum's element tree (Screens/Components/Standard/Behaviors panel). Triggers: GumTreeView, GumTreeNode, ElementTreeViewManager, ElementTreeViewCreator, MainTreeViewPlugin, TreeIconRegistry, tree icons, node add/refresh/drag. For the '!' overlay see gum-tool-errors; for selection plumbing see gum-tool-selection.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Gum Tool Tree View Reference skill

What this skill tells your AI

The instructions your AI receives, as published by vchelaru/gum in .claude/skills/gum-tool-tree-view/SKILL.md and read by ahel’s review.

The left-hand panel listing Screens, Components, Standard Elements, Behaviors, and the instances inside an open element. The model and logic are framework-neutral in Tool/TreeViewPlugin.Core (net10.0, shared by both heads); each head supplies only the panel, through IElementTreeView.

File map

FilePurpose
Tool/TreeViewPlugin.Core/GumTreeNode.cs (+ GumTreeNodeCollection.cs)The node model both trees bind to; implements ITreeNodeMutable
Tool/TreeViewPlugin.Core/ElementTreeViewManager.cs (+ .RightClick.cs)Builds, refreshes, searches and selects nodes; builds the right-click menu as ContextMenuItemViewModels
Tool/TreeViewPlugin.Core/IElementTreeView.csThe panel contract the manager talks to, its factory, and the external-drop event args
Tool/TreeViewPlugin.Core/TreeSelection/TreeSelectionModel.csSelection state and rules (click, range, toggle, keyboard nav, drag start, pruning) both controls feed
Tool/TreeViewPlugin.Core/TreeSelection/ (other files)Click/range/key decision classes on neutral enums; TreeDropKind and TreeDropLogic
Tool/TreeViewPlugin.Core/TreeIconCatalog.csIcon index → artwork path + theme color key, shared by both heads' registries
Tool/TreeViewPlugin.Core/MainTreeViewPlugin.csWires plugin events to RefreshUi(...) and error-indicator updates; loaded by both hosts
Tool/TreeViewPlugin.Core/TreeViewStateService.cs, CollapseToggleService.csExpansion state: persisted across sessions, and the collapse-button toggle
Gum/Plugins/InternalPlugins/TreeView/WpfElementTreeView.csWPF panel: GumTreeView, search box, flat results, collapse buttons, chip palette; WPF drag/cursor glue
Gum/Controls/GumTreeView.cs (+ .DragDrop.cs)WPF control: hit testing, expander clicks, drag start, drop adornment; delegates selection to TreeSelectionModel
Gum/Themes/Frb.TreeView.xaml, Gum/Controls/TreeIconRegistry.cs, TreeNodeIcon.csWPF row template and icon drawing
Tool/Gum.Avalonia/Plugins/TreeView/Avalonia panel (AvaloniaElementTreeView), row-list control (AvaloniaGumTreeView), palette, icons
Tools/Gum.Presentation/Services/RefreshCoalescer.csCollapses N RequestRefresh() calls in one synchronous burst into a single IDispatcher-posted refresh

The states tree (center-top "States" tab) shares StateTreePluginBase, StateTreeRightClickService and StateTreeKeyboardHandler in Tools/Gum.Presentation/Plugins/InternalPlugins/StatePlugin/; MainStatePlugin (WPF) and AvaloniaStatePlugin only build their tree control.

ElementTreeViewManager and its RightClick partial speak ITreeNode/ITreeNodeMutable, delegating to headless twins in Tools/Gum.Presentation/Managers/ (TreeNodeImageLogic, the TreeNode*Extensions families, TreeNodeExpansionPaths). Prefer adding logic there over growing the manager.

Selection is on the model, not the container

TreeView enforces a single selected item and clears the previous one on every change, so TreeViewItem.IsSelected is deliberately never set. TreeSelectionModel tracks the selection and the row visuals bind to GumTreeNode.IsSelected. The Avalonia tree goes further and is a flat, virtualized row list rather than a TreeView. Consequences:

  • Keyboard navigation is TreeSelectionModel.HandleKeyDown, called from each control's key handler.
  • A change to selection behavior goes in TreeSelectionModel (with a test), never in one head's control.
  • IsExpanded is ordinary two-way bound state, so expansion survives a rebuild without being captured and replayed.

Icons

TreeIconRegistry maps an index (the shared TreeNodeImageIndices constants, produced by the headless TreeNodeImageLogic) to a pack URI plus a theme color key; TreeNodeIcon renders the pair.

  • Source PNGs must be white-on-transparent, alpha carrying the shading. Tinting fills a shape with the theme brush and uses the artwork as an OpacityMask, so a colored source multiplies wrong.
  • Adding an icon is a constant in TreeNodeImageIndices plus an entry in TreeIconRegistry — in any position. The numbering is not tied to load order.
  • Icons re-tint on theme change via TreeIconRegistry.NotifyThemeChanged(); nothing is regenerated.

Refresh model

RefreshUi() is diff-based — existing nodes are reused and only differing ImageIndex/position/Tag/ Text are written. Replacing nodes wholesale would drop selection and scroll position.

Tag distinguishes node kinds: folder/container nodes have Tag == null; element nodes carry an ElementSave/BehaviorSave; instance nodes an InstanceSave.

MainTreeViewPlugin.HandleElementImported requests a refresh through a RefreshCoalescer rather than calling RefreshUi() directly, so importing N elements in one batch (Forms theme, .gumx import) produces one refresh instead of N.

Gotchas

  • Reordering within one collection must be remove-then-insert. GumTreeNodeCollection throws if a node is inserted into the collection it already belongs to, because detaching first would shift the index the caller computed. Reparenting across collections is a plain add.
  • Drag payloads travel in TreeDragPayload, not on the drag data. Gum's *Save types aren't [Serializable], so anything put on a WPF data object comes back null; the data carries only a marker format (TreeDragPayload.DataFormat in WPF, AvaloniaDragFormats.TreeNodes in Avalonia). Both heads' canvas drop readers and search-result lists use the same static.
  • ITreeNode.FullPath is backslash-separated. CopyPasteLogic slices a "Components\\" prefix off it.
  • Persisted expansion state is forward-slash-joined node Text paths (TreeNodeExpansionPaths). Changing either the separator or the use of Text silently discards every user's saved state.
  • Virtualization is off (the WPF TreeView default). That is what makes GumTreeView.ContainerFor/EnsureVisible reliable — turning it on would break container lookup for off-screen nodes.
  • GumTreeView.EnsureVisible defers to a Loaded dispatcher callback, since a newly-expanded ancestor's child has no container until layout runs.
  • The Avalonia row (TreeRowView) is an indent column plus a highlight Border holding the expander, icon and text, stretched to the row's right edge, and the rows themselves span the viewport (ItemsControl.MinWidth follows ScrollViewer.Viewport), matching the WPF RowBorder. Hover/selection colors go on the highlight, never on the row. A press on the expander calls AvaloniaGumTreeView.NotifyExpanderPressed() so the matching release neither selects nor opens the context menu (#4694); keep that if the expander is reworked.

Signals

GitHub stars
620
Forks
80
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
gum-tool-tree-view
Source
github.com/vchelaru/gum