QT实现右下角弹框提示
程序动画部分来自龚建波大佬的博客:https://blog.csdn.net/gongjianbo1992/article/details/106885483
首先上实机效果图,相较于龚建波大佬的代码,打破了原有的界面布局,新的界面布局用于缩放比适配和阴影效果,相当于做了些界面美化的工作
实现思路:
动画部分使用QPropertyAnimation 属性动画配合动画组。然后根据设置来决定启用哪些动画、是否定时关闭
由于只有一个实例存在,所以可以看到如演示所示,第二次调用弹框显示的时候,会进行判断,判断实例上的动画组是否完结,如果没完结的话,会立即完结动画开始下一组动画
阴影效果部分使用QGraphicsDropShadowEffect,使用此效果需要界面UI留有显示阴影的空间
代码部分:
获取系统缩放比的代码
//************************************ // Method: getDpi // Description:获取系统dpi(缩放比例) // Returns: 缩放比例 //************************************ double getDpi() { double dDpi = 1; // Get desktop dc HDC desktopDc = GetDC(NULL); // Get native resolution float horizontalDPI = GetDeviceCaps(desktopDc, LOGPIXELSX); float verticalDPI = GetDeviceCaps(desktopDc, LOGPIXELSY); int dpi = (horizontalDPI + verticalDPI) / 2; dDpi = 1 + ((dpi - 96) / 24)*0.25; //为了保证页面显示正常,暂时不支持小于1和大于2的缩放系数 if (dDpi < 1) { dDpi = 1; } return dDpi; }
TrayMessageDlg.h
#pragma once #include <QWidget> #include <QPropertyAnimation> #include <QParallelAnimationGroup> #include <QTimer> #include "ui_TrayMessageDlg.h" class TrayMessageDlg : public QWidget { Q_OBJECT public: //动画模式枚举 enum AnimationMode { //无动画 NoAnimation = 0x00, //仅透明度动画 OpacityAnimation = 0x01, //仅位置动画 PosAnimation = 0x02, //全部动画 //OpacityAnimation|PosAnimation AllAnimation = 0xFF }; public: explicit TrayMessageDlg(); ~TrayMessageDlg(); //显示弹框-已显示动画重新开始,timeout<=0不会定时消失 static void showTip(const QString &title, const QString &texts, int timeout); //显示弹框-已显示不重复动画 static void keepTip(const QString &texts); //隐藏弹框 static void hideTip(); //设置动画模式 static TrayMessageDlg::AnimationMode getMode(); static void setMode(TrayMessageDlg::AnimationMode newMode); protected: void paintEvent(QPaintEvent *event); private: //初始化动画设置 void initAnimation(); //初始化定时器设置 void initTimer(); //准备定时器 void readyTimer(int timeout); //启动显示动画-已显示动画重新开始 void showAnimation(); //启动显示动画-已显示不重复动画 void keepAnimation(); //启动隐藏动画 void hideAnimation(); private: Ui::TrayMessageDlg *ui; //唯一实例 static TrayMessageDlg *instance; //动画设置 static AnimationMode mode; //动画组 QParallelAnimationGroup *showGroup; //保存动画结束状态 bool showAnimEnd = false; //透明度属性动画 QPropertyAnimation *showOpacity = nullptr; //位置属性动画 QPropertyAnimation *showPos = nullptr; //定时关闭 QTimer *hideTimer = nullptr; //定时计数 int hideCount = 0; //缩放比例,适配高分屏 double m_dpi; QGraphicsDropShadowEffect *m_pEffect; };
TrayMessageDlg.cpp
无法编译的部分为日志输出
#include <stdafx.h> #include "TrayMessageDlg.h" #include "ui_TrayMessageDlg.h" #include <QApplication> #include <QScreen> #include <QDebug> #include "../Common/Utils.h" #include "Log.h" TrayMessageDlg* TrayMessageDlg::instance = nullptr; TrayMessageDlg::AnimationMode TrayMessageDlg::mode = TrayMessageDlg::AllAnimation; #define SHADOW_WIDTH 10 //边框阴影宽度 TrayMessageDlg::TrayMessageDlg() : QWidget(nullptr),ui(new Ui::TrayMessageDlg),showGroup(new QParallelAnimationGroup(this)) { try { ui->setupUi(this); setWindowFlags(Qt::FramelessWindowHint | Qt::ToolTip); setAttribute(Qt::WA_TranslucentBackground,true);//背景透明 setAttribute(Qt::WA_DeleteOnClose); //setWindowModality(Qt::WindowModal); ui->btnClose->setIcon(QIcon(":/YozoUCloud/Resources/Main/window_close_n.png")); //缩放比适配 m_dpi = getDpi(); setFixedSize(300 * m_dpi, 160 * m_dpi); QFont font; font.setPixelSize(14*m_dpi); //字体基础是14 font.setFamily(QString::fromLocal8Bit("微软雅黑")); ui->titleLabel->setFont(font); ui->contentLabel->setFont(font); ui->countLabel->setFont(font); //添加阴影效果 m_pEffect = new QGraphicsDropShadowEffect(this);//该类提供了图形元素的阴影效果,用于增加立体感。 m_pEffect->setOffset(0, 0);//用于设定在哪个方向产生阴影效果,如果dx为负数,则阴影在图形元素的左边 m_pEffect->setColor(Qt::gray);//用于设定阴影的颜色 m_pEffect->setBlurRadius(20);//用于设定阴影的模糊度 ui->frame->setGraphicsEffect(m_pEffect); ui->verticalLayout->setContentsMargins(10,10,10,0);//设置frame和主窗口的距离,也就是阴影的距离 //关闭按钮事件绑定 connect(ui->btnClose, &QPushButton::clicked, this, &TrayMessageDlg::hideTip); //程序退出时释放 connect(qApp, &QApplication::aboutToQuit, this, &TrayMessageDlg::close); //动画初始化设置 initAnimation(); //定时器设置 initTimer(); } catch (...) { Log::WriteOutput(LogType::Error, L"TrayMessageDlg::TrayMessageDlg() Failed!"); } } TrayMessageDlg::~TrayMessageDlg() { try { if (ui) { delete ui; ui = NULL; } if (showGroup) { delete showGroup; showGroup = NULL; } if (hideTimer) { delete hideTimer; hideTimer = NULL; } if (m_pEffect) { delete m_pEffect; m_pEffect = NULL; } } catch (...) { Log::WriteOutput(LogType::Error, L"TrayMessageDlg::~TrayMessageDlg() Failed!"); } } void TrayMessageDlg::showTip(const QString &title, const QString &texts, int timeout) { try { if (!instance){ //仅在ui线程 instance = new TrayMessageDlg(); } instance->readyTimer(timeout); //模态框 instance->setWindowModality(Qt::WindowModal); instance->ui->contentLabel->setText(texts); instance->ui->titleLabel->setText(title); instance->showAnimation(); } catch (...) { Log::WriteOutput(LogType::Error, L"TrayMessageDlg::showTip() Failed!"); return; } } void TrayMessageDlg::keepTip(const QString &texts) { try { if (!instance){ //仅在ui线程 instance = new TrayMessageDlg; } instance->readyTimer(0); //模态框 instance->setWindowModality(Qt::WindowModal); instance->ui->contentLabel->setText(texts); instance->keepAnimation(); } catch (...) { Log::WriteOutput(LogType::Error, L"TrayMessageDlg::keepTip() Failed!"); return; } } //关闭按钮点击事件 void TrayMessageDlg::hideTip() { try { if (!instance){ return; } instance->ui->countLabel->hide(); instance->hideAnimation(); } catch (...) { Log::WriteOutput(LogType::Error, L"TrayMessageDlg::hideTip() Failed!"); return; } } TrayMessageDlg::AnimationMode TrayMessageDlg::getMode() { return mode; } void TrayMessageDlg::setMode(TrayMessageDlg::AnimationMode newMode) { if (mode != newMode){ mode = newMode; } } void TrayMessageDlg::initAnimation() { try { //透明度动画 showOpacity = new QPropertyAnimation(this, "windowOpacity"); //判断是否设置了此模式的动画 if (mode&AnimationMode::OpacityAnimation){ showOpacity->setDuration(1500); showOpacity->setStartValue(0); } else{ showOpacity->setDuration(0); showOpacity->setStartValue(1); } showOpacity->setEndValue(1); showGroup->addAnimation(showOpacity); //位置动画 showPos = new QPropertyAnimation(this, "pos"); QScreen * screen = QGuiApplication::primaryScreen(); if (screen) { const QRect desk_rect = screen->availableGeometry(); const QPoint hide_pos{ desk_rect.width() - this->width(), desk_rect.height() }; const QPoint show_pos{ desk_rect.width() - this->width(), desk_rect.height() - this->height() }; //判断是否设置了此模式的动画 if (mode&AnimationMode::PosAnimation){ showPos->setDuration(1500); showPos->setStartValue(hide_pos); } else{ showPos->setDuration(0); showPos->setStartValue(show_pos); } showPos->setEndValue(show_pos); } showGroup->addAnimation(showPos); // connect(showGroup, &QParallelAnimationGroup::finished, [this]{ //back消失动画结束关闭窗口 if (showGroup->direction() == QAbstractAnimation::Backward){ //Qt::WA_DeleteOnClose后手动设置为null instance = nullptr; qApp->disconnect(this); //关闭时设置为非模态,方式主窗口被遮挡,待测试 this->setWindowModality(Qt::NonModal); this->close(); } else{ //配合keepAnimation showAnimEnd = true; //配合定时关闭 if (hideCount>0) hideTimer->start(); } }); } catch (...) { Log::WriteOutput(LogType::Error, L"TrayMessageDlg::initAnimation() Failed!"); return; } } void TrayMessageDlg::initTimer() { try { hideTimer = new QTimer(this); hideTimer->setInterval(1000); //1s间隔 connect(hideTimer, &QTimer::timeout, [this]{ if (hideCount>1){ hideCount--; ui->countLabel->setText(QString::fromLocal8Bit("%1s后自动关闭").arg(hideCount)); } else{ ui->countLabel->hide(); hideTimer->stop(); hideTip(); } }); } catch (...) { Log::WriteOutput(LogType::Error, L"TrayMessageDlg::initTimer() Failed!"); return; } } void TrayMessageDlg::readyTimer(int timeout) { try { //先设置,在显示动画结束再start开始计时器 hideCount = timeout; hideTimer->stop(); if (hideCount>0){ ui->countLabel->setText(QString::fromLocal8Bit("%1s后自动关闭").arg(hideCount)); } else{ ui->countLabel->hide(); } } catch (...) { Log::WriteOutput(LogType::Error, L"TrayMessageDlg::readyTimer() Failed!"); return; } } void TrayMessageDlg::showAnimation() { try { showGroup->setDirection(QAbstractAnimation::Forward); if (showGroup->state() == QAbstractAnimation::Running) { showGroup->stop(); //停止正在进行的动画重新 } showGroup->start(); show(); } catch (...) { Log::WriteOutput(LogType::Error, L"TrayMessageDlg::showAnimation() Failed!"); return; } } void TrayMessageDlg::keepAnimation() { try { //show没有完成,或者正在动画中才进入 if (!showAnimEnd || showGroup->state() != QAbstractAnimation::Stopped){ showGroup->setDirection(QAbstractAnimation::Forward); showGroup->start(); show(); } } catch (...) { Log::WriteOutput(LogType::Error, L"TrayMessageDlg::keepAnimation() Failed!"); return; } } void TrayMessageDlg::hideAnimation() { try { //Backward反向执行动画 showGroup->setDirection(QAbstractAnimation::Backward); showGroup->start(); } catch (...) { Log::WriteOutput(LogType::Error, L"TrayMessageDlg::hideAnimation() Failed!"); return; } } void TrayMessageDlg::paintEvent(QPaintEvent *event) { try { //目前没有动作,可以删除 } catch (...) { Log::WriteOutput(LogType::Error, L"TrayMessageDlg::paintEvent() Failed!"); return; } }
TrayMessageDlg.ui
<?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>TrayMessageDlg</class> <widget class="QWidget" name="TrayMessageDlg"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>300</width> <height>160</height> </rect> </property> <property name="windowTitle"> <string>Form</string> </property> <property name="styleSheet"> <string notr="true"> #frame{ background-color: white; border:none; border-radius:6px; } #titleArea{ background-color: white; border-bottom:1px solid rgb(227, 227, 227); } #titleLabel{ background-color:white; border:none; } #contentLabel{ background-color:white; padding: 6px 20px; } #countLabel{ background-color:white; color:rgb(59, 119, 229); } #btnClose{ background-color:white; border:none; border-radius:5px } #btnClose:hover{ background-color:rgb(255,64,64); color: rgb(0, 85, 127); } </string> </property> <layout class="QVBoxLayout" name="verticalLayout"> <property name="spacing"> <number>0</number> </property> <property name="leftMargin"> <number>0</number> </property> <property name="topMargin"> <number>0</number> </property> <property name="rightMargin"> <number>0</number> </property> <property name="bottomMargin"> <number>0</number> </property> <item> <widget class="QFrame" name="frame"> <property name="frameShape"> <enum>QFrame::StyledPanel</enum> </property> <property name="frameShadow"> <enum>QFrame::Raised</enum> </property> <layout class="QVBoxLayout" name="verticalLayout_2" stretch="2,5"> <property name="spacing"> <number>0</number> </property> <property name="leftMargin"> <number>6</number> </property> <property name="topMargin"> <number>6</number> </property> <property name="rightMargin"> <number>6</number> </property> <property name="bottomMargin"> <number>6</number> </property> <item> <widget class="QWidget" name="titleArea" native="true"> <property name="minimumSize"> <size> <width>0</width> <height>50</height> </size> </property> <layout class="QHBoxLayout" name="horizontalLayout" stretch="2,2,1"> <property name="spacing"> <number>0</number> </property> <property name="leftMargin"> <number>20</number> </property> <property name="topMargin"> <number>0</number> </property> <property name="rightMargin"> <number>20</number> </property> <property name="bottomMargin"> <number>1</number> </property> <item> <widget class="QLabel" name="titleLabel"> <property name="text"> <string>提示</string> </property> </widget> </item> <item> <widget class="QLabel" name="countLabel"> <property name="text"> <string>3s后自动关闭</string> </property> <property name="alignment"> <set>Qt::AlignCenter</set> </property> </widget> </item> <item> <widget class="QPushButton" name="btnClose"> <property name="sizePolicy"> <sizepolicy hsizetype="Minimum" vsizetype="Minimum"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> <property name="minimumSize"> <size> <width>16</width> <height>16</height> </size> </property> <property name="maximumSize"> <size> <width>25</width> <height>25</height> </size> </property> <property name="text"> <string/> </property> </widget> </item> </layout> </widget> </item> <item> <widget class="QLabel" name="contentLabel"> <property name="baseSize"> <size> <width>0</width> <height>0</height> </size> </property> <property name="text"> <string/> </property> <property name="alignment"> <set>Qt::AlignCenter</set> </property> <property name="wordWrap"> <bool>true</bool> </property> </widget> </item> </layout> </widget> </item> </layout> </widget> <resources/> <connections/> </ui>