在使用Qt做界面开发时,为了提升视觉效果,经常会采用无边框窗口设计。
实现无边框其实很简单,一行代码搞定。
setWindowFlag(Qt::FramelessWindowHint);
由于移除了系统默认标题栏,窗口失去了原生的移动和缩放功能,需通过代码手动实现。
本文旨在使用 Qt 框架实现一个无边框窗口,具备以下核心功能:
- 去除系统默认的窗口边框和标题栏;
- 支持通过鼠标拖动实现窗口整体移动;
- 预留最小化、最大化、关闭等标准窗口操作的接口结构(按钮功能将在后续章节中实现)。
该方案适用于希望自定义窗口外观、提升界面美观度的 Qt 开发者,尤其适合初学者理解无边框窗口的基本实现原理。
核心技术点
|------------------------------------------|----------------------------|
| 技术 | 说明 |
| setWindowFlags(Qt::FramelessWindowHint) | 移除系统默认边框和标题栏 |
| setWindowFlags(Qt::WindowSystemMenuHint) | 保留系统右键菜单(如移动、大小调整等),提升用户体验 |
| 重写 mousePressEvent | 记录鼠标按下时的相对位置,为拖动做准备 |
| 重写 mouseMoveEvent | 根据鼠标移动实时更新窗口位置,实现拖动效果 |
Part1无边框窗口实现
1.1、头文件定义
BasicFramelessWindow.h
#pragma once
#include <QtWidgets/QWidget>
#include <QMouseEvent>
class BasicFramelessWindow : public QWidget
{
Q_OBJECT
public:
explicit BasicFramelessWindow(QWidget *parent = nullptr);
~BasicFramelessWindow();
protected:
void mousePressEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
private:
QPoint dragPosition; // 记录鼠标按下时相对于窗口左上角的偏移
};
说明:
- 继承自 QWidget,构建基础窗口;
- 定义两个受保护的事件处理函数,用于捕获鼠标行为;
- 使用 dragPosition 存储拖动起始点与窗口坐标之间的偏移量。
1.2、源文件实现
BasicFramelessWindow.cpp
#include "BasicFramelessWindow.h"
BasicFramelessWindow::BasicFramelessWindow(QWidget *parent)
: QWidget(parent)
{
// 设置窗口为无边框,并保留系统菜单(右键可调出系统操作)
setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint);
// 关闭透明背景(确保背景正常显示)
setAttribute(Qt::WA_TranslucentBackground, false);
// 设置固定窗口大小
setFixedSize(600, 400);
// 设置样式:白色背景 + 灰色边框(便于观察)
setStyleSheet("background-color: white; border: 1px solid gray;");
}
BasicFramelessWindow::~BasicFramelessWindow()
{
// 析构函数(当前无需特殊处理)
}
// 鼠标按下事件:记录拖动起始位置
void BasicFramelessWindow::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
// 计算鼠标点击位置与窗口左上角的偏移
dragPosition = event->globalPosition().toPoint() - frameGeometry().topLeft();
event->accept(); // 接受事件,防止被其他控件处理
}
}
// 鼠标移动事件:执行窗口拖动
void BasicFramelessWindow::mouseMoveEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton) {
// 根据偏移量移动窗口
move(event->globalPosition().toPoint() - dragPosition);
event->accept();
}
}
关键逻辑解析:
- event->globalPosition().toPoint():获取鼠标在屏幕坐标系中的位置;
- frameGeometry().topLeft():获取窗口在屏幕上的左上角坐标;
- 两者的差值即为"拖动锚点",确保鼠标始终"抓着"窗口同一位置移动;
- move(...)直接改变窗口位置,实现平滑拖动。
运行程序后,将显示一个 600×400 的白色无边框窗口
虽然界面空白,但已具备以下能力:
- 可通过鼠标左键点击并拖动窗口任意位置实现移动
- 窗口无系统标题栏和边框
- 保留系统右键菜单(可通过右键点击任务栏图标调出"移动""大小"等选项)
Part2实现自定义标题栏
实现自定义标题栏:支持拖动、双击最大化与动态按钮切换
2.1、功能目标
|--------|--------------------------|
| 功能 | 说明 |
| 自定义标题栏 | 替代系统默认标题栏,支持自由布局与样式定制 |
| 三按钮控制 | 最小化、最大化/还原、关闭,通过信号与主窗口通信 |
| 动态图标切换 | 窗口最大化时,"最大化按钮"自动变为"还原图标" |
| 拖动移动 | 鼠标按住标题栏可拖动窗口(非最大化状态下) |
| 双击切换状态 | 双击标题栏实现最大化 ↔ 正常状态切换 |
2.2、标题栏组件实现(TitleBar)
我们将标题栏封装为独立组件 TitleBar,继承自 QWidget,便于在不同窗口中复用。
1. 头文件:TitleBar.h
#pragma once
#include <QWidget>
#include <QPushButton>
#include <QLabel>
#include <QHBoxLayout>
#include <QMouseEvent>
class TitleBar : public QWidget
{
Q_OBJECT
public:
explicit TitleBar(QWidget* parent = nullptr);
// 设置当前是否为最大化状态,用于图标切换
void setMaximized(bool maximized);
signals:
void signalMinimize(); // 发送最小化信号
void signalMaximizeRestore(); // 发送最大化/还原切换信号
void signalClose(); // 发送关闭信号
protected:
void mousePressEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void mouseDoubleClickEvent(QMouseEvent* event) override;
private:
QPushButton* btnMin; // 最小化按钮
QPushButton* btnMaxRestore; // 最大化/还原按钮
QPushButton* btnClose; // 关闭按钮
QLabel* titleLabel; // 标题标签
QPoint dragPosition; // 拖动偏移量
bool isMaximized; // 当前是否最大化
};
设计说明:
- 这里头定义了个 TitleBar 类,继承自 QWidget。
- public 里有构造函数和设置图标状态的 setMaximized 方法;
- signals 那块是三个信号,对应最小化、最大化 / 还原、关闭这几个动作;
- protected 里重载了鼠标按下、移动和双击事件;
- private 里就是那三个按钮、标题标签、记录拖动位置的 dragPosition,还有标记是否最大化的 isMaximized。
2. 源文件:TitleBar.cpp
#include "TitleBar.h"
#include <QMouseEvent>
#include <QStyle>
#include <QApplication>
TitleBar::TitleBar(QWidget* parent)
: QWidget(parent)
{
isMaximized = false;
setFixedHeight(35); // 标题栏高度
setAttribute(Qt::WA_StyledBackground, true); // 启用样式表渲染
// 设置整体样式
setStyleSheet(R"(
TitleBar {
background-color: rgb(223, 235, 250);
}
QPushButton {
border: none;
background-color: transparent;
min-width: 45px;
min-height: 35px;
}
QPushButton:hover {
background-color: rgb(211, 226, 237);
}
QPushButton:pressed {
background-color: rgba(255, 255, 255, 50);
}
)");
// 标题文本
titleLabel = new QLabel("My App");
titleLabel->setStyleSheet("border:none; background:transparent; font-weight:bold; padding-left:10px;");
// 创建按钮并设置图标(需确保资源已添加到qrc)
btnMin = new QPushButton();
btnMin->setIcon(QIcon(":/new/prefix1/resources/min.png"));
btnMaxRestore = new QPushButton();
btnMaxRestore->setIcon(QIcon(":/new/prefix1/resources/max.png"));
btnClose = new QPushButton();
btnClose->setIcon(QIcon(":/new/prefix1/resources/close.png"));
// 布局管理
auto mainLayout = new QHBoxLayout(this);
mainLayout->addWidget(titleLabel);
mainLayout->addStretch();
mainLayout->addWidget(btnMin);
mainLayout->addWidget(btnMaxRestore);
mainLayout->addWidget(btnClose);
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->setSpacing(0);
// 信号连接
connect(btnMin, &QPushButton::clicked, this, &TitleBar::signalMinimize);
connect(btnMaxRestore, &QPushButton::clicked, this, &TitleBar::signalMaximizeRestore);
connect(btnClose, &QPushButton::clicked, this, &TitleBar::signalClose);
}
图标状态切换
void TitleBar::setMaximized(bool maximized)
{
isMaximized = maximized;
btnMaxRestore->setIcon(
isMaximized ?
QIcon(":/new/prefix1/resources/restore.png") : // 还原图标
QIcon(":/new/prefix1/resources/max.png") // 最大化图标
);
}
鼠标事件处理
void TitleBar::mousePressEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton) {
dragPosition = event->globalPosition().toPoint() - parentWidget()->frameGeometry().topLeft();
}
}
void TitleBar::mouseMoveEvent(QMouseEvent* event)
{
if ((event->buttons() & Qt::LeftButton) && !isMaximized) {
parentWidget()->move(event->globalPosition().toPoint() - dragPosition);
}
}
⚠️ 注意:仅在非最大化状态下允许拖动,避免误操作。
void TitleBar::mouseDoubleClickEvent(QMouseEvent* event)
{
Q_UNUSED(event);
emit signalMaximizeRestore(); // 双击触发最大化/还原
}
双击标题栏即可切换窗口状态,符合用户习惯。
2.3、整合至主窗口(BasicFramelessWindow)
接下来将 TitleBar 嵌入主窗口,并连接信号槽实现完整控制逻辑。
1. 更新头文件:BasicFramelessWindow.h
#pragma once
#include <QWidget>
#include <QMouseEvent>
#include "TitleBar.h"
class BasicFramelessWindow : public QWidget
{
Q_OBJECT
public:
explicit BasicFramelessWindow(QWidget* parent = nullptr);
~BasicFramelessWindow();
protected:
void mousePressEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
private slots:
void onMinimize();
void onMaxRestore();
void onClose();
private:
TitleBar* titleBar;
bool isMaximized;
QPoint dragPosition;
};
2. 实现主窗口逻辑:BasicFramelessWindow.cpp
#include "BasicFramelessWindow.h"
#include <QVBoxLayout>
BasicFramelessWindow::BasicFramelessWindow(QWidget* parent)
: QWidget(parent), isMaximized(false)
{
setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint);
setAttribute(Qt::WA_TranslucentBackground, false);
setFixedSize(600, 400);
setStyleSheet("background-color: white; border: 1px solid gray;");
// 创建标题栏
titleBar = new TitleBar(this);
// 连接信号与槽
connect(titleBar, &TitleBar::signalMinimize, this, &BasicFramelessWindow::onMinimize);
connect(titleBar, &TitleBar::signalMaximizeRestore, this, &BasicFramelessWindow::onMaxRestore);
connect(titleBar, &TitleBar::signalClose, this, &BasicFramelessWindow::onClose);
// 主布局
auto mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(titleBar);
mainLayout->addStretch();
mainLayout->setSpacing(0);
mainLayout->setContentsMargins(1, 1, 1, 1); // 留出边框间隙
}
BasicFramelessWindow::~BasicFramelessWindow() = default;
void BasicFramelessWindow::mousePressEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton) {
dragPosition = event->globalPosition().toPoint() - frameGeometry().topLeft();
event->accept();
}
}
void BasicFramelessWindow::mouseMoveEvent(QMouseEvent* event)
{
if (event->buttons() & Qt::LeftButton) {
move(event->globalPosition().toPoint() - dragPosition);
event->accept();
}
}
// 槽函数实现
void BasicFramelessWindow::onMinimize()
{
showMinimized();
}
void BasicFramelessWindow::onMaxRestore()
{
if (isMaximized) {
showNormal();
} else {
showMaximized();
}
isMaximized = !isMaximized;
titleBar->setMaximized(isMaximized); // 同步按钮图标
}
void BasicFramelessWindow::onClose()
{
close();
}
信号-槽机制实现松耦合;
窗口状态变化后同步更新标题栏图标;
支持最小化、最大化/还原、关闭全功能。
2.4、运行效果

Part3实现无边框窗口
接下来我们将实现一个关键功能: 像系统窗口一样,通过鼠标拖动窗口边缘或角落来调整大小。
这包括:
- 上、下、左、右四边拉伸
- 四个角(左上、右上、左下、右下)斜向缩放
- 鼠标悬停时显示对应方向的光标(↔、↕、↘ 等)
- 动态调整窗口尺寸,支持最小宽高限制
3.1、功能说明
|--------|-------------------------------|
| 功能 | 说明 |
| 捕捉鼠标位置 | 判断鼠标是否位于窗口边缘或角落区域 |
| 改变鼠标形状 | 显示为 ↔(水平)、↕(垂直)、↘(对角线)等系统缩放光标 |
| 响应鼠标拖动 | 按下后拖动鼠标,动态调整窗口大小 |
| 限制最小尺寸 | 防止窗口被缩到不可见或过小 |
3.2、代码实现
核心类设计:CustomWindow
我们将窗口缩放功能封装在 CustomWindow 类中,继承自 QWidget,作为所有需要无边框缩放功能窗口的基类。
CustomWindow.h
#pragma once
#include <QtWidgets/QWidget>
#include<QMouseEvent>
#include<qpoint.h>
class CustomWindow :public QWidget
{
Q_OBJECT
public:
explicit CustomWindow(QWidget* parent = nullptr);
protected:
void mousePressEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event)override;
void mouseMoveEvent(QMouseEvent* event)override;
void leaveEvent(QEvent* event)override;
private:
enum ResizeRegion {
NoEdge = 0,
Left,
Right,
Top,
Bottom,
TopLeft,
TopRight,
BottomLeft,
BottomRight
};
const int EDGE_MARGIN = 8; //边缘检测范围
ResizeRegion getResizeRegion(const QPoint& pos);
bool isResizing = false; //是否正在缩放
ResizeRegion currentRegion = NoEdge;
QPoint dragStartGlobalPos; //鼠标拖动起点
QRect originalGeometry; //拖动时窗口原始位置
};
代码详解:
- 这里定义了个 CustomWindow 类,继承自 QWidget。
- protected 里重载了鼠标按下、释放、移动和离开事件。
- private 里搞了个枚举 ResizeRegion,把窗口的边缘和角落都分了类,从 NoEdge(不在边缘)到各个方向的边缘和角落。
- EDGE_MARGIN,设成 8,就是边缘检测的范围,鼠标离边缘这么近就算是在边缘区域了。
- getResizeRegion 方法是用来判断鼠标位置属于哪个区域的。
- 变量isResizing 标记是不是正在缩放,currentRegion 记当前鼠标在哪个区域,dragStartGlobalPos 是鼠标开始拖动时的全局位置,originalGeometry 是拖动前窗口的位置和大小。
CustomWindow.cpp
#include "CustomWindow.h"
CustomWindow::CustomWindow(QWidget* parent):QWidget(parent)
{
setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint); //无边框
setMouseTracking(true); //鼠标移动触发mouseMoveEvent
}
构造函数里,先设了无边框,然后 setMouseTracking (true),这样鼠标在窗口上动的时候,不用按鼠标键也能触发 mouseMoveEvent,方便检测鼠标位置换光标。
//准备拖动
void CustomWindow::mousePressEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton && currentRegion != NoEdge) {
isResizing = true;
dragStartGlobalPos = event->globalPosition().toPoint();
originalGeometry = geometry();
}
QWidget::mousePressEvent(event);
}
鼠标按下事件里,要是按的是左键,而且当前鼠标在边缘区域(不是 NoEdge),就把 isResizing 设为 true,记下拖动开始时鼠标的全局位置 dragStartGlobalPos,还有当时窗口的位置大小 originalGeometry。
//结束拖动
void CustomWindow::mouseReleaseEvent(QMouseEvent* event)
{
isResizing = false;
QWidget::mouseReleaseEvent(event);
}
鼠标释放的时候,就把 isResizing 改成 false,结束缩放。
//设置光标或拖动缩放
void CustomWindow::mouseMoveEvent(QMouseEvent* event)
{
if (isResizing) {
QPoint delta = event->globalPosition().toPoint() - dragStartGlobalPos;
QRect newGeom = originalGeometry;
switch (currentRegion) {
case Left:
newGeom.setLeft(originalGeometry.left() + delta.x());
break;
case Right:
newGeom.setRight(originalGeometry.right() + delta.x());
break;
case Top:
newGeom.setTop(originalGeometry.top() + delta.y());
break;
case Bottom:
newGeom.setBottom(originalGeometry.bottom() + delta.y());
break;
case TopLeft:
newGeom.setTopLeft(originalGeometry.topLeft() + delta);
break;
case TopRight:
newGeom.setTopRight(originalGeometry.topRight() + delta);
break;
case BottomLeft:
newGeom.setBottomLeft(originalGeometry.bottomLeft() + delta);
break;
case BottomRight:
newGeom.setBottomRight(originalGeometry.bottomRight() + delta);
break;
default:
break;
}
if (newGeom.width() >= minimumWidth() && newGeom.height() >= minimumHeight()) {
setGeometry(newGeom);
}
}
else {
//设置鼠标光标形状
ResizeRegion region = getResizeRegion(event->pos());
currentRegion = region;
switch (region) {
case Left:
case Right:
setCursor(Qt::SizeHorCursor);
break;
case Top:
case Bottom:
setCursor(Qt::SizeVerCursor);
break;
case TopLeft:
case BottomRight:
setCursor(Qt::SizeFDiagCursor);
break;
case TopRight:
case BottomLeft:
setCursor(Qt::SizeBDiagCursor);
break;
default:
unsetCursor();
break;
}
}
QWidget::mouseMoveEvent(event);
}
鼠标移动事件分两种情况:要是正在缩放(isResizing 为 true),就先算一下鼠标移动的距离 delta------ 当前鼠标全局位置减去开始拖动时的位置。然后根据 currentRegion,也就是当前缩放的区域,调整 newGeom(新的窗口位置大小)。比如是 Left 区域,就调整窗口的左边界;是 Right 就调右边界,四个角也各有对应的调整方式。调整完了,得检查新的宽度和高度是不是不小于最小尺寸,符合条件就用 setGeometry 设置新的窗口形状。
要是没在缩放,就调用 getResizeRegion 判断鼠标当前在哪个区域,然后根据区域换光标形状。比如左右边缘就用水平缩放的光标↔,上下边缘用垂直缩放的光标↕,对角就用对应的对角线光标,不在边缘就恢复默认光标。
//鼠标离开窗口,取消高亮
void CustomWindow::leaveEvent(QEvent* event)
{
if (!isResizing)unsetCursor();
QWidget::leaveEvent(event);
}
鼠标离开窗口时,要是没在缩放,就把光标恢复默认。
//获取边缘区域
CustomWindow::ResizeRegion CustomWindow::getResizeRegion(const QPoint& pos)
{
bool onLeft = pos.x() <= EDGE_MARGIN;
bool onRight = pos.x() >= width() - EDGE_MARGIN;
bool onTop = pos.y() <= EDGE_MARGIN;
bool onButtom = pos.y() >= height() - EDGE_MARGIN;
if (onTop && onLeft)return TopLeft;
if (onTop && onRight)return TopRight;
if (onButtom && onLeft)return BottomLeft;
if (onButtom && onRight)return BottomRight;
if (onTop)return Top;
if (onButtom)return Bottom;
if (onLeft)return Left;
if (onRight)return Right;
return NoEdge;
}
getResizeRegion 方法就是判断鼠标位置 pos 属于哪个区域。先看是不是在左、右、上、下边缘(根据 EDGE_MARGIN 判断),然后组合一下,比如又在顶部又在左边,就是 TopLeft,依次类推,最后返回对应的区域。
3.3、整合代码逻辑
因为 CustomWindow 是单独的类,要在之前的 BasicFramelessWindow 里用上它的缩放功能,得把逻辑整合一下。
先改 BasicFramelessWindow.h,让它继承 CustomWindow,把重复的鼠标事件处理逻辑去掉:
#pragma once
#include"TitleBar.h"
#include"CustomWindow.h"
class BasicFramelessWindow : public CustomWindow
{
Q_OBJECT
public:
explicit BasicFramelessWindow(QWidget *parent = nullptr);
~BasicFramelessWindow();
private slots:
void onMinimize();
void onMaxRestore();
void onClose();
private:
TitleBar* titleBar;
bool isMaximized;
};
这样就不用自己处理鼠标事件了,直接用 CustomWindow 的。
再改 BasicFramelessWindow.cpp,把原来的鼠标事件处理代码删掉,调整构造函数:
BasicFramelessWindow::BasicFramelessWindow(QWidget *parent)
: CustomWindow(parent),isMaximized(false)
还要把原来的 setFixedSize () 改成 resize (),因为现在要能缩放窗口,不能固定大小了。
这么一整合,窗口的鼠标拉伸功能就全实现啦,拽着边缘或者角落就能随便调整大小了。
Part4圆角与阴影效果
咱接下来搞 "无边框窗口的圆角与阴影效果"。要是在现有程序里改,得动的地方不少,所以咱直接建个新项目来实现这效果。
4.1、核心实现原理
|--------|--------------------------------------------|
| 技术点 | 实现方式 |
| 去除系统边框 | setWindowFlags(Qt::FramelessWindowHint) |
| 支持透明背景 | setAttribute(Qt::WA_TranslucentBackground) |
| 实现圆角 | 在 contentWidget 上设置 border-radius 样式 |
| 实现阴影 | 使用 QGraphicsDropShadowEffect 添加到内容控件 |
| 避免锯齿 | 使用 QPainterPath 绘制抗锯齿背景(可选增强) |
4.2、代码实现
CustomWindow.h
#pragma once
#include <QtWidgets/QWidget>
class CustomWindow : public QWidget
{
Q_OBJECT
public:
explicit CustomWindow(QWidget* parent = nullptr);
~CustomWindow();
protected:
void paintEvent(QPaintEvent* event) override;
private:
void initUI(); // 初始化UI
QWidget* contentWidget; // 主内容区域(圆角+阴影载体)
};
这里定义了 CustomWindow 类,继承自 QWidget。public 里是构造和析构函数;protected 重载了 paintEvent 事件,后面绘图要用;private 里有个 contentWidget 指针当主内容区,还有个 initUI 方法用来初始化界面。
CustomWindow.cpp
#include "CustomWindow.h"
#include <QGraphicsDropShadowEffect>
#include <QPainter>
#include <QPainterPath>
#include <QVBoxLayout>
#include <QLabel>
CustomWindow::CustomWindow(QWidget* parent)
: QWidget(parent)
{
// 设置无边框窗口
setWindowFlags(Qt::FramelessWindowHint | Qt::Window);
// 启用透明背景(关键!)
setAttribute(Qt::WA_TranslucentBackground);
// 初始大小
resize(600, 400);
// 初始化界面
initUI();
}
构造函数里,先设了无边框窗口的标志,然后 setAttribute (Qt::WA_TranslucentBackground) 是开启透明背景,这样后面的阴影和圆角才能正常显示。
resize 把窗口初始大小设为 600x400,最后调用 initUI 初始化界面。
void CustomWindow::initUI() {
contentWidget = new QWidget(this);
contentWidget->setObjectName("contentWidget");
contentWidget->setStyleSheet("#contentWidget {"
" background-color: white;"
" border-radius: 10px;"
" border: 1px solid #E0E0E0;"
"}");
initUI 方法里,先 new 了个 contentWidget。给它设了个对象名 "contentWidget",后面写样式表好用。样式表里指定了 contentWidget 的背景是白色,边框圆角 10 像素,还有个 1 像素的浅灰色边框,这样圆角效果就有了。
// 添加阴影
QGraphicsDropShadowEffect* shadow = new QGraphicsDropShadowEffect(this);
shadow->setBlurRadius(20);
shadow->setOffset(0, 0);
shadow->setColor(QColor(0, 0, 0, 80));
contentWidget->setGraphicsEffect(shadow);
这部分是加阴影。new 了个 QGraphicsDropShadowEffect 对象,setBlurRadius (20) 是说阴影的模糊半径 20 像素,看着更柔和;setOffset (0,0) 是阴影偏移量,这儿设成不偏移;setColor 是阴影颜色,用了半透明的黑色。最后把这阴影效果设给 contentWidget。
auto label = new QLabel("窗口阴影与圆角", contentWidget);
label->setAlignment(Qt::AlignCenter);
label->setStyleSheet("font-size: 24px;");
// 布局
QVBoxLayout* layout = new QVBoxLayout(contentWidget);
layout->addWidget(label);
QVBoxLayout* mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(10, 10, 10, 10); // 阴影边距
mainLayout->addWidget(contentWidget);
}
这里建了个标签 label,显示 "窗口阴影与圆角",设成居中对齐,字体大小 24 像素。然后用 QVBoxLayout 给 contentWidget 搞了布局,把 label 加进去。外面又弄了个 mainLayout 当整个窗口的布局,setContentsMargins 设了 10 像素的边距,给阴影留地方,最后把 contentWidget 加进去。
void CustomWindow::paintEvent(QPaintEvent* event) {
// 绘制透明背景
QPainter painter(this);
painter.fillRect(rect(), Qt::transparent);
}
paintEvent 里用 QPainter 把窗口背景画成透明的,避免出现不该有的底色,保证阴影能正常显示。
CustomWindow::~CustomWindow()
{}
析构函数就空着,没啥特殊要处理的。
运行效果

总结
通过以上实践,我们构建了一个功能完备、结构清晰、视觉现代的 Qt 无边框窗口框架。它不仅突破了传统 Qt 窗口的样式限制,也为开发专业级桌面应用提供了坚实的基础。
该方案适用于登录界面、主程序窗口、弹窗、设置面板等多种场景,具备良好的工程价值和扩展潜力。