检查并修复弹出窗口、对话框、消息框,未设置owner的情况
This commit is contained in:
parent
c910ab3ae7
commit
ae845bc571
137
AGENTS.md
137
AGENTS.md
@ -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变更)
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -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`
|
||||
|
||||
### 实际修复案例
|
||||
|
||||
#### 案例1:CollisionAnalysisDialog(预计算碰撞分析)
|
||||
|
||||
**问题**:打开大模型点击生成动画后,碰撞结果分析窗口藏在主窗口背后,无法点击
|
||||
|
||||
**修复**:
|
||||
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
|
||||
// 使用方式1:Code-Behind 中从 UserControl 获取 Owner
|
||||
var dialog = new MyDialog();
|
||||
DialogHelper.SetOwnerFromUserControl(dialog, this);
|
||||
dialog.ShowDialog();
|
||||
|
||||
// 使用方式2:ViewModel 中自动查找 Owner
|
||||
var dialog = new MyDialog();
|
||||
DialogHelper.SetOwnerSafely(dialog);
|
||||
dialog.ShowDialog();
|
||||
|
||||
// 使用方式3:一键显示对话框(自动处理 Owner)
|
||||
var result = DialogHelper.ShowDialog(new MyDialog(), this);
|
||||
|
||||
// 使用方式4:MessageBox 确保置顶
|
||||
var result = DialogHelper.ShowMessageBox(
|
||||
"确认删除?",
|
||||
"提示",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Question);
|
||||
```
|
||||
|
||||
### 方法说明
|
||||
|
||||
| 方法 | 场景 | 说明 |
|
||||
|------|------|------|
|
||||
| `SetOwnerFromUserControl` | Code-Behind | 从 UserControl 获取父窗口 |
|
||||
| `FindOwnerWindow` | ViewModel | 查找合适的 Owner(MainWindow -> 活动窗口) |
|
||||
| `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. **渐进式降级**:自动尝试多种方式获取 Owner(MainWindow -> 活动窗口 -> null)
|
||||
3. **静默失败**:设置 Owner 失败时不影响对话框显示(仅记录日志)
|
||||
4. **Topmost 仍是主要手段**:XAML 中仍需设置 `Topmost="True"`
|
||||
|
||||
---
|
||||
|
||||
*本文档将随着项目发展持续更新,确保设计指导的有效性和实用性。*
|
||||
|
||||
@ -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)
|
||||
├── 存在 → 提示用户选择使用历史记录或重新生成
|
||||
└── 不存在 → 创建新记录,继续正常流程
|
||||
└── 不存在 → 创建新记录,继续正常流程
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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("坐标系探索完成");
|
||||
|
||||
@ -18,7 +18,8 @@ NavisworksTransport 关于对话框 - 采用与主界面一致的Navisworks 2026
|
||||
Height="400"
|
||||
Width="500"
|
||||
ResizeMode="NoResize"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Topmost="True">
|
||||
|
||||
<Window.Resources>
|
||||
<!-- 引用共享的Navisworks 2026样式资源 -->
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
Height="720" Width="720"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
ResizeMode="CanResize"
|
||||
Topmost="True"
|
||||
Background="White">
|
||||
|
||||
<Window.Resources>
|
||||
|
||||
@ -21,6 +21,7 @@ NavisworksTransport 碰撞检测报告对话框 - 采用与主界面一致的Nav
|
||||
Width="900"
|
||||
ResizeMode="CanResize"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Topmost="True"
|
||||
MinHeight="800"
|
||||
MinWidth="600">
|
||||
|
||||
|
||||
@ -21,6 +21,7 @@ NavisworksTransport 配置编辑器对话框 - 采用与主界面一致的Navisw
|
||||
Width="900"
|
||||
ResizeMode="CanResize"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Topmost="True"
|
||||
MinHeight="500"
|
||||
MinWidth="700">
|
||||
|
||||
|
||||
@ -4,7 +4,8 @@
|
||||
Title="坐标系探索结果"
|
||||
Height="600" Width="700"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
ResizeMode="CanResize">
|
||||
ResizeMode="CanResize"
|
||||
Topmost="True">
|
||||
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
|
||||
@ -18,7 +18,8 @@ NavisworksTransport 帮助对话框 - 采用与主界面一致的Navisworks 2026
|
||||
Height="500"
|
||||
Width="650"
|
||||
ResizeMode="NoResize"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Topmost="True">
|
||||
|
||||
<Window.Resources>
|
||||
<!-- 引用共享的Navisworks 2026样式资源 -->
|
||||
|
||||
@ -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);
|
||||
}, "显示关于");
|
||||
}
|
||||
|
||||
|
||||
@ -18,7 +18,8 @@
|
||||
Height="700"
|
||||
Width="750"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
ResizeMode="CanResize">
|
||||
ResizeMode="CanResize"
|
||||
Topmost="True">
|
||||
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -16,7 +16,8 @@
|
||||
Height="500"
|
||||
Width="800"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
ResizeMode="CanResize">
|
||||
ResizeMode="CanResize"
|
||||
Topmost="True">
|
||||
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
|
||||
@ -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
215
src/Utils/DialogHelper.cs
Normal 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>
|
||||
/// 安全地设置对话框 Owner(ViewModel 场景)
|
||||
/// </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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user