using NavisworksTransport.Core.AliasTree;
using NavisworksTransport.Core.Config;
using NavisworksTransport.UI.WPF.ViewModels;
using NavisworksTransport.Utils;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using Autodesk.Navisworks.Api.DocumentParts;
using NavisApplication = Autodesk.Navisworks.Api.Application;
namespace NavisworksTransport.UI.WPF.Views
{
///
/// 别名导航树面板 — 核心 UI 逻辑
///
public partial class AliasTreeControl : UserControl
{
// ============================================================
// 状态
// ============================================================
private AliasDataStore _dataStore;
private string _copiedAliasTreeJson;
/// 当控件检测到模型就绪但数据存储未连接时触发
internal event Action OnDatabaseNeeded;
/// 保存别名前触发(确保数据库已创建)
internal event Action BeforeSaveAlias;
private Dictionary _nodeMap = new Dictionary();
/// 记录 ExpandAndSelect 按需加载但无别名的深层节点,下次选择时清理
private HashSet _onDemandNodeKeys = new HashSet();
private bool _isInternalSelection = false;
private Autodesk.Navisworks.Api.ModelItem _currentSelectionItem;
private bool _built = false;
/// 完整展示的最大层级深度(默认2层)
public int MaxFullDisplayDepth { get; set; } = 2;
// ============================================================
// 生命周期
// ============================================================
public AliasTreeControl()
{
InitializeComponent();
// 从配置文件读取初始值
MaxFullDisplayDepth = ConfigManager.Instance.Current.AliasTree?.MaxFullDisplayDepth ?? 2;
// 订阅配置变更,实时生效
ConfigManager.Instance.CategoryConfigurationChanged += OnAliasTreeConfigChanged;
// 将 AliasTreeView 的 items 源指向根节点集合
var rootNodes = new System.Collections.ObjectModel.ObservableCollection();
AliasTreeView.ItemsSource = rootNodes;
// 文档切换事件
NavisApplication.ActiveDocumentChanged += (s, e) => { _built = false; RebuildTree(); };
NavisApplication.ActiveDocumentChanging += (s, e) => { _built = false; };
// 模型加载事件:面板启动时文档已存在但模型未加载时触发
var doc = NavisApplication.ActiveDocument;
if (doc != null)
doc.Models.CollectionChanged += (s, e) =>
{
_built = false;
RebuildTree();
OnDatabaseNeeded?.Invoke();
};
HookSelectionEvents();
// 立即构建:面板打开时如果文档已存在,不走事件也要构建
RebuildTree();
}
///
/// 获取当前数据存储(null 表示未连接)
///
public AliasDataStore GetDataStore() => _dataStore;
///
/// 设置数据存储(由外部在 PathDatabase 就绪后调用)
///
public void SetDataStore(AliasDataStore store)
{
_dataStore = store;
LogManager.Info($"[别名树] SetDataStore: _built={_built}, aliasCount={store?.Count}");
if (!_built)
RebuildTree();
else
RefreshAliasesFromStore();
// 无论树是否已构建,确保已存别名的深层节点路径被加载
var aliasMap = _dataStore?.LoadAll();
if (aliasMap != null && aliasMap.Count > 0)
EnsureAliasedPathsVisible(aliasMap);
// EnsureAliasedPathsVisible 可能创建了新节点,需要再刷一次别名到这些节点上
if (_built)
RefreshAliasesFromStore();
}
private void RefreshAliasesFromStore()
{
if (_dataStore == null) return;
try
{
var aliasMap = _dataStore.LoadAll();
LogManager.Info($"[别名树] RefreshAliases: loaded={aliasMap.Count}, nodeMap={_nodeMap.Count}");
int matched = 0;
foreach (var kvp in aliasMap)
{
int sepIdx = kvp.Key.IndexOf("||", StringComparison.Ordinal);
string lookupKey = sepIdx >= 0 ? kvp.Key.Substring(sepIdx + 2) : kvp.Key;
if (_nodeMap.TryGetValue(lookupKey, out var node))
{
try
{
node.Alias = kvp.Value;
matched++;
}
catch (Exception ex)
{
LogManager.Error($"[别名树] RefreshAliases set error: key={kvp.Key}, {ex.Message}");
}
}
}
LogManager.Info($"[别名树] RefreshAliases: matched={matched}");
}
catch (Exception ex)
{
LogManager.Error($"[别名树] RefreshAliases 异常: {ex.Message}");
}
}
///
/// 清理资源
///
public void Cleanup()
{
ConfigManager.Instance.CategoryConfigurationChanged -= OnAliasTreeConfigChanged;
UnhookSelectionEvents();
_dataStore = null;
_nodeMap.Clear();
}
private void OnAliasTreeConfigChanged(object sender, Tuple args)
{
var config = ConfigManager.Instance.Current;
int newDepth = config.AliasTree?.MaxFullDisplayDepth ?? 2;
if (MaxFullDisplayDepth != newDepth)
{
MaxFullDisplayDepth = newDepth;
LogManager.Info($"[别名树] 配置变更: MaxFullDisplayDepth={newDepth}");
_built = false;
RebuildTree();
}
}
// ============================================================
// 选择事件(双向联动)
// ============================================================
private bool _selectionHooked = false;
private void HookSelectionEvents()
{
if (_selectionHooked) return;
try
{
var doc = NavisApplication.ActiveDocument;
if (doc != null)
{
doc.CurrentSelection.Changed += OnNavisSelectionChanged;
_selectionHooked = true;
}
}
catch (Exception ex) { LogManager.Debug($"[别名树] HookSelection事件异常: {ex.Message}"); }
}
private void UnhookSelectionEvents()
{
try
{
var doc = NavisApplication.ActiveDocument;
if (doc != null)
{
doc.CurrentSelection.Changed -= OnNavisSelectionChanged;
}
}
catch (Exception ex) { LogManager.Debug($"[别名树] UnhookSelection异常: {ex.Message}"); }
_selectionHooked = false;
}
///
/// 别名树选中 → 同步到 Navisworks 选择
///
private void OnTreeViewSelected(AliasNodeViewModel node)
{
// 由 NW 选择触发的 ExpandAndSelect 过程中的被动选中事件,不反向同步
if (_isInternalSelection) return;
LogManager.Debug($"[别名树] OnTreeViewSelected: {node.OriginalName}");
_isInternalSelection = true;
try
{
_currentSelectionItem = node.ModelItem;
TbCurrentNode.Text = $"当前: {node.OriginalName}";
var doc = NavisApplication.ActiveDocument;
if (doc != null && doc.CurrentSelection != null && node.ModelItem != null)
{
var coll = new Autodesk.Navisworks.Api.ModelItemCollection { node.ModelItem };
try
{
doc.CurrentSelection.Clear();
doc.CurrentSelection.CopyFrom(coll);
}
catch (NullReferenceException)
{
// NW API 内部 NRE(文档过渡状态),忽略
}
catch (Exception ex)
{
LogManager.Error($"[别名树] NW选择异常: {ex.GetType().Name}");
}
}
}
catch (Exception ex)
{
LogManager.Error($"[别名树] 选中同步异常: {ex.Message}");
}
finally
{
_isInternalSelection = false;
}
}
private void StartInlineEdit(AliasNodeViewModel node, TreeViewItem tvi)
{
// 直接用,不需要 tvi
StartInlineEdit(node);
}
private void StartInlineEdit(AliasNodeViewModel node)
{
ClearAllEditing();
node.IsEditing = true;
}
///
/// Navisworks 选择变化 → 同步到别名树高亮
///
private void OnNavisSelectionChanged(object sender, EventArgs e)
{
if (_isInternalSelection) return;
try
{
var doc = NavisApplication.ActiveDocument;
if (doc == null) return;
var selected = doc.CurrentSelection?.SelectedItems;
if (selected.Count == 1)
{
var item = selected.First();
_currentSelectionItem = item;
TbCurrentNode.Text = $"当前: {item.DisplayName}";
LogManager.Debug($"[别名树] NW选中: {item.DisplayName}");
ExpandAndSelect(item);
}
else
{
_currentSelectionItem = null;
TbCurrentNode.Text = selected.Count > 1 ? $"当前: ({selected.Count} 个选中)" : "当前: 无";
}
}
catch (Exception ex)
{
LogManager.Error($"[别名树] 选择变化处理异常: {ex.Message}");
}
}
// ============================================================
// 树构建
// ============================================================
private void RebuildTree()
{
if (_built) return;
_built = true;
try
{
var rootNodes = AliasTreeView.ItemsSource as System.Collections.ObjectModel.ObservableCollection;
if (rootNodes == null) return;
rootNodes.Clear();
_nodeMap.Clear();
var doc = NavisApplication.ActiveDocument;
if (doc == null || doc.Models == null) return;
// 全量加载别名到内存
Dictionary aliasMap = null;
Dictionary displayPathToAlias = null;
if (_dataStore != null)
{
aliasMap = _dataStore.LoadAll();
displayPathToAlias = BuildDisplayPathAliasMap(aliasMap);
}
// 遍历所有 root items
foreach (var model in doc.Models)
{
var rootItem = model.RootItem;
if (rootItem != null)
{
var node = BuildNode(rootItem, aliasMap, displayPathToAlias, 0, "", 0);
if (node != null)
rootNodes.Add(node);
}
}
// 重新挂载选择事件(可能文档刚切换)
HookSelectionEvents();
// 对已有别名的深层节点,强制加载其路径使其可见
if (aliasMap != null && aliasMap.Count > 0)
EnsureAliasedPathsVisible(aliasMap);
LogManager.Info($"[别名树] 树构建完成,共 {_nodeMap.Count} 个节点");
}
catch (Exception ex)
{
LogManager.Error($"[别名树] 构建失败: {ex.Message}");
}
}
///
/// 递归构建节点树(主线程调用)。
/// siblingIndex 用于同名兄弟去重,0 表示第一个(不追加后缀)。
///
private AliasNodeViewModel BuildNode(Autodesk.Navisworks.Api.ModelItem modelItem,
Dictionary aliasMap,
Dictionary displayPathToAlias,
int depth, string parentDisplayPath, int siblingIndex)
{
if (modelItem == null) return null;
string displayName = !string.IsNullOrEmpty(modelItem.DisplayName) ? modelItem.DisplayName : "几何对象";
// 父节点 key 可能是完整格式 "IndexPath||DisplayPath",也可能是 ".../name#N"
// 只取纯 DisplayPath 部分(去掉 || 前缀和结尾的 #N 后缀)
string parentPath = parentDisplayPath;
int sepIdx = parentPath.IndexOf("||", StringComparison.Ordinal);
if (sepIdx >= 0) parentPath = parentPath.Substring(sepIdx + 2);
string displayPath = string.IsNullOrEmpty(parentPath)
? displayName
: parentPath + "/" + displayName;
// 同名兄弟去重:第二个起追加 #索引
string displayPathKey = siblingIndex > 0 ? displayPath + "#" + siblingIndex : displayPath;
// 别名匹配:精确匹配 displayPathKey
string alias = null;
displayPathToAlias?.TryGetValue(displayPathKey, out alias);
var node = new AliasNodeViewModel
{
NodeKey = displayPathKey,
ModelItem = modelItem,
OriginalName = displayName,
Alias = alias,
ChildCount = modelItem.Children.Count(),
Depth = depth
};
_nodeMap[displayPathKey] = node;
// 深度限制:超过阈值且无别名 → 不展开子节点,不加占位
bool hasChildren = node.ChildCount > 0;
if (hasChildren && depth >= MaxFullDisplayDepth && string.IsNullOrEmpty(alias))
hasChildren = false;
if (hasChildren)
{
node.Children.Add(new AliasNodeViewModel
{
DisplayName = "加载中...",
OriginalName = "加载中..."
});
}
return node;
}
private void LazyLoadChildren(AliasNodeViewModel parentNode, Dictionary aliasMap,
Dictionary displayPathToAlias)
{
// 检查是否只有占位节点
if (parentNode.Children.Count != 1 || parentNode.Children[0].OriginalName != "加载中...")
return;
parentNode.Children.Clear();
try
{
var item = parentNode.ModelItem;
if (item == null) return;
// 同名去重:统计每个名字已出现的次数
var nameCount = new Dictionary();
foreach (var childItem in item.Children)
{
string name = !string.IsNullOrEmpty(childItem.DisplayName) ? childItem.DisplayName : "几何对象";
nameCount.TryGetValue(name, out int count);
int siblingIdx = count;
nameCount[name] = count + 1;
var childNode = BuildNode(childItem, aliasMap, displayPathToAlias,
parentNode.Depth + 1, parentNode.NodeKey, siblingIdx);
if (childNode != null)
{
childNode.Parent = parentNode;
parentNode.Children.Add(childNode);
}
}
}
catch (Exception ex)
{
LogManager.Error($"[别名树] 懒加载子节点失败: {ex.Message}");
}
}
// ============================================================
///
/// 从 aliasMap 构建 DisplayPath → alias 索引(去掉 IndexPath|| 前缀)
///
private static Dictionary BuildDisplayPathAliasMap(Dictionary aliasMap)
{
if (aliasMap == null || aliasMap.Count == 0) return null;
var map = new Dictionary();
foreach (var kvp in aliasMap)
{
// key 格式: "IndexPath||DisplayPath" → 取 DisplayPath 部分
int sepIdx = kvp.Key.IndexOf("||", StringComparison.Ordinal);
string displayPath = sepIdx >= 0 ? kvp.Key.Substring(sepIdx + 2) : kvp.Key;
map[displayPath] = kvp.Value;
}
return map;
}
// ============================================================
///
/// 确保节点有完整 NodeKey(含 IndexPath)。首次保存别名时调用 CreatePathId 升级 key。
///
private void EnsureFullNodeKey(AliasNodeViewModel node)
{
if (node == null || node.ModelItem == null) return;
// 已是完整格式
if (node.NodeKey != null && node.NodeKey.Contains("||")) return;
try
{
var identity = AliasNodeIdentity.FromModelItem(node.ModelItem);
node.NodeKey = identity.ToKey();
}
catch (Exception ex)
{
LogManager.Error($"[别名树] EnsureFullNodeKey 失败: {ex.Message}");
}
}
private Autodesk.Navisworks.Api.ModelItem FindModelItemByNodeKey(string key)
{
if (string.IsNullOrEmpty(key)) return null;
// 判断 key 格式:含 "||" 为完整格式 → ResolvePathId;否则为 DisplayPath → _nodeMap 查找
int sepIdx = key.IndexOf("||", StringComparison.Ordinal);
if (sepIdx >= 0)
{
if (!AliasNodeIdentity.TryParseKey(key, out var identity) || string.IsNullOrEmpty(identity.IndexPath))
return null;
return ResolveByIndexPath(identity.IndexPath);
}
// 简化格式:DisplayPath → 查 _nodeMap
if (_nodeMap.TryGetValue(key, out var node) && node.ModelItem != null)
return node.ModelItem;
return null;
}
private Autodesk.Navisworks.Api.ModelItem ResolveByIndexPath(string indexPath)
{
try
{
var doc = NavisApplication.ActiveDocument;
if (doc == null || doc.Models == null) return null;
int colonIdx = indexPath.IndexOf(':');
if (colonIdx < 0) return null;
if (!int.TryParse(indexPath.Substring(0, colonIdx), out int modelIndex))
return null;
string pathId = indexPath.Substring(colonIdx + 1);
return doc.Models.ResolvePathId(new ModelItemPathId { ModelIndex = modelIndex, PathId = pathId });
}
catch
{
return null;
}
}
// ============================================================
// 展开并高亮指定 ModelItem
// 策略:利用 ModelItem.Parent 链逐层匹配,不依赖 IndexPath。
// ============================================================
private void ExpandAndSelect(Autodesk.Navisworks.Api.ModelItem item)
{
if (item == null) return;
LogManager.Info($"[别名树] ExpandAndSelect: target={item.DisplayName}");
// 整个 ExpandAndSelect 期间阻止树→NW 反向联动
_isInternalSelection = true;
try
{
ExpandAndSelectInternal(item);
}
finally
{
_isInternalSelection = false;
}
}
private void ExpandAndSelectInternal(Autodesk.Navisworks.Api.ModelItem item)
{
// 0. 清理上次按需加载的无别名深层节点
CleanupOnDemandNodes();
// 1. 沿 Parent 链构建祖先列表(根 → 目标)
var chain = new System.Collections.Generic.List();
var current = item;
while (current != null)
{
chain.Insert(0, current);
current = current.Parent;
}
// 2. 在 rootNodes 中找匹配的根节点
var rootNodes = AliasTreeView.ItemsSource
as System.Collections.ObjectModel.ObservableCollection;
if (rootNodes == null || rootNodes.Count == 0) return;
AliasNodeViewModel treeNode = null;
foreach (var rn in rootNodes)
{
if (rn.ModelItem != null && rn.ModelItem.Equals(chain[0]))
{
treeNode = rn;
break;
}
}
if (treeNode == null)
{
LogManager.Warning($"[别名树] 根节点未找到: {chain[0].DisplayName}");
return;
}
// 3. 目标就是根节点
if (chain.Count == 1)
{
SelectTreeNode(treeNode);
return;
}
// 4. 沿链逐层向下,匹配/加载子节点
for (int i = 1; i < chain.Count; i++)
{
var targetModelItem = chain[i];
// 展开当前节点
ExpandTreeNode(treeNode);
// 加载子节点(有占位 → 全量懒加载;否则只找目标那一个)
var aliasMap = _dataStore?.LoadAll();
var displayAlias = BuildDisplayPathAliasMap(aliasMap);
if (treeNode.Children.Count == 1 && treeNode.Children[0].OriginalName == "加载中...")
{
LazyLoadChildren(treeNode, aliasMap, displayAlias);
}
// 在已加载的子节点中查找匹配
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, displayAlias,
treeNode.Depth + 1, treeNode.NodeKey, 0);
if (child != null)
{
child.Parent = treeNode;
treeNode.Children.Add(child);
// 超限且无别名 → 下次选择时清理
if (child.Depth >= MaxFullDisplayDepth && !child.HasAlias)
_onDemandNodeKeys.Add(child.NodeKey);
}
}
if (child == null)
{
LogManager.Warning($"[别名树] 无法定位子节点: {targetModelItem.DisplayName}");
return;
}
treeNode = child;
}
// 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);
if (parentTvi != null)
{
parentTvi.BringIntoView();
parentTvi.UpdateLayout();
Dispatcher.BeginInvoke(new Action(() =>
{
AliasTreeView.UpdateLayout();
var childTvi = parentTvi.ItemContainerGenerator.ContainerFromItem(node) as TreeViewItem;
if (childTvi != null)
{
_isProgrammaticSelect = true;
childTvi.IsSelected = true;
_isProgrammaticSelect = false;
childTvi.BringIntoView();
}
}), System.Windows.Threading.DispatcherPriority.Background);
return;
}
}
// 无父节点(根节点)直接选中
var tvi = GetTreeViewItemForNode(node);
if (tvi != null)
{
_isProgrammaticSelect = true;
tvi.IsSelected = true;
_isProgrammaticSelect = false;
tvi.BringIntoView();
}
}
///
/// 清理上次按需加载的、无别名的深层节点。
/// ExpandAndSelect 每次调用前执行,确保前一次超限无别名节点被移除。
///
private void CleanupOnDemandNodes()
{
if (_onDemandNodeKeys.Count == 0) return;
foreach (var key in _onDemandNodeKeys)
{
if (_nodeMap.TryGetValue(key, out var node))
{
node.Parent?.Children.Remove(node);
_nodeMap.Remove(key);
}
}
_onDemandNodeKeys.Clear();
}
///
/// 确保所有已存别名的节点路径在树中可见。
/// 在 RebuildTree 后调用,对深层别名节点逐级加载其祖先。
///
private void EnsureAliasedPathsVisible(Dictionary aliasMap)
{
LogManager.Info($"[别名树] EnsureAliasedPathsVisible: 共 {aliasMap.Count} 条别名");
foreach (var kvp in aliasMap)
{
LogManager.Debug($"[别名树] alias: key={kvp.Key} -> \"{kvp.Value}\"");
}
int loaded = 0;
foreach (var kvp in aliasMap)
{
try
{
LogManager.Debug($"[别名树] EnsureAliased: key={kvp.Key}, alias={kvp.Value}");
var item = FindModelItemByNodeKey(kvp.Key);
if (item == null)
{
LogManager.Debug($"[别名树] EnsureAliased: FindModelItemByNodeKey null, key={kvp.Key}");
continue;
}
if (EnsurePathLoaded(item))
loaded++;
}
catch (Exception ex)
{
LogManager.Error($"[别名树] 加载别名路径失败: {kvp.Key}, {ex.Message}");
}
}
if (loaded > 0)
LogManager.Info($"[别名树] 已确保 {loaded} 个别名节点路径可见");
}
///
/// 沿 ModelItem 父链逐层加载节点到树中(不选中)。
/// 逻辑与 ExpandAndSelect 相同,只是最终不选中。
///
private bool EnsurePathLoaded(Autodesk.Navisworks.Api.ModelItem item)
{
// 1. 构建祖先链
var chain = new System.Collections.Generic.List();
var current = item;
while (current != null)
{
chain.Insert(0, current);
current = current.Parent;
}
var chainStr = string.Join(" -> ", chain.ConvertAll(c => c.DisplayName ?? "?"));
LogManager.Debug($"[别名树] EnsurePathLoaded: chain=[{chainStr}], depth={chain.Count - 1}");
// 2. 匹配根节点
var rootNodes = AliasTreeView.ItemsSource
as System.Collections.ObjectModel.ObservableCollection;
if (rootNodes == null) return false;
AliasNodeViewModel treeNode = null;
foreach (var rn in rootNodes)
{
if (rn.ModelItem != null && rn.ModelItem.Equals(chain[0]))
{
treeNode = rn;
break;
}
}
if (treeNode == null) return false;
if (chain.Count == 1) return true;
// 3. 逐层加载
var aliasMap = _dataStore?.LoadAll();
var displayAlias = BuildDisplayPathAliasMap(aliasMap);
for (int i = 1; i < chain.Count; i++)
{
var targetModelItem = chain[i];
ExpandTreeNode(treeNode);
if (treeNode.Children.Count == 1 && treeNode.Children[0].OriginalName == "加载中...")
LazyLoadChildren(treeNode, aliasMap, displayAlias);
AliasNodeViewModel child = null;
foreach (var c in treeNode.Children)
{
if (c.OriginalName != "加载中..." && c.ModelItem != null && c.ModelItem.Equals(targetModelItem))
{
child = c;
break;
}
}
if (child == null)
{
int siblingIdx = NavisworksTransport.Core.AliasTree.AliasNodeIdentity.ComputeSiblingIndex(targetModelItem);
child = BuildNode(targetModelItem, aliasMap, displayAlias,
treeNode.Depth + 1, treeNode.NodeKey, siblingIdx);
if (child != null)
{
child.Parent = treeNode;
treeNode.Children.Add(child);
}
}
if (child == null)
{
LogManager.Debug($"[别名树] EnsurePathLoaded: child null at L{i}");
return false;
}
treeNode = child;
}
return true;
}
///
/// 从 TreeView 根开始,沿 parent 链找 ViewModel 对应的 TreeViewItem。
/// 虚拟化下可能返回 null。
///
private TreeViewItem GetTreeViewItemForNode(AliasNodeViewModel node)
{
if (node == null) return null;
// 收集从根到目标的路径
var path = new List();
var current = node;
while (current != null)
{
path.Insert(0, current);
current = current.Parent;
}
// 从 TreeView 根开始找
ItemsControl container = AliasTreeView;
foreach (var item in path)
{
if (item == null) break;
AliasTreeView.UpdateLayout();
container.UpdateLayout();
var tvi = container.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
if (tvi == null) return null;
container = tvi;
}
return container as TreeViewItem;
}
// ============================================================
// 事件处理:工具栏
// ============================================================
private void OnAliasInputKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
BeforeSaveAlias?.Invoke();
if (_currentSelectionItem == null || _dataStore == null) return;
string input = TxtAliasInput.Text?.Trim();
if (string.IsNullOrEmpty(input)) return;
if (input == _currentSelectionItem?.DisplayName)
input = null;
var identity = AliasNodeIdentity.FromModelItem(_currentSelectionItem);
LogManager.Info($"[别名树] 保存别名: {input}, NodeKey={identity.ToKey()}");
_dataStore.SetAlias(identity, input);
TxtAliasInput.Clear();
RefreshNodeDisplay(identity.ToKey());
}
}
private void OnAutoGenClick(object sender, RoutedEventArgs e)
{
// 阶段 3 实现
SetStatus("自动生成尚未实现");
}
private void OnExportClick(object sender, RoutedEventArgs e)
{
if (_dataStore == null) { SetStatus("数据库未连接"); return; }
var dlg = new Microsoft.Win32.SaveFileDialog
{
Filter = "JSON 文件|*.alias.json",
Title = "导出别名映射"
};
if (dlg.ShowDialog() != true) return;
try
{
var allAliases = _dataStore.LoadAll();
var list = new List