WinUI3(.NET8)访问App.xaml的TrayIcon资源触发COMException错误
问题详情
在WinUI 3(.NET 8)项目中集成H.NotifyIcon.WinUI包,参考H.NotifyIcon.Apps.WinUI.Windowless示例实现系统托盘功能时,从App.xaml的ResourceDictionary中访问声明的TaskbarIcon资源时触发运行时错误:
System.Runtime.InteropServices.COMException: 'Unspecified error' (HResult: -2147467259)
错误堆栈:
at System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(Int32 errorCode) at WinRT.DelegateExtensions.DynamicInvokeAbi(Delegate del, Object[] invoke_params) at ABI.Windows.Foundation.Collections.IMapMethods`2.Lookup(IObjectReference obj, K key) at ABI.System.Collections.Generic.IDictionaryMethods`2.Lookup(IObjectReference obj, K key) at ABI.System.Collections.Generic.IDictionaryMethods`2.Indexer_Get(IObjectReference obj, Dictionary`2 __lookupCache, K key) at Microsoft.UI.Xaml.ResourceDictionary.get_Item(Object key) at Project.App.OnLaunched(LaunchActivatedEventArgs args) in C:\Project\App.xaml.cs:line 38 at Microsoft.UI.Xaml.Application.Microsoft.UI.Xaml.IApplicationOverrides.OnLaunched(LaunchActivatedEventArgs args) at ABI.Microsoft.UI.Xaml.IApplicationOverrides.Do_Abi_OnLaunched_0(IntPtr thisPtr, IntPtr args)
集成步骤:
- 添加
H.NotifyIcon.WinUINuGet依赖 - 在App.xaml的
<Application/>标签中添加xmlns:tb="using:H.NotifyIcon"命名空间 - 将示例中
TrayIconResources.xaml的XamlUICommand和tb:TaskbarIcon代码添加到App.xaml的ResourceDictionary(尝试过直接引用MergedDictionaries,问题依旧) - 在
App.xaml.cs的OnLaunched方法中访问Resources["TrayIcon"]
核心现象:ResourceDictionary中的XamlUICommand可正常访问,仅TaskbarIcon资源触发错误;移除IconSource属性后错误仍存在,图标文件的生成操作和复制设置均正确。
解决方案
1. 避免在Application级ResourceDictionary中声明TaskbarIcon
TaskbarIcon依赖Win32托盘组件的UI上下文,Application级资源初始化时,窗口环境尚未完全就绪,导致COM交互失败。建议将TaskbarIcon的声明移至窗口级ResourceDictionary,或通过代码动态创建。
2. 代码动态创建TaskbarIcon(推荐)
在OnLaunched方法中动态实例化TaskbarIcon,确保窗口上下文就绪后再创建:
using H.NotifyIcon; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Windows.ApplicationModel.Activation; namespace Project { public partial class App : Application { private TaskbarIcon _trayIcon; protected override void OnLaunched(LaunchActivatedEventArgs args) { // 初始化主窗口 var mainWindow = new MainWindow(); mainWindow.Activate(); // 创建TaskbarIcon实例 _trayIcon = new TaskbarIcon { Visibility = Visibility.Visible, ToolTipText = "ToolTip", ContextMenuMode = ContextMenuMode.SecondWindow, LeftClickCommand = Resources["ShowHideWindowCommand"] as ICommand, NoLeftClickDelay = true, IconSource = new BitmapIconSource { UriSource = new Uri("ms-appx:///Assets/app.ico") } }; // 配置上下文菜单 var flyout = new MenuFlyout(); flyout.Items.Add(new MenuFlyoutItem { Command = Resources["ShowHideWindowCommand"] as ICommand }); flyout.Items.Add(new MenuFlyoutSeparator()); flyout.Items.Add(new MenuFlyoutItem { Command = Resources["ShowWindowViewCommand"] as ICommand }); flyout.Items.Add(new MenuFlyoutItem { Command = Resources["ShowEventViewCommand"] as ICommand }); flyout.Items.Add(new MenuFlyoutSeparator()); flyout.Items.Add(new MenuFlyoutItem { Command = Resources["ExitApplicationCommand"] as ICommand }); _trayIcon.ContextFlyout = flyout; } } }
3. 无窗口项目的额外处理
如果是无窗口项目,需先初始化CoreWindow确保UI线程环境就绪:
using Windows.UI.Core; protected override async void OnLaunched(LaunchActivatedEventArgs args) { if (CoreApplication.MainView == null) { CoreApplication.CreateNewView(); await CoreApplication.MainView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { var window = CoreWindow.GetForCurrentThread(); window.Activate(); }); } // 后续创建TaskbarIcon的代码同上 }
4. 更新H.NotifyIcon版本
确保使用最新稳定版的H.NotifyIcon.WinUI NuGet包,旧版本可能存在与.NET 8的兼容性问题。
原因分析
TaskbarIcon内部依赖Win32原生托盘组件,需要完整的UI窗口上下文才能完成COM对象初始化。而Application级ResourceDictionary在应用启动早期加载,此时窗口环境尚未完全建立,导致托盘组件初始化失败,抛出未指定的COM错误。XamlUICommand是纯逻辑对象,无UI上下文依赖,因此可以正常访问。
内容的提问来源于stack exchange,提问作者rcbevans

