# 文档切换三级处理机制设计方案

**日期**: 2025-09-14
**作者**: Claude
**背景**: 解决Navisworks插件在文档切换时的崩溃问题

## 一、问题分析

### 发现的问题

1. Navisworks是SDI（单文档界面）应用，ActiveDocument在整个生命周期中保持不变
2. 文档内容变化通过`Models.CollectionChanged`事件通知
3. 原代码在Models清空时调用了完整的CleanupManagers，导致IdleEventManager被Dispose，引发崩溃

### 测试观察结果

通过观察版本测试，发现Models.CollectionChanged事件的触发模式：

**场景1：第一次打开文件**

- 事件#1：Count=0，状态保持无模型（文档清空准备加载）
- 事件#2：Count=1，从无模型→有模型（文件加载完成）

**场景2：重新打开新文件**

- 事件#3：Count=0，从有模型→无模型（旧文件被清空）
- 事件#4：Count=1，从无模型→有模型（新文件加载完成）

**场景3：关闭程序**

- 事件#5：Count=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. 可能需要在某些管理器中区分"清理状态"和"销毁实例"

这个方案清晰地分离了不同场景的处理逻辑，避免了混淆，确保了稳定性。
