在使用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);
}