前言
为什么去边框?
Qt 默认创建的窗口使用的是操作系统提供的装饰风格(包括边框、标题栏、系统按钮等)。这些边框在外观、交互和布局方面往往受限,无法满足现代 UI 设计需求。因此,我们希望手动实现这些行为,从而获得完全的控制权。
目标
实现一个最基本的无边框窗口,具备如下功能:
- 去除系统默认边框和标题栏;
- 实现窗口可移动(鼠标拖动);
- 提供最小化、最大化、关闭的功能接口(初步结构,按钮稍后实现);
核心技术点
setWindowFlags(Qt::FramelessWindowHint):去除系统边框;
重载 mousePressEvent / mouseMoveEvent 实现窗口拖动;
保留最小化、最大化、关闭功能(后续添加按钮控制);
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; //鼠标按下时,记录偏移位置
};
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 grag;");
}
BasicFramelessWindow::~BasicFramelessWindow()
{}
void BasicFramelessWindow::mousePressEvent(QMouseEvent * event)
{
if (event->button() == Qt::LeftButton) {
dragPosition = event->globalPosition().toPoint() - this->frameGeometry().topLeft();
event->accept();
}
}
void BasicFramelessWindow::mouseMoveEvent(QMouseEvent* event)
{
if (event->buttons() & Qt::LeftButton) {
move(event->globalPosition().toPoint() - dragPosition);
event->accept();
}
}
效果展示
没错,一个空白界面。但是由于重写了mousePressEvent和mouseMoveEvent,可以通过鼠标去拖动窗口移动,现在最简单的无边框窗口雏形就完成了。
2 自定义标题栏与按钮样式
目标
| 功能 | 描述 |
|---|---|
| 自定义标题栏 | 替换系统默认标题栏 |
| 三个按钮 | 最小化、最大化/还原、关闭 |
| 动态切换样式 | 最大化状态改变按钮图标 |
| 支持拖动/双击最大化 | 拖动窗口、双击切换窗口状态 |
添加自定义标题栏组件
TitleBar.h
#pragma once
#include <QtWidgets/QWidget>
#include<qpushbutton.h>
#include<qlabel.h>
#include<QHBoxLayout>
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.cpp
#include "TitleBar.h"
#include<QMouseEvent>
#include<qstyle.h>
#include<qapplication.h>
#include<qlist.h>
TitleBar::TitleBar(QWidget* parent) : QWidget(parent)
{
isMaximized = false;
setFixedHeight(35);
setAttribute(Qt::WA_StyledBackground, true);//启用样式表主题渲染
setStyleSheet("TitleBar{background-color:rgb(223,235,250);}"
"QPushButton {"
" border: none;" // 去掉边框
" background-color: transparent;" // 透明背景
"}"
"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;");
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(); //双击切换最大化状态
}
整合到主窗口
BasicFramelessWindow.h
#pragma once
#include <QtWidgets/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; //鼠标按下时,记录偏移位置
};
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 grag;");
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()
{}
void BasicFramelessWindow::mousePressEvent(QMouseEvent * event)
{
if (event->button() == Qt::LeftButton) {
dragPosition = event->globalPosition().toPoint() - this->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();
}
效果展示
3 窗口边框拖动缩放功能(鼠标拉伸)
目标
实现像普通窗口一样,通过拖动边缘或角落来改变窗口大小,支持:
- 上、下、左、右边缘拖动
- 四个角(左上、右上、左下、右下)拖动
| 功能 | 说明 |
|---|---|
| 捕捉鼠标位置 | 判断是否在边缘或角落 |
| 改变鼠标形状 | 显示为 ↔、↕、↘ 等缩放光标 |
| 响应鼠标拖动 | 动态调整窗口大小 |
| 限制最小尺寸 | 避免窗口缩到看不见 |
代码实现
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.cpp
#include "CustomWindow.h"
CustomWindow::CustomWindow(QWidget* parent):QWidget(parent)
{
setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint); //无边框
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);
}
//结束拖动
void CustomWindow::mouseReleaseEvent(QMouseEvent* event)
{
isResizing = false;
QWidget::mouseReleaseEvent(event);
}
//设置光标或拖动缩放
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);
}
//鼠标离开窗口,取消高亮
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;
}
整合代码逻辑
由于是单独定义的类,要在BasicFramelessWindow中使用CustomWindow的窗口缩放功能,需要整合逻辑。下面是具体实现:
修改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;
};
修改BasicFramelessWindow.cpp
移除原有的鼠标事件处理,调整构造函数:
BasicFramelessWindow::BasicFramelessWindow(QWidget *parent)
: CustomWindow(parent),isMaximized(false)
将BasicFramelessWindow.cpp中setFixedSize()函数改为使用resize()
整合完毕,窗口的鼠标拉伸功能就完全实现了。
4 无边框窗口的圆角与阴影效果
由于在现有程序中去设计效果需要更改地方很多,因此,我们创建一个新项目去实现该效果。
代码示例
CustomWindow.h
#pragma once
#include <QtWidgets/QWidget>
class CustomWindow : public QWidget
{
Q_OBJECT
public:
CustomWindow(QWidget *parent = nullptr);
~CustomWindow();
protected:
void paintEvent(QPaintEvent* event) override;
private:
QWidget* contentWidget; // 主内容区
void initUI();
};
CustomWindow.cpp
#include "CustomWindow.h"
#include <QGraphicsDropShadowEffect>
#include <QPainter>
#include <QPainterPath>
#include <QVBoxLayout>
#include<qlabel.h>
CustomWindow::CustomWindow(QWidget* parent)
: QWidget(parent) {
setWindowFlags(Qt::FramelessWindowHint | Qt::Window);
setAttribute(Qt::WA_TranslucentBackground); // 支持透明背景
resize(600, 400);
initUI();
}
void CustomWindow::initUI() {
contentWidget = new QWidget(this);
contentWidget->setObjectName("contentWidget");
contentWidget->setStyleSheet("#contentWidget {"
" background-color: white;"
" border-radius: 10px;"
" border: 1px solid #E0E0E0;"
"}");
// 添加阴影
QGraphicsDropShadowEffect* shadow = new QGraphicsDropShadowEffect(this);
shadow->setBlurRadius(20);
shadow->setOffset(0, 0);
shadow->setColor(QColor(0, 0, 0, 80));
contentWidget->setGraphicsEffect(shadow);
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);
}
void CustomWindow::paintEvent(QPaintEvent* event) {
// 绘制透明背景
QPainter painter(this);
painter.fillRect(rect(), Qt::transparent);
}
CustomWindow::~CustomWindow()
{}
效果展示
总结
本文从 Qt 默认窗口的局限出发,探讨如何脱离系统窗口样式,构建自定义的无边框窗口界面。我们主要完成了以下几个方面的工作:
- 基础无边框窗口实现
使用
Qt::FramelessWindowHint去除原生边框,并设置透明背景以支持自绘界面。 - 添加自定义标题栏及按钮 模拟常见应用布局,自定义最小化、最大化/还原、关闭按钮,支持最大化状态样式切换、标题栏双击行为等。
- 实现窗口拖动与拉伸功能
通过重写
mousePressEvent/mouseMoveEvent或使用nativeEvent,实现窗口边缘拉伸和任意区域拖动。 - 圆角与阴影效果
使用
paintEvent绘制圆角背景、搭配QGraphicsDropShadowEffect实现视觉上的柔和阴影,提升整体美观度。