BeepTree
Owner-drawn hierarchical tree control with custom rendering, scrolling, and advanced node management
📑 Table of Contents
Overview
BeepTree is a sophisticated owner-drawn tree control that provides advanced hierarchical data visualization with complete custom rendering. Unlike traditional TreeView controls, BeepTree uses BeepButton, BeepImage, and BeepCheckBox components for rendering without creating child controls, offering superior performance and customization.
Built on the BeepControl foundation, BeepTree features custom scrollbars,
multi-selection support, advanced node management, context menus, filtering capabilities,
and seamless integration with the Beep theming system. It provides hit-testing, hover
effects, and comprehensive event handling for rich user interactions.
Key Features
🎨 Owner-Drawn Rendering
Custom rendering using BeepButton, BeepImage, and BeepCheckBox without child controls
🌳 SimpleItem Architecture
Hierarchical data structure with parent-child relationships and state management
🖱️ Advanced Interaction
Hit-testing, hover effects, multi-selection, drag-and-drop ready foundation
📜 Custom Scrollbars
BeepScrollBar integration with smooth scrolling and viewport management
🔍 Search & Filter
Node finding, filtering with predicates, and visibility management
📋 Context Menus
BeepPopupListForm integration for dynamic context menu generation
⚡ High Performance
Efficient rendering with virtual scrolling and hit-area caching
🎭 Complete Theming
Full integration with Beep theming system for consistent styling
Basic Usage
Simple Tree Creation
// Create a basic BeepTree
var tree = new BeepTree
{
Size = new Size(300, 400),
Location = new Point(10, 10),
ShowCheckBox = true,
AllowMultiSelect = false,
ShowVerticalScrollBar = true,
ShowHorizontalScrollBar = true
};
// Create root nodes
var rootNode1 = new SimpleItem
{
Text = "Root Node 1",
ImagePath = "folder.svg",
IsExpanded = true
};
var rootNode2 = new SimpleItem
{
Text = "Root Node 2",
ImagePath = "folder.svg",
IsExpanded = false
};
// Add nodes to tree
var nodes = new List { rootNode1, rootNode2 };
tree.Nodes = nodes;
// Add to form
this.Controls.Add(tree);
Creating Hierarchical Structure
// Create hierarchical tree structure
var tree = new BeepTree { Size = new Size(400, 500) };
// Create root node
var rootNode = new SimpleItem
{
Text = "Company",
ImagePath = "company.svg",
IsExpanded = true,
Children = new BindingList()
};
// Create departments
var departments = new[]
{
new SimpleItem { Text = "Engineering", ImagePath = "engineering.svg" },
new SimpleItem { Text = "Marketing", ImagePath = "marketing.svg" },
new SimpleItem { Text = "Sales", ImagePath = "sales.svg" }
};
foreach (var dept in departments)
{
dept.ParentItem = rootNode;
dept.Children = new BindingList();
dept.IsExpanded = true;
// Add employees to each department
for (int i = 1; i <= 3; i++)
{
var employee = new SimpleItem
{
Text = $"Employee {i}",
ImagePath = "person.svg",
ParentItem = dept
};
dept.Children.Add(employee);
}
rootNode.Children.Add(dept);
}
tree.Nodes = new List { rootNode };
Event Handling
var tree = new BeepTree();
// Handle node selection
tree.NodeSelected += (sender, e) => {
if (tree.SelectedNode != null)
{
// Console.WriteLine($"Selected: {tree.SelectedNode.Text}");
}
};
// Handle node expansion
tree.NodeExpanded += (sender, e) => {
// Console.WriteLine($"Expanded node");
};
// Handle node checking
tree.NodeChecked += (sender, e) => {
// Console.WriteLine($"Node checked");
};
// Handle double-click
tree.NodeDoubleClicked += (sender, e) => {
if (tree.SelectedNode != null)
{
// Console.WriteLine($"Double-clicked: {tree.SelectedNode.Text}");
EditNode(tree.SelectedNode);
}
};
// Handle right-click for context menu
tree.NodeRightClicked += (sender, e) => {
if (tree.ClickedNode != null)
{
var contextMenu = CreateContextMenuFor(tree.ClickedNode);
tree.ShowContextMenu(contextMenu);
}
};
Node Management
Adding Nodes Dynamically
// Add root node
var newRootNode = new SimpleItem
{
Text = "New Root",
ImagePath = "folder.svg",
Children = new BindingList()
};
tree.AddNodeWithBranch(newRootNode);
// Add child node to existing parent
var childNode = new SimpleItem
{
Text = "Child Node",
ImagePath = "file.svg"
};
tree.AddNodeWithBranch(childNode, parentNode.GuidId);
// Add node with full hierarchy
var projectNode = new SimpleItem
{
Text = "Project Alpha",
ImagePath = "project.svg",
IsExpanded = true,
Children = new BindingList
{
new SimpleItem { Text = "Task 1", ImagePath = "task.svg" },
new SimpleItem { Text = "Task 2", ImagePath = "task.svg" },
new SimpleItem { Text = "Task 3", ImagePath = "task.svg" }
}
};
foreach (var child in projectNode.Children)
{
child.ParentItem = projectNode;
}
tree.AddNodeWithBranch(projectNode, departmentNode.GuidId);
Finding and Selecting Nodes
// Find node by text
var foundNode = tree.FindNode("Employee 1");
if (foundNode != null)
{
tree.SelectedNode = foundNode;
tree.EnsureVisible(foundNode);
}
// Find node by GUID
var nodeByGuid = tree.GetNodeByGuid("some-guid-id");
// Find node by predicate
var projectNodes = tree.FindNode(node =>
node.Text.StartsWith("Project") &&
node.Children.Count > 5);
// Select multiple nodes (if AllowMultiSelect is true)
tree.AllowMultiSelect = true;
tree.SelectAllNodes();
// Navigate programmatically
tree.SelectNextNode();
tree.SelectPreviousNode();
Filtering and Searching
// Filter nodes by text content
tree.FilterNodes(node =>
node.Text.Contains("Engineering", StringComparison.OrdinalIgnoreCase));
// Filter by node type or custom property
tree.FilterNodes(node =>
node.ImagePath.Contains("person") ||
node.Text.StartsWith("Task"));
// Complex filtering with multiple criteria
tree.FilterNodes(node => {
// Show completed tasks or all folders
return node.ImagePath.Contains("folder") ||
(node.ImagePath.Contains("task") && node.IsChecked);
});
// Clear filter to show all nodes
tree.ClearFilter();
// Expand/collapse operations
tree.ExpandAll();
tree.CollapseAll();
// Ensure specific node is visible
tree.EnsureVisible(importantNode);
Properties
Core Properties
| Property | Type | Description |
|---|---|---|
Nodes |
IList<SimpleItem> | Root level nodes collection |
SelectedNode |
SimpleItem | Currently selected node |
SelectedNodes |
List<SimpleItem> | Multiple selected nodes |
ClickedNode |
SimpleItem | Last clicked node |
AllowMultiSelect |
bool | Enable multiple node selection |
Display Properties
ShowCheckBox bool
Show checkboxes for each node
TextFont Font
Font used for node text
UseThemeFont bool
Use theme font instead of custom font
TextAlignment TextAlignment
Text alignment in nodes
Scrolling Properties
| Property | Type | Description |
|---|---|---|
ShowVerticalScrollBar |
bool | Show vertical scrollbar when needed |
ShowHorizontalScrollBar |
bool | Show horizontal scrollbar when needed |
Methods
| Method | Parameters | Description |
|---|---|---|
AddNodeWithBranch |
SimpleItem newItem, string parentGuidId | Add node to tree with hierarchical structure |
RemoveNode |
SimpleItem item | Remove node from tree |
FindNode |
string text | Find node by text content |
FindNode |
Func<SimpleItem, bool> predicate | Find node using custom predicate |
FilterNodes |
Func<SimpleItem, bool> predicate | Filter visible nodes based on predicate |
ExpandAll |
None | Expand all nodes in tree |
CollapseAll |
None | Collapse all nodes in tree |
EnsureVisible |
SimpleItem item | Scroll to make node visible |
SelectAllNodes |
None | Select all nodes (multi-select mode) |
ShowContextMenu |
BindingList<SimpleItem> menuList | Show context menu for current node |
Events
BeepTree provides comprehensive event handling for all user interactions:
NodeSelected
Node was selected
NodeDoubleClicked
Node was double-clicked
NodeExpanded
Node was expanded
NodeCollapsed
Node was collapsed
NodeChecked
Node checkbox was checked
NodeRightClicked
Node was right-clicked
Event Handling Examples
var tree = new BeepTree();
// Selection events
tree.SelectedItemChanged += (sender, e) => {
// Console.WriteLine($"Selection changed to: {e.SelectedItem?.Text}");
UpdateStatusBar(e.SelectedItem);
};
tree.NodeSelected += (sender, e) => {
var selectedNode = tree.SelectedNode;
if (selectedNode != null)
{
LoadNodeDetails(selectedNode);
UpdatePropertyPanel(selectedNode);
}
};
// Interaction events
tree.NodeDoubleClicked += (sender, e) => {
var clickedNode = tree.ClickedNode;
if (clickedNode != null)
{
if (clickedNode.Children.Count > 0)
{
clickedNode.IsExpanded = !clickedNode.IsExpanded;
}
else
{
EditNode(clickedNode);
}
}
};
// Context menu events
tree.NodeRightClicked += (sender, e) => {
var rightClickedNode = tree.ClickedNode;
if (rightClickedNode != null)
{
var contextMenu = CreateContextMenuFor(rightClickedNode);
tree.ShowContextMenu(contextMenu);
}
};
tree.MenuItemSelected += (sender, e) => {
var selectedMenuItem = e.SelectedItem;
var targetNode = tree.ClickedNode;
switch (selectedMenuItem.Text)
{
case "Edit":
EditNode(targetNode);
break;
case "Delete":
DeleteNode(targetNode);
break;
case "Add Child":
AddChildNode(targetNode);
break;
}
};
Examples
File System Explorer
public class FileSystemTree : BeepTree
{
public FileSystemTree()
{
ShowCheckBox = false;
AllowMultiSelect = true;
// Load root drives
LoadDrives();
// Handle expansion for lazy loading
NodeExpanded += OnNodeExpanded;
NodeDoubleClicked += OnNodeDoubleClicked;
}
private void LoadDrives()
{
var nodes = new List();
foreach (var drive in DriveInfo.GetDrives())
{
if (drive.IsReady)
{
var driveNode = new SimpleItem
{
Text = $"{drive.Name} ({drive.DriveType})",
ImagePath = GetDriveIcon(drive.DriveType),
Tag = drive.RootDirectory,
Children = new BindingList()
};
// Add placeholder for expansion
if (HasSubDirectories(drive.RootDirectory))
{
driveNode.Children.Add(new SimpleItem { Text = "Loading..." });
}
nodes.Add(driveNode);
}
}
Nodes = nodes;
}
private void OnNodeExpanded(object sender, BeepMouseEventArgs e)
{
var expandedNode = SelectedNode;
if (expandedNode?.Tag is DirectoryInfo directory)
{
LoadDirectoryContents(expandedNode, directory);
}
}
private void LoadDirectoryContents(SimpleItem parentNode, DirectoryInfo directory)
{
try
{
parentNode.Children.Clear();
// Add subdirectories
foreach (var subDir in directory.GetDirectories())
{
var dirNode = new SimpleItem
{
Text = subDir.Name,
ImagePath = "folder.svg",
Tag = subDir,
ParentItem = parentNode,
Children = new BindingList()
};
if (HasSubDirectories(subDir))
{
dirNode.Children.Add(new SimpleItem { Text = "Loading..." });
}
parentNode.Children.Add(dirNode);
}
// Add files
foreach (var file in directory.GetFiles())
{
var fileNode = new SimpleItem
{
Text = file.Name,
ImagePath = GetFileIcon(file.Extension),
Tag = file,
ParentItem = parentNode
};
parentNode.Children.Add(fileNode);
}
}
catch (UnauthorizedAccessException)
{
parentNode.Children.Clear();
parentNode.Children.Add(new SimpleItem
{
Text = "Access Denied",
ImagePath = "error.svg"
});
}
Invalidate();
}
private string GetDriveIcon(DriveType driveType)
{
return driveType switch
{
DriveType.Fixed => "hard-drive.svg",
DriveType.CDRom => "cd-rom.svg",
DriveType.Removable => "usb-drive.svg",
DriveType.Network => "network-drive.svg",
_ => "drive.svg"
};
}
}
Project Management Tree
public class ProjectManagementTree : BeepTree
{
public ProjectManagementTree()
{
ShowCheckBox = true;
AllowMultiSelect = true;
LoadProjects();
NodeChecked += OnNodeChecked;
NodeUnchecked += OnNodeUnchecked;
}
private void LoadProjects()
{
var projects = new List
{
CreateProject("Project Alpha", "active"),
CreateProject("Project Beta", "planning"),
CreateProject("Project Gamma", "completed")
};
Nodes = projects;
}
private SimpleItem CreateProject(string name, string status)
{
var project = new SimpleItem
{
Text = name,
ImagePath = GetProjectStatusIcon(status),
Tag = new { Name = name, Status = status },
Children = new BindingList(),
IsExpanded = true
};
// Add phases
var phases = new[] { "Requirements", "Design", "Development", "Testing", "Deployment" };
foreach (var phaseName in phases)
{
var phase = new SimpleItem
{
Text = phaseName,
ImagePath = "phase.svg",
ParentItem = project,
Children = new BindingList(),
IsExpanded = false
};
// Add tasks to each phase
for (int i = 1; i <= 3; i++)
{
var task = new SimpleItem
{
Text = $"{phaseName} Task {i}",
ImagePath = "task.svg",
ParentItem = phase,
IsChecked = Random.Shared.Next(0, 2) == 1
};
phase.Children.Add(task);
}
project.Children.Add(phase);
}
return project;
}
private void OnNodeChecked(object sender, BeepMouseEventArgs e)
{
var checkedNode = SelectedNode;
if (checkedNode != null)
{
// Auto-check children
SetChildrenChecked(checkedNode, true);
// Update parent status
UpdateParentCheckStatus(checkedNode);
// Update progress
UpdateProjectProgress(GetProjectNode(checkedNode));
}
}
private void SetChildrenChecked(SimpleItem node, bool isChecked)
{
foreach (var child in node.Children)
{
child.IsChecked = isChecked;
SetChildrenChecked(child, isChecked);
}
}
private SimpleItem GetProjectNode(SimpleItem node)
{
var current = node;
while (current.ParentItem != null)
{
current = current.ParentItem;
}
return current;
}
private void UpdateProjectProgress(SimpleItem projectNode)
{
var totalTasks = CountAllTasks(projectNode);
var completedTasks = CountCompletedTasks(projectNode);
var progress = totalTasks > 0 ? (completedTasks * 100) / totalTasks : 0;
// Update project node text with progress
var projectInfo = projectNode.Tag;
projectNode.Text = $"{projectInfo.GetType().GetProperty("Name").GetValue(projectInfo)} ({progress}% Complete)";
Invalidate();
}
}
Database Schema Tree
public class DatabaseSchemaTree : BeepTree
{
private IDMEEditor dmeEditor;
public DatabaseSchemaTree(IDMEEditor editor)
{
dmeEditor = editor;
ShowCheckBox = true;
AllowMultiSelect = true;
LoadDatabaseConnections();
NodeExpanded += OnNodeExpanded;
NodeRightClicked += OnNodeRightClicked;
}
private void LoadDatabaseConnections()
{
var nodes = new List();
foreach (var connection in dmeEditor.ConfigEditor.DataConnections)
{
var connectionNode = new SimpleItem
{
Text = connection.ConnectionName,
ImagePath = GetConnectionIcon(connection.DatabaseType),
Tag = connection,
Children = new BindingList(),
IsExpanded = false
};
// Add schema placeholder
connectionNode.Children.Add(new SimpleItem { Text = "Loading..." });
nodes.Add(connectionNode);
}
Nodes = nodes;
}
private void OnNodeExpanded(object sender, BeepMouseEventArgs e)
{
var expandedNode = SelectedNode;
if (expandedNode?.Tag is ConnectionProperties connection)
{
LoadDatabaseSchema(expandedNode, connection);
}
}
private void LoadDatabaseSchema(SimpleItem connectionNode, ConnectionProperties connection)
{
try
{
var dataSource = dmeEditor.GetDataSource(connection.ConnectionName);
if (dataSource != null)
{
connectionNode.Children.Clear();
var entities = dataSource.GetEntitesList();
foreach (var entity in entities)
{
var entityNode = new SimpleItem
{
Text = entity.EntityName,
ImagePath = entity.Viewtype == ViewType.Table ? "table.svg" : "view.svg",
Tag = entity,
ParentItem = connectionNode,
Children = new BindingList()
};
// Add columns
foreach (var field in entity.Fields)
{
var columnNode = new SimpleItem
{
Text = $"{field.FieldName} ({field.Fieldtype})",
ImagePath = GetColumnIcon(field),
Tag = field,
ParentItem = entityNode
};
entityNode.Children.Add(columnNode);
}
connectionNode.Children.Add(entityNode);
}
}
}
catch (Exception ex)
{
connectionNode.Children.Clear();
connectionNode.Children.Add(new SimpleItem
{
Text = $"Error: {ex.Message}",
ImagePath = "error.svg"
});
}
Invalidate();
}
private string GetConnectionIcon(DatabaseType dbType)
{
return dbType switch
{
DatabaseType.SqlServer => "sql-server.svg",
DatabaseType.Oracle => "oracle.svg",
DatabaseType.MySql => "mysql.svg",
DatabaseType.PostgreSql => "postgresql.svg",
_ => "database.svg"
};
}
}
Theming
BeepTree provides comprehensive theming support that affects all visual elements:
Theme Application
var tree = new BeepTree
{
Theme = "DarkTheme"
};
// Theme automatically applies:
// - TreeBackColor (background)
// - TreeForeColor (text color)
// - TreeNodeSelectedBackColor (selected node background)
// - TreeNodeSelectedForeColor (selected node text)
// - TreeNodeHoverBackColor (hover background)
// - TreeNodeHoverForeColor (hover text)
// Apply theme manually if needed
tree.ApplyTheme();
Theme-Responsive Features
- Node Colors - Background, foreground, and selection colors adapt to theme
- Scrollbars - Custom scrollbars use theme colors and styling
- Icons - SVG icons automatically adapt to theme foreground colors
- Checkboxes - Node checkboxes use theme checkbox styling
- Context Menus - BeepPopupListForm inherits theme styling
BeepTree automatically applies theme colors to all internal components including buttons, images, and scrollbars, ensuring a cohesive visual experience.
Best Practices
⚡ Performance
- Lazy Loading: Load child nodes only when parent is expanded
- Virtual Scrolling: Built-in virtual scrolling handles large trees efficiently
- Hit-Testing: Use efficient hit-testing for mouse interactions
- Batch Updates: Suspend drawing during bulk node operations
- Memory Management: Dispose resources properly, especially with dynamic content
📊 Data Management
- GUID Usage: Always use unique GUIDs for node identification
- Parent-Child Relations: Maintain proper parent-child relationships
- State Management: Use node properties for expansion, selection, and visibility states
- Data Binding: Bind complex objects to node Tag property
🎨 User Experience
- Consistent Icons: Use consistent icon sets for different node types
- Context Menus: Provide appropriate context menus for different node types
- Keyboard Navigation: Implement keyboard shortcuts for common operations
- Visual Feedback: Use hover effects and selection highlighting
- Progressive Disclosure: Show details gradually as users explore
♿ Accessibility
- Keyboard Support: Ensure full keyboard navigation support
- Screen Readers: Use meaningful node text and descriptions
- High Contrast: Test with high contrast themes
- Focus Indicators: Provide clear focus indication
Be careful with recursive operations on very deep trees as they can cause stack overflow. Consider iterative approaches for deep hierarchies.
Use BeepTree for hierarchical data that benefits from custom rendering and advanced interaction patterns. Perfect for file browsers, organizational charts, and data explorers.