对于Win7系统,程序的托盘图标最终是放置在和XP一样的ToolbarWindow32工具条窗口,但是有两个地方,同样是ToolbarWindow32工具条窗口,父窗口是不一样的。在右下角可见区域,ToolbarWindow32工具条窗口的父窗口是SysPager窗口,遍历方法同XP一样。对于掩藏的ToolbarWindow32工具条窗口,必须通过点击桌面右下角的可见区域左边的一个按钮,才会显示出来,其父窗口则是一个叫做托盘溢出的窗口NotifyIconOverflowWindow。
得到句柄的代码:
HWND FindTrayToolbarWindow()
{
HWND hWnd = ::FindWindow(L"Shell_TrayWnd", NULL);
if(hWnd)
{
hWnd = ::FindWindowEx(hWnd,NULL,L"TrayNotifyWnd", NULL);
if(hWnd)
{
hWnd = ::FindWindowEx(hWnd,NULL,L"SysPager", NULL);
if(hWnd)
{
hWnd = ::FindWindowEx(hWnd, NULL,L"ToolbarWindow32", NULL);
}
}
}
return hWnd;
}
//获取溢出托盘区窗口句柄
HWND FindNotifyIconOverflowWindow()
{
HWND hWnd = NULL;
hWnd = FindWindow(L"NotifyIconOverflowWindow", NULL);
if (hWnd != NULL)
{
hWnd = FindWindowEx(hWnd, NULL, L"ToolbarWindow32", NULL);
}
return hWnd;
}
得到句柄后:
给ToolbarWindow32句柄发送 TB_BUTTONCOUNT消息得到托盘窗口TBBUTTON的个数 -> 通过得到总数,用FOR循环,向每个BUTTON发送TB_GETBUTTON消息 -> 用ReadProcessMemory读取每个TBBUTTON结构 -> 再通过TBBUTTON.dwData 得到TRAYDATA结构。
TB_GETBUTTON的SendMessage是跨进程操作,lAddress要在ToolbarWindow32所在的进程中申请
void EnumNotifyWindow(HWND hWnd)
{
DWORD dwProcessId = 0;
GetWindowThreadProcessId(hWnd,&dwProcessId);
HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE,FALSE, dwProcessId);
if ( hProcess==NULL ){
return;
}
LPVOID lAddress = VirtualAllocEx(hProcess, 0, 4096, MEM_COMMIT, PAGE_READWRITE);
if ( lAddress==NULL ){
return;
}
DWORD lTextAdr = 0;
BYTE buff[1024] = {0};
HWND hwnd = NULL;
UINT uid;
UINT uCallbackMessage;
int nDataOffset = sizeof(TBBUTTON) - sizeof(INT_PTR) - sizeof(DWORD_PTR);
int nStrOffset = 18;
//是否是64位
#ifdef _WIN64
#else
nDataOffset+=4;
nStrOffset+=6;
#endif
//得到圖標個數
int lButton = SendMessage(hWnd, TB_BUTTONCOUNT, 0, 0);
for (int i = 0; i < lButton; i++)
{
//得到TBBUTTON的地址
SendMessage(hWnd, TB_GETBUTTON, i, (LPARAM)lAddress);
//读TBBUTTON的地址
ReadProcessMemory(hProcess,lAddress,&tb,sizeof(TBBUTTON),0);
//得到TRAYDATA的地址
ReadProcessMemory(hProcess, (LPVOID)((DWORD)lAddress + nDataOffset), &lTextAdr, 4, 0);
if ( lTextAdr!=-1 ) {
//讀TRAYDATA的地址
ReadProcessMemory(hProcess, (LPCVOID)lTextAdr, buff, 1024, 0);
hwnd = (HWND)(*((DWORD*)buff));
uid = (UINT)(*((UINT*)buff+1));
qInfo()<<uid;
uCallbackMessage = (UINT)(*((DWORD*)buff+2));
qInfo()<<uCallbackMessage;
QString strPath = QString().fromWCharArray((WCHAR *)buff + nStrOffset);
qInfo()<<strPath;
QString strTitle = QString().fromWCharArray((WCHAR *)buff + nStrOffset + MAX_PATH);
qInfo()<<strTitle;
hIcon = (HICON)(*((DWORD*)buff+6));
}
}
VirtualFreeEx(hProcess, lAddress, 4096, MEM_RELEASE);
CloseHandle(hProcess);
}
通过TB_GETBUTTON得到的是TBBUTTON结构,其dwData指向的是一个TRAYDATA
typedef struct _TBBUTTON {
int iBitmap;
int idCommand;
BYTE fsState;
BYTE fsStyle;
#ifdef _WIN64
BYTE bReserved[6]; // padding for alignment
#elif defined(_WIN32)
BYTE bReserved[2]; // padding for alignment
#endif
DWORD_PTR dwData;
INT_PTR iString;
} TBBUTTON, NEAR* PTBBUTTON, *LPTBBUTTON;
typedef const TBBUTTON *LPCTBBUTTON;
//不是全对,hicon 应该还要靠后一个指针
struct TRAYDATA
{
HWND hWnd;
UINT uID;
UINT uCallbackMessage;
DWORD Reserved1[2];
HICON hIcon;
DWORD Reserved2[3];
TCHAR szExePath[MAX_PATH];
TCHAR szTip[128];
};
托盘的本质是一个win32_toolbar,找到窗口句柄然后就可以调用api得到按钮。对其发消息进行图标隐藏,移动之类的可以成功, 但用下面的方式给toolbox发送点击事件则一直没有成功。
PostMessage(GetParent(hwndTB), WM_COMMAND, idCommand, (LPARAM)hwndTB);
hwndTB:ToolbarWindow32的句柄
参考:
https://www.codeproject.com/Articles/10807/Shell-Tray-Info-Arrange-your-system-tray-icons
http://www.qingfengju.com/article.asp?id=294