【问题标题】:Refreshing system tray icons programmatically以编程方式刷新系统托盘图标
【发布时间】:2012-01-10 16:08:59
【问题描述】:

我有一个带有系统托盘图标的应用程序。卸载时,如果该进程正在运行,我将终止该进程。因此,由于没有优雅地停止应用程序,该图标仍保留在系统托盘中,并且只有当我们将鼠标悬停在其上时才会移除。我编写了一个代码,它将沿着托盘运行光标并将光标返回到其初始位置。这就是我所做的:

        [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;
         }

这在所有情况下都很好,除非启用了“不显示通知图标”选项。在这种情况下,有什么方法可以刷新托盘吗?

编辑 正如 cmets 建议的那样,我改变了方法。我没有杀死托盘应用程序,而是在我的应用程序服务(是的,忘了提,我也有一个与应用程序一起运行的服务)和托盘应用程序之间建立了通信。在卸载时,我会停止服务,从服务停止方法中,我会向托盘应用程序发送一个特定格式的套接字消息并要求它关闭,我会将通知图标可见性设置为 false。这将使托盘应用程序在后台运行,因此我使用“taskkill”来删除该应用程序。它在 Win7 和 Vista 中运行良好,但在 Win XP 中无法正常运行。但是我还没有编写任何特定于环境的代码。有什么可能的线索吗?

【问题讨论】:

  • 我曾经遇到过类似的情况。我所做的是在 Form_Closing 事件中从 NotifyIcon 组件中处理出来,并且效果很好。
  • 一种不那么老套的方法可能是通过卸载程序与您的应用程序进行通信。 (虽然我没有这方面的知识)
  • 你不想写这样的代码。不要杀人,好好问。
  • 不要终止进程。要求关闭。写上面的代码绝对没有任何意义。
  • 谢谢大家!会关闭应用程序。

标签: c# pinvoke system-tray trayicon


【解决方案1】:

这与我使用的类似。 我添加到触摸图库界面的简单浮动键盘。用户还希望将我的键盘作为桌面上的独立应用程序。所以我这样做了,为它创建了一个托盘应用程序。现在 - 如果它打开并且他们启动我的画廊怎么办?

他们会有两个键盘。

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

请注意 - 这仅适用于英语语言环境安装。要使其适用于另一种语言,请将“用户推荐的通知区域”和“通知区域”更改为翻译/等效字符串。

[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);
}

【讨论】:

  • 有没有一种方法可以检查应用程序是否已经在运行并告诉他们或关注它,而不是杀死它并重新启动它?
  • 这个答案效果最好,至少对于我的环境是 windows 10x64。它会从显示和隐藏的图标中删除,在 (maruf-dotnetdeveloper.blogspot.com/2012/08/…) 找到的图标不会删除显示的图标。
  • 使用 Resource Hacker 在 %windir%\system32\\explorer32.exe.mui 中查找本地化字符串。您需要安装所需的 Windows 语言包 (MUI)。
  • 你可以通过null而不是传递"User Promoted Notification Area""Notification Area"来获取第一个子窗口。这样你就得到了一个独立于语言的版本。
【解决方案2】:

【讨论】:

    【解决方案3】:

    使用这个工具 http://www.codeproject.com/Articles/19620/LP-TrayIconBuster

    它遍历 TrayNotifyWnd 和 NotifyIconOverflowWindow 中的 ToolBarButtons 并删除那些具有空文件名的按钮。

    【讨论】:

      【解决方案4】:

      如果您不想这样做并且没有运行 .NET4.0,则使用管道或 TCP 之类的东西关闭当前实例应该不难。

      正如每个人都暗示的那样,问题在于通过终止您的进程,它没有机会取消注册其托盘图标实例,因此它会一直存在,直到 Windows 尝试向它发送事件(下次您移动将鼠标悬停在它上面),此时 Windows 将删除它。

      根据您使用的安装程序,这可能很容易或更困难。大多数流行的安装程序框架允许插件,其中一些支持管道,更多支持 TCP 请求。或者,编写一个小型可执行文件,您的安装程序可以在开始卸载过程之前运行它,它会与您的主应用程序通信并发送关闭消息。

      最后一点,如果您可以使用 .NET4.0,那么我建议您查看内置的 System.IO.Pipes 命名空间和包含的类。

      【讨论】:

        猜你喜欢
        • 2011-10-14
        • 1970-01-01
        • 1970-01-01
        • 2011-11-11
        • 1970-01-01
        • 1970-01-01
        • 2016-08-22
        • 1970-01-01
        • 2011-10-25
        相关资源
        最近更新 更多