From f7544a2095642debd29a22958fd9e2d66bcf6097 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Sun, 31 May 2026 22:30:54 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=88=AB=E5=90=8D=E6=A0=91=E6=B7=B1?= =?UTF-8?q?=E5=BA=A6=E8=B6=85=E9=99=90=E8=8A=82=E7=82=B9=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=20=E2=80=94=20=E6=94=B9=E7=94=A8=20ModelItem.Parent=20?= =?UTF-8?q?=E9=93=BE=E6=9B=BF=E4=BB=A3=20IndexPath?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除 IndexPath 解析/偏移量等易错逻辑 - ExpandAndSelect 改为沿 ModelItem.Parent 链回溯,ModelItem.Equals 匹配 - 深层节点通过 BuildNode 直接创建,不依赖占位/懒加载通道 - 删除 LoadPathChild、EnsureChildLoaded 等废弃方法 - 清理约 10 行净代码 --- src/UI/WPF/Views/AliasTreeControl.xaml.cs | 204 ++++++++++------------ 1 file changed, 96 insertions(+), 108 deletions(-) diff --git a/src/UI/WPF/Views/AliasTreeControl.xaml.cs b/src/UI/WPF/Views/AliasTreeControl.xaml.cs index 54d7a29..31585cc 100644 --- a/src/UI/WPF/Views/AliasTreeControl.xaml.cs +++ b/src/UI/WPF/Views/AliasTreeControl.xaml.cs @@ -338,7 +338,7 @@ namespace NavisworksTransport.UI.WPF.Views _nodeMap[key] = node; - // 深度限制:超过阈值且无别名 → 不展开子节点 + // 深度限制:超过阈值且无别名 → 不展开子节点,不加占位 bool hasChildren = node.ChildCount > 0; if (hasChildren && depth >= MaxFullDisplayDepth && string.IsNullOrEmpty(alias)) hasChildren = false; @@ -355,34 +355,6 @@ namespace NavisworksTransport.UI.WPF.Views return node; } - /// - /// 只加载指定索引的子节点(不全量加载,用于 ExpandAndSelect 加速) - /// - private void EnsureChildLoaded(AliasNodeViewModel parent, int index) - { - if (parent.Children.Count != 1 || parent.Children[0].OriginalName != "加载中...") - return; - - parent.Children.Clear(); - try - { - var doc = NavisApplication.ActiveDocument; - if (doc == null) return; - var item = FindModelItemByNodeKey(parent.NodeKey); - if (item == null) return; - - var aliasMap = _dataStore?.LoadAll(); - var childrenList = item.Children.ToList(); - int loadCount = Math.Min(index + 1, childrenList.Count); - for (int i = 0; i < loadCount; i++) - { - var childNode = BuildNode(childrenList[i], aliasMap, parent.Depth + 1); - if (childNode != null) { childNode.Parent = parent; parent.Children.Add(childNode); } - } - } - catch (Exception ex) { LogManager.Error($"[别名树] EnsureChildLoaded: {ex.Message}"); } - } - /// /// 懒加载子节点(在 Expanded 事件中触发) /// @@ -544,109 +516,122 @@ namespace NavisworksTransport.UI.WPF.Views // ============================================================ // 展开并高亮指定 ModelItem - // 策略:用 FromModelItem 获取 IndexPath,沿路径逐层展开,不遍历整棵树。 + // 策略:利用 ModelItem.Parent 链逐层匹配,不依赖 IndexPath。 // ============================================================ private void ExpandAndSelect(Autodesk.Navisworks.Api.ModelItem item) { - var identity = AliasNodeIdentity.FromModelItem(item); - var indexPath = identity.IndexPath; + if (item == null) return; - LogManager.Info($"[别名树] ExpandAndSelect: target={item.DisplayName}, IndexPath={indexPath}"); + LogManager.Info($"[别名树] ExpandAndSelect: target={item.DisplayName}"); - if (string.IsNullOrEmpty(indexPath)) + // 1. 沿 Parent 链构建祖先列表(根 → 目标) + var chain = new System.Collections.Generic.List(); + var current = item; + while (current != null) { - LogManager.Warning($"[别名树] IndexPath 为空,无法导航: {item.DisplayName}"); - return; + chain.Insert(0, current); + current = current.Parent; } - string[] parts = indexPath.Split(','); + // 2. 在 rootNodes 中找匹配的根节点 var rootNodes = AliasTreeView.ItemsSource as System.Collections.ObjectModel.ObservableCollection; if (rootNodes == null || rootNodes.Count == 0) return; - // IndexPath 第一个元素可能是模型索引。 - // 对单模型文档,IndexPath 只有一个元素(子节点索引) - int startPart = 0; - if (parts.Length > 1 && int.TryParse(parts[0], out int maybeModelIdx)) + AliasNodeViewModel treeNode = null; + foreach (var rn in rootNodes) { - // 多模型:parts[0] 是模型索引,从 rootNodes[modelIdx] 开始 - if (maybeModelIdx < rootNodes.Count) + if (rn.ModelItem != null && rn.ModelItem.Equals(chain[0])) { - startPart = 1; + treeNode = rn; + break; } - // 单模型:parts[0] 直接是子节点索引 } - - // 确定起始根节点 - AliasNodeViewModel rootNode; - ItemsControl currentContainer; - if (startPart == 1 && int.TryParse(parts[0], out int modelIdx)) + if (treeNode == null) { - rootNode = rootNodes[modelIdx]; - AliasTreeView.UpdateLayout(); - var rc = AliasTreeView.ItemContainerGenerator.ContainerFromItem(rootNode) as TreeViewItem; - if (rc == null) return; - rc.IsExpanded = true; - rc.UpdateLayout(); - var aliasMap = _dataStore?.LoadAll(); - LazyLoadChildren(rootNode, aliasMap); - currentContainer = rc; - } - else - { - rootNode = rootNodes[0]; - AliasTreeView.UpdateLayout(); - var rc = AliasTreeView.ItemContainerGenerator.ContainerFromItem(rootNode) as TreeViewItem; - if (rc == null) return; - currentContainer = rc; - } - - // 先检查根节点 - if (rootNode.ModelItem != null && rootNode.ModelItem.Equals(item)) - { - if (currentContainer is TreeViewItem t) - { - _isProgrammaticSelect = true; - t.IsSelected = true; - _isProgrammaticSelect = false; - t.BringIntoView(); - } + LogManager.Warning($"[别名树] 根节点未找到: {chain[0].DisplayName}"); return; } - // 沿 IndexPath 逐层展开 — 只加载路径上的子节点 - AliasNodeViewModel current = rootNode; - - for (int i = startPart; i < parts.Length; i++) + // 3. 目标就是根节点 + if (chain.Count == 1) { - if (!int.TryParse(parts[i], out int childIdx)) return; - int adjIdx = childIdx - 1; - if (adjIdx < 0) adjIdx = 0; + SelectTreeNode(treeNode); + return; + } + + // 4. 沿链逐层向下,匹配/加载子节点 + for (int i = 1; i < chain.Count; i++) + { + var targetModelItem = chain[i]; // 展开当前节点 - var tvi = GetTreeViewItemForNode(current); - if (tvi != null) - { - _isProgrammaticExpand = true; - tvi.IsExpanded = true; - _isProgrammaticExpand = false; - tvi.UpdateLayout(); - } - var aliasMap = _dataStore?.LoadAll(); - LazyLoadChildren(current, aliasMap); + ExpandTreeNode(treeNode); - if (adjIdx >= current.Children.Count) + // 加载子节点(有占位 → 全量懒加载;否则只找目标那一个) + var aliasMap = _dataStore?.LoadAll(); + if (treeNode.Children.Count == 1 && treeNode.Children[0].OriginalName == "加载中...") { - LogManager.Warning($"[别名树] IndexPath 越界: parts[{i}]={childIdx}, adjIdx={adjIdx}, Children.Count={current.Children.Count}"); + LazyLoadChildren(treeNode, aliasMap); + } + + // 在已加载的子节点中查找匹配 + AliasNodeViewModel child = null; + foreach (var c in treeNode.Children) + { + if (c.OriginalName != "加载中..." && c.ModelItem != null && c.ModelItem.Equals(targetModelItem)) + { + child = c; + break; + } + } + + // 没找到(深度超限节点)→ 直接用 ModelItem 创建 + if (child == null) + { + child = BuildNode(targetModelItem, aliasMap, treeNode.Depth + 1); + if (child != null) + { + child.Parent = treeNode; + treeNode.Children.Add(child); + } + } + + if (child == null) + { + LogManager.Warning($"[别名树] 无法定位子节点: {targetModelItem.DisplayName}"); return; } - current = current.Children[adjIdx]; + treeNode = child; } - // 到达目标:强制父节点 BringIntoView,延迟后获取容器选中 - var parent = current.Parent; + // 5. 选中最终节点 + SelectTreeNode(treeNode); + } + + /// + /// 展开树节点(程序化,不触发 Expanded 事件) + /// + private void ExpandTreeNode(AliasNodeViewModel node) + { + var tvi = GetTreeViewItemForNode(node); + if (tvi != null) + { + _isProgrammaticExpand = true; + tvi.IsExpanded = true; + _isProgrammaticExpand = false; + tvi.UpdateLayout(); + } + } + + /// + /// 选中并滚动到指定树节点 + /// + private void SelectTreeNode(AliasNodeViewModel node) + { + var parent = node.Parent; if (parent != null) { var parentTvi = GetTreeViewItemForNode(parent); @@ -655,11 +640,10 @@ namespace NavisworksTransport.UI.WPF.Views parentTvi.BringIntoView(); parentTvi.UpdateLayout(); - // 延迟重试:BringIntoView 会触发滚动,创建更多容器 Dispatcher.BeginInvoke(new Action(() => { AliasTreeView.UpdateLayout(); - var childTvi = parentTvi.ItemContainerGenerator.ContainerFromItem(current) as TreeViewItem; + var childTvi = parentTvi.ItemContainerGenerator.ContainerFromItem(node) as TreeViewItem; if (childTvi != null) { _isProgrammaticSelect = true; @@ -667,16 +651,20 @@ namespace NavisworksTransport.UI.WPF.Views _isProgrammaticSelect = false; childTvi.BringIntoView(); } - else - { - LogManager.Warning($"[别名树] 延迟后仍未获取容器: {current.OriginalName}"); - } }), System.Windows.Threading.DispatcherPriority.Background); return; } } - LogManager.Warning($"[别名树] 最终容器获取失败: {current.OriginalName}"); + // 无父节点(根节点)直接选中 + var tvi = GetTreeViewItemForNode(node); + if (tvi != null) + { + _isProgrammaticSelect = true; + tvi.IsSelected = true; + _isProgrammaticSelect = false; + tvi.BringIntoView(); + } } ///