NavisworksTransport/doc/working/2025-09-14_文档切换三级处理机制设计.md

332 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 文档切换三级处理机制设计方案
**日期**: 2025-09-14
**作者**: Claude
**背景**: 解决Navisworks插件在文档切换时的崩溃问题
## 一、问题分析
### 发现的问题
1. Navisworks是SDI单文档界面应用ActiveDocument在整个生命周期中保持不变
2. 文档内容变化通过`Models.CollectionChanged`事件通知
3. 原代码在Models清空时调用了完整的CleanupManagers导致IdleEventManager被Dispose引发崩溃
### 测试观察结果
通过观察版本测试发现Models.CollectionChanged事件的触发模式
**场景1第一次打开文件**
- 事件#1Count=0状态保持无模型文档清空准备加载
- 事件#2Count=1从无模型→有模型文件加载完成
**场景2重新打开新文件**
- 事件#3Count=0从有模型→无模型旧文件被清空
- 事件#4Count=1从无模型→有模型新文件加载完成
**场景3关闭程序**
- 事件#5Count=0从有模型→无模型程序关闭前清理
## 二、设计方案
### 整体架构
```
插件生命周期:
├── OnLoaded插件启动- 重量级初始化
├── Models.CollectionChanged文档切换- 轻量级状态管理
└── OnUnloading/DestroyControlPane插件关闭- 重量级清理
```
### 三种处理级别
1. **插件初始化**OnLoaded
- 创建所有管理器实例
- 初始化UI组件
- 订阅事件
2. **文档状态切换**Models.CollectionChanged
- 轻量级操作
- 清理/刷新ModelItem引用
- 重置可视化状态
- 保持管理器实例存活
3. **插件关闭**OnUnloading + DestroyControlPane
- 重量级清理
- 销毁所有管理器实例
- 取消所有事件订阅
- 释放所有资源
## 三、具体实现
### 1. 添加必要的成员变量
```csharp
public class Main : DockPanePlugin
{
// 状态跟踪变量
private bool _hasModels = false; // 跟踪模型状态
private bool _isShuttingDown = false; // 插件关闭标志
private int _eventCount = 0; // 事件计数(调试用)
private DateTime _pluginStartTime; // 插件启动时间
}
```
### 2. OnLoaded - 插件初始化(一次性)
```csharp
protected override void OnLoaded()
{
base.OnLoaded();
_pluginStartTime = DateTime.Now;
_isShuttingDown = false;
LogManager.Info("[文档管理] 插件OnLoaded - 开始初始化");
// 订阅文档事件
SubscribeToDocumentEvents();
// 一次性初始化管理器(创建实例)
if (NavisApplication.ActiveDocument != null)
{
LogManager.Info("[文档管理] 插件加载时发现活动文档,初始化管理器");
InitializeManagers(); // 创建管理器实例
// 检查文档是否有内容
if (NavisApplication.ActiveDocument.Models?.Count > 0)
{
_hasModels = true;
RefreshDocumentStates(); // 刷新状态
}
}
}
```
### 3. OnModelsCollectionChanged - 文档切换处理(频繁)
```csharp
private void OnModelsCollectionChanged(object sender, EventArgs e)
{
_eventCount++;
// 如果插件正在关闭,跳过所有处理
if (_isShuttingDown)
{
LogManager.Info("[文档管理] 插件正在关闭跳过Models.CollectionChanged处理");
return;
}
var activeDoc = NavisApplication.ActiveDocument;
var modelCount = activeDoc?.Models?.Count ?? 0;
bool currentHasModels = modelCount > 0;
LogManager.Info($"[文档管理] Models.CollectionChanged #{_eventCount}, Count={modelCount}, 状态: {(_hasModels ? "" : "")} -> {(currentHasModels ? "" : "")}");
// 状态转换:从有到无(文档被清空)
if (_hasModels && !currentHasModels)
{
LogManager.Info("[文档管理] 文档已清空,执行轻量级清理");
ClearDocumentStates();
}
// 状态转换:从无到有(文档加载完成)
else if (!_hasModels && currentHasModels)
{
LogManager.Info("[文档管理] 文档已加载,刷新状态");
RefreshDocumentStates();
}
// 状态未变化
else
{
LogManager.Info("[文档管理] 模型集合更新但状态未变");
}
_hasModels = currentHasModels;
}
```
### 4. 新增轻量级方法
```csharp
/// <summary>
/// 轻量级清理 - 仅清理文档相关状态,不销毁管理器
/// </summary>
private void ClearDocumentStates()
{
try
{
LogManager.Info("[文档管理] 开始轻量级状态清理...");
// 1. 停止动画(如果正在播放)
var animationManager = PathAnimationManager.GetInstance();
if (animationManager?.IsAnimating == true)
{
animationManager.StopAnimation();
}
// 清理动画中的ModelItem引用
animationManager?.ClearReferences();
// 2. 清理路径管理器状态
var pathManager = PathPlanningManager.GetActivePathManager();
if (pathManager != null)
{
pathManager.ResetPathEditState();
pathManager.ClearInvalidReferences(); // 清理ModelItem引用
}
// 3. 清理路径可视化
PathPointRenderPlugin.Instance?.ClearAllPaths();
// 4. 清理碰撞检测缓存
ClashDetectiveIntegration.Instance?.ClearCollisionCache();
ClashDetectiveIntegration.ClearAllCaches();
// 5. 清除临时材质
if (NavisApplication.ActiveDocument?.Models != null)
{
NavisApplication.ActiveDocument.Models.ResetAllTemporaryMaterials();
}
// 6. 通知文档状态管理器
DocumentStateManager.Instance.OnDocumentInvalidated();
LogManager.Info("[文档管理] 轻量级状态清理完成");
}
catch (Exception ex)
{
LogManager.Warning($"[文档管理] 状态清理时出现警告: {ex.Message}");
}
}
/// <summary>
/// 轻量级刷新 - 仅刷新状态,不重新创建管理器
/// </summary>
private void RefreshDocumentStates()
{
try
{
LogManager.Info("[文档管理] 开始刷新文档状态...");
// 1. 通知文档状态管理器
DocumentStateManager.Instance.OnDocumentReady();
// 2. 清理旧缓存
ClashDetectiveIntegration.ClearAllCaches();
// 3. 刷新路径管理器(如果需要)
var pathManager = PathPlanningManager.GetActivePathManager();
pathManager?.RefreshDocument();
LogManager.Info("[文档管理] 文档状态刷新完成");
}
catch (Exception ex)
{
LogManager.Warning($"[文档管理] 状态刷新时出现警告: {ex.Message}");
}
}
```
### 5. OnUnloading - 插件关闭(一次性)
```csharp
protected override void OnUnloading()
{
LogManager.Info("[文档管理] 插件OnUnloading - 开始关闭");
// 设置关闭标志阻止后续的Models事件处理
_isShuttingDown = true;
// 取消订阅文档事件
UnsubscribeFromDocumentEvents();
// 执行完整的管理器清理(销毁实例)
CleanupManagers();
base.OnUnloading();
}
```
### 6. DestroyControlPane - 控件销毁
```csharp
public override void DestroyControlPane(Control pane)
{
GlobalExceptionHandler.SafeExecute(() =>
{
LogManager.Info("开始销毁DockPane控制面板");
// 设置关闭标志(双重保险)
_isShuttingDown = true;
// 清理WPF控件资源
if (pane is ElementHost elementHost && elementHost.Child is UI.WPF.LogisticsControlPanel wpfControl)
{
wpfControl.Cleanup();
elementHost.Child = null;
}
// 释放控件资源
pane?.Dispose();
LogManager.Info("DockPane控制面板销毁完成");
}, "销毁控制面板");
}
```
### 7. 保持不变的方法
- **InitializeManagers()** - 保持原样,用于创建管理器实例
- **CleanupManagers()** - 保持原样,用于销毁管理器实例
- **SubscribeToDocumentEvents()** - 保持原样
- **UnsubscribeFromDocumentEvents()** - 保持原样
## 四、关键设计原则
### 1. 分离关注点
- 插件生命周期管理OnLoaded/OnUnloading
- 文档状态管理Models.CollectionChanged
- UI生命周期管理CreateControlPane/DestroyControlPane
### 2. 轻量级 vs 重量级
- **轻量级**:清理引用、重置状态、清空缓存
- **重量级**:创建/销毁实例、订阅/取消事件
### 3. 防御性编程
- 使用`_isShuttingDown`标志防止关闭时的事件处理
- 所有清理操作都包含try-catch
- 使用GlobalExceptionHandler.SafeExecute
### 4. 性能优化
- 文档切换时不重新创建管理器
- 只在必要时清理和刷新
- 避免重复操作
## 五、预期效果
1. **启动时**:完整初始化,创建所有组件
2. **文档切换时**:快速清理和刷新,保持响应性
3. **关闭时**:干净退出,避免异常
## 六、实施计划
1. 先实现`_isShuttingDown`标志,防止关闭时的崩溃
2. 实现`ClearDocumentStates()`和`RefreshDocumentStates()`方法
3. 修改`OnModelsCollectionChanged`使用新的轻量级方法
4. 测试各种场景确保稳定性
## 七、注意事项
1. PathPlanningManager可能需要添加`RefreshDocument()`方法
2. 确保所有管理器都有适当的清理引用方法
3. 考虑添加更多的日志来跟踪状态变化
4. 可能需要在某些管理器中区分"清理状态"和"销毁实例"
这个方案清晰地分离了不同场景的处理逻辑,避免了混淆,确保了稳定性。