Qt 系统托盘(加hover效果)

Posted 积跬步,到千里

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Qt 系统托盘(加hover效果)相关的知识,希望对你有一定的参考价值。

原文链接: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的,但是事件调用没效果,最后的效果图:

 

          

 

 

 



以上是关于Qt 系统托盘(加hover效果)的主要内容,如果未能解决你的问题,请参考以下文章

electron系统托盘

Qt设置系统托盘图标和程序图标

Qt5 (Linux/LXDE) 中不显示系统托盘图标

QT之Qml使用QSystemTrayIcon实现系统托盘

Qt 实现系统托盘,托盘菜单,托盘消息

Qt系统托盘