Poser une questionPoser une question
 

TraitéeCustom system tray?

  • lundi 15 décembre 2008 11:40Termination Médailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateur
     
    Hello coders,

    Is there a way in VB2005/2008 to list all the apps in the systray and nothing but them. I can list all the windows by theyre hwnd, but it also shows the taskbar windows and windows not even shown anywhere in windows.
    So I need to somehow list all the systemtray icons in a listbox.

    If something's not clear, or if you need a code example please reply!
    Help is apprecieted.
    Greets, Thom.

Réponses

  • mercredi 17 décembre 2008 18:46jo0ls Médailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateur
     TraitéeA du code
    Well, I had some success. I got data back from the tray, listing the process identifiers, paths to the processes, text and some of the icons. From reading here and there it seems that some apps, like msn messenger, use windows messages to change the icons when they are running, and the handles that you get using the technique in the article aren't standard HICONs. To capture those you need to create your own dummy tray window (class "Shell_TrayWnd") and catch the WM_COPYDATA messages. I had no luck doing that overriding CreateParams as it throws as "invalid class" error. I found others getting the same error searching the web.

    There follows a snippet of the code that gets some of the information. The rest of the code (definitions of all the stuff it uses) is here.


    Note - I'm running on Vista x64 and one of the structures TBBUTTON will need to be redefined for x86. I'll test it later on XP32.

    Public Class Tray 
     
        Private Shared Function FindTrayToolbarWindow() As IntPtr 
            Dim hwnd As IntPtr = NativeMethods.FindWindow("Shell_TrayWnd"Nothing
            If (hwnd <> IntPtr.Zero) Then 
                hwnd = NativeMethods.FindWindowEx(hwnd, Nothing"TrayNotifyWnd"Nothing
                If (hwnd <> IntPtr.Zero) Then 
                    hwnd = NativeMethods.FindWindowEx(hwnd, Nothing"SysPager"Nothing
                    If (hwnd <> IntPtr.Zero) Then 
                        hwnd = NativeMethods.FindWindowEx(hwnd, Nothing"ToolbarWindow32"Nothing
                    End If 
                End If 
            End If 
            Return hwnd 
        End Function 
     
        Private Shared Function CountButtons(ByVal hTrayToolbarWnd As IntPtr) As Integer 
            Dim count As Integer = NativeMethods.SendMessage(hTrayToolbarWnd, TB_BUTTONCOUNT, IntPtr.Zero, _
                                           IntPtr.Zero).ToInt32 
            Return count 
        End Function 
     
        Private Shared Function GetButtons(ByVal hwndTrayToolbar As IntPtr) As List(Of TrayButton) 
            Dim buttonCount As Integer = CountButtons(hwndTrayToolbar) 
            Dim buttons As New List(Of TrayButton)(buttonCount) 
     
            ' We need to allocation memory inside the process controlling the Tray toolbar. 
     
            ' Find the process ID of the process controlling the tray, using the tray handle. 
            Dim pid As UInteger 
            Dim uiResult As UInteger = GetWindowThreadProcessId(hwndTrayToolbar, pid) 
     
            ' Get a process handle.         
            Dim hProcess As SafeProcessHandle = Nothing 
            Try 
                hProcess = OpenProcess(PROCESS_VM_OPERATION Or PROCESS_VM_READ Or PROCESS_VM_WRITE Or _
                                                      PROCESS_QUERY_INFORMATION, 
    False, pid) 
                For index As Integer = 0 To buttonCount - 1 
                    Dim tb As TrayButton = GetButton(hProcess, index, hwndTrayToolbar) 
                    If tb IsNot Nothing Then buttons.Add(tb) 
                Next 
            Finally 
                If hProcess IsNot Nothing Then 
                    hProcess.Close() 
                    hProcess.Dispose() 
                End If 
            End Try 
     
            Return buttons 
        End Function 
     
        Private Shared Function GetButton(ByVal hProcess As SafeProcessHandle, ByVal index As Integer, _ 
                                          ByVal hwndTrayToolbar As IntPtr) As TrayButton 
            Dim tbb As New TBBUTTON 
            Dim friendlyTB As New TrayButton 
            friendlyTB.SetTrayIndex(index) 
            Dim pButton As IntPtr = IntPtr.Zero 
            Try 
                ' Create memory in the explorer process to store a TBBUTTON strucutre: 
                pButton = VirtualAllocEx(hProcess, Nothing, tbb.Size, MEM_COMMIT, PAGE_READWRITE) 
                ' Use SendMessage to request that the memory is filled with the TBBUTTON @ index. 
                Dim bResult As Boolean = SendMessage(hwndTrayToolbar, TB_GETBUTTON, index, pButton) 
                If bResult = False Then Throw New Win32Exception 
                ' And read the memory from the other process: 
                Dim bytesReturned As Integer 
                bResult = ReadProcessMemory(hProcess, pButton, tbb, tbb.Size, bytesReturned) 
                If bResult = False Then Throw New Win32Exception 
                ' Now we need to get the NOTIFYICONDATA-sorta structure: 
                Dim nid As New NotifyIconData 
                ' It lives at the memory pointed to by tbb.data, again it is in the other process: 
                bResult = ReadProcessMemory(hProcess, tbb.data, nid, nid.Size, bytesReturned) 
                If bResult = False Then Throw New Win32Exception 
                ' And we can grab the string too, though it might not work for all icons as some store an index: 
                Dim sb As New StringBuilder(1024) 
                bResult = ReadProcessMemory(hProcess, tbb.iString, sb, sb.Capacity, bytesReturned) 
                If bResult = False Then Throw New Win32Exception 
                friendlyTB.SetText(sb.ToString) 
                ' Next we can get the process id, from the buttons hwnd: 
                Dim id As UInteger 
                GetWindowThreadProcessId(nid.hWnd, id) 
                friendlyTB.SetPid(id) 
                ' And another api to get the image file name from the process id: 
                friendlyTB.SetProcessPath(FilenameFromPid(id)) 
                ' For the icon, see if one exists first.             
                Dim iconInfo As New ICONINFO 
                bResult = GetIconInfo(nid.hIcon, iconInfo) 
                Try 
                    If bResult Then 
                        Dim icon As Icon = icon.FromHandle(nid.hIcon) 
                        friendlyTB.SetIcon(icon) 
                    Else 
                        ' These are invalid icon handles. The icons are using a different technique 
                        ' - sending windows messages to the tray to get it to update the icons. 
                    End If 
                Finally 
                    ' prevent memory leaks - we need to clean up the handles inside the ICONINFO structure. 
                    If iconInfo.hbmColor <> IntPtr.Zero Then 
                        DeleteObject(iconInfo.hbmColor).ToString() 
                    End If 
                    If iconInfo.hbmMask <> IntPtr.Zero Then 
                        DeleteObject(iconInfo.hbmMask).ToString() 
                    End If 
                End Try 
            Finally 
                If pButton <> IntPtr.Zero Then 
                    Dim freeResult As Boolean = VirtualFreeEx(hProcess, pButton, 0, MEM_RELEASE) 
                    If freeResult = False Then Throw New Win32Exception 
                End If 
            End Try 
                Return friendlyTB 
        End Function 
     
        Private Shared Function FilenameFromPid(ByVal pid As UInteger) As String 
            Dim sb As New StringBuilder(260) 
            Dim pTrayProcess As SafeProcessHandle = Nothing 
            Try 
                pTrayProcess = OpenProcess(PROCESS_QUERY_INFORMATION Or PROCESS_VM_READ, False, pid) 
                GetModuleFileNameEx(pTrayProcess, Nothing, sb, sb.Capacity) 
                Return sb.ToString 
            Finally 
                If pTrayProcess IsNot Nothing Then 
                    pTrayProcess.Close() 
                    pTrayProcess.Dispose() 
                End If 
            End Try 
        End Function 
     
        Public Shared Function GetTrayButtons() As List(Of TrayButton) 
            Dim hwnd As IntPtr = FindTrayToolbarWindow() 
            Return GetButtons(hwnd) 
        End Function 
     
    End Class 
     



Toutes les réponses

  • lundi 15 décembre 2008 12:29jo0ls Médailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateur
     
    Here's one technique, wrong language...

    http://www.codeproject.com/KB/applications/ShellTrayInfo.aspx

    Edit: Ok, this will be tricky.

    At some stage you will be sending TB_GETBUTTON to get back a TBBUTTON structure. This won't work unless you allocate memory for the TBBUTTON inside the other process. So you can't just create a variable referencing a TBBUTTON and use SendMessage - you'll get "invalid handle" back. (Processes create handles to their own virtual address spaces, so one processes handles don't point to the same bit of memory if you used them in another process.)


  • mercredi 17 décembre 2008 18:46jo0ls Médailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateur
     TraitéeA du code
    Well, I had some success. I got data back from the tray, listing the process identifiers, paths to the processes, text and some of the icons. From reading here and there it seems that some apps, like msn messenger, use windows messages to change the icons when they are running, and the handles that you get using the technique in the article aren't standard HICONs. To capture those you need to create your own dummy tray window (class "Shell_TrayWnd") and catch the WM_COPYDATA messages. I had no luck doing that overriding CreateParams as it throws as "invalid class" error. I found others getting the same error searching the web.

    There follows a snippet of the code that gets some of the information. The rest of the code (definitions of all the stuff it uses) is here.


    Note - I'm running on Vista x64 and one of the structures TBBUTTON will need to be redefined for x86. I'll test it later on XP32.

    Public Class Tray 
     
        Private Shared Function FindTrayToolbarWindow() As IntPtr 
            Dim hwnd As IntPtr = NativeMethods.FindWindow("Shell_TrayWnd"Nothing
            If (hwnd <> IntPtr.Zero) Then 
                hwnd = NativeMethods.FindWindowEx(hwnd, Nothing"TrayNotifyWnd"Nothing
                If (hwnd <> IntPtr.Zero) Then 
                    hwnd = NativeMethods.FindWindowEx(hwnd, Nothing"SysPager"Nothing
                    If (hwnd <> IntPtr.Zero) Then 
                        hwnd = NativeMethods.FindWindowEx(hwnd, Nothing"ToolbarWindow32"Nothing
                    End If 
                End If 
            End If 
            Return hwnd 
        End Function 
     
        Private Shared Function CountButtons(ByVal hTrayToolbarWnd As IntPtr) As Integer 
            Dim count As Integer = NativeMethods.SendMessage(hTrayToolbarWnd, TB_BUTTONCOUNT, IntPtr.Zero, _
                                           IntPtr.Zero).ToInt32 
            Return count 
        End Function 
     
        Private Shared Function GetButtons(ByVal hwndTrayToolbar As IntPtr) As List(Of TrayButton) 
            Dim buttonCount As Integer = CountButtons(hwndTrayToolbar) 
            Dim buttons As New List(Of TrayButton)(buttonCount) 
     
            ' We need to allocation memory inside the process controlling the Tray toolbar. 
     
            ' Find the process ID of the process controlling the tray, using the tray handle. 
            Dim pid As UInteger 
            Dim uiResult As UInteger = GetWindowThreadProcessId(hwndTrayToolbar, pid) 
     
            ' Get a process handle.         
            Dim hProcess As SafeProcessHandle = Nothing 
            Try 
                hProcess = OpenProcess(PROCESS_VM_OPERATION Or PROCESS_VM_READ Or PROCESS_VM_WRITE Or _
                                                      PROCESS_QUERY_INFORMATION, 
    False, pid) 
                For index As Integer = 0 To buttonCount - 1 
                    Dim tb As TrayButton = GetButton(hProcess, index, hwndTrayToolbar) 
                    If tb IsNot Nothing Then buttons.Add(tb) 
                Next 
            Finally 
                If hProcess IsNot Nothing Then 
                    hProcess.Close() 
                    hProcess.Dispose() 
                End If 
            End Try 
     
            Return buttons 
        End Function 
     
        Private Shared Function GetButton(ByVal hProcess As SafeProcessHandle, ByVal index As Integer, _ 
                                          ByVal hwndTrayToolbar As IntPtr) As TrayButton 
            Dim tbb As New TBBUTTON 
            Dim friendlyTB As New TrayButton 
            friendlyTB.SetTrayIndex(index) 
            Dim pButton As IntPtr = IntPtr.Zero 
            Try 
                ' Create memory in the explorer process to store a TBBUTTON strucutre: 
                pButton = VirtualAllocEx(hProcess, Nothing, tbb.Size, MEM_COMMIT, PAGE_READWRITE) 
                ' Use SendMessage to request that the memory is filled with the TBBUTTON @ index. 
                Dim bResult As Boolean = SendMessage(hwndTrayToolbar, TB_GETBUTTON, index, pButton) 
                If bResult = False Then Throw New Win32Exception 
                ' And read the memory from the other process: 
                Dim bytesReturned As Integer 
                bResult = ReadProcessMemory(hProcess, pButton, tbb, tbb.Size, bytesReturned) 
                If bResult = False Then Throw New Win32Exception 
                ' Now we need to get the NOTIFYICONDATA-sorta structure: 
                Dim nid As New NotifyIconData 
                ' It lives at the memory pointed to by tbb.data, again it is in the other process: 
                bResult = ReadProcessMemory(hProcess, tbb.data, nid, nid.Size, bytesReturned) 
                If bResult = False Then Throw New Win32Exception 
                ' And we can grab the string too, though it might not work for all icons as some store an index: 
                Dim sb As New StringBuilder(1024) 
                bResult = ReadProcessMemory(hProcess, tbb.iString, sb, sb.Capacity, bytesReturned) 
                If bResult = False Then Throw New Win32Exception 
                friendlyTB.SetText(sb.ToString) 
                ' Next we can get the process id, from the buttons hwnd: 
                Dim id As UInteger 
                GetWindowThreadProcessId(nid.hWnd, id) 
                friendlyTB.SetPid(id) 
                ' And another api to get the image file name from the process id: 
                friendlyTB.SetProcessPath(FilenameFromPid(id)) 
                ' For the icon, see if one exists first.             
                Dim iconInfo As New ICONINFO 
                bResult = GetIconInfo(nid.hIcon, iconInfo) 
                Try 
                    If bResult Then 
                        Dim icon As Icon = icon.FromHandle(nid.hIcon) 
                        friendlyTB.SetIcon(icon) 
                    Else 
                        ' These are invalid icon handles. The icons are using a different technique 
                        ' - sending windows messages to the tray to get it to update the icons. 
                    End If 
                Finally 
                    ' prevent memory leaks - we need to clean up the handles inside the ICONINFO structure. 
                    If iconInfo.hbmColor <> IntPtr.Zero Then 
                        DeleteObject(iconInfo.hbmColor).ToString() 
                    End If 
                    If iconInfo.hbmMask <> IntPtr.Zero Then 
                        DeleteObject(iconInfo.hbmMask).ToString() 
                    End If 
                End Try 
            Finally 
                If pButton <> IntPtr.Zero Then 
                    Dim freeResult As Boolean = VirtualFreeEx(hProcess, pButton, 0, MEM_RELEASE) 
                    If freeResult = False Then Throw New Win32Exception 
                End If 
            End Try 
                Return friendlyTB 
        End Function 
     
        Private Shared Function FilenameFromPid(ByVal pid As UInteger) As String 
            Dim sb As New StringBuilder(260) 
            Dim pTrayProcess As SafeProcessHandle = Nothing 
            Try 
                pTrayProcess = OpenProcess(PROCESS_QUERY_INFORMATION Or PROCESS_VM_READ, False, pid) 
                GetModuleFileNameEx(pTrayProcess, Nothing, sb, sb.Capacity) 
                Return sb.ToString 
            Finally 
                If pTrayProcess IsNot Nothing Then 
                    pTrayProcess.Close() 
                    pTrayProcess.Dispose() 
                End If 
            End Try 
        End Function 
     
        Public Shared Function GetTrayButtons() As List(Of TrayButton) 
            Dim hwnd As IntPtr = FindTrayToolbarWindow() 
            Return GetButtons(hwnd) 
        End Function 
     
    End Class 
     



  • vendredi 19 décembre 2008 14:09Termination Médailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateur
     
    Hi,

    First of all: Thank you for your time and effort.
    There might be a problem however. When I compile it (F5, debug mode) it throws an "A first chance exception of type 'System.ComponentModel.Win32Exception' occurred in TrayHelper.dll" error.
    I'm using Windows Vista x64 Ultimate. I'll try it on a x86 machine too, but so far it ain't working.

    Regards,
    Thom.

  • dimanche 21 décembre 2008 09:19Termination Médailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateur
     
    ok, I tested it on my x86 vista machine and it works like a charm. However, on my X64 ultimate PC it won't work yet. The form just stays empty.
    Also, would this work on XP too?
  • mardi 23 décembre 2008 22:08Termination Médailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateur
     
    Ok I tried it again today and after a recompile it actually worked!
    This is an awesome plugin, i´m sure going to use it.
    Credit will be given. thanks a lot!
  • mercredi 24 décembre 2008 11:19jo0ls Médailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateur
     
    Here I have two declarations of TBBUTTON, one for x86 and one for x64. To determine which to use, I check the size of an IntPtr at runtime:
    |
    v
    http://cid-862dee3ec267cb5c.skydrive.live.com/self.aspx/Public/GetTheTray2.zip



    If you have it working on the two OSes, then it might be that the structure is declared with the 32-bit layout, and the project is compiled for an x86 build target. Then it will be fine on x86, and run under WOW64 on x64, I think.
  • mercredi 24 décembre 2008 11:38Termination Médailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateur
     
    Works like a charm! Even on XP x86 and x64!
    Next problem is how to minimize/maximize the programs associated with the icons, and how to get the contextmenu for those programs.
    I found a VB6 program doing that, but I can't get it to work on VB.NET
    You can get the program here:
    http://www.planetsourcecode.com/vb/scripts/ShowCode.asp?txtCodeId=44558&lngWId=1
  • mercredi 4 novembre 2009 16:29Termination Médailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateur
     
    I know i'm resurrecting a long-dead thread but i needed to share this.
    The above program no longer works correctly in windows 7. It doesn't show the icons in the NotifyIconOverflowWindow (the tiny window you get when you click the arrow)
    I corrected the code so that is does.
    You can find it here: http://cid-ced963654a4b84a6.skydrive.live.com/self.aspx/.Public/GetTheTray%5E_x32%5E_x64%5E_win7.zip