偶然看到 web 端某个应用,右下角的错误信息弹窗配合上动画看起来很不错,于是想通过 Qt 自己实现一个类似的功能

初定需求

  1. 弹窗信息从右下角屏幕外,移动到右下角,停留 3s 左右后,逐渐消失
  2. 鼠标悬浮在弹窗上时,即使弹窗已经停留了 3s,依旧不能消失, 等鼠标移出弹窗后,逐渐消失
  3. 弹窗上添加超链接,用户点击的时候,可以有对应操作
  4. 旧的弹窗信息没有消失时候,出现新的弹窗的话,新弹窗动画方式不变,旧的弹窗向上移动一个弹窗的单位

先定这几个,以后想到可以在额外实现

功能的实现

先放一个最后实现的 gif,之后在逐步解释实现的过程

接下来的部分主要是代码逻辑的分析,文章最后有工程在 github 的地址

右下角动画弹出

这个功能大概的逻辑

  1. 初始化一个弹窗,移动到右下角的屏幕外
  2. 一个 m_showTimer 定时器,每隔 1 ms, 弹窗向上移动 1 个单位,直到移动到指定位置
  3. 移动到指定位置后,m_stayTimer 定时器,每隔 1000 ms 触发一次,3次之后,启动关闭的 m_closeTimer 的定时器
  4. m_closeTimer 定时器, 每隔 100 ms, 窗口的透明度 (完全不透明为1) 减少 0.2,直到透明度为 0 时,关闭窗口

messageshow.h

// 窗口最后稳定不动时的坐标QPoint end_showPoint;// 因为是从右下角滑出, x 方向是固定的,所以需要记录一下当前窗口的实时 y 坐标int m_currentHeight;

// 3 个定时器QTimer* m_showTimer;QTimer* m_stayTimer;QTimer* m_closeTimer;

// 记录 m_stayTimer 执行的次数int m_stayExeTime;// 记录当前窗口的透明度double m_transparent;

messageshow.cpp

// 所有定时器对应执行的信号槽connect(m_showTimer, SIGNAL(timeout()),  this, SLOT(slotMsgMove()));connect(m_stayTimer, SIGNAL(timeout()),  this, SLOT(slotMsgStay()));connect(m_closeTimer, SIGNAL(timeout()), this, SLOT(slotMsgClose()));

// 显示弹窗的初始化函数void MessageShow::showMessage(){    m_showTimer->stop();    m_stayTimer->stop();    m_closeTimer->stop();    setWindowOpacity(1);

    // 获取当前桌面的位置    QRect desk_rect = QApplication::desktop()->availableGeometry();

    // 计算最后呈现的位置    end_showPoint.setX(desk_rect.width() - rect().width());    end_showPoint.setY(desk_rect.height() - rect().height());

    // 先将弹窗移动到桌面外    m_currentHeight = desk_rect.height();    move(desk_rect.width(), desk_rect.height());

    this->open();    m_showTimer->start(1);}

// m_showTimer 每次 timeout 后,需要执行的函数void MessageShow::slotMsgMove(){    // Qt 默认的坐标系原点是左上角,所以弹窗需要从右下角向上弹出,x 坐标一直不变, y 坐标一直减    m_currentHeight--;    move(end_showPoint.x(), m_currentHeight);    // 如果移动到制定位置后,m_showTimer 定时器停止,开启 m_stayTimer 定时器    if(m_currentHeight <= end_showPoint.y() )    {        m_showTimer->stop();        // m_enterEvent 是为了标识之后的另一个功能,鼠标是否在弹窗内        if (!m_enterEvent)            m_stayTimer->start(1000);    }}

// m_stayTimer 每次 timeout 后, 需要执行的函数void MessageShow::slotMsgStay(){    // 记录 m_stayTimer 运行次数    m_stayExeTime++;    // 当执行了3次,每次1000ms, 也就是停留 3s 之后, 启动 m_closeTimer 定时器    if(m_stayExeTime >= 3)    {        m_stayTimer->stop();        m_closeTimer->start(100);    }}

// m_closeTimer 每次 timeout 后, 需要执行的函数void MessageShow::slotMsgClose(){    // 每次执行 1次后, 透明度减少 0.2, 直到透明度为0后,停止 m_closeTimer, 并关闭窗口    m_transparent -= 0.2;    if(m_transparent <= 0.0)    {        m_closeTimer->stop();        emit sigClose(this);    }    else    {        setWindowOpacity(m_transparent);    }}

这样,一个完整的从显示,到停留,到最后的消失,代码逻辑就完成

鼠标进入和离开的逻辑

鼠标进入后,弹窗一直停留, 鼠标离开弹窗,停留 1s 后,逐渐淡化消失

void MessageShow::enterEvent(QEvent *event){    Q_UNUSED(event);

    m_enterEvent = true;    // 弹窗一直停留,停止  m_stayTimer 定时器即可    m_stayTimer->stop();    m_transparent = 1.0;    setWindowOpacity(1.0);    m_closeTimer->stop();}

void MessageShow::leaveEvent(QEvent *event){    Q_UNUSED(event);    m_enterEvent = false;

    // 鼠标离开时后, 重新开启 m_stayTimer 定时器    // 因为要停留 1s, 将 m_stayExeTime 设置成已经执行2次, 再运行1次,也就是 1000ms 后,就会开启 m_closeTimer 定时器    m_stayExeTime = 2;    m_stayTimer->start(1000);}

弹窗上添加超链接

上面介绍了完整的弹窗显示动画逻辑,现在只剩下设置弹窗中的内容设置

messageshow.h

void setInfomation(QString titleInfo, QString msg);void setInfomation(QString titleInfo, QString msg, QString extraInfo);

messageshow.cpp

// ui 界面里简单用 label_titleInfo 显示 title 内容, label_msg 里显示具体的信息// 此接口主要显示简单的弹窗内容void MessageShow::setInfomation(QString titleInfo, QString msg){    ui->label_titleInfo->setText(titleInfo);    ui->label_msg->setText(msg);        ui->label_msg->setToolTip(msg);}

// 弹窗上携带超链接的接口void MessageShow::setInfomation(QString titleInfo, QString msg, QString extraInfo){    ui->label_titleInfo->setText(titleInfo);    ui->label_msg->setText(QString(") + extraInfo + QString("\">")+msg);    ui->label_msg->setToolTip(msg);// void sigClickUrl(QString), 使用弹窗的对象捕获 sigClickUrl 信号,处理用户自定义的超链接    connect(ui->label_msg,SIGNAL(linkActivated(QString)),this,SIGNAL(sigClickUrl(QString)));}

多个弹窗同时出现的处理

涉及到对多个弹窗同时管理,这里使用 单例 的形式实现一个管理弹窗的管理类 PopupManagePopupManage 负责 MessageShow 对象的创建和销毁,现在的主要问题是,如何计算每个弹窗在稳定的时候位置

先简单理一下逻辑

  • 第一个弹窗出现时,从右下角向上滑出,滑出到指定位置停留若干秒
  • 此时弹出第二个弹窗,第二个弹窗依旧从右下角向上滑出,但是此时第一个弹窗应该继续向上移动一个弹窗的身位

所以,整体的代码逻辑可以这样,使用 PopupManage 创建一个新的 MessageShow 的弹窗, 将 MessageShow 对象存入 QList m_popupList 中, 当再次创建新的弹窗时,通知 m_popupList 中所有的 MessageShow 对象, 让它们向上移动一个弹窗的身位

现在看一下简单的代码逻辑

popupmanage.h

class PopupManage : public QObject{    Q_OBJECT

public:    // 单例实现    static PopupManage * getInstance();

    // 创建 MessageShow 对象的接口    void setInfomation(QString titleInfo, QString msg);    void setInfomation(QString titleInfo, QString msg, QString extraInfo);

private slots:    // 创建 MessageShow 对象之后,进行一些额外的操作    void addMessageShow(MessageShow *popup);    // MessageShow 关闭的时候,PopupManage 需要执行的操作    void deleteMessageShow(MessageShow *popup);    // 通知 m_popupList 所有的 MessageShow 的对象    void notifyMessageShow();

signals:    void sigClickUrl(QString);

private:    PopupManage();    ~PopupManage();

    static PopupManage * m_popupManager;    QList m_popupList;}popupmanage.cpp```C++// 创建 MessageShow 对象后,需要执行的一些操作void PopupManage::addMessageShow(MessageShow *popup){    connect(popup, &MessageShow::sigClose, this, &PopupManage::deleteMessageShow);    connect(popup, &MessageShow::sigClickUrl, this, &PopupManage::sigClickUrl);    m_popupList.append(popup);this->notifyMessageShow();}// MessageShow 关闭的时候,PopupManage 需要执行的操作void PopupManage::deleteMessageShow(MessageShow *popup){    disconnect(popup, &MessageShow::sigClose, this, &PopupManage::deleteMessageShow);    disconnect(popup, &MessageShow::sigClickUrl, this, &PopupManage::sigClickUrl);    m_popupList.removeOne(popup);delete popup;    popup = nullptr;}// 通知所有的 MessageShow 改变位置void PopupManage::notifyMessageShow(){for(int i = 0; i         m_popupList.at(i)->updatePosition();    }}PopupManage *PopupManage::getInstance(){return m_popupManager;}void PopupManage::setInfomation(QString titleInfo, QString msg){    MessageShow * popup = new MessageShow();    popup->setInfomation(titleInfo, msg);    addMessageShow(popup);}void PopupManage::setInfomation(QString titleInfo, QString msg, QString extraInfo){    MessageShow * popup = new MessageShow();    popup->setInfomation(titleInfo, msg, extraInfo);    addMessageShow(popup);}

这里 MessageShow 有个函数 updatePosition(), 在创建 MessageShow 对象之后,直接调用了 updatePosition(), 所有这里的 updatePosition() 一方面的作用是在弹窗第一次显示的 showMessage 功能,另一方面是创建新的弹窗之后的改变位置功能

void MessageShow::updatePosition(){    // 如果是第一次显示,调用 showMessage 函数, 否则是改变位置     if(m_firstShow){        this->showMessage();        m_firstShow = false;    }else{        end_showPoint.setY(end_showPoint.y()- this->height()-2);        m_currentHeight = m_currentHeight - this->height()-2;        move(end_showPoint.x(), m_currentHeight);    }}

这里 PopupManage 也可以这样实现

void PopupManage::setInfomation(QString titleInfo, QString msg){    MessageShow * popup = new MessageShow();    popup->setInfomation(titleInfo, msg);    connect(popup, &MessageShow::sigClose, this, &PopupManage::deleteMessageShow);    connect(popup, &MessageShow::sigClickUrl, this, &PopupManage::sigClickUrl);

    for(int i = 0; i         m_popupList.at(i)->updatePosition();    }

    popup->showMessage();    m_popupList.append(popup);}

这里 updatePosition() 就可以只是改变位置,因为在通知所有 MessageShow 对象改变位置的时候,新创建的对象还没有存储到 m_popupList

还可以优化的地方

  • 这里没有对弹窗的数量进行控制,可能会出现,弹窗超出界面的现象
  • 目前弹窗的样式和布局设置的最简单的方式,也可以进一步改进

一些疑问

不知道有没有注意到这里的弹窗用的显示方法是 this->open(), 而不是 show() 或者 exec()

  • exec() 是模态的弹窗,这个肯定直接被 pass 了,不可能让一个弹窗阻塞了主窗口
  • 当当前焦点的窗口是 QWidget 等非模态窗口时, show()open() 这两种显示的方法都可以,整体的弹窗功能都是完整
  • 但是当当前焦点窗口是模态窗口时,使用 show() 时, MessageShow 的对象是无法获取鼠标事件事件的,这个我们是懂得,模态窗口实际上是新创建了一个事件循环, MessageShow 的对象的鼠标事件肯定不能获取,但是 open 却可以,难道是新的事件循环里将 MessageShow 也添加里进来了? 这里自己不是很懂,先给自己挖个新一篇博客的坑,有时间好好研究一些 Qt 的事件循环机制

代码地址

github 地址 :https://github.com/catcheroftime/PopMessage

qt 创建第二个ui_Qt自定义提示信息弹窗相关推荐

  1. 【QT】自定义日历弹窗

    一.构想 自定义日历弹窗的制作主要是分为两部分,1.自定义日历,2.点击LineEdit时,将日历窗口弹出来.首先针对如何自定义日历制定思路,通过上网查询 QT自带了一个日历类QCalendarWid ...

  2. Qt 之 自定义提示信息框—迅雷风格

    一.简述 最近一直在研究迅雷9的界面,花了点时间做了几个通用的提示信息框,整体风格与迅雷9界面相同.支持模态和非模态两种模式窗口.提示框效果见下图. 我们可以根据设置不同的参数来设置提示框标题.显示内 ...

  3. android自定义xml弹窗,Android自定义弹窗提醒控件使用详解

    Android中原生的Dialog弹窗提醒控件样式单一,有时候并不能满足我们的项目需求,而且一个工程里面有时候会在多处都用到弹窗提醒的功能,代码会出现大量的冗余,工作之余,就自己实现了这么一个弹窗提醒 ...

  4. 微信小程序之自定义模态弹窗(带动画)实例

    代码地址如下: http://www.demodashi.com/demo/13991.html 一.前期准备工作 软件环境:微信开发者工具 官方下载地址:https://mp.weixin.qq.c ...

  5. android 自定义权限弹窗_Android-开源通用弹窗的封装CommonPopupWindow(总得向别人学点什么)...

    终于也是自己封装一些东西了,不管烂不烂,还是自我鼓励了一把!(之前学maven发布也是这个目的) 开源地址: FanChael/CommonPopupWindow 弹窗效果如下: @tip后面会逐渐完 ...

  6. vue 定义全局弹框_VUE路由拦截:Vue自定义全局弹窗组件

    前言 在任何一个平台中,如果需要增加用户黏度,除了用户需要的基本内容外,用户登录注册提交信息也是非常重要的一环,可以了解用户基本信息,用户喜欢等. 抛出前后端混合开发外,vue可以轻松的实现路由拦截. ...

  7. 这可能是第二好的自定义 View 教程之属性动画

    上期文章镇楼: 这可能是第二好的自定义 View 教程之绘制 凯哥的文章确实写的细而好呀,这不,活生生把 面试系列 先放一放,继续讲解我们的动画. 为啥是第二好? 一看就是没看 前面的文章 的.这里就 ...

  8. 使用Qt创建XML文档及XML文档的增删改

    目录名字 使用Qt创建XML文档及XML文档的增删改 XML文档的操作 :QXml + QFile+QTextStream 创建XML的一般步骤: XML 文档的操作(添加.查找.更新.删除) 添加的 ...

  9. Qt Creator添加新的自定义向导

    Qt Creator添加新的自定义向导 添加新的自定义向导 向导类型 定位向导 向导开发提示 将动作映射到键盘快捷键 详细输出 将向导集成到内部版本 在向导中使用变量 本地化向导 创建向导 向导可用的 ...

  10. QT Core | 信号槽03 - 自定义信号与槽

    文章目录 一.前言 二.新建一个QT控制台项目 2.1.New File or Project 2.2.Project Location 2.3.Define Build System 2.4.Kit ...

最新文章

  1. 力扣(LeetCode)刷题,简单+中等题(第28期)
  2. cs架构嵌入bs_CS与BS架构区别、比较、及现状与趋势分析
  3. 手机文件share.php,华为P10手机Huawei Share怎么用?一秒共享手机文件
  4. 白话Elasticsearch73_ES生产集群中的索引管理01
  5. 联想EDU同传系统 版本7.5 7.6在机房中出现的一些故障和解决办法
  6. openwrt dhcp 无法获取ip_电脑的 ip 是怎么来的呢?我又没有配置过
  7. Java 远程通讯技术及原理分析
  8. 关于laravel数据库问题
  9. 常用html元素的取值和赋值方法总结
  10. 烧钱两年,做事对得起工资,也要对得起公司这份决心
  11. Unity 安装失败原因
  12. Ubuntu-区域截图
  13. foxmail超大附件服务器文件怎么删,foxmail邮件太大怎么发?foxmail发送超大附件的方法...
  14. AIOT:基于智能家居谈AIOT
  15. 使用Keras画神经网络准确性图
  16. docker学习笔记(二)docker常用命令
  17. 【全栈软件测试】软件测试学习路线介绍
  18. 云计算基础概念——Iaas、PaaS、SaaS
  19. Flagger on ASM——基于Mixerless Telemetry实现渐进式灰度发布系列 3 渐进式灰度发布
  20. 禁止windows更新唤醒计算机,windows10睡眠被自动更新唤醒的解决方法

热门文章

  1. java 注册表 下载_Java修改windows注册表(完全修改)
  2. python 从入门到实践_Python编程从入门到实践日记Day36
  3. centos6 和 centos7 防火墙基本操作
  4. maven实现RBAC权限管理,实现不同角色下对应不同菜单
  5. fortran语言和python_fortran是什么语言吗?
  6. 打开gedit_使用 gedit 文本编辑器优化你的 GNOME 体验 | Linux 中国
  7. VideoView播放视频会引起其它音乐播放器暂停问题解决
  8. 【转】C#字符串转换为日期
  9. 数据库IO性能知识整理
  10. linux命令与示例 week 3