修改Clashdetective进度条不展示百分比,以及不能取消的问题;进度条关闭主窗口失焦问题

This commit is contained in:
tian 2026-02-14 22:22:51 +08:00
parent 3138b73a5f
commit a9ccc057d3
4 changed files with 433 additions and 12 deletions

View File

@ -4218,3 +4218,220 @@ public static class ViewpointHelper
**文档版本**2026.2
**最后更新**:基于 ViewpointHelper 重构经验(聚焦功能实现)
**适用版本**Navisworks Manage 2026
## 15. Progress 进度条与窗口焦点管理 ⚠️ 重要
### 15.1 问题现象
在使用 `Application.BeginProgress` 创建进度条后,如果在 `finally` 块中关闭进度条并立即显示消息对话框,会出现以下问题:
1. **焦点丢失**关闭进度条后Navisworks 主窗口失去焦点Windows 将焦点交给其他应用程序
2. **消息框被遮盖**:消息对话框显示在其他应用程序后面,用户需要点击任务栏才能看到
3. **用户体验差**:用户以为程序卡死或无响应
### 15.2 根本原因
Navisworks 的 `Progress` 是一个**应用程序级别的模态对话框**
```
进度条关闭时:
1. Windows 焦点管理器需要决定下一个获得焦点的窗口
2. 如果 Navisworks 主窗口之前被进度条遮盖/禁用
3. 关闭后焦点可能没有正确回到主窗口
4. Windows 可能将焦点交给其他应用程序(如资源管理器、浏览器等)
```
### 15.3 错误做法
```csharp
// ❌ 错误:先关闭进度条,后显示消息框
try
{
// 执行长时间操作...
ShowMessage("操作完成"); // 先显示消息
}
finally
{
Application.EndProgress(); // 后关闭进度条
}
// 结果:消息框显示在进度条后面,进度条关闭时焦点丢失
```
### 15.4 正确做法
**解决方案:在关闭进度条之前,先显式激活 Navisworks 主窗口**
```csharp
// ✅ 正确:先激活主窗口,再关闭进度条,最后显示消息框
using System.Runtime.InteropServices;
public class ClashDetectiveIntegration
{
// Windows API: 设置前台窗口
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
public void LongRunningOperation()
{
Progress progress = Application.BeginProgress("正在处理...", "请稍候");
try
{
// 执行长时间操作...
RunCollisionDetection(progress);
}
finally
{
// 🔥 关键:先激活 Navisworks 主窗口
try
{
var mainWindow = Autodesk.Navisworks.Api.Application.Gui.MainWindow;
if (mainWindow != null)
{
SetForegroundWindow(mainWindow.Handle);
}
}
catch { /* 忽略激活失败 */ }
// 然后关闭进度条
Application.EndProgress();
}
// 最后显示消息对话框(在主窗口激活后)
ShowCancellationMessage();
}
private void ShowCancellationMessage()
{
// 使用主窗口作为父窗口
var mainWindow = System.Windows.Forms.Form.FromHandle(
Autodesk.Navisworks.Api.Application.Gui.MainWindow.Handle);
System.Windows.Forms.MessageBox.Show(
mainWindow, // 指定父窗口
"操作已取消。",
"取消",
System.Windows.Forms.MessageBoxButtons.OK,
System.Windows.Forms.MessageBoxIcon.Information);
}
}
```
### 15.5 实际应用案例
**场景:碰撞检测取消后的处理**
```csharp
public bool CreateAllAnimationCollisionTests(...)
{
// 重置取消标志
_wasLastTestCanceled = false;
Progress progress = Application.BeginProgress("碰撞检测数据分析中,请稍候...");
bool wasCanceled = false;
try
{
var result = RunClashDetectiveTestsAndSaveToDatabase(..., progress);
wasCanceled = result.wasCanceled;
if (wasCanceled)
{
// 设置取消标志
_wasLastTestCanceled = true;
// 清空结果
ClearCurrentTestCache();
// 恢复物体状态
PathAnimationManager.GetInstance().RestoreAnimatedObjectState(animatedObject);
// 重置动画完成标志
ResetAnimationCompletedFlag(routeId);
}
}
finally
{
// 🔥 关键:先激活 Navisworks 主窗口(避免关闭进度条后丢失焦点)
try
{
var mainWindow = Autodesk.Navisworks.Api.Application.Gui.MainWindow;
if (mainWindow != null)
{
SetForegroundWindow(mainWindow.Handle);
}
}
catch (Exception ex)
{
LogManager.Debug($"[ClashDetective] 激活主窗口失败: {ex.Message}");
}
// 关闭进度条
Application.EndProgress();
}
// 如果取消,在 finally 之后显示提示消息
if (wasCanceled)
{
ShowCancellationMessage();
return false;
}
return true;
}
```
### 15.6 关键要点总结
| 步骤 | 操作 | 目的 |
|------|------|------|
| 1 | `SetForegroundWindow(mainWindow.Handle)` | 显式激活 Navisworks 主窗口 |
| 2 | `Application.EndProgress()` | 关闭进度条 |
| 3 | `MessageBox.Show(mainWindow, ...)` | 显示消息对话框(使用父窗口) |
**为什么这个顺序有效?**
1. **激活主窗口**:在进度条关闭前,先让主窗口成为活动窗口
2. **关闭进度条**:进度条关闭时,焦点会回到已经激活的主窗口
3. **显示消息框**:消息框作为主窗口的子窗口,自然显示在最前面
### 15.7 常见错误
```csharp
// ❌ 错误1先显示消息框后关闭进度条
try { }
finally
{
ShowMessage("完成"); // 消息框在进度条前显示
Application.EndProgress(); // 关闭进度条时消息框也消失
}
// ❌ 错误2只关闭进度条不激活主窗口
try { }
finally
{
Application.EndProgress(); // 焦点丢失
}
ShowMessage("完成"); // 消息框显示在其他程序后面
// ❌ 错误3使用延时等待
finally
{
Application.EndProgress();
Thread.Sleep(100); // 不必要的延时
Application.DoEvents();
}
```
### 15.8 最佳实践
1. **始终使用 `SetForegroundWindow`** 在关闭进度条前激活主窗口
2. **指定父窗口**`MessageBox.Show` 中使用 `mainWindow` 参数
3. **正确处理异常** 激活失败不应影响主要功能
4. **保持代码简洁** 不需要 Thread.Sleep 等延时操作
5. **测试验证** 在取消操作后验证焦点是否正确回到 Navisworks
---
*最后更新2026-02-14 - 添加了 Progress 进度条焦点管理的实践经验*

View File

@ -1681,10 +1681,11 @@ namespace NavisworksTransport.Core.Animation
var oldCursor = System.Windows.Input.Mouse.OverrideCursor;
System.Windows.Input.Mouse.OverrideCursor = System.Windows.Input.Cursors.Wait;
bool testCompleted = false;
try
{
// 🔥 使用用户设置的检测容差(米)
ClashDetectiveIntegration.Instance.CreateAllAnimationCollisionTests(
testCompleted = ClashDetectiveIntegration.Instance.CreateAllAnimationCollisionTests(
_allCollisionResults,
_detectionTolerance,
_currentRouteId,
@ -1704,8 +1705,16 @@ namespace NavisworksTransport.Core.Animation
System.Windows.Input.Mouse.OverrideCursor = oldCursor;
}
_completedCollisionTests.Add(_currentAnimationHash); // 记录此配置已完成碰撞检测
LogManager.Info($"碰撞测试汇总已创建并记录,此配置后续播放将跳过碰撞检测");
// 只有在检测成功完成(未被取消)的情况下才记录为已完成
if (testCompleted)
{
_completedCollisionTests.Add(_currentAnimationHash); // 记录此配置已完成碰撞检测
LogManager.Info($"碰撞测试汇总已创建并记录,此配置后续播放将跳过碰撞检测");
}
else
{
LogManager.Info("碰撞检测被取消,不记录为已完成,允许下次重新检测");
}
}
else if (_completedCollisionTests.Contains(_currentAnimationHash))
{

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Autodesk.Navisworks.Api;
using Autodesk.Navisworks.Api.Clash;
using NavisworksTransport.Core;
@ -15,6 +16,10 @@ namespace NavisworksTransport
/// </summary>
public class ClashDetectiveIntegration
{
// Windows API: 设置前台窗口
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
private static ClashDetectiveIntegration _instance;
private DocumentClash _documentClash;
@ -71,6 +76,17 @@ namespace NavisworksTransport
private string _currentTestName;
// 🔥 检测取消标志
private bool _wasLastTestCanceled = false;
/// <summary>
/// 上次检测是否被取消
/// </summary>
public bool WasLastTestCanceled
{
get { return _wasLastTestCanceled; }
}
/// <summary>
/// 单例实例
/// </summary>
@ -663,8 +679,8 @@ namespace NavisworksTransport
/// <summary>
/// 运行ClashDetective测试并保存到数据库公共方法供批处理和非批处理调用
/// </summary>
/// <returns>碰撞分组、主测试、确认的碰撞数量</returns>
public (ClashResultGroup collisionGroup, ClashTest addedMainTest, int confirmedCount) RunClashDetectiveTestsAndSaveToDatabase(
/// <returns>碰撞分组、主测试、确认的碰撞数量、是否被取消</returns>
public (ClashResultGroup collisionGroup, ClashTest addedMainTest, int confirmedCount, bool wasCanceled) RunClashDetectiveTestsAndSaveToDatabase(
List<CollisionResult> precomputedCollisions,
double detectionGap,
string routeId,
@ -719,7 +735,7 @@ namespace NavisworksTransport
if (addedMainTest == null)
{
LogManager.Error("[分组测试] 无法获取添加后的主测试对象");
return (null, null, 0);
return (null, null, 0, false);
}
// 如果没有有效碰撞,直接保存空记录并返回
@ -745,7 +761,7 @@ namespace NavisworksTransport
precomputedCollisionCount
);
return (null, addedMainTest, 0);
return (null, addedMainTest, 0, false);
}
// 第二步:创建分组并添加碰撞结果(应用智能去重)
@ -781,11 +797,17 @@ namespace NavisworksTransport
int skippedCount = 0;
int testCount = 0; // 检测计数器,用于进度日志
int totalTests = validCollisions.Count; // 总检测次数(预估)
int totalGroups = groupedCollisions.Count; // 总组数
var doc = Application.ActiveDocument;
// 初始化进度条
progress?.Update(0.0);
// 收集所有临时测试,用于批量删除
var tempTestsToRemove = new List<ClashTest>();
bool wasCanceled = false;
// 2. 遍历每一组
for (int groupIndex = 0; groupIndex < groupedCollisions.Count; groupIndex++)
{
@ -793,9 +815,17 @@ namespace NavisworksTransport
if (progress != null && progress.IsCanceled)
{
LogManager.Info($"[分组测试] 用户取消操作,已处理 {groupIndex}/{groupedCollisions.Count} 组");
wasCanceled = true;
break;
}
// 更新进度条每5组更新一次避免过于频繁
if (groupIndex % 5 == 0 || groupIndex == groupedCollisions.Count - 1)
{
double progressPercent = (double)groupIndex / totalGroups * 0.7; // 组进度占70%
progress?.Update(progressPercent);
}
var group = groupedCollisions[groupIndex];
// 3. 排序:按距离/重叠深度排序,优先检测最严重的碰撞
@ -898,12 +928,20 @@ namespace NavisworksTransport
_documentClash.TestsData.TestsRunTest(addedTempTest);
// 增加检测计数并打印进度日志(每100次
// 增加检测计数并打印进度日志(每50次更新一次进度条100次打印日志
testCount++;
if (testCount % 50 == 0)
{
// 基于总检测次数计算进度组进度占70%检测进度占30%
double groupProgress = (double)(groupIndex + 1) / totalGroups * 0.7;
double testProgress = (double)testCount / totalTests * 0.3;
double totalProgress = Math.Min(groupProgress + testProgress, 0.99);
progress?.Update(totalProgress);
}
if (testCount % 100 == 0)
{
double progressPercent = (double)testCount / totalTests * 100;
LogManager.Info($"[ClashDetective进度] 已执行 {testCount}/{totalTests} 次检测 ({progressPercent:F1}%)");
double logProgressPercent = (double)testCount / totalTests * 100;
LogManager.Info($"[ClashDetective进度] 已执行 {testCount}/{totalTests} 次检测 ({logProgressPercent:F1}%)");
}
var refreshedTempTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == tempTestName) as ClashTest;
@ -966,6 +1004,12 @@ namespace NavisworksTransport
}
}
// 如果用户取消,直接返回,不执行后续操作(临时测试将在下方统一清理)
if (wasCanceled)
{
LogManager.Info($"[分组测试] 检测已被用户取消,跳过结果处理和保存");
}
// 🔥 优化4批量删除所有临时测试
if (tempTestsToRemove.Count > 0)
{
@ -983,6 +1027,15 @@ namespace NavisworksTransport
}
}
// 如果用户取消,直接返回,不执行后续操作
if (wasCanceled)
{
return (null, addedMainTest, confirmedCount, true);
}
// 完成进度条
progress?.Update(1.0);
LogManager.Info($"[分组测试] ClashDetective检测完成: 执行 {testCount} 次检测, 确认碰撞 {confirmedCount} 组, 跳过 {skippedCount} 个冗余检测点");
// 第三步:处理碰撞结果
@ -1067,7 +1120,7 @@ namespace NavisworksTransport
LogManager.Info($"[ClashDetective] 结果已保存到数据库");
// 第四步:将分组添加到主测试(由调用方完成)
return (collisionGroup, addedMainTest, confirmedCount);
return (collisionGroup, addedMainTest, confirmedCount, false);
}
/// <summary>
@ -1084,7 +1137,11 @@ namespace NavisworksTransport
/// <param name="virtualVehicleWidth">虚拟车辆宽度(米)</param>
/// <param name="virtualVehicleHeight">虚拟车辆高度(米)</param>
/// <param name="pathPoints">路径点列表(用于测试完成后恢复物体位置)</param>
public void CreateAllAnimationCollisionTests(
/// <summary>
/// 创建并运行ClashDetective碰撞测试
/// </summary>
/// <returns>true = 检测完成false = 用户取消</returns>
public bool CreateAllAnimationCollisionTests(
List<CollisionResult> precomputedCollisions,
double detectionGap,
string routeId,
@ -1097,6 +1154,9 @@ namespace NavisworksTransport
double virtualVehicleHeight,
List<Point3D> pathPoints = null)
{
// 重置取消标志
_wasLastTestCanceled = false;
try
{
LogManager.Info($"=== 使用预计算碰撞数据创建ClashDetective测试容差: {detectionGap}米)===");
@ -1129,6 +1189,7 @@ namespace NavisworksTransport
ClashTest addedMainTest = null;
ClashResultGroup collisionGroup = null;
int confirmedCount = 0;
bool wasCanceled = false;
try
{
@ -1149,12 +1210,61 @@ namespace NavisworksTransport
collisionGroup = result.collisionGroup;
addedMainTest = result.addedMainTest;
confirmedCount = result.confirmedCount;
wasCanceled = result.wasCanceled;
// 检查是否被取消
if (wasCanceled)
{
LogManager.Info("[CreateAllAnimationCollisionTests] 用户取消了碰撞检测");
// 设置取消标志
_wasLastTestCanceled = true;
// 清空当前测试结果,避免取消后显示旧结果
ClearCurrentTestCache();
_currentTestName = null;
_clashDetectiveCollisionCount = 0;
// 恢复物体状态
if (animatedObject != null && IsModelItemValid(animatedObject))
{
PathAnimationManager.GetInstance().RestoreAnimatedObjectState(animatedObject);
}
// 重置动画完成标志,允许重新运行动画
ResetAnimationCompletedFlag(routeId);
}
}
finally
{
// 先激活 Navisworks 主窗口(避免关闭进度条后丢失焦点)
try
{
var mainWindow = Autodesk.Navisworks.Api.Application.Gui.MainWindow;
if (mainWindow != null)
{
// 使用 Windows API 激活窗口
SetForegroundWindow(mainWindow.Handle);
LogManager.Debug("[ClashDetective] 已激活 Navisworks 主窗口");
}
}
catch (Exception ex)
{
LogManager.Debug($"[ClashDetective] 激活主窗口失败: {ex.Message}");
}
// 关闭进度条
Application.EndProgress();
}
// 如果取消,显示提示消息后返回
if (wasCanceled)
{
// 在 finally 之后显示消息对话框
ShowCancellationMessage();
return false; // 返回取消状态
}
// UI操作将分组添加到主测试
if (collisionGroup != null && addedMainTest != null && collisionGroup.Children.Count > 0)
{
@ -1199,6 +1309,8 @@ namespace NavisworksTransport
{
LogManager.Error($"动画结束后创建测试失败: {ex.Message}");
}
return !_wasLastTestCanceled; // 返回是否成功完成(未被取消)
}
/// <summary>
@ -1248,6 +1360,67 @@ namespace NavisworksTransport
}
}
/// <summary>
/// 显示碰撞检测取消提示消息
/// </summary>
private void ShowCancellationMessage()
{
try
{
// 获取 Navisworks 主窗口句柄作为父窗口
var mainWindow = System.Windows.Forms.Form.FromHandle(
Autodesk.Navisworks.Api.Application.Gui.MainWindow.Handle);
// 使用 Windows Forms 显示提示框,指定父窗口保持焦点
System.Windows.Forms.MessageBox.Show(
mainWindow,
"碰撞检测已取消。\n\n您可以重新运行动画来重新检测碰撞。",
"检测取消",
System.Windows.Forms.MessageBoxButtons.OK,
System.Windows.Forms.MessageBoxIcon.Information);
LogManager.Info("[ClashDetective] 已显示取消提示消息");
}
catch (Exception ex)
{
// 如果获取父窗口失败,使用默认方式
System.Windows.Forms.MessageBox.Show(
"碰撞检测已取消。\n\n您可以重新运行动画来重新检测碰撞。",
"检测取消",
System.Windows.Forms.MessageBoxButtons.OK,
System.Windows.Forms.MessageBoxIcon.Information);
LogManager.Info("[ClashDetective] 已显示取消提示消息(无父窗口)");
}
}
/// <summary>
/// 重置动画完成标志,允许重新运行动画进行碰撞检测
/// </summary>
/// <param name="routeId">路径ID</param>
private void ResetAnimationCompletedFlag(string routeId)
{
try
{
// 从已完成检测集合中移除该路径的哈希,允许重新检测
var animManager = PathAnimationManager.GetInstance();
if (animManager != null)
{
// 先尝试清除当前动画配置的检测记录
animManager.ForceRecreateCollisionTest();
// 同时清除所有记录(确保万无一失)
PathAnimationManager.ClearAllCollisionTestRecords();
LogManager.Info($"[ClashDetective] 已重置动画完成标志允许重新检测。路径ID: {routeId}");
}
}
catch (Exception ex)
{
LogManager.Error($"重置动画完成标志失败: {ex.Message}");
}
}
/// <summary>
/// 检查ModelItem是否仍然有效
/// </summary>
@ -1352,6 +1525,18 @@ namespace NavisworksTransport
}
}
/// <summary>
/// 清空取消标志
/// </summary>
public void ClearWasLastTestCanceled()
{
if (_wasLastTestCanceled)
{
LogManager.Info("[ClashDetective] 清空取消标志");
_wasLastTestCanceled = false;
}
}
/// <summary>
/// 清空当前测试名称
/// </summary>

View File

@ -1802,6 +1802,16 @@ namespace NavisworksTransport.UI.WPF.ViewModels
CanStopAnimation = false;
UpdateMainStatus("动画已完成");
UpdateMediaControlProperties(); // 更新媒体控制属性
// 检查检测是否被取消
if (_clashIntegration?.WasLastTestCanceled == true)
{
LogManager.Info("[动画完成] 检测已被取消,跳过报告生成和高亮");
// 重置取消标志
_clashIntegration.ClearWasLastTestCanceled();
break;
}
// 先清除所有碰撞高亮包括预计算和ClashDetective
ModelHighlightHelper.ClearCollisionHighlights();
// 生成碰撞报告(无论有无碰撞)并等待完成