是什么导致我的 NotifyIcon 在 Alt+F4 后隐藏?
What is causing my NotifyIcon to hide after Alt+F4?
我有一个 WPF 应用程序,它使用 winforms NotifyIcon
在托盘上显示上下文菜单。当我执行以下步骤时,图标消失了。
- 右键单击托盘中的通知图标
- Select 显示模态对话框的上下文菜单项
- 关闭该对话框
- 按 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);
}
我有一个 WPF 应用程序,它使用 winforms NotifyIcon
在托盘上显示上下文菜单。当我执行以下步骤时,图标消失了。
- 右键单击托盘中的通知图标
- Select 显示模态对话框的上下文菜单项
- 关闭该对话框
- 按 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);
}