前提
最近因为工作比较忙,好几天没有更新了,今天得空,跟大家分享一个基于Qt实现的、开源的
、可用性强的自定义消息通知窗口。可用于在程序中显示临时消息、定时通知、提示或警告等,首先先来看下实现效果:
源码获取及编译
https://gitee.com/cassfrontend/qt-notify
下载源码以后,用Qt打开项目,然后重新构建即可,很方便,不需要额外的操作,源码文件如下:
实现方法
这个自定义窗口实现起来很简单,主要基于两个类,一个是Notify类,该类用来实现这个自定义提示窗口的布局、外观样式以及窗口的显示和关闭。另一个类是NotifyManager类,该类用来管理Notify窗口,控制窗口的显示位置、最大显示个数以及窗口显示的持续时间,并维护一个窗口队列,用来缓存多余的消息直到其可以显示为止。
Notify类
#ifndef NOTIFY_H#define NOTIFY_H#include #include #include #include class Notify : public QWidget{ Q_OBJECTpublic: explicit Notify(int displayTime, QWidget *parent = 0); void setIcon(const QString &value); void setTitle(const QString &value); void setBody(const QString &value); void setUrl(const QString &value); void showGriant();Q_SIGNALS: void disappeared();private: int displayTime; QString icon; QString title; QString body; QString url; QLabel * backgroundLabel; QLabel * iconLabel; QLabel * titleLabel; QLabel * bodyLabel; QPushButton * closeBtn; void hideGriant(); void mousePressEvent(QMouseEvent *event);};#endif // NOTIFY_H
#include #include #include #include #include #include #include #include #include #include "notify.h"Notify::Notify (int displayTime, QWidget *parent) : QWidget(parent), displayTime(displayTime){ this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint | Qt::Tool | Qt::WindowStaysOnTopHint); //表明当前widget没有背景,即,当前widget接收到paint事件时背景不会自动重绘 this->setAttribute(Qt::WA_NoSystemBackground, true); //设置透明背景 this->setAttribute(Qt::WA_TranslucentBackground, true); backgroundLabel = new QLabel(this); backgroundLabel->move(0, 0); backgroundLabel->setObjectName("notify-background"); QHBoxLayout *mainLayout = new QHBoxLayout(backgroundLabel); QVBoxLayout *contentLayout = new QVBoxLayout(); iconLabel = new QLabel(backgroundLabel); iconLabel->setFixedWidth(40); iconLabel->setAlignment(Qt::AlignCenter); titleLabel = new QLabel(backgroundLabel); titleLabel->setObjectName("notify-title"); bodyLabel = new QLabel(backgroundLabel); bodyLabel->setObjectName("notify-body"); QFont font = bodyLabel->font(); font.setPixelSize(12); bodyLabel->setFont(font); contentLayout->addWidget(titleLabel); contentLayout->addWidget(bodyLabel); mainLayout->addWidget(iconLabel); mainLayout->addSpacing(5); mainLayout->addLayout(contentLayout); closeBtn = new QPushButton("×", backgroundLabel); closeBtn->setObjectName("notify-close-btn"); closeBtn->setFixedSize(24, 24); connect(closeBtn, &QPushButton::clicked, this, [this]{ Q_EMIT disappeared(); }); this->setStyleSheet("#notify-background {" "border: 1px solid #ccc;" "background:#515151;" "border-radius: 4px;" "} " "#notify-title{" "font-weight: bold;" "color: white;" "font-size: 14px;" "}" "#notify-body{" "color: white;" "}" "#notify-close-btn{ " "border: 0;" "color: white;" "}" "#notify-close-btn:hover{ " "background: #ccc;" "}");}void Notify::showGriant(){ this->show(); titleLabel->setText(title); QPixmap tempPix = QPixmap(this->icon); tempPix = tempPix.scaled(QSize(30, 30), Qt::KeepAspectRatio); iconLabel->setPixmap(tempPix); backgroundLabel->setFixedSize(this->size()); closeBtn->move(backgroundLabel->width() - closeBtn->width(), 0); // 超过长度省略号 QFontMetrics elidfont(bodyLabel->font()); QString text = elidfont.elidedText(this->body, Qt::ElideRight, bodyLabel->width() - 5); bodyLabel->setText(text); QPropertyAnimation *animation = new QPropertyAnimation(this, "windowOpacity", this); animation->setStartValue(0); animation->setEndValue(1); animation->setDuration(200); animation->start(); connect(animation, &QPropertyAnimation::finished, this, [animation, this](){ animation->deleteLater(); QTimer::singleShot(displayTime, this, [this](){ this->hideGriant(); }); });}void Notify::hideGriant(){ QPropertyAnimation *animation = new QPropertyAnimation(this, "windowOpacity", this); animation->setStartValue(this->windowOpacity()); animation->setEndValue(0); animation->setDuration(200); animation->start(); connect(animation, &QPropertyAnimation::finished, this, [animation, this](){ this->hide(); animation->deleteLater(); Q_EMIT disappeared(); });}void Notify::mousePressEvent(QMouseEvent *event){ if(event->button() == Qt::LeftButton) { QMessageBox::information(this, "新消息", body, QMessageBox::Ok); //if(!url.isEmpty()) // QDesktopServices::openUrl(url); hideGriant(); }}void Notify::setUrl(const QString &value){ url = value;}void Notify::setBody(const QString &value){ body = value;}void Notify::setTitle(const QString &value){ title = value;}void Notify::setIcon(const QString &value){ icon = value;}
NotifyManager类
#ifndef NOTIFYMANAGER_H#define NOTIFYMANAGER_H#include #include #include "notify.h"class NotifyManager : public QObject{ Q_OBJECTpublic: explicit NotifyManager( QObject *parent = 0); void notify(const QString &title, const QString &body, const QString &icon, const QString url); void setMaxCount(int count); void setDisplayTime(int ms);private: class NotifyData { public: NotifyData(const QString &icon, const QString &title, const QString &body, const QString url): icon(icon), title(title), body(body), url(url) { } QString icon; QString title; QString body; QString url; }; void rearrange(); void showNext(); QQueue dataQueue; QList notifyList; int maxCount; int displayTime;};#endif // NOTIFYMANAGER_H
#include #include #include #include #include "notifymanager.h"const int RIGHT = 10;const int BOTTOM = 10;const int HEIGHT = 60;const int WIDTH = 300;const int SPACE = 20;NotifyManager::NotifyManager(QObject *parent): QObject(parent), maxCount(6), displayTime(5 * 1000){}void NotifyManager::notify(const QString &title, const QString &body, const QString &icon, const QString url){ dataQueue.enqueue(NotifyData(icon, title, body, url)); showNext();}void NotifyManager::setMaxCount(int count){ maxCount = count;}void NotifyManager::setDisplayTime(int ms){ displayTime = ms;}// 调整所有提醒框的位置void NotifyManager::rearrange(){ QDesktopWidget *desktop = QApplication::desktop(); QRect desktopRect = desktop->availableGeometry(); QPoint bottomRignt = desktopRect.bottomRight(); QList::iterator i; for (i = notifyList.begin(); i != notifyList.end(); ++i) { int index = notifyList.indexOf((*i)); QPoint pos = bottomRignt - QPoint(WIDTH + RIGHT, (HEIGHT + SPACE) * (index + 1) - SPACE + BOTTOM); QPropertyAnimation *animation = new QPropertyAnimation((*i), "pos", this); animation->setStartValue((*i)->pos()); animation->setEndValue(pos); animation->setDuration(300); animation->start(); connect(animation, &QPropertyAnimation::finished, this, [animation, this](){ animation->deleteLater(); }); }}void NotifyManager::showNext(){ if(notifyList.size() >= maxCount || dataQueue.isEmpty()) return; NotifyData data = dataQueue.dequeue(); Notify *notify = new Notify(this->displayTime); notify->setIcon(data.icon); notify->setTitle(data.title); notify->setBody(data.body); notify->setUrl(data.url); notify->setFixedSize(WIDTH, HEIGHT); QDesktopWidget *desktop = QApplication::desktop(); QRect desktopRect = desktop->availableGeometry(); // 计算提醒框的位置 QPoint bottomRignt = desktopRect.bottomRight(); QPoint pos = bottomRignt - QPoint(notify->width() + RIGHT, (HEIGHT + SPACE) * (notifyList.size() + 1) - SPACE + BOTTOM); notify->move(pos); notify->showGriant(); notifyList.append(notify); connect(notify, &Notify::disappeared, this, [notify, this](){ this->notifyList.removeAll(notify); this->rearrange(); // 如果列表是满的,重排完成后显示 if(this->notifyList.size() == this->maxCount - 1){ QTimer::singleShot(300, this, [this]{ this->showNext(); }); } else { this->showNext(); } notify->deleteLater(); });}
使用方法
大家如果要在自己的程序中使用这个自定义消息通知窗口,只需要把上面介绍的两个类对应的头文件及源文件总计4个文件添加到自己的项目中,然后再需要显示通知的地方,调用窗口即可,调用方法如下:
NotifyManager *manager = new NotifyManager(this);manager->notify("新消息", "Mikasoi,你有一条新的微信消息!", "额外参数,暂未使用");
注意提示窗口也是可以点击的,原项目中点击窗口会打开一个网站,我将其修改为点击以后弹出一个QMessageBox窗口,大家也可以根据需要自行修改。
窗口外观样式是通过样式表来修改的,如下所示:
this->setStyleSheet("#notify-background {" "border: 1px solid #ccc;" "background:#515151;" "border-radius: 4px;" "} " "#notify-title{" "font-weight: bold;" "color: white;" "font-size: 14px;" "}" "#notify-body{" "color: white;" "}" "#notify-close-btn{ " "border: 0;" "color: white;" "}" "#notify-close-btn:hover{ " "background: #ccc;" "}");
窗口的背景、图标、消息标题、消息内容、字体、关闭按钮等都可以修改,也可以预先编写好几个qss文件,在程序中通过条件应用不同的样式,来达到区分不同消息类型或优先级。
总结
总体来说,这个项目代码量少、实现方法简单、使用方便、可自定义程度高、无第三方插件,很适合刚学Qt的朋友来研究和学习Qt中自定义窗口的方法,也可以举一反三,仿照其思路实现自己的自定义窗口。