fix: crash from VisualTreeHelper.GetParent during rapid clicks

- OnTreeViewItemLeftClick: use e.OriginalSource.DataContext directly,
  not visual tree traversal (FindAncestor)
- _isProgrammaticExpand prevents Expanded event from triggering bulk
  LazyLoadChildren during ExpandAndSelect
- OnNavisSelectionChanged: synchronous processing with _isInternalSelection
  guard prevents feedback loop
- Try-catch wrappers on all COM API call sites (FromModelItem, BuildNode)
This commit is contained in:
tian 2026-05-31 18:12:34 +08:00
parent cba8e3f3b7
commit 6a3be184a6
2 changed files with 114 additions and 43 deletions

View File

@ -49,9 +49,22 @@ namespace NavisworksTransport.Core.AliasTree
if (item == null)
throw new ArgumentNullException(nameof(item));
// COM API 必须在 STA 主线程
if (System.Threading.Thread.CurrentThread.GetApartmentState() != System.Threading.ApartmentState.STA)
LogManager.Error($"[别名树] FromModelItem 不在 STA 线程!");
// 1. IndexPath — 复用 ModelItemAnalysisHelper
var indexPaths = ModelItemAnalysisHelper.GetModelItemIndexPaths(item);
string indexPath = indexPaths.FirstOrDefault() ?? string.Empty;
string indexPath;
try
{
var indexPaths = ModelItemAnalysisHelper.GetModelItemIndexPaths(item);
indexPath = indexPaths.FirstOrDefault() ?? string.Empty;
}
catch (Exception ex)
{
LogManager.Error($"[别名树] GetModelItemIndexPaths 异常: {ex.Message}");
indexPath = string.Empty;
}
// 2. DisplayPath — 父链 DisplayName + 同级索引
var parts = new List<string>();

View File

@ -172,6 +172,8 @@ namespace NavisworksTransport.UI.WPF.Views
_isInternalSelection = true;
try
{
_currentSelectionItem = node.ModelItem;
TbCurrentNode.Text = $"当前: {node.OriginalName}";
var doc = NavisApplication.ActiveDocument;
if (doc != null && node.ModelItem != null)
{
@ -185,7 +187,7 @@ namespace NavisworksTransport.UI.WPF.Views
}
catch (Exception ex)
{
LogManager.Error($"[别名树] NW选择异常: {ex.Message} {ex.StackTrace}");
LogManager.Error($"[别名树] NW选择异常: {ex.GetType().Name}");
}
}
}
@ -217,37 +219,34 @@ namespace NavisworksTransport.UI.WPF.Views
/// </summary>
private void OnNavisSelectionChanged(object sender, EventArgs e)
{
Dispatcher.BeginInvoke(new Action(() =>
if (_isInternalSelection) return;
try
{
try
var doc = NavisApplication.ActiveDocument;
if (doc == null) return;
var selected = doc.CurrentSelection.SelectedItems;
if (selected.Count == 1)
{
var doc = NavisApplication.ActiveDocument;
if (doc == null) return;
var item = selected.First();
_currentSelectionItem = item;
TbCurrentNode.Text = $"当前: {item.DisplayName}";
var selected = doc.CurrentSelection.SelectedItems;
if (selected.Count == 1)
{
var item = selected.First();
_currentSelectionItem = item;
TbCurrentNode.Text = $"当前: {item.DisplayName}";
LogManager.Info($"[别名树] NW选中: DisplayName={item.DisplayName}, ClassName={item.ClassName}");
LogManager.Info($"[别名树] NW选中: DisplayName={item.DisplayName}, ClassName={item.ClassName}");
// 内部触发时不展开(别名树点击引起的),只更新 _currentSelectionItem
if (!_isInternalSelection)
ExpandAndSelect(item);
}
else
{
_currentSelectionItem = null;
TbCurrentNode.Text = selected.Count > 1 ? $"当前: ({selected.Count} 个选中)" : "当前: 无";
}
ExpandAndSelect(item);
}
catch (Exception ex)
else
{
LogManager.Error($"[别名树] 选择变化处理失败: {ex.Message}");
_currentSelectionItem = null;
TbCurrentNode.Text = selected.Count > 1 ? $"当前: ({selected.Count} 个选中)" : "当前: 无";
}
}));
}
catch (Exception ex)
{
LogManager.Error($"[别名树] 选择变化处理异常: {ex.Message}");
}
}
// ============================================================
@ -306,7 +305,16 @@ namespace NavisworksTransport.UI.WPF.Views
{
if (modelItem == null) return null;
var identity = AliasNodeIdentity.FromModelItem(modelItem);
AliasNodeIdentity identity;
try
{
identity = AliasNodeIdentity.FromModelItem(modelItem);
}
catch (Exception ex)
{
LogManager.Error($"[别名树] BuildNode FromModelItem 异常: {ex.Message}");
return null;
}
string key = identity.ToKey();
string alias = null;
@ -347,6 +355,34 @@ 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>
@ -579,7 +615,7 @@ namespace NavisworksTransport.UI.WPF.Views
return;
}
// 沿 IndexPath 逐层展开 — 纯 ViewModel 导航,不依赖 TreeViewItem
// 沿 IndexPath 逐层展开 — 只加载路径上的子节点
AliasNodeViewModel current = rootNode;
for (int i = startPart; i < parts.Length; i++)
@ -588,11 +624,13 @@ namespace NavisworksTransport.UI.WPF.Views
int adjIdx = childIdx - 1;
if (adjIdx < 0) adjIdx = 0;
// 尝试获取当前节点 TreeViewItem 用于展开(没有就算了,继续)
// 展开当前节点
var tvi = GetTreeViewItemForNode(current);
if (tvi != null)
{
_isProgrammaticExpand = true;
tvi.IsExpanded = true;
_isProgrammaticExpand = false;
tvi.UpdateLayout();
}
var aliasMap = _dataStore?.LoadAll();
@ -604,8 +642,7 @@ namespace NavisworksTransport.UI.WPF.Views
return;
}
var child = current.Children[adjIdx];
current = child;
current = current.Children[adjIdx];
}
// 到达目标:强制父节点 BringIntoView延迟后获取容器选中
@ -1081,14 +1118,15 @@ namespace NavisworksTransport.UI.WPF.Views
private void OnTreeViewItemExpanded(object sender, RoutedEventArgs e)
{
if (_isProgrammaticExpand) return;
if (!(e.OriginalSource is TreeViewItem tvi) || !(tvi.DataContext is AliasNodeViewModel node))
return;
var aliasMap = _dataStore?.LoadAll();
LazyLoadChildren(node, aliasMap);
}
private bool _isProgrammaticSelect;
private bool _isProgrammaticExpand;
/// <summary>
/// 树节点被选中时:如果 500ms 内再次选中同一节点 → 进入内联编辑
@ -1098,21 +1136,32 @@ namespace NavisworksTransport.UI.WPF.Views
/// </summary>
private void OnTreeViewItemRightClick(object sender, MouseButtonEventArgs e)
{
var tvi = FindAncestor(e.OriginalSource as DependencyObject);
if (tvi != null)
try
{
tvi.IsSelected = true;
tvi.Focus();
var tvi = FindAncestor(e.OriginalSource as DependencyObject);
if (tvi != null)
{
tvi.IsSelected = true;
tvi.Focus();
}
}
catch (Exception ex)
{
LogManager.Error($"[别名树] RCK-异常: {ex.GetType().Name}");
}
}
private static TreeViewItem FindAncestor(DependencyObject current)
{
while (current != null)
try
{
if (current is TreeViewItem tvi) return tvi;
current = VisualTreeHelper.GetParent(current);
while (current != null)
{
if (current is TreeViewItem tvi) return tvi;
current = VisualTreeHelper.GetParent(current);
}
}
catch { }
return null;
}
@ -1121,10 +1170,19 @@ namespace NavisworksTransport.UI.WPF.Views
/// </summary>
private void OnTreeViewItemLeftClick(object sender, MouseButtonEventArgs e)
{
var tvi = FindAncestor(e.OriginalSource as DependencyObject);
if (tvi != null && tvi.IsSelected && tvi.DataContext is AliasNodeViewModel node && !node.IsOrphan)
try
{
StartInlineEdit(node);
// 从点击元素的 DataContext 获取节点,不遍历可视化树
if (e.OriginalSource is FrameworkElement fe && fe.DataContext is AliasNodeViewModel node && !node.IsOrphan)
{
var selNode = GetSelectedNode();
if (node == selNode)
StartInlineEdit(node);
}
}
catch (Exception ex)
{
LogManager.Error($"[别名树] LCK-异常: {ex.GetType().Name}");
}
}