CRUD operations with interactive UI#
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 meaningfulTag
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
withThreeCheckState = true
for parent nodes that aggregate child check states