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

如何使用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 可以无阻塞地写入消息,而真正的展示交给内部状态控制来完成。

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

相关文章:

  • js获取明天日期、Vue3大菠萝 Pinia的使用
  • Unity:Surface Effector 2D(表面效应器 2D)
  • C++入门(上)--《Hello C++ World!》(1)(C/C++)
  • 学习海康VisionMaster之亮度测量
  • 【Bootstrap V4系列】学习入门教程之 组件-按钮组(Button group)
  • Spring 框架的底层原理
  • linux 高并发 文件句柄数 fs 及 tcp端口数调优
  • 【心海资源】telegram换U地址完整源码
  • 【算法学习】递归、搜索与回溯算法(一)
  • # 部署深度学习模型:Flask API 服务端与客户端通信实战
  • 手写 Vue 源码 === reactive 方法
  • Vim 命令从头学习记录
  • Java设计模式: 工厂模式与策略模式
  • 使用 JavaScript 实现数据导出为 Excel 和 CSV 文件
  • #基础Machine Learning 算法(上)
  • 大模型微调Fine-tuning:从概念到实践的全面解析
  • LeetCode算法题 (反转链表)Day17!!!C/C++
  • Excel VBA 自定义函数
  • 学习路线(机器人系统)
  • 【中间件】brpc_基础_TimerThread
  • 李干杰走访各民主党派中央和全国工商联机关
  • “五一”假期国内出游3.14亿人次,同比增长6.4%
  • 新闻1+1丨多地政府食堂开放 “舌尖上的服务”,反映出怎样的理念转变?
  • 特朗普考虑任命副幕僚长米勒任国安顾问,曾策划驱逐移民行动
  • 中国企业转口贸易破局之道:出口国多元化,内外贸一体化
  • 多地晒五一假期前两日成绩单,湖南单日客流同比增长逾三成