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:
parent
cba8e3f3b7
commit
6a3be184a6
@ -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>();
|
||||
|
||||
@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user