使用Qt开发桌面软件的过程中,有时需要用到提示语,过几秒后自动消失,这种Toast的样式,比如“网络断开连接”,“已超时”,“对方拒绝”等等,而且可能多次出现。那么用Qt怎么来实现呢,一般可能想到的是用QTimer定时器处理,但这种要写信号槽,本篇介绍用timerEvent定时器事件处理。
创建一个对话框继承QDialog,添加一个水平布局,布局上放一个QLabel,QLabel背景透明,文字居中,设置对话框菜单栏隐藏
ToastDailog()
{
auto layout = new QHBoxLayout;//水平布局
m_label = new QLabel;
m_label->setStyleSheet("color:white; background:transparent");
layout->addWidget(m_label);
m_label->setAlignment(Qt::AlignCenter); //文字居中
setLayout(layout);
setWindowFlag(Qt::FramelessWindowHint); //隐藏菜单框
setAttribute(Qt::WA_ShowWithoutActivating, true);
}
显示的设置对话框的背景色,设置显示在窗口的最顶端。
void show(CustomToast::Level level, const QString &text)
{
QPalette p = palette();
p.setColor(QPalette::Window, QColor(0, 0, 0, 200));
setPalette(p);
m_label->setText(text);
setWindowFlag(Qt::WindowStaysOnTopHint);//窗口置顶
QDialog::show();
}
显示Toast启动定时器
m_toastDailog->show(level, text);
m_timerId = startTimer(interval);
定时器到时,关闭窗口
killTimer(m_timerId);
m_toastDailog->accept(); //隐藏模态对话框
完整的示例代码如下:
CustomToast.h
#ifndef CUSTOMTOAST_H
#define CUSTOMTOAST_H
#include <QObject>
#include <QRect>
class ToastDailog;
class CustomToast : public QObject
{
Q_OBJECT
public:
enum Level {INFO, WARN, ERROR};
void show(Level level, const QString& text, int interval = 2000);
static CustomToast &instance();
private:
explicit CustomToast(QObject *parent = nullptr);
void timerEvent(QTimerEvent *event) override;
private:
ToastDailog *m_toastDailog;
int m_timerId{0};
QRect m_gemoetry;
};
#endif // CUSTOMTOAST_H
CustomToast.cpp
#include "CustomToast.h"
#include <QDialog>
#include <QHBoxLayout>
#include <QLabel>
#include <QEvent>
#include <QDebug>
class ToastDailog: public QDialog
{
public:
ToastDailog()
{
auto layout = new QHBoxLayout;//水平布局
m_label = new QLabel;
m_label->setStyleSheet("color:white; background:transparent");
layout->addWidget(m_label);
m_label->setAlignment(Qt::AlignCenter); //文字居中
setLayout(layout);
setWindowFlag(Qt::FramelessWindowHint); //隐藏菜单框
setAttribute(Qt::WA_ShowWithoutActivating, true);
}
void show(CustomToast::Level level, const QString &text)
{
QPalette p = palette();
p.setColor(QPalette::Window, QColor(0, 0, 0, 200));
switch (level) {
case CustomToast::INFO:
p.setColor(QPalette::Window, QColor(0, 0, 0, 200));//黑底
break;
case CustomToast::WARN:
p.setColor(QPalette::Window, QColor(0, 0, 255, 200));//蓝底
break;
default:
p.setColor(QPalette::Window, QColor(255, 0, 0, 200));//红底
break;
}
setPalette(p);
m_label->setText(text);
setWindowFlag(Qt::WindowStaysOnTopHint);//窗口置顶
QDialog::show();
}
private:
QLabel *m_label;
};
void CustomToast::show(CustomToast::Level level, const QString &text, int interval)
{
m_toastDailog->show(level, text);
if(m_timerId != 0)
{
//如果已开启一个定时器,先把他关掉
killTimer(m_timerId);
}
m_timerId = startTimer(interval); //启动定时器,interval毫秒触发定时器事件,直到调用killTimer
}
CustomToast &CustomToast::instance()
{
static CustomToast thisToast;//这种实例化方法会自动回收内存
return thisToast;
}
CustomToast::CustomToast(QObject *parent) : QObject(parent)
{
m_toastDailog = new ToastDailog;
}
void CustomToast::timerEvent(QTimerEvent *event)
{
killTimer(m_timerId);
m_timerId = 0;
m_toastDailog->accept(); //隐藏模态对话框
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
public slots:
void showInfoToast();
void showWarnToast();
void showErrorToast();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "CustomToast.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
connect(ui->pushButton_info, SIGNAL(clicked()), this, SLOT(showInfoToast()));
connect(ui->pushButton_warn, SIGNAL(clicked()), this, SLOT(showWarnToast()));
connect(ui->pushButton_error, SIGNAL(clicked()), this, SLOT(showErrorToast()));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::showInfoToast()
{
CustomToast::instance().show(CustomToast::INFO, QStringLiteral("文件不存在"));
}
void MainWindow::showWarnToast()
{
CustomToast::instance().show(CustomToast::WARN, QStringLiteral("登录失败"));
}
void MainWindow::showErrorToast()
{
CustomToast::instance().show(CustomToast::ERROR, QStringLiteral("网络断开连接"));
}
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
运行效果:
上面这个有一个缺陷,就是如果移动主窗体,弹出的toast不会在主窗体居中了,
方案二:
#ifndef MYTOAST_H
#define MYTOAST_H
#include <QLabel>
#include <QTimer>
class MyToast : public QLabel
{
Q_OBJECT
public:
explicit MyToast(QWidget *parent = nullptr);
~MyToast();
void startToast(const QString &text, int interval = 0);
private slots:
void slotCloseToast();
private:
void initView();
private:
QTimer *m_timer;
};
#endif // MYTOAST_H
#include "MyToast.h"
#include <QDebug>
MyToast::MyToast(QWidget *parent) :
QLabel(parent),
m_timer(new QTimer)
{
setAttribute(Qt::WA_DeleteOnClose); //关闭后自动析构
initView();
}
MyToast::~MyToast()
{
qDebug() << "MyToast::~MyToast======= line=== " << __LINE__;
}
void MyToast::startToast(const QString &text, int interval)
{
if(m_timer->isActive())
m_timer->stop();
//计算文本内容的宽度和高度
QFontMetrics metrics(QFont("微软雅黑", 14));
int nWidth = metrics.width(text);
int nHeight = metrics.height();
//设置窗口大小,给周边一些空间
this->resize(nWidth + 16, nHeight + 10);
this->setText(text);
//和上面设置的字体一样
this->setFont(QFont("微软雅黑", 14));
int px = (this->parentWidget()->width() - this->width()) / 2;
int py = (this->parentWidget()->height() - this->height()) / 2;
this->move(px, py);
this->show();
m_timer->start(interval);
}
//定时器到时关闭弹窗
void MyToast::slotCloseToast()
{
m_timer->stop();
this->close();
}
void MyToast::initView()
{
//设置样式
this->setStyleSheet("background-color:#ff0000; color:#00ff00;border-radius:4px");
//文本居中
this->setAlignment(Qt::AlignCenter);
connect(m_timer, &QTimer::timeout, this, &MyToast::slotCloseToast);
}
使用
void MainWindow::showErrorToast()
{
//CustomToast::instance().show(CustomToast::ERROR, QStringLiteral("网络断开连接"));
MyToast *toast = new MyToast(this);
toast->startToast(QStringLiteral("网络断开连接"), 2000);
}
运行效果:
这个托动主窗口位置,弹出的都会在主窗体的中间。
这种方案没有动画,下面介绍一种带动画的QPropertyAnimation使用。
方案三:
#ifndef TOASTANIMATION_H
#define TOASTANIMATION_H
#include <QWidget>
namespace Ui {
class ToastAnimation;
}
class ToastAnimation : public QWidget
{
Q_OBJECT
public:
explicit ToastAnimation(QWidget *parent = nullptr);
~ToastAnimation();
void initView();
void setText(const QString &text);
void showAnimation(int timeout = 2000); //动画方式show出,默认2秒后消失
//静态调用
static void showTip(const QString &text, QWidget *parent = nullptr);
protected:
virtual void paintEvent(QPaintEvent *event) override;
private:
Ui::ToastAnimation *ui;
};
#endif // TOASTANIMATION_H
#include "ToastAnimation.h"
#include "ui_ToastAnimation.h"
#include <QPropertyAnimation>
#include <QScreen>
#include <QGuiApplication>
#include <QPainter>
#include <QTimer>
#include <QDebug>
ToastAnimation::ToastAnimation(QWidget *parent) :
QWidget(parent),
ui(new Ui::ToastAnimation)
{
ui->setupUi(this);
initView();
}
ToastAnimation::~ToastAnimation()
{
qDebug() << "~ToastAnimation============line==" << __LINE__;
delete ui;
}
void ToastAnimation::initView()
{
setWindowFlags(windowFlags()|Qt::FramelessWindowHint|Qt::Tool); //无边框 无任务栏
setAttribute(Qt::WA_TranslucentBackground, true);//背景透明
ui->label->setStyleSheet("background-color:#000000; color:#ffffff;border-radius:4px");
ui->label->setFont(QFont("Microsoft YaHei", 14));
ui->label->setAlignment(Qt::AlignCenter);
}
void ToastAnimation::setText(const QString &text)
{
ui->label->setText(text);
ui->label->resize(ui->label->width() + 16, ui->label->height() + 20);
}
void ToastAnimation::showAnimation(int timeout)
{
//开始动画
QPropertyAnimation *animation = new QPropertyAnimation(this, "windowOpacity");
animation->setDuration(1000);
animation->setStartValue(0);
animation->setEndValue(1);
animation->start();
show();
QTimer::singleShot(timeout, [&]
{
//结束动画
QPropertyAnimation *animation = new QPropertyAnimation(this, "windowOpacity");
animation->setDuration(1000);
animation->setStartValue(1);
animation->setEndValue(0);
animation->start();
connect(animation, &QPropertyAnimation::finished, [&]{
close();
deleteLater();//关闭后析构
});
});
}
void ToastAnimation::showTip(const QString &text, QWidget *parent)
{
qDebug() << "showTip======text===" << text << " line====" << __LINE__;
ToastAnimation *toast = new ToastAnimation(parent);
//toast->setWindowFlags(toast->windowFlags()|Qt::WindowStaysOnTopHint);//置顶
toast->setText(text);
toast->adjustSize();//设置完文本后调整大小
int px = 0;
int py = 0;
if(parent == nullptr) {
//主屏的60%高度位置
QScreen *pScreen = QGuiApplication::primaryScreen();
px = (pScreen->size().width() - toast->width()) / 2;
py = pScreen->size().height() * 6/10;
}
else {
//弹框居中
px = (parent->width() - toast->width()) / 2 + parent->pos().x();
py = (parent->height() - toast->height()) / 2 + parent->pos().y();
}
toast->move(px, py);
toast->showAnimation();
}
//绘制背景为黑色
void ToastAnimation::paintEvent(QPaintEvent *event)
{
QPainter paint(this);
paint.setPen(Qt::NoPen);
paint.setBrush(Qt::black);
paint.setRenderHint(QPainter::Antialiasing);
QPainterPath path;
path.addRoundedRect(rect(), 20, 20);
paint.setClipPath(path); //圆角边框去掉毛刺
QBrush brush = QBrush(QColor("#000000")); //黑色背景
paint.setBrush(brush);
paint.drawRect(rect());
}
测试代码:
void MainWindow::showWarnToast()
{
//CustomToast::instance().show(CustomToast::WARN, QStringLiteral("登录失败"));
ToastAnimation::showTip(QStringLiteral("登录失败"), this);
}
运行结果:
参考: