Skip to content

CRUD operations with interactive UI#

Flexible TreeView structure

Overview#

Modern applications demand intuitive, hierarchical interfaces that enable users to efficiently manage and interact with data. Many existing TreeView components lack the flexibility required for advanced scenarios, often forcing developers to write excessive boilerplate code and manually handle complex tree interactions.

Flexible TreeView provides a comprehensive solution for designing and implementing sophisticated hierarchical views and interactions. It streamlines development, increases productivity, and simplifies your codebase.

How Flexible TreeView helps#

Flexible TreeView accelerates your development process by offering:

  • Complex UI - Build advanced hierarchical interfaces in hours, not weeks.
  • Contextual data - Present relevant data dynamically based on node type.
  • In-place editing - Edit data directly within the tree structure—no need for separate forms.
  • Rich editor controls - Leverage built-in support for text fields, checkboxes, combo boxes, date pickers, and more.

Implementation#

Here's how to implement the CRUD operations form with Flexible TreeView:

void RunUseCase()
{
    CreateTreeviewStructure();
    LoadEmployeesData();
}

void CreateTreeviewStructure()
{
    treeView.Theme = eVisualTheme.Office2007Blue;
    treeView.Options.Node.AutoNodeHeight = true;
    treeView.Options.NodeControl.NodeControlFilterMode = eNodeControlFilterMode.Dynamic;
    treeView.FilterNodeControl += FilterNodeControl;
    treeView.NodeButtonClicked += OnNodeButton_Click;

    TreeColumn nameColumn = new TreeColumn("Employee", 200);
    TreeColumn positionColumn = new TreeColumn("Position", 150);
    TreeColumn hireDateColumn = new TreeColumn("Hire Date", 100);
    TreeColumn salaryColumn = new TreeColumn("Salary", 100);
    TreeColumn activeColumn = new TreeColumn("Active", 60);
    TreeColumn actionsColumn = new TreeColumn("", 100);

    nameColumn.AttachTo(treeView);
    positionColumn.AttachTo(treeView);
    hireDateColumn.AttachTo(treeView);
    salaryColumn.AttachTo(treeView);
    activeColumn.AttachTo(treeView);
    actionsColumn.AttachTo(treeView);

    NodeTextBox nameControl = new NodeTextBox();
    nameControl.DataFieldName = "Text";
    nameControl.ApplyChangesOnLostFocus = true;
    nameControl.Editable = true;
    nameControl.AttachToColumn(nameColumn);
    nameControl.AttachTo(treeView);

    NodeComboBox positionControl = new NodeComboBox();
    positionControl.DataFieldName = "Position";
    positionControl.ApplyChangesOnLostFocus = true;
    positionControl.Editable = true;
    foreach (var position in _employeePositions)
    {
        positionControl.DropDownItems.Add(position);
    }
    positionControl.AttachToColumn(positionColumn);
    positionControl.AttachTo(treeView);

    NodeDateTime hireDateControl = new NodeDateTime();
    hireDateControl.DataFieldName = "HireDate";
    hireDateControl.ApplyChangesOnLostFocus = true;
    hireDateControl.Editable = true;
    hireDateControl.AttachToColumn(hireDateColumn);
    hireDateControl.AttachTo(treeView);

    NodeNumeric salaryControl = new NodeNumeric();
    salaryControl.DataFieldName = "Salary";
    salaryControl.ApplyChangesOnLostFocus = true;
    salaryControl.Editable = true;
    salaryControl.DecimalPlaces = 0;
    salaryControl.DisplayFormat.Enabled = true;
    salaryControl.DisplayFormat.FormatText = "${0}";
    salaryControl.AttachToColumn(salaryColumn);
    salaryControl.AttachTo(treeView);

    NodeCheckBox activeControl = new NodeCheckBox();
    activeControl.DataFieldName = "CheckState";
    activeControl.Editable = true;
    activeControl.AttachToColumn(activeColumn);
    activeControl.AttachTo(treeView);

    NodeButton deleteButton = new NodeButton();
    deleteButton.StaticValue = "Delete";
    deleteButton.Tag = "delete";
    deleteButton.AttachToColumn(actionsColumn);
    deleteButton.AttachTo(treeView);

    NodeButton addButton = new NodeButton();
    addButton.StaticValue = "Add";
    addButton.Tag = "add";
    addButton.AttachToColumn(actionsColumn);
    addButton.AttachTo(treeView);
}

void FilterNodeControl(FlexibleTreeView treeview, FilterNodeControlEventArgs args)
{
    if (!(args.NodeControl is BindableControl nodeControl))
        return;

    var isDepartmentNode = args.Node is DepartmentNode;

    // Hide non-relevant node controls for department nodes.
    if (isDepartmentNode)
    {
        if (nodeControl.DataFieldName is "Position" or "HireDate" or "Salary")
            args.ControlVisibility = eNodeControlVisibility.Hidden;
    }
    else
    {
        if (nodeControl is NodeButton button && button.Tag == "add")
            args.ControlVisibility = eNodeControlVisibility.Hidden;
    }
}

void LoadEmployeesData()
{
    treeView.Nodes.Clear();

    Dictionary<string, List<Employee>> departments = new Dictionary<string, List<Employee>>()
    {
        ["Engineering"] = new List<Employee>
        {
            new Employee("John Smith", "Manager", new DateTime(2018, 5, 10), 120000),
            new Employee("Alice Johnson", "Developer", new DateTime(2019, 3, 15), 95000),
            new Employee("David Lee", "Developer", new DateTime(2020, 7, 22), 90000)
        },
        ["Design"] = new List<Employee>
        {
            new Employee("Emma Wilson", "Manager", new DateTime(2017, 11, 8), 110000),
            new Employee("Michael Brown", "Designer", new DateTime(2019, 9, 3), 85000)
        }
    };

    foreach (var dept in departments)
    {
        var deptNode = new DepartmentNode(dept.Key)
        {
            CheckType = eCheckType.CheckBox,
            InteractiveCheckMode = eInteractiveCheckMode.ManualChangeDenied,
            ThreeCheckState = true
        };
        deptNode.AttachTo(treeView);

        bool isChecked = true;
        foreach (var emp in dept.Value)
        {
            var empNode = new EmployeeNode(emp.Name)
            {
                Position = _employeePositions.IndexOf(emp.Position),
                HireDate = emp.Hired,
                Salary = emp.Salary,
                Checked = isChecked
            };
            // make every second employee active.
            isChecked ^= true;
            empNode.AttachTo(deptNode);
        }
    }

    treeView.ExpandAll();
}

void AddEmployee(Node parent)
{
    var newEmployee = new EmployeeNode("New Employee")
    {
        Position = _employeePositions.IndexOf("Developer"),
        HireDate = DateTime.Today,
        Salary = 80000,
        Checked = true
    };

    newEmployee.AttachTo(parent);
    newEmployee.Focus();
}

void OnNodeButton_Click(FlexibleTreeView treeview, NodeControlEventArgs args)
{
    var node = args.Node;
    var opName = (string)args.NodeControl.Tag;

    if (opName == "add")
    {
        AddEmployee(args.Node);
        return;
    }

    if (opName == "delete")
    {
        var nodeTypeName = node is DepartmentNode ? "department" : "employee";

        if (MessageBox.Show($"Are you sure you want to delete this {nodeTypeName}?",
                "Confirm Delete", MessageBoxButtons.YesNo) == DialogResult.Yes)
        {
            node.Detach();
        }
    }
}

public class BusinessUnitNode : Node
{
    protected BusinessUnitNode(string name) : base(name)
    {
    }
}

public class EmployeeNode : BusinessUnitNode
{
    public int EmployeeId { get; set; }
    public int? Position { get; set; }
    public DateTime HireDate { get; set; }
    public decimal Salary { get; set; }

    public EmployeeNode(string name) : base(name)
    {
    }
}

public class DepartmentNode : BusinessUnitNode
{
    public DepartmentNode(string name) : base(name)
    {
    }
}

public class Employee
{
    public string Name { get; set; }
    public string Position { get; set; }
    public DateTime Hired { get; set; }
    public decimal Salary { get; set; }

    public Employee(string name, string position, DateTime hired, decimal salary)
    {
        Name = name;
        Position = position;
        Hired = hired;
        Salary = salary;
    }
}
Sub RunUseCase()  
    CreateTreeviewStructure()  
    LoadEmployeesData()  
End Sub  

Sub CreateTreeviewStructure()  
    treeView.Theme = eVisualTheme.Office2007Blue  
    treeView.Options.Node.AutoNodeHeight = True  
    treeView.Options.NodeControl.NodeControlFilterMode = eNodeControlFilterMode.Dynamic  
    AddHandler treeView.FilterNodeControl, AddressOf FilterNodeControl  
    AddHandler treeView.NodeButtonClicked, AddressOf OnNodeButton_Click  

    Dim nameColumn As New TreeColumn("Employee", 200)  
    Dim positionColumn As New TreeColumn("Position", 150)  
    Dim hireDateColumn As New TreeColumn("Hire Date", 100)  
    Dim salaryColumn As New TreeColumn("Salary", 100)  
    Dim activeColumn As New TreeColumn("Active", 60)  
    Dim actionsColumn As New TreeColumn("", 100)  

    nameColumn.AttachTo(treeView)  
    positionColumn.AttachTo(treeView)  
    hireDateColumn.AttachTo(treeView)  
    salaryColumn.AttachTo(treeView)  
    activeColumn.AttachTo(treeView)  
    actionsColumn.AttachTo(treeView)  

    Dim nameControl As New NodeTextBox()  
    nameControl.DataFieldName = "Text"  
    nameControl.ApplyChangesOnLostFocus = True  
    nameControl.Editable = True  
    nameControl.AttachToColumn(nameColumn)  
    nameControl.AttachTo(treeView)  

    Dim positionControl As New NodeComboBox()  
    positionControl.DataFieldName = "Position"  
    positionControl.ApplyChangesOnLostFocus = True  
    positionControl.Editable = True  
    For Each position In _employeePositions  
        positionControl.DropDownItems.Add(position)  
    Next  
    positionControl.AttachToColumn(positionColumn)  
    positionControl.AttachTo(treeView)  

    Dim hireDateControl As New NodeDateTime()  
    hireDateControl.DataFieldName = "HireDate"  
    hireDateControl.ApplyChangesOnLostFocus = True  
    hireDateControl.Editable = True  
    hireDateControl.AttachToColumn(hireDateColumn)  
    hireDateControl.AttachTo(treeView)  

    Dim salaryControl As New NodeNumeric()  
    salaryControl.DataFieldName = "Salary"  
    salaryControl.ApplyChangesOnLostFocus = True  
    salaryControl.Editable = True  
    salaryControl.DecimalPlaces = 0  
    salaryControl.DisplayFormat.Enabled = True  
    salaryControl.DisplayFormat.FormatText = "${0}"  
    salaryControl.AttachToColumn(salaryColumn)  
    salaryControl.AttachTo(treeView)  

    Dim activeControl As New NodeCheckBox()  
    activeControl.DataFieldName = "CheckState"  
    activeControl.Editable = True  
    activeControl.AttachToColumn(activeColumn)  
    activeControl.AttachTo(treeView)  

    Dim deleteButton As New NodeButton()  
    deleteButton.StaticValue = "Delete"  
    deleteButton.Tag = "delete"  
    deleteButton.AttachToColumn(actionsColumn)  
    deleteButton.AttachTo(treeView)  

    Dim addButton As New NodeButton()  
    addButton.StaticValue = "Add"  
    addButton.Tag = "add"  
    addButton.AttachToColumn(actionsColumn)  
    addButton.AttachTo(treeView)  
End Sub  

Sub FilterNodeControl(treeview As FlexibleTreeView, args As FilterNodeControlEventArgs)  
    Dim nodeControl As BindableControl = TryCast(args.NodeControl, BindableControl)  
    If nodeControl Is Nothing Then  
        Return  
    End If  

    Dim isDepartmentNode = TypeOf args.Node Is DepartmentNode  

    ' Hide non-relevant node controls for department nodes.  
    If isDepartmentNode Then  
        If nodeControl.DataFieldName = "Position" OrElse nodeControl.DataFieldName = "HireDate" OrElse nodeControl.DataFieldName = "Salary" Then  
            args.ControlVisibility = eNodeControlVisibility.Hidden  
        End If  
    Else  
        Dim button As NodeButton = TryCast(nodeControl, NodeButton)  
        If button IsNot Nothing AndAlso button.Tag = "add" Then  
            args.ControlVisibility = eNodeControlVisibility.Hidden  
        End If  
    End If  
End Sub  

Sub LoadEmployeesData()  
    treeView.Nodes.Clear()  

    Dim departments As New Dictionary(Of String, List(Of Employee)) From {  
        {"Engineering", New List(Of Employee) From {  
            New Employee("John Smith", "Manager", New DateTime(2018, 5, 10), 120000),  
            New Employee("Alice Johnson", "Developer", New DateTime(2019, 3, 15), 95000),  
            New Employee("David Lee", "Developer", New DateTime(2020, 7, 22), 90000)  
        }},  
        {"Design", New List(Of Employee) From {  
            New Employee("Emma Wilson", "Manager", New DateTime(2017, 11, 8), 110000),  
            New Employee("Michael Brown", "Designer", New DateTime(2019, 9, 3), 85000)  
        }}  
    }  

    For Each dept In departments  
        Dim deptNode As New DepartmentNode(dept.Key) With {  
            .CheckType = eCheckType.CheckBox,  
            .InteractiveCheckMode = eInteractiveCheckMode.ManualChangeDenied,  
            .ThreeCheckState = True  
        }  
        deptNode.AttachTo(treeView)  

        Dim isChecked As Boolean = True  
        For Each emp In dept.Value  
            Dim empNode As New EmployeeNode(emp.Name) With {  
                .Position = _employeePositions.IndexOf(emp.Position),  
                .HireDate = emp.Hired,  
                .Salary = emp.Salary,  
                .Checked = isChecked  
            }  
            ' make every second employee active.  
            isChecked = Not isChecked  
            empNode.AttachTo(deptNode)  
        Next  
    Next  

    treeView.ExpandAll()  
End Sub  

Sub AddEmployee(parent As Node)  
    Dim newEmployee As New EmployeeNode("New Employee") With {  
        .Position = _employeePositions.IndexOf("Developer"),  
        .HireDate = DateTime.Today,  
        .Salary = 80000,  
        .Checked = True  
    }  

    newEmployee.AttachTo(parent)  
    newEmployee.Focus()  
End Sub  

Sub OnNodeButton_Click(treeview As FlexibleTreeView, args As NodeControlEventArgs)  
    Dim node = args.Node  
    Dim opName As String = CStr(args.NodeControl.Tag)  

    If opName = "add" Then  
        AddEmployee(args.Node)  
        Return  
    End If  

    If opName = "delete" Then  
        Dim nodeTypeName As String = If(TypeOf node Is DepartmentNode, "department", "employee")  

        If MessageBox.Show($"Are you sure you want to delete this {nodeTypeName}?",  
                "Confirm Delete", MessageBoxButtons.YesNo) = DialogResult.Yes Then  
            node.Detach()  
        End If  
    End If  
End Sub  

Public Class BusinessUnitNode  
    Inherits Node  

    Protected Sub New(name As String)  
        MyBase.New(name)  
    End Sub  
End Class  

Public Class EmployeeNode  
    Inherits BusinessUnitNode  

    Public Property EmployeeId As Integer  
    Public Property Position As Integer?  
    Public Property HireDate As DateTime  
    Public Property Salary As Decimal  

    Public Sub New(name As String)  
        MyBase.New(name)  
    End Sub  
End Class  

Public Class DepartmentNode  
    Inherits BusinessUnitNode  

    Public Sub New(name As String)  
        MyBase.New(name)  
    End Sub  
End Class  

Public Class Employee  
    Public Property Name As String  
    Public Property Position As String  
    Public Property Hired As DateTime  
    Public Property Salary As Decimal  

    Public Sub New(name As String, position As String, hired As DateTime, salary As Decimal)  
        Me.Name = name  
        Me.Position = position  
        Me.Hired = hired  
        Me.Salary = salary  
    End Sub  
End Class

Best Practices#

This use case demonstrates several key techniques for building interactive CRUD interfaces:

  • Use node classes hierarchy - Create distinct node classes (DepartmentNode, EmployeeNode) inheriting from a common base to support mixed hierarchical data and apply different behaviors per node type
  • Dynamic control filtering - Use FilterNodeControl treeview event to dynamically show/hide node controls based on node context to maintain clean UI
  • In-place editing - Enable ApplyChangesOnLostFocus for immediate data changes persistence when the user clicks outside of the node control editor
  • Intuitive action controls - Use NodeButton with meaningful Tag values to distinguish between different operations in a single event handler
  • Data formatting - Configure DisplayFormat for numeric node controls
  • Interactive check state - Use InteractiveCheckMode.ManualChangeDenied with ThreeCheckState = true for parent nodes that aggregate child check states