fix: 别名树深度超限节点导航 — 改用 ModelItem.Parent 链替代 IndexPath

- 移除 IndexPath 解析/偏移量等易错逻辑
- ExpandAndSelect 改为沿 ModelItem.Parent 链回溯,ModelItem.Equals 匹配
- 深层节点通过 BuildNode 直接创建,不依赖占位/懒加载通道
- 删除 LoadPathChild、EnsureChildLoaded 等废弃方法
- 清理约 10 行净代码
This commit is contained in:
tian 2026-05-31 22:30:54 +08:00
parent ea49774f39
commit f7544a2095

View File

@ -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;
}
/// <summary>
/// 只加载指定索引的子节点(不全量加载,用于 ExpandAndSelect 加速)
/// </summary>
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}"); }
}
/// <summary>
/// 懒加载子节点(在 Expanded 事件中触发)
/// </summary>
@ -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<Autodesk.Navisworks.Api.ModelItem>();
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<AliasNodeViewModel>;
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);
}
/// <summary>
/// 展开树节点(程序化,不触发 Expanded 事件)
/// </summary>
private void ExpandTreeNode(AliasNodeViewModel node)
{
var tvi = GetTreeViewItemForNode(node);
if (tvi != null)
{
_isProgrammaticExpand = true;
tvi.IsExpanded = true;
_isProgrammaticExpand = false;
tvi.UpdateLayout();
}
}
/// <summary>
/// 选中并滚动到指定树节点
/// </summary>
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();
}
}
/// <summary>