diff --git a/.serena/cache/csharp/document_symbols_cache_v23-06-25.pkl b/.serena/cache/csharp/document_symbols_cache_v23-06-25.pkl index 1843f7c..2be3597 100644 Binary files a/.serena/cache/csharp/document_symbols_cache_v23-06-25.pkl and b/.serena/cache/csharp/document_symbols_cache_v23-06-25.pkl differ diff --git a/NavisworksTransportPlugin.csproj b/NavisworksTransportPlugin.csproj index 644afd5..dc61700 100644 --- a/NavisworksTransportPlugin.csproj +++ b/NavisworksTransportPlugin.csproj @@ -184,6 +184,12 @@ LayerManagementView.xaml + + HelpDialog.xaml + + + AboutDialog.xaml + @@ -258,6 +264,20 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + + Designer + MSBuild:Compile + + diff --git a/Plugin.csproj b/Plugin.csproj index 0345b03..417437a 100644 --- a/Plugin.csproj +++ b/Plugin.csproj @@ -1,2 +1,31 @@ - \ No newline at end of file + + Designer + MSBuild:Compile + + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + + Designer + MSBuild:Compile + + + + \ No newline at end of file diff --git a/doc/guide/design_principles.md b/doc/guide/design_principles.md index e62899c..68aba20 100644 --- a/doc/guide/design_principles.md +++ b/doc/guide/design_principles.md @@ -1235,7 +1235,303 @@ private void ShowPositionCorrectionDialog(Point3D originalPos, Point3D corrected - 精度要求高的导航系统 - 自动化路径生成工具 -## 13. 线程安全实践经验总结 +## 13. WPF数据绑定最佳实践:避免自定义集合陷阱 + +### 问题描述 + +在NavisworksTransport项目中发现了一个经典的WPF数据绑定问题:自定义的 `ThreadSafeObservableCollection` 与WPF标准数据绑定机制不兼容,导致UI显示重复数据。这个问题揭示了"过度工程"的风险以及回归标准实践的重要性。 + +### 问题根本原因分析 + +#### 13.1 设计理念冲突 + +```csharp +// ❌ 问题:自定义线程安全集合与WPF冲突 +public class ThreadSafeObservableCollection : ObservableCollection +{ + // 内部实现复杂的UI线程marshaling + private void OnCollectionChanged() + { + // 自定义的UI线程处理机制 + _uiStateManager.QueueUIUpdate(() => + { + base.OnCollectionChanged(...); + }); + } +} + +// ✅ 解决方案:使用WPF标准集合 +public ObservableCollection PathRoutes { get; set; } + = new ObservableCollection(); +``` + +**核心冲突**: +- `ThreadSafeObservableCollection`试图提供线程安全,通过内部机制自动将变更marshaling到UI线程 +- WPF数据绑定期望使用标准的`ObservableCollection`,由框架本身处理UI线程marshaling +- 双重UI线程处理机制导致重复通知和不可预测的行为 + +#### 13.2 异步处理复杂性 + +```csharp +// ❌ 导致问题的异步初始化机制 +public PathRouteViewModel() +{ + InitializeDefaults(); + _ = InitializeAsync(); // "火后不理"的异步调用 +} + +public async Task InitializeAsync() +{ + await _uiStateManager.ExecuteUIUpdateAsync(() => + { + // 异步UI更新可能与同步数据创建产生时序问题 + Points.CollectionChanged += OnPointsCollectionChanged; + _isInitialized = true; + }); +} +``` + +**时序问题分析**: +1. 同步的数据创建(RefreshPathRoutes) +2. 异步的UI初始化(InitializeAsync) +3. UI更新队列积压(保底定时器强制处理) +4. 重复的UI更新执行 + +#### 13.3 事件处理重叠 + +发现有三个机制同时处理相同的数据变更: +```csharp +// 机制1:手动刷新 +RefreshPathRoutes() // 从Core数据创建UI路径点 + +// 机制2:事件响应 +OnPathPointsListUpdated() // 响应路径点更新事件 + +// 机制3:路径生成事件 +OnRouteGenerated() // 处理自动路径生成 + +// 结果:同样的路径点被多次添加到UI +``` + +### 完整解决方案 + +#### 13.4 回归WPF标准实践 + +```csharp +// ✅ 正确做法:使用标准ObservableCollection +public class PathEditingViewModel : ViewModelBase +{ + // 路径集合使用标准集合 + public ObservableCollection PathRoutes { get; private set; } + = new ObservableCollection(); +} + +public class PathRouteViewModel : ViewModelBase +{ + // 路径点集合也使用标准集合 + public ObservableCollection Points { get; private set; } + = new ObservableCollection(); + + // 简化初始化:同步完成,无异步复杂性 + public PathRouteViewModel() + { + InitializeDefaults(); + CompleteInitialization(); + } + + private void CompleteInitialization() + { + // 直接订阅事件,无需异步 + Points.CollectionChanged += OnPointsCollectionChanged; + _isInitialized = true; + } +} +``` + +#### 13.5 职责分离和重复检查 + +```csharp +// ✅ 事件处理器包含重复检查逻辑 +private async void OnPathPointsListUpdated(object sender, PathPointsListUpdatedEventArgs e) +{ + if (e?.Route == null) return; + + await SafeExecuteAsync(() => + { + var pathViewModel = PathRoutes.FirstOrDefault(p => p.Name == e.Route.Name); + if (pathViewModel != null) + { + // 关键:检查是否需要更新,避免重复处理 + if (pathViewModel.Points.Count == e.Route.Points.Count) + { + LogManager.Info($"路径点数量已正确({pathViewModel.Points.Count}),跳过重复更新"); + return; + } + + // 执行实际更新 + pathViewModel.Points.Clear(); + foreach (var point in e.Route.Points) + { + // 添加路径点... + } + } + }, "处理路径点列表更新事件"); +} +``` + +### 关键经验教训 + +#### 13.6 过度工程的陷阱 + +**问题**:试图通过复杂的自定义机制"改进"框架的标准行为 +**结果**:引入了与框架机制的冲突,造成更多问题 +**教训**:WPF的`ObservableCollection`已经是充分测试的成熟解决方案 + +```csharp +// ❌ 过度设计:复杂的自定义线程安全集合 +public class ThreadSafeObservableCollection : ObservableCollection +{ + private readonly UIStateManager _uiStateManager; + private readonly object _lockObject = new object(); + + protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + { + // 复杂的线程安全逻辑 + if (_uiStateManager != null) + { + _uiStateManager.QueueUIUpdate(() => base.OnCollectionChanged(e)); + } + // 与WPF绑定机制产生冲突 + } +} + +// ✅ 简单有效:使用标准解决方案 +public ObservableCollection Items { get; private set; } = new ObservableCollection(); +``` + +#### 13.7 线程安全的正确处理方式 + +在WPF中,正确的线程安全做法是: +- **数据操作**在适当的线程中执行 +- **UI更新**统一通过`Dispatcher.Invoke`或`UIStateManager`在UI线程执行 +- **不要在集合层面**实现线程安全,而是在操作层面控制 + +```csharp +// ✅ 正确的线程安全模式 +public async Task AddPathAsync(PathRouteViewModel path) +{ + await _uiStateManager.ExecuteUIUpdateAsync(() => + { + PathRoutes.Add(path); // 在UI线程上操作标准集合 + }); +} +``` + +#### 13.8 调试复杂问题的方法论 + +从这个案例中学到的调试方法: +1. **详细日志追踪**:记录事件时序和调用栈 +2. **识别异步副作用**:关注"火后不理"的异步调用 +3. **分析处理机制重叠**:多个组件处理相同数据的情况 +4. **回归简单方案**:当复杂方案出问题时,考虑标准做法 + +### 设计原则总结 + +#### 13.9 集合使用原则 + +```csharp +// ✅ WPF UI绑定:使用标准ObservableCollection +public ObservableCollection Items { get; private set; } + = new ObservableCollection(); + +// ✅ 后台数据处理:可以使用线程安全集合 +private readonly ConcurrentBag _processingQueue + = new ConcurrentBag(); + +// ✅ UI更新时:统一在UI线程操作 +public async Task UpdateUI(List newData) +{ + await _uiStateManager.ExecuteUIUpdateAsync(() => + { + Items.Clear(); + foreach (var item in newData) + { + Items.Add(new ItemViewModel(item)); + } + }); +} +``` + +#### 13.10 ViewModel设计原则 + +```csharp +// ✅ 保持ViewModel简单和同步 +public class SimpleViewModel : ViewModelBase +{ + public SimpleViewModel() + { + // 同步初始化,避免复杂的异步逻辑 + InitializeProperties(); + SubscribeToEvents(); + } + + // ✅ 使用标准属性更改通知 + private string _status; + public string Status + { + get => _status; + set => SetProperty(ref _status, value); + } +} + +// ❌ 避免复杂的异步初始化 +public class ComplexViewModel : ViewModelBase +{ + public ComplexViewModel() + { + _ = InitializeAsync(); // 导致时序问题 + } + + private async Task InitializeAsync() + { + // 复杂的异步初始化逻辑 + // 可能与UI绑定产生冲突 + } +} +``` + +### 适用场景和建议 + +#### 13.11 何时使用标准集合 + +✅ **使用ObservableCollection的场景**: +- WPF数据绑定 +- UI列表显示 +- 用户交互集合 +- ViewModel中的集合属性 + +✅ **使用线程安全集合的场景**: +- 后台数据处理 +- 多线程生产者-消费者模式 +- 缓存和队列 +- 非UI相关的数据结构 + +#### 13.12 实施检查清单 + +在代码审查中重点检查: +- [ ] ViewModel中的集合是否使用标准`ObservableCollection`? +- [ ] 是否避免了"过度设计"的自定义集合? +- [ ] UI更新是否统一在UI线程执行? +- [ ] 是否存在多个机制处理相同数据的情况? +- [ ] 异步初始化是否真的必要? + +### 结论 + +这个UI重复问题的根本原因是**试图通过自定义的`ThreadSafeObservableCollection`来"改进"WPF的标准数据绑定,但这种改进引入了与框架机制的冲突,导致重复的UI更新和不可预测的行为**。 + +解决方案是**回归WPF的最佳实践,使用标准集合和框架提供的机制**。这个案例很好地说明了在软件开发中,简单、标准的解决方案往往比复杂的自定义方案更可靠。 + +## 14. 线程安全实践经验总结 ### 问题描述 diff --git a/doc/working/ThreadSafeObservableCollection_Migration_Plan.md b/doc/working/ThreadSafeObservableCollection_Migration_Plan.md new file mode 100644 index 0000000..ab7bb8b --- /dev/null +++ b/doc/working/ThreadSafeObservableCollection_Migration_Plan.md @@ -0,0 +1,323 @@ +# ThreadSafeObservableCollection 全面迁移计划 + +## 问题背景 + +在解决UI重复显示问题的过程中,我们发现了 `ThreadSafeObservableCollection` 与WPF标准数据绑定机制不兼容的根本问题。虽然已经在关键文件(`PathEditingViewModel` 和 `PathRouteViewModel`)中成功修复,但项目中仍有大量使用 `ThreadSafeObservableCollection` 的地方需要评估和迁移。 + +## 使用现状分析 + +通过代码搜索发现,项目中 `ThreadSafeObservableCollection` 的使用分布如下: + +### 使用位置统计 +- **ViewModels**: 8个文件使用了约15处 + - `LogisticsControlViewModel.cs` (4处) + - `LayerManagementViewModel.cs` (5处) + - `ModelSettingsViewModel.cs` (3处) + - `AnimationControlViewModel.cs` (1处) + - `SystemManagementViewModel.cs` (1处) + - `LogisticsControlViewModelcopy.cs` (5处) + - ✅ `PathEditingViewModel.cs` (已迁移) + - ✅ `PathRouteViewModel.cs` (已迁移) + +- **核心基础设施**: + - `src/UI/WPF/Collections/ThreadSafeObservableCollection.cs` - 核心实现 + - `src/Core/UIUpdate/Updates/CollectionUpdateOperation.cs` - UIUpdate系统支持 + - `src/UI/WPF/Services/DataBindingBestPractices.cs` - 最佳实践指导 + +- **测试文件**: + - `UnitTests/Collections/ThreadSafeObservableCollectionBasicTests.cs` + +## 核心问题分析 + +### 1. WPF数据绑定冲突 +`ThreadSafeObservableCollection` 试图提供线程安全,通过内部机制自动将变更marshaling到UI线程,但与WPF数据绑定期望的标准机制产生冲突: + +```csharp +// ❌ 问题:双重UI线程处理机制 +ThreadSafeObservableCollection 内部处理 + WPF数据绑定机制 = 重复UI更新 +``` + +### 2. 过度工程陷阱 +自定义的线程安全集合引入了与框架机制的冲突,造成比解决的问题更多的问题。 + +## 迁移策略 + +### Phase 1: 分类评估 (不破坏现有功能) + +#### 1.1 需要迁移的场景 - WPF UI绑定 +**原则**: 用于WPF数据绑定的集合应使用标准 `ObservableCollection` + +**需要迁移的文件**: +- ✅ `PathEditingViewModel.cs` (已完成) +- ✅ `PathRouteViewModel.cs` (已完成) +- `LogisticsControlViewModel.cs` +- `LayerManagementViewModel.cs` +- `ModelSettingsViewModel.cs` +- `AnimationControlViewModel.cs` +- `SystemManagementViewModel.cs` + +#### 1.2 建议保留的场景 - 后台数据处理 +**原则**: 非UI绑定的数据集合可以继续使用线程安全集合 + +**保留使用的场景**: +- 多线程后台处理的临时数据 +- 缓存和队列等数据结构 +- 不直接绑定到UI的数据管理 + +### Phase 2: 渐进式迁移 (确保稳定性) + +#### 迁移步骤模板 + +对于每个需要迁移的ViewModel: + +1. **准备工作** + ```bash + # 备份当前文件 + copy OriginalViewModel.cs OriginalViewModel.cs.backup + ``` + +2. **代码修改** + ```csharp + // 替换集合声明 + // ❌ 修改前 + public ThreadSafeObservableCollection Items { get; private set; } + = new ThreadSafeObservableCollection(); + + // ✅ 修改后 + public ObservableCollection Items { get; private set; } + = new ObservableCollection(); + ``` + +3. **简化初始化** + ```csharp + // ❌ 复杂的异步初始化 + public ViewModel() + { + InitializeDefaults(); + _ = InitializeAsync(); // "火后不理"的异步调用 + } + + // ✅ 简单的同步初始化 + public ViewModel() + { + InitializeDefaults(); + CompleteInitialization(); + } + ``` + +4. **添加重复检查** + ```csharp + // ✅ 事件处理器包含重复检查逻辑 + private async void OnDataUpdated(object sender, DataUpdatedEventArgs e) + { + if (e?.Data == null) return; + + await SafeExecuteAsync(() => + { + // 关键:检查是否需要更新,避免重复处理 + if (Items.Count == e.Data.Count) + { + LogManager.Info($"数据数量已正确({Items.Count}),跳过重复更新"); + return; + } + + // 执行实际更新 + Items.Clear(); + foreach (var item in e.Data) + { + Items.Add(item); + } + }, "处理数据更新事件"); + } + ``` + +5. **测试验证** + - 编译测试:确保无编译错误 + - 功能测试:验证UI显示正常,无重复数据 + - 性能测试:确认UI响应性能 + +#### 迁移优先级 + +1. **高优先级** - 直接用于WPF数据绑定,有UI重复显示风险 + - `LogisticsControlViewModel.cs` + - `LayerManagementViewModel.cs` + - `ModelSettingsViewModel.cs` + +2. **中优先级** - 间接影响UI显示 + - `AnimationControlViewModel.cs` + - `SystemManagementViewModel.cs` + +3. **低优先级** - 纯后台数据处理,无直接UI影响 + - 保留现有实现或根据具体需求决定 + +### Phase 3: 架构优化 (长期改进) + +#### 3.1 使用原则制定 + +建立清晰的集合选择原则: + +```csharp +// ✅ WPF UI绑定场景 +public ObservableCollection UIBoundItems { get; private set; } + = new ObservableCollection(); + +// ✅ 后台数据处理场景 +private readonly ConcurrentBag _processingQueue + = new ConcurrentBag(); + +// ✅ UI更新统一处理 +public async Task UpdateUI(List newData) +{ + await _uiStateManager.ExecuteUIUpdateAsync(() => + { + UIBoundItems.Clear(); + foreach (var item in newData) + { + UIBoundItems.Add(item); + } + }); +} +``` + +#### 3.2 重构建议 + +1. **保持向后兼容** + - 继续维护 `ThreadSafeObservableCollection` 类 + - 保留 `CollectionUpdateOperation` 的支持 + - 更新文档说明使用场景 + +2. **架构清晰化** + - WPF UI绑定 → 标准 `ObservableCollection` + - 后台数据处理 → `ThreadSafeObservableCollection` 或 `ConcurrentCollection` + - 线程安全更新 → 统一通过 `UIStateManager` 处理 + +## 详细迁移计划 + +### 第一批迁移: LogisticsControlViewModel +```csharp +// 需要迁移的属性: +- LogisticsModels (ThreadSafeObservableCollection) +- AvailableCategories (ThreadSafeObservableCollection) +- AvailableFrameRates (ThreadSafeObservableCollection) +``` + +**验证项目**: +- [ ] 物流模型显示正常 +- [ ] 分类选择功能正常 +- [ ] 帧率设置功能正常 +- [ ] 无UI重复显示问题 + +### 第二批迁移: LayerManagementViewModel +```csharp +// 需要迁移的属性: +- AvailableAttributes (ThreadSafeObservableCollection) +- DepthOptions (ThreadSafeObservableCollection) +- SplitStrategies (ThreadSafeObservableCollection) +- SplitPreviewResults (ThreadSafeObservableCollection) +``` + +**验证项目**: +- [ ] 图层管理界面显示正常 +- [ ] 属性选择功能正常 +- [ ] 模型分割预览功能正常 +- [ ] 分割策略选择正常 + +### 第三批迁移: ModelSettingsViewModel +```csharp +// 需要迁移的属性: +- AvailableCategories (ThreadSafeObservableCollection) +- PriorityLevels (ThreadSafeObservableCollection) +- LogisticsModels (ThreadSafeObservableCollection) +``` + +**验证项目**: +- [ ] 模型设置界面显示正常 +- [ ] 分类设置功能正常 +- [ ] 优先级设置功能正常 + +### 第四批迁移: 其他ViewModels +- `AnimationControlViewModel.cs` - AvailableFrameRates +- `SystemManagementViewModel.cs` - LogLevels + +## 风险评估与缓解措施 + +### 风险1: UI性能问题 +**风险等级**: 中等 +**缓解措施**: +- 分阶段迁移,每次迁移后进行性能测试 +- 监控UI响应时间,如有问题及时回滚 + +### 风险2: 多线程并发问题 +**风险等级**: 中等 +**缓解措施**: +- 保留UIStateManager机制,确保UI更新线程安全 +- 对非UI绑定的数据处理场景保留ThreadSafeObservableCollection + +### 风险3: 现有功能破坏 +**风险等级**: 低 +**缓解措施**: +- 每个ViewModel迁移完成后进行完整功能测试 +- 保留备份文件,支持快速回滚 +- 渐进式迁移,一次只处理一个ViewModel + +### 风险4: 团队学习成本 +**风险等级**: 低 +**缓解措施**: +- 提供清晰的迁移指南和代码示例 +- 在设计文档中明确新的使用原则 + +## 预期收益 + +### 短期收益 +1. **消除UI重复显示问题** - 避免双重UI更新机制冲突 +2. **提高代码可靠性** - 使用经过充分测试的标准WPF机制 +3. **简化调试过程** - 减少自定义机制带来的复杂性 + +### 长期收益 +1. **遵循WPF最佳实践** - 与框架设计理念保持一致 +2. **提高代码可维护性** - 减少自定义实现的维护负担 +3. **改善团队开发效率** - 新团队成员更容易理解标准WPF模式 + +## 实施时间表 + +**建议时间安排**: +- Phase 1 (评估阶段): 已完成 +- Phase 2 (渐进式迁移): 2-4周 + - 第一批迁移: 1周 + - 第二批迁移: 1周 + - 第三、四批迁移: 1-2周 +- Phase 3 (架构优化): 1周 + - 文档更新和代码清理 + +**里程碑检查点**: +- 每批迁移完成后进行功能验证 +- 所有迁移完成后进行全面集成测试 +- 最终进行性能和稳定性测试 + +## 结论 + +这个迁移计划基于实际发现的UI重复显示问题,采用渐进式、风险可控的方式,在保证现有功能正常的前提下,逐步解决架构问题,最终建立更健壮、更符合WPF最佳实践的集合管理机制。 + +通过这次迁移,我们不仅解决了具体的技术问题,更重要的是建立了"简单、标准的解决方案往往比复杂的自定义方案更可靠"的架构理念。 + +--- + +## 附录 + +### A. 相关文档 +- [设计原则文档 - WPF数据绑定最佳实践](../guide/design_principles.md#13-wpf数据绑定最佳实践避免自定义集合陷阱) +- [ThreadSafeObservableCollection实现总结](../memories/threadsafe_observable_collection_implementation.md) + +### B. 参考代码示例 +参考已成功迁移的文件: +- `src/UI/WPF/ViewModels/PathEditingViewModel.cs` +- `src/UI/WPF/Models/PathRouteViewModel.cs` + +### C. 迁移检查清单 +- [ ] 集合声明已更改为ObservableCollection +- [ ] 构造函数已简化为同步初始化 +- [ ] 事件处理器包含重复检查逻辑 +- [ ] 编译无错误无警告 +- [ ] UI显示功能正常 +- [ ] 无重复数据显示 +- [ ] 性能无明显退化 \ No newline at end of file diff --git a/src/Commands/ImportPathCommand.cs b/src/Commands/ImportPathCommand.cs index d77f9a4..5bd3319 100644 --- a/src/Commands/ImportPathCommand.cs +++ b/src/Commands/ImportPathCommand.cs @@ -507,34 +507,6 @@ namespace NavisworksTransport.Commands UpdateProgress(85, "正在完成导入操作..."); ThrowIfCancellationRequested(cancellationToken); - // 第六阶段:后处理(90%) - UpdateProgress(90, "正在完成导入后处理..."); - - try - { - await _uiStateManager.ExecuteUIUpdateAsync(() => - { - // 自动选择第一个导入的路径为当前路径 - if (_parameters.AutoSelectFirstRoute && result.ImportedCount > 0) - { - var firstImportedRoute = _pathPlanningManager.Routes - .Where(r => result.ImportedPaths.Any(ip => ip.StartsWith(r.Name))) - .FirstOrDefault(); - - if (firstImportedRoute != null) - { - _pathPlanningManager.SetCurrentRoute(firstImportedRoute); - LogInfo($"已自动选择路径为当前路径: {firstImportedRoute.Name}"); - } - } - LogInfo("UI状态已刷新"); - }); - } - catch (Exception refreshEx) - { - LogWarning($"刷新UI状态失败: {refreshEx.Message}"); - } - // 完成(100%) UpdateProgress(100, "路径导入完成"); diff --git a/src/Core/MainPlugin.cs b/src/Core/MainPlugin.cs index 0914061..1c61c0d 100644 --- a/src/Core/MainPlugin.cs +++ b/src/Core/MainPlugin.cs @@ -2918,19 +2918,8 @@ namespace NavisworksTransport refreshPathList(); // 初始加载 - // 监听路径生成事件,自动刷新路径列表 - var activePathManager = PathPlanningManager.GetActivePathManager(); - if (activePathManager != null) - { - activePathManager.RouteGenerated += (sender, route) => - { - GlobalExceptionHandler.SafeExecute(() => - { - LogManager.Info($"检测到新路径生成:{route?.Name},自动刷新动画控制面板路径列表"); - refreshPathList(); - }, "路径生成事件处理"); - }; - } + // 注意:路径生成事件的UI更新已由PathEditingViewModel处理,避免重复订阅 + // 如果需要特殊的动画面板刷新逻辑,应该通过其他方式实现 // 生成动画事件处理 createAnimationButton.Click += (sender, e) => diff --git a/src/Core/PathPlanningManager.cs b/src/Core/PathPlanningManager.cs index 249f037..91b5054 100644 --- a/src/Core/PathPlanningManager.cs +++ b/src/Core/PathPlanningManager.cs @@ -988,14 +988,15 @@ namespace NavisworksTransport _routes.Add(route); RaiseStatusChanged($"已添加路径: {route.Name}", PathPlanningStatusType.Success); - // 如果当前没有活动路径,设置此路径为活动路径 - if (_currentRoute == null || _currentRoute.Points.Count == 0) - { - CurrentRoute = route; - } + // AddRoute只负责添加路径到数据集合,不自动设置当前路径 + // 路径选择应该通过专门的选择逻辑处理,而不是添加操作的副作用 + // if (_currentRoute == null || _currentRoute.Points.Count == 0) + // { + // CurrentRoute = route; + // } - // 触发路径生成事件 - RaiseRouteGenerated(route, RouteGenerationMethod.Manual); + // 注释掉多余的路径生成事件调用,Manual类型路径通过UI层的RefreshPathRoutes()统一刷新 + // RaiseRouteGenerated(route, RouteGenerationMethod.Manual); return true; } diff --git a/src/UI/WPF/LogisticsControlPanel.xaml b/src/UI/WPF/LogisticsControlPanel.xaml index 0dd91b6..7a697eb 100644 --- a/src/UI/WPF/LogisticsControlPanel.xaml +++ b/src/UI/WPF/LogisticsControlPanel.xaml @@ -1,3 +1,15 @@ + + + + + + + + + + + @@ -13,32 +35,60 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - -