
Syncfusion Blazor Treeview
- 232 installs
- 4 repo stars
- Updated July 28, 2026
- syncfusion/blazor-ui-components-skills
Use syncfusion-blazor-treeview for development tasks
About
syncfusion-blazor-treeview: A skill for development. This provides functionality for development workflows.
- syncfusion-blazor-treeview
Syncfusion Blazor Treeview by the numbers
- 232 all-time installs (skills.sh)
- +12 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #1,651 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Jul 29, 2026 (Skillselion catalog sync)
npx skills add https://github.com/syncfusion/blazor-ui-components-skills --skill syncfusion-blazor-treeviewAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 232 |
|---|---|
| repo stars | ★ 4 |
| Last updated | July 28, 2026 |
| Repository | syncfusion/blazor-ui-components-skills ↗ |
What it does
Use syncfusion-blazor-treeview for development tasks
Files
Implementing Syncfusion Blazor TreeView Component
The Blazor TreeView component displays hierarchical data in an expandable/collapsible tree structure. It supports local and remote data binding, single and multi-selection, editing, checkboxes, drag-drop reordering, virtualization for large datasets, filtering, and comprehensive event handling.
---
📋 Table of Contents
1. When to Use 2. Installation & Setup 3. Quick Start 4. Key Properties 5. Key Methods 6. Key Events 7. Common Patterns 8. Complete Reference Navigation
---
When to Use This Skill
Use the TreeView component when you need to:
- Display hierarchical data in a tree structure with expandable/collapsible nodes
- Single selection: Allow users to select one node from the tree
- Multi-selection: Enable selection of multiple tree nodes using Ctrl+Click and Shift+Click
- Checkbox selection: Provide checkbox-based multi-selection with automatic parent-child state management
- Edit nodes: Allow inline renaming or editing of node text
- Drag and drop: Enable reordering nodes within the hierarchy
- Filter and search: Implement search functionality to find nodes
- Remote data sources: Bind to Web APIs, OData services, or custom endpoints
- Handle events: Respond to expand, collapse, select, edit, and drag-drop actions
- Virtualization: Display large datasets (1000+ nodes) with smooth scrolling
- Custom styling: Apply icons, colors, and templates for nodes
---
Installation & Setup
Install Syncfusion NuGet packages and configure your Blazor project:
// 1. Install NuGet packages
// Install-Package Syncfusion.Blazor.Navigations -Version 26.1.35
// Install-Package Syncfusion.Blazor.Themes -Version 26.1.35
// 2. Add to _Imports.razor
@using Syncfusion.Blazor
@using Syncfusion.Blazor.Navigations
// 3. Register service in Program.cs
builder.Services.AddSyncfusionBlazor();
// 4. Add CSS theme to Index.html or _Layout.cshtml
<link href="_content/Syncfusion.Blazor.Themes/bootstrap5.css" rel="stylesheet" />---
Quick Start
@using Syncfusion.Blazor.Navigations
<SfTreeView TValue="MailItem">
<TreeViewFieldsSettings TValue="MailItem"
Id="Id"
Text="FolderName"
Child="Children"
DataSource="@MyFolder">
</TreeViewFieldsSettings>
<TreeViewEvents TValue="MailItem" NodeSelected="OnNodeSelected"></TreeViewEvents>
</SfTreeView>
@code {
public class MailItem
{
public string? Id { get; set; }
public string? FolderName { get; set; }
public List<MailItem>? Children { get; set; }
}
void OnNodeSelected(NodeSelectEventArgs args)
{
Console.WriteLine($"Selected: {args.NodeData.Text}");
}
List<MailItem> MyFolder = new()
{
new MailItem { Id = "1", FolderName = "Inbox", Children = new() },
new MailItem { Id = "2", FolderName = "Sent", Children = new() }
};
}---
Key Properties
| Property | Type | Default | Purpose |
|---|---|---|---|
AllowDragAndDrop | bool | false | Enable/disable drag-drop hierarchy reordering |
AllowEditing | bool | false | Allow double-click node renaming |
AllowMultiSelection | bool | false | Enable Ctrl+Click multi-selection |
ShowCheckBox | bool | false | Display checkboxes for each node |
AutoCheck | bool | true | Auto-check/uncheck children when parent checked |
EnablePersistence | bool | false | Persist expanded/selected/checked state to localStorage |
EnableVirtualization | bool | false | Virtual scrolling for 1000+ nodes (requires Height) |
ExpandedNodes | string[] | Empty | Initially expanded node IDs (2-way bindable) |
SelectedNodes | string[] | Empty | Selected node IDs (2-way bindable) |
CheckedNodes | string[] | Empty | Checked node IDs (2-way bindable) |
LoadOnDemand | bool | true | Load children only when node expands |
ExpandOn | ExpandAction | Click | Trigger expand on Click/DoubleClick/None |
Height | string | "auto" | Fixed height (required for virtualization) |
---
Key Methods
| Method | Purpose |
|---|---|
ExpandAllAsync() | Expand all nodes |
ExpandAllAsync(string[] nodeIds) | Expand specific nodes by ID |
CollapseAllAsync() | Collapse all nodes |
CollapseAllAsync(string[] nodeIds) | Collapse specific nodes |
BeginEditAsync(string nodeId) | Enter edit mode for a node |
GetTreeData() | Get all tree data |
GetTreeData(string nodeId) | Get specific node data by ID |
EnsureVisibleAsync(string nodeId) | Scroll to make node visible |
CheckAllAsync() | Check all checkboxes |
UncheckAllAsync() | Uncheck all checkboxes |
ClearStateAsync() | Clear all state (selection, expand, check) |
---
Key Events
| Event | Fires When | Common Uses |
|---|---|---|
Created | TreeView initialized | Post-initialization setup, load preferences |
DataBound | Data binding complete | Auto-expand default nodes, validate data |
NodeSelected | Node left-clicked | Load node details, enable actions |
NodeClicked | Node clicked | Distinguish single vs double-click |
NodeExpanded | Node expanded | Load child nodes (load-on-demand) |
NodeCollapsed | Node collapsed | Optional: Unload children from memory |
NodeEditing | Before edit mode | Validate permissions, prevent edits |
NodeEdited | Edit confirmed | Validate new text, save to server |
OnNodeDragStart | Drag begins | Prevent dragging restricted nodes |
NodeDropped | Drop completed | Update hierarchy in server |
NodeChecking | Before checkbox changes | Prevent checking restricted nodes |
NodeChecked | Checkbox changed | Update related data, trigger actions |
DataSourceChanged | Data source updated | Re-apply filters, refresh calculations |
OnActionFailure | Action fails (API error) | Recover from errors, show notifications |
OnKeyPress | Key pressed | Implement keyboard shortcuts (Delete, F2, etc) |
---
Common Patterns
Pattern 1: Basic Selection
<SfTreeView TValue="Item" @bind-SelectedNodes="@SelectedIds">
<TreeViewFieldsSettings TValue="Item" DataSource="@Items" />
<TreeViewEvents TValue="Item" NodeSelected="OnSelect"></TreeViewEvents>
</SfTreeView>
@code {
string[] SelectedIds = Array.Empty<string>();
void OnSelect(NodeSelectEventArgs args) => Console.WriteLine(args.NodeData.Text);
}Pattern 2: Multiple Selection
<SfTreeView TValue="Item" AllowMultiSelection="true" @bind-SelectedNodes="@SelectedIds">
<TreeViewFieldsSettings TValue="Item" DataSource="@Items" />
</SfTreeView>Pattern 3: Load on Demand
void OnNodeExpanded(NodeExpandEventArgs args)
{
if (args.NodeData.HasChild && args.NodeData.Child == null)
{
// Load children from API
args.NodeData.Child = await FetchChildren(args.NodeData.Id);
}
}Pattern 4: Drag and Drop
<SfTreeView TValue="Item" AllowDragAndDrop="true">
<TreeViewFieldsSettings TValue="Item" DataSource="@Items" />
<TreeViewEvents TValue="Item" NodeDropped="OnDropped"></TreeViewEvents>
</SfTreeView>
void OnDropped(DragAndDropEventArgs args) => UpdateHierarchy(args);Pattern 5: Node Editing
<SfTreeView TValue="Item" AllowEditing="true" DoubleClickAction="DoubleClickAction.Edit">
<TreeViewFieldsSettings TValue="Item" DataSource="@Items" />
<TreeViewEvents TValue="Item" NodeEdited="OnEdited"></TreeViewEvents>
</SfTreeView>
void OnEdited(NodeEditEventArgs args) => SaveChanges(args.NodeData);Pattern 6: Checkboxes
<SfTreeView TValue="Item" AllowCheckBoxes="true" ChildChecking="ChildCheckState.Both">
<TreeViewFieldsSettings TValue="Item" DataSource="@Items" />
</SfTreeView>
var checked = treeRef.GetAllCheckedNodes();---
Documentation and Navigation Guide
Getting Started
📄 Read: references/getting-started.md
- Installation and NuGet package setup
- Project configuration by type (WebAssembly, Server, Web App, MAUI)
- CSS theme configuration
- Service registration and first TreeView component
Data Binding and Sources
📄 Read: references/data-binding.md
- Local data binding (hierarchical and self-referential structures)
- Remote data with OData, OData V4, and Web API adaptors
- Load on Demand for large datasets
- TreeViewFieldsSettings property mappings
- DataBound event for post-binding operations
Node Selection
📄 Read: references/node-selection.md
- Single node selection (default behavior)
- Multi-selection with AllowMultiSelection property
- Accessing selected node data via NodeSelected event
- Programmatic selection using SelectedNodes binding
- Selection validation and conditional selection
Expand and Collapse Actions
📄 Read: references/expand-collapse-actions.md
- Expand/collapse methods (ExpandAllAsync, CollapseAllAsync)
- ExpandedNodes two-way binding for programmatic control
- Initial expand state via data source
- Expand/collapse animations with TreeViewNodeAnimationSettings
- Load-on-demand child node loading via NodeExpanded event
Node Editing
📄 Read: references/node-editing.md
- Enable editing with AllowEditing property
- Double-click to enter edit mode
- BeginEditAsync method for programmatic edit entry
- NodeEditing and NodeEdited events for validation
- Rename operations with conflict detection
Checkbox Features
📄 Read: references/checkbox-features.md
- ShowCheckBox property for multi-item selection
- AutoCheck for automatic parent-child synchronization
- CheckedNodes two-way binding for programmatic control
- Getting checked nodes with filtering and iteration
- Permissions and role-based checkbox patterns
Events and Callbacks
📄 Read: references/events-handling.md
- Lifecycle events (Created, DataBound)
- Selection events (NodeSelected, SelectedNodesChanged)
- Expand/collapse events (NodeExpanded, NodeCollapsed)
- Edit events (NodeEditing, NodeEdited)
- Checkbox events (NodeChecking, NodeChecked)
- Drag-drop events (OnNodeDragStart, NodeDropped)
- Keyboard shortcuts (Enter, Delete, F2, Arrow keys)
Advanced Features
📄 Read: references/advanced-features.md
- Drag-and-drop with hierarchy reordering
- UI virtualization for 1000+ nodes
- Search and filtering functionality
- Sorting (Ascending, Descending, None)
- Performance optimization techniques
Customization and Styling
📄 Read: references/customization-styling.md
- Icon customization (expand/collapse, node icons)
- Dynamic icons based on data
- Text wrapping and display formatting
- CSS styling with e-icons classes
- Theme support and responsive design
Authorization and Security
📄 Read: references/authorization-authentication.md
- Authentication setup with AuthorizeView
- Role-based authorization and permissions
- Node-level access control
- Claims-based authorization patterns
- Securing edit operations and drag-drop
Navigation Patterns
📄 Read: references/navigation-patterns.md
- Node traversal methods (parent, children, siblings)
- Breadcrumb navigation implementation
- NavigateUrl property for node links
- Parent-child navigation relationships
- Deep-linking to specific nodes
---
Quick Links and Real-World Examples
Need help? Start with: 1. Quick Start - Get running in 5 minutes 2. Common Patterns - Copy-paste patterns for your use case 3. Key Properties - Find property details 4. data-binding.md - Learn data binding approaches 5. events-handling.md - Understand all events
Real-world implementations:
- File Browser: Use hierarchical data + expand-collapse + icons + drag-drop
- Organization Chart: Use data-binding + templates + multi-level navigation
- Navigation Menu: Use hierarchical data + keyboard navigation + load-on-demand
- Category Filter: Use self-referential data + checkboxes + filtering
- Permissions UI: Use checkboxes + AutoCheck + role-based authorization
---
Summary
This skill provides comprehensive guidance for implementing the Syncfusion Blazor TreeView component. Use the Documentation and Navigation Guide section above to find the specific reference file you need based on your use case.
Advanced Features in TreeView
Table of Contents
Drag and Drop
Enable Drag and Drop
@using Syncfusion.Blazor.Navigations
<SfTreeView TValue="EmployeeData" AllowDragAndDrop="true">
<TreeViewFieldsSettings TValue="EmployeeData"
Id="Id"
Text="Name"
Child="Children"
DataSource="@Team">
</TreeViewFieldsSettings>
<TreeViewEvents TValue="EmployeeData"
OnNodeDragStart="OnDragStart"
NodeDropped="OnNodeDropped">
</TreeViewEvents>
</SfTreeView>
@code {
public class EmployeeData
{
public string? Id { get; set; }
public string? Name { get; set; }
public List<EmployeeData>? Children { get; set; }
}
List<EmployeeData> Team = new()
{
new EmployeeData
{
Id = "team1",
Name = "Development",
Children = new()
{
new EmployeeData { Id = "dev1", Name = "Alice" },
new EmployeeData { Id = "dev2", Name = "Bob" }
}
}
};
void OnDragStart(DragAndDropEventArgs args)
{
Console.WriteLine($"Dragging: {args.DraggedNodeData.Name}");
}
void OnNodeDropped(DragAndDropEventArgs args)
{
var draggedNodeId = args.DraggedNodeData.Id;
var dropIndex = args.DropIndex;
Console.WriteLine($"Dropped node {draggedNodeId} at index {dropIndex}");
// Update hierarchy
UpdateNodePosition(draggedNodeId, dropIndex);
}
void UpdateNodePosition(string draggedNodeId, int dropIndex)
{
// Implementation for updating node hierarchy
}
}Drag Drop Indicators
The TreeView shows visual indicators during drag-drop:
- Plus (+) icon: Drop as child node
- Minus (-) icon: Cannot drop at this location
- Line indicator: Drop as sibling node
Restrict Dragging
void OnNodeDragStart(DragAndDropEventArgs args)
{
// Prevent dragging system nodes
if (args.DraggedNodeData.Id.StartsWith("system_"))
{
args.Cancel = true;
return;
}
}Virtualization
Enable UI Virtualization
Virtualization renders only visible nodes, improving performance with large datasets:
@using Syncfusion.Blazor.Navigations
<SfTreeView TValue="TreeData"
EnableVirtualization="true"
Height="400">
<TreeViewFieldsSettings TValue="TreeData"
Id="Id"
Text="Name"
ParentID="Pid"
HasChildren="HasChild"
DataSource="@TreeDataSource">
</TreeViewFieldsSettings>
</SfTreeView>
@code {
public class TreeData
{
public string? Id { get; set; }
public string? Pid { get; set; }
public string? Name { get; set; }
public bool HasChild { get; set; }
}
List<TreeData> TreeDataSource = new();
protected override void OnInitialized()
{
// Generate large dataset
for (int i = 1; i <= 1000; i++)
{
TreeDataSource.Add(new TreeData
{
Id = i.ToString(),
Name = $"Node {i}",
HasChild = i % 10 == 0
});
}
}
}Virtualization Requirements:
- Set
EnableVirtualization="true" - Set fixed
Height(in pixels) - Use with
LoadOnDemand="true"for optimal performance
Benefits:
- Smooth scrolling with thousands of items
- Reduced memory usage
- Faster initial render
Limitations:
- Cannot use expand/collapse animation
- Select All selects only visible items
Search and Filter
Implement Search Functionality
@using Syncfusion.Blazor.Navigations
<input type="text"
@bind="SearchText"
@bind:event="oninput"
placeholder="Search nodes..."
@onkeyup="HandleSearch" />
<SfTreeView TValue="MailItem">
<TreeViewFieldsSettings TValue="MailItem"
Id="Id"
Text="FolderName"
ParentID="ParentId"
HasChildren="HasChildren"
DataSource="@FilteredFolders">
</TreeViewFieldsSettings>
</SfTreeView>
@code {
string SearchText = "";
List<MailItem> FilteredFolders => GetFilteredFolders();
List<MailItem> GetFilteredFolders()
{
if (string.IsNullOrWhiteSpace(SearchText))
return MyFolder;
var searchLower = SearchText.ToLower();
var matched = new HashSet<string>();
// Find matching nodes
foreach (var folder in MyFolder)
{
if (folder.FolderName?.ToLower().Contains(searchLower) ?? false)
{
matched.Add(folder.Id);
// Include all parents
var current = folder;
while (current?.ParentId != null)
{
matched.Add(current.ParentId);
current = MyFolder.FirstOrDefault(x => x.Id == current.ParentId);
}
}
}
return MyFolder.Where(x => matched.Contains(x.Id)).ToList();
}
void HandleSearch()
{
// Trigger search on input
StateHasChanged();
}
public class MailItem
{
public string? Id { get; set; }
public string? ParentId { get; set; }
public string? FolderName { get; set; }
public bool HasChildren{ get; set; }
}
List<MailItem> MyFolder = new()
{
new MailItem { Id = "1", FolderName = "Documents", HasChildren=true },
new MailItem { Id = "1-1", ParentId = "1", FolderName = "Work" },
new MailItem { Id = "1-2", ParentId = "1", FolderName = "Personal" }
};
}Highlight Search Results
@using System.Text.RegularExpressions
<TreeViewTemplates TValue="MailItem">
<NodeTemplate>
<span>@Html.Raw(HighlightSearchText((context as MailItem).FolderName))</span>
</NodeTemplate>
</TreeViewTemplates>
@code {
string HighlightSearchText(string text)
{
if (string.IsNullOrWhiteSpace(SearchText) || string.IsNullOrWhiteSpace(text))
return text;
var pattern = $"({Regex.Escape(SearchText)})";
var highlighted = Regex.Replace(
text,
pattern,
"<mark style='background-color: yellow;'>$1</mark>",
RegexOptions.IgnoreCase
);
return highlighted;
}
}Sorting
Sort Tree Nodes
@using System.Linq
<button @onclick="SortAscending">Sort A-Z</button>
<button @onclick="SortDescending">Sort Z-A</button>
<SfTreeView TValue="FileItem">
<TreeViewFieldsSettings TValue="FileItem"
Id="Id"
Text="FileName"
ParentID="ParentId"
DataSource="@SortedFiles">
</TreeViewFieldsSettings>
</SfTreeView>
@code {
public class FileItem
{
public string? Id { get; set; }
public string? ParentId { get; set; }
public string? FileName { get; set; }
}
List<FileItem> AllFiles = new()
{
new FileItem { Id = "1", FileName = "Zebra" },
new FileItem { Id = "2", FileName = "Apple" },
new FileItem { Id = "3", FileName = "Monkey" }
};
List<FileItem> SortedFiles => AllFiles;
void SortAscending()
{
AllFiles.Sort((a, b) => a.FileName?.CompareTo(b.FileName) ?? 0);
StateHasChanged();
}
void SortDescending()
{
AllFiles.Sort((a, b) => b.FileName?.CompareTo(a.FileName) ?? 0);
StateHasChanged();
}
}Natural Sort (alphanumeric)
void SortNatural()
{
AllFiles.Sort((a, b) =>
NaturalSort(a.FileName, b.FileName)
);
StateHasChanged();
}
int NaturalSort(string? a, string? b)
{
if (a == b) return 0;
if (a == null) return -1;
if (b == null) return 1;
var aChars = a.ToCharArray();
var bChars = b.ToCharArray();
int aIndex = 0, bIndex = 0;
while (aIndex < aChars.Length && bIndex < bChars.Length)
{
if (char.IsDigit(aChars[aIndex]) && char.IsDigit(bChars[bIndex]))
{
// Extract numbers
long aNum = 0, bNum = 0;
while (aIndex < aChars.Length && char.IsDigit(aChars[aIndex]))
aNum = aNum * 10 + (aChars[aIndex++] - '0');
while (bIndex < bChars.Length && char.IsDigit(bChars[bIndex]))
bNum = bNum * 10 + (bChars[bIndex++] - '0');
if (aNum != bNum)
return aNum.CompareTo(bNum);
}
else
{
if (aChars[aIndex] != bChars[bIndex])
return aChars[aIndex].CompareTo(bChars[bIndex]);
aIndex++;
bIndex++;
}
}
return aChars.Length.CompareTo(bChars.Length);
}Integration
Context Menu Integration
@using Syncfusion.Blazor.Navigations
<SfTreeView TValue="EmployeeData" @ref="tree">
<TreeViewFieldsSettings TValue="EmployeeData" DataSource="@ListData" />
<TreeViewEvents TValue="EmployeeData" NodeClicked="nodeClicked"></TreeViewEvents>
<SfContextMenu TValue="MenuItem" Target="#treeview" Items="@MenuItems">
<MenuEvents TValue="MenuItem" ItemSelected="OnMenuSelect"></MenuEvents>
</SfContextMenu>
</SfTreeView>
@code {
SfTreeView<EmployeeData> tree;
string selectedNodeId;
public List<MenuItem> MenuItems = new()
{
new MenuItem { Text = "Copy" },
new MenuItem { Text = "Rename" },
new MenuItem { Text = "Delete" }
};
void nodeClicked(NodeClickEventArgs args)
{
selectedNodeId = args.NodeData.Id;
}
void OnMenuSelect(MenuEventArgs args)
{
switch (args.Item.Text)
{
case "Copy":
CopyNode(selectedNodeId);
break;
case "Rename":
RenameNode(selectedNodeId);
break;
case "Delete":
DeleteNode(selectedNodeId);
break;
}
}
}Badge Integration
<TreeViewTemplates TValue="TaskItem">
<NodeTemplate>
<div style="display: flex; justify-content: space-between; align-items: center;">
<span>@((context as TaskItem).Name)</span>
@if ((context as TaskItem).Count > 0)
{
<span style="background: #ff6b6b; color: white; border-radius: 12px; padding: 2px 8px; font-size: 12px;">
@((context as TaskItem).Count)
</span>
}
</div>
</NodeTemplate>
</TreeViewTemplates>
@code {
public class TaskItem
{
public string? Id { get; set; }
public string? Name { get; set; }
public int Count { get; set; }
}
}Performance Optimization
Best Practices
1. Enable virtualization for large datasets (>500 nodes) 2. Use LoadOnDemand to load children only when expanded 3. Cache node lookups to avoid repeated searches 4. Implement pagination for API responses 5. Debounce search to avoid excessive filtering 6. Use shouldRender patterns to skip unnecessary renders
Lazy Loading Pattern
void OnNodeExpanded(NodeExpandEventArgs args)
{
if (args.NodeData.HasChild && args.NodeData.Child == null)
{
// Only load if not already loaded
LoadChildNodes(args.NodeData.Id);
}
}
async Task LoadChildNodes(string parentId)
{
var children = await FetchFromApi(parentId);
// Update data source
}Avoid Performance Pitfalls
// ❌ BAD: Recreating entire list on filter
void BadSearch()
{
MyFolder = MyFolder // Creates new list every time
.Where(x => x.Name.Contains(SearchText))
.ToList();
}
// ✅ GOOD: Use computed property
List<MailItem> FilteredFolders => MyFolder
.Where(x => x.FolderName?.Contains(SearchText) ?? false)
.ToList();Common Patterns
Pattern 1: Multi-Select with Toolbar
<div>
<button @onclick="DeleteSelected" disabled="@(SelectedNodeIds.Length == 0)">
Delete @SelectedNodeIds.Length
</button>
<button @onclick="ExportSelected" disabled="@(SelectedNodeIds.Length == 0)">
Export @SelectedNodeIds.Length
</button>
</div>
<SfTreeView TValue="Item" AllowMultiSelection="true" @bind-SelectedNodes="@SelectedNodeIds">
<TreeViewFieldsSettings TValue="Item" DataSource="@Items" />
</SfTreeView>
@code {
string[] SelectedNodeIds = Array.Empty<string>();
async Task DeleteSelected()
{
foreach (var id in SelectedNodeIds)
await DeleteItem(id);
SelectedNodeIds = Array.Empty<string>();
}
}Methods Reference
Expand/Collapse Methods
ExpandAllAsync()
Expands all nodes in the TreeView:
@ref SfTreeView<Employee> TreeViewRef;
<SfTreeView TValue="Employee" @ref="TreeViewRef">
<TreeViewFieldsSettings TValue="Employee" DataSource="@Employees" />
</SfTreeView>
<button @onclick="ExpandAll">Expand All</button>
@code {
SfTreeView<Employee> TreeViewRef;
async Task ExpandAll()
{
await TreeViewRef.ExpandAllAsync();
}
}CollapseAllAsync()
Collapses all nodes in the TreeView:
<button @onclick="CollapseAll">Collapse All</button>
@code {
async Task CollapseAll()
{
await TreeViewRef.CollapseAllAsync();
}
}---
Data Retrieval Methods
GetTreeData()
Retrieves all data from TreeView:
async Task GetAllData()
{
var allData = TreeViewRef.GetTreeData();
Console.WriteLine($"Total nodes: {allData.Count}");
foreach (var node in allData)
{
Console.WriteLine($"ID: {node.Id}, Text: {node.Text}");
}
}GetNode(nodeId)
Retrieves a specific node:
async Task GetSpecificNode()
{
var node = TreeViewRef.GetNode("employee-5");
if (node != null)
{
Console.WriteLine($"Found: {node.Text}");
}
}GetDisabledNodesAsync()
Gets all disabled nodes:
async Task GetDisabled()
{
var disabledNodes = await TreeViewRef.GetDisabledNodesAsync();
Console.WriteLine($"Disabled nodes: {disabledNodes.Count}");
}---
Node Modification Methods
AddNodes()
Adds new nodes to TreeView:
void AddNewNode()
{
var newNode = new Employee
{
Id = "emp-new",
Name = "New Employee",
ParentId = "emp-1"
};
TreeViewRef.AddNodes(newNode);
}
void AddMultipleNodes()
{
var newNodes = new List<Employee>
{
new Employee { Id = "emp-new-1", Name = "Employee 1", ParentId = "emp-1" },
new Employee { Id = "emp-new-2", Name = "Employee 2", ParentId = "emp-1" }
};
TreeViewRef.AddNodes(newNodes);
}RemoveNodes()
Removes nodes from TreeView:
void RemoveNode()
{
TreeViewRef.RemoveNodes("emp-5");
}
void RemoveMultipleNodes()
{
var nodeIds = new string[] { "emp-5", "emp-6", "emp-7" };
TreeViewRef.RemoveNodes(nodeIds);
}RefreshNodeAsync()
Updates a specific node's data:
async Task UpdateNodeData()
{
var node = new Employee
{
Id = "emp-5",
Name = "Updated Name",
ParentId = "emp-1"
};
await TreeViewRef.RefreshNodeAsync(node);
}---
Navigation Methods
EnsureVisibleAsync()
Scrolls TreeView to make specified node visible:
async Task ScrollToNode()
{
await TreeViewRef.EnsureVisibleAsync("emp-5");
// Optional: also select the node
SelectedNodes = new string[] { "emp-5" };
}---
Enable/Disable Methods
EnableNodesAsync()
Enables disabled nodes:
async Task EnableNodes()
{
var nodeIds = new string[] { "emp-5", "emp-6" };
await TreeViewRef.EnableNodesAsync(nodeIds);
}DisableNodesAsync()
Disables nodes (prevents interaction):
async Task DisableNodes()
{
var nodeIds = new string[] { "emp-5", "emp-6" };
await TreeViewRef.DisableNodesAsync(nodeIds);
}---
Checkbox Methods
CheckAllAsync()
Checks all nodes (if checkboxes enabled):
async Task CheckAll()
{
await TreeViewRef.CheckAllAsync();
}UncheckAllAsync()
Unchecks all nodes:
async Task UncheckAll()
{
await TreeViewRef.UncheckAllAsync();
}GetAllCheckedNodes()
Gets all checked node IDs:
void GetChecked()
{
var checkedNodeIds = TreeViewRef.GetAllCheckedNodes();
Console.WriteLine($"Checked nodes: {string.Join(", ", checkedNodeIds)}");
}---
State Management Methods
ClearStateAsync()
Clears all state (selected, expanded, checked nodes):
async Task ClearState()
{
await TreeViewRef.ClearStateAsync();
// After this:
// - No nodes are selected
// - No nodes are expanded
// - No nodes are checked
// - Scroll position reset
}---
Advanced Property Reference
EnableVirtualization
<SfTreeView TValue="DataItem"
EnableVirtualization="true"
Height="500px"
LoadOnDemand="true">
<TreeViewFieldsSettings TValue="DataItem" DataSource="@LargeDataset" />
</SfTreeView>
// Benefits:
// - Renders only visible nodes
// - Handles 100K+ nodes smoothly
// - Reduces memory footprint
// - Requires Height to be setSortOrder and SortComparer
// Ascending sort
<SfTreeView TValue="Employee" SortOrder="SortOrder.Ascending">
<TreeViewFieldsSettings TValue="Employee" DataSource="@Employees" />
</SfTreeView>
// Custom sort with IComparer
public class EmployeeComparer : IComparer<object>
{
public int Compare(object x, object y)
{
var emp1 = x as Employee;
var emp2 = y as Employee;
// Sort by Name length first, then alphabetically
int result = emp1.Name.Length.CompareTo(emp2.Name.Length);
if (result == 0)
result = emp1.Name.CompareTo(emp2.Name);
return result;
}
}
<SfTreeView TValue="Employee" SortComparer="@new EmployeeComparer()">
<TreeViewFieldsSettings TValue="Employee" DataSource="@Employees" />
</SfTreeView>DropArea
// Allow drag-drop only within tree container
<div class="tree-container">
<SfTreeView TValue="Employee"
AllowDragAndDrop="true"
DropArea=".tree-container">
<TreeViewFieldsSettings TValue="Employee" DataSource="@Employees" />
</SfTreeView>
</div>
// Or specify by ID
<SfTreeView TValue="Employee"
AllowDragAndDrop="true"
DropArea="#dropzone">
<TreeViewFieldsSettings TValue="Employee" DataSource="@Employees" />
</SfTreeView>
<div id="dropzone"></div>FullRowNavigable & FullRowSelect
// Allow selection anywhere on node row (not just text)
<SfTreeView TValue="Employee" FullRowSelect="true">
<TreeViewFieldsSettings TValue="Employee" DataSource="@Employees" />
</SfTreeView>
// Allow navigation anywhere on node row
<SfTreeView TValue="Employee" FullRowNavigable="true">
<TreeViewFieldsSettings TValue="Employee" DataSource="@Employees" />
</SfTreeView>---
Common Patterns with Methods
Pattern 1: Bulk Operations
async Task BulkUpdateNodes()
{
// Get all checked nodes
var checkedIds = TreeViewRef.GetAllCheckedNodes();
// Update each node
foreach (var id in checkedIds)
{
var node = TreeViewRef.GetNode(id);
if (node != null)
{
node.Name = $"Updated: {node.Name}";
await TreeViewRef.RefreshNodeAsync(node);
}
}
// Clear selection
await TreeViewRef.ClearStateAsync();
}Pattern 2: Export Selected Nodes
async Task ExportSelected()
{
var selectedNodes = SelectedNodes;
var exportData = new List<EmployeeExport>();
foreach (var id in selectedNodes)
{
var node = TreeViewRef.GetNode(id);
if (node != null)
{
exportData.Add(new EmployeeExport
{
Id = node.Id,
Name = node.Text,
Level = node.Level
});
}
}
// Export to CSV/JSON
await ExportToFile(exportData);
}Pattern 3: Dynamic Tree Building
async Task BuildDynamicTree()
{
// Start with root nodes
TreeViewRef.AddNodes(rootNodes);
// Expand root
await TreeViewRef.ExpandAllAsync();
// Load child nodes on demand
var allNodes = TreeViewRef.GetTreeData();
var parentIds = allNodes
.Where(n => n.HasChildren)
.Select(n => n.Id)
.ToList();
// Fetch and add children for each parent
foreach (var parentId in parentIds)
{
var children = await FetchChildNodes(parentId);
TreeViewRef.AddNodes(children);
}
}---
Troubleshooting
Issue: Virtualization causes flickering
- Increase Height value
- Reduce number of items per render
- Check for heavy computations in template
Issue: Search too slow
- Implement debouncing
- Limit results displayed
- Use indexed search if possible
Issue: Drag-drop not working with virtualization
- Virtualization and drag-drop have limited compatibility
- Consider disabling one or the other
- Test with smaller datasets first
Next Steps
- Use events-handling.md for all advanced events
- Check accessibility.md for accessible patterns
- Review performance.md for optimization strategies
Authorization and Authentication in TreeView
Table of Contents
1. Authentication Setup 2. Basic AuthorizeView Integration 3. Role-Based Authorization 4. Permission Filtering 5. Node-Level Security 6. Claims-Based Authorization 7. Securing Events and Methods 8. Best Practices for Security 9. Troubleshooting 10. Next Steps
---
Authentication Setup
Basic AuthorizeView Integration
Protect the entire TreeView component from unauthorized access:
@using Syncfusion.Blazor.Navigations
@using Microsoft.AspNetCore.Authorization
<AuthorizeView>
<Authorized>
<div>
<p>Welcome, @context.User.Identity?.Name!</p>
<SfTreeView TValue="MailItem">
<TreeViewFieldsSettings TValue="MailItem"
Id="Id"
Text="FolderName"
ParentID="ParentId"
DataSource="@MyFolder">
</TreeViewFieldsSettings>
</SfTreeView>
<form method="post" action="Identity/Account/LogOut">
<button type="submit">Log out</button>
</form>
</div>
</Authorized>
<NotAuthorized>
<p>You are not authorized to view this content.</p>
</NotAuthorized>
</AuthorizeView>
@code {
public class MailItem
{
public string? Id { get; set; }
public string? ParentId { get; set; }
public string? FolderName { get; set; }
}
List<MailItem> MyFolder = new()
{
new MailItem { Id = "1", FolderName = "Inbox" },
new MailItem { Id = "2", FolderName = "Sent" }
};
}Role-Based Authorization
Restrict by User Role
@using Syncfusion.Blazor.Navigations
@using Microsoft.AspNetCore.Authorization
<AuthorizeView Roles="Admin,Manager">
<Authorized>
<SfTreeView TValue="EmployeeItem">
<TreeViewFieldsSettings TValue="EmployeeItem" DataSource="@Employees" />
</SfTreeView>
</Authorized>
<NotAuthorized>
<p>You don't have permission to view employee data.</p>
</NotAuthorized>
</AuthorizeView>
@code {
public class EmployeeItem
{
public string? Id { get; set; }
public string? Name { get; set; }
}
List<EmployeeItem> Employees = new()
{
new EmployeeItem { Id = "1", Name = "Alice" },
new EmployeeItem { Id = "2", Name = "Bob" }
};
}Multiple Role Check
<AuthorizeView Policy="CanViewReports">
<Authorized>
<!-- Show TreeView -->
</Authorized>
<NotAuthorized>
<p>You don't have permission.</p>
</NotAuthorized>
</AuthorizeView>Permission-Based Node Filtering
Filter Nodes by User Permissions
@using Syncfusion.Blazor.Navigations
@inject AuthenticationStateProvider AuthenticationStateProvider
<SfTreeView TValue="FolderItem">
<TreeViewFieldsSettings TValue="FolderItem"
Id="Id"
Text="FolderName"
ParentID="ParentId"
DataSource="@FilteredFolders">
</TreeViewFieldsSettings>
</SfTreeView>
@code {
[CascadingParameter]
private Task<AuthenticationState>? authenticationStateTask { get; set; }
string CurrentUserId = "";
List<FolderItem> FilteredFolders => GetUserFolders();
public class FolderItem
{
public string? Id { get; set; }
public string? ParentId { get; set; }
public string? FolderName { get; set; }
public string? OwnerId { get; set; }
public bool IsPublic { get; set; }
}
List<FolderItem> AllFolders = new()
{
new FolderItem { Id = "1", FolderName = "Public", IsPublic = true },
new FolderItem { Id = "2", FolderName = "Private", OwnerId = "user1" },
new FolderItem { Id = "3", FolderName = "Shared", OwnerId = "user2" }
};
protected override async Task OnInitializedAsync()
{
if (authenticationStateTask != null)
{
var state = await authenticationStateTask;
CurrentUserId = state.User.FindFirst("sub")?.Value ?? "";
}
}
List<FolderItem> GetUserFolders()
{
return AllFolders.Where(f =>
f.IsPublic ||
f.OwnerId == CurrentUserId
).ToList();
}
}Node-Level Authorization
Control Node Visibility
@using Syncfusion.Blazor.Navigations
@using System.Security.Claims
@inject AuthenticationStateProvider AuthenticationStateProvider
<SfTreeView TValue="DocumentItem">
<TreeViewFieldsSettings TValue="DocumentItem"
Id="Id"
Text="FileName"
ParentID="ParentId"
DataSource="@VisibleDocuments">
</TreeViewFieldsSettings>
<TreeViewEvents TValue="DocumentItem"
NodeSelected="OnNodeSelected"
NodeEditing="OnNodeEditing">
</TreeViewEvents>
</SfTreeView>
@code {
[CascadingParameter]
private Task<AuthenticationState>? authenticationStateTask { get; set; }
ClaimsPrincipal? currentUser;
public class DocumentItem
{
public string? Id { get; set; }
public string? ParentId { get; set; }
public string? FileName { get; set; }
public string? AccessLevel { get; set; } // "public", "restricted", "private"
}
List<DocumentItem> AllDocuments = new()
{
new DocumentItem { Id = "1", FileName = "Public Report", AccessLevel = "public" },
new DocumentItem { Id = "2", FileName = "Confidential", AccessLevel = "restricted" },
new DocumentItem { Id = "3", FileName = "Personal", AccessLevel = "private" }
};
List<DocumentItem> VisibleDocuments => GetVisibleDocuments();
protected override async Task OnInitializedAsync()
{
if (authenticationStateTask != null)
{
var state = await authenticationStateTask;
currentUser = state.User;
}
}
List<DocumentItem> GetVisibleDocuments()
{
return AllDocuments.Where(d =>
CanUserAccessDocument(d)
).ToList();
}
bool CanUserAccessDocument(DocumentItem doc)
{
return doc.AccessLevel switch
{
"public" => true,
"restricted" => currentUser?.IsInRole("Manager") ?? false,
"private" => currentUser?.Identity?.Name == "admin",
_ => false
};
}
void OnNodeSelected(NodeSelectEventArgs args)
{
var doc = (DocumentItem)args.NodeData;
if (!CanUserAccessDocument(doc))
{
args.Cancel = true;
Console.WriteLine("Access denied");
}
}
void OnNodeEditing(NodeEditEventArgs args)
{
var doc = (DocumentItem)args.NodeData;
// Only admin can edit
if (currentUser?.Identity?.Name != "admin")
{
args.Cancel = true;
}
}
}Action-Level Authorization
Control Operations by Permission
@using Syncfusion.Blazor.Navigations
@using Microsoft.AspNetCore.Authorization
@inject IAuthorizationService AuthorizationService
<button @onclick="DeleteNode"
disabled="@(!CanDeleteNodes)">
Delete
</button>
<SfTreeView TValue="FileItem">
<TreeViewFieldsSettings TValue="FileItem" DataSource="@MyFiles" />
<TreeViewEvents TValue="FileItem"
OnNodeDragStart="OnBeforeDrag"
NodeEditing="OnNodeEditing">
</TreeViewEvents>
</SfTreeView>
@code {
[CascadingParameter]
private Task<AuthenticationState>? authenticationStateTask { get; set; }
ClaimsPrincipal? currentUser;
bool CanDeleteNodes => currentUser?.IsInRole("Admin") ?? false;
bool CanEditNodes => currentUser?.IsInRole("Editor") ?? false;
bool CanDragNodes => currentUser?.IsInRole("Organizer") ?? false;
public class FileItem
{
public string? Id { get; set; }
public string? FileName { get; set; }
}
List<FileItem> MyFiles = new()
{
new FileItem { Id = "1", FileName = "Document.pdf" }
};
protected override async Task OnInitializedAsync()
{
if (authenticationStateTask != null)
{
var state = await authenticationStateTask;
currentUser = state.User;
}
}
async Task DeleteNode()
{
if (!CanDeleteNodes)
{
Console.WriteLine("Delete permission denied");
return;
}
// Proceed with deletion
}
void OnBeforeDrag(NodeDragEventArgs args)
{
if (!CanDragNodes)
{
args.Cancel = true;
Console.WriteLine("Drag permission denied");
}
}
void OnNodeEditing(NodeEditEventArgs args)
{
if (!CanEditNodes)
{
args.Cancel = true;
Console.WriteLine("Edit permission denied");
}
}
}Setup Authentication in Program.cs
Configure Authentication Service
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
// Add authentication
builder.Services.AddOidcAuthentication(options =>
{
builder.Configuration.Bind("Auth0", options.ProviderOptions);
});
// Add authorization
builder.Services.AddAuthorizationCore();
var app = builder.Build();
await app.RunAsync();Configure Authorization Policies
// In Blazor Server Program.cs
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("CanViewReports", policy =>
policy.RequireRole("Admin", "Manager"));
options.AddPolicy("CanEditNodes", policy =>
policy.RequireRole("Editor", "Admin"));
options.AddPolicy("CanDeleteNodes", policy =>
policy.RequireRole("Admin"));
});Logout and Session Management
User Logout
@using Syncfusion.Blazor.Navigations
<AuthorizeView>
<Authorized>
<p>Logged in as: @context.User.Identity?.Name</p>
<SfTreeView TValue="MailItem">
<TreeViewFieldsSettings TValue="MailItem" DataSource="@MyFolder" />
</SfTreeView>
<form method="post" action="Identity/Account/LogOut">
<button type="submit" class="btn btn-danger">Log out</button>
</form>
</Authorized>
<NotAuthorized>
<p>Not logged in. <a href="Identity/Account/Login">Log in</a></p>
</NotAuthorized>
</AuthorizeView>
@code {
public class MailItem
{
public string? Id { get; set; }
public string? FolderName { get; set; }
}
List<MailItem> MyFolder = new()
{
new MailItem { Id = "1", FolderName = "Inbox" }
};
}Common Patterns
Pattern 1: Conditional Features
@if (currentUser?.IsInRole("Admin") ?? false)
{
<div>
<button @onclick="BulkDelete">Bulk Delete</button>
<button @onclick="ExportAll">Export All</button>
</div>
}
<SfTreeView TValue="Item"
AllowEdit="@(currentUser?.IsInRole("Editor") ?? false)"
AllowDragAndDrop="@(currentUser?.IsInRole("Organizer") ?? false)">
<TreeViewFieldsSettings TValue="Item" DataSource="@VisibleItems" />
</SfTreeView>Pattern 2: Audit Logging
async Task OnNodeModified(NodeEditEventArgs args)
{
// Log the action
await AuditLog(new()
{
Action = "NodeEdited",
NodeId = args.NodeData.Id,
UserId = CurrentUserId,
OldValue = args.OldText,
NewValue = args.NewText,
Timestamp = DateTime.UtcNow
});
}Best Practices
1. Always check authorization before sensitive operations 2. Filter data server-side, not just client-side 3. Use role-based access for broad permissions 4. Use claims-based access for fine-grained control 5. Log security events for audit trails 6. Test with multiple roles to ensure proper filtering 7. Don't rely on UI disabling alone for security
Troubleshooting
Issue: AuthorizeView not working
- Ensure CascadingAuthenticationState is set up
- Check authentication is configured in Program.cs
- Verify AuthenticationState is provided
Issue: Nodes still visible after filtering
- Ensure filtering happens in GetVisibleDocuments
- Check that CanUserAccessDocument logic is correct
- Verify CurrentUserId is set properly
Issue: Permission checks not firing
- Ensure event handlers are attached
- Verify Cancel property is set to true
- Check authorization logic
Next Steps
- Use node-selection.md for selective access
- Implement events-handling.md for operation logging
- Check advanced-features.md for role-based features
Checkbox Features in TreeView
Table of Contents
Enable Checkboxes
Basic Checkbox Setup
Display checkboxes before each node by setting ShowCheckBox="true":
@using Syncfusion.Blazor.Navigations
<SfTreeView TValue="MailItem" ShowCheckBox="true">
<TreeViewFieldsSettings TValue="MailItem"
Id="Id"
Text="FolderName"
ParentID="ParentId"
HasChildren="HasSubFolders"
IsChecked="IsChecked"
DataSource="@MyFolder">
</TreeViewFieldsSettings>
</SfTreeView>
@code {
public class MailItem
{
public string? Id { get; set; }
public string? ParentId { get; set; }
public string? FolderName { get; set; }
public bool HasSubFolders { get; set; }
public bool? IsChecked { get; set; } // null = indeterminate
}
List<MailItem> MyFolder = new()
{
new MailItem { Id = "1", FolderName = "Inbox", HasSubFolders = true },
new MailItem { Id = "2", ParentId = "1", FolderName = "Important" }
};
}Checkbox Display States
- Checked - All child nodes are checked (✓)
- Unchecked - No child nodes are checked (☐)
- Indeterminate - Some child nodes are checked (◐)
AutoCheck Behavior
The AutoCheck property controls whether parent and child checkboxes affect each other.
Dependent Checkboxes (Default: AutoCheck="true")
When a parent node is checked, all child nodes automatically become checked. When a parent node is unchecked, all children become unchecked:
<SfTreeView TValue="MailItem" ShowCheckBox="true" AutoCheck="true">
<TreeViewFieldsSettings TValue="MailItem"
IsChecked="IsChecked"
DataSource="@MyFolder">
</TreeViewFieldsSettings>
</SfTreeView>
@code {
public class MailItem
{
public string? Id { get; set; }
public string? FolderName { get; set; }
public bool? IsChecked { get; set; }
}
List<MailItem> MyFolder = new()
{
new MailItem { Id = "1", FolderName = "Documents", IsChecked = false },
new MailItem { Id = "1-1", FolderName = "Work", IsChecked = false },
new MailItem { Id = "1-2", FolderName = "Personal", IsChecked = false }
};
// User checks Documents:
// → Work becomes checked
// → Personal becomes checked
// All related nodes update automatically
}AutoCheck Behavior:
- ✓ Parent checked → All children checked
- ☐ Parent unchecked → All children unchecked
- ◐ Parent indeterminate → Some children checked, some unchecked
- Parent updates automatically based on child state changes
Independent Checkboxes (AutoCheck="false")
Child nodes can be checked/unchecked without affecting parents:
<SfTreeView TValue="MailItem" ShowCheckBox="true" AutoCheck="false">
<TreeViewFieldsSettings TValue="MailItem"
IsChecked="IsChecked"
DataSource="@MyFolder">
</TreeViewFieldsSettings>
</SfTreeView>
@code {
// Same data model
// User checks "Work":
// → "Documents" state NOT affected
// → "Personal" state NOT affected
// Only "Work" checkbox changes
}Use AutoCheck="false" when:
- Each node represents an independent choice
- Parent-child relationships don't imply selection dependency
- You want fine-grained user control
Checkbox State Management
Initialize Checkbox States
Set initial checkbox states via the IsChecked property in your data:
List<MailItem> MyFolder = new()
{
new MailItem { Id = "1", FolderName = "Inbox", IsChecked = true },
new MailItem { Id = "2", FolderName = "Sent", IsChecked = false },
new MailItem { Id = "3", FolderName = "Drafts", IsChecked = null } // Indeterminate
};IsChecked Values:
true- Node is checkedfalse- Node is uncheckednull- Node is indeterminate (partial selection)
Update Checkbox State Programmatically
@using Syncfusion.Blazor.Navigations
<button @onclick="CheckAllNodes">Check All</button>
<button @onclick="UncheckAllNodes">Uncheck All</button>
<SfTreeView TValue="MailItem" ShowCheckBox="true">
<TreeViewFieldsSettings TValue="MailItem"
IsChecked="IsChecked"
DataSource="@MyFolder">
</TreeViewFieldsSettings>
</SfTreeView>
@code {
List<MailItem> MyFolder = new()
{
new MailItem { Id = "1", FolderName = "Inbox", IsChecked = false },
new MailItem { Id = "2", FolderName = "Sent", IsChecked = false }
};
void CheckAllNodes()
{
foreach (var item in MyFolder)
{
item.IsChecked = true;
}
StateHasChanged();
}
void UncheckAllNodes()
{
foreach (var item in MyFolder)
{
item.IsChecked = false;
}
StateHasChanged();
}
}Handle Checkbox Change Events
<SfTreeView TValue="MailItem" ShowCheckBox="true">
<TreeViewFieldsSettings TValue="MailItem" IsChecked="IsChecked" DataSource="@MyFolder" />
<TreeViewEvents TValue="MailItem" NodeSelected="OnCheckboxChange"></TreeViewEvents>
</SfTreeView>
@code {
void OnCheckboxChange(NodeSelectEventArgs args)
{
var isChecked = args.NodeData.IsChecked;
var nodeId = args.NodeData.Id;
Console.WriteLine($"Node {nodeId} is now {(isChecked ? "checked" : "unchecked")}");
// Perform actions based on checkbox state
UpdateParentState(nodeId, isChecked);
}
void UpdateParentState(string nodeId, bool? isChecked)
{
// Custom logic for parent/child synchronization
}
}CheckedNodes Property Reference
The CheckedNodes property enables two-way binding for managing which nodes are checked. This property accepts an array of node IDs (as strings) and can be updated programmatically:
@using Syncfusion.Blazor.Navigations
<button @onclick="CheckSpecificNodes">Check Work & Development</button>
<button @onclick="ClearAllChecks">Clear All</button>
<p>Checked Nodes: @string.Join(", ", CheckedNodeIds)</p>
<SfTreeView TValue="MailItem"
ShowCheckBox="true"
AutoCheck="true"
@bind-CheckedNodes="@CheckedNodeIds">
<TreeViewFieldsSettings TValue="MailItem"
Id="Id"
Text="FolderName"
ParentID="ParentId"
HasChildren="HasSubFolders"
DataSource="@MyFolder">
</TreeViewFieldsSettings>
</SfTreeView>
@code {
// Two-way bound property for checked nodes
string[] CheckedNodeIds = Array.Empty<string>();
List<MailItem> MyFolder = new()
{
new MailItem { Id = "1", FolderName = "Projects", HasSubFolders = true },
new MailItem { Id = "2", ParentId = "1", FolderName = "Work" },
new MailItem { Id = "3", ParentId = "1", FolderName = "Development" },
new MailItem { Id = "4", FolderName = "Archive", HasSubFolders = false }
};
void CheckSpecificNodes()
{
// Set specific nodes as checked
CheckedNodeIds = new[] { "2", "3" };
}
void ClearAllChecks()
{
// Clear all checked nodes
CheckedNodeIds = Array.Empty<string>();
}
}CheckedNodes Features:
- Two-way binding - Changes reflect immediately in UI
- Programmatic control - Set checked nodes from code
- Array of IDs - Pass multiple node IDs as strings
- Dynamic updates - Modify at any time during component lifecycle
Bulk Operations with CheckedNodes
async Task CheckAllNodesAsync()
{
// Get all node IDs and check them
var allNodeIds = GetAllNodeIds(MyFolder);
CheckedNodeIds = allNodeIds.ToArray();
await InvokeAsync(StateHasChanged);
}
async Task UncheckAllNodesAsync()
{
CheckedNodeIds = Array.Empty<string>();
await InvokeAsync(StateHasChanged);
}
List<string> GetAllNodeIds(List<MailItem> items)
{
var ids = new List<string>();
foreach (var item in items)
{
ids.Add(item.Id);
}
return ids;
}Getting Checked Nodes
Access Checked Nodes via Reference
@using Syncfusion.Blazor.Navigations
<button @onclick="GetCheckedNodes">Get Checked Nodes</button>
<SfTreeView TValue="MailItem"
ShowCheckBox="true"
@ref="treeRef">
<TreeViewFieldsSettings TValue="MailItem"
IsChecked="IsChecked"
DataSource="@MyFolder">
</TreeViewFieldsSettings>
</SfTreeView>
<div>
<h3>Checked Items:</h3>
@foreach (var item in CheckedItems)
{
<p>@item.FolderName</p>
}
</div>
@code {
SfTreeView<MailItem> treeRef;
List<MailItem> CheckedItems = new();
async Task GetCheckedNodes()
{
CheckedItems.Clear();
// Get all checked nodes
var allNodes = FlattenTree(MyFolder);
foreach (var node in allNodes)
{
if (node.IsChecked == true)
{
CheckedItems.Add(node);
}
}
}
List<MailItem> FlattenTree(List<MailItem> items)
{
var result = new List<MailItem>();
foreach (var item in items)
{
result.Add(item);
// Add recursive flattening if hierarchical
}
return result;
}
List<MailItem> MyFolder = new()
{
new MailItem { Id = "1", FolderName = "Inbox", IsChecked = true },
new MailItem { Id = "2", FolderName = "Sent", IsChecked = false },
new MailItem { Id = "3", FolderName = "Drafts", IsChecked = true }
};
}Filter Checked Nodes
List<MailItem> GetCheckedNodesLinq()
{
return FlattenTree(MyFolder)
.Where(x => x.IsChecked == true)
.ToList();
}
List<string> GetCheckedNodeIds()
{
return FlattenTree(MyFolder)
.Where(x => x.IsChecked == true)
.Select(x => x.Id)
.ToList();
}Common Patterns
Pattern 1: Permissions Selection (Checkboxes + Hierarchy)
@using Syncfusion.Blazor.Navigations
<button @onclick="SavePermissions">Save Permissions</button>
<SfTreeView TValue="Permission" ShowCheckBox="true" AutoCheck="true">
<TreeViewFieldsSettings TValue="Permission"
Id="Id"
Text="Name"
Child="Children"
IsChecked="Granted"
DataSource="@Permissions">
</TreeViewFieldsSettings>
</SfTreeView>
@code {
public class Permission
{
public string? Id { get; set; }
public string? Name { get; set; }
public bool? Granted { get; set; }
public List<Permission>? Children { get; set; }
}
List<Permission> Permissions = new()
{
new Permission
{
Id = "admin",
Name = "Admin",
Granted = false,
Children = new()
{
new Permission { Id = "users", Name = "Manage Users", Granted = false },
new Permission { Id = "settings", Name = "Settings", Granted = false }
}
}
};
async Task SavePermissions()
{
var grantedPerms = GetGrantedPermissions();
// Save to database
await SaveToDatabase(grantedPerms);
}
List<string> GetGrantedPermissions()
{
var result = new List<string>();
void Traverse(List<Permission> items)
{
foreach (var item in items)
{
if (item.Granted == true)
result.Add(item.Id);
if (item.Children != null)
Traverse(item.Children);
}
}
Traverse(Permissions);
return result;
}
}Pattern 2: Multi-Select with Item Count
<div>
<strong>Selected: @CheckedCount / @TotalCount</strong>
</div>
<SfTreeView TValue="Item" ShowCheckBox="true">
<TreeViewFieldsSettings TValue="Item"
IsChecked="Selected"
DataSource="@Items">
</TreeViewFieldsSettings>
<TreeViewEvents TValue="Item" NodeSelected="OnSelectionChange"></TreeViewEvents>
</SfTreeView>
@code {
int CheckedCount { get; set; }
int TotalCount { get; set; }
void OnSelectionChange(NodeSelectEventArgs args)
{
CheckedCount = CountChecked(Items);
TotalCount = Items.Count;
}
int CountChecked(List<Item> items)
{
return items.Count(x => x.Selected == true);
}
}Pattern 3: Category Selection Filter
<button @onclick="ApplyFilter">Filter by Selected</button>
<SfTreeView TValue="Category" ShowCheckBox="true">
<TreeViewFieldsSettings TValue="Category"
IsChecked="Selected"
DataSource="@Categories">
</TreeViewFieldsSettings>
</SfTreeView>
@code {
void ApplyFilter()
{
var selectedCategories = Categories
.Where(x => x.Selected == true)
.Select(x => x.Id)
.ToList();
// Filter data source based on selected categories
FilterDataByCategories(selectedCategories);
}
}Best Practices
1. Always map IsChecked field in TreeViewFieldsSettings 2. Use AutoCheck appropriately - Enable for hierarchical selections, disable for independent choices 3. Initialize IsChecked in your data model (true/false/null) 4. Handle checkbox changes via events for real-time updates 5. Use two-way binding for reactive state updates 6. Test parent-child relationships to ensure expected behavior 7. Provide feedback when checkbox state changes (toasts, logs)
Troubleshooting
Issue: Checkboxes not appearing
- Ensure
ShowCheckBox="true"is set - Verify
IsCheckedproperty is mapped in TreeViewFieldsSettings - Check CSS theme is loaded
Issue: AutoCheck not working
- Confirm
AutoCheck="true"(default) - Verify data has parent-child relationships
- Check that ParentID/Child mappings are correct
Issue: Checked state not persisting
- Ensure IsChecked property is nullable (bool?)
- Call StateHasChanged() after updates
- Verify data changes trigger re-render
Next Steps
- Use node-selection.md for regular node selection
- Implement expand-collapse-actions.md for node expansion
- Handle events-handling.md for checkbox and selection events
Customization and Styling in TreeView
Table of Contents
Icon Customization
Customize Expand/Collapse Icons
Override default expand/collapse icons with custom icons:
@using Syncfusion.Blazor.Navigations
<SfTreeView TValue="MailItem">
<TreeViewFieldsSettings TValue="MailItem" DataSource="@MyFolder" />
</SfTreeView>
<style>
/* Override expand icon */
.e-treeview .e-list-item.e-expanded>.e-expand-icon::before {
content: "\e729"; /* Custom icon content */
}
/* Override collapse icon */
.e-treeview .e-list-item:not(.e-expanded)>.e-expand-icon::before {
content: "\e726"; /* Custom icon content */
}
</style>Use Font Awesome Icons
<!-- Add Font Awesome library -->
<style>
.e-treeview .e-expand-icon {
font-family: 'FontAwesome';
font-size: 14px;
}
.e-treeview .e-list-item.e-expanded>.e-expand-icon::before {
content: "\f078"; /* fa-chevron-down */
}
.e-treeview .e-list-item:not(.e-expanded)>.e-expand-icon::before {
content: "\f054"; /* fa-chevron-right */
}
</style>Node Icons
@using Syncfusion.Blazor.Navigations
<SfTreeView TValue="MailItem">
<TreeViewFieldsSettings TValue="MailItem"
Id="Id"
Text="FolderName"
ImageUrl="Icon"
DataSource="@MyFolder">
</TreeViewFieldsSettings>
</SfTreeView>
@code {
public class MailItem
{
public string? Id { get; set; }
public string? FolderName { get; set; }
public string? Icon { get; set; } // CSS class or image URL
}
List<MailItem> MyFolder = new()
{
new MailItem { Id = "1", FolderName = "Documents", Icon = "e-icons e-folder" },
new MailItem { Id = "2", FolderName = "Settings", Icon = "e-icons e-settings" }
};
}Dynamic Icons
Get Dynamic Icons Based on Data
@using Syncfusion.Blazor.Navigations
<SfTreeView TValue="FileItem">
<TreeViewFieldsSettings TValue="FileItem"
Id="Id"
Text="FileName"
ImageUrl="GetIconClass"
DataSource="@FileList">
</TreeViewFieldsSettings>
</SfTreeView>
@code {
public class FileItem
{
public string? Id { get; set; }
public string? FileName { get; set; }
public string? FileType { get; set; } // "folder", "file", "image", etc.
}
string GetIconClass(FileItem item)
{
return item.FileType switch
{
"folder" => "e-icons e-folder",
"image" => "e-icons e-image",
"document" => "e-icons e-document",
"music" => "e-icons e-music",
_ => "e-icons e-file"
};
}
List<FileItem> FileList = new()
{
new FileItem { Id = "1", FileName = "Documents", FileType = "folder" },
new FileItem { Id = "2", FileName = "photo.jpg", FileType = "image" }
};
}Icon Colors Based on Status
/* Icon color based on status */
.e-treeview .e-list-item.completed .e-list-icon {
color: green;
}
.e-treeview .e-list-item.pending .e-list-icon {
color: orange;
}
.e-treeview .e-list-item.failed .e-list-icon {
color: red;
}Text Wrapping
Enable Text Wrapping with AllowTextWrap
The AllowTextWrap property enables text wrapping for node labels when they exceed the node width:
@using Syncfusion.Blazor.Navigations
<!-- Enable text wrapping for long node labels -->
<SfTreeView TValue="MailItem" AllowTextWrap="true" Height="400px">
<TreeViewFieldsSettings TValue="MailItem"
Id="Id"
Text="FolderName"
ParentID="ParentId"
DataSource="@MyFolder">
</TreeViewFieldsSettings>
</SfTreeView>
@code {
public class MailItem
{
public string? Id { get; set; }
public string? ParentId { get; set; }
public string? FolderName { get; set; } // Long text will wrap
}
List<MailItem> MyFolder = new()
{
new MailItem {
Id = "1",
FolderName = "Very Long Folder Name That Exceeds Default Width and Will Wrap to Next Line"
},
new MailItem {
Id = "2",
FolderName = "Another folder with a description that is quite lengthy"
}
};
}AllowTextWrap Property:
- Default value:
false - When `true`: Text wraps to multiple lines if it exceeds node width
- When `false`: Text is truncated with ellipsis
Text Wrapping with Custom Width
Combine AllowTextWrap with CSS to control wrapping behavior:
<SfTreeView TValue="MailItem" AllowTextWrap="true" CssClass="custom-wrap">
<TreeViewFieldsSettings TValue="MailItem" DataSource="@MyFolder" />
</SfTreeView>
<style>
/* Control text wrapping width */
.custom-wrap .e-list-item .e-list-text {
max-width: 250px;
word-wrap: break-word;
word-break: break-word;
}
/* Optional: Increase node height for wrapped text */
.custom-wrap .e-list-item {
height: auto;
min-height: 32px;
padding: 6px 0;
}
</style>Text Wrapping Examples
Without AllowTextWrap (Default):
- Long text: "This is a very long folder name..." → Truncated
- Node height: 32px (fixed)
With AllowTextWrap="true":
- Long text wraps to multiple lines
- Node height expands automatically
- Improves readability of lengthy labels
// Example comparison
<div>
<h4>Without Text Wrap (AllowTextWrap="false")</h4>
<SfTreeView TValue="Item" AllowTextWrap="false">
<TreeViewFieldsSettings TValue="Item" DataSource="@Items" />
</SfTreeView>
<h4>With Text Wrap (AllowTextWrap="true")</h4>
<SfTreeView TValue="Item" AllowTextWrap="true">
<TreeViewFieldsSettings TValue="Item" DataSource="@Items" />
</SfTreeView>
</div>Template Customization
Custom Node Template
@using Syncfusion.Blazor.Navigations
<SfTreeView TValue="EmployeeItem">
<TreeViewFieldsSettings TValue="EmployeeItem" DataSource="@EmployeeList" />
<TreeViewTemplates TValue="EmployeeItem">
<NodeTemplate>
<div class="custom-node">
<span class="emp-icon">👤</span>
<span class="emp-name">@((context as EmployeeItem).Name)</span>
<span class="emp-role">(@((context as EmployeeItem).Role))</span>
</div>
</NodeTemplate>
</TreeViewTemplates>
</SfTreeView>
@code {
public class EmployeeItem
{
public string? Id { get; set; }
public string? Name { get; set; }
public string? Role { get; set; }
public string? ReportsTo { get; set; }
}
List<EmployeeItem> EmployeeList = new()
{
new EmployeeItem { Id = "1", Name = "Alice", Role = "Manager" },
new EmployeeItem { Id = "2", Name = "Bob", Role = "Developer" }
};
}
<style>
.custom-node {
display: flex;
align-items: center;
gap: 8px;
padding: 4px;
}
.emp-icon {
font-size: 18px;
}
.emp-role {
color: #666;
font-size: 12px;
}
</style>Template with Buttons
<TreeViewTemplates TValue="FolderItem">
<NodeTemplate>
<div style="display: flex; justify-content: space-between; align-items: center; width: 100%;">
<span>@((context as FolderItem).Name)</span>
<button @onclick="() => DeleteNode((context as FolderItem).Id)">Delete</button>
</div>
</NodeTemplate>
</TreeViewTemplates>
@code {
async Task DeleteNode(string nodeId)
{
MyFolder.RemoveAll(x => x.Id == nodeId);
StateHasChanged();
}
}CSS Styling
Override TreeView Styles
/* Change background color */
.e-treeview {
background-color: #f5f5f5;
}
/* Style node text */
.e-treeview .e-list-item .e-list-text {
font-size: 14px;
font-weight: 500;
}
/* Hover effect */
.e-treeview .e-list-item:hover {
background-color: #e3f2fd;
}
/* Selected node */
.e-treeview .e-list-item.e-active {
background-color: #2196f3;
color: white;
}
/* Expanded node */
.e-treeview .e-list-item.e-expanded {
background-color: #f0f0f0;
}Custom Indentation
/* Change indentation width */
.e-treeview .e-list-item {
padding-left: 32px; /* Default is 24px */
}
.e-treeview .e-list-item .e-list-item {
padding-left: 64px;
}Node Height
/* Increase node height */
.e-treeview .e-list-item {
height: 40px;
line-height: 40px;
}Themes
Apply Built-in Theme
<!-- Bootstrap 5 -->
<link href="_content/Syncfusion.Blazor.Themes/bootstrap5.css" rel="stylesheet" />
<!-- Material -->
<link href="_content/Syncfusion.Blazor.Themes/material.css" rel="stylesheet" />
<!-- Fluent -->
<link href="_content/Syncfusion.Blazor.Themes/fluent.css" rel="stylesheet" />
<!-- Tailwind -->
<link href="_content/Syncfusion.Blazor.Themes/tailwind.css" rel="stylesheet" />Custom Theme with CSS Variables
:root {
--treeview-bg: #ffffff;
--treeview-text: #333333;
--treeview-hover: #f0f0f0;
--treeview-selected: #2196f3;
--treeview-selected-text: #ffffff;
}
.e-treeview {
background-color: var(--treeview-bg);
color: var(--treeview-text);
}
.e-treeview .e-list-item:hover {
background-color: var(--treeview-hover);
}
.e-treeview .e-list-item.e-active {
background-color: var(--treeview-selected);
color: var(--treeview-selected-text);
}Dark Theme
/* Dark Mode */
.dark-mode .e-treeview {
background-color: #1e1e1e;
color: #e0e0e0;
}
.dark-mode .e-treeview .e-list-item:hover {
background-color: #333333;
}
.dark-mode .e-treeview .e-list-item.e-active {
background-color: #1976d2;
}
.dark-mode .e-expand-icon {
color: #b0b0b0;
}Common Patterns
Pattern 1: Responsive Styling
/* Mobile */
@media (max-width: 768px) {
.e-treeview .e-list-item {
height: 32px;
font-size: 14px;
}
.e-treeview .e-expand-icon {
width: 20px;
}
}
/* Desktop */
@media (min-width: 769px) {
.e-treeview .e-list-item {
height: 40px;
font-size: 14px;
}
}Pattern 2: Status-Based Styling
public class TaskItem
{
public string? Id { get; set; }
public string? Name { get; set; }
public string? Status { get; set; } // "completed", "pending", "failed"
public string GetStatusClass()
{
return Status switch
{
"completed" => "status-completed",
"failed" => "status-failed",
_ => "status-pending"
};
}
}
<TreeViewTemplates TValue="TaskItem">
<NodeTemplate>
<div class="@((context as TaskItem).GetStatusClass())">
@((context as TaskItem).Name)
</div>
</NodeTemplate>
</TreeViewTemplates>
<style>
.status-completed {
color: green;
text-decoration: line-through;
}
.status-failed {
color: red;
font-weight: bold;
}
.status-pending {
color: orange;
}
</style>Pattern 3: Level-Based Indentation
/* Different styling per level */
.e-treeview .e-list-item[data-level="0"] {
background-color: #f0f0f0;
font-weight: bold;
}
.e-treeview .e-list-item[data-level="1"] {
padding-left: 40px;
}
.e-treeview .e-list-item[data-level="2"] {
padding-left: 60px;
font-size: 13px;
}Pattern 4: Conditional Icon Color
<TreeViewTemplates TValue="FileItem">
<NodeTemplate>
<span style="@GetIconStyle((context as FileItem))">
@GetIconClass((context as FileItem))
</span>
<span>@((context as FileItem).FileName)</span>
</NodeTemplate>
</TreeViewTemplates>
@code {
string GetIconStyle(FileItem item)
{
return item.FileType switch
{
"folder" => "color: #1976d2;",
"image" => "color: #ff9800;",
"error" => "color: #f44336;",
_ => "color: #757575;"
};
}
}Best Practices
1. Use CSS classes instead of inline styles for reusability 2. Override theme variables for consistent branding 3. Provide light and dark theme options 4. Test responsiveness on mobile devices 5. Use semantic markup in templates 6. Cache template results for performance 7. Keep templates simple for better performance
Troubleshooting
Issue: Custom styles not applying
- Use CSS specificity selector (
.e-treeviewprefix) - Check CSS file is loaded after theme
- Inspect element to see applied styles
Issue: Icons not displaying
- Verify icon font is loaded
- Check icon class name exists
- Use browser DevTools to verify CSS
Issue: Template not rendering
- Ensure TreeViewTemplates is nested under SfTreeView
- Add TValue property to TreeViewTemplates tag
- Check template context type matches TValue
- Verify no syntax errors in template HTML
Next Steps
- Use advanced-features.md for integration patterns
- Check accessibility.md for accessible styling
- Review events-handling.md for dynamic styling
Data Binding in TreeView
Table of Contents
Overview
The TreeView component supports multiple data binding methods through the DataSource property. The component can bind:
- Local data: Hierarchical objects or self-referential lists
- Remote data: Web APIs, OData services, or HTTP endpoints
- Mixed approach: Local + remote (load on demand)
The TreeViewFieldsSettings component maps your data properties to TreeView fields:
Id- Unique identifier for each nodeText- Display text for nodeParentID- Parent node reference (self-referential)Child- Child nodes collection (hierarchical)HasChildren- Whether node has childrenExpanded- Initial expand stateIsChecked- Checkbox state (if enabled)
Local Data Binding
Hierarchical Data
Hierarchical data uses nested lists where each parent has a Child property containing child nodes:
@using Syncfusion.Blazor.Navigations
<SfTreeView TValue="MailItem">
<TreeViewFieldsSettings TValue="MailItem"
Id="Id"
Text="FolderName"
Child="SubFolders"
DataSource="@MyFolder">
</TreeViewFieldsSettings>
</SfTreeView>
@code {
public class MailItem
{
public string? Id { get; set; }
public string? FolderName { get; set; }
public List<MailItem>? SubFolders { get; set; }
}
List<MailItem> MyFolder = new();
protected override void OnInitialized()
{
var folder1 = new List<MailItem>
{
new MailItem { Id = "1-1", FolderName = "Important" },
new MailItem { Id = "1-2", FolderName = "Drafts" }
};
MyFolder.Add(new MailItem
{
Id = "1",
FolderName = "Inbox",
SubFolders = folder1
});
}
}Hierarchical Data Benefits:
- Natural nested structure
- Easy to understand parent-child relationships
- Observable collection support for dynamic updates
Hierarchical Data Limitations:
- Cannot easily include root-level items alongside parents
- Requires separate handling for flat and nested structures
Self-Referential Data
Self-referential data uses a flat list where each item references its parent via ParentID:
@using Syncfusion.Blazor.Navigations
<SfTreeView TValue="MailItem">
<TreeViewFieldsSettings TValue="MailItem"
Id="Id"
Text="FolderName"
ParentID="ParentId"
HasChildren="HasSubFolders"
DataSource="@MyFolder">
</TreeViewFieldsSettings>
</SfTreeView>
@code {
public class MailItem
{
public string? Id { get; set; }
public string? ParentId { get; set; } // null for root items
public string? FolderName { get; set; }
public bool HasSubFolders { get; set; }
}
List<MailItem> MyFolder = new()
{
// Root level items (ParentId = null)
new MailItem { Id = "1", FolderName = "Inbox", HasSubFolders = true, ParentId = null },
new MailItem { Id = "2", FolderName = "Sent", HasSubFolders = false, ParentId = null },
// Child items (ParentId references parent Id)
new MailItem { Id = "1-1", FolderName = "Important", ParentId = "1" },
new MailItem { Id = "1-2", FolderName = "Drafts", ParentId = "1" }
};
}Self-Referential Data Benefits:
- Flexible structure (can have both root and nested items)
- Easier for database mapping
- Simpler dynamic updates
- Better for large datasets
When to use: API responses, database queries, complex hierarchies
Remote Data Binding
The DataSource property accepts both List<T> and SfDataManager instances. When using remote data, assign a configured SfDataManager to the DataSource property within TreeViewFieldsSettings:
<SfTreeView TValue="EmployeeData">
<TreeViewFieldsSettings TValue="EmployeeData"
DataSource="@DataManager"> <!-- SfDataManager assigned here -->
</TreeViewFieldsSettings>
</SfTreeView>Web API with DataManager
@using Syncfusion.Blazor.Navigations
@using Syncfusion.Blazor.Data
<SfTreeView TValue="EmployeeData">
<TreeViewFieldsSettings TValue="EmployeeData"
Id="Id"
Text="Name"
ParentID="ParentId"
HasChildren="HasChild"
DataSource="@WebApiManager"> <!-- SfDataManager instance -->
</TreeViewFieldsSettings>
</SfTreeView>
@code {
public class EmployeeData
{
public string? Id { get; set; }
public string? ParentId { get; set; }
public string? Name { get; set; }
public bool HasChild { get; set; }
}
SfDataManager WebApiManager;
protected override void OnInitialized()
{
// Configure DataManager with Web API endpoint
WebApiManager = new SfDataManager
{
Url = "url",
Adaptor = Syncfusion.Blazor.Adaptors.AdaptorType.WebApiAdaptor
};
}
}Key Points:
DataSourceproperty receives theSfDataManagerinstance (placed insideTreeViewFieldsSettings)Adaptorspecifies the data service type (WebApiAdaptor for Web APIs)Urlis the remote endpoint- Hierarchical data is auto-constructed from ParentID relationships
OData Service Binding
@using Syncfusion.Blazor.Navigations
@using Syncfusion.Blazor.Data
<SfTreeView TValue="EmployeeData">
<TreeViewFieldsSettings TValue="EmployeeData"
Id="EmployeeID"
Text="FirstName"
HasChildren="EmployeeID"
DataSource="@ODataManager"
Query="@ODataQuery">
</TreeViewFieldsSettings>
</SfTreeView>
@code {
public class EmployeeData
{
public string? EmployeeID { get; set; }
public string? FirstName { get; set; }
}
SfDataManager ODataManager;
Query ODataQuery;
protected override void OnInitialized()
{
ODataManager = new SfDataManager
{
Url = "url",
Adaptor = Syncfusion.Blazor.Adaptors.AdaptorType.ODataV4Adaptor
};
ODataQuery = new Query()
.Select(new List<string> { "EmployeeID", "FirstName" })
.Take(5);
}
}OData V4 Service
@using Syncfusion.Blazor.Navigations
@using Syncfusion.Blazor.Data
<SfTreeView TValue="EmployeeData">
<TreeViewFieldsSettings TValue="EmployeeData"
Id="EmployeeID"
Text="FirstName"
HasChildren="EmployeeID"
DataSource="@ODataV4Manager"
Query="@ODataV4Query">
</TreeViewFieldsSettings>
</SfTreeView>
@code {
public class EmployeeData
{
public string? EmployeeID { get; set; }
public string? FirstName { get; set; }
}
SfDataManager ODataV4Manager;
Query ODataV4Query;
protected override void OnInitialized()
{
ODataV4Manager = new SfDataManager
{
Url = "url",
Adaptor = Syncfusion.Blazor.Adaptors.AdaptorType.ODataV4Adaptor
};
ODataV4Query = new Query()
.Select(new List<string> { "EmployeeID", "FirstName" })
.RequiresCount();
}
}Load on Demand
The LoadOnDemand property (default: true) optimizes performance by loading child nodes only when parent is expanded.
Enable Load on Demand (Default)
// By default, only root nodes load initially
<SfTreeView TValue="EmployeeData" LoadOnDemand="true">
<TreeViewFieldsSettings TValue="EmployeeData"
Id="Id"
Text="Name"
ParentID="ParentId"
HasChildren="HasChild"
DataSource="@Employees">
</TreeViewFieldsSettings>
</SfTreeView>Benefits:
- Faster initial render
- Lower bandwidth for large datasets
- Smooth user experience
Disable Load on Demand
// All nodes load at initialization
<SfTreeView TValue="EmployeeData" LoadOnDemand="false">
<TreeViewFieldsSettings TValue="EmployeeData"
Id="Id"
Text="Name"
Child="Children"
DataSource="@Employees">
</TreeViewFieldsSettings>
</SfTreeView>Use when: Small datasets or all data must be available immediately
Property Mappings
Complete Field Mapping Example
<TreeViewFieldsSettings TValue="EmployeeData"
Id="EmployeeId" // Unique identifier
Text="EmployeeName" // Display text
ParentID="ReportsTo" // Parent reference
HasChildren="IsManager" // Has children flag
Expanded="InitialExpanded" // Expand state
IsChecked="InitialChecked" // Checkbox state
ImageUrl="ProfileImage" // Icon/image URL
Tooltip="EmployeeTitle" // Hover tooltip
DataSource="@EmployeeList">
</TreeViewFieldsSettings>
@code {
public class EmployeeData
{
public string? EmployeeId { get; set; }
public string? EmployeeName { get; set; }
public string? ReportsTo { get; set; }
public bool IsManager { get; set; }
public bool InitialExpanded { get; set; }
public bool? InitialChecked { get; set; }
public string? ProfileImage { get; set; }
public string? EmployeeTitle { get; set; }
}
}Hierarchical Data Mapping
<TreeViewFieldsSettings TValue="OrganizationNode"
Id="NodeId"
Text="NodeName"
Child="Departments" // Nested list property
DataSource="@Organization">
</TreeViewFieldsSettings>
@code {
public class OrganizationNode
{
public string? NodeId { get; set; }
public string? NodeName { get; set; }
public List<OrganizationNode>? Departments { get; set; }
}
}DataBound Event
The DataBound event fires after all data is populated:
<SfTreeView TValue="MailItem">
<TreeViewFieldsSettings TValue="MailItem" DataSource="@MyFolder" />
<TreeViewEvents TValue="MailItem" DataBound="OnDataBound"></TreeViewEvents>
</SfTreeView>
@code {
void OnDataBound()
{
// Perform post-binding operations
// - Expand specific nodes
// - Select default node
// - Apply conditional formatting
}
}Common Patterns
Pattern 1: Database Query Result
protected override async Task OnInitializedAsync()
{
// Fetch from database via API
var response = await HttpClient.GetAsync("/api/departments");
MyFolder = await response.Content.ReadAsAsync<List<MailItem>>();
}Pattern 2: Dynamic Data Update
async Task UpdateTreeData()
{
// Fetch fresh data from server
MyFolder = await FetchDataFromServer();
// Trigger re-render
StateHasChanged();
}
<button @onclick="UpdateTreeData">Refresh Tree</button>Pattern 3: Mixed Local and Remote
protected override async Task OnInitializedAsync()
{
// Load root from local data
MyFolder = GetLocalRootNodes();
// Load child data on demand via events
}
void OnNodeExpanded(NodeExpandEventArgs args)
{
// Load child nodes from API only when expanded
var children = await FetchChildNodesFromApi(args.NodeData.Id);
args.NodeData.Children = children;
}Pattern 4: Filtered Data
List<MailItem> FilteredFolder
{
get => MyFolder.Where(x => x.Visible).ToList();
}
<SfTreeView TValue="MailItem">
<TreeViewFieldsSettings TValue="MailItem" DataSource="@FilteredFolder" />
</SfTreeView>Best Practices
1. Use HasChildren hint to avoid loading all data at once 2. Prefer self-referential for large or complex hierarchies 3. Enable LoadOnDemand for performance with large datasets 4. Cache remote data to avoid repeated API calls 5. Use Id and ParentId consistently across your data 6. Test with realistic data sizes to validate performance 7. Handle DataBound event for post-binding operations
Troubleshooting
Issue: Nodes not displaying
- Verify DataSource has data
- Check field mappings match class properties
- Ensure Id values are unique
Issue: Performance degradation
- Enable LoadOnDemand for large datasets
- Consider virtualization
- Check data size and optimize API responses
Issue: Remote data not loading
- Verify API endpoint is accessible
- Check CORS settings on server
- Inspect browser console for errors
- Verify DataManager configuration
DataSource Property Reference
The DataSource property is the core data binding property:
// Local data (List)
public List<MailItem> LocalData { get; set; }
<SfTreeView TValue="MailItem">
<TreeViewFieldsSettings TValue="MailItem"
Id="Id"
Text="Name"
DataSource="@LocalData">
</TreeViewFieldsSettings>
</SfTreeView>
// Remote data (DataManager)
SfDataManager remoteData = new SfDataManager
{
Url = "url",
Adaptor = Syncfusion.Blazor.Adaptors.AdaptorType.JsonAdaptor
};
<SfTreeView TValue="Item">
<TreeViewFieldsSettings TValue="Item"
Id="Id"
Text="Name"
ParentID="ParentId"
HasChildren="HasChild"
DataSource="@remoteData">
</TreeViewFieldsSettings>
</SfTreeView>| Property | Type | Purpose |
|---|---|---|
DataSource | IEnumerable / SfDataManager | Collection of data items or remote data configuration |
---
LoadOnDemand Property Reference
Controls when child nodes are loaded:
// Enable (Default) - Load children only when parent expands
<SfTreeView TValue="EmployeeData" LoadOnDemand="true">
<TreeViewFieldsSettings TValue="EmployeeData"
Id="Id"
Text="Name"
ParentID="ParentId"
HasChildren="HasChild"
DataSource="@Employees">
</TreeViewFieldsSettings>
</SfTreeView>
// Disable - Load all nodes at initialization
<SfTreeView TValue="EmployeeData" LoadOnDemand="false">
<TreeViewFieldsSettings TValue="EmployeeData"
Id="Id"
Text="Name"
Child="Children"
DataSource="@AllEmployees">
</TreeViewFieldsSettings>
</SfTreeView>| Property | Type | Default | Performance Impact |
|---|---|---|---|
LoadOnDemand | bool | true | Faster initial render, reduces bandwidth with large datasets |
When to Use Each:
LoadOnDemand="true": Large datasets, API-driven data, deep hierarchies, performance criticalLoadOnDemand="false": Small datasets, all data readily available, needs full tree access
---
ExpandOn Property Reference
Controls which action triggers node expand/collapse:
// Expand on Click (Default)
<SfTreeView TValue="TreeData" ExpandOn="ExpandAction.Click">
<TreeViewFieldsSettings TValue="TreeData" DataSource="@Data" />
</SfTreeView>
// Expand on Double-Click
<SfTreeView TValue="TreeData" ExpandOn="ExpandAction.DoubleClick">
<TreeViewFieldsSettings TValue="TreeData" DataSource="@Data" />
</SfTreeView>
// No automatic expand
<SfTreeView TValue="TreeData" ExpandOn="ExpandAction.None">
<TreeViewFieldsSettings TValue="TreeData" DataSource="@Data" />
</SfTreeView>| Property | Type | Default | Options |
|---|---|---|---|
ExpandOn | ExpandAction | Click | Click, DoubleClick, None |
---
TreeViewFieldsSettings Complete Reference
Maps data class properties to TreeView fields:
Required Fields
public class Employee
{
public string? Id { get; set; } // Maps to Id field
public string? Name { get; set; } // Maps to Text field
public string? ParentId { get; set; } // Maps to ParentID field
public List<Employee>? Staff { get; set; } // Maps to Child field
public bool HasTeam { get; set; } // Maps to HasChildren field
}
<TreeViewFieldsSettings TValue="Employee"
Id="Id"
Text="Name"
ParentID="ParentId"
Child="Staff"
HasChildren="HasTeam"
DataSource="@Employees">
</TreeViewFieldsSettings>Optional Fields
public class Employee
{
public string? Id { get; set; }
public string? Name { get; set; }
public string? ParentId { get; set; }
public List<Employee>? Staff { get; set; }
public bool HasTeam { get; set; }
// Optional fields
public bool IsExpanded { get; set; } // Maps to Expanded
public bool? IsSelected { get; set; } // Maps to IsChecked
public string? ProfileImage { get; set; } // Maps to ImageUrl
public string? JobTitle { get; set; } // Maps to Tooltip
public string? CompanyUrl { get; set; } // Maps to NavigateUrl
}
<TreeViewFieldsSettings TValue="Employee"
Id="Id"
Text="Name"
ParentID="ParentId"
Child="Staff"
HasChildren="HasTeam"
Expanded="IsExpanded"
IsChecked="IsSelected"
ImageUrl="ProfileImage"
Tooltip="JobTitle"
NavigateUrl="CompanyUrl"
DataSource="@Employees">
</TreeViewFieldsSettings>Complete Field Mapping Reference
| Setting | Type | Purpose | Example |
|---|---|---|---|
Id | string | Unique identifier for each node | "EmployeeId" |
Text | string | Display text for node | "EmployeeName" |
ParentID | string | Parent node reference (self-referential) | "ReportsTo" |
Child | string | Child nodes collection (hierarchical) | "Staff" or "Children" |
HasChildren | string | Boolean field indicating if node has children | "HasTeam" or "IsManager" |
Expanded | string | Boolean field for initial expanded state | "InitialExpanded" |
IsChecked | string | Checkbox state field (bool? for nullable) | "Selected" or "IsChecked" |
Selected | string | Boolean field for initial selected state | "InitialSelected" |
ImageUrl | string | Node icon/image URL field | "IconUrl" or "ProfilePic" |
Tooltip | string | Tooltip text field for nodes | "EmployeeTitle" or "Description" |
NavigateUrl | string | URL for node navigation | "CompanyPage" or "ProfileLink" |
Query | Query | OData query for filtering/sorting | Query().Where(...) |
---
Query Property for Remote Data
Apply filtering and sorting to remote data:
@using Syncfusion.Blazor.Data
<SfTreeView TValue="Employee">
<TreeViewFieldsSettings TValue="Employee"
Id="Id"
Text="Name"
ParentID="ParentId"
DataSource="@Manager"
Query="@QueryData">
</TreeViewFieldsSettings>
</SfTreeView>
@code {
SfDataManager Manager = new SfDataManager
{
Url = "url",
Adaptor = Syncfusion.Blazor.Adaptors.AdaptorType.ODataV4Adaptor
};
Query QueryData = new Query()
.Where("IsActive", "equal", true)
.Where("Department", "equal", "Engineering");
}---
DataBound vs Data Binding Events
DataBound Event (After Binding Complete)
<SfTreeView TValue="Employee">
<TreeViewFieldsSettings TValue="Employee" DataSource="@Employees" />
<TreeViewEvents TValue="Employee" DataBound="OnDataBound"></TreeViewEvents>
</SfTreeView>
@code {
void OnDataBound(DataBoundEventArgs args)
{
Console.WriteLine("Data binding complete");
Console.WriteLine($"Total nodes: {args.Data?.Count}");
// Auto-expand root nodes after binding
ExpandRootNodes();
}
void ExpandRootNodes()
{
var rootNodes = Employees
.Where(x => x.ParentId == null)
.Select(x => x.Id)
.ToArray();
ExpandedNodes = rootNodes;
}
}DataSourceChanged Event (When Data Changes)
<SfTreeView TValue="Employee">
<TreeViewFieldsSettings TValue="Employee" DataSource="@Employees" />
<TreeViewEvents TValue="Employee" DataSourceChanged="OnDataSourceChanged"></TreeViewEvents>
</SfTreeView>
@code {
void OnDataSourceChanged()
{
Console.WriteLine("Data source has changed");
// Refresh state, validation, etc.
}
}---
State Persistence with EnablePersistence
Automatically save and restore data binding state:
<SfTreeView TValue="Employee"
EnablePersistence="true"
@bind-SelectedNodes="@SelectedNodes"
@bind-ExpandedNodes="@ExpandedNodes">
<TreeViewFieldsSettings TValue="Employee" DataSource="@Employees" />
</SfTreeView>
@code {
string[] SelectedNodes = Array.Empty<string>();
string[] ExpandedNodes = Array.Empty<string>();
}What Gets Persisted:
- Expanded nodes
- Selected nodes
- Checked nodes (if checkboxes enabled)
- Scroll position
Storage: Browser localStorage
---
Common Data Binding Mistakes to Avoid
1. Forgetting LoadOnDemand with Large Data
// ❌ BAD - Loads all 100K nodes on init
<SfTreeView TValue="Data" LoadOnDemand="false">
// ✅ GOOD - Loads only visible nodes
<SfTreeView TValue="Data" LoadOnDemand="true">2. Inconsistent Field Mappings
// ❌ BAD - Property names don't match class
<TreeViewFieldsSettings TValue="Employee" Id="employee_id" Text="EmployeeName" />
// ✅ GOOD - Property names match exactly
<TreeViewFieldsSettings TValue="Employee" Id="Id" Text="Name" />3. Missing HasChildren Indicator
// ❌ BAD - No HasChildren = inefficient queries
<TreeViewFieldsSettings TValue="Employee"
Id="Id"
Text="Name"
ParentID="ParentId"
DataSource="@Employees">
</TreeViewFieldsSettings>
// ✅ GOOD - HasChildren prevents unnecessary API calls
<TreeViewFieldsSettings TValue="Employee"
Id="Id"
Text="Name"
ParentID="ParentId"
HasChildren="IsManager"
DataSource="@Employees">
</TreeViewFieldsSettings>4. Forgetting to Set Data Types
// ❌ BAD - Data might not load
List<Employee> Employees; // Null or uninitialized
// ✅ GOOD - Data initialized
List<Employee> Employees = new();
protected override void OnInitialized()
{
LoadEmployeeData();
}---
Performance Tuning for Data Binding
For Large Local Datasets (10K+ items)
<SfTreeView TValue="LargeDataItem"
LoadOnDemand="true"
EnableVirtualization="true"
Height="500px">
<TreeViewFieldsSettings TValue="LargeDataItem"
Id="Id"
Text="Name"
ParentID="ParentId"
HasChildren="HasChild"
DataSource="@LargeData">
</TreeViewFieldsSettings>
</SfTreeView>For Remote API Data
<SfTreeView TValue="RemoteItem" LoadOnDemand="true">
<TreeViewFieldsSettings TValue="RemoteItem"
Id="Id"
Text="Name"
ParentID="ParentId"
HasChildren="HasChild"
DataSource="@ApiManager"
Query="@FilterQuery">
</TreeViewFieldsSettings>
<TreeViewEvents TValue="RemoteItem" NodeExpanded="OnNodeExpanded"></TreeViewEvents>
</SfTreeView>
@code {
// Only load visible nodes on demand
void OnNodeExpanded(NodeExpandEventArgs args)
{
// API loads children only when needed
}
}---
Next Steps
- Use node-selection.md to handle user selections
- Enable checkbox-features.md for multi-selection
- Implement expand-collapse-actions.md for node expansion
Events Handling in TreeView
Table of Contents
- Event Structure
- Lifecycle Events
- Node Interaction Events
- Expand/Collapse Events
- Edit Events
- Drag-Drop Events
- Common Patterns
Event Structure
All TreeView events are attached via the TreeViewEvents component:
@using Syncfusion.Blazor.Navigations
<SfTreeView TValue="MailItem">
<TreeViewFieldsSettings TValue="MailItem" DataSource="@MyFolder" />
<TreeViewEvents TValue="MailItem"
Created="OnCreated"
DataBound="OnDataBound"
NodeSelected="OnNodeSelected"
NodeClicked="OnNodeClicked"
NodeExpanded="OnNodeExpanded"
NodeCollapsed="OnNodeCollapsed"
NodeEditing="OnNodeEditing"
NodeEdited="OnNodeEdited"
OnNodeDragStart="OnDragStart"
NodeDropped="OnNodeDropped">
</TreeViewEvents>
</SfTreeView>
@code {
// Event handlers below
}Lifecycle Events
Created Event
Fires when TreeView is fully initialized and rendered:
void OnCreated(ActionEventArgs args)
{
Console.WriteLine("TreeView created and ready");
// Perform post-initialization actions:
// - Set initial expanded nodes
// - Load user preferences
// - Initialize filters
InitializeTreeAfterCreation();
}
void InitializeTreeAfterCreation()
{
// Load user's last view state
SelectedNodeIds = new[] { "1" };
ExpandedNodeIds = new[] { "1", "2" };
}DataBound Event
Fires when data is populated in TreeView:
void OnDataBound(DataBoundEventArgs args)
{
Console.WriteLine("Data binding complete");
Console.WriteLine($"Total nodes: {args.Data?.Count}");
// Perform post-binding operations:
// - Select default node
// - Expand specific nodes
// - Validate data
ExpandDefaultNodes();
}
void ExpandDefaultNodes()
{
// Auto-expand root nodes on first load
var rootNodes = MyFolder
.Where(x => x.ParentId == null)
.Select(x => x.Id)
.ToArray();
ExpandedNodeIds = rootNodes;
}Node Interaction Events
NodeSelected Event
Fires when a node is selected (left-click):
void OnNodeSelected(NodeSelectEventArgs args)
{
var node = args.NodeData;
Console.WriteLine($"Selected: {node.Id} - {node.Text}");
Console.WriteLine($"Level: {node.Level}");
Console.WriteLine($"Selected: {node.Selected}");
// Access node properties
SelectedNodeId = node.Id;
SelectedNodeText = node.Text;
// Load related data
LoadNodeDetails(node.Id);
}NodeClicked Event
Fires when a node is clicked (regardless of selection):
void OnNodeClicked(NodeClickEventArgs args)
{
var clickCount = args.ClickCount;
var node = args.NodeData;
if (clickCount == 1)
{
// Single click
Console.WriteLine($"Single click: {node.Text}");
}
else if (clickCount == 2)
{
// Double click
Console.WriteLine($"Double click: {node.Text}");
// Usually triggers edit mode
}
}Prevent Node Selection
void OnNodeSelecting(NodeSelectEventArgs args)
{
// Set Cancel = true to prevent selection
if (args.NodeData.Id == "restricted")
{
args.Cancel = true;
return;
}
if (args.NodeData.Level > 2)
{
args.Cancel = true; // Prevent selecting deep nodes
return;
}
}Expand/Collapse Events
NodeExpanded Event
Fires when a node is expanded:
void OnNodeExpanded(NodeExpandEventArgs args)
{
var nodeId = args.NodeData.Id;
var nodeText = args.NodeData.Text;
Console.WriteLine($"Expanded: {nodeText}");
// Load child nodes if implementing load-on-demand
LoadChildNodesIfNeeded(nodeId);
}
async Task LoadChildNodesIfNeeded(string parentId)
{
// Check if children already loaded
if (HasChildren(parentId))
return;
// Fetch from API
var children = await FetchChildNodes(parentId);
// Add to data source
foreach (var child in children)
{
MyFolder.Add(new MailItem
{
Id = child.Id,
ParentId = parentId,
FolderName = child.Name
});
}
StateHasChanged();
}NodeCollapsed Event
Fires when a node is collapsed:
void OnNodeCollapsed(NodeCollapseEventArgs args)
{
var nodeId = args.NodeData.Id;
Console.WriteLine($"Collapsed: {nodeId}");
// Optional: Unload children to save memory
UnloadChildrenIfLarge(nodeId);
}
void UnloadChildrenIfLarge(string parentId)
{
var childCount = MyFolder
.Count(x => x.ParentId == parentId);
if (childCount > 100)
{
// Remove children from data source
var toRemove = MyFolder
.Where(x => x.ParentId == parentId)
.ToList();
foreach (var item in toRemove)
MyFolder.Remove(item);
}
}Edit Events
NodeEditing Event
Fires before entering edit mode:
void OnNodeEditing(NodeEditEventArgs args)
{
var nodeId = args.NodeData.Id;
var currentText = args.NodeData.Text;
Console.WriteLine($"Before edit: {currentText}");
// Prevent editing certain nodes
if (nodeId.StartsWith("system_"))
{
args.Cancel = true;
return;
}
// Check permissions
if (!UserHasEditPermission(nodeId))
{
args.Cancel = true;
return;
}
}
bool UserHasEditPermission(string nodeId)
{
// Check user role or node ownership
return CurrentUser.Role == "Admin" || CurrentUser.CanEdit;
}NodeEdited Event
Fires after edit is confirmed:
void OnNodeEdited(NodeEditEventArgs args)
{
var nodeId = args.NodeData.Id;
var oldText = args.OldText;
var newText = args.NewText;
Console.WriteLine($"After edit: {oldText} → {newText}");
// Validate new text
if (!ValidateNodeName(newText))
{
args.Cancel = true;
EditError = "Invalid name";
return;
}
// Update data source
var node = MyFolder.FirstOrDefault(x => x.Id == nodeId);
if (node != null)
{
node.FolderName = newText;
}
// Update server
SaveNodeChange(nodeId, newText);
}
bool ValidateNodeName(string name)
{
// Check for empty
if (string.IsNullOrWhiteSpace(name))
return false;
// Check length
if (name.Length > 100)
return false;
// Check special characters
var invalidChars = new[] { '<', '>', '"', '|' };
if (name.Any(c => invalidChars.Contains(c)))
return false;
return true;
}Checkbox Events
NodeChecking Event
Fires before a node's checkbox state changes:
void OnNodeChecking(NodeCheckingEventArgs args)
{
var nodeId = args.NodeData.Id;
var isChecking = args.IsChecked;
Console.WriteLine($"Before checkbox change: Node {nodeId}, IsChecked: {isChecking}");
// Prevent checking certain nodes
if (nodeId.StartsWith("readonly_"))
{
args.Cancel = true; // Prevent the check action
return;
}
// Validate permissions
if (!UserHasCheckPermission(nodeId))
{
args.Cancel = true;
return;
}
// Business logic validation
if (isChecking && !ValidateCheckPrerequisites(nodeId))
{
args.Cancel = true;
return;
}
}
bool UserHasCheckPermission(string nodeId)
{
return CurrentUser.Role == "Admin" || CurrentUser.CanSelectItems;
}
bool ValidateCheckPrerequisites(string nodeId)
{
// Example: Can't check a node if parent isn't checked
var node = MyFolder.FirstOrDefault(x => x.Id == nodeId);
if (node?.ParentId != null)
{
var parent = MyFolder.FirstOrDefault(x => x.Id == node.ParentId);
return parent?.IsChecked == true;
}
return true;
}NodeChecked Event
Fires after a node's checkbox state is successfully changed:
void OnNodeChecked(NodeCheckedEventArgs args)
{
var nodeId = args.NodeData.Id;
var isChecked = args.IsChecked;
Console.WriteLine($"After checkbox change: Node {nodeId}, IsChecked: {isChecked}");
// Update related data
UpdateNodeMetadata(nodeId, isChecked);
// Trigger dependent actions
if (isChecked)
{
OnNodeCheckActionAsync(nodeId);
}
else
{
OnNodeUncheckActionAsync(nodeId);
}
// Log the change
LogCheckboxChange(nodeId, isChecked);
}
void UpdateNodeMetadata(string nodeId, bool isChecked)
{
var node = MyFolder.FirstOrDefault(x => x.Id == nodeId);
if (node != null)
{
node.IsChecked = isChecked;
// Save to database or state management
}
}
async Task OnNodeCheckActionAsync(string nodeId)
{
// Perform actions when node is checked
// Example: Load related data, enable features, etc.
await InvokeAsync(StateHasChanged);
}
async Task OnNodeUncheckActionAsync(string nodeId)
{
// Perform actions when node is unchecked
// Example: Clear selection, disable features, etc.
await InvokeAsync(StateHasChanged);
}
void LogCheckboxChange(string nodeId, bool isChecked)
{
Console.WriteLine($"[AUDIT] Checkbox changed: NodeId={nodeId}, IsChecked={isChecked}, Timestamp={DateTime.Now}");
}Drag-Drop Events
OnNodeDragStart Event
Fires when drag operation begins:
void OnNodeDragStart(DragAndDropEventArgs args)
{
var draggedNodeId = args.DraggedNodeData.Id;
Console.WriteLine($"Drag started: {draggedNodeId}");
// Prevent dragging certain nodes
if (draggedNodeId.StartsWith("locked_"))
{
args.Cancel = true;
return;
}
// Optional: Show visual feedback
IsDragging = true;
}OnNodeDragged Event
Fires repeatedly while dragging:
void OnNodeDragged(DragAndDropEventArgs args)
{
var draggedNodeId = args.DraggedNodeData.Id;
// Update visual indicator
// Note: Cannot cancel at this stage
Console.WriteLine($"Dragging node: {draggedNodeId}");
}NodeDropped Event
Fires when node is dropped:
void OnNodeDropped(DragAndDropEventArgs args)
{
var draggedNodeId = args.DraggedNodeData.Id;
var dropIndex = args.DropIndex;
Console.WriteLine($"Dropped node {draggedNodeId} at index {dropIndex}");
// Update data model
UpdateNodeHierarchy(draggedNodeId, dropIndex);
}
void UpdateNodeHierarchy(string draggedNodeId, int dropIndex)
{
var draggedNode = MyFolder.FirstOrDefault(x => x.Id == draggedNodeId);
if (draggedNode == null)
return;
// Update node position based on dropIndex
// Remove from current position and insert at new position
MyFolder.Remove(draggedNode);
MyFolder.Insert(dropIndex, draggedNode);
// Save to database
SaveHierarchyChange(draggedNodeId, dropIndex);
}Event Arguments Reference
NodeSelectEventArgs
public class NodeSelectEventArgs
{
public TreeViewNodeData NodeData { get; set; } // Selected node
public bool Cancel { get; set; } // Cancel selection
}NodeClickEventArgs
public class NodeClickEventArgs
{
public TreeViewNodeData NodeData { get; set; }
public int ClickCount { get; set; } // 1 or 2
}NodeExpandEventArgs / NodeCollapseEventArgs
public class NodeExpandEventArgs
{
public TreeViewNodeData NodeData { get; set; }
public bool Cancel { get; set; }
}NodeEditEventArgs
public class NodeEditEventArgs
{
public TreeViewNodeData NodeData { get; set; }
public string OldText { get; set; }
public string NewText { get; set; }
public bool Cancel { get; set; }
}DragAndDropEventArgs
public class DragAndDropEventArgs
{
public TreeViewNodeData DraggedNodeData { get; set; }
public int DropIndex { get; set; }
public bool Cancel { get; set; }
}Common Patterns
Pattern 1: Multi-Step Edit with Validation
string EditingNodeId = "";
string EditError = "";
void OnNodeEditing(NodeEditEventArgs args)
{
EditingNodeId = args.NodeData.Id;
EditError = "";
}
void OnNodeEdited(NodeEditEventArgs args)
{
// Step 1: Validate locally
if (!ValidateNodeName(args.NewText))
{
EditError = "Invalid name format";
args.Cancel = true;
return;
}
// Step 2: Check for duplicates
if (CheckDuplicate(args.NewText, args.NodeData.Id))
{
EditError = "Name already exists";
args.Cancel = true;
return;
}
// Step 3: Update
var node = MyFolder.FirstOrDefault(x => x.Id == args.NodeData.Id);
if (node != null)
node.FolderName = args.NewText;
EditingNodeId = "";
EditError = "";
}Pattern 2: Event Logging
List<EventLog> EventLogs = new();
void LogEvent(string eventType, string nodeId, string message)
{
EventLogs.Add(new EventLog
{
Timestamp = DateTime.Now,
EventType = eventType,
NodeId = nodeId,
Message = message
});
}
void OnNodeSelected(NodeSelectEventArgs args)
{
LogEvent("NodeSelected", args.NodeData.Id, $"Selected: {args.NodeData.Text}");
}
void OnNodeExpanded(NodeExpandEventArgs args)
{
LogEvent("NodeExpanded", args.NodeData.Id, "Node expanded");
}Pattern 3: Cascade Events
void OnNodeExpanded(NodeExpandEventArgs args)
{
// When user expands, auto-select first child
var firstChild = MyFolder.FirstOrDefault(x => x.ParentId == args.NodeData.Id);
if (firstChild != null)
{
SelectedNodeIds = new[] { firstChild.Id };
StateHasChanged();
}
}Pattern 4: Real-time Sync
async Task OnNodeEdited(NodeEditEventArgs args)
{
var nodeId = args.NodeData.Id;
var newText = args.NewText;
try
{
// Update server immediately
await HttpClient.PutAsJsonAsync(
$"/api/folders/{nodeId}",
new { name = newText }
);
// Show success feedback
ShowSuccessNotification("Node updated");
}
catch (Exception ex)
{
// Revert on error
args.Cancel = true;
ShowErrorNotification($"Failed to update: {ex.Message}");
}
}Additional Events
DataSourceChanged Event
Fires when the data source is changed or updated:
<SfTreeView TValue="Employee">
<TreeViewFieldsSettings TValue="Employee" DataSource="@Employees" />
<TreeViewEvents TValue="Employee" DataSourceChanged="OnDataSourceChanged"></TreeViewEvents>
</SfTreeView>
@code {
void OnDataSourceChanged()
{
Console.WriteLine("Data source has been changed");
// Perform actions like:
// - Refresh calculations
// - Update derived data
// - Re-apply filters
RecalculateNodeStats();
ApplyCurrentFilters();
}
void RecalculateNodeStats()
{
// Recalculate any statistics based on new data
}
}OnActionFailure Event
Fires when a TreeView action fails (e.g., API error during load on demand):
<SfTreeView TValue="Employee">
<TreeViewFieldsSettings TValue="Employee" DataSource="@Employees" />
<TreeViewEvents TValue="Employee" OnActionFailure="OnActionFailed"></TreeViewEvents>
</SfTreeView>
@code {
void OnActionFailed(FailureEventArgs args)
{
Console.WriteLine($"Action failed: {args.Error}");
// Handle error gracefully
if (args.Error != null)
{
// Log error
LogError(args.Error);
// Show user notification
ShowErrorNotification("Failed to load data. Please try again.");
// Attempt recovery
AttemptRecovery();
}
}
void LogError(Exception error)
{
// Send to error tracking service
Console.WriteLine($"Error: {error.Message}");
}
void AttemptRecovery()
{
// Reload data or reset state
}
}OnKeyPress Event
Fires when user presses a key while TreeView has focus:
<SfTreeView TValue="Employee">
<TreeViewFieldsSettings TValue="Employee" DataSource="@Employees" />
<TreeViewEvents TValue="Employee" OnKeyPress="OnKeyPressed"></TreeViewEvents>
</SfTreeView>
@code {
void OnKeyPressed(KeyboardEventArgs args)
{
Console.WriteLine($"Key pressed: {args.Key}");
switch (args.Key)
{
case "Enter":
HandleEnterKey();
break;
case "Delete":
HandleDeleteKey();
break;
case "F2":
HandleEditKey();
break;
case "ArrowUp":
case "ArrowDown":
case "ArrowLeft":
case "ArrowRight":
// Navigation handled automatically
break;
}
}
void HandleEnterKey()
{
// Expand/Collapse or Select action
Console.WriteLine("Enter key pressed");
}
void HandleDeleteKey()
{
// Delete current node
Console.WriteLine("Delete key pressed");
}
void HandleEditKey()
{
// Enter edit mode
Console.WriteLine("F2 (Edit) key pressed");
}
}Keyboard Shortcuts Reference:
- Enter: Expand/Collapse or Select node
- Delete: Delete selected node
- F2: Enter edit mode
- Arrow Keys: Navigate between nodes
- Ctrl+A: Select all nodes (if multi-selection enabled)
- Ctrl+C/X/V: Copy/Cut/Paste (if editing enabled)
- Escape: Exit edit mode
OnNodeRender Event
Fires for each node as it's rendered, allowing custom rendering logic:
<SfTreeView TValue="Employee">
<TreeViewFieldsSettings TValue="Employee" DataSource="@Employees" />
<TreeViewEvents TValue="Employee" OnNodeRender="OnNodeRender"></TreeViewEvents>
</SfTreeView>
@code {
void OnNodeRender(NodeRenderEventArgs args)
{
var nodeData = args.Node;
Console.WriteLine($"Rendering node: {nodeData.Text}");
// Apply conditional formatting
if (nodeData.Level > 3)
{
// Hide deeply nested nodes
args.Node.Hidden = true;
}
// Add custom classes
if (IsHighPriority(nodeData))
{
args.Node.CssClass = "priority-high";
}
else if (IsLowPriority(nodeData))
{
args.Node.CssClass = "priority-low";
}
// Modify node appearance
if (IsDisabled(nodeData))
{
args.Node.Disabled = true;
}
}
bool IsHighPriority(TreeViewNodeData node)
{
// Custom logic to determine priority
return node.Text?.Contains("URGENT") ?? false;
}
bool IsLowPriority(TreeViewNodeData node)
{
return node.Level > 2;
}
bool IsDisabled(TreeViewNodeData node)
{
return node.Id?.StartsWith("disabled_") ?? false;
}
}
<style>
.priority-high {
color: red;
font-weight: bold;
}
.priority-low {
color: gray;
opacity: 0.7;
}
</style>---
Complete Event Handler Attachment
Attach multiple events in one place:
<SfTreeView TValue="Employee">
<TreeViewFieldsSettings TValue="Employee" DataSource="@Employees" />
<TreeViewEvents TValue="Employee"
Created="OnCreated"
DataBound="OnDataBound"
DataSourceChanged="OnDataSourceChanged"
NodeSelected="OnNodeSelected"
NodeClicked="OnNodeClicked"
NodeExpanding="OnNodeExpanding"
NodeExpanded="OnNodeExpanded"
NodeCollapsing="OnNodeCollapsing"
NodeCollapsed="OnNodeCollapsed"
NodeEditing="OnNodeEditing"
NodeEdited="OnNodeEdited"
NodeChecking="OnNodeChecking"
NodeChecked="OnNodeChecked"
OnNodeDragStart="OnDragStart"
OnNodeDragged="OnDragged"
OnNodeDragStop="OnDragStop"
NodeDropped="OnNodeDropped"
OnKeyPress="OnKeyPressed"
OnNodeRender="OnNodeRender"
OnActionFailure="OnActionFailed">
</TreeViewEvents>
</SfTreeView>
@code {
// Define all handlers here
}---
Best Practices
1. Handle errors gracefully in event handlers 2. Cancel events when validation fails 3. Provide user feedback via toasts or status messages 4. Log important events for debugging 5. Avoid heavy operations in frequently-fired events (OnNodeDragged) 6. Use StateHasChanged() after manual updates 7. Cache event results to avoid repeated calculations 8. Handle keyboard events for accessibility 9. Use OnNodeRender for conditional formatting 10. Catch OnActionFailure for error recovery
Troubleshooting
Issue: Events not firing
- Ensure TreeViewEvents component is added
- Check event handler names match property names
- Verify no exceptions in event handler
Issue: Cancel not working
- Some events don't support Cancel (OnNodeDragged)
- Verify Cancel property exists on event args
- Check cancel logic is correct
Issue: Performance degradation
- Move heavy logic from OnNodeDragged to NodeDropped
- Cache data instead of recalculating
- Use throttling for rapid-fire events
Next Steps
- Use expand-collapse-actions.md for expand events
- Implement node-editing.md for edit events
- Check advanced-features.md for drag-drop details
Getting Started with Blazor TreeView
This reference covers installation, project setup, and creating your first TreeView component in Blazor applications.
Table of Contents
1. Installation 2. Project Setup by Application Type 3. CSS Theme Configuration 4. Service Registration 5. Creating Your First TreeView 6. Data Binding 7. Validation Checklist 8. Common Issues & Troubleshooting
---
Installation
NuGet Package Installation
The Blazor TreeView component is part of the Syncfusion Navigations package. Install the required NuGet packages:
Install-Package Syncfusion.Blazor.Navigations -Version 26.1.35
Install-Package Syncfusion.Blazor.Themes -Version 26.1.35Or using .NET CLI:
dotnet add package Syncfusion.Blazor.Navigations --version 26.1.35
dotnet add package Syncfusion.Blazor.Themes --version 26.1.35
dotnet restoreProject Setup by Application Type
Blazor WebAssembly App
1. Create project using Visual Studio or .NET CLI:
dotnet new blazorwasm -o BlazorApp
cd BlazorApp2. Install NuGet packages (see above)
3. Update ~/_Imports.razor:
@using Syncfusion.Blazor
@using Syncfusion.Blazor.Navigations4. Register service in Program.cs:
builder.Services.AddSyncfusionBlazor();5. Add theme in Index.html (head section):
<link href="_content/Syncfusion.Blazor.Themes/bootstrap5.css" rel="stylesheet" />Blazor Server App
1. Create project:
dotnet new blazorserver -o BlazorApp
cd BlazorApp2. Install NuGet packages (see above)
3. Update _Imports.razor:
@using Syncfusion.Blazor
@using Syncfusion.Blazor.Navigations4. Register service in Program.cs:
builder.Services.AddSyncfusionBlazor();5. Add theme in _Layout.cshtml (head section):
<link href="_content/Syncfusion.Blazor.Themes/bootstrap5.css" rel="stylesheet" />Blazor Web App (Interactive)
1. Create project:
dotnet new blazor -o BlazorApp -int Auto
cd BlazorApp2. Install NuGet packages in Client project (.csproj)
3. Update Client project _Imports.razor:
@using Syncfusion.Blazor
@using Syncfusion.Blazor.Navigations4. Register service in Program.cs (Client project):
builder.Services.AddSyncfusionBlazor();5. Add theme in Client project Index.html:
<link href="_content/Syncfusion.Blazor.Themes/bootstrap5.css" rel="stylesheet" />MAUI Blazor App
1. Create project:
dotnet new maui -n MauiApp
cd MauiApp2. Install NuGet packages in the MAUI project
3. Update _Imports.razor:
@using Syncfusion.Blazor
@using Syncfusion.Blazor.Navigations4. Register service in MauiProgram.cs:
.AddMauiBlazorWebView()
.ConfigureSyncfusionBlazor();5. Add theme in wwwroot/index.html:
<link href="_content/Syncfusion.Blazor.Themes/bootstrap5.css" rel="stylesheet" />CSS Theme Configuration
Syncfusion provides multiple built-in themes:
<!-- Bootstrap 5 Theme -->
<link href="_content/Syncfusion.Blazor.Themes/bootstrap5.css" rel="stylesheet" />
<!-- Material Theme -->
<link href="_content/Syncfusion.Blazor.Themes/material.css" rel="stylesheet" />
<!-- Fluent Theme -->
<link href="_content/Syncfusion.Blazor.Themes/fluent.css" rel="stylesheet" />
<!-- Tailwind Theme -->
<link href="_content/Syncfusion.Blazor.Themes/tailwind.css" rel="stylesheet" />First TreeView Component
Minimal Example
@using Syncfusion.Blazor.Navigations
<SfTreeView TValue="TreeData">
<TreeViewFieldsSettings TValue="TreeData"
Id="Id"
Text="Name"
Child="Children"
DataSource="@TreeItems">
</TreeViewFieldsSettings>
</SfTreeView>
@code {
public class TreeData
{
public string? Id { get; set; }
public string? Name { get; set; }
public List<TreeData>? Children { get; set; }
}
List<TreeData> TreeItems = new()
{
new TreeData { Id = "1", Name = "Documents" },
new TreeData { Id = "2", Name = "Images" },
new TreeData { Id = "3", Name = "Music" }
};
}Example with Hierarchical Data
@using Syncfusion.Blazor.Navigations
<SfTreeView TValue="Folder">
<TreeViewFieldsSettings TValue="Folder"
Id="Id"
Text="FolderName"
Child="SubFolders"
Expanded="Expanded"
DataSource="@FolderData">
</TreeViewFieldsSettings>
</SfTreeView>
@code {
public class Folder
{
public string? Id { get; set; }
public string? FolderName { get; set; }
public bool Expanded { get; set; }
public List<Folder>? SubFolders { get; set; }
}
List<Folder> FolderData = new();
protected override void OnInitialized()
{
var documents = new List<Folder>
{
new Folder { Id = "1-1", FolderName = "My Documents" },
new Folder { Id = "1-2", FolderName = "Downloads" }
};
FolderData.Add(new Folder
{
Id = "1",
FolderName = "Documents",
Expanded = true,
SubFolders = documents
});
}
}Service Registration
The AddSyncfusionBlazor() method registers all Syncfusion Blazor components globally. Call it in Program.cs:
using Syncfusion.Blazor;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
// Register Syncfusion services
builder.Services.AddSyncfusionBlazor();
await builder.Build().RunAsync();Validation Checklist
- [ ] NuGet packages installed (Navigations + Themes)
- [ ] Namespaces added to _Imports.razor
- [ ] Service registered in Program.cs
- [ ] CSS theme link added to HTML
- [ ] TreeView component renders in browser
- [ ] Data displays correctly
Common Issues & Troubleshooting
Issue: "SfTreeView type not found"
- Ensure
@using Syncfusion.Blazor.Navigationsis in _Imports.razor
Issue: Styles not applying
- Verify CSS theme link is correct in HTML head
- Check browser console for 404 errors on CSS files
Issue: Data not displaying
- Verify DataSource is populated
- Check field mappings (Id, Text, Child) match data class properties
- Ensure data is initialized in OnInitialized()
Issue: Component not rendering
- Ensure Syncfusion service is registered in Program.cs
- Check for JavaScript console errors
- Verify project target framework is .NET 6.0 or later
Next Steps
- Proceed to data-binding.md to learn different data binding approaches
- Use node-selection.md to enable user interaction
- Check events-handling.md for handling TreeView events