同样的程序,在使用 Visual Studio 调试的时候和直接运行的时候相比,总会有一些细微之处是不同的。大多数时候这些不同可以忽略,但是一旦这些不同是我们产品需求的一部分的时候,你可能就会发现调试和非调试状态下的行为不同却找不到原因,非常抓狂!
本文记录我遇到的一个 WPF 窗口调试的案例。看完后大家至少知道 Visual Studio 调试时的一个小坑,更进一步则可以在出现奇妙问题的时候打开一个新的思路。
UI 自动化
微软有一款自动化办公软件 Power Automate Desktop,它可以录制你对某软件的操作,以便在后续自动化进行这些操作。
一天,我正用它来自动化操作我正在开发中的一款小工具软件(WPF 框架),但发现它竟然无法识别我界面中的任何控件,无论怎么识别,都是一整个窗口。这导致 Power Automate Desktop 的自动化操作对我正开发的软件毫无作用,这怎么能忍!
Visual Studio 干了啥!
我用 snoop 查看了一下我软件界面里的控件,发现没有什么异常。
不过,意外发现有一个名为“AdornerWindow”的窗口引起了我的注意,直接在 snoop 里将其设为隐藏后,Power Automate Desktop 瞬间即可正常识别我软件里面的各种控件了。
然而,我不能每次自动化之前先用 snoop 隐藏一下这个窗口吧,所以就打算在我窗口的 ContentRendered
事件里把它干掉。这就有了下面这段代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public MainWindow()
{
InitializeComponent();
ContentRendered += RecordingCaptureWindow_ContentRendered;
}
private void RecordingCaptureWindow_ContentRendered(object? sender, EventArgs e)
{
HandleVisualStudioHacking();
}
/// <summary>
/// 因为 Visual Studio 会在调试状态下向此窗口添加一个全覆盖窗口,导致我们无法捕获到下方控件。所以我们需要将此窗口移除。
/// </summary>
[Conditional("DEBUG")]
private void HandleVisualStudioHacking()
{
var windows = Application.Current.Windows.OfType<Window>().ToList();
var thisWindowIndex = windows.IndexOf(this);
var suspiciousWindowIndex = thisWindowIndex + 1;
if (suspiciousWindowIndex < windows.Count
&& windows[suspiciousWindowIndex] is { } suspiciousWindow
&& suspiciousWindow.GetType().FullName == "Microsoft.VisualStudio.DesignTools.WpfTap.WpfVisualTreeService.Adorners.AdornerWindow")
{
suspiciousWindow.Close();
}
}
因为发现每一个 WPF 窗口上面都会覆盖这样一个透明窗口,所以我拿到主线程所有窗口的列表,找到当前窗口的下一个(因为假想 Visual Studio 总会在我们创建完一个窗口后立即创建覆盖窗口),然后把它关掉。
本文会经常更新,请阅读原文: https://blog.walterlv.com/post/visual-studio-add-a-window-covering-my-whole-wpf-window ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 吕毅 (包含链接: https://blog.walterlv.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 (walter.lv@qq.com) 。