.NET 4.8 WPF中GraphicsCapturePicker仅在FolderPicker后可用的排查方向
问题回顾
项目结构为MainWindow包裹两个用户控件:ScreenCaptureUserControl负责显示、ActualUserControl提供控制功能。点击ActualUserControl中的屏幕捕获按钮时,直接调用ScreenCaptureUserControl的StartPickerCaptureAsync方法,GraphicsCapturePicker.PickSingleItemAsync无法弹出选择窗口;但先调用FolderPicker.PickSingleFolderAsync后,GraphicsCapturePicker就能正常工作。关键差异是调用FolderPicker会自动加载System.Runtime.InteropServices.WindowsRuntime程序集,手动加载该程序集却无法解决问题。
可能的排查与解决方向
1. 强制初始化WinRT环境而非仅加载程序集
手动加载程序集后,需要触发WinRT的完整初始化逻辑,而非仅创建程序集内对象。可在调用GraphicsCapturePicker前执行以下代码:
// 强制加载目标程序集 Assembly.Load("System.Runtime.InteropServices.WindowsRuntime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); // 调用一个简单的WinRT API触发环境初始化 _ = Windows.Storage.StorageFile.GetFileFromPathAsync(".").AsTask(); // 无需等待完成,仅触发初始化流程
2. 严格确保线程上下文合规
WinRT组件对线程公寓状态要求严格,即使确认在UI线程,也可显式检查并控制上下文:
// 确认当前线程为STA(WPF UI线程默认是STA,此处做二次校验) if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA) { throw new InvalidOperationException("必须在STA线程中调用WinRT选择器"); } // 调用选择器时显式保留UI线程上下文 GraphicsCaptureItem item = await picker.PickSingleItemAsync().ConfigureAwait(true);
3. 在应用启动时提前初始化WinRT环境
将WinRT环境初始化提前到应用启动阶段(如App.xaml.cs的OnStartup方法),避免调用选择器时才处理:
protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); // 提前加载程序集 Assembly.Load("System.Runtime.InteropServices.WindowsRuntime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); // 触发WinRT初始化 _ = Windows.Storage.KnownFolders.DocumentsLibrary; }
4. 检查系统版本与权限
GraphicsCapturePicker仅支持Windows 10 1803(内部版本17134)及以上系统,且需要屏幕捕获权限:
// 检查系统版本 var osVersion = Environment.OSVersion.Version; if (osVersion.Major < 10 || (osVersion.Major == 10 && osVersion.Build < 17134)) { MessageBox.Show("当前系统版本不支持屏幕捕获选择器"); return; }
若为打包发布的WPF应用,需在Package.appxmanifest中声明屏幕捕获权限;非打包应用则需检查系统设置中是否开启对应权限。
5. 优化窗口句柄获取逻辑
避免依赖外部传入窗口句柄,改为在ScreenCaptureUserControl内部直接获取父窗口句柄,确保句柄有效性:
private IntPtr GetWindowHandle() { var parentWindow = Window.GetWindow(this); return new WindowInteropHelper(parentWindow).Handle; } // 在StartPickerCaptureAsync中替换为内部获取句柄 var intPtr = GetWindowHandle();
内容的提问来源于stack exchange,提问作者highlyyspirited

