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>

  

  

 

posted @ 2020-11-13 11:09  Suarezz  阅读(1901)  评论(0编辑  收藏  举报