当前位置: 首页 > backend >正文

如何使用QWidgets设计一个类似于Web Toast的控件?

如何使用QWidgets设计一个类似于Web Toast的控件?

前言

​ 笔者这段时间沉迷于给我的下位机I.MX6ULL做桌面,这里抽空更新一下QT的东西。这篇文章是跟随CCMoveWidget一样的文章,尝试分享自己如何书写这份代码的思考的过程,和笔者自己反思的不足之处。

​ 核心组件是继承自 QWidget 的 DesktopToast 类,其通过 QLabel 作为消息展示载体,并结合 QPropertyAnimation 完成提示动画效果。该设计首先考虑了窗口的非侵入性,使用 Qt 的无边框、Tool 类型窗口标志,并启用透明背景和非激活显示属性,从而实现一个不打断用户操作、不占用任务栏的浮动消息框

接口设计

​ 相对于上一篇文章的StackWidget_SwitchAnimations而言,这个会好一些,这里笔者设计的接口是这样的:

#ifndef DESKTOPTOAST_H
#define DESKTOPTOAST_H
#include <QPointer>
#include <QWidget>
#include <QQueue>
class QLabel;
class QPropertyAnimation;
class DesktopToast : public QWidget
{Q_OBJECT
public:explicit    DesktopToast(QWidget *parent = nullptr);/* enqueue the message */void        set_message(const QString& message);
signals:void        do_show_toast(QString msg);
private:void        adjust_place();void        start_animation();void        start_close_animation();/* fetch from pool and display */void        set_message_impl(const QString& message);QLabel*     label;QPoint      startPos, endPos;int         animation_maintain_msec{500};int         wait_time{1000};QPointer<QPropertyAnimation> moveAnimation{nullptr};QPointer<QPropertyAnimation> fadeAnimation{nullptr};bool isHandling{false};/** when large amount of messages smash in,* pools do the job of Buffering the message* warning: Queue itself is not thread safe, add* lock if in multithread*/QQueue<QString> pools;
};#endif // DESKTOPTOAST_H

​ 这里区分几个点:第一个事情是label,这个是信息显示的一个载体,startPos, endPos是用来标记控制我们的Toast的位置的。animation_maintain_msec控制动画的时常,wait_time是稳定的事件消息显示。moveAnimation这个是笔者用来控制出现的动画,fadeAnimation是消失的动画。

#include <QLabel>
#include <QGuiApplication>
#include <QPropertyAnimation>
#include <QScreen>
#include <QTimer>
#include "desktoptoast.h"// Constructor: configure the window flags and label style
DesktopToast::DesktopToast(QWidget *parent): QWidget{parent}
{// Make the window frameless, floating and always on topsetWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::WindowStaysOnTopHint);// Enable translucent background for rounded corners and alpha gradientsetAttribute(Qt::WA_TranslucentBackground);// Do not grab focus or activate the windowsetAttribute(Qt::WA_ShowWithoutActivating);// Create the label to display the toast messagelabel = new QLabel(this);// Use gradient background and rounded corners for better UI appearancesetStyleSheet("QLabel {""background: qlineargradient(spread:pad, ""x1:0, y1:0, x2:1, y2:1, ""stop:0 rgba(250, 250, 250, 100), ""stop:1 rgba(230, 230, 230, 100));""border-radius: 10px;""}");// Connect the internal signal to the implementation slotconnect(this, &DesktopToast::do_show_toast,this, &DesktopToast::set_message_impl);
}// Play the entry animation to slide the toast into view
void DesktopToast::start_animation()
{show(); // ensure the widget is visibleif (moveAnimation) {moveAnimation->stop();       // stop any ongoing animationmoveAnimation->deleteLater(); // clean up old animation}// Animate the widget's position from startPos to endPosmoveAnimation = new QPropertyAnimation(this, "pos");moveAnimation->setDuration(animation_maintain_msec);moveAnimation->setStartValue(startPos);moveAnimation->setEndValue(endPos);moveAnimation->setEasingCurve(QEasingCurve::OutCubic); // smooth-out easingmoveAnimation->start(QAbstractAnimation::DeleteWhenStopped);
}// Play the exit animation to slide the toast out and prepare for next message
void DesktopToast::start_close_animation()
{show(); // required to animate out properlyif (fadeAnimation) {fadeAnimation->stop();fadeAnimation->deleteLater();}// Reuse the position animation for simplicity, sliding back to startPosfadeAnimation = new QPropertyAnimation(this, "pos");fadeAnimation->setDuration(animation_maintain_msec);fadeAnimation->setStartValue(endPos);fadeAnimation->setEndValue(startPos);// When animation finishes, hide the widget and check the message queueconnect(fadeAnimation, &QPropertyAnimation::finished, this, [this]() {isHandling = false;hide();// If there are still messages in the queue, show the next oneif (!pools.isEmpty()) {isHandling = true;QString msg = pools.dequeue();emit do_show_toast(msg);}});fadeAnimation->start(QAbstractAnimation::DeleteWhenStopped);
}// Calculate and apply the toast position based on parent or screen geometry
void DesktopToast::adjust_place()
{QWidget* referenceWidget = parentWidget();if (referenceWidget) {// Position relative to parent if availableQRect parentRect = referenceWidget->rect();QPoint topCenter(parentRect.width() / 2 - width() / 2, 30);endPos = referenceWidget->mapToGlobal(topCenter);} else {// Otherwise, position at top center of primary screen
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)QRect screenGeometry = QGuiApplication::primaryScreen()->availableGeometry();
#elseQRect screenGeometry = QApplication::desktop()->availableGeometry();
#endifint screenWidth = screenGeometry.width();int screenX = screenGeometry.x();QPoint topCenter(screenX + (screenWidth - width()) / 2, screenGeometry.top() + 30);endPos = topCenter;}// Slide animation will start from above the final positionstartPos = QPoint(endPos.x(), endPos.y() - 70);move(startPos);
}// Enqueue a message to be displayed; if idle, trigger it immediately
void DesktopToast::set_message(const QString& message)
{pools.enqueue(message);if (!isHandling) {isHandling = true;QString msg = pools.dequeue();emit do_show_toast(msg);}
}// Display the message and start both animations and close timer
void DesktopToast::set_message_impl(const QString& message)
{label->setText(message);label->adjustSize();resize(label->size()); // fit to label sizeadjust_place(); // determine where to show the toastshow();raise(); // bring on top of sibling widgetsstart_animation(); // enter animation// Start close animation after wait time + animation durationQTimer::singleShot(wait_time + animation_maintain_msec, this, &DesktopToast::start_close_animation);
}

​ 这里的QString 队列 pools 实现了简单但实用的消息缓冲机制,使该提示框具备顺序展示大量消息的能力,并在注释中清楚地提醒了其非线程安全性。

​ 为了避免动画冲突,我是用 QPointer 包装动画对象,在启动前清理旧动画,确保每一次动画都是全新的过程,并用 isHandling 标志位控制消息的串行处理。位置信息由 adjust_place 函数动态调整,无论是否有父窗口,这样总是居中定位在屏幕上方;而实际的展示逻辑通过 set_message_impl 驱动,该函数不仅设置 QLabel 文本并自适应尺寸,还协调动画播放和自动关闭,呈现出一种自然的消息过渡过程。整个机制通过信号 do_show_toast 解耦用户接口与实际执行逻辑,使 set_message 可以无阻塞地写入消息,而真正的展示交给内部状态控制来完成。

​ 演示一下:
请添加图片描述

http://www.xdnf.cn/news/4005.html

相关文章:

  • 【Java ee初阶】多线程(5)
  • Electron 架构详解:主进程与渲染进程的协作机制
  • [低代码 + AI] 明道云与 Dify 的三种融合实践方式详解
  • FreeRTOS菜鸟入门(十一)·信号量·二值、计数、递归以及互斥信号量的区别·优先级翻转以及继承机制详解
  • 英伟达语音识别模型论文速读:Token-and-Duration Transducer(TDT)架构
  • Android 控件CalendarView、TextClock用法
  • Notebook.ai 开源程序是一套工具,供作家、游戏设计师和角色扮演者创建宏伟的宇宙 - 以及其中的一切
  • GZ人博会自然资源系统(测绘)备考笔记
  • 25:三大分类器原理
  • 小刚说C语言刷题—1038编程求解数学中的分段函数
  • brpc 安装及使用
  • MVC、MVP、MVVM三大架构区别
  • HTML05:超链接标签及应用
  • C++笔记之反射、Qt中的反射系统、虚幻引擎中的反射系统
  • 利用jQuery 实现多选标签下拉框,提升表单交互体验
  • 动态指令参数:根据组件状态调整指令行为
  • AI Agent开发第50课-机器学习的基础-线性回归如何应用在商业场景中
  • 软考 系统架构设计师系列知识点 —— 黑盒测试与白盒测试(1)
  • GStreamer开发笔记(三):测试gstreamer/v4l2+sdl2/v4l2+QtOpengl打摄像头延迟和内存
  • 电赛经验分享——模块篇
  • android-ndk开发(4): linux开发机有线连接android设备
  • 命令模式(Command Pattern)
  • [USACO1.1] 坏掉的项链 Broken Necklace Java
  • C++ -- 内存管理
  • 探寻适用工具:AI+3D 平台与工具的关键能力及选型考量 (AI+3D 产品经理笔记 S2E03)
  • Java面试:微服务与大数据场景下的技术挑战
  • 《MATLAB实战训练营:从入门到工业级应用》高阶挑战篇-《5G通信速成:MATLAB毫米波信道建模仿真指南》
  • MySQL JOIN详解:掌握数据关联的核心技能
  • 【翻译、转载】【译文】模型上下文协议(Model Context Protocol, MCP)简介
  • java技术总监简历模板