
UI组件前端【免费下载链接】ant-design-blazorA rich set of enterprise-class UI components based on Ant Design and Blazor.项目地址https://gitcode.com/gh_mirrors/an/ant-design-blazor点击查看免费下载导读在 Ant Design Blazor 的Tree组件中仅开启Draggable属性只能实现全节点可拖、全节点可放的粗粒度交互。本文以官方示例 Droppable.razor 为骨架深入讲解DraggableExpression与DroppableExpression两个表达式参数前者按节点判定谁可以被拖动后者按被拖节点 落点节点 落点方位三元组判定谁可以接收并结合 TreeNodeTitle.razor.cs 与 TreeNode.razor.cs 的源码调用链说明拖放判定、目标高亮、数据移动与事件回调的完整执行过程。读完本文你将能在组织架构树、文件目录树等场景中实现只有指定类型节点可拖、只有指定目标可接收的精细化拖放控制。一、从整体拖放到精确控制Ant Design Blazor 的Tree组件源码位于 components/tree通过一个布尔参数开启拖放能力[Parameter] public bool Draggable { get; set; }该参数定义于 Tree.razor.cs开启后组件根元素会附加draggable-tree样式类见 SetClassMapper。一旦开启所有节点默认都既可拖也可放这在需要约束拖放范围例如只允许拖动叶节点、只允许放入指定层级的业务场景中远远不够。为此Tree组件额外提供了两个表达式参数用于在节点维度和目标维度上做精细化控制参数类型作用DraggableExpressionFuncTreeNodeTItem, bool传入待判定的节点返回true表示该节点允许被拖动DroppableExpressionFuncTreeNodeTItem, TreeNodeTItem, bool, bool传入被拖节点、落点节点、是否落在落点下方返回true表示允许此次拖放两者的签名定义见 Tree.razor.cs。值得注意的是DroppableExpression的第三个参数dropBelowtrue表示被拖节点将插到落点节点的下方成为其兄弟节点false表示被拖节点将放入落点节点内部成为其子节点。这一方位参数让规则可以区分可做子节点与只能插在下方两种场景是整个精确控制的关键。二、官方示例全解析组织架构树官方文档 Droppable.md 对应的完整示例位于同目录的 Droppable.razor其业务场景是一棵公司 — 部门 — 员工三层组织架构树。核心思路是只有员工节点可以被拖动员工节点不能接收放入其内部的拖放。完整代码Tree Stylewidth:100%; DataSourcetreeData Draggable OnDroponDrop DraggableExpressionnode node.DataItem.Type NodeType.ntEmployeee DroppableExpressionIsDroppable TItemData Selectable BlockNode DefaultExpandAll ChildrenExpressionnode node.DataItem.Children TitleExpressionnode node.DataItem.Title KeyExpressionnode node.DataItem.Key IsLeafExpressionnode !node.DataItem.Children.Any() /Tree code { ListData treeData new ListData { new() { Title Company, Key 1, Type NodeType.ntCompany, Children new ListData { new() { Title Department1, Key 11, Type NodeType.ntDepartment, Children new ListData { new() { Title Tom, Key 111, Type NodeType.ntEmployeee }, new() { Title John, Key 112, Type NodeType.ntEmployeee } } }, new() { Title Department2, Key 12, Type NodeType.ntDepartment, Children new ListData { new() { Title Jim, Key 121, Type NodeType.ntEmployeee }, new() { Title Lili, Key 122, Type NodeType.ntEmployeee } } } } } }; public enum NodeType { ntCompany, ntDepartment, ntEmployeee } public class Data { public string Key { get; set; } public string Title { get; set; } public NodeType Type { get; set; } public ListData Children { get; set; } new(); } private bool IsDroppable(TreeNodeData draggingNode, TreeNodeData dropNode, bool dropBelow) { if (dropNode.Key 111) return false; if (dropBelow dropNode.GetParentNode() null) return false; return dropNode.DataItem.Type ! NodeType.ntEmployeee || dropBelow; } private void onDrop(TreeEventArgsData e) { } }2.1 数据模型与节点类型示例使用TItemData泛型参数将树与自定义实体绑定通过TitleExpression、KeyExpression、ChildrenExpression、IsLeafExpression四个表达式把实体属性映射为树的标题、键、子节点与叶子判定。实体内部的Type枚举ntCompany/ntDepartment/ntEmployeee是拖放规则的判据来源——这正是DraggableExpression/DroppableExpression可以自由读取node.DataItem的原因表达式收到的TreeNodeTItem对象持有DataItem属性开发者可以在表达式闭包内访问业务实体的任意字段。2.2 拖放规则拆解DraggableExpressionnode node.DataItem.Type NodeType.ntEmployeee表示只有Type ntEmployeee的节点Tom、John、Jim、Lili显示为可拖动公司节点与部门节点均不可拖。IsDroppable方法则实现了三条递进规则if (dropNode.Key 111) return false;—— 键为111的节点Tom拒绝任何拖放落到它身上相当于锁定该节点if (dropBelow dropNode.GetParentNode() null) return false;—— 若要把节点插到根节点下方即插到树的顶层成为公司节点的兄弟一律拒绝。GetParentNode()是 TreeNode.razor.cs 提供的公开 API根节点返回nullreturn dropNode.DataItem.Type ! NodeType.ntEmployeee || dropBelow;—— 若落点节点本身是员工非叶子容器只允许插到它下方dropBelow true不允许放入它内部员工节点之下不应再挂子节点若落点是公司或部门则两种方位都允许。这三条规则叠加后的效果员工可以被拖到部门/公司内部或任意节点下方但不能拖到 Tom 身上不能拖到树的顶层也不能把员工塞进另一个员工内部。三、DraggableExpression可拖判定的源码链路DraggableExpression的值最终落到每个TreeNodeTItem的Draggable属性上。TreeNode的该属性定义于 TreeNode.razor.csprivate bool _draggable true; [Parameter] public bool Draggable { get TreeComponent.Draggable _draggable; set _draggable value; }可见节点级可拖状态是树的Draggable开关 节点自身_draggable的合取结果——即使表达式放行只要树未开启Draggable也不会生效。表达式的结果在节点初始化时计算并写入见 TreeNode.razor.csif (TreeComponent.DraggableExpression ! null) Draggable TreeComponent.DraggableExpression(this);渲染层面TreeNodeTitle.razor 将CanDraggable映射为 HTML5 拖放属性并绑定全部拖放事件draggable(CanDraggable ? true : false) aria-grabbed(CanDraggable ? true : false) ondragover:preventDefault(TreeComponent.DragItem ! null) ondragoverOnDragOver ondragleave:preventDefault(TreeComponent.DragItem ! null) ondragleaveOnDragLeave ondragenter:preventDefault(TreeComponent.DragItem ! null) ondragenterOnDragEnter ondrop:preventDefault(TreeComponent.DragItem ! null) ondropOnDrop ondragstarte { if (CanDraggable) OnDragStart(e); } ondragende { if (CanDraggable) OnDragEnd(e); }拖动开始时OnDragStart 把当前节点写入TreeComponent.DragItem拖拽起点节点随后自动折叠该节点并触发OnDragStart回调拖动期间TreeComponent.DragItem即正在被拖动的节点后续所有落点判定都以它为准。四、DroppableExpression落点判定与目标高亮4.1 判定时机与调用链DroppableExpression并非只在松手时调用一次而是在拖动悬停期间被反复调用用于决定目标节点是否显示可放置高亮。其核心入口是 TreeNodeTitle.razor.cs 的CanDropprivate bool CanDrop(TreeNodeTItem draggingNode, TreeNodeTItem dropNode, bool dropBelow) { if (TreeComponent.DroppableExpression ! null) return TreeComponent.DroppableExpression(draggingNode, dropNode, dropBelow); return true; }若未设置DroppableExpression默认所有目标都放行返回true。4.2 方位判定OffSETX 阈值OnDragOverTreeNodeTitle.razor.cs根据鼠标在目标节点标题上的横向位移决定放入内部还是插到下方阈值常量为OffSETX 25像素private const double OffSETX 25; // ... if (e.ClientX - _dragTargetClientX OffSETX) { if (!CanDrop(TreeComponent.DragItem, SelfNode, false)) return; SelfNode.SetTargetBottom(); SelfNode.SetParentTargetContainer(); _ SelfNode.Expand(true); // 视为子节点容器时自动展开 } else { if (!CanDrop(TreeComponent.DragItem, SelfNode, true)) return; SelfNode.SetTargetBottom(true); SelfNode.SetParentTargetContainer(true); } SelfNode.DragTarget true;含义拖入目标节点后继续向右移动超过 25px则意图是成为该节点的子节点dropBelow false否则意图是插到该节点下方dropBelow true。两种意图分别调用一次CanDrop只要对应方向的判定返回false本次落点高亮就被否决。这也解释了示例规则三的写法员工节点在向右拖入内部方向dropBelow false被拒绝而插到下方方向dropBelow true被放行。4.3 目标高亮的样式证据节点是否成为合法落点通过 TreeNode.razor.cs 的 ClassMapper 反映到 DOM.If(drop-target, () DragTarget) .If(drag-over-gap-bottom, () DragTarget DragTargetBottom) .If(drag-over, () DragTarget !DragTargetBottom) .If(drop-container, () TargetContainer)其中DragTargetBottomTreeNode.razor.cs记录落点在下方的方位状态同时 TreeNodeTitle.razor 在合法落点渲染一条ant-tree-drop-indicator指示线。视觉反馈与判定逻辑一一对应用户看到的高亮正是DroppableExpression刚刚放行的结果。五、松手后的数据移动与事件回调当拖拽在合法目标上松手OnDrop 根据落点方位执行节点数据移动private void OnDrop(DragEventArgs e) { if (TreeComponent.DragItem null || !SelfNode.DragTarget) return; SelfNode.DragTarget false; SelfNode.SetParentTargetContainer(); if (SelfNode.DragTargetBottom) TreeComponent.DragItem.DragMoveDown(SelfNode); // 插到目标下方 else TreeComponent.DragItem.DragMoveInto(SelfNode); // 放入目标内部 if (TreeComponent.OnDrop.HasDelegate) TreeComponent.OnDrop.InvokeAsync(new TreeEventArgsTItem( TreeComponent, TreeComponent.DragItem, e, SelfNode.DragTargetBottom) { TargetNode SelfNode }); }两种移动的内部实现见 TreeNode.razor.csDragMoveInto(treeNode)先从原父节点Remove()再treeNode.AddChildNode(DataItem)挂到目标下并将目标标记为非叶子treeNode.IsLeaf false后自动展开DragMoveDown(treeNode)先Remove()再treeNode.AddNextNode(DataItem)插到目标节点的下一个兄弟位置。落点事件参数TreeEventArgsTItemTreeEventArgs.cs携带完整上下文Node被拖节点、TargetNode落点节点、DropBelow是否落在下方、Tree组件实例以及OriginalEvent原始MouseEventArgs。开发者可在OnDrop回调中读取这些信息做后续业务处理如持久化新的父子关系示例中的onDrop(TreeEventArgsData e)即该回调的占位实现。拖放结束后OnDragEndTreeNodeTitle.razor.cs在可拖节点上触发源码注释特别提醒该回调必须设置见 Tree.razor.cs用于清理拖动产生的临时状态。与此相关的完整事件家族为OnDragStart、OnDragEnter、OnDragLeave、OnDrop、OnDragEndTree.razor.cs。六、实战要点与边界提醒表达式是纯判定、不负责数据变更DraggableExpression/DroppableExpression只决定能否拖 / 能否放实际的数据移动由组件内部完成。若移动后还需同步后端请在OnDrop中依据e.Node、e.TargetNode、e.DropBelow自行处理持久化。注意dropBelow的两种语义true 插到目标节点下方兄弟关系false 放入目标节点内部父子关系。写规则时建议同时覆盖两个方向否则会出现拖得动但放不下或放得下但方向不符合预期的现象。根节点与顶层约束示例用dropNode.GetParentNode() null判断根节点。由于GetParentNode()返回null表示该节点位于树顶层可用于禁止把节点拖到顶层或禁止根节点接收子节点。判定时机频繁DroppableExpression在dragenter/dragover期间会被高频调用见 OnDragEnter 与 OnDragOver表达式应保持轻量避免在其中执行耗时逻辑。与其它节点参数协同Draggable属性与DraggableExpression是与关系TreeNode.razor.cs表达式之外节点本身的Disabled等状态也会影响交互组合使用时注意叠加效果。七、结语DraggableExpression与DroppableExpression把 HTML5 原生拖放抽象成了两个可读性极强的判定函数一个回答这个节点能不能被拖走一个回答拖到这儿、以这种方位落下去行不行。配合OnDrop回调与TreeEventArgsTItem的事件上下文ant-design-blazor 的Tree组件可以轻松承载组织架构调整、菜单排序、文件归类等需要严格约束拖放边界的业务。官方完整示例可直接参考 Droppable.razor对应的基础拖放示例见 Draggable.razor组件实现细节可在 components/tree 目录下继续深挖。赞分享UI组件前端【免费下载链接】ant-design-blazorA rich set of enterprise-class UI components based on Ant Design and Blazor.项目地址https://gitcode.com/gh_mirrors/an/ant-design-blazor点击查看免费下载相关推荐ant-design-blazor 树组件拖放精确控制DraggableExpression 与 DroppableExpression 实战解析ant design blazor 树组件拖放精确控制DraggableExpression 与 DroppableExpression 实战解析 本篇指南基前端UI组件设计系统Ant Design Blazor Segmented 分段控制器组件实战指南Ant Design Blazor Segmented 分段控制器组件实战指南 本文以 ant design blazor 开源仓库中的 Segmented分UI组件前端Ant Design Blazor 树形控件增强实现节点级选中状态绑定Ant Design Blazor 树形控件增强实现节点级选中状态绑定 在 Ant Design Blazor 组件库的树形控件开发中开发者 GoldSuc前端UI组件设计系统上一篇Cherry Studio 文件系统重构FileMetadata 消费者全量审计与 FileEntry 迁移路线图下一篇CANN PTO-ISA 向量幂运算指令 TPOW语法、算法、约束与跨平台实现解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考