464 lines
14 KiB
Markdown
464 lines
14 KiB
Markdown
# Navisworks WPF插件UI架构重构技术设计方案
|
||
|
||
## 文档信息
|
||
|
||
- **创建日期**: 2025-08-16
|
||
- **版本**: v1.0
|
||
- **状态**: 技术设计阶段
|
||
- **作者**: Claude技术架构师 + Claude Code
|
||
|
||
## 1. 项目背景
|
||
|
||
### 1.1 问题描述
|
||
|
||
当前Navisworks 2026 WPF插件在路径规划完成后频繁崩溃,根本原因是将2017年的同步事件驱动架构强行迁移到异步WPF/MVVM模式,导致严重的线程安全问题。
|
||
|
||
### 1.2 技术现状
|
||
|
||
- **架构**: WPF + MVVM模式
|
||
- **数据绑定**: ObservableCollection + PropertyChanged
|
||
- **线程模型**: 后台线程 + Dispatcher异步调用
|
||
- **状态管理**: 事件驱动 + 多层嵌套调用
|
||
|
||
### 1.3 关键问题
|
||
|
||
1. **线程竞争**: 后台线程直接修改UI绑定的ObservableCollection
|
||
2. **事件重入**: PropertyChanged事件链导致循环调用
|
||
3. **调用链过深**: 多层Dispatcher.BeginInvoke嵌套
|
||
4. **状态不一致**: UI更新缺乏原子性保证
|
||
|
||
## 2. 技术架构设计
|
||
|
||
### 2.1 总体架构图
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ UI Layer (WPF) │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ LogisticsControlViewModel (重构) │
|
||
│ ├── ThreadSafeObservableCollection<PathRouteViewModel> │
|
||
│ ├── UIStateManager.ExecuteUIUpdate() │
|
||
│ └── Command Pattern (替代事件驱动) │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ UI State Management │
|
||
│ ┌─────────────────┐ ┌─────────────────┐ │
|
||
│ │ UIStateManager │ │ ViewModelBase │ │
|
||
│ │ (线程安全) │ │ (防重入机制) │ │
|
||
│ └─────────────────┘ └─────────────────┘ │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ Business Logic Layer │
|
||
│ PathPlanningManager (解耦UI) │
|
||
│ ├── IPathPlanningService (接口) │
|
||
│ ├── PathPlanningResult (数据传输对象) │
|
||
│ └── StatusUpdateCommand (命令模式) │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 2.2 核心组件设计
|
||
|
||
#### 2.2.1 UIStateManager - 统一UI状态管理器
|
||
|
||
```csharp
|
||
public class UIStateManager
|
||
{
|
||
private readonly SynchronizationContext _uiContext;
|
||
private readonly object _lock = new object();
|
||
private readonly Queue<UIUpdateOperation> _updateQueue;
|
||
private volatile bool _isProcessing = false;
|
||
|
||
// 线程安全的UI更新接口
|
||
public Task ExecuteUIUpdateAsync<T>(Func<T> operation) where T : class
|
||
{
|
||
return Task.Run(() =>
|
||
{
|
||
lock (_lock)
|
||
{
|
||
if (_uiContext.CheckAccess())
|
||
{
|
||
return operation();
|
||
}
|
||
else
|
||
{
|
||
T result = default(T);
|
||
_uiContext.Send(_ => result = operation(), null);
|
||
return result;
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// 批量UI更新(原子性保证)
|
||
public void ExecuteBatchUIUpdate(params Action[] updates)
|
||
{
|
||
if (_uiContext.CheckAccess())
|
||
{
|
||
foreach (var update in updates)
|
||
update();
|
||
}
|
||
else
|
||
{
|
||
_uiContext.Send(_ =>
|
||
{
|
||
foreach (var update in updates)
|
||
update();
|
||
}, null);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 2.2.2 ThreadSafeObservableCollection - 线程安全集合
|
||
|
||
```csharp
|
||
public class ThreadSafeObservableCollection<T> : ObservableCollection<T>
|
||
{
|
||
private readonly object _lock = new object();
|
||
private readonly SynchronizationContext _synchronizationContext;
|
||
|
||
public ThreadSafeObservableCollection()
|
||
{
|
||
_synchronizationContext = SynchronizationContext.Current;
|
||
BindingOperations.EnableCollectionSynchronization(this, _lock);
|
||
}
|
||
|
||
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||
{
|
||
if (_synchronizationContext != null && _synchronizationContext != SynchronizationContext.Current)
|
||
{
|
||
_synchronizationContext.Send(_ => base.OnCollectionChanged(e), null);
|
||
}
|
||
else
|
||
{
|
||
base.OnCollectionChanged(e);
|
||
}
|
||
}
|
||
|
||
// 批量操作接口
|
||
public void AddRange(IEnumerable<T> items)
|
||
{
|
||
lock (_lock)
|
||
{
|
||
foreach (var item in items)
|
||
Items.Add(item);
|
||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 2.2.3 重构的ViewModelBase - 防重入PropertyChanged
|
||
|
||
```csharp
|
||
public class ViewModelBase : INotifyPropertyChanged
|
||
{
|
||
private readonly UIStateManager _uiStateManager;
|
||
private readonly HashSet<string> _updatingProperties = new HashSet<string>();
|
||
private readonly object _propertyLock = new object();
|
||
|
||
public event PropertyChangedEventHandler PropertyChanged;
|
||
|
||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||
{
|
||
lock (_propertyLock)
|
||
{
|
||
// 防重入检查
|
||
if (_updatingProperties.Contains(propertyName))
|
||
return;
|
||
|
||
_updatingProperties.Add(propertyName);
|
||
}
|
||
|
||
try
|
||
{
|
||
_uiStateManager.ExecuteUIUpdateAsync(() =>
|
||
{
|
||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||
});
|
||
}
|
||
finally
|
||
{
|
||
lock (_propertyLock)
|
||
{
|
||
_updatingProperties.Remove(propertyName);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 批量属性更新
|
||
protected void SetProperties(params (string name, object value)[] properties)
|
||
{
|
||
_uiStateManager.ExecuteBatchUIUpdate(
|
||
properties.Select(p => new Action(() => OnPropertyChanged(p.name))).ToArray()
|
||
);
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 2.2.4 Command Pattern替代事件驱动
|
||
|
||
```csharp
|
||
public interface IPathPlanningCommand
|
||
{
|
||
Task<PathPlanningResult> ExecuteAsync();
|
||
}
|
||
|
||
public class AutoPathPlanningCommand : IPathPlanningCommand
|
||
{
|
||
private readonly Point3D _startPoint;
|
||
private readonly Point3D _endPoint;
|
||
private readonly PathPlanningManager _manager;
|
||
|
||
public async Task<PathPlanningResult> ExecuteAsync()
|
||
{
|
||
// 纯业务逻辑,不涉及UI
|
||
var route = await _manager.AutoPlanPathAsync(_startPoint, _endPoint);
|
||
return new PathPlanningResult
|
||
{
|
||
IsSuccess = route != null,
|
||
Route = route,
|
||
Message = route != null ? "规划成功" : "未找到可行路径"
|
||
};
|
||
}
|
||
}
|
||
|
||
// UI层处理
|
||
public class LogisticsControlViewModel : ViewModelBase
|
||
{
|
||
private async void ExecuteAutoPlanPath()
|
||
{
|
||
var command = new AutoPathPlanningCommand(_startPoint, _endPoint, _pathPlanningManager);
|
||
var result = await command.ExecuteAsync();
|
||
|
||
// 使用UIStateManager统一处理UI更新
|
||
await _uiStateManager.ExecuteUIUpdateAsync(() =>
|
||
{
|
||
if (result.IsSuccess)
|
||
{
|
||
// 原子性UI更新
|
||
var viewModel = CreatePathRouteViewModel(result.Route);
|
||
PathRoutes.Add(viewModel);
|
||
SelectedPathRoute = viewModel;
|
||
StatusText = result.Message;
|
||
}
|
||
else
|
||
{
|
||
StatusText = result.Message;
|
||
}
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.3 状态机设计
|
||
|
||
```csharp
|
||
public enum UIState
|
||
{
|
||
Idle,
|
||
Planning,
|
||
Updating,
|
||
Error
|
||
}
|
||
|
||
public class UIStateMachine
|
||
{
|
||
private UIState _currentState = UIState.Idle;
|
||
private readonly object _stateLock = new object();
|
||
|
||
public bool TryTransition(UIState fromState, UIState toState)
|
||
{
|
||
lock (_stateLock)
|
||
{
|
||
if (_currentState == fromState)
|
||
{
|
||
_currentState = toState;
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
}
|
||
|
||
public UIState CurrentState
|
||
{
|
||
get { lock (_stateLock) return _currentState; }
|
||
}
|
||
}
|
||
```
|
||
|
||
## 3. 实施计划
|
||
|
||
### 3.1 第一阶段:基础设施搭建(1-2周)
|
||
|
||
#### 3.1.1 创建核心组件
|
||
|
||
- [ ] 实现UIStateManager类
|
||
- [ ] 实现ThreadSafeObservableCollection类
|
||
- [ ] 重构ViewModelBase基类
|
||
- [ ] 创建UIStateMachine状态机
|
||
|
||
#### 3.1.2 单元测试
|
||
|
||
- [ ] UIStateManager线程安全测试
|
||
- [ ] ThreadSafeObservableCollection并发测试
|
||
- [ ] ViewModelBase防重入测试
|
||
|
||
### 3.2 第二阶段:核心组件重构(2-3周)
|
||
|
||
#### 3.2.1 重构LogisticsControlViewModel
|
||
|
||
```csharp
|
||
public class LogisticsControlViewModel : ViewModelBase
|
||
{
|
||
// 使用线程安全集合
|
||
public ThreadSafeObservableCollection<PathRouteViewModel> PathRoutes { get; }
|
||
|
||
// 使用UIStateManager处理所有UI更新
|
||
private readonly UIStateManager _uiStateManager;
|
||
|
||
// 使用状态机管理UI状态
|
||
private readonly UIStateMachine _stateMachine;
|
||
|
||
// 使用Command Pattern替代事件订阅
|
||
public ICommand AutoPlanPathCommand { get; }
|
||
}
|
||
```
|
||
|
||
#### 3.2.2 解耦PathPlanningManager
|
||
|
||
```csharp
|
||
public interface IPathPlanningService
|
||
{
|
||
Task<PathRoute> AutoPlanPathAsync(Point3D start, Point3D end, double vehicleSize, double safetyMargin);
|
||
Task<bool> ValidatePathAsync(PathRoute route);
|
||
}
|
||
|
||
public class PathPlanningManager : IPathPlanningService
|
||
{
|
||
// 移除所有UI相关事件
|
||
// 只保留纯业务逻辑
|
||
|
||
public async Task<PathRoute> AutoPlanPathAsync(Point3D start, Point3D end, double vehicleSize, double safetyMargin)
|
||
{
|
||
// 纯后台线程执行,不触发任何UI事件
|
||
return await Task.Run(() => {
|
||
// 原有的路径规划逻辑
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 3.2.3 重新设计UI更新流程
|
||
|
||
```
|
||
旧流程 (有问题):
|
||
PathPlanningManager.AutoPlanPath()
|
||
→ 后台线程触发StatusChanged事件
|
||
→ UI线程订阅事件处理
|
||
→ 直接修改ObservableCollection
|
||
→ 嵌套Dispatcher调用
|
||
→ 崩溃
|
||
|
||
新流程 (安全):
|
||
UI.ExecuteAutoPlanPath()
|
||
→ Command.ExecuteAsync()
|
||
→ PathPlanningService.AutoPlanPathAsync()
|
||
→ 返回PathPlanningResult
|
||
→ UIStateManager.ExecuteUIUpdateAsync()
|
||
→ 原子性UI更新
|
||
→ 完成
|
||
```
|
||
|
||
### 3.3 第三阶段:测试验证(1周)
|
||
|
||
#### 3.3.1 压力测试场景
|
||
|
||
1. **重复规划测试**: 连续执行100次"选择起点→选择终点→自动规划"
|
||
2. **属性修改测试**: 重复执行"通道→门→规划→删除→规划"场景
|
||
3. **并发操作测试**: 同时进行多个UI操作
|
||
4. **内存泄漏测试**: 长时间运行检查内存使用
|
||
|
||
#### 3.3.2 功能验证清单
|
||
|
||
- [ ] 自动路径规划功能正常
|
||
- [ ] UI响应性能良好
|
||
- [ ] 无UI线程阻塞
|
||
- [ ] 无内存泄漏
|
||
- [ ] 错误处理正确
|
||
|
||
### 3.4 第四阶段:性能优化(1周)
|
||
|
||
#### 3.4.1 UI性能优化
|
||
|
||
- [ ] 减少不必要的PropertyChanged触发
|
||
- [ ] 优化数据绑定性能
|
||
- [ ] 实现虚拟化长列表
|
||
|
||
#### 3.4.2 内存管理优化
|
||
|
||
- [ ] 确保事件订阅正确释放
|
||
- [ ] 优化大对象内存分配
|
||
- [ ] 实现对象池模式
|
||
|
||
## 4. 风险控制
|
||
|
||
### 4.1 技术风险
|
||
|
||
1. **兼容性风险**: 新架构可能影响现有功能
|
||
- **缓解措施**: 分阶段迁移,保持API接口不变
|
||
|
||
2. **性能风险**: 线程安全机制可能影响性能
|
||
- **缓解措施**: 性能基准测试,优化关键路径
|
||
|
||
3. **复杂性风险**: 新架构增加代码复杂度
|
||
- **缓解措施**: 详细文档,代码审查
|
||
|
||
### 4.2 进度风险
|
||
|
||
1. **开发进度**: 重构工作量可能超出预期
|
||
- **缓解措施**: 分阶段实施,每阶段可独立交付
|
||
|
||
2. **测试时间**: 充分测试需要时间
|
||
- **缓解措施**: 自动化测试,并行开发和测试
|
||
|
||
### 4.3 回滚策略
|
||
|
||
- 每个阶段都在独立分支开发
|
||
- 保持主分支稳定
|
||
- 每个阶段完成后合并,确保可回滚
|
||
|
||
## 5. 预期效果
|
||
|
||
### 5.1 稳定性提升
|
||
|
||
- **消除UI崩溃**: 通过线程安全机制彻底解决崩溃问题
|
||
- **提高可靠性**: 原子性UI更新确保状态一致性
|
||
- **增强健壮性**: 防重入机制避免循环调用
|
||
|
||
### 5.2 维护性改善
|
||
|
||
- **代码结构清晰**: 分层架构,职责明确
|
||
- **可测试性强**: 依赖注入,便于单元测试
|
||
- **可扩展性好**: 接口设计,便于功能扩展
|
||
|
||
### 5.3 用户体验优化
|
||
|
||
- **响应速度快**: 异步操作,UI不阻塞
|
||
- **操作流畅**: 无卡顿,无异常
|
||
- **功能稳定**: 重复操作不崩溃
|
||
|
||
## 6. 总结
|
||
|
||
本方案通过全面重构UI架构,从根本上解决了WPF版本的稳定性问题。核心思想是:
|
||
|
||
1. **分离关注点**: 业务逻辑与UI逻辑完全分离
|
||
2. **线程安全**: 统一的线程安全管理机制
|
||
3. **原子操作**: 确保UI更新的原子性
|
||
4. **防重入**: 避免事件循环调用
|
||
5. **状态管理**: 清晰的UI状态流转
|
||
|
||
通过这个重构方案,将恢复2017版本的稳定性,同时保持WPF现代UI的优势,为后续功能扩展打下坚实基础。
|
||
|
||
---
|
||
|
||
*本文档将根据实施进度持续更新,确保设计方案与实际开发保持同步。*
|