以编程方式刷新系统托盘图标

8

我有一个带系统托盘图标的应用程序。在卸载时,如果它正在运行,我会杀死该进程。因此,由于我没有正常停止应用程序,图标仍然留在系统托盘中,只有当我们将鼠标悬停在其上时才会删除。我编写了一段代码,可以沿着托盘运行光标,并将光标返回到初始位置。以下是我的做法:

        [DllImport("user32.dll")]
        static extern IntPtr FindWindow(string className, string windowName);
        [DllImport("user32.dll")]
        static extern IntPtr FindWindowEx(IntPtr parent, IntPtr child, string className, string windowName);
        [DllImport("user32.dll")]
        static extern bool GetWindowRect(HandleRef handle, out RECT rct);

        [StructLayout(LayoutKind.Sequential)]
        struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }

        void RefreshTray()
        {
            IntPtr taskbar_Handle = FindWindow("Shell_Traywnd", "");
            IntPtr tray_Handle = FindWindowEx(taskbar_Handle, IntPtr.Zero, "TrayNotifyWnd", "");

            RECT rct;

            if (!(GetWindowRect(new HandleRef(null, tray_Handle), out rct)))
            {
            }

            System.Drawing.Point init = Control.MousePosition;

            for (int i = rct.Left; i < rct.Right-20; i++)
            {
                Cursor.Position = new System.Drawing.Point(i, (rct.Bottom + rct.Top) / 2);
            }

            Cursor.Position = init;
         }

除了在选项“不显示通知图标”启用时,这个工作都很好。在这种情况下,有什么办法可以刷新托盘?
编辑:根据评论的建议,我改变了我的方法。我与托盘应用程序之间建立了通信,而不是杀死托盘应用程序。卸载时,我停止服务并向托盘应用程序发送特定格式的套接字消息,并要求其关闭。然后,我将通知图标可见性设置为false。这将使托盘应用程序在后台运行,因此我使用“taskkill”来删除应用程序。它在Win7和Vista中运行良好,但在Win XP中无法正常工作。但我没有编写任何特定环境的代码。是否有可能提供一些线索?

2
我曾经遇到过类似的情况。我所做的是在Form_Closing事件中处理NotifyIcon组件,这样它就能正常工作了。 - Azhar Khorasany
3
一种更不繁琐的方法可能是从卸载程序中与您的应用程序进行通信。(尽管我在这个领域没有经验) - George Duckett
8
不要写出这样的代码。不要杀戮,要礼貌地请求。 - Hans Passant
3
不要终止该进程,请求其关闭。上述代码完全没有任何意义。 - David Heffernan
谢谢大家!我将关闭应用程序。 - Niranjan
@AzharKhorasany 当应用程序被杀死时,Form_Closing不会触发。至少对我来说没有起作用。 - mmbrian
4个回答

14

这与我使用的类似。我在触摸画廊界面中添加了一个简单的浮动键盘。用户希望将我的键盘作为独立应用程序添加到他们的桌面上。因此,我创建了一个托盘应用程序。现在 - 如果它已经打开,他们启动我的画廊会怎样?

他们将拥有两个键盘。

当然 - 用户可以结束第一个,但关闭它更容易。由于我杀掉它没有任何后果,所以我这样做了。但是托盘图标仍然留下来了,因为它正在等待事件。为了解决这个问题,我刷新了托盘区域。

请注意 - 这只适用于英语语言环境安装。要使其在其他语言上工作,请将“User Promoted Notification Area”和“Notification Area”更改为相应的翻译/等效字符串。

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
    public int left;
    public int top;
    public int right;
    public int bottom;
}

[DllImport("user32.dll")]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

[DllImport("user32.dll")]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass,
    string lpszWindow);

[DllImport("user32.dll")]
public static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect);

[DllImport("user32.dll")]
public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, int wParam, int lParam);

public static void RefreshTrayArea()
{
    IntPtr systemTrayContainerHandle = FindWindow("Shell_TrayWnd", null);
    IntPtr systemTrayHandle = FindWindowEx(systemTrayContainerHandle, IntPtr.Zero, "TrayNotifyWnd", null);
    IntPtr sysPagerHandle = FindWindowEx(systemTrayHandle, IntPtr.Zero, "SysPager", null);
    IntPtr notificationAreaHandle = FindWindowEx(sysPagerHandle, IntPtr.Zero, "ToolbarWindow32", "Notification Area");
    if (notificationAreaHandle == IntPtr.Zero)
    {
        notificationAreaHandle = FindWindowEx(sysPagerHandle, IntPtr.Zero, "ToolbarWindow32",
            "User Promoted Notification Area");
        IntPtr notifyIconOverflowWindowHandle = FindWindow("NotifyIconOverflowWindow", null);
        IntPtr overflowNotificationAreaHandle = FindWindowEx(notifyIconOverflowWindowHandle, IntPtr.Zero,
            "ToolbarWindow32", "Overflow Notification Area");
        RefreshTrayArea(overflowNotificationAreaHandle);
    }
    RefreshTrayArea(notificationAreaHandle);
}

private static void RefreshTrayArea(IntPtr windowHandle)
{
    const uint wmMousemove = 0x0200;
    RECT rect;
    GetClientRect(windowHandle, out rect);
    for (var x = 0; x < rect.right; x += 5)
        for (var y = 0; y < rect.bottom; y += 5)
            SendMessage(windowHandle, wmMousemove, 0, (y << 16) + x);
}

有没有一种方法可以检查应用程序是否已经在运行并告诉它们或将其聚焦,而不是杀死它并重新启动? - mbomb007
这个答案在我的环境中(Windows 10x64)效果最佳。它可以从显示和隐藏的托盘中移除图标,而那个链接(http://maruf-dotnetdeveloper.blogspot.com/2012/08/c-refreshing-system-tray-icon.html)找到的方法无法移除已显示的图标。 - Michael Hall
1
使用Resource Hacker在%windir%\system32<LOCALE>\explorer32.exe.mui中查找本地化字符串。您需要安装所需的Windows语言包(MUI)。 - mcandal
1
你可以传递 null 来获取第一个子窗口,而不是传递 "User Promoted Notification Area""Notification Area"。这样你就可以得到一个与语言无关的版本。 - Andre Kampling
@AndreKampling,你的解决方案不起作用。 - Ahmed Osama
在Windows 10中,文件名将是explorer.exe.mui - Ahmed Osama

2

2
使用类似管道或TCP之类的东西关闭当前实例不应该很难,如果您不想这样做并且没有运行.NET4.0。正如每个人所暗示的那样,问题在于通过杀死进程,它没有机会注销托盘图标实例,因此它会一直停留在那里,直到Windows尝试向其发送事件(下次您将鼠标移动到它上面时)此时Windows将删除它。根据您使用的安装程序,这可能很容易或更难。大多数流行的安装程序框架都允许插件,并且其中一些支持Pipes,更多支持TCP请求。或者,在卸载过程开始之前,编写一个小型可执行文件,该文件可以与您的主应用程序通信并发送关闭消息。最后,请注意,如果您可以使用.NET4.0,我建议查看内置的System.IO.Pipes命名空间和包含的类。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接