NavisworksTransport/.kiro/specs/dockpane-migration/design.md

13 KiB
Raw Blame History

Design Document

Overview

本设计文档详细描述了将Navisworks物流路径规划插件从AddInPlugin架构迁移到DockPanePlugin架构的技术实现方案。基于Navisworks 2026 API文档中的DockPanePlugin示例代码我们将重新设计插件架构以提供可停靠的用户界面面板并利用WPF技术栈实现更现代化的用户体验。

Architecture

插件架构变更

当前架构 (AddInPlugin)

[PluginAttribute("Basic", "Tian", ToolTip = "Transport Plugin", DisplayName = "Transport Plugin")]
[AddInPlugin(AddInLocation.AddIn)]
public class Main : AddInPlugin
{
    public override int Execute(params string[] parameters)
    {
        // 显示模态对话框
        ShowDockPane();
        return 0;
    }
}

目标架构 (DockPanePlugin)

[Plugin("NavisworksTransport.Main", "Tian",
    DisplayName = "物流路径规划",
    ToolTip = "物流路径规划和动画仿真插件")]
[DockPanePlugin(420, 700)] // 宽度420, 高度700
[Strings("NavisworksTransport.Tian.name")]
public class Main : DockPanePlugin
{
    public override Control CreateControlPane()
    {
        // 创建WPF用户控件并用ElementHost托管
        var wpfControl = new LogisticsControlPanel();
        var elementHost = new ElementHost
        {
            Dock = DockStyle.Fill,
            Child = wpfControl
        };
        return elementHost;
    }

    public override void DestroyControlPane(Control pane)
    {
        // 清理资源和事件订阅
        if (pane is ElementHost host && host.Child is LogisticsControlPanel wpfControl)
        {
            wpfControl.Cleanup();
        }
        pane.Dispose();
    }
}

WPF用户控件架构

主控件结构

LogisticsControlPanel (UserControl)
├── MainTabControl (TabControl)
│   ├── ModelSettingsTab (TabItem)
│   │   └── ModelSettingsView (UserControl)
│   ├── PathEditingTab (TabItem)
│   │   └── PathEditingView (UserControl)
│   ├── AnimationControlTab (TabItem)
│   │   └── AnimationControlView (UserControl)
│   └── SystemManagementTab (TabItem)
│       └── SystemManagementView (UserControl)
└── BottomPanel (StackPanel)
    ├── HelpButton (Button)
    ├── AboutButton (Button)
    └── CloseButton (Button)

Components and Interfaces

1. 主插件类 (Main)

职责:

  • 实现DockPanePlugin接口
  • 管理插件生命周期
  • 创建和销毁控件面板

关键方法:

  • CreateControlPane(): 创建ElementHost并托管WPF控件
  • DestroyControlPane(Control pane): 清理资源和释放控件

2. WPF主控件 (LogisticsControlPanel)

职责:

  • 作为所有功能的容器
  • 管理Tab页面切换
  • 处理全局事件和状态

关键属性和方法:

public partial class LogisticsControlPanel : UserControl
{
    // 子视图引用
    private ModelSettingsView _modelSettingsView;
    private PathEditingView _pathEditingView;
    private AnimationControlView _animationControlView;
    private SystemManagementView _systemManagementView;
    
    // 管理器实例
    private PathPlanningManager _pathPlanningManager;
    private static bool _isSessionInitialized = false;
    
    public LogisticsControlPanel()
    {
        InitializeComponent();
        InitializeSession();
        InitializeViews();
        SubscribeToEvents();
    }
    
    public void Cleanup()
    {
        UnsubscribeFromEvents();
        CleanupManagers();
    }
}

3. 子视图控件

ModelSettingsView

  • 类别属性设置
  • 物流模型列表
  • 可见性控制
  • 统计信息显示

PathEditingView

  • 路径列表管理
  • 当前路径编辑
  • 路径文件管理

AnimationControlView

  • 动画参数设置
  • 播放控制
  • 碰撞检测

SystemManagementView

  • 模型分层拆分
  • 日志管理
  • 插件设置
  • 系统信息

4. MVVM架构支持

ViewModel基类

public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    
    protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }
}

主ViewModel

public class LogisticsControlViewModel : ViewModelBase
{
    private string _selectedModelsText;
    private string _instructionText;
    private ObservableCollection<LogisticsModel> _logisticsModels;
    
    public string SelectedModelsText
    {
        get => _selectedModelsText;
        set => SetProperty(ref _selectedModelsText, value);
    }
    
    public string InstructionText
    {
        get => _instructionText;
        set => SetProperty(ref _instructionText, value);
    }
    
    public ObservableCollection<LogisticsModel> LogisticsModels
    {
        get => _logisticsModels;
        set => SetProperty(ref _logisticsModels, value);
    }
    
    // Commands
    public ICommand RefreshCommand { get; }
    public ICommand ClearSelectionCommand { get; }
    public ICommand ShowAllCommand { get; }
    public ICommand HideAllCommand { get; }
}

Data Models

1. 数据传输对象

LogisticsModel

public class LogisticsModel : INotifyPropertyChanged
{
    private string _name;
    private string _category;
    private string _attributes;
    private bool _isVisible;
    
    public string Name
    {
        get => _name;
        set => SetProperty(ref _name, value);
    }
    
    public string Category
    {
        get => _category;
        set => SetProperty(ref _category, value);
    }
    
    public string Attributes
    {
        get => _attributes;
        set => SetProperty(ref _attributes, value);
    }
    
    public bool IsVisible
    {
        get => _isVisible;
        set => SetProperty(ref _isVisible, value);
    }
    
    public ModelItem NavisworksItem { get; set; }
    
    // INotifyPropertyChanged implementation
    public event PropertyChangedEventHandler PropertyChanged;
    
    private void SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return;
        field = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

PathRoute

public class PathRoute : INotifyPropertyChanged
{
    private string _name;
    private ObservableCollection<PathPoint> _points;
    private bool _isActive;
    
    public string Name
    {
        get => _name;
        set => SetProperty(ref _name, value);
    }
    
    public ObservableCollection<PathPoint> Points
    {
        get => _points;
        set => SetProperty(ref _points, value);
    }
    
    public bool IsActive
    {
        get => _isActive;
        set => SetProperty(ref _isActive, value);
    }
    
    // INotifyPropertyChanged implementation
    public event PropertyChangedEventHandler PropertyChanged;
    
    private void SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return;
        field = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

2. 业务逻辑管理器

现有的管理器类保持不变但需要适配WPF的事件模型

  • PathPlanningManager
  • PathDataManager
  • VisibilityManager
  • ModelSplitterManager
  • LogisticsAnimationManager

Error Handling

1. WPF异常处理

public partial class LogisticsControlPanel : UserControl
{
    public LogisticsControlPanel()
    {
        InitializeComponent();
        
        // 订阅WPF异常事件
        Dispatcher.UnhandledException += OnDispatcherUnhandledException;
    }
    
    private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
    {
        LogManager.Error($"[WPF异常] {e.Exception.Message}");
        LogManager.Error($"[WPF异常] 堆栈信息: {e.Exception.StackTrace}");
        
        // 标记异常已处理,避免程序崩溃
        e.Handled = true;
        
        // 显示用户友好的错误信息
        MessageBox.Show($"界面操作发生错误: {e.Exception.Message}", 
            "错误", MessageBoxButton.OK, MessageBoxImage.Warning);
    }
}

2. 线程安全处理

public class ThreadSafeHelper
{
    public static void InvokeOnUIThread(Action action)
    {
        if (Application.Current?.Dispatcher?.CheckAccess() == true)
        {
            action();
        }
        else
        {
            Application.Current?.Dispatcher?.Invoke(action);
        }
    }
    
    public static T InvokeOnUIThread<T>(Func<T> func)
    {
        if (Application.Current?.Dispatcher?.CheckAccess() == true)
        {
            return func();
        }
        else
        {
            return Application.Current.Dispatcher.Invoke(func);
        }
    }
}

Testing Strategy

1. 单元测试

插件生命周期测试

[TestClass]
public class MainPluginTests
{
    [TestMethod]
    public void CreateControlPane_ShouldReturnElementHost()
    {
        // Arrange
        var plugin = new Main();
        
        // Act
        var control = plugin.CreateControlPane();
        
        // Assert
        Assert.IsInstanceOfType(control, typeof(ElementHost));
        Assert.IsNotNull(((ElementHost)control).Child);
        Assert.IsInstanceOfType(((ElementHost)control).Child, typeof(LogisticsControlPanel));
    }
    
    [TestMethod]
    public void DestroyControlPane_ShouldDisposeControl()
    {
        // Arrange
        var plugin = new Main();
        var control = plugin.CreateControlPane();
        
        // Act
        plugin.DestroyControlPane(control);
        
        // Assert
        Assert.IsTrue(control.IsDisposed);
    }
}

WPF控件测试

[TestClass]
public class LogisticsControlPanelTests
{
    [TestMethod]
    public void Constructor_ShouldInitializeAllViews()
    {
        // Arrange & Act
        var control = new LogisticsControlPanel();
        
        // Assert
        Assert.IsNotNull(control.FindName("MainTabControl"));
        Assert.IsNotNull(control.FindName("ModelSettingsTab"));
        Assert.IsNotNull(control.FindName("PathEditingTab"));
        Assert.IsNotNull(control.FindName("AnimationControlTab"));
        Assert.IsNotNull(control.FindName("SystemManagementTab"));
    }
}

2. 集成测试

Navisworks API集成测试

[TestClass]
public class NavisworksIntegrationTests
{
    [TestMethod]
    public void SelectionChanged_ShouldUpdateViewModel()
    {
        // 需要在Navisworks环境中运行
        // 测试选择变更事件是否正确更新WPF界面
    }
    
    [TestMethod]
    public void PathPlanning_ShouldCreateVisualPath()
    {
        // 测试路径规划功能是否在WPF界面中正确显示
    }
}

3. 用户界面测试

WPF自动化测试

[TestClass]
public class UIAutomationTests
{
    [TestMethod]
    public void TabSwitching_ShouldWorkCorrectly()
    {
        // 使用WPF测试框架测试Tab切换功能
    }
    
    [TestMethod]
    public void DataBinding_ShouldUpdateUI()
    {
        // 测试数据绑定是否正确更新界面
    }
}

Performance Considerations

1. WPF渲染优化

  • 使用虚拟化ListView和TreeView处理大量数据
  • 实现延迟加载避免界面卡顿
  • 使用异步操作处理耗时任务

2. 内存管理

  • 正确实现IDisposable接口
  • 及时取消事件订阅避免内存泄漏
  • 使用WeakReference处理长期引用

3. 线程优化

  • 将Navisworks API调用保持在主线程
  • 使用BackgroundWorker处理后台任务
  • 合理使用Dispatcher.BeginInvoke避免界面阻塞

Migration Strategy

阶段1基础架构迁移

  1. 创建DockPanePlugin基础结构
  2. 实现ElementHost托管机制
  3. 创建基本的WPF用户控件框架

阶段2功能模块迁移

  1. 迁移模型设置功能到WPF
  2. 迁移路径编辑功能到WPF
  3. 迁移动画控制功能到WPF
  4. 迁移系统管理功能到WPF

阶段3优化和测试

  1. 实现MVVM模式和数据绑定
  2. 优化性能和用户体验
  3. 完善错误处理和异常管理
  4. 进行全面测试和调试

阶段4部署和验证

  1. 更新部署配置
  2. 验证插件注册和加载
  3. 进行用户验收测试
  4. 文档更新和培训