从系统托盘中的图标获取工具提示文本

Posted

技术标签:

【中文标题】从系统托盘中的图标获取工具提示文本【英文标题】:Get ToolTip Text from Icon in System Tray 【发布时间】:2011-06-16 02:28:14 【问题描述】:

我正在尝试从系统托盘中读取不是我自己的应用程序的工具提示文本。基本上,我认为这是获取一些状态信息的最简单方法。

使用 C# 提取 ToolTip 文本的最简单方法是什么?

【问题讨论】:

我很想知道是否有人对此有解决方案。一年前我遇到过,但想不通。我尝试了获取应用程序的主要 HWND 并检索标题等方法,但它不适用于我尝试访问的应用程序。也许对其他人来说可能会有所不同。 我认为你会遇到的问题是,实际上有很多方法可以显示工具提示,从自定义窗口控件到标准窗口消息,甚至绘制驱动的 UI,以及什么不是。你有更详细的吗?比如它是什么程序,等等? 这是潘多拉一号桌面应用程序。不幸的是,它是一个 Adob​​e AIR 应用程序,但它确实创建了一个标准外观的系统托盘图标,用于显示当前播放的曲目。实际上更好的是获取该图标的上下文菜单文本,因为它还具有播放/暂停状态......这将非常有帮助。 你有没有想过这个问题? 【参考方案1】:

让我们从寻找系统托盘窗口句柄开始:

    [DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr FindWindowEx(IntPtr hWndParent, IntPtr hWndChildAfter, string lpClassName, string lpWindowName);

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

    static IntPtr GetSystemTrayHandle()
    
        IntPtr hWndTray = FindWindow("Shell_TrayWnd", null);
        if (hWndTray != IntPtr.Zero)
        
            hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "TrayNotifyWnd", null);
            if (hWndTray != IntPtr.Zero)
            
                hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "SysPager", null);
                if (hWndTray != IntPtr.Zero)
                
                    hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "ToolbarWindow32", null);
                    return hWndTray;
                
            
        

        return IntPtr.Zero;
    

系统托盘窗口是一个工具栏类,需要获取单个图标的信息:

    private static unsafe bool GetTBButton(IntPtr hToolbar, int i, ref TBBUTTON tbButton, ref string text, ref IntPtr ipWindowHandle)
    
        // One page
        const int BUFFER_SIZE = 0x1000;

        byte[] localBuffer = new byte[BUFFER_SIZE];

        UInt32 processId = 0;
        UInt32 threadId = User32.GetWindowThreadProcessId(hToolbar, out processId);

        IntPtr hProcess = Kernel32.OpenProcess(ProcessRights.ALL_ACCESS, false, processId);
        if (hProcess == IntPtr.Zero)  Debug.Assert(false); return false; 

        IntPtr ipRemoteBuffer = Kernel32.VirtualAllocEx(
            hProcess,
            IntPtr.Zero,
            new UIntPtr(BUFFER_SIZE),
            MemAllocationType.COMMIT,
            MemoryProtection.PAGE_READWRITE);

        if (ipRemoteBuffer == IntPtr.Zero)  Debug.Assert(false); return false; 

        // TBButton
        fixed (TBBUTTON* pTBButton = &tbButton)
        
            IntPtr ipTBButton = new IntPtr(pTBButton);

            int b = (int)User32.SendMessage(hToolbar, TB.GETBUTTON, (IntPtr)i, ipRemoteBuffer);
            if (b == 0)  Debug.Assert(false); return false; 

            // this is fixed
            Int32 dwBytesRead = 0;
            IntPtr ipBytesRead = new IntPtr(&dwBytesRead);

            bool b2 = Kernel32.ReadProcessMemory(
                hProcess,
                ipRemoteBuffer,
                ipTBButton,
                new UIntPtr((uint)sizeof(TBBUTTON)),
                ipBytesRead);

            if (!b2)  Debug.Assert(false); return false; 
        

        // button text
        fixed (byte* pLocalBuffer = localBuffer)
        
            IntPtr ipLocalBuffer = new IntPtr(pLocalBuffer);

            int chars = (int)User32.SendMessage(hToolbar, TB.GETBUTTONTEXTW, (IntPtr)tbButton.idCommand, ipRemoteBuffer);
            if (chars == -1)  Debug.Assert(false); return false; 

            // this is fixed
            Int32 dwBytesRead = 0;
            IntPtr ipBytesRead = new IntPtr(&dwBytesRead);

            bool b4 = Kernel32.ReadProcessMemory(
                hProcess,
                ipRemoteBuffer,
                ipLocalBuffer,
                new UIntPtr(BUFFER_SIZE),
                ipBytesRead);

            if (!b4)  Debug.Assert(false); return false; 

            text = Marshal.PtrToStringUni(ipLocalBuffer, chars);

            if (text == " ") text = String.Empty;
        

        Kernel32.VirtualFreeEx(
            hProcess,
            ipRemoteBuffer,
            UIntPtr.Zero,
            MemAllocationType.RELEASE);

        Kernel32.CloseHandle(hProcess);

        return true;
    

现在,您所要做的就是遍历按钮并获取数据:

        IntPtr _ToolbarWindowHandle = GetSystemTrayHandle();
        UInt32 count = User32.SendMessage(_ToolbarWindowHandle, TB.BUTTONCOUNT, 0, 0);

        for (int i = 0; i < count; i++)
        
            TBBUTTON tbButton = new TBBUTTON();
            string text = String.Empty;
            IntPtr ipWindowHandle = IntPtr.Zero;

            bool b = GetTBButton(_ToolbarWindowHandle, i, ref tbButton, ref text, ref ipWindowHandle);

        

【讨论】:

为了简单起见,我省略了一些 PInvoke 声明,您自己找到它们应该没有问题 仍在尝试使其正常工作... TB.GETBUTTON 来自什么?我在任何地方都找不到所需的结构。 出于好奇,您是否知道如何从特定 SysTray 图标的上下文菜单中获取文本?另外,上面是否有任何简单的方法来获取特定应用程序的托盘图标句柄? 我相信这只能在 Win7 中使用,如果您选择“始终在任务栏上显示所有图标和通知”,否则如果用户不打开隐藏图标,按钮将不可用。 您的 GetTBButton 采用 ref 参数 ipWindowHandle 但似乎从未实际设置或使用它?【参考方案2】:

如果有人遇到这个帖子并且有同样的需求,我发布了一个帖子,询问如何正确实现代码示例并获得了很多帮助,以及一个可行的解决方案:

Trouble implementing code example using PInvoke Declarations

【讨论】:

以上是关于从系统托盘中的图标获取工具提示文本的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Python 和 GTK 创建具有自定义文本和透明背景的状态图标/系统托盘图标?

如何制作仅在系统托盘中运行的 .NET Windows 窗体应用程序?

Winforms 通知图标在系统托盘中重复

node-webkit,nwjs 系统托盘Tray实践

node-webkit,nwjs 系统托盘Tray实践

VC++检查程序托盘图标是否掩藏到沙漏区域中(附源码)