You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何绑定MudBlazor TreeView到自定义对象及实现子节点懒加载

MudBlazor动态TreeView实现方案

1. 绑定对象构造要求

MudTreeView没有强制绑定特定类型,你可以自定义和数据库表结构匹配的节点实体类,只要满足两个核心要求即可:

  • 包含唯一标识、父节点标识两个字段,用于层级关联
  • 包含子节点集合属性,类型可以用List<T>或者IEnumerable<T>,T为你自定义的节点类

推荐的自定义节点类示例:

public class TreeNodeDto
{
    // 节点唯一ID,和数据库主键对应
    public int Id { get; set; }
    // 父节点ID,一级节点的ParentId统一设为null
    public int? ParentId { get; set; }
    // 节点显示文本
    public string Name { get; set; }
    // 节点展开状态,用于和MudTreeView绑定
    public bool IsExpanded { get; set; }
    // 子节点集合
    public List<TreeNodeDto> Children { get; set; } = new();
    // 标记子节点是否已加载,避免重复请求数据库
    public bool IsChildrenLoaded { get; set; }
}

如果不想自定义类,也可以直接使用MudBlazor自带的TreeViewItemData<T>类,自定义类的优势是更贴合业务场景,扩展属性更方便。

2. 一级节点初始化+点击懒加载子节点逻辑

初始加载逻辑

页面初始化时,只从数据库查询所有ParentId == null的一级节点,赋值给MudTreeView绑定的根节点集合即可,不需要一次性拉取全量数据。

点击加载子节点逻辑

通过MudTreeView的ExpandedChanged事件触发子节点加载:

  • 节点被展开时先判断IsChildrenLoaded状态,如果已经加载过直接返回
  • 如果未加载,将当前节点ID作为ParentId查询条件拉取子节点,赋值到当前节点的Children属性,同时将IsChildrenLoaded设为true即可

3. 完整实现示例

页面代码

@page "/dynamic-treeview"
@inject ITreeRepository TreeRepository <!-- 替换为你自己的数据库访问服务 -->

<MudTreeView Items="RootNodes" T="TreeNodeDto" ExpandedChanged="OnNodeExpanded">
    <ItemTemplate>
        <MudTreeViewItem @bind-Expanded="context.IsExpanded" T="TreeNodeDto" Value="@context">
            <TitleContent>
                @context.Name
                <!-- 新增、删除操作按钮 -->
                <MudIconButton Icon="@Icons.Material.Filled.Add" Size="Size.Small" OnClick="() => AddNode(context)" Class="ml-2" />
                <MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Small" OnClick="() => DeleteNode(context)" Class="ml-1" />
            </TitleContent>
            <Items>
                @context.Children
            </Items>
        </MudTreeViewItem>
    </ItemTemplate>
</MudTreeView>

@code {
    // 根节点集合,初始仅存储一级节点
    private List<TreeNodeDto> RootNodes { get; set; } = new();

    protected override async Task OnInitializedAsync()
    {
        // 初始加载所有一级节点
        RootNodes = await TreeRepository.GetListByParentIdAsync(null);
    }

    private async Task OnNodeExpanded(TreeNodeDto node, bool isExpanded)
    {
        // 仅在展开且子节点未加载时请求数据库
        if (isExpanded && !node.IsChildrenLoaded)
        {
            node.Children = await TreeRepository.GetListByParentIdAsync(node.Id);
            node.IsChildrenLoaded = true;
        }
    }

    // 新增节点逻辑
    private async Task AddNode(TreeNodeDto parentNode)
    {
        // 实际场景可替换为弹窗输入节点信息
        var newNode = new TreeNodeDto
        {
            Name = "新节点",
            ParentId = parentNode.Id
        };
        // 写入数据库
        var savedNode = await TreeRepository.AddAsync(newNode);
        // 更新内存集合触发UI刷新
        parentNode.Children.Add(savedNode);
    }

    // 删除节点逻辑
    private async Task DeleteNode(TreeNodeDto node)
    {
        // 数据库删除
        await TreeRepository.DeleteAsync(node.Id);
        // 从内存集合中移除对应节点
        if (node.ParentId == null)
        {
            RootNodes.Remove(node);
            return;
        }
        var parentNode = FindParentNode(RootNodes, node.ParentId.Value);
        parentNode?.Children.Remove(node);
    }

    // 辅助方法:递归查找父节点
    private TreeNodeDto FindParentNode(List<TreeNodeDto> nodes, int parentId)
    {
        foreach (var node in nodes)
        {
            if (node.Id == parentId) return node;
            var childResult = FindParentNode(node.Children, parentId);
            if (childResult != null) return childResult;
        }
        return null;
    }
}

注意事项

  • 数据库访问逻辑可以根据你使用的框架替换为EF Core、SqlSugar等对应的查询代码,示例中的ITreeRepository仅为抽象演示
  • 新增/删除操作完成后直接操作对应内存集合即可,Blazor会自动触发UI重绘,无需额外调用刷新方法
  • 如果需要支持节点选中、禁用等能力,直接在TreeNodeDto中添加对应属性,绑定到MudTreeViewItem的对应参数即可

内容的提问来源于stack exchange,提问作者user976508

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 05:06:05