原文链接:http://www.cnblogs.com/qnkk123/p/6840944.html
最近项目需要添加系统托盘,本来Qt的QSystemTrayIcon可以实现的,但是要求要添加hover效果,并显示未读消息(就和qq的托盘差不多,移动上去显示未读列表),加了这个要求QSystemTrayIcon就没法实现了,最后使用的是NOTIFYICONDATA实现的,记录下。
1.创建一个系统托盘:
NOTIFYICONDATA m_nid;
CMsgTrayPos m_traypos;
QLabel *m_pSysIcon;
qApp->installNativeEventFilter(this); //创建托盘图标 m_pSysIcon = new QLabel; m_nid.cbSize = sizeof m_nid; m_nid.hIcon = qt_pixmapToWinHICON(QIcon(":/style/blue/signin/logo.png").pixmap(16, 16)); m_nid.hWnd = HWND(m_pSysIcon->winId()); m_nid.uCallbackMessage = WM_TRAYNOTIFY; m_nid.uID = 1; m_nid.uFlags = NIF_ICON | NIF_MESSAGE; Shell_NotifyIcon(NIM_ADD, &m_nid); m_traypos.SetNotifyIconInfo(HWND(this->winId()), 1, WM_TRAYNOTIFY);
2,这就创建好系统托盘了,接着就是鼠标事件函数:
bool CMainWindow::nativeEventFilter(const QByteArray & eventType, void * message, long * result) { if (eventType == "windows_generic_MSG" || eventType == "windows_dispatcher_MSG") { MSG * pMsg = reinterpret_cast<MSG *>(message); if (pMsg->message == WM_TRAYNOTIFY) { switch (pMsg->lParam) { case WM_MOUSEMOVE: m_traypos.OnMouseMove(); break; case WM_MOUSEHOVER: { if (m_pSysNaviWidget->newMessageCount() > 0) // 有消息则移动上去显示未读列表 { QPoint point = cursor().pos(); m_pSysNaviWidget->show(); m_pSysNaviWidget->move(point.x() - 200, point.y() - 150); } } break; case WM_MOUSELEAVE: {
// 如果hover之后移动到未读列表,则不消失,移动到其他地方则隐藏未读列表 QPoint point = cursor().pos(); QPoint naviPoint = m_pSysNaviWidget->pos(); if (point.x() > naviPoint.x() && point.x() < naviPoint.x() + 200 && point.y() > naviPoint.y() && point.y() < naviPoint.y() + 150) { } else { m_pSysNaviWidget->hide(); } } break; case WM_LBUTTONDBLCLK: { //ShowWindow(HWND(this->winId()), SW_SHOW); 界面会假死 showWindow(); break; } case WM_LBUTTONDOWN: //m_Menu->show(); break; case WM_RBUTTONDOWN: { creatMenu(); m_pPop_menu->exec(QCursor::pos()); } break; } } } return false; }
3、现在hover就可以显示未读列表了,下一步是有消息时闪烁托盘图标,设置的是定时器,事件到就调用下面这个函数
void CMainWindow::onFlickerSysIcon(bool bFlicker) { if (bFlicker) { m_nid.hIcon = qt_pixmapToWinHICON(QIcon(":/style/blue/signin/logo.png").pixmap(16, 16)); } else { m_nid.hIcon = NULL; } Shell_NotifyIcon(NIM_MODIFY, &m_nid); // 修改图标 }
4、闪烁搞定就是右键菜单
void CMainWindow::initSysMenu() { //创建菜单、菜单项 m_pPop_menu = new QMenu(); m_pControl_action = new QAction("打开主面板", m_pPop_menu); m_pSetting_action = new QAction("设置", m_pPop_menu); m_pOpinion_action = new QAction("意见反馈", m_pPop_menu); m_pExit_action = new QAction("退出", m_pPop_menu); //连接信号与槽 connect(m_pControl_action, SIGNAL(triggered()), this, SLOT(onControlAction())); connect(m_pSetting_action, SIGNAL(triggered()), this, SLOT(onSettingAction())); connect(m_pOpinion_action, SIGNAL(triggered()), this, SLOT(onOpinionAction())); connect(m_pExit_action, SIGNAL(triggered()), this, SLOT(onBtnQuitSelected())); }
void CMainWindow::creatMenu() { m_pPop_menu->addAction(m_pControl_action); m_pPop_menu->addSeparator(); m_pPop_menu->addAction(m_pSetting_action); m_pPop_menu->addAction(m_pOpinion_action); m_pPop_menu->addAction(m_pExit_action); }
右键是上面的鼠标事件函数中的WM_RBUTTONDOWN;
5.从托盘显示界面,本来是使用 ShowWindow(HWND(this->winId()), SW_SHOW); 但是显示的界面会假死,不能操作,所以我选择了个折中的方式:
void CMainWindow::showWindow() { if (!isVisible()) { hide(); show(); } }
6.退出程序是删除托盘:
Shell_NotifyIcon(NIM_DELETE, &m_nid);
7.需要到的其他文件:
#ifndef CTRAYPOS_H #define CTRAYPOS_H #include <windows.h> class CTrayPos { private: POINT m_ptMouse; HANDLE m_hThread; HANDLE m_hExitEvent; BOOL m_bTrackMouse; CRITICAL_SECTION m_cs; public: CTrayPos(); virtual ~CTrayPos(); static UINT CALLBACK TrackMousePt(PVOID pvClass); VOID OnMouseMove(); BOOL IsMouseHover(); protected: virtual VOID OnMouseHover() = 0; virtual VOID OnMouseLeave() = 0; }; class CMsgTrayPos : public CTrayPos { private: HWND m_hNotifyWnd; UINT m_uID; UINT m_uCallbackMsg; public: CMsgTrayPos(HWND hwnd=NULL, UINT uID=0, UINT uCallbackMsg=0); ~CMsgTrayPos(); VOID SetNotifyIconInfo(HWND hwnd, UINT uID, UINT uCallbackMsg); protected: VOID OnMouseHover(); VOID OnMouseLeave(); }; #endif
#include <process.h> #include "CTraypos.h" CTrayPos::CTrayPos() { UINT uThreadId; m_bTrackMouse = FALSE; m_hExitEvent = CreateEvent(NULL, FALSE, FALSE, NULL); m_hThread = (HANDLE) _beginthreadex(NULL, 0, CTrayPos::TrackMousePt, this, 0, &uThreadId); InitializeCriticalSection(&m_cs); } CTrayPos::~CTrayPos() { if(m_hThread != NULL) { SetEvent(m_hExitEvent); if(WaitForSingleObject(m_hThread, 5000) == WAIT_TIMEOUT) { TerminateThread(m_hThread, 0); } CloseHandle(m_hThread); m_hThread = NULL; } if(m_hExitEvent != NULL) { CloseHandle(m_hExitEvent); m_hExitEvent = NULL; } DeleteCriticalSection(&m_cs); } UINT CALLBACK CTrayPos::TrackMousePt(PVOID pvClass) { POINT ptMouse; CTrayPos *pTrayPos = (CTrayPos *) pvClass; while(WaitForSingleObject(pTrayPos->m_hExitEvent, 100) == WAIT_TIMEOUT) { if(pTrayPos->m_bTrackMouse == TRUE) { GetCursorPos(&ptMouse); if(ptMouse.x != pTrayPos->m_ptMouse.x || ptMouse.y != pTrayPos->m_ptMouse.y) { pTrayPos->m_bTrackMouse = FALSE; pTrayPos->OnMouseLeave(); } } } return 0; } VOID CTrayPos::OnMouseMove() { EnterCriticalSection(&m_cs); GetCursorPos(&m_ptMouse); if(m_bTrackMouse == FALSE) { OnMouseHover(); m_bTrackMouse = TRUE; } LeaveCriticalSection(&m_cs); } BOOL CTrayPos::IsMouseHover() { return m_bTrackMouse; } ////////////////////////////////////////////////////////////////////////// CMsgTrayPos::CMsgTrayPos(HWND hwnd, UINT uID, UINT uCallbackMsg) : CTrayPos() { SetNotifyIconInfo(hwnd, uID, uCallbackMsg); } CMsgTrayPos::~CMsgTrayPos() { } VOID CMsgTrayPos::SetNotifyIconInfo(HWND hwnd, UINT uID, UINT uCallbackMsg) { m_hNotifyWnd = hwnd; m_uID = uID; m_uCallbackMsg = uCallbackMsg; } VOID CMsgTrayPos::OnMouseHover() { if(m_hNotifyWnd != NULL && IsWindow(m_hNotifyWnd)) PostMessage(m_hNotifyWnd, m_uCallbackMsg, m_uID, WM_MOUSEHOVER); } VOID CMsgTrayPos::OnMouseLeave() { if(m_hNotifyWnd != NULL && IsWindow(m_hNotifyWnd)) PostMessage(m_hNotifyWnd, m_uCallbackMsg, m_uID, WM_MOUSELEAVE); }
总结:本来打算用QSystemTrayIcon的Tooltip事件来完成hover的,但是事件调用没效果,最后的效果图: