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
🎨 Theming Tip

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
⚠️ Performance Warning

Be careful with recursive operations on very deep trees as they can cause stack overflow. Consider iterative approaches for deep hierarchies.

✅ Success Pattern

Use BeepTree for hierarchical data that benefits from custom rendering and advanced interaction patterns. Perfect for file browsers, organizational charts, and data explorers.