zl程序教程

您现在的位置是:首页 >  工具

当前栏目

QT托盘消息模拟QQ消息通知

QQQt模拟消息 通知 托盘
2023-09-14 09:07:00 时间

使用QQ时,当有消息过来时,托盘的图标一闪一闪的,鼠标悬浮时出现消息通知列表,显示名字和数量,Qt有自带的消息托盘,那没有鼠标悬浮事件,这里通知定时器查找鼠标的坐标,在托盘范围内时显示消息列表,离开托盘范围时隐藏列表,消息列表采用Qt的Model、View加自定义委托实现,代码实现如下:

#ifndef ITEMDEFINE_H
#define ITEMDEFINE_H
 
#include <QString>
#include <QMetaType>
#include <QDateTime>
 
 
#define PrintTime (QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz"))
 
 
//托盘消息结构
typedef struct STTrayInfo
{
    QString strAvatar{""};//头像
    QString strName{""};
    int unReadCount{0};//未读数
}TrayInfo;
 
 
Q_DECLARE_METATYPE(TrayInfo)
 
#endif // ITEMDEFINE_H
#ifndef CITEMDELEGATE_H
#define CITEMDELEGATE_H
 
#include <QStyledItemDelegate>
#include <QModelIndex>
#include <QStandardItemModel>
 
class CItemDelegate : public QStyledItemDelegate
{
    Q_OBJECT
 
 
public:
    explicit CItemDelegate(QObject *parent = nullptr);
    ~CItemDelegate();
 
    //重画函数
    void paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const;
    QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
 
private:
    QFont font = QFont("Microsoft YaHei");
};
 
#endif // CITEMDELEGATE_H
#include "CItemDelegate.h"
#include <QFileInfo>
#include <QPainter>
#include <QStyledItemDelegate>
#include <QStyle>
#include <QEvent>
#include <QDebug>
#include "ItemDefine.h"
 
 
CItemDelegate::CItemDelegate(QObject *parent) :
    QStyledItemDelegate(parent)
{
    font.setPixelSize(10);
}
 
CItemDelegate::~CItemDelegate()
{
 
}
 
void CItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    if(index.isValid())
    {
        painter->save();
        QVariant variant = index.data(Qt::UserRole+1);//根据角色取值
        TrayInfo data = variant.value<TrayInfo>();
 
        QStyleOptionViewItem viewOption(option);//用来在视图中画一个item
 
        QRectF rect;
        rect.setX(option.rect.x());
        rect.setY(option.rect.y());
        rect.setWidth(option.rect.width()-1);
        rect.setHeight(option.rect.height()-1);
 
        if(option.state.testFlag(QStyle::State_Selected))//选中画背景
        {
            painter->setPen(QPen(Qt::blue));
            painter->setBrush(QColor(229, 241, 255));
            painter->drawRect(rect);
        }
        else if(option.state.testFlag(QStyle::State_MouseOver))//鼠标悬浮画背景
        {
            painter->setPen(QPen(Qt::green));
            painter->setBrush(QColor(189, 193, 217));//bdc1d9
            painter->drawRect(rect);
        }
//        else{//默认状态
//            painter->setPen(QPen(Qt::gray));
//            painter->setBrush(Qt::NoBrush);
//            painter->drawRect(path);
//        }
 
 
        //画头像
        QPixmap pix(data.strAvatar);
        if(pix.isNull())
            pix.load(":/image/system_tray.png");
        pix = pix.scaled(30, 30, Qt::KeepAspectRatio, Qt::SmoothTransformation);
        QRect imageRect = QRect(rect.left() + 5, rect.top()+5, 30, 30);
        painter->drawPixmap(imageRect, pix);
 
        //画名字
        QFontMetrics fontMetrics(font);
        //如果当前字体下,字符串长度大于100
        QString fileName = data.strName;
        if(fontMetrics.width(fileName) > 150)
        {
            fileName = QFontMetrics(font).elidedText(fileName, Qt::ElideRight, 150);
        }
        QRect NameRect = QRect(rect.left() + 40, rect.top()+10, 150, 18);
        painter->setRenderHints(QPainter::Antialiasing|QPainter::TextAntialiasing, true);//抗锯齿
        painter->setPen(QPen(Qt::blue));
        painter->setFont(font);
        painter->drawText(NameRect, Qt::AlignLeft, fileName);
 
        //画圆
        QRect circle = QRect(rect.left() + 200, rect.top()+5, 30, 20);
        painter->setBrush(Qt::red);//画填充
        painter->setPen(QPen(Qt::red));
        painter->drawRoundedRect(circle, 10, 10, Qt::RelativeSize);//圆角矩形
        //画统计数字
        QRect UnReadRect = QRect(rect.left() + 200, rect.top()+5, 30, 20);
        painter->setPen(QPen(Qt::white));
        painter->setFont(font);
        painter->drawText(UnReadRect, Qt::AlignCenter, QString("%1").arg(data.unReadCount));
        painter->restore();
    }
}
 
QSize CItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    return QSize(260, 40);
}
#ifndef CNOTICELIST_H
#define CNOTICELIST_H
 
#include <QWidget>
#include <QStandardItemModel>
#include "CItemDelegate.h"
#include "ItemDefine.h"
#include <QMutex>
#include <QMap>
 
namespace Ui {
class CNoticeList;
}
 
class CNoticeList : public QWidget
{
    Q_OBJECT
 
public:
    explicit CNoticeList(QWidget *parent = nullptr);
    ~CNoticeList();
    void initView();
    void initData();
    void initConnect();
    void initModelData();
    void setWidgetHeight();
    int rowCount();
 
 
    void deleteListItem(const TrayInfo &sessionInfo);
    void changeListItem(const TrayInfo &sessionInfo);
 
 
public slots:
    void slotItemClicked(const QModelIndex &index);
    void slotIgnoreAll();
 
 
protected:
    void mouseReleaseEvent(QMouseEvent *event);
 
private:
    Ui::CNoticeList *ui;
    QStandardItemModel *m_model{nullptr};
    CItemDelegate *m_delegate{nullptr};
    int m_nMessageTotal{0};
    QMutex m_MutexNotice;
    QMap<QString, QList<QString>> m_mapMsgid;
};
 
#endif // CNOTICELIST_H
#include "CNoticeList.h"
#include "ui_CNoticeList.h"
#include <QMouseEvent>
#include <QDebug>
#include <QStringListModel>
#include <QScrollBar>
#include <QFileInfo>
 
CNoticeList::CNoticeList(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::CNoticeList)
{
    ui->setupUi(this);
    setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinimizeButtonHint | Qt::WindowStaysOnTopHint //不显示菜单栏
                   | Qt::Tool);//不显示任务栏图标
    initView();
    initData();
    initConnect();
}
 
CNoticeList::~CNoticeList()
{
    delete ui;
}
 
void CNoticeList::initView()
{
    ui->pushButton_ignoreAll->setStyleSheet("QPushButton{"
                                             "font-family: \"Microsoft YaHei\";"
                                             "font-size: 12px;"
                                             "color: blue;}");
    ui->label_newMesage->setStyleSheet("QLabel{font-family: \"Microsoft YaHei\";font-size: 12px;}");
    ui->label_count->setStyleSheet("QLabel{font-family: \"Microsoft YaHei\";font-size: 12px;}");
    ui->listView->verticalScrollBar()->setStyleSheet("QScrollBar:vertical{width:8px;background:rgba(0,0,0,0%);"
                                                      "margin:0px,0px,0px,0px;padding-top:9px;padding-bottom:9px;}"// 留出9px给上面和下面的箭头
                                                      "QScrollBar::handle:vertical{width:8px;background:rgba(0,0,0,25%);"
                                                      "border-radius:4px;min-height:20;}"// 滚动条两端变成椭圆
                                                      "QScrollBar::handle:vertical:hover{width:8px;background:rgba(0,0,0,50%);"
                                                      "border-radius:4px;min-height:20;}");// 鼠标放到滚动条上的时候,颜色变深
    ui->listView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
    ui->listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
}
 
void CNoticeList::initData()
{
    m_delegate = new CItemDelegate();
    m_model = new QStandardItemModel();
    ui->listView->setItemDelegate(m_delegate);
    ui->listView->setModel(m_model);
    ui->listView->setSpacing(0);
    initModelData();
}
 
void CNoticeList::initConnect()
{
    connect(ui->listView, &QListView::clicked, this, &CNoticeList::slotItemClicked);
    connect(ui->pushButton_ignoreAll, &QPushButton::clicked, this, &CNoticeList::slotIgnoreAll);
}
 
void CNoticeList::initModelData()
{
    QStringListModel *stringlistModel = new QStringListModel(QColor::colorNames(), this);//获取颜色值
    QStringList colorList = stringlistModel->stringList();
 
    for (int i = 1; i < 7; ++i)
    {
        QStandardItem *Item = new QStandardItem;
        TrayInfo itemData;
        itemData.strAvatar = QString(":/image/%1.png").arg(i);
        itemData.strName = colorList[i];
        itemData.unReadCount = 1;
        m_nMessageTotal += 1;
        Item->setData(itemData.strName, Qt::UserRole);  // 单一存取  这个用来匹配过滤条件
        Item->setData(QVariant::fromValue(itemData), Qt::UserRole+1);//整体存取
        m_model->insertRow(0, Item);      //追加Item
    }
 
    setWidgetHeight();
}
 
void CNoticeList::setWidgetHeight()
{
    //刷新消息数量
    if(m_nMessageTotal > 0)
        ui->label_count->setText(QString("%1").arg(m_nMessageTotal));
    else
        ui->label_count->setText("");
 
    if(m_model->rowCount() < 5)
    {
        ui->listView->setMinimumHeight(m_model->rowCount() * 40 + 4);
        ui->listView->setMaximumHeight(m_model->rowCount() * 40 + 4);
        this->setMinimumHeight(ui->listView->height() + 60);
        this->setMaximumHeight(ui->listView->height() + 60);
    }
    else
    {
        ui->listView->setMinimumHeight(204);
        ui->listView->setMaximumHeight(204);
        this->setMinimumHeight(264);
        this->setMaximumHeight(264);
    }
}
 
int CNoticeList::rowCount()
{
    if(nullptr == m_model)
        return 0;
    else
        m_model->rowCount();
}
 
void CNoticeList::mouseReleaseEvent(QMouseEvent *event)
{
    if(event->button() == Qt::LeftButton)
        this->hide();
}
 
void CNoticeList::deleteListItem(const TrayInfo &info)
{
    for(int i = 0; i < m_model->rowCount(); ++i)
    {
        QStandardItem *Item = m_model->item(i);
        QString strName = Item->data(Qt::UserRole).toString();
        if(0 == strName.compare(info.strName))
        {
            QVariant variant = Item->data(Qt::UserRole+1);//根据角色取值
            TrayInfo data = variant.value<TrayInfo>();
            m_nMessageTotal -= data.unReadCount;//总消息数减去这个会话消息数
            if(m_nMessageTotal < 0)
                m_nMessageTotal = 0;
            m_model->removeRow(i);
            break;
        }
    }
 
    setWidgetHeight();
}
 
void CNoticeList::slotItemClicked(const QModelIndex &index)
{
    QMutexLocker locker(&m_MutexNotice);
 
    QVariant variant = index.data(Qt::UserRole+1);//根据角色取值
    TrayInfo data = variant.value<TrayInfo>();
    //删除这个item
    deleteListItem(data);
 
    //隐藏自己
    this->hide();
 
}
 
void CNoticeList::slotIgnoreAll()
{
    QMutexLocker locker(&m_MutexNotice);
 
    if(m_model->rowCount() > 0)
    {
        m_model->clear();
        m_nMessageTotal = 0;
        this->hide();
    }
 
}
 
#ifndef CUSTOMSYSTEMTRAY_H
#define CUSTOMSYSTEMTRAY_H
 
 
#include <QSystemTrayIcon>
#include <QListView>
#include <QStandardItemModel>
#include <QStandardItem>
#include <QStringListModel>
#include "CItemDelegate.h"
#include "ItemDefine.h"
#include "CNoticeList.h"
 
class QAction;
class QMemu;
class QSystemTrayIcon;
class QWidget;
class QWidgetAction;
class QPushButton;
class QToolButton;
class QLabel;
class QHBoxLayout;
class QPainter;
class QTimer;
 
class CustomSystemTray : public QSystemTrayIcon
{
    Q_OBJECT
public:
    explicit CustomSystemTray(QObject *parent = nullptr);
    void initData();
 
    void checkMousePosition();//Qt的托盘没有悬停事件,通过鼠标光标位置来判断悬浮事件
    void initConnect();
 
 
public slots:
    void slotMessageNotice();
    void slotFlashTimerOut();
 
private:
    QTimer *m_pMessageNotice{nullptr};//模拟消息通知
    QTimer *m_pTimerFlash{nullptr};//图标闪烁
    int m_flashCount{0};
    CNoticeList *m_noticList{nullptr};
};
#endif // CUSTOMSYSTEMTRAY_H
#include "CustomSystemTray.h"
 
#include <QPushButton>
#include <QToolButton>
#include <QWidget>
#include <QSystemTrayIcon>
#include <QMenu>
#include <QAction>
#include <QApplication>
#include <QWidgetAction>
#include <QLabel>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QPainter>
#include <QDebug>
#include <QTimer>
#include <QApplication>
 
 
 
CustomSystemTray::CustomSystemTray(QObject *parent) :
    QSystemTrayIcon(parent)
{
    initData();
}
 
void CustomSystemTray::initData()
{
    m_noticList = new CNoticeList();
    m_pTimerFlash = new QTimer;
    connect(m_pTimerFlash, &QTimer::timeout, this, &CustomSystemTray::slotFlashTimerOut);
    m_pTimerFlash->start(500);
}
 
void CustomSystemTray::checkMousePosition()
{
    //托盘位置24X40
    QRect currentRect = geometry();
 
    int xMin = currentRect.x();
    int xMax = currentRect.x() + currentRect.width();
 
    int yMin = currentRect.y();
    int yMax = currentRect.y() + currentRect.height();
 
    int x = QCursor::pos().x();
    int y = QCursor::pos().y();
 
    if(xMin<=x && x<=xMax && yMin<=y && y<=yMax)
    {
        QRect menuRect;
        menuRect.setLeft(xMin);
        menuRect.setTop(yMin - m_noticList->height());
        menuRect.setWidth(m_noticList->width());
        menuRect.setHeight(m_noticList->height());
        m_noticList->setGeometry(menuRect);
 
        if(m_noticList->rowCount() > 0)
            m_noticList->show();
        else
            m_noticList->hide();
    }
    else if((nullptr != m_noticList) && (!m_noticList->isHidden()))
    {
        //如果是显示的消息框,鼠标在显示框范围内不隐藏
        int pxMin = xMin;
        int pxMax = xMin + m_noticList->width();
        int pyMin = yMin - m_noticList->height();
        int pyMax = yMin;
 
        if(pxMin<=x && x<=pxMax && pyMin<=y && y<=pyMax)
            m_noticList->show();
        else
            m_noticList->hide();
    }
}
 
 
void CustomSystemTray::initConnect()
{
    // 此处使用定时器模拟消息的发送
    m_pMessageNotice = new QTimer;
    connect(m_pMessageNotice, &QTimer::timeout, this, &CustomSystemTray::slotMessageNotice);
    m_pMessageNotice->start(3000);
}
 
void CustomSystemTray::slotMessageNotice()
{
    m_flashCount = 0;
    if (m_pTimerFlash == nullptr)
    {
        m_pTimerFlash = new QTimer;
        connect(m_pTimerFlash, &QTimer::timeout, this, &CustomSystemTray::slotFlashTimerOut);
        m_pTimerFlash->start(500);
    }
}
 
void CustomSystemTray::slotFlashTimerOut()
{
    if(m_noticList->rowCount() < 1)
    {
        this->setIcon(QIcon(":/image/qq2.jpg"));
        return;
    }
    //qDebug() << "slotFlashTimerOut============m_flashCount=====" << m_flashCount;
    // 消息通知,使用定时器刷新图标
    m_flashCount++;
    checkMousePosition();
    if (m_flashCount % 2 == 1)
    {
        this->setIcon(QIcon(":/image/qq2.jpg"));
    }
    else
    {
        this->setIcon(QIcon(":/image/qq1.jpg"));
    }
}

运行效果
在这里插入图片描述