1. 程式人生 > >Qt 系統托盤(加hover效果)

Qt 系統托盤(加hover效果)

界面 lose bytearray blog ndb pex 打開 tip tin

最近項目需要添加系統托盤,本來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的,但是事件調用沒效果,最後的效果圖:

技術分享 技術分享



Qt 系統托盤(加hover效果)