☰
Qt6 实现 主窗体外另开一个浮窗,并让它动起来
2026/10/8 2:08:10 网站建设 项目流程

Qt6 实现 主窗体外另开一个浮窗,并让它动起来

flyfish

下载地址在文末

主窗口里点一下,屏幕别的地方冒出一张卡片、一条通知、一个悬浮工具条。它不是主窗口里嵌着的子控件,而是飘在外面的独立窗口——能跨过主窗口的边界,能被别的窗口盖住,能被拖到屏幕任意位置。

第一反应是把一个 QWidget 塞进主窗口,再用 move / raise 想把它挪出去。但子控件永远被父窗口的矩形裁切,出不了主窗口的边框。要让它真正出去,得让它成为一个属于自己的顶层窗口。

QWidget 什么时候是子控件,什么时候是窗口

Qt 里一个 QWidget 到底是不是独立窗口,看的是 window flags,不是有没有传父对象。

// 普通用法:parent 是主窗口 → 嵌在主窗口里的子控件auto*label=newQLabel(parent);// 想让它成为独立窗口:auto*w=newQWidget(parent);w->setWindowFlags(Qt::Window);// 关键:给自己一个 Window 标志w->show();

只要带了Qt::Window,不管有没有传 parent,它都会向系统要一个自己的原生窗口(Windows 上是 HWND,macOS 上是 NSWindow),有自己的 z 序、能跑到父窗口外面。parent 在这个场景下只负责一件事:主窗口关掉时它跟着被析构,不用手动管生命周期。

所以"在主窗体外建窗体"这句,落到代码上就一句话:给 QWidget 加Qt::Window标志,它就是一个独立窗口了。

做成无边框的浮窗

带系统标题栏的窗口不适合做卡片、通知这类东西。去掉边框用Qt::FramelessWindowHint:

w->setWindowFlags(Qt::FramelessWindowHint|Qt::Window);w->setAttribute(Qt::WA_TranslucentBackground,true);// 背景透明,圆角才干净

WA_TranslucentBackground不能省。不透明窗口你画了圆角,四个角会是方形的黑补丁;背景透明之后,paintEvent 里只画卡片本体,其余区域天然透掉,圆角和阴影才成立。

阴影我直接挂一个QGraphicsDropShadowEffect,它按你画出来的内容轮廓投影,透明的地方不投影:

auto*eff=newQGraphicsDropShadowEffect(w);eff->setBlurRadius(22);eff->setOffset(0,8);eff->setColor(QColor(0,0,0,130));w->setGraphicsEffect(eff);

窗口本身要比卡片大一圈(我四周各留 14px),那圈是给阴影的呼吸空间,不画任何东西。

窗体动画:顶层窗口动不了整体变换

窗口独立了,接下来的问题是:怎么让它"动"。

子控件可以用setTransform整体缩放,但顶层窗口是系统级的,Qt 不会替你做几何变换。能直接动的只有两样:geometry()(位置和尺寸)和windowOpacity()(透明度)。

我用一个弹簧去驱动它的中心坐标、缩放、透明度,每帧把这四个值合成成真实的窗口矩形:

voidCardWindow::applyGeometry(){constdoublesc=m_scale.value;constintw=qMax(1,qRound((m_fullW+28)*sc));constinth=qMax(1,qRound((m_fullH+28)*sc));constintx=qRound(m_cx.value-w/2.0);// 绕中心缩放constinty=qRound(m_cy.value-h/2.0);setGeometry(x,y,w,h);setWindowOpacity(qBound(0.0,m_opacity.value,1.0));}

位置我用的是中心坐标而不是左上角,因为缩放是绕中心发生的。你要是拿左上角去乘缩放,窗口会朝右下方被拽大,看着像被拉出来的,不是从中间长出来的。这一个坐标选择,基本决定了动画真不真。

弹簧本身不复杂,五个字段:当前值、目标值、速度、阻尼比、响应。每帧朝目标推一截。想弹出就把目标设到终点;想中途打断就直接改目标,当前值和速度原地接着用,不会跳变。比起写死duration=300ms的固定时长动画,它天然就能被打断、能接住松手时的惯性。

一个定时器驱动所有浮窗

主窗口持有这些卡片的指针列表,起一个约 60fps 的定时器,每帧把每张卡片的弹簧都 step 一遍:

voidMainWindow::tickAll(){constqint64 now=m_clock.nsecsElapsed();boolmoving=false;for(auto*c:m_cards)if(c->tick(now))moving=true;if(!moving)m_timer->stop();// 都停了就停表,不空转耗电}

"都在主窗体外"在这里是实打实的含义:这些卡片的geometry用的是屏幕全局坐标,跟主窗口的矩形没有隶属关系。你拖主窗口,卡片不动;卡片飘在屏幕任意角落都行。它们只是逻辑上挂在主窗口底下,方便统一调度和回收。

收起完成的卡片要在定时器循环之外再删,不然边遍历m_cards边delete会踩野指针:

// closed 信号只往待删列表里塞,循环结束后再统一 deleteLaterfor(auto*c:m_toDelete){m_cards.removeAll(c);c->deleteLater();}

下载地址

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询