检查并修复弹出窗口、对话框、消息框,未设置owner的情况

This commit is contained in:
tian 2026-02-23 00:25:17 +08:00
parent c910ab3ae7
commit ae845bc571
19 changed files with 644 additions and 289 deletions

137
AGENTS.md
View File

@ -57,31 +57,6 @@
- 构建 Release 配置的 x64 平台
- 输出目录: `bin\x64\Release\`
### 手动构建
```bash
# 设置MSBuild路径
set MSBUILD_PATH="C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe"
# 构建主项目
%MSBUILD_PATH% "TransportPlugin.csproj" /p:Configuration=Release /p:Platform=x64
# 构建测试项目
%MSBUILD_PATH% "NavisworksTransport.UnitTests.csproj" /p:Configuration=Release /p:Platform=x64
```
### 运行测试
```bash
./run-unit-tests.bat
```
该脚本将:
1. 构建主项目
2. 构建测试项目
3. 使用 VSTest.Console 运行单元测试
### 部署插件
```bash
@ -325,6 +300,7 @@ double distance = GeometryHelper.Distance(pointA, pointB);
```
**复用检查清单:**
- 需要单位转换?→ 使用 `UnitsConverter`
- 需要几何计算?→ 使用 `GeometryHelper`
- 需要日志记录?→ 使用 `LogManager`
@ -450,102 +426,19 @@ private void OnStatusChanged(string status)
### WPF UI开发注意事项
#### 必须检查XAML资源引用有效性
#### XAML资源引用原则
**问题**使用未在XAML中定义的Converter/Style资源会导致窗口无法显示
**原则**:所有 XAML 中引用的资源Converter、Style、Brush 等)必须在当前文件或合并的资源字典中有定义。
```xml
<!-- ❌ 错误使用了未定义的Converter -->
Visibility="{Binding HasItems, Converter={StaticResource InverseBoolToVisibilityConverter}}"
**约束**
<!-- ✅ 正确使用已定义的Converter -->
Visibility="{Binding HasItems, Converter={StaticResource BoolToVisibilityConverter}, ConverterParameter=Inverse}"
```
- 禁止复制其他文件的 XAML 代码而不检查资源定义
- 每添加一个 `{StaticResource xxx}` 引用,立即确认 `x:Key="xxx"` 存在
- 小步增量开发,每步验证窗口可正常打开
**项目中已定义的资源AnimationControlView.xaml**
**关键错误**:资源键名拼写错误、未定义的 Converter、错误的外部资源字典路径
| 资源名 | 类型 | 说明 |
|--------|------|------|
| `BoolToVisibilityConverter` | BoolToVisibilityConverter | 布尔转可见性支持Inverse参数 |
**XAML开发工作流程必须遵守**
1. **每添加一个资源引用,立即确认其存在**
```
❌ 错误做法:
- 复制其他文件的XAML代码
- 一次性写大量XAML再测试
✅ 正确做法:
- 每写一行 {StaticResource xxx},立即检查 xxx 是否已定义
- 使用 Ctrl+F 搜索 xxx 确认在当前文件或合并字典中存在
```
2. **资源定义位置优先级**
- 本文件 `<Window.Resources>``<UserControl.Resources>` 内定义
- 引用的外部资源字典(如 `NavisworksStyles.xaml`
- 必须在 XAML 顶部检查 `MergedDictionaries` 是否正确合并
3. **新增XAML文件时必做检查清单**
```
□ 所有 {StaticResource xxx} 引用都有定义
□ 所有 xmlns:local 命名空间映射正确
□ 所有 ValueConverter 在 xaml.cs 中已实现
□ 合并的资源字典路径正确pack://application:,,,/...
□ 编译通过后立即运行测试,验证窗口能正常打开
```
4. **常见错误模式与修正**
| 错误写法 | 问题 | 正确写法 |
|---------|------|---------|
| `InverseBoolToVisibilityConverter` | 未定义 | `BoolToVisibilityConverter` + `ConverterParameter=Inverse` |
| `VisibilityConverter` | 未定义 | `BoolToVisibilityConverter` |
| `BooleanToVisibilityConverter` (键名) | 引用时使用 `BoolToVisibilityConverter` | 键名和引用保持一致 |
| 拼写错误的Style名 | 找不到资源 | 检查资源字典中的精确定义 |
5. **调试XAML资源错误**
- 错误信息:`无法在资源字典中找到名为 xxx 的资源`
- 定位方法从堆栈跟踪找到最后加载的XAML元素
- 快速修复:临时注释掉报错元素,添加简化版本测试
- 预防措施:小步增量开发,每步验证窗口可正常显示
#### AI助手强制执行机制防止再次犯错
**写XAML前必须大声说出以下承诺**
> "我承诺本次XAML开发将严格遵守渐进式验证原则每添加一个资源引用立即验证绝不一次性写大量代码。"
**编写过程中的强制检查点:**
| 步骤 | 强制动作 | 验证命令/方法 |
|-----|---------|--------------|
| 1. 写完XAML结构 | 列出所有 `{StaticResource xxx}` | `grep -n "StaticResource" File.xaml` |
| 2. 检查每个引用 | 在文件中搜索资源定义 | 确认 `x:Key="xxx"` 存在 |
| 3. 检查Converter | 确认 xaml.cs 中有实现 | `grep "class.*Converter.*:" File.xaml.cs` |
| 4. 首次编译 | 必须通过 | `compile.bat` |
| 5. 运行时验证 | 窗口能正常打开无异常 | 实际运行测试 |
**如果违反规则的后果:**
- 必须立即停止当前工作
- 回滚到上一个可运行的版本
- 重新开始,严格按照检查点执行
**自检话术(每次保存前自问):**
```
□ 我是否复制了其他文件的XAML代码而没有检查资源
□ 我是否一次性写了超过50行XAML才测试
□ 我能否确定每个 {StaticResource} 都在当前文件或合并字典中定义?
□ 如果现在运行,窗口会崩溃吗?
```
**历史错误记录(警示):**
- 2025-02-14: PathAnalysisDialog.xaml 使用了 `BoolToVisibilityConverter` 但定义的是 `BooleanToVisibilityConverter`,导致窗口崩溃
- 根本原因:没有逐条执行检查清单,过度自信
**参考**:详细检查清单和常见错误模式见 `doc/guide/design_principles.md` 第16节
#### UI色彩规范 - Material Design
@ -576,6 +469,17 @@ Visibility="{Binding HasItems, Converter={StaticResource BoolToVisibilityConvert
3. 使用 `Color.FromByteRGB(r, g, b)` 定义颜色Navisworks API
4. 保持透明度一致:通行空间类用 0.8-0.9,碰撞类用不透明
#### 对话框置顶原则
**原则**在Navisworks插件环境中所有模态对话框必须确保置顶显示避免被主窗口遮挡。
**约束**
- 所有模态对话框必须在 XAML 中设置 `Topmost="True"`
- 使用 `DialogHelper` 工具类统一处理 Owner 设置(`src/Utils/DialogHelper.cs`
**参考**:详细方案见 `doc/guide/design_principles.md` 第15节、第17节
## 配置系统
配置文件使用 TOML 格式,默认配置位于 `default_config.toml`
@ -723,6 +627,7 @@ namespace NavisworksTransport.UnitTests
- **API文档**: `doc/navisworks_api/NET/documentation/NET API.chm`
- **COM API文档**: `doc/navisworks_api/COM/documentation/NavisWorksCOM.chm`
- **设计原则**: `doc/guide/design_principles.md`(线程安全、对话框置顶等设计模式)
- **设计文档**: `doc/design/2026/`
- **架构设计**: `doc/architecture/`
- **迁移指南**: `doc/migration/`2017到2026的API变更

View File

@ -316,6 +316,7 @@
<Compile Include="src\Utils\ModelItemAnalysisHelper.cs" />
<Compile Include="src\Utils\GeometryHelper.cs" />
<Compile Include="src\Utils\GeometryCacheManager.cs" />
<Compile Include="src\Utils\DialogHelper.cs" />
<Compile Include="src\Utils\LogManager.cs" />
<Compile Include="src\Utils\NavisworksApiHelper.cs" />
<Compile Include="src\Utils\NavisworksSelectionHelper.cs" />

View File

@ -1775,4 +1775,374 @@ C# 7.3 功能总结
C# 版本兼容性表
| .NET Framework | 默认 C# 版本 | 最高支持 C# 版本 | |----------------|-------------|-----------------| | 4.6.1 | C# 7.0 | C# 7.3 | | 4.7 | C# 7.0 | C# 7.3 | | 4.7.1 | C# 7.1 | C# 7.3 | | 4.7.2 | C# 7.3 | C# 7.3 | | 4.8 | C# 7.3 | C# 7.3 | | 4.8.1 | C# 7.3 | C# 7.3 |
## 15. WPF对话框置顶问题窗口Z-Order
### 问题描述
在Navisworks插件开发中WPF对话框可能会出现在主窗口背后导致用户无法操作。这个问题在处理大模型时尤为明显
- **小模型**:动画生成速度快,对话框弹出时主窗口仍处于活动状态
- **大模型**:预计算耗时较长,用户可能点击主窗口,导致窗口焦点/Z-Order变化。对话框弹出时不在最前面
### 根本原因
1. **Navisworks是Win32应用程序**WPF作为插件嵌入其中`Application.Current.MainWindow` 不可靠
2. **Owner属性未正确设置**时,`WindowStartupLocation="CenterOwner"` 无法正常工作
3. **异步操作耗时**导致窗口焦点在对话框显示前发生变化
### 解决方案
#### 15.1 XAML层解决方案推荐
在对话框XAML中添加 `Topmost="True"`,确保窗口始终置顶:
```xml
<!-- ✅ 正确设置Topmost确保窗口置顶 -->
<Window x:Class="NavisworksTransport.UI.WPF.Views.CollisionAnalysisDialog"
Title="预计算碰撞分析 - 排除建议"
WindowStartupLocation="CenterOwner"
Topmost="True"
...>
```
**参考实现**`GenerateNavigationMapDialog.xaml`、`EditRotationWindow.xaml`、`AerialHeightDialog.xaml`
#### 15.2 代码层解决方案(备用)
在ViewModel中动态查找Owner窗口
```csharp
// ✅ 正确多重备用方案查找Owner窗口
private void ShowDialogWithOwner(Window dialog)
{
// 方案1尝试使用MainWindow
var mainWindow = System.Windows.Application.Current?.MainWindow;
if (mainWindow != null && mainWindow.IsVisible)
{
try
{
dialog.Owner = mainWindow;
}
catch (InvalidOperationException)
{
LogManager.Debug("[对话框] 无法设置MainWindow为Owner尝试备用方案");
}
}
// 方案2遍历所有窗口找活动窗口
if (dialog.Owner == null && System.Windows.Application.Current != null)
{
foreach (Window window in System.Windows.Application.Current.Windows)
{
if (window.IsActive && window.IsLoaded)
{
try
{
dialog.Owner = window;
LogManager.Debug($"[对话框] 设置活动窗口为Owner: {window.Title}");
break;
}
catch (InvalidOperationException)
{
// 继续尝试下一个窗口
}
}
}
}
dialog.ShowDialog();
}
```
#### 15.3 MessageBox的Owner设置
```csharp
// ✅ 正确为MessageBox传入owner参数
Window owner = System.Windows.Application.Current?.MainWindow;
if (owner == null || !owner.IsVisible)
{
foreach (Window window in System.Windows.Application.Current?.Windows ?? new WindowCollection())
{
if (window.IsActive)
{
owner = window;
break;
}
}
}
var result = System.Windows.MessageBox.Show(
owner, // 关键传入owner确保MessageBox置顶
message,
"检测配置已存在",
System.Windows.MessageBoxButton.YesNo,
System.Windows.MessageBoxImage.Question);
```
#### 15.4 Code-Behind中的Owner设置
当在UserControl中显示对话框时使用 `Window.GetWindow(this)`
```csharp
// ✅ 正确从UserControl获取父窗口
private void ShowHelpDialog()
{
var helpDialog = new Views.HelpDialog
{
Owner = Window.GetWindow(this) // 关键获取当前UserControl所在的Window
};
helpDialog.ShowDialog();
}
```
**参考实现**`LogisticsControlPanel.xaml.cs`、`PathEditingView.xaml.cs`
### 实际修复案例
#### 案例1CollisionAnalysisDialog预计算碰撞分析
**问题**:打开大模型点击生成动画后,碰撞结果分析窗口藏在主窗口背后,无法点击
**修复**
1. XAML添加 `Topmost="True"`
2. ViewModel添加备用Owner查找逻辑
```xml
<!-- CollisionAnalysisDialog.xaml -->
<Window ...
WindowStartupLocation="CenterOwner"
Topmost="True"> <!-- 添加此行 -->
```
```csharp
// AnimationControlViewModel.cs - ShowCollisionAnalysisDialog方法
// 添加备用Owner查找逻辑详见15.2节)
```
#### 案例2重复配置提示MessageBox
**问题**检测配置重复提示的MessageBox也可能被主窗口遮挡
**修复**
```csharp
// ShowDuplicateConfigDialog方法
var result = System.Windows.MessageBox.Show(
owner, // 传入owner参数
message,
"检测配置已存在",
System.Windows.MessageBoxButton.YesNo,
System.Windows.MessageBoxImage.Question);
```
### 设计原则
| 场景 | 推荐方案 | 说明 |
|------|---------|------|
| 自定义WPF对话框 | `Topmost="True"` | 最简单可靠 |
| 需要居中对齐 | `Topmost="True"` + 动态设置Owner | 兼顾置顶和位置 |
| MessageBox | 传入owner参数 | 系统对话框特殊处理 |
| UserControl内调用 | `Window.GetWindow(this)` | 获取正确的父窗口 |
### 检查清单
- [ ] 所有模态对话框是否设置了 `Topmost="True"`
- [ ] 需要居中的对话框是否正确设置了Owner
- [ ] MessageBox是否传入了owner参数
- [ ] UserControl中显示对话框是否使用了 `Window.GetWindow(this)`
- [ ] 是否有多重备用方案处理Owner查找失败
### 常见陷阱
1. **仅依赖 `Application.Current.MainWindow`** - 在Navisworks环境中不可靠
2. **只设置 `WindowStartupLocation="CenterOwner"` 但不设置Owner** - 窗口位置不可预测
3. **忽略异步操作的时序问题** - 耗时操作后窗口焦点可能已变化
4. **MessageBox不传入owner** - 系统对话框也可能被遮挡
## 16. XAML资源引用检查清单
### 问题描述
使用未在XAML中定义的Converter/Style资源会导致窗口无法显示这是WPF开发中常见的运行时错误。
### 错误示例
```xml
<!-- ❌ 错误使用了未定义的Converter -->
Visibility="{Binding HasItems, Converter={StaticResource InverseBoolToVisibilityConverter}}"
<!-- ✅ 正确使用已定义的Converter -->
Visibility="{Binding HasItems, Converter={StaticResource BoolToVisibilityConverter}, ConverterParameter=Inverse}"
```
### 项目中已定义的资源
| 资源名 | 类型 | 说明 |
|--------|------|------|
| `BoolToVisibilityConverter` | BoolToVisibilityConverter | 布尔转可见性支持Inverse参数 |
### 开发工作流程
1. **每添加一个资源引用,立即确认其存在**
```
❌ 错误做法:
- 复制其他文件的XAML代码
- 一次性写大量XAML再测试
✅ 正确做法:
- 每写一行 {StaticResource xxx},立即检查 xxx 是否已定义
- 使用 Ctrl+F 搜索 xxx 确认在当前文件或合并字典中存在
```
2. **资源定义位置优先级**
- 本文件 `<Window.Resources>``<UserControl.Resources>` 内定义
- 引用的外部资源字典(如 `NavisworksStyles.xaml`
- 必须在 XAML 顶部检查 `MergedDictionaries` 是否正确合并
3. **新增XAML文件时必做检查清单**
```
□ 所有 {StaticResource xxx} 引用都有定义
□ 所有 xmlns:local 命名空间映射正确
□ 所有 ValueConverter 在 xaml.cs 中已实现
□ 合并的资源字典路径正确pack://application:,,,/...
□ 编译通过后立即运行测试,验证窗口能正常打开
```
4. **常见错误模式与修正**
| 错误写法 | 问题 | 正确写法 |
|---------|------|---------|
| `InverseBoolToVisibilityConverter` | 未定义 | `BoolToVisibilityConverter` + `ConverterParameter=Inverse` |
| `VisibilityConverter` | 未定义 | `BoolToVisibilityConverter` |
| `BooleanToVisibilityConverter` (键名) | 引用时使用 `BoolToVisibilityConverter` | 键名和引用保持一致 |
| 拼写错误的Style名 | 找不到资源 | 检查资源字典中的精确定义 |
5. **调试XAML资源错误**
- 错误信息:`无法在资源字典中找到名为 xxx 的资源`
- 定位方法从堆栈跟踪找到最后加载的XAML元素
- 快速修复:临时注释掉报错元素,添加简化版本测试
- 预防措施:小步增量开发,每步验证窗口可正常显示
### 历史错误记录
- **2025-02-14**: `PathAnalysisDialog.xaml` 使用了 `BoolToVisibilityConverter` 但定义的是 `BooleanToVisibilityConverter`,导致窗口崩溃
- **根本原因**:没有逐条执行检查清单,过度自信
### AI助手强制检查点
| 步骤 | 强制动作 | 验证方法 |
|-----|---------|---------|
| 1. 写完XAML结构 | 列出所有 `{StaticResource xxx}` | `grep -n "StaticResource" File.xaml` |
| 2. 检查每个引用 | 在文件中搜索资源定义 | 确认 `x:Key="xxx"` 存在 |
| 3. 检查Converter | 确认 xaml.cs 中有实现 | `grep "class.*Converter.*:" File.xaml.cs` |
| 4. 首次编译 | 必须通过 | `compile.bat` |
| 5. 运行时验证 | 窗口能正常打开 | 实际运行测试 |
---
## 17. 对话框辅助类DialogHelper
### 问题描述
项目中多处重复代码用于设置对话框 Owner 和确保窗口置顶。需要统一封装,减少重复代码。
### DialogHelper 工具类
`src/Utils/DialogHelper.cs` 提供了统一的对话框处理方法:
```csharp
// 使用方式1Code-Behind 中从 UserControl 获取 Owner
var dialog = new MyDialog();
DialogHelper.SetOwnerFromUserControl(dialog, this);
dialog.ShowDialog();
// 使用方式2ViewModel 中自动查找 Owner
var dialog = new MyDialog();
DialogHelper.SetOwnerSafely(dialog);
dialog.ShowDialog();
// 使用方式3一键显示对话框自动处理 Owner
var result = DialogHelper.ShowDialog(new MyDialog(), this);
// 使用方式4MessageBox 确保置顶
var result = DialogHelper.ShowMessageBox(
"确认删除?",
"提示",
MessageBoxButton.YesNo,
MessageBoxImage.Question);
```
### 方法说明
| 方法 | 场景 | 说明 |
|------|------|------|
| `SetOwnerFromUserControl` | Code-Behind | 从 UserControl 获取父窗口 |
| `FindOwnerWindow` | ViewModel | 查找合适的 OwnerMainWindow -> 活动窗口) |
| `SetOwnerSafely` | ViewModel | 安全设置 Owner带异常处理 |
| `ShowDialog` | 通用 | 自动设置 Owner 并显示对话框 |
| `ShowMessageBox` | 通用 | 显示置顶 MessageBox |
| `SetWin32Owner` | 特殊 | 设置 Win32 父窗口Navisworks 主窗口) |
### 迁移示例
**旧代码(重复且冗长)**
```csharp
// Code-Behind
var dialog = new HelpDialog();
try
{
var parentWindow = Window.GetWindow(this);
if (parentWindow != null)
{
dialog.Owner = parentWindow;
}
}
catch (Exception ownerEx)
{
LogManager.Warning($"设置Owner失败: {ownerEx.Message}");
}
dialog.ShowDialog();
```
```csharp
// ViewModel
var dialog = new MyDialog();
var mainWindow = System.Windows.Application.Current?.MainWindow;
if (mainWindow != null && mainWindow.IsVisible)
{
try
{
dialog.Owner = mainWindow;
}
catch (InvalidOperationException)
{
// 备用方案...
foreach (Window window in System.Windows.Application.Current.Windows)
{
// ...
}
}
}
dialog.ShowDialog();
```
**新代码(简洁统一)**
```csharp
// Code-Behind
DialogHelper.ShowDialog(new HelpDialog(), this);
// ViewModel
DialogHelper.ShowDialog(new MyDialog());
```
### 设计原则
1. **代码复用优先**:使用 `DialogHelper` 替代重复的 Owner 设置代码
2. **渐进式降级**:自动尝试多种方式获取 OwnerMainWindow -> 活动窗口 -> null
3. **静默失败**:设置 Owner 失败时不影响对话框显示(仅记录日志)
4. **Topmost 仍是主要手段**XAML 中仍需设置 `Topmost="True"`
---
*本文档将随着项目发展持续更新,确保设计指导的有效性和实用性。*

View File

@ -1,7 +1,5 @@
### 生成动画测试流程
# 生成动画测试流程
用户点击"生成动画"
@ -19,8 +17,9 @@ CheckExistingDetectionRecord
用户生成动画 → 检测到相同配置的历史记录 → 弹出对话框询问
用户选择"使用历史记录"
用户选择"使用历史记录"
1. 设置 IsUsingHistoryRecord = true
2. 在历史列表中选中该记录
3. 自动滚动到该记录
@ -30,7 +29,7 @@ CheckExistingDetectionRecord
├── true → 跳过 ClashDetective 测试,记录日志
└── false → 正常执行 ClashDetective 测试
生成动画
检查动画帧缓存
@ -39,4 +38,4 @@ CheckExistingDetectionRecord
检查配置是否已存在_animationHashToRecordId
├── 存在 → 提示用户选择使用历史记录或重新生成
└── 不存在 → 创建新记录,继续正常流程
└── 不存在 → 创建新记录,继续正常流程

View File

@ -3036,22 +3036,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
try
{
var dialog = new CollisionAnalysisDialog(hotspots, totalCollisions);
// 安全地设置 Owner确保窗口已显示
var mainWindow = System.Windows.Application.Current?.MainWindow;
if (mainWindow != null && mainWindow.IsVisible)
{
try
{
dialog.Owner = mainWindow;
}
catch (InvalidOperationException)
{
// Owner 设置失败,继续显示对话框(无 Owner
LogManager.Debug("[碰撞分析] 无法设置对话框 Owner将以独立窗口显示");
}
}
DialogHelper.SetOwnerSafely(dialog);
var result = dialog.ShowDialog();
if (result == null)
@ -3404,7 +3389,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
$"• 【是】直接使用历史记录(跳过重复检测)\n" +
$"• 【否】重新生成新记录";
var result = System.Windows.MessageBox.Show(
var result = DialogHelper.ShowMessageBox(
message,
"检测配置已存在",
System.Windows.MessageBoxButton.YesNo,

View File

@ -1247,11 +1247,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 设置当前值,默认优先使用当前视窗尺寸
dialog.SetInitialValues(ScreenshotWidthSetting, ScreenshotHeightSetting, ScreenshotFormatSetting, preferCurrentViewport: true);
var mainWindow = System.Windows.Application.Current.MainWindow;
if (mainWindow != null && mainWindow.IsLoaded)
{
dialog.Owner = mainWindow;
}
DialogHelper.SetOwnerSafely(dialog);
if (dialog.ShowDialog() == true)
{

View File

@ -619,33 +619,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 创建并显示日志查看器对话框
var logViewerDialog = new NavisworksTransport.UI.WPF.Views.LogViewerDialog();
// 尝试设置窗口所有者,使用更安全的方式
try
{
// 查找当前活动的主窗口
var mainWindow = System.Windows.Application.Current.MainWindow;
if (mainWindow != null && mainWindow.IsLoaded)
{
logViewerDialog.Owner = mainWindow;
}
else
{
// 如果主窗口不可用,查找当前活动的窗口
foreach (System.Windows.Window window in System.Windows.Application.Current.Windows)
{
if (window.IsActive && window.IsLoaded)
{
logViewerDialog.Owner = window;
break;
}
}
}
}
catch (Exception ownerEx)
{
// 如果设置Owner失败记录警告但继续显示窗口
LogManager.Warning($"设置日志查看器Owner失败: {ownerEx.Message}");
}
// 设置窗口所有者
DialogHelper.SetOwnerSafely(logViewerDialog);
// 显示对话框使用Show而不是ShowDialog以避免阻塞
logViewerDialog.Show();
@ -678,32 +653,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 创建并显示配置编辑器对话框
var configEditorDialog = new NavisworksTransport.UI.WPF.Views.ConfigEditorDialog();
// 尝试设置窗口所有者
try
{
var mainWindow = System.Windows.Application.Current.MainWindow;
if (mainWindow != null && mainWindow.IsLoaded)
{
configEditorDialog.Owner = mainWindow;
}
else
{
foreach (System.Windows.Window window in System.Windows.Application.Current.Windows)
{
if (window.IsActive && window.IsLoaded)
{
configEditorDialog.Owner = window;
break;
}
}
}
}
catch (Exception ownerEx)
{
LogManager.Warning($"设置配置编辑器Owner失败: {ownerEx.Message}");
}
// 显示对话框
// 设置窗口所有者并显示对话框
DialogHelper.SetOwnerSafely(configEditorDialog);
bool? result = configEditorDialog.ShowDialog();
if (result == true)
@ -1726,31 +1677,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
ResultText = result
};
// 尝试设置对话框所有者
try
{
var mainWindow = System.Windows.Application.Current?.MainWindow;
if (mainWindow != null && mainWindow.IsLoaded)
{
resultDialog.Owner = mainWindow;
}
else
{
foreach (System.Windows.Window window in System.Windows.Application.Current.Windows)
{
if (window.IsActive && window.IsLoaded)
{
resultDialog.Owner = window;
break;
}
}
}
}
catch (Exception ex)
{
LogManager.Warning($"[坐标系探索] 设置视图状态失败: {ex.Message}");
}
// 设置对话框所有者并显示
DialogHelper.SetOwnerSafely(resultDialog);
resultDialog.ShowDialog();
UpdateMainStatus("坐标系探索完成");

View File

@ -18,7 +18,8 @@ NavisworksTransport 关于对话框 - 采用与主界面一致的Navisworks 2026
Height="400"
Width="500"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner">
WindowStartupLocation="CenterOwner"
Topmost="True">
<Window.Resources>
<!-- 引用共享的Navisworks 2026样式资源 -->

View File

@ -8,6 +8,7 @@
Height="720" Width="720"
WindowStartupLocation="CenterOwner"
ResizeMode="CanResize"
Topmost="True"
Background="White">
<Window.Resources>

View File

@ -21,6 +21,7 @@ NavisworksTransport 碰撞检测报告对话框 - 采用与主界面一致的Nav
Width="900"
ResizeMode="CanResize"
WindowStartupLocation="CenterOwner"
Topmost="True"
MinHeight="800"
MinWidth="600">

View File

@ -21,6 +21,7 @@ NavisworksTransport 配置编辑器对话框 - 采用与主界面一致的Navisw
Width="900"
ResizeMode="CanResize"
WindowStartupLocation="CenterOwner"
Topmost="True"
MinHeight="500"
MinWidth="700">

View File

@ -4,7 +4,8 @@
Title="坐标系探索结果"
Height="600" Width="700"
WindowStartupLocation="CenterOwner"
ResizeMode="CanResize">
ResizeMode="CanResize"
Topmost="True">
<Grid Margin="10">
<Grid.RowDefinitions>

View File

@ -18,7 +18,8 @@ NavisworksTransport 帮助对话框 - 采用与主界面一致的Navisworks 2026
Height="500"
Width="650"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner">
WindowStartupLocation="CenterOwner"
Topmost="True">
<Window.Resources>
<!-- 引用共享的Navisworks 2026样式资源 -->

View File

@ -3,6 +3,7 @@ using System.Windows;
using System.Windows.Controls;
using NavisworksTransport.UI.WPF.ViewModels;
using NavisworksTransport.UI.WPF.Views;
using NavisworksTransport.Utils;
using NavisApplication = Autodesk.Navisworks.Api.Application;
namespace NavisworksTransport.UI.WPF
@ -328,11 +329,7 @@ namespace NavisworksTransport.UI.WPF
{
GlobalExceptionHandler.SafeExecute(() =>
{
var helpDialog = new Views.HelpDialog
{
Owner = Window.GetWindow(this)
};
helpDialog.ShowDialog();
DialogHelper.ShowDialog(new Views.HelpDialog(), this);
}, "显示帮助");
}
@ -343,11 +340,7 @@ namespace NavisworksTransport.UI.WPF
{
GlobalExceptionHandler.SafeExecute(() =>
{
var aboutDialog = new Views.AboutDialog
{
Owner = Window.GetWindow(this)
};
aboutDialog.ShowDialog();
DialogHelper.ShowDialog(new Views.AboutDialog(), this);
}, "显示关于");
}

View File

@ -18,7 +18,8 @@
Height="700"
Width="750"
WindowStartupLocation="CenterScreen"
ResizeMode="CanResize">
ResizeMode="CanResize"
Topmost="True">
<Window.Resources>
<ResourceDictionary>

View File

@ -6,6 +6,7 @@ using NavisworksTransport.UI.WPF.ViewModels;
using NavisworksTransport.UI.WPF.Views;
using NavisworksTransport.Core.Services;
using NavisworksTransport.Core.Models;
using NavisworksTransport.Utils;
namespace NavisworksTransport.UI.WPF.Views
{
@ -93,21 +94,7 @@ namespace NavisworksTransport.UI.WPF.Views
// 创建并显示时标对话框
var dlg = new TimeTagDialog(_timeTagService, route);
// 设置父窗口
try
{
var parentWindow = Window.GetWindow(this);
if (parentWindow != null)
{
dlg.Owner = parentWindow;
}
}
catch (Exception ownerEx)
{
LogManager.Warning($"设置Owner失败: {ownerEx.Message}");
}
DialogHelper.SetOwnerFromUserControl(dlg, this);
dlg.ShowDialog();
LogManager.Info($"时标对话框关闭: {route.Name}");
}
@ -158,25 +145,8 @@ namespace NavisworksTransport.UI.WPF.Views
var dialog = new GenerateNavigationMapDialog();
// 在Navisworks环境中尝试找到合适的父窗口
try
{
// 尝试设置Owner为当前WPF窗口的顶级父窗口
var parentWindow = Window.GetWindow(this);
if (parentWindow != null)
{
dialog.Owner = parentWindow;
LogManager.Info("设置GenerateNavigationMapDialog的Owner为当前窗口");
}
else
{
LogManager.Info("未找到父窗口将以独立窗口方式显示GenerateNavigationMapDialog");
}
}
catch (Exception ownerEx)
{
LogManager.Warning($"设置Owner失败将以独立窗口显示: {ownerEx.Message}");
}
// 设置父窗口
DialogHelper.SetOwnerFromUserControl(dialog, this);
if (dialog.ShowDialog() == true && dialog.IsConfirmed)
{
@ -272,24 +242,8 @@ namespace NavisworksTransport.UI.WPF.Views
{
_boundsWindow = new ModelItemBoundsWindow();
// 在Navisworks环境中尝试找到合适的父窗口
try
{
var parentWindow = Window.GetWindow(this);
if (parentWindow != null)
{
_boundsWindow.Owner = parentWindow;
LogManager.Info("设置ModelItemBoundsWindow的Owner为当前窗口");
}
else
{
LogManager.Info("未找到父窗口将以独立窗口方式显示ModelItemBoundsWindow");
}
}
catch (Exception ownerEx)
{
LogManager.Warning($"设置Owner失败将以独立窗口显示: {ownerEx.Message}");
}
// 设置父窗口(非模态窗口)
DialogHelper.SetOwnerFromUserControl(_boundsWindow, this);
// 使用 Show() 而非 ShowDialog(),允许窗口一直开着
_boundsWindow.Show();

View File

@ -16,7 +16,8 @@
Height="500"
Width="800"
WindowStartupLocation="CenterScreen"
ResizeMode="CanResize">
ResizeMode="CanResize"
Topmost="True">
<Window.Resources>
<ResourceDictionary>

View File

@ -7,6 +7,7 @@
Height="750" Width="1100"
WindowStartupLocation="CenterOwner"
ResizeMode="CanResize"
Topmost="True"
x:Name="root">
<Window.Resources>
<ResourceDictionary>

215
src/Utils/DialogHelper.cs Normal file
View File

@ -0,0 +1,215 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using Autodesk.Navisworks.Api;
namespace NavisworksTransport.Utils
{
/// <summary>
/// 对话框辅助类 - 统一处理对话框 Owner 设置和置顶显示
/// </summary>
public static class DialogHelper
{
/// <summary>
/// 从 UserControl 获取父窗口并设置为对话框 Owner
/// </summary>
/// <param name="dialog">要显示的对话框</param>
/// <param name="userControl">当前 UserControl</param>
/// <returns>是否成功设置 Owner</returns>
public static bool SetOwnerFromUserControl(Window dialog, UserControl userControl)
{
if (dialog == null || userControl == null)
return false;
try
{
var parentWindow = Window.GetWindow(userControl);
if (parentWindow != null)
{
dialog.Owner = parentWindow;
return true;
}
}
catch (Exception ex)
{
LogManager.Warning($"[DialogHelper] 设置 Owner 失败: {ex.Message}");
}
return false;
}
/// <summary>
/// 在 ViewModel 中查找合适的 Owner 窗口
/// 策略MainWindow -> 活动窗口 -> null
/// </summary>
/// <returns>找到的窗口,或 null</returns>
public static Window FindOwnerWindow()
{
try
{
// 方案1尝试 MainWindow
var mainWindow = System.Windows.Application.Current?.MainWindow;
if (mainWindow != null && (mainWindow.IsVisible || mainWindow.IsLoaded))
{
return mainWindow;
}
}
catch (Exception ex)
{
LogManager.Debug($"[DialogHelper] 获取 MainWindow 失败: {ex.Message}");
}
// 方案2遍历查找活动窗口
try
{
if (System.Windows.Application.Current != null)
{
foreach (Window window in System.Windows.Application.Current.Windows)
{
if (window.IsActive && window.IsLoaded)
{
return window;
}
}
}
}
catch (Exception ex)
{
LogManager.Debug($"[DialogHelper] 遍历窗口失败: {ex.Message}");
}
return null;
}
/// <summary>
/// 安全地设置对话框 OwnerViewModel 场景)
/// </summary>
/// <param name="dialog">要设置的对话框</param>
/// <returns>是否成功设置</returns>
public static bool SetOwnerSafely(Window dialog)
{
if (dialog == null)
return false;
if (dialog.Owner != null)
return true; // 已设置
var owner = FindOwnerWindow();
if (owner != null)
{
try
{
dialog.Owner = owner;
LogManager.Debug($"[DialogHelper] 设置 Owner: {owner.Title}");
return true;
}
catch (InvalidOperationException ex)
{
LogManager.Warning($"[DialogHelper] 设置 Owner 失败: {ex.Message}");
}
}
return false;
}
/// <summary>
/// 显示对话框(自动处理 Owner 设置)
/// </summary>
/// <param name="dialog">要显示的对话框</param>
/// <param name="userControl">可选的 UserControl用于获取 Owner</param>
/// <returns>对话框结果</returns>
public static bool? ShowDialog(Window dialog, UserControl userControl = null)
{
if (dialog == null)
throw new ArgumentNullException(nameof(dialog));
// 如果提供了 UserControl优先使用它获取 Owner
if (userControl != null)
{
SetOwnerFromUserControl(dialog, userControl);
}
else
{
// 否则尝试自动查找
SetOwnerSafely(dialog);
}
return dialog.ShowDialog();
}
/// <summary>
/// 显示 MessageBox 并确保置顶
/// </summary>
/// <param name="messageBoxText">消息内容</param>
/// <param name="caption">标题</param>
/// <param name="button">按钮类型</param>
/// <param name="icon">图标</param>
/// <returns>MessageBox 结果</returns>
public static MessageBoxResult ShowMessageBox(
string messageBoxText,
string caption,
MessageBoxButton button = MessageBoxButton.OK,
MessageBoxImage icon = MessageBoxImage.Information)
{
var owner = FindOwnerWindow();
if (owner != null)
{
return MessageBox.Show(owner, messageBoxText, caption, button, icon);
}
return MessageBox.Show(messageBoxText, caption, button, icon);
}
/// <summary>
/// 获取 Navisworks 主窗口句柄Win32
/// </summary>
/// <returns>窗口句柄,或 IntPtr.Zero</returns>
public static IntPtr GetNavisworksMainWindowHandle()
{
try
{
var mainWindow = Autodesk.Navisworks.Api.Application.Gui?.MainWindow;
if (mainWindow != null)
{
return mainWindow.Handle;
}
}
catch (Exception ex)
{
LogManager.Debug($"[DialogHelper] 获取 Navisworks 主窗口句柄失败: {ex.Message}");
}
return IntPtr.Zero;
}
/// <summary>
/// 设置 Win32 父窗口(用于需要强制置顶到 Navisworks 主窗口的场景)
/// </summary>
/// <param name="dialog">对话框</param>
/// <returns>是否成功</returns>
public static bool SetWin32Owner(Window dialog)
{
if (dialog == null)
return false;
var handle = GetNavisworksMainWindowHandle();
if (handle == IntPtr.Zero)
return false;
try
{
var helper = new WindowInteropHelper(dialog);
helper.Owner = handle;
return true;
}
catch (Exception ex)
{
LogManager.Warning($"[DialogHelper] 设置 Win32 Owner 失败: {ex.Message}");
}
return false;
}
}
}