Custom system tray?
- 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
- 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).ToInt32Return 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
- Marqué comme réponseMartin Xie - MSFTMSFT, Modérateurvendredi 19 décembre 2008 13:48
Toutes les réponses
- 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.) - 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).ToInt32Return 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
- Marqué comme réponseMartin Xie - MSFTMSFT, Modérateurvendredi 19 décembre 2008 13:48
- 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.
- 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?
- 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!
- 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.
- 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
- 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- ModifiéTermination jeudi 5 novembre 2009 09:56added link

