Direct method: Seems to be perfect if end user do not use a different tray manager than the one that is installed with MS Windows.
This method was proposed by Neal Andrews and I ported it from VB (source code) to C++. The main idea behind this method is that system tray uses an ordinary toolbar control to display icons (if you don't believe me - check it out with Spy++ application). It's also an easy thing to find a handle of this control and ask it directly for the rectangle of our icon. There are two things that need to be implemented. First, we need to find a handle to toolbar control. It can be done by enumerating all windows in a system and finding the one with Shell_TrayWnd
class name (this is a main window for a system tray). Then we enumerate all the child windows of the tray to find a toolbar (ToolbarWindow32
class name).
Once we have a handle to a toolbar, we can query it for the number of icons it currently posseses:
int iButtonsCount = SendMessage(hWndTray, TB_BUTTONCOUNT, 0, 0);
If number of icons seems to be fine (is greater than 0), ee can start thinking how to ask this control for our icon. If toolbar would be a part of our application, we could just send it TB_GETBUTTON and TB_GETITEMRECT messages. It could look like:
for(int iButton=0; iButton<iButtonsCount; iButton++)
{
TBBUTTON buttonData;
SendMessage(hWndTray, TB_GETBUTTON, iButton, (LPARAM)&buttonData);
}
But in our code, such message would fail or even raise a General Protection Fault error! The main reason is that we can't pass pointer to locally allocated TBBUTTON
structure to another process (process of Windows tray application). To solve this problem, we need to allocate TBBUTTON
structure inside tray application process. Then we can send message to a toolbar with a pointer to that allocated memory, and at the end - we can read this block of memory back to our application.
Code sample (error checking was skipped for easier reading):
BOOL FindOutPositionOfIconDirectly(const HWND a_hWndOwner,
const int a_iButtonID, CRect& a_rcIcon)
{
HWND hWndTray = GetTrayToolbarControl();
DWORD dwTrayProcessID = -1;
GetWindowThreadProcessId(hWndTray, &dwTrayProcessID);
HANDLE hTrayProc =
OpenProcess(PROCESS_ALL_ACCESS, 0, dwTrayProcessID);
int iButtonsCount = SendMessage(hWndTray, TB_BUTTONCOUNT, 0, 0);
LPVOID lpData = VirtualAllocEx(hTrayProc, NULL,
sizeof(TBBUTTON), MEM_COMMIT, PAGE_READWRITE);
BOOL bIconFound = FALSE;
for(int iButton=0; iButton<iButtonsCount; iButton++)
{
DWORD dwBytesRead = -1;
TBBUTTON buttonData;
SendMessage(hWndTray, TB_GETBUTTON, iButton, (LPARAM)lpData);
ReadProcessMemory(hTrayProc, lpData, &buttonData,
sizeof(TBBUTTON), &dwBytesRead);
DWORD dwExtraData[2] = { 0,0 };
ReadProcessMemory(hTrayProc, (LPVOID)buttonData.dwData,
dwExtraData, sizeof(dwExtraData), &dwBytesRead);
HWND hWndOfIconOwner = (HWND) dwExtraData[0];
int iIconId = (int) dwExtraData[1];
if(hWndOfIconOwner != a_hWndOwner || iIconId != a_iButtonID)
{
continue;
}
if( buttonData.fsState & TBSTATE_HIDDEN )
{
break;
}
RECT rcPosition = {0,0};
SendMessage(hWndTray, TB_GETITEMRECT, iButton, (LPARAM)lpData);
ReadProcessMemory(hTrayProc, lpData,
&rcPosition, sizeof(RECT), &dwBytesRead);
MapWindowPoints(hWndTray, NULL, (LPPOINT)&rcPosition, 2);
a_rcIcon = rcPosition;
bIconFound = TRUE;
break;
}
VirtualFreeEx(hTrayProc, lpData, NULL, MEM_RELEASE);
CloseHandle(hTrayProc);
return bIconFound;
}