是什么导致我的 NotifyIcon 在 Alt+F4 后隐藏?

What is causing my NotifyIcon to hide after Alt+F4?

我有一个 WPF 应用程序,它使用 winforms NotifyIcon 在托盘上显示上下文菜单。当我执行以下步骤时,图标消失了。

  1. 右键单击托盘中的通知图标
  2. Select 显示模态对话框的上下文菜单项
  3. 关闭该对话框
  4. 按 Alt+F4

这是我看到此错误的最小示例。

XAML:

<Window x:Class="killtrayicon.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:killtrayicon"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <Button Content="button" Click="Button_Click"/>
</Grid>
</Window>

后面的代码:

namespace killtrayicon
{
    using System.Windows;

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private System.Windows.Forms.NotifyIcon notifyIcon = new System.Windows.Forms.NotifyIcon();

        public MainWindow()
        {
            InitializeComponent();

            notifyIcon.Icon = Properties.Resources.icon;
            notifyIcon.Visible = true;
            notifyIcon.Text = "test";
            notifyIcon.ContextMenu = new System.Windows.Forms.ContextMenu();
            notifyIcon.ContextMenu.MenuItems.Add("click", (s, e) =>
            {
                MessageBox.Show("menu");
            });
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            notifyIcon.Icon = Properties.Resources.icon;
        }
    }
}

点击我的主按钮 window 重置图标,通知图标再次出现。所以通知图标本身并没有被删除。检查 NotifyIcon 的实例显示它在重置图标之前仍然可见,并且 Icon 属性 指向我资源中的有效 ICO。

我怀疑上下文菜单是问题所在,因为如果我通过单击托盘图标显示模式对话框,则不会出现此问题。

如何让 NotifyIcon 不响应 Alt+F4?

编辑:此问题与 this question 重复,但该问题没有重现该问题的示例代码(死 link),该问题的 link 已提交给 Microsoft也是一个死人 link,并且没有接受实际解决方案的答案。

我找到了解决方案。 NotifyIcon 创建一个隐藏的 NativeWindow 作为 window 消息的收件人,该消息由 Shell_NotifyIcon 创建的图标生成。 window 正在使用默认的 window 过程,它像处理任何其他 window 一样处理 Alt+F4。通过将其变成 WM_CLOSE。您需要使用 Win32 API 来子类化 NativeWindow 中的 HWND,拦截 WM_CLOSE,并忽略它。

首先添加来自comctl32.dll的一些Win32方法:

public static class Comctl32
{
    public const string DLL = "comctl32.dll";

    public const uint WM_CLOSE = 0x0010;
    public const uint WM_NCDESTROY = 0x0082;

    public delegate IntPtr SubclassWndProc(IntPtr hWnd, uint uMsg, UIntPtr wParam, UIntPtr lParam, UIntPtr uIdSubclass, UIntPtr dwRefData);

    [DllImport(DLL, CharSet = CharSet.Auto, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
    public static extern bool SetWindowSubclass(
        [param: In]
            IntPtr hWnd,
        [param: In]
            SubclassWndProc pfnSubclass,
        [param: In]
            UIntPtr uIdSubclass,
        [param: In]
            UIntPtr dwRefData);

    [DllImport(DLL, CharSet = CharSet.Auto, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
    public static extern bool RemoveWindowSubclass(
        [param: In]
            IntPtr hWnd,
        [param: In]
            SubclassWndProc pfnSubclass,
        [param: In]
            UIntPtr uIdSubclass);

    [DllImport(DLL, CharSet = CharSet.Auto, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
    public static extern IntPtr DefSubclassProc(
        [param: In]
            IntPtr hWnd,
        [param: In, MarshalAs(UnmanagedType.U4)]
            uint uMsg,
        [param: In]
            UIntPtr WPARAM,
        [param: In]
            UIntPtr LPARAM);
}

然后将代码添加到您的 NotifyIcon 以子类化隐藏托盘图标 window。您需要使用反射来获取 window 因为它不是 public:

private Native.Comctl32.SubclassWndProc subclassWndProc;

...

// Get the HWND from the notify icon
Type notifyIconType = typeof(System.Windows.Forms.NotifyIcon);
BindingFlags hidden = BindingFlags.NonPublic | BindingFlags.Instance;
var window = notifyIconType.GetField("window", hidden).GetValue(this.notifyIcon) as System.Windows.Forms.NativeWindow;

// Inject our window proc to intercept window messages
this.subclassWndProc = this.TrayWndProc;
Native.Comctl32.SetWindowSubclass(window.Handle, this.subclassWndProc, UIntPtr.Zero, UIntPtr.Zero);

然后拦截WM_CLOSE忽略Alt+F4。我们还确保在 WM_NCDESTROY:

上取消子类化
private IntPtr TrayWndProc(IntPtr hWnd, uint uMsg, UIntPtr wParam, UIntPtr lParam, UIntPtr uIdSubclass, UIntPtr dwRefData)
{
    switch (uMsg)
    {
        // Ignore the close message to avoid Alt+F4 killing the tray icon
        case Native.Comctl32.WM_CLOSE:
            return IntPtr.Zero;

        // Clean up subclassing
        case Native.Comctl32.WM_NCDESTROY:
            Native.Comctl32.RemoveWindowSubclass(hWnd, this.subclassWndProc, UIntPtr.Zero))
            break;
    }

    // Invoke the default window proc
    return Native.Comctl32.DefSubclassProc(hWnd, uMsg, wParam, lParam);
}