我们的 .NET 应用程序能够生成 .png 文件,这些文件是 WPF Canvas 的屏幕截图。
这可以从客户端触发并由我们的另一个应用程序处理(本质上是将作业分派给它进行排队和处理的应用程序),该应用程序可以在另一台计算机上运行,然后负责截取屏幕截图。
这个功能效果很好,并且已经使用了很长时间。这包括在虚拟机上,即使没有人连接到该机器,即没有渲染显示。我们没有对代码进行任何更改。
但是,从 Windows 10 版本 1903(2019 年 5 月更新)开始,如果在无人连接的虚拟机上生成,则生成的屏幕截图始终为空白。如果您当前已连接到虚拟机,则它可以正常工作。我们还在更新 1909 和 2004 中重现了此问题。
我们得到的结果是尺寸正确的完全透明的.png。
我已经远程调试了创建屏幕截图的虚拟机,没有任何明显错误 - 所有属性(例如高度、宽度和可见性)都是正确的。没有抛出异常。
我们认为这一定与没有可以访问的显示或者类似的东西有关。然而,奇怪的是,这过去工作得很好,所以我们对 Windows 更新之间可能发生的变化感到困惑,尽管我们的代码保持不变。
还有其他人遇到过这个问题或类似问题并设法解决它吗?我知道 WPF 不完全支持在服务中使用 - 这个用例是否会交叉到该用例,因为应用程序在技术上在创建渲染时没有显示?
这是我们代码的精简示例,供参考:
' A method that ensures all contents of the canvas has been loaded in, then sets the canvases height and width based on its children,
' ensuring that it is layed out fully in preperation for the screenshot
theCanvas.UpdateSize()
Try
Dim renderBitmap As New RenderTargetBitmap(CInt(theCanvas.Width), CInt(theCanvas.Height), 96.0, 96.0, PixelFormats.Pbgra32)
renderBitmap.Render(theCanvas)
Dim directoryPath as String = Path.GetDirectoryName(saveLocation)
Directory.CreateDirectory(directoryPath)
Using outStream as New FileStream(saveLocation, FileMode.OpenOrCreate)
Dim encoder As New PngBitmapEncoder()
encoder.Frames.Add(BitmapFrame.Create(renderBitmap))
encoder.Save(outStream)
End Using
Catch ex as IOException
...
End Try
我们找出了问题所在。
Microsoft 于 2017 年修复了 Windows 问题,在此过程中,发布了默认行为,其中名为
ShouldRenderEvenWhenNoDisplayDevicesAreAvailable
的设置将设置为 False。显然,这意味着在我们的用例中,WPF 没有渲染任何内容,因为技术上没有可用的显示设备。
将以下内容添加到我们应用程序的 app.config 文件中可以修复该问题:
<runtime>
<AppContextSwitchOverrides value="Switch.System.Windows.Media.ShouldNotRenderInNonInteractiveWindowStation=false;Switch.System.Windows.Media.ShouldRenderEvenWhenNoDisplayDevicesAreAvailable=true" />
</runtime>
我们的一个应用程序也有同样的问题。正如丹尼尔正确指出的那样,我们可以追溯到我们更新环境以获取此 Microsoft 更新时的根本原因。
我们的应用程序使用 RenderTargetBitmap 来生成图像。只要存在活动的远程桌面会话,它就会正确呈现,因为在这种情况下它会检测到有效的显示设备。但是,每当没有活动的远程桌面会话时,就会出现渲染问题,因为它无法检测到任何显示设备。因此,结果将是空白/透明图像。
Daniel 更新 App.Config 的答案非常适合我们的场景。但我们还发现微软有更多这方面的文档,其中大部分在AppContext下有详细介绍。
总而言之,选择退出更新的主要方法有3种。这些是按照 Microsoft 注释的优先顺序排列的,其中一个将覆盖另一个:
(1) 使用 SetSwitch 方法以编程方式更改设置,如下所示:
AppContext.SetSwitch("System.Windows.Media.ShouldRenderEvenWhenNoDisplayDevicesAreAvailable", true);
AppContext.SetSwitch("Switch.System.Windows.Media.ShouldNotRenderInNonInteractiveWindowStation", false);
(2)更改App.Config文件:
<configuration>
<runtime>
<AppContextSwitchOverrides value="Switch.System.Windows.Media.ShouldNotRenderInNonInteractiveWindowStation=false;Switch.System.Windows.Media.ShouldRenderEvenWhenNoDisplayDevicesAreAvailable=true"
</runtime>
</configuration>
(3)更改注册表:
“向 HKLM\SOFTWARE\Microsoft.NETFramework\AppContext 子项添加新的字符串值。将条目的名称设置为交换机的名称。将其值设置为以下选项之一:True、true、False 或假的。
在 64 位操作系统上,您还必须将相同的条目添加到 HKLM\SOFTWARE\Wow6432Node\Microsoft.NETFramework\AppContext 子项。
使用注册表定义AppContext开关具有机器范围;也就是说,它会影响机器上运行的每个应用程序。”
相关链接如下:
https://learn.microsoft.com/en-us/dotnet/api/system.windows.media.imaging.rendertargetbitmap.render
https://learn.microsoft.com/en-us/dotnet/api/system.appcontext
https://learn.microsoft.com/en-us/dotnet/api/system.appcontext.setswitch
可以通过代码来完成此操作,您必须对两者使用“Switch”AppContext.SetSwitch:
AppContext.SetSwitch("Switch.System.Windows.Media.ShouldNotRenderInNonInteractiveWindowStation", false); AppContext.SetSwitch("Switch.System.Windows.Media.ShouldRenderEvenWhenNoDisplayDevicesAreAvailable", true);
并且这必须在初始化主窗口之前完成! 适用于 .NET Framework 和 .NET 5++