C# UWP 如何获取N级可变深度嵌套ListView中元素的各级父集合索引
解决方案
1. 统一数据模型约定
首先给嵌套条目类增加父子关联属性,后续所有索引查找逻辑都基于该模型实现:
// 标记条目所属的父集合类型 public enum ParentCollectionType { Root, Children, Friends } public class NestItem : INotifyPropertyChanged { // 你提前分配的唯一ID public Guid Id { get; set; } // 父级条目ID,根节点设为null public Guid? ParentId { get; set; } // 当前条目属于父级的哪个集合 public ParentCollectionType ParentCollection { get; set; } // 业务所需的子集合 public ObservableCollection<NestItem> Children { get; set; } = new(); public ObservableCollection<NestItem> Friends { get; set; } = new(); // 其他自定义业务属性 }
ListView的ItemSource对应根集合:ObservableCollection<NestItem> RootItems { get; set; }
2. 拖拽事件中获取基础信息
WPF/MAUI/WinUI等主流框架的拖拽事件都可以拿到放置目标的绑定上下文,以及插入位置信息:
private void OnDrop(object sender, DragEventArgs e) { if (!e.Data.GetDataPresent(typeof(NestItem))) return; var draggedItem = e.Data.GetData(typeof(NestItem)) as NestItem; var targetUiElement = sender as FrameworkElement; // 拿到要放入的父级条目实例 var targetParentItem = targetUiElement.DataContext as NestItem; // 标记要放入父级的Children还是Friends集合,可通过UI元素的Tag/附加属性区分 var targetCollection = targetUiElement.Tag.ToString() == "Children" ? ParentCollectionType.Children : ParentCollectionType.Friends; // 获取插入位置索引,ListView可直接用ItemContainerGenerator.IndexFromContainer拿到 int insertIndex = GetInsertIndex(e.GetPosition(targetUiElement), targetUiElement); // 生成完整索引路径 var indexPath = GetFullIndexPath(targetParentItem, targetCollection, RootItems); // 按路径插入元素 InsertItemByPath(RootItems, indexPath, draggedItem, insertIndex); }
3. 生成全层级索引路径
用迭代方式从目标父级往上遍历,自动收集所有上层索引,无需提前知道嵌套深度:
// 索引路径节点,记录每一层的所属集合和对应索引 public class IndexPathNode { public ParentCollectionType CollectionType { get; set; } public int Index { get; set; } } /// <summary> /// 生成从根节点到目标集合的完整索引路径 /// </summary> public List<IndexPathNode> GetFullIndexPath(NestItem targetParentItem, ParentCollectionType targetCollection, ObservableCollection<NestItem> rootItems) { var path = new List<IndexPathNode>(); // 先添加目标集合节点 path.Add(new IndexPathNode { CollectionType = targetCollection }); var currentItem = targetParentItem; while (currentItem != null) { int currentIndex = -1; if (currentItem.ParentId == null) { // 已遍历到根层级 currentIndex = rootItems.IndexOf(currentItem); path.Insert(0, new IndexPathNode { CollectionType = ParentCollectionType.Root, Index = currentIndex }); break; } // 查找父级条目(数据量大的话建议全局维护Dictionary<Guid, NestItem>缓存,无需递归遍历) var parentItem = FindItemById(rootItems, currentItem.ParentId.Value); // 查找当前条目在父集合中的索引 currentIndex = currentItem.ParentCollection switch { ParentCollectionType.Children => parentItem.Children.IndexOf(currentItem), ParentCollectionType.Friends => parentItem.Friends.IndexOf(currentItem), _ => -1 }; // 插入到路径头部 path.Insert(0, new IndexPathNode { CollectionType = currentItem.ParentCollection, Index = currentIndex }); currentItem = parentItem; } return path; } // 辅助方法:按ID递归查找条目 public NestItem FindItemById(ObservableCollection<NestItem> items, Guid id) { foreach (var item in items) { if (item.Id == id) return item; var childRes = FindItemById(item.Children, id); if (childRes != null) return childRes; var friendRes = FindItemById(item.Friends, id); if (friendRes != null) return friendRes; } return null; }
4. 按索引路径插入元素
遍历路径定位到目标集合,完成插入操作,同时更新拖拽元素的父级关联属性:
/// <summary> /// 按索引路径将拖拽元素插入到正确位置 /// </summary> public void InsertItemByPath(ObservableCollection<NestItem> rootItems, List<IndexPathNode> path, NestItem draggedItem, int insertIndex) { // 先移除元素原来的位置 RemoveItemFromOriginalPosition(rootItems, draggedItem); // 插入到根集合的特殊情况 if (path.Count == 1 && path[0].CollectionType == ParentCollectionType.Root) { rootItems.Insert(insertIndex, draggedItem); draggedItem.ParentId = null; draggedItem.ParentCollection = ParentCollectionType.Root; return; } object currentCollection = rootItems; // 遍历到倒数第二个节点,最后一个节点为要插入的目标集合 for (int i = 0; i < path.Count - 1; i++) { var node = path[i]; var currentItem = ((ObservableCollection<NestItem>)currentCollection)[node.Index]; if (i == path.Count - 2) { // 到达目标集合的父级,执行插入 var targetCollection = path[i + 1].CollectionType; if (targetCollection == ParentCollectionType.Children) { currentItem.Children.Insert(insertIndex, draggedItem); draggedItem.ParentId = currentItem.Id; draggedItem.ParentCollection = ParentCollectionType.Children; } else { currentItem.Friends.Insert(insertIndex, draggedItem); draggedItem.ParentId = currentItem.Id; draggedItem.ParentCollection = ParentCollectionType.Friends; } break; } // 切换到下一级集合继续遍历 currentCollection = path[i + 1].CollectionType switch { ParentCollectionType.Children => currentItem.Children, ParentCollectionType.Friends => currentItem.Friends, _ => currentCollection }; } } // 辅助方法:移除元素的原有引用 private void RemoveItemFromOriginalPosition(ObservableCollection<NestItem> rootItems, NestItem item) { if (item.ParentId == null) { rootItems.Remove(item); return; } var parent = FindItemById(rootItems, item.ParentId.Value); if (item.ParentCollection == ParentCollectionType.Children) parent.Children.Remove(item); else parent.Friends.Remove(item); }
内容的提问来源于stack exchange,提问作者Qaiser
相关产品推荐
相关产品推荐

